Why is the eventListener not being applied to the button element?

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.

  • 2

    sigh. ChatGPT is not a computer programmer.

    – 

  • 1

    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 do warriorArticleDiv.appendChild(buttonContainer)

    – 

  • 1

    adding buttonContainer.outerHTML to warriorsContainer.innerHTML creates new elements, so the listeners added by addEventListener are not included

    – 

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:

  1. Create the buttonElement and attach an event listener to it.
  2. Append this buttonElement to the buttonContainer.
  3. 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.

Leave a Comment