Reputation: 1986
I have created this very common scenario of menu links, in a native vanilla JS web-component format.
<side-nav>
<nav-item id="1">1</nav-item>
<nav-item id="2">2</nav-item>
<nav-item id="3">3</nav-item>
<nav-item id="4">4</nav-item>
</side-nav>
Naturally, when I click on any of nav-item, I need to
With web-components I have observed that I can handle that in two ways.
Method 1:
<nav-item>
gets clicked<side-nav>
<nav-item>
components of side-nav and deactivate an already active <nav-item>
if it is not the event.target node.code for <nav-item>
async connectedCallback() {
this.addEventListener('click' , (event) => {
this.setAttribute('selected', true);// immediately mark current component selected
this.dispatchEvent(new CustomEvent('navitem-selected', { bubbles: true, composed: true , detail: { id: this.id} })); // dispatch event, so that parent can loop and deselect other items
});
}
and code for the parent <side-nav>
async connectedCallback() {
this.addEventListener('navitem-selected', (event) => {
let items = this.shadowRoot.querySelector('slot').assignedElements();
items.forEach((item) => {
if(item.getAttribute('id') !== event.detail.id) {
item.removeAttribute('selected');
}
});
});
}
Method 2:
Completely ignore parent and handle the activation logic only in the <nav-item>
static get observedAttributes() {
return ['id', 'selected'];
}
attributeChangedCallback(name, oldValue, newValue) {
if(name == 'id'){
this.id = newValue;
}
if(name == 'selected')
{
if(newValue){
this.shadowRoot.querySelector('.root').classList.add('selected');
} else {
this.shadowRoot.querySelector('.root').classList.remove('selected');
}
}
}
async connectedCallback() {
this.addEventListener('click' , (event) => {
this.dispatchEvent(new CustomEvent('navitem-selected', { bubbles: true, composed: true , detail: { id: this.id} }));
});
const hostNode = this.shadowRoot.querySelector('.root').getRootNode().host.parentNode;
hostNode.addEventListener('navitem-selected', (event) => {
if(this.id == event.detail.id){
this.setAttribute('selected', true);
} else if(this.hasAttribute('selected')){
this.removeAttribute('selected');
}
});
}
Both methods work fine but for the life of me I can not figure out which one is better when considering good programming practices and performance.
I have a general concept that child should not know about siblings or parents, but parents should manage children. I am bit blur on the best practices here and would love to be pulled to light.
Upvotes: 3
Views: 1609
Reputation: 10975
Think of this like you would if it were a set of standard components. Most standard components do not know anything about any other components. So it is the job of a parent component to listen to the events from the child components and make changes to all of the children, as needed.
The only odd-ball component is the <label>
component with the for
attribute. You set the for
attribute to the id
of the component that will receive focus when you click on the <label>
. The label that has a click
event handler and, if there is a for
attribute it looks for a component with that id
. If it finds that component then it calls the .focus()
function on that component.
Doing this your components are not tied together but can connect together.
If I were writing the code I would do it the first way.
Upvotes: 1