skip to Main Content

I’m trying to dynamically add a custom element I created, but it’s not generating them properly.

div-item declaration:

class Item extends HTMLElement {
    constructor() {
        super();
        this.src = this.getAttribute("src");
        this.link = this.getAttribute("href");
        this.attachShadow({ mode: 'open'});
        this.text = this.textContent.trim();

        this.shadowRoot.innerHTML = `
        <style>/* Styles excluded */</style>
        <div>
            <a href='${this.link}'>
                <div class="bg">
                    <img src="${this.src}" alt="${this.text}">
                </div>
                <p>${this.text}</p>
            </a>
        </div>
        `;
    }
}

customElements.define('div-item', Item);

Javascript generating:

function createDivItems() {
  // Get all elements with the class name "row"
  var rows = document.getElementsByClassName("row");

  // Iterate over each row element
  for (var j = 0; j < rows.length; j++) {
    var row = rows[j];

    // Remove all existing div-item elements from the row
    row.innerHTML = '';

    // Create and add elements based on the window width
    for (var i = 0; i < (window.innerWidth - 60); i += 120) {
      // Create a new custom element
      var elem = document.createElement("div-item", {"src": '<?php echo "assets/not found.png"; ?>', "href": '<?php echo "/newPage.php"; -- placeholders for implementing the shop system ?>'});

      // Set attributes for the custom element
      elem.setAttribute("src", '<?php echo "assets/not found.png"; ?>');
      elem.setAttribute("href", '<?php echo "/newPage.php"; ?>');

      elem.innerHTML = 'not used.'

      // Append the custom element to the current row
      row.appendChild(elem);
    }
  }
}

I thought it would create the icons and text, but it just creates an empty square outline (from the CSS that was removed)

I put a delay on the creation of the custom tag and it generated them properly, but when I resized it became the empty square again.

Basically, I think when I create the tag it stops checking for the attributes, so I need to set the attributes when the element is created, not after it is.

2

Answers


  1. The constructor is called inside the createElement() call (or during upgrade if the element was parser inserted).

    So by the time you call the setAttribute() method, the constructor has already been called and your element’s shadow has already been built, without the attribute values.

    Instead you want to the shadow content of your element during its connectedCallback:

    class Item extends HTMLElement {
        connectedCallback() {
            this.src = this.getAttribute("src");
            this.link = this.getAttribute("href");
            this.attachShadow({ mode: 'open'});
            this.text = this.textContent.trim();
    
            this.shadowRoot.innerHTML = `
            <style>/* Styles excluded */</style>
            <div>
                <a href='${this.link}'>
                    <div class="bg">
                        <img src="${this.src}" alt="${this.text}">
                    </div>
                    <p>${this.text}</p>
                </a>
            </div>
            `;
        }
    }
    
    customElements.define('div-item', Item);
    
    
    function createDivItems() {
      // Get all elements with the class name "row"
      var rows = document.getElementsByClassName("row");
    
      // Iterate over each row element
      for (var j = 0; j < rows.length; j++) {
        var row = rows[j];
    
        // Remove all existing div-item elements from the row
        row.innerHTML = '';
    
        // Create and add elements based on the window width
        for (var i = 0; i < (window.innerWidth - 60); i += 120) {
          // Create a new custom element
          // Note: the options argument of createElement only takes an 'is' property
          var elem = document.createElement("div-item"); 
    
          // Set attributes for the custom element
          elem.setAttribute("src", "assets/not found.png");
          elem.setAttribute("href", "/newPage.php");
    
          elem.innerHTML = 'not used.'
    
          // Append the custom element to the current row
          row.appendChild(elem);
        }
      }
    }
    
    createDivItems();
    <div class="row"></div>
    Login or Signup to reply.
  2. Kaiido is right, you can not use the constructor to do DOM operations when you do
    createElement("<div-item"), because that element is in memory and only gets created in the DOM
    when you do appendChild(elem)

    appendChild is also not what you want; use modern append (not supported in good old Internet Explorer)

    And do not call append(Child) inside the loop, every call will probably cause DOM-repaints and reflows.

    I focussed below code on the pain points; you can add your own stuff

    Note: the connectedCallback has a potentiual DOM issue when trying to read lightDOM,
    not in this snippet.
    98 out of 100 developers get it wrong. See my blogbost

    <div id="row1" class="row">FOO</div>
    <div id="row2" class="row">BAR</div>
    <div id="row3" class="row">BAZ</div>
    
    <script>
      customElements.define('div-item', class extends HTMLElement {
        connectedCallback() {
          let src = this.getAttribute("src");
          this.attachShadow({mode:'open'})
              .innerHTML = `<img src="${src}" style="width:40px">` + this.textContent.trim();
        }
      });
    
      var rows = [...document.getElementsByClassName("row")];
      rows.map(row => {
        row.innerHTML = ''; // delete FOO, BAR in row
        const length = Math.ceil((window.innerWidth - 60) / 120);
        console.log(row.id, "add", length, "div-items");
        const items = Array(length).fill(0).map((_, index) => {
          let elem = document.createElement("div-item");
          elem.setAttribute("src", "//svg-cdn.github.io/lamb.svg");
          elem.append(row.id, "-", index);
          return elem;
        });
        row.append(...items); // append to row ONCE!!
      })
    </script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search