The core issue is due to the req()
function being asynchronous - meaning the req()
function is called but it finishes at some unknown point in the future. While each req()
is waiting to finish the script continues and the addListeners()
function is called, using the .header__flipper
selector - but due to the asynchronous behavior the .header__flipper
elements aren't created yet so the event listeners aren't added.
As a demo, I've added a timeout to the addListeners()
function so it waits 1 second before being called. This gives the req()
functions time to complete and allows the event listeners to attach correctly.
However - setTimeout()
IS NOT the solution - the snippet below is only for demonstrating the issue, scroll down for the correct solution.
{
const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
const container = document.getElementById("container");
const cityTemplate = () => {
const template = `<section class="weather">
<button class="header__flipper"><span aria-hidden="true">→</span></button>
<header class="header">
<h1 class="header__heading">dfgdfgd
</h1>
</header>
</section>`;
return template;
};
const addListeners = (collection, ev, fn) => {
for (let i = 0; i < Array.from(collection).length; i++) {
collection[i].addEventListener(ev, fn, false);
}
}
const req = (id, key) => {
const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
fetch(url).then((res) => {
res.json().then((data) => {
container.innerHTML += cityTemplate(data);
});
})
}
req("6695624", API_KEY);
req("6695624", API_KEY);
req("6695624", API_KEY);
// For Demo Only
// The req() function is asynchronous so the addListeners() function was attempting to attach to the elements before they were created
window.setTimeout(function() {
addListeners(document.getElementsByClassName("header__flipper"), "click", () => {
alert("test");
})
}, 1000)
}
<div id="container"></div>
Solution
The solution is to attach the event listeners to a parent selector (as @Nishad has recommended). The idea is to attach the click event listener to a parent element (like #container
), and within the listener callback function check if the event target is one of the new dynamic elements.
In your case, the addition of the <span class="header__flipper__aria" aria-hidden="true">→</span>
within the button complicates things a bit because the event target could be either the <button>
or the <span>
. This requires us to check if the event target is either of those elements.
{
const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
const container = document.getElementById("container");
const cityTemplate = () => {
const template = `<section class="weather">
<button class="header__flipper"><span class="header__flipper__aria" aria-hidden="true">→</span></button>
<header class="header">
<h1 class="header__heading">dfgdfgd
</h1>
</header>
</section>`;
return template;
};
const addListeners = (collection, ev, fn) => {
collection.addEventListener(ev, fn, false);
}
const req = (id, key) => {
const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
fetch(url).then((res) => {
res.json().then((data) => {
container.innerHTML += cityTemplate(data);
});
})
}
req("6695624", API_KEY);
req("6695624", API_KEY);
req("6695624", API_KEY);
addListeners(document.getElementById("container"), "click", (event) => {
var classes = event.target.classList;
if (classes.contains("header__flipper") || classes.contains("header__flipper__aria")) {
alert("test");
}
})
}
<div id="container"></div>
Alternative Solution
An alternative would be to attach the event listener to the button within each dynamic element in the callback when the dynamic element is created, like this:
{
const API_KEY = "9829fe5eab6c963bbe4850df2d7de4aa";
const container = document.getElementById("container");
const cityTemplate = () => {
const newEl = document.createElement("section");
newEl.classList.add("weather");
const template = `<button class="header__flipper"><span class="header__flipper__aria" aria-hidden="true">→</span></button>
<header class="header">
<h1 class="header__heading">dfgdfgd
</h1>
</header>`;
newEl.innerHTML = template;
return newEl;
};
const req = (id, key) => {
const url = `https://api.openweathermap.org/data/2.5/forecast?id=${id}&APPID=${key}`;
fetch(url).then((res) => {
res.json().then((data) => {
const city = cityTemplate(data);
city.querySelector("button").addEventListener("click", function(){
alert("test");
}, false);
container.appendChild(city);
});
})
}
req("6695624", API_KEY);
req("6695624", API_KEY);
req("6695624", API_KEY);
}
<div id="container"></div>