Home > front end >  why it doesn't loop through the loop (JavaScript, Class, ShadowDOM, HTML Template tag)
why it doesn't loop through the loop (JavaScript, Class, ShadowDOM, HTML Template tag)

Time:09-23

      class Product extends HTMLElement {
        connectedCallback() {
          let products = [
            { name: "carrot", price: "$ 6.99" },
            { name: "blueberry", price: "$ 6.99" }
          ];

          let pro = document.querySelector("#product_item").content;
          let copyHTML = document.importNode(pro, true);

          for (let product of products) {
            copyHTML.querySelector(".cerealName").textContent = product.name;
            this.attachShadow({ mode: "open" });
            this.shadowRoot.append(copyHTML.cloneNode(true));
          }
        }
      }
      customElements.define("product-item", Product);
    <main>
      <product-item></product-item>
      <product-item></product-item>
    </main>

    <template id="product_item">
      <li >
        <p  style="font-size: 3rem"></p>
      </li>
    </template>

I expected the result to be a

carrot blueberry

But the result came with

carrot carrot

I can't seem to run the loop, can you tell me why?

CodePudding user response:

You only need to call attachShadow once - after that it generates an error:

Failed to execute 'attachShadow' on 'Element': Shadow root cannot be created on a host which already hosts a shadow tree.

You also only need one <product-item> element.

      class Product extends HTMLElement {
        connectedCallback() {
          let products = [
            { name: "carrot", price: "$ 6.99" },
            { name: "blueberry", price: "$ 6.99" }
          ];

          let pro = document.querySelector("#product_item").content;
          let copyHTML = document.importNode(pro, true);
          this.attachShadow({ mode: "open" });
          for (let product of products) {
            copyHTML.querySelector(".cerealName").textContent = product.name;
            this.shadowRoot.append(copyHTML.cloneNode(true));
          }
        }
      }
      customElements.define("product-item", Product);
    <main>
      <product-item></product-item>
    </main>

    <template id="product_item">
      <li >
        <span  style="font-size: 1.3rem"></span>
      </li>
    </template>

CodePudding user response:

And you are also cloning the <template> twice with importNode and cloneNode

cloneNode is all you need, importNode is for importing from other documents.

And your component processed all products in one component; whilst you have multiple <product-item> in hour HTML

So I presume you want to make <product-item> display one product

I rewrote it a bit to (hopefully) let the code explain itself:

Note: the use of shadowDOM is probably overkill, it now blocks styling content.
Easily removed by removing 1 .attachShadow(...) and 3 .shadowRoot references.

customElements.define("product-item", class extends HTMLElement {
  connectedCallback() {
    let products = [{ name: "carrot", price: "6.99" },
                    { name: "blueberry", price: "6.99" }];

    let template = document.getElementById(this.nodeName).content;
    this.attachShadow({mode:"open"}).append(template.cloneNode(true));
    
    let product = products[this.getAttribute("nr")-1]; // UX starts at 1
    
    if (product) {
      this.shadowRoot.querySelector("b").textContent = product.name;
      this.shadowRoot.querySelector("span").innerHTML = `  US$ ${product.price}`;
    } else {
      this.shadowRoot.innerHTML = `No product`
    }
  }
});
<ul>
  <product-item nr=1></product-item>
  <product-item nr=2></product-item>
  <product-item nr=3></product-item>
</ul>

<template id="PRODUCT-ITEM">
  <li >
    <b>NAME</b><span>PRICE</span>
  </li>
</template>

<style>
  body {
    color: red; /* "Inheritable" CSS styles not blocked! */
  }
  .product { /* blocked by shadowDOM */
    color:green; 
  }
</style>

If you do want one <my-products> creating all HTML in one go; innerHTML is shorter and faster

customElements.define("my-products", class extends HTMLElement {
  connectedCallback() {
    let products = [{ name: "carrot", price: "$ 6.99" },
                    { name: "blueberry", price: "$ 6.99" }];

    this
        //.attachShadow({mode:"open"})
        .innerHTML = products.map(product =>
          `<li ><b>${product.name}</b> - ${product.price}</li>`
        ).join("");
  }
});
<my-products></my-products>

<style>
  .product {
    color:green;
  }
</style>

  • Related