Search code examples
javascripthtmlcustom-element

Include attributes in document.createElement for custom elements


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.


Solution

  • Kaiido is right, you can not use the constructor to do (some) 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 (appends multiple DOM nodes, but was not supported in good old Internet Explorer)
    Because you do not want to 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 because they don't know what the connectedCallback does 100%
    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", "//we-are-all.github.io/sheep.svg");
          elem.append(row.id, "-", index);
          return elem;
        });
        row.append(...items); // append to row ONCE!!
      })
    </script>