I am trying to add an event listener on the HTML element show belown, generated dynamically within a forEach loop. I'm getting the proper response from the JSON Placeholder API initially, however, none of my attempts to bind an event on any of the HTML elements I generate after the initial response seem to produce result when I try out the event.
I'm either getting no 'Clicked' at all or ... addEventListener
is not a function, depending on where I put the addEventListener
code.
Is there any way to make this possible in a clean and reusable way? Also, is this the preferred way of generating dynamic HTML through vanilla JS? Or should I generate it manually, by using document.createElement()
and appendChild()
for example?
Ultimately, I am meaning to pass the ID of the clicked target to the getPosts
function fetch URL.
Any help would be greatly appreciated!
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data => createResult(data));
function createResult(data) {
const container = document.getElementById('result');
data.forEach((user) => {
const { id, name, email, address: { city, street } } = user;
let result =
`<div class="user" data-uid=${id}>
<h5 id="user-${id}"> User ID: ${id} </h5>
<ul class="w3-ul">
<li> User Full Name : ${name}</li>
<li> User Email : ${email} </li>
<li> User Address : ${city}, ${street} </li>
</ul>
</div>`;
container.innerHTML += result;
document.body.addEventListener("click", function (e) {
if (e.target &&
e.target.classList.contains("user")) {
console.log('Clicked');
}
});
});
}
function getPosts(e) {
fetch(`https://jsonplaceholder.typicode.com/users/${user.id}`)
.then(res => res.json())
.then(data => console.log(data));
}
<div id=result></div>
There are two problems with your approach. The first problem is that you add the click
handler to document.body
for every element in data
.
If you call createResult()
only once then move the .addEventListener()
call before or after the .forEach()
container.addEventListener("click", ...);
data.forEach(...);
If you call createResult()
multiple times then move the .addEventListener()
out of it and maybe call it on DOMContentLoaded
window.addEventListener("DOMContentLoaded", () => {
document.getElementById('result').addEventListener("click", ...);
});
The second problem is e.target
. e.target
is the element that triggered the click
event. And this most likely wont be div.result
but an element in div.result
. You have to traverse up the DOM until you find div.result
. A possible way can be to use Element.closest()
or a loop and Node.parentElement
.
fakeFetch().then(createResult);
function createResult(data) {
const container = document.getElementById('result');
container.addEventListener("click", function (e) {
const user = e.target.closest(".user");
if (user) {
getPosts(user.dataset.uid);
}
});
data.forEach((user) => {
const { id, name, email, address: { city, street } } = user;
let result =
`<div class="user" data-uid="${id}">
<h5 id="user-${id}"> User ID: ${id} </h5>
<ul class="w3-ul">
<li> User Full Name : ${name}</li>
<li> User Email : ${email} </li>
<li> User Address : ${city}, ${street} </li>
</ul>
</div>`;
container.innerHTML += result;
});
}
function getPosts(userId) {
console.log(userId);
}
function fakeFetch() {
return new Promise(resolve => {
const user = [
{ id: 1, name: "a", mail: "[email protected]", address: { city: "a", street: "a" } },
{ id: 2, name: "b", mail: "[email protected]", address: { city: "b", street: "b" } }
];
setTimeout(resolve(user), 1000);
})
}
<div id="result"></div>
And as a second note:
Don't use .innerHTML
. This will overwrite the content of the element and therefor remove any event handlers. Use Element.insertAdjacentHTML()
instead.
container.innerHTML += result
would then be
container.insertAdjacentHTML("beforeend", result);
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments