skip to Main Content

I’m trying to grab an element from page and add an addEventHandler to it. Element (link with class .catalog__link) was dynamically created in another external function when the page was loaded using insertAdjacentHTML. I invoke them both in third js file via import keyword. Everything loads perfectly on the page, the elements are created for me, but I can’t grab them from another function connected to the page. Here’s examples of codes. I have two external function, combined in one js file and simple html page

This is fillCatalog.js (First file)


const row = document.querySelector('.catalog__row');

export const fill = function (brand) {
  fetch(`./data/${brand}.json`)
    .then(function (response) {
      return response.json();
    })
    .then(function (data) {
      let products = [...data.products];

      products.forEach(product => {
       
        row.insertAdjacentHTML(
          'afterbegin',
          ` <a class="catalog__link" href="#" >
              <div class="catalog__product">
              <div class="catalog__product-img">
              <img class="catalog__productImg" src=${product['img-src']} alt="" srcset="" />
              </div>
              <h3 class="catalog__product-model">${product['model']}</h3>
              <p class="catalog__product-brand">${product['brand']}</p>
              <span class="catalog__product-price">${product['price']}</span>
              
          </div></a>`
        );
      });
    });
};

This is productSave.js (Second file)


class Product {
  constructor(cardImg, cardName, cardBrand = '', cardPrice) {
    this.cardImg = cardImg;
    this.cardName = cardName;
    this.cardBrand = cardBrand;
    this.cardPrice = cardPrice;
  }
}

const links = document.querySelectorAll('.catalog__link');

export const productSave = function () {
  window.addEventListener('DOMContentLoaded', () => {
    console.log(links);
    links.forEach(link => {
      link.addEventListener('click', e => {
        productItem = link.querySelector('.catalog__product');
        const newProduct = new Product(
          productItem.querySelector('catalog__productImg').src,
          productItem.querySelector('.catalog__product-model').textContent,
          productItem.querySelector('.catalog__product-brand').textContent,
          productItem
            .querySelector('.catalog__product-price-price')
            .textContent.replace(/D/g, '')
        );
        localStorage.setItem('newCard', JSON.stringify(newProduct));
        console.log(card.querySelector('.card__name').textContent);
      });
    });
  });
};

This is third file where I invoke external function

loadContent.js

import { fill } from './fillCatalog.js';
import { productSave } from './productSave.js';
fill('jordans');
productSave();

Simple HTML

<html lang="ru">
  <head>
    <!--=include head.html-->
    </style>
  </head>
  <body>
<div class="catalog">
  <div class="catalog__content">
    <div class="catalog__row"></div>
  </div>
</div>

  </body>

  <script type="module" src="../js/goodscart.js"></script>
  <script type="module" src="../js/loadContent.js"></script>
</html>

I’ve tried use beforeend afterent and etc. Tried also use getElementsByTag, it returns empty HtmlCollection[]. After insertAdjacentHTML, can’t select like usually this links.

Could anyone help me please with this issue? I can’t find solution for that. Thank you

3

Answers


  1. Chosen as BEST ANSWER

    Thank you very much,Vincent. You gave me good direction to search solution.

    i put it all to setTimeout. And it's working good. I'm not strong enough in JS to use and manipulate promises. Thank you

    export const productSave = function () {
      window.addEventListener('DOMContentLoaded', () => {
        setTimeout(() => {
          const links = document.querySelectorAll('.catalog__link');
    
          links.forEach(link => {
            link.addEventListener('click', e => {
              const product = link.querySelector('.catalog__product');
    
              console.log(product);
    
              const newProduct = new Product(
                product.querySelector('.catalog__productImg').src,
                product.querySelector('.catalog__product-model').textContent,
                product.querySelector('.catalog__product-brand').textContent,
                product
                  .querySelector('.catalog__product-price')
                  .textContent.replace(/D/g, '')
              );
    
              console.log(newProduct);
              localStorage.setItem('newCard', JSON.stringify(newProduct));
            });
          });
        }, 500);
      });
    };
    

  2. You need to define links inside the function to avoid it being initialized before the elements are rendered.

    Initialization of links

    class Product {
    ...
    }
    
    // since this is not in a function it is initialized on import so 
    // before you render the `catalog__link` 
    const links = document.querySelectorAll('.catalog__link'); // <-- This
    
    export const productSave = function () {
    // <-- Should be here
    ...
    };
    
    

    Handling the fill Promise

    Futhermore there is a fetch reuqest inside fill which returned a promise.

    Then you can properly chain the call to the following function like so

    // productSave will only be executed after the 
    // fetch from fill has concluded sucesscully
    fill('jordans').then(productSave);
    
    export const fill = function (brand) {
      return fetch(`./data/${brand}.json`).then(...)
      // return as a promise
    };
    

    Workaround to detect classes in dom

    You can also implement a detector that waits for at least one link to be rendered. This Promise would resolve after at least 1 item with catalog__link can be found. Then you can chain your logic with .then(...). This one also includes a timeout of 10 seconds which executes .catch(...) (if present otherwise throws) if the element was not found in time.

    new Promise((res, rej) => {
        const timeout = setTimeout(() => {
            clearTimeout(timeout);
            clearInterval(interval);
            rej("Not Found")
        }, 10000);
        const interval = setInterval(() => {
            const links = document.querySelectorAll('.catalog__link');
            if (links.length == 0) {
                return;
            }
            clearInterval(interval);
            clearTimeout(timeout);
            res(links)
        },200);
    });
    
    Login or Signup to reply.
    1. fill is an async process due to the fetch but you’re calling productSave immediately after calling it so the DOM hasn’t been built by the time the code tries to pick up the elements.

    2. Move your links caching within the productSave function.

    It maybe easier to separate out the asynchronous parts from the synchronous parts. In this example there’s a separate getData function which is awaited and then the data can be filled, and saved.

    // `getData` is only responsible for the async process
    async function getData(brand) {
      try {
        const res = await fetch(`./data/${brand}.json`);
        if (res.ok) return res.json();
        throw new Error();
      } catch (err) {
        console.log(err);
      }
    }
    
    // `fill` creates the HTML
    function fill({ products }) {
      const row = document.querySelector('.catalog__row');
      products.forEach(product => {
        row.insertAdjacentHTML('afterbegin', yourTemplateString);
      });
    }
    
    // `productSave` adds the listeners
    function productSave() {
      const links = document.querySelectorAll('.catalog__link');
      links.forEach(link => ...etc )}
    }
    
    // `main` needs to be async because it needs to `await`
    // the parsed JSON from `getData` - but after that you can call the
    // functions in series
    async function main() {
      const data = await getData('jordans');
      fill(data);
      productSave();
    }
    
    main();
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search