Search code examples
javascripthtmlweb-component

How to access attribute of a WebComponent?


I am trying an example of custom web components from MDN. So I ended with this code

class Info extends HTMLElement
{
    constructor()
    {
        super();

        const shadow = this.attachShadow({mode: 'open'});
        const wrapper = document.createElement('span');
        wrapper.setAttribute('class', 'wrapper');

        const icon = document.createElement('span');
        icon.setAttribute('class', 'icon');
        icon.setAttribute('tabindex', 0);

        const info = document.createElement('span');
        info.setAttribute('class', 'info');

        // Take attribute content and put it inside the info span
        const text = this.getAttribute('data-text');
        info.textContent = text;
        console.log(JSON.stringify(this.attributes['img']));

        // Insert icon
        const img = document.createElement('img');
        img.src = this.hasAttribute('img') ? this.getAttribute('img') : 'img/default.png';
        icon.appendChild(img);

        // Create some CSS to apply to the shadow dom
        const style = document.createElement('style');

        style.textContent = `
            .wrapper {  position: relative;     }
            img {   width: 1.2rem;  }
        `;

        // Attach the created elements to the shadow dom
        shadow.appendChild(style);
        shadow.appendChild(wrapper);
        wrapper.appendChild(icon);
        wrapper.appendChild(info);
    }
}
customElements.define('a-info', Info);
<html>
    <head></head>
    <body>
        <a-info img="img/alt.png" data-text="Your card."></a-info>
    </body>
</html>

It almost works. The element is created except this.getAttribute('data-text') returns undefined. I run it locally in Firefox. Could someone explain what is the issue with this example, please?


Solution

  • Yes, in the Constructor phase there is no DOM available

    see: https://andyogo.github.io/custom-element-reactions-diagram/

    And don't rely too much on official documentation, it is bloated and sometimes plain wrong:

    <script>
      class Info extends HTMLElement {
        constructor() {
          let createElement = (name, className) => {
            let el = document.createElement(name);
            if (className) el.classList.add(className);
            return el;
          }
          // yes you can add code *before* super()
          const style = createElement('style');
          style.textContent = `.wrapper {position:relative}img {width: auto}`;
          const wrapper = createElement('span', 'wrapper');
          const icon = createElement('span', 'icon');
          icon.setAttribute('tabindex', 0);
    
          super() // set AND return this scope
            .attachShadow({mode: 'open'}) // set AND return this.shadowRoot
            .append(style,wrapper);
            
          this.info = createElement('span', 'info');
          this.img = createElement('img');
          icon.append(this.img);
          wrapper.append(icon, this.info);
        }
        connectedCallback() {
          this.info.textContent = this.getAttribute('data-text') || "card?";
          this.img.src = this.getAttribute('img') || 'https://via.placeholder.com/42';
        }
      }
      customElements.define('a-info', Info);
    </script>
    <a-info></a-info>
    <a-info img="https://via.placeholder.com/110" data-text="Card #1"></a-info>
    <a-info img="https://via.placeholder.com/120" data-text="Card #2"></a-info>