javascriptdomrenderingqueryselectorinsertadjacenthtml

Can't grab dynamically created elements via querySelector using insertAdjacentHTML


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


Solution

  • 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);
    });