I have two components where one is the parent component and the other is the child component. Now the parent has two children within it. which child click for add a style in it, i would require to remove the style in other children. so at a time only one child will keep the style. how to do this?
LiveDemo - click on the button. I am not able to remove the style back.
here is my code :
class Parent extends HTMLElement {
shadowRoot;
constructor(){
super();
this.shadowRoot = this.attachShadow({mode: 'open'});
}
connectedCallback(){
this.render();
}
render() {
this.shadowRoot.innerHTML = `<div>
<children-holder></children-holder>
<children-holder></children-holder>
<children-holder></children-holder>
</div>`
}
}
customElements.define('parent-holder', Parent);
class Children extends HTMLElement {
shadowRoot;
constructor(){
super()
this.shadowRoot = this.attachShadow({mode: 'open'});
}
connectedCallback(){
this.render();
this.shadowRoot.querySelector('button').addEventListener('click', () => {
this.shadowRoot.querySelector('button').style.border = "";
this.shadowRoot.querySelector('button').style.border = "3px solid red";
})
}
render() {
this.shadowRoot.innerHTML = `
<div><button class="button">Click me!</button></div>`
}
}
customElements.define('children-holder', Children);
If you make Custom Element children access a parentNode, and loop its DOM elements..
You are creating a dependency between components
evt.target
will be the button clickedclass Parent extends HTMLElement {
constructor() {
super()
.attachShadow({mode: 'open'})
.shadowRoot.innerHTML = `<div>` +
`<children-holder></children-holder>`.repeat(3) +
`</div>`
}
connectedCallback() {
this.shadowRoot.addEventListener('click', evt => {
if (evt.target.nodeName === 'CHILDREN-HOLDER')
document.dispatchEvent(new CustomEvent('myStateEvent', {
detail: evt.target // THE BUTTON CLICKED
}));
});
}
}
customElements.define('parent-holder', Parent);
class Children extends HTMLElement {
constructor() {
super()
this.attachShadow({mode: 'open'});
}
connectedCallback() {
this.shadowRoot.innerHTML = `<div><button class="button">Click me!</button></div>`;
document.addEventListener('myStateEvent', evt => {
let IwasClicked = evt.detail === this;
this.shadowRoot.querySelector('button').style.border = IwasClicked ? "3px solid red" : ""
});
}
}
customElements.define('children-holder', Children);
Notes
dispatch and listen are both on the document
, you can attach them anywhere
events bubble UP, not down
the default events like click
bubble out of shadow DOM
Custom Events require composed:true
read: https://developer.mozilla.org/en-US/docs/Web/API/Event/Event
I did the dispatch in the Parent for clearity (A DEPENDENCY!)
It might be better to make the Child do the dispatchEvent, So it becomes:
Yo! everyone listening! I was clicked, WE ALL do whatever WE need to do
And keep all logic in one component:
connectedCallback() {
let root = this.shadowRoot;
let eventName = "myStateEvent";
root.innerHTML = `<div><button class="button">Click me!</button></div>`;
document.addEventListener(eventName, evt => {
let button = root.querySelector("button");
button.style.border = evt.detail === button ? "3px solid red" : "";
});
root.addEventListener("click", evt =>
document.dispatchEvent(
new CustomEvent(eventName, {
detail: evt.target // THE BUTTON CLICKED
})
)
);
}
And you might now ask: Why not use the click
event?
That is possible once you understand that event.target
is NOT what you might think it is.
When events originate from shadow DOM, the event.target
value is the last shadowDOM it pierced
So your button click sets different event.target
values:
Listener on <children-holder> event.target = button
Listener on <parent-holder> event.target = <children-holder>
Listener on document event.target = <parent-holder>
To solve your Button-Select-Color use-case with one click
event
the button click is the dispatcher, sending a click event UP the DOM,
through all shadowDOM boundaries
You have to check the event.composedPath()
function which retuns an Array of ALL DOM elements the Event passed.
(note: event.path
is Chrome only!!)
connectedCallback() {
let root = this.shadowRoot;
root.innerHTML = `<div><button>Click me!</button></div>`;
root.host.getRootNode().addEventListener("click", evt => {
let button = root.querySelector("button");
button.style.border = evt.composedPath().includes(button) ? "3px solid red" : "";
});
}
Notes
root.host.getRootNode()
allows one selected button per parent Componentdocument
and it is one button per pageevt.composedPath().includes(root)
identifies the child-component