Reputation: 15
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
Upvotes: 0
Views: 123
Reputation: 1055
You need to define links
inside the function to avoid it being initialized before the elements are rendered.
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
...
};
fill
PromiseFuthermore 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
};
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);
});
Upvotes: 0
Reputation: 63524
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.
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();
Upvotes: 0
Reputation: 15
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);
});
};
Upvotes: 0