0
  1. I have trouble grabbing dynamically created DOM elements and
  2. I don't know where the subsequent click event delegation should take place (i.e. in my main .js file or inside the UI class).

This object-oriented learning project sends async HTTP requests using fetch to the Punk API and fetches a list of beers based on user search query. The output is not hard-coded but the innerHTML of the list is dynamically created from the API data inside a class called UI.

In app.js I handle the promises and use ui.paint() to display the beers on the page.

Each beer has a 'buy' button that is also dynamically created.

I want to grab all the buttons, loop through them, attach a click event.

I've tried:

  1. grabbing the buttons in app.js with queryselectorAll - it doesn't work, it returns an empty node list - this selector must have been hoisted and at the time of calling the buttons clearly don't exist yet - IS THERE SOME ASYNC WAY to solve this and wait for the API data to come back and for the new elements to be created? (note: once I have the list of beers, I can grab the full node list with querySelectorAll in the browser console...)

  2. I also tried wrapping the queryselectorAll inside a function and call that function inside the click event function that handles the promises. It did not work either.

  3. I tried putting the event in app.js as well as inside the UI prototype class (which looked neater) but none of them works.

app JS
const ui = new UI();

document.getElementById('submit-button- name').addEventListener('click', (e) => {
e.preventDefault();

const newBeer = new Beer();

newBeer.getByName()
    .then(beerResult => {
        ui.paint(beerResult);
    })
    .catch(err => {
        console.log(err)
    });
});

const buyBtns = document.querySelectorAll('#buy-btn');

buyBtns.forEach((buyBtn) => {
   buyBtn.addEventListener('click', (e) => {
      ui.addToCart(e)
   });  
});
then here is the UI class, trying to pick up the event (I'm trying to grab the button that's in the last <a> tag of the innerHTML and push it in a cart array which I would later display on a separate page). 
class UI {
constructor() {
    this.productRow = document.getElementById('products-row');
} 

paint(beerResult) {
    let output = '';

    beerResult.forEach(beer => {
        output += 
        `<div class="card">
            <img id="beer-card-img" src="${beer.image_url}" alt="beer-image" class="card-image-top">
            <div class="card-body">
                <h5 class="card-title">${beer.name}</h5>
                <h6 class="tagline">${beer.tagline}.</h6>
                <p id="description" class="card-text">${beer.description}</p>
                <p id="abv" class="card-text">ABV: ${beer.abv}</p>
                <p id="ibu" class="card-text">IBU: ${beer.ibu}</p>
                <p id="ph" class="card-text">PH: ${beer.ph}</p>
                <p id="first-brewed" class="card-text">First brewed: ${beer.first_brewed}</p>
                <a href="#" class="btn btn-outline-dark btn-sm">See details</a>
                <a href="#" id="buy-btn" class="btn btn-danger btn-sm">Buy</a>
            </div>
        </div>`
    });

    // console.log(output);

    this.productRow.innerHTML = output;
}

addToCart(e) {

    let cart = [];

    if (e.target.parentElement.parentElement.classList.contains('card')) {
        cart.push(e.target.parentElement.parentElement)
    }

    console.log(cart);
}

}

Right now, nothing happens when I click 'Buy'. What I want to happen is beers being pushed into an array (as objects) to be later displayed on a separate page.

Thanks of your patience, time and advice!

Adam
  • 375
  • 1
  • 4
  • 10

1 Answers1

0
  • It makes more sense to create the event listener when you create the button. So I would put the event listener in the UI class.

  • If you use document.createElement() to create the button, you can attach the handler right away. You don't need querySelector()

  • Since the addToCart function is also in the UI you can call that function and pass the beer object right away

This is a simplified code example:

class UI {

  paint(beerResult) {

    for(let b of beerResult) {
        let card = document.createElement("card")
        let buyButton = document.createElement("button")

        card.appendChild(buyButton)

        buyButton.addEventListener("click", (e) => {
            this.addToCart(b)
        })

        this.productRow.appendChild(card)
    }
  }

  addToCart(b) {
      console.log("added this beer to the cart: " + b.description)
  }
}

App js

let ui = new UI()
ui.paint([{description:"a nice beer"},{description:"some beer"}])

And here is a working JSFiddle

Kokodoko
  • 26,167
  • 33
  • 120
  • 197