When searching how to attach an event listener to a dynamically loaded div the accepted answer seems to be event delegation.
I did not find another suggested way when HTML is added with insertAdjacentHTML
.
I am adding divs to the DOM like follows:
document.getElementById('container').insertAdjacentHTML(
'beforeend', `<div >
<div >Card header content</div>
<div >
<h2>Card body</h2>
<select>
<option value="value1">Value 1</option>
</select><br>
Some more text
</div>
</div>`
);
// Event delegation
document.addEventListener('click', function (e) {
console.log(e.target.className);
});
.card{
background: rosybrown;
text-align: center;
width: 50%;
}
h2{
margin-top: 0;
}
<div id="container">
</div>
I hope you can see the console output when clicking anywhere on the card.
Event delegation how I know it works by attaching the click event on a "big" parent element like document
and then comparing attributes like the className
of the clicked element and making something if it matches the intended class name.
The issue is that this event.target
targets the precise clicked elements inside the card, but I can't do much with that info, can I? I need the whole card (a parent of those elements) to be able to make the right action for the user (in this case, redirect to a more detailed view of what the card previewed).
A dirty possible solution, I thought, would be wrapping the div with an <a>
-tag, but then the select
and other buttons / interactive elements of the card wouldn't work, and the user would just be directed to the link.
CodePudding user response:
You can use Element.closest
to retrieve the element you need.
document.getElementById('container').insertAdjacentHTML(
'beforeend', `<div >
<div >Card header content</div>
<div >
<h2>Card body</h2>
<select>
<option value="value1">Value 1</option>
</select><br>
Some more text
</div>
</div>`
);
// Event delegation
document.addEventListener('click', function (e) {
console.clear();
console.log(e.target.closest(`.card`));
});
.card{
background: rosybrown;
text-align: center;
width: 50%;
}
h2{
margin-top: 0;
}
<div id="container">
</div>
CodePudding user response:
You can try create the cards and assign listener to them in this way, then use this
instead of e.target
:
const cardElement = document.createElement('div')
cardElement.classList.add('card')
cardElement.insertAdjacentHTML(
'beforeend', `
<div >Card header content</div>
<div >
<h2>Card body</h2>
<select>
<option value="value1">Value 1</option>
</select><br>
Some more text
</div>`)
document.getElementById('container').appendChild(cardElement);
// Event delegation
cardElement.addEventListener('click', function (e) {
console.clear()
console.log(this);
});
.card{
background: rosybrown;
text-align: center;
width: 50%;
}
h2{
margin-top: 0;
}
<div id="container">
</div>
CodePudding user response:
I'm just expanding a bit on @KooiInc's answer, all credit and upvotes should go to KooiInc... .closest()
is a great tool for event delegation.
I think the concept can be demonstrated more clearly by putting in a second card. I've added a cardId
so it can be clearly seen in the console output which card was found.
I also moved the delegation from document
to the #container
— you handle fewer click events if you push the delegation listener down to the closest element that contains the objects you are interested in.
const container = document.getElementById('container');
let cardId = 1;
// Add the 1st card
container.insertAdjacentHTML(
'beforeend', `<div id="${cardId}">
<div >Card header content</div>
<div >
<h2>Card ${cardId} Body</h2>
<select>
<option value="value${cardId}">Value ${cardId}</option>
</select><br>
Some more text
</div>
</div>`
);
// Add a 2nd card
cardId;
container.insertAdjacentHTML(
'beforeend', `<div id="${cardId}">
<div >Card header content</div>
<div >
<h2>Card ${cardId} Body</h2>
<select>
<option value="value${cardId}">Value ${cardId}</option>
</select><br>
Some more text
</div>
</div>`
);
// Event delegation
container.addEventListener('click', function (e) {
console.clear();
console.log(e.target.closest(`.card`));
});
.card{
background: rosybrown;
text-align: center;
width: 50%;
}
.card .card {
margin-top: 1rem;
}
h2{
margin-top: 0;
}
<div id="container">
</div>
In real life I would likely be putting most of this in a addCard()
function.