Currently developing a shop-like web page for a school task and I am running into a problem where the eventListeners seem to not be applied to the button elements.
I’m running a forEach
loop on an array containing data on the items that you can buy in the shop. Basically, every object
in array
> print article with object data to the container, along with adding an eventListener to the button which is supposed to pass along data to a handleBuyEvent function to further check if the user has enough money/resources etc. etc.
The issue is when the articles are printed to the web page (no errors in the console) nothing happens when I click the buttons, and when I inspect the button elements, under eventListeners it says no listeners…
import WarriorsModule from "./modules/warriorsModule.js";
import InventoryModule from "./modules/inventoryModule.js";
const warriors = WarriorsModule().getAll(); // returns array with objects containing data
const inventory = InventoryModule();
const warriorsContainer = document.querySelector("#warriorsContainer");
const ironResources = document.querySelector("#ironResource");
const coinResources = document.querySelector("#coinResource");
const woodResources = document.querySelector("#woodResource");
// Updates current resources on screen.
const updateResources = () => {
ironResources.innerHTML = inventory.getIronBalance();
coinResources.innerHTML = inventory.getCoinBalance();
woodResources.innerHTML = inventory.getWoodBalance();
}
updateResources();
const handleBuyEvent = (category, price, img) => {
if (inventory.getCoinBalance() >= price) {
inventory.addToArmy({
category: `${category}`,
price: `${price}`,
img: `${img}`
});
inventory.removeCoins(price);
updateResources();
} else {
alert(
`You are missing: ${
(price - inventory.getCoinBalance())
} coins to buy this unit.`
);
}
};
let handleClickEvent = (category, price, img) => {
handleBuyEvent(category, price, img);
};
const printWarriors = () => {
// This id is being used to identify the different buttons and containers in each article
let id = 0;
warriors.forEach((object) => {
const buttonContainer = document.createElement("div");
buttonContainer.setAttribute("id", `buttonContainer${id}`);
// Create the button element and set up its properties
const buttonElement = document.createElement("button");
buttonElement.setAttribute("id", `buyButton${id}`);
buttonElement.setAttribute("class", "btn btn-success m-4");
buttonElement.innerHTML = `
Buy ${object.category} Warrior - ${object.price}
`;
const category = object.category;
const price = object.price;
const img = object.img;
buttonElement.addEventListener('click', () => {
handleClickEvent(category, price, img);
});
buttonContainer.appendChild(buttonElement);
// Prints article to website
warriorsContainer.innerHTML += `
<article class="col-sm-12 col-md-6 col-lg-4">
<div class="warriorArticle shadow rounded d-flex flex-column align-items-center">
<h3 class="m-4 fs-2" >${object.category}</h3>
<div class="row">
<img class="categoryImage" src="${object.img}" alt="${object.category}-img"/>
</div>
${buttonContainer.outerHTML}
</div>
</article>
`;
// Increments the ID so that on the next iteration the ID set is different.
id += 1;
});
};
printWarriors();
I have tried asking chatGPT 100 times but it doesn’t seem to find the issue. I read through the code to see if there was a mistake somewhere regarding getting the ID for certain elements in case that is why it was never applied.
At this point, I have no idea why it won’t work and at the same time gives no errors AND everything else is printed and shown as expected.
It’s because you added buttonContainer
using warriorsContainer.innerHTML
and buttonContainer.outerHTML
. If you want to add the actual button along with its event listeners to the DOM, you need to truly insert the element using something like appendChild()
or insertAdjacentElement()
.
you can use appendChild instead, delete ${buttonContainer.outerHTML}, and add an onload event like this
warriorsContainer.onload = () => {
warriorsContainer.querySelector('.warriorArticle').appendChild(buttonContainer)
}
Your issue arises from the order in which you are executing operations. When you create the buttonElement
and attach an event listener to it, the element exists in memory but has not been attached to the DOM
yet. However, when you later append the entire article
to warriorsContainer
using innerHTML
, it does not copy the event listeners.
Here’s a breakdown of my understanding of the task:
- Create the
buttonElement
and attach an event listener to it. - Append this buttonElement to the
buttonContainer
. - Append this
buttonContainer
to the warriorsContainer using innerHTML +=.
That said, using innerHTML +=
does not copy over the event listeners from the in-memory version of the element. To resolve this, you need to attach the created DOM nodes directly to the warriorsContainer without converting them to strings with innerHTML.
Here’s a revised version of the printWarriors
function to fix this:
const printWarriors = () => {
let id = 0;
warriors.forEach((object) => {
const warriorArticle = document.createElement("div");
warriorArticle.className = "warriorArticle shadow rounded d-flex flex-column align-items-center";
const header = document.createElement("h3");
header.className = "m-4 fs-2";
header.textContent = object.category;
const imgDiv = document.createElement("div");
imgDiv.className = "row";
const imgElement = document.createElement("img");
imgElement.className = "categoryImage";
imgElement.src = object.img;
imgElement.alt = `${object.category}-img`;
imgDiv.appendChild(imgElement);
const buttonElement = document.createElement("button");
buttonElement.id = `buyButton${id}`;
buttonElement.className = "btn btn-success m-4";
buttonElement.textContent = `Buy ${object.category} Warrior - ${object.price}`;
buttonElement.addEventListener('click', () => {
handleClickEvent(object.category, object.price, object.img);
});
warriorArticle.appendChild(header);
warriorArticle.appendChild(imgDiv);
warriorArticle.appendChild(buttonElement);
const article = document.createElement("article");
article.className = "col-sm-12 col-md-6 col-lg-4";
article.appendChild(warriorArticle);
warriorsContainer.appendChild(article);
id += 1;
});
};
By using the DOM
methods (createElement, appendChild, etc.)
and not using innerHTML
for appending dynamic content, the event listeners attached to elements remain intact.
sigh. ChatGPT is not a computer programmer.
Once you’ve converted
buttonContainer
to a string that string no longer has any relationship with the element. Therefore any event listener it had is gone. Instead dowarriorArticleDiv.appendChild(buttonContainer)
adding
buttonContainer.outerHTML
towarriorsContainer.innerHTML
creates new elements, so the listeners added byaddEventListener
are not included