无法通过使用insertAdjacentHTML在querySelector中获取动态创建的元素。

huangapple go评论72阅读模式
英文:

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(&#39;.catalog__row&#39;);

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 =&gt; {
       
        row.insertAdjacentHTML(
          &#39;afterbegin&#39;,
          ` &lt;a class=&quot;catalog__link&quot; href=&quot;#&quot; &gt;
              &lt;div class=&quot;catalog__product&quot;&gt;
              &lt;div class=&quot;catalog__product-img&quot;&gt;
              &lt;img class=&quot;catalog__productImg&quot; src=${product[&#39;img-src&#39;]} alt=&quot;&quot; srcset=&quot;&quot; /&gt;
              &lt;/div&gt;
              &lt;h3 class=&quot;catalog__product-model&quot;&gt;${product[&#39;model&#39;]}&lt;/h3&gt;
              &lt;p class=&quot;catalog__product-brand&quot;&gt;${product[&#39;brand&#39;]}&lt;/p&gt;
              &lt;span class=&quot;catalog__product-price&quot;&gt;${product[&#39;price&#39;]}&lt;/span&gt;
              
          &lt;/div&gt;&lt;/a&gt;`
        );
      });
    });
};

This is productSave.js (Second file)


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

const links = document.querySelectorAll(&#39;.catalog__link&#39;);

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

This is third file where I invoke external function

loadContent.js

import { fill } from &#39;./fillCatalog.js&#39;;
import { productSave } from &#39;./productSave.js&#39;;
fill(&#39;jordans&#39;);
productSave();

Simple HTML

&lt;html lang=&quot;ru&quot;&gt;
  &lt;head&gt;
    &lt;!--=include head.html--&gt;
    &lt;/style&gt;
  &lt;/head&gt;
  &lt;body&gt;
&lt;div class=&quot;catalog&quot;&gt;
  &lt;div class=&quot;catalog__content&quot;&gt;
    &lt;div class=&quot;catalog__row&quot;&gt;&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

  &lt;/body&gt;

  &lt;script type=&quot;module&quot; src=&quot;../js/goodscart.js&quot;&gt;&lt;/script&gt;
  &lt;script type=&quot;module&quot; src=&quot;../js/loadContent.js&quot;&gt;&lt;/script&gt;
&lt;/html&gt;

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 在函数内部以避免在元素呈现之前初始化它。

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.

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(&#39;.catalog__link&#39;); // &lt;-- This

export const productSave = function () {
// &lt;-- 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(&#39;jordans&#39;).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) =&gt; {
    const timeout = setTimeout(() =&gt; {
        clearTimeout(timeout);
        clearInterval(interval);
        rej(&quot;Not Found&quot;)
    }, 10000);
    const interval = setInterval(() =&gt; {
        const links = document.querySelectorAll(&#39;.catalog__link&#39;);
        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(&#39;DOMContentLoaded&#39;, () =&gt; {
    setTimeout(() =&gt; {
      const links = document.querySelectorAll(&#39;.catalog__link&#39;);

      links.forEach(link =&gt; {
        link.addEventListener(&#39;click&#39;, e =&gt; {
          const product = link.querySelector(&#39;.catalog__product&#39;);

          console.log(product);

          const newProduct = new Product(
            product.querySelector(&#39;.catalog__productImg&#39;).src,
            product.querySelector(&#39;.catalog__product-model&#39;).textContent,
            product.querySelector(&#39;.catalog__product-brand&#39;).textContent,
            product
              .querySelector(&#39;.catalog__product-price&#39;)
              .textContent.replace(/\D/g, &#39;&#39;)
          );

          console.log(newProduct);
          localStorage.setItem(&#39;newCard&#39;, JSON.stringify(newProduct));
        });
      });
    }, 500);
  });
};

答案3

得分: 0

  1. fill是一个异步过程,因为它涉及到fetch,但你在调用它之后立即调用了productSave,所以在代码尝试获取元素之前,DOM还没有构建好。

  2. 将你的链接缓存移到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();

希望对你有所帮助。

英文:
  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.

<!-- 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(&#39;.catalog__row&#39;);
  products.forEach(product =&gt; {
    row.insertAdjacentHTML(&#39;afterbegin&#39;, yourTemplateString);
  });
}

// `productSave` adds the listeners
function productSave() {
  const links = document.querySelectorAll(&#39;.catalog__link&#39;);
  links.forEach(link =&gt; ...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(&#39;jordans&#39;);
  fill(data);
  productSave();
}

main();

<!-- end snippet -->

huangapple
  • 本文由 发表于 2023年5月24日 18:13:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/76322403.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定