I've got a child web component (exercise-input
) with a custom event that's sending some data over to my parent web component (muscle-group
) it's nested in. The issue is that when I add additional instances of my parent component to the page, the button in the child component that updates the value in the parent component will update that value on every instance of the parent component.
I used document.dispatchEvent(numSetsDecrease)
in the child and document.addEventListener
in the parent - is there something I need to replace "document" with? Do I need to stop propagation somehow?
The child component:
const exerciseInputTemplate = document.createElement('template');
exerciseInputTemplate.innerHTML = `
<div class="exercises">
<hr>
<div class="exerciseSetsRepRange">
<div class="sideBySide">
<input type="text" placeholder="Exercise">
<div class="numSetsBtns">
<button class="subtractSetBtn">-</button>
<span class="numSets"></span>
<button class="addSetBtn">+</button>
</div>
</div>
<div class="sideBySide">
<input type="number" placeholder="Rep Range Low" min="1">
<input type="number" placeholder="Rep Range High" min="1">
</div>
</div>
</div>
`;
class ExerciseInput extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open'});
this.shadowRoot.appendChild(exerciseInputTemplate.content.cloneNode(true));
}
connectedCallback() {
const subtractSetBtn = this.shadowRoot.querySelector('.subtractSetBtn');
const numSets = this.shadowRoot.querySelector('.numSets');
const addSetBtn = this.shadowRoot.querySelector('.addSetBtn');
numSets.textContent = 3 + ' sets';
// Click listner for subtract set btn with nested custom event
subtractSetBtn.addEventListener('click', function(event){
let numSetsInt = parseInt(numSets.textContent);
numSetsInt -= 1;
numSets.textContent = numSetsInt + ' sets';
if (parseInt(numSets.textContent) === 0) {
subtractSetBtn.disabled = true;
}
// Custom event sending -1 to the parent
const numSetsDecrease = new CustomEvent('numSetsDecrease', {
bubbles: false,
detail: {
numSetValue: -1
}
});
document.dispatchEvent(numSetsDecrease);
});
// Click listner for add set btn with nested custom event
addSetBtn.addEventListener('click', function(event){
let numSetsInt = parseInt(numSets.textContent);
numSetsInt += 1;
numSets.textContent = numSetsInt + ' sets';
if (parseInt(numSets.textContent) > 0) {
subtractSetBtn.disabled = false;
}
// Custom event sending +1 to the parent
const numSetsIncrease = new CustomEvent('numSetsIncrease', {
bubbles: false,
detail: {
numSetValue: 1
}
});
document.dispatchEvent(numSetsIncrease);
});
}
}
window.customElements.define('exercise-input', ExerciseInput);
The parent component:
const muscleGroupTemplate = document.createElement('template');
muscleGroupTemplate.innerHTML = `
<form class="programBuilder">
<div class="inputDiv muscleGroupDiv">
<input id="muscleGroupInput" name="muscleGroupInput" list="muscleGroupsList" placeholder="Muscle Group">
<datalist id="muscleGroupsList">
</datalist>
</div>
<div class="inputDiv setsDiv">
<label for="setsRange" class="setsValue"></label>
<input type="range" min="1" max="30" class="setsRange" value="15">
<p class="numSetsOutput"></p>
</div>
<exercise-input></exercise-input>
<exercise-input></exercise-input>
<exercise-input></exercise-input>
<button class="addExerciseBtn">Add Exercise</button>
</form>
`;
class MuscleGroup extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open'});
this.shadowRoot.appendChild(muscleGroupTemplate.content.cloneNode(true));
}
connectedCallback() {
// Updating the number sets output with data from the child elements
const numSetsOutput = this.shadowRoot.querySelector('.numSetsOutput');
numSetsOutput.textContent = '9 total sets';
document.addEventListener('numSetsDecrease', function(event){
let numSetsOutputValue = parseInt(numSetsOutput.textContent);
numSetsOutputValue += event.detail.numSetValue;
numSetsOutput.textContent = `${numSetsOutputValue} total sets`;
});
document.addEventListener('numSetsIncrease', function(event){
let numSetsOutputValue = parseInt(numSetsOutput.textContent);
numSetsOutputValue += event.detail.numSetValue;
numSetsOutput.textContent = `${numSetsOutputValue} total sets`;
});
}
}
window.customElements.define('muscle-group', MuscleGroup);
Any help would be greatly appreciated! I've been trying to figure this out all day.
An Event bubbles up the DOM.
https://javascript.info/bubbling-and-capturing
document.addEventListener
captures all named Events from its children
Instead you:
bubbles:true
and composed:true
to cross shadowRoots<muscle-group>
then captures the bubbling Events and processes themstopPropagation
, Events bubbling on to document
do no harmPS. Your <exercise-iput>
has 2 buttons which do the same, one adds, one substracts.
The moment you copy/pasted that first button code, a "This Needs A Component" alarm should have warned you to create a <exercise-input-button add="-1">