Not able to bind onchange event to lit-flatpickr element - javascript

import 'lit-flatpickr';
import { html, LitElement } from 'lit-element';
class MyApp extends LitElement {
getValue() {
this.shadowRoot.querySelector('#my-date-picker').getValue();
}
getSelectedDate(){
console.log('selected date');
}
render() {
return html<lit-flatpickr id="my-date-picker" altInput altFormat="F j, Y" dateFormat="Y-m-d" theme="material_orange" minDate="2020-01" maxDate="2020-12-31" #change="${this.getSelectedDate}" ></lit-flatpickr>;
}
}
getSelectedDate is not triggering at all. Can you help us how invoke hooks and methods of lit-flatpickr?
https://github.com/Matsuuu/lit-flatpickr

Try this:
(async function() {
await import('https://unpkg.com/lit-flatpickr?module');
const { html, css, LitElement } = await import('https://unpkg.com/lit?module');
class MyApp extends LitElement {
static get styles() {
return css`
lit-flatpickr {
background: pink;
}
`;
}
get picker() {
return this.shadowRoot.querySelector('#my-date-picker')
}
getValue() {
this.picker.getValue();
}
getSelectedDates(e) {
console.log(e);
}
render() {
return html `
<lit-flatpickr id="my-date-picker"
altInput
allowInput
altFormat="F j, Y"
dateFormat="Y-m-d"
theme="material_orange"
minDate="2020-01"
maxDate="2020-12-31"
.onChange="${this.getSelectedDates}"
></lit-flatpickr>
`;
}
}
customElements.define('my-app', MyApp);
})();
<my-app id="app"></my-app>
<lit-flatpickr> (at least the version i got as of this writing) doesn't have any DOM events, you have to pass these custom on* functions instead.

Related

Storybook with web components - changing arguments dynamically on code

I have a modal component and I'm writing the story for it. It looks something like this:
import { Story, Meta } from '#storybook/html';
export default {
title: 'Components/Modal',
argTypes: {
open: {
name: 'Opened',
control: 'boolean'
},
},
args: {
open: false,
}
} as Meta;
const Template: Story = (args) => {
return `
<my-modal open="${args.open}">
Some example content inside the modal
</my-modal>
`;
};
export const Modal: Story = Template.bind({});
I have the arg open on the controls and I can change its value to true and the modal shows. But I would like the story to have a button and when it's clicked, the modal shows.
I can't find a way to do this in the current version of Storybook for web components.
I've seen there are some hooks available for React (import { useArgs } from '#storybook/api';) that allows you to change the arguments value dynamically but I can't see how to do this for web components?
Any helps will be highly appreciated.
Just add that button to the template:
import { Story, Meta } from '#storybook/html';
export default {
title: 'Components/Modal',
argTypes: {
open: {
name: 'Opened',
control: 'boolean'
},
},
args: {
open: false,
}
} as Meta;
const Template: Story = (args) => {
return `
<button
type="button"
onclick="this.nextElementSibling.open = !this.nextElementSibling.open">
Toggle Modal
</button>
<my-modal .open=${args.open}>
Some example content inside the modal
</my-modal>
`;
};
export const Modal: Story = Template.bind({});
Also, for boolean attributes - if implemented properly -
you should work with the property (prefix it in the template with a .) rather than the attribute.
Doing that with all native code isn't rocket science...
<my-dialog id="DIALOG" open>
Hello *Native* Web Components world!
</my-dialog>
<button onclick="DIALOG.open()">OPEN</button>
<script>
customElements.define("my-dialog", class extends HTMLElement {
static get observedAttributes() {
return ["open"];
}
constructor() {
super() // sets and returns 'this'
.attachShadow({mode:"open"}) // sets and return this.shadowRoot
.innerHTML = `<dialog><slot></slot><button>Close</button></dialog>`;
this.dialog = this.shadowRoot.querySelector("dialog");
}
connectedCallback() {
this.onclick = () => this.close(); // or attach to button
}
attributeChangedCallback(name,oldValue,newValue) {
this.open();
}
open() {
this.dialog.showModal(); // or .show()
}
close() {
this.dialog.close();
}
});
</script>

Reference web component root attribute with CSS

Created a web component with a shadow DOM. When the button is clicked it adds the open attribute to the web component.
I would like to show the hidden div in the CSS when the open is added with CSS styling. Is it possible for the shadow DOM styles to reference attributes on the web component root? Otherwise, I have to add a superfluous class or attribute within the shadow DOM.
class CustomComponent extends HTMLElement {
constructor() {
super();
this.element = this.attachShadow({mode: 'open'});
}
static get observedAttributes() {
return ['open'];
}
attributeChangedCallback(attrName, oldValue, newValue) {
if (newValue !== oldValue) {
this[attrName] = this.hasAttribute(attrName);
}
}
connectedCallback() {
const template = document.getElementById('custom-component');
const node = document.importNode(template.content, true);
this.element.appendChild(node);
this.element.querySelector('button').addEventListener('click', () => {
this.setAttribute('open', '');
});
}
}
customElements.define('custom-component', CustomComponent);
<template id="custom-component">
<style>
div {
display: none;
}
[open] div {
display: block;
}
</style>
<button>Open</button>
<div>Content</div>
</template>
<custom-component></custom-component>
It appears the host CSS pseudo selector is designed to handle this precise situation.
class CustomComponent extends HTMLElement {
constructor() {
super();
this.element = this.attachShadow({mode: 'open'});
}
static get observedAttributes() {
return ['open'];
}
attributeChangedCallback(attrName, oldValue, newValue) {
if (newValue !== oldValue) {
this[attrName] = this.hasAttribute(attrName);
}
}
connectedCallback() {
const template = document.getElementById('custom-component');
const node = document.importNode(template.content, true);
this.element.appendChild(node);
this.element.querySelector('button').addEventListener('click', () => {
this.setAttribute('open', '');
});
}
}
customElements.define('custom-component', CustomComponent);
<template id="custom-component">
<style>
div {
display: none;
}
:host([open]) div {
display: block;
}
</style>
<button>Open</button>
<div>Content</div>
</template>
<custom-component></custom-component>

WebComponents: how to get the resolved value of a slot in shadow DOM?

I have a web component and I want to adjust the value of its slot.
Unfortunately, I am unable to get the resolved value from it.
How can I do that?
const template = document.createElement('template');
template.innerHTML = `
<p><slot></slot></p>
`;
class MyComp extends HTMLElement {
constructor() {
super();
this.root = this.attachShadow({mode: 'open'});
this.root.appendChild(template.content.cloneNode(true));
}
connectedCallback() {
const slot = this.shadowRoot.querySelector('slot');
console.log('VALUE:', slot.innerText); // always empty
}
}
customElements.define('my-comp', MyComp);
<my-comp>abc</my-comp>
You can assign an EventListener to watch for SLOT changes
MDN Documentation
slotchange event
assignedNodes
::slotted
How do I style the last slotted element in a web component
customElements.define('my-element', class extends HTMLElement {
constructor() {
super();
this.attachShadow({
mode: 'open'
}).appendChild(document.getElementById(this.nodeName).content.cloneNode(true));
}
connectedCallback() {
this.listeners = [...this.shadowRoot.querySelectorAll("SLOT")].map(slot => {
let name = "slotchange";
let func = (evt) => {
let nodes = slot.assignedNodes();
console.log(`Slot ${slot.name} changed to ${nodes[0].outerHTML}`)
}
slot.addEventListener(name, func);
return () => slot.removeEventListener(name, func); // return cleanup function!!!
})
}
disconnectedCallback() {
this.listeners.forEach(removeFunc => removeFunc());
}
});
<template id="MY-ELEMENT">
<style>
::slotted(*) {
background: yellow;
margin: 0;
}
::slotted(span) {
color: red;
}
</style>
<b>
<slot name=title></slot>
</b>
<slot name=content></slot>
</template>
<my-element>
<span slot="title">Hello World</span>
<p slot="content">What a wonderful day!</p>
</my-element>
<my-element>
<span slot="title">Hello Tomorrow</span>
<p slot="content">What will you bring?</p>
</my-element>
Less code, easier to understand. For simple templates I'd rather create the elements programmatically, then add a slotchange listener:
class MyComp extends HTMLElement {
p = document.createElement('p');
slot = document.createElement('slot');
constructor() {
super();
this.p.append(this.slot);
this.attachShadow({mode: 'open'}).append(this.p);
this.slot.addEventListener('slotchange', () => console.log('VALUE:', this.slottedValue))
}
get slottedValue() { return this.slot.assignedNodes()[0].wholeText }
}
customElements.define('my-comp', MyComp);
console.log(document.querySelector('my-comp').slottedValue);
<my-comp>abc</my-comp>

How to pass event to a child in LitElement

I want to make a drop-down menu and that when clicking on the input, the menu is displayed with a toggle that removes or places the 'hidden' class
I have this method
toggleMenu() {
this.classList.toggle("hidden");
}
And here the template.
render(){
return html`
<input #click="${this.toggleMenu}" type="button">
<ul class="hidden">
<slot></slot>
</ul>
`;
}
One straightforward solution is to add a property to your custom element, e.g. open, that is toggled in your toggleMenu method:
static get properties() {
return {
open: { type: Boolean },
};
}
constructor() {
super();
this.open = false;
}
toggleMenu() {
this.open = !this.open;
}
Then in your render method set the <ul>'s class attribute based on the value of this.open:
render(){
return html`
<button #click=${this.toggleMenu} type="button">Toggle</button>
<ul class=${this.open ? '' : 'hidden'}>
<slot></slot>
</ul>
`;
}
You can see this working in the below snippet:
// import { LitElement, css, html } from 'lit-element';
const { LitElement, css, html } = litElement;
class DropDownMenu extends LitElement {
static get properties() {
return {
open: { type: Boolean },
};
}
static get styles() {
return css`
ul.hidden {
display: none;
}
`;
}
constructor() {
super();
this.open = false;
}
toggleMenu() {
this.open = !this.open;
}
render(){
return html`
<button #click=${this.toggleMenu} type="button">Toggle</button>
<ul class=${this.open ? '' : 'hidden'}>
<slot></slot>
</ul>
`;
}
}
customElements.define('drop-down-menu', DropDownMenu);
<script src="https://bundle.run/lit-element#2.2.1"></script>
<drop-down-menu>
<li>Item 1</li>
<li>Item 2</li>
</drop-down-menu>
If you want to apply additional classes to the <ul> you'll want to look into the classMap function as described in the LitElement docs.
Alternatively, you can add reflect: true to the open property declaration, which lets you show or hide the <ul> using CSS alone, rather than setting its class in render:
static get properties() {
return {
open: {
type: Boolean,
reflect: true,
},
};
}
static get styles() {
return css`
ul {
display: none;
}
:host([open]) ul {
display: block;
}
`;
}
Here it is in a working snippet:
// import { LitElement, css, html } from 'lit-element';
const { LitElement, css, html } = litElement;
class DropDownMenu extends LitElement {
static get properties() {
return {
open: {
type: Boolean,
reflect: true,
},
};
}
static get styles() {
return css`
ul {
display: none;
}
:host([open]) ul {
display: block;
}
`;
}
constructor() {
super();
this.open = false;
}
toggleMenu() {
this.open = !this.open;
}
render(){
return html`
<button #click=${this.toggleMenu} type="button">Toggle</button>
<ul>
<slot></slot>
</ul>
`;
}
}
customElements.define('drop-down-menu', DropDownMenu);
<script src="https://bundle.run/lit-element#2.2.1"></script>
<drop-down-menu>
<li>Item 1</li>
<li>Item 2</li>
</drop-down-menu>
Both of these are common approaches and the best one for your application will depend on your use case and personal preferences.
I like to keep it simple, if you need a reference to the DOM node then pass the event to the function like the following:
toggleMenu(ev) {
ev.target.classList.toggle("hidden");
}
And for the render method
render(){
return html`
<input #click="${(ev)=>{this.toggleMenu(ev)}}" type="button">
<ul class="hidden">
<slot></slot>
</ul>
`;
}
And you're done

How to reset ViewContainerRef in angular2 after change Detection?

So I am working on this app in which I have used ViewContainerRef along with dynamicComponentLoader like below:
generic.component.ts
export class GenericComponent implements OnInit, OnChanges{
#ViewChild('target', { read: ViewContainerRef }) target;
#Input('input-model') inputModel: any = {};
constructor(private dcl: DynamicComponentLoader) { }
ngAfterViewInit() {
this.dcl.loadNextToLocation(DemoComponent,this.target)
.then(ref => {
if (this.inputModel[this.objAttr] === undefined) {
ref.instance.inputModel = this.inputModel;
} else {
ref.instance.inputModel[this.objAttr] = this.inputModel[this.objAttr];
}
});
console.log('Generic Component :===== DemoComponent===== Loaded');
}
ngOnChanges(changes) {
console.log('ngOnChanges - propertyName = ' + JSON.stringify(changes['inputModel'].currentValue));
this.inputModel=changes['inputModel'].currentValue;
}
}
generic.html
<div #target></div>
So It renders the DemoComponentin target element correctly.
but when I am changing the inputModel then I want to reset the view of target element.
I tried onOnChanges to reset the inputModel , its getting changed correctly but the view is not getting updated for respective change.
So I want to know if is it possible to reset the view inside ngOnChanges after the inputModel is updated?
any inputs?
There is no connection between this.inputModel and ref.instance.inputModel. If one changes you need to copy it again.
For example like:
export class GenericComponent implements OnInit, OnChanges{
componentRef:ComponentRef;
#ViewChild('target', { read: ViewContainerRef }) target;
#Input('input-model') inputModel: any = {};
constructor(private dcl: DynamicComponentLoader) { }
ngAfterViewInit() {
this.dcl.loadNextToLocation(DemoComponent,this.target)
.then(ref => {
this.componentRef = ref;
this.updateModel();
});
console.log('Generic Component :===== DemoComponent===== Loaded');
}
updateModel() {
if(!this.componentRef) return;
if (this.inputModel[this.objAttr] === undefined) {
this.componentRef.instance.inputModel = this.inputModel;
} else {
this.componentRef.instance.inputModel[this.objAttr] = this.inputModel[this.objAttr];
}
}
ngOnChanges(changes) {
console.log('ngOnChanges - propertyName = ' + JSON.stringify(changes['inputModel'].currentValue));
this.inputModel=changes['inputModel'].currentValue;
this.updateModel();
}
}

Categories

Resources