英文:
Can't grab dynamically created elements via querySelector using insertAdjacentHTML
问题
我正在尝试从页面中获取一个元素并为其添加一个事件处理程序。元素(具有类名.catalog__link的链接)是在页面加载时通过insertAdjacentHTML在另一个外部函数中动态创建的。我通过import关键字在第三个js文件中调用它们。页面上的一切都加载得很完美,元素也为我创建好了,但是我无法从连接到页面的另一个函数中获取它们。以下是代码示例。我有两个外部函数,合并在一个js文件中,以及一个简单的HTML页面。
这是fillCatalog.js(第一个文件):
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>`
        );
      });
    });
};
这是productSave.js(第二个文件):
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')
            .textContent.replace(/\D/g, '')
        );
        localStorage.setItem('newCard', JSON.stringify(newProduct));
        console.log(card.querySelector('.card__name').textContent);
      });
    });
  });
};
这是第三个文件,我在其中调用外部函数loadContent.js:
import { fill } from './fillCatalog.js';
import { productSave } from './productSave.js';
fill('jordans');
productSave();
简单的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>
我尝试过使用beforeend和afterend等方法。还尝试过使用getElementsByTag,它返回空的HtmlCollection[]。在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
答案1
得分: 0
You need to define links 在函数内部以避免在元素呈现之前初始化它。
links 的初始化
class Product {
...
}
// 由于这不在函数内,因此在导入时初始化,因此
// 在呈现`catalog__link`之前
const links = document.querySelectorAll('.catalog__link'); // <-- 这里
export const productSave = function () {
// <-- 应该在这里
...
};
处理 fill Promise
此外,在 fill 中有一个返回 promise 的 fetch 请求。
然后,您可以正确地链接对以下函数的调用,如下所示
// 仅在
// 从fill成功地返回后执行
// productSave
fill('jordans').then(productSave);
export const fill = function (brand) {
  return fetch(`./data/${brand}.json`).then(...)
  // 作为一个promise返回
};
用于检测DOM中类的解决方法
您还可以实现一个检测器,等待至少呈现一个链接。这个 Promise 在至少找到一个带有 catalog__link 的项后会解析。然后,您可以使用 .then(...) 链接您的逻辑。这个解决方案还包括一个10秒的超时,在超时后执行 .catch(...)(如果存在的话,否则会抛出异常),如果在时间内找不到元素。
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);
});
英文:
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);
});
答案2
得分: 0
非常感谢,Vincent。您给了我寻找解决方案的良好指导。
我将所有内容放到了setTimeout中,现在一切运行良好。我在JavaScript方面不够强大,无法使用和操作promises。谢谢。
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);
  });
};
英文:
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);
  });
};
答案3
得分: 0
- 
fill是一个异步过程,因为它涉及到fetch,但你在调用它之后立即调用了productSave,所以在代码尝试获取元素之前,DOM还没有构建好。 - 
将你的链接缓存移到
productSave函数内部。 
将异步部分与同步部分分开可能会更容易。在这个示例中,有一个单独的getData函数,它被等待,然后数据可以被填充和保存。
// `getData`仅负责异步过程
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`创建HTML
function fill({ products }) {
  const row = document.querySelector('.catalog__row');
  products.forEach(product => {
    row.insertAdjacentHTML('afterbegin', yourTemplateString);
  });
}
// `productSave`添加监听器
function productSave() {
  const links = document.querySelectorAll('.catalog__link');
  links.forEach(link => ...etc )}  // 在这里添加你的逻辑
}
// `main`需要是异步的,因为它需要从`getData`中等待解析后的JSON数据 - 但之后你可以依次调用这些函数
async function main() {
  const data = await getData('jordans');
  fill(data);
  productSave();
}
main();
希望对你有所帮助。
英文:
- 
fillis an async process due to thefetchbut you're callingproductSaveimmediately 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
productSavefunction. 
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.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
// `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();
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论