Search code examples
javascripthtmlweb-component

How To Set An Attribute Of A Web Component Dynamically


I am stuck on trying to set an attribute for a custom web component called ws-dialog I designed. It always adds an attribute undefined on the code:

<ws-dialog class="global" undefined="add-page-content"></ws-dialog>

What I want to do here is to declare an attribute like template instead of undefined. Here is the code that I used for adding an onclick event to all dialog target elements:

function addDialogEvents(target) {
    target.addEventListener("click", () => {
        let dialog = document.createElement(Dialog.getName());
        let body = document.querySelector("body");

        dialog.template = "add-page-content";

        body.append(dialog);
});

}

And here is the code for my custom component:

"use strict";


class Dialog extends HTMLElement {


    connectedCallback() {
        //Styles for this custom component are declared in a separate CSS module
    }


    updateContent(id) {
        let content = document.querySelector(`template#${id}`).content;

        if (content)
            this.append(content);
    }


    attributeChangedCallback(name, oldValue, newValue) {
        this.TEMPLATE = "template";

        switch (name) {
            case this.TEMPLATE:
                this.updateContent(newValue);
                break;
        }
    }


    static get observedAttributes() {
        return [this.TEMPLATE];
    }


    static getName() {
        return "ws-dialog";
    }


    get template() {
        return this.getAttribute(this.TEMPLATE);
    }


    set template(template) {
        this.setAttribute(this.TEMPLATE, template);
    }

}



customElements.define(Dialog.getName(), Dialog);

Solution

  • Here is a playground to help you see when what method is fired.

    Note when and how many times observedAttributes is called.
    That means your this.TEMPLATE is undefined, and thus becomes a String undefined in the Array

    <script>
      const log = (...args) => {
        let div=document.body.appendChild(document.createElement('DIV'));
        div.style = `background:${args.shift()};color:white;font:13px Arial`;
        div.append(args.join(" "));
      }
      customElements.define('my-element', class extends HTMLElement {
        log() {
          log(this.getAttribute("color"), this.outerHTML.split(">")[0],'>', ...arguments);
        }
        static get observedAttributes() {
          log('red', `my-element observedAttributes`);// NO 'this' / Element here!
          return ["color"];
        }
        constructor() { super().log("constructor") }
        connectedCallback() {
          this.log("connectedCallback" , this.innerHTML || "No innerHTML" );//FireFox difference!
          setTimeout(() => this.log(`delayed connectedCallback ${this.innerHTML}`), 0);
        }
        attributeChangedCallback(name, oldValue, newValue) { // 4th W3C parameter = Namespace (not implemented in Browsers)
          this.log("attributeChangedCallback", name, oldValue || "null", newValue);
        }
        disconnectedCallback(){ this.log("disconnectedCallback") }
      })
      document.body.onload = () => {
        log('magenta', 'onload event');
        A.setAttribute("color", "darkolivegreen");
        B.innerHTML = "<my-element id=C color=hotpink>Charlie replaced Bravo</my-element>";
        B.remove();
      }
    </script>
    <my-element id=A color=green>Alfa</my-element>
    <my-element id=B color=blue>Bravo</my-element>

    Notes:

    • Helpful diagram at: https://andyogo.github.io/custom-element-reactions-diagram/

    • The execution order is slightly different in FireFox only, my advice is to not Develop, only Test in Firefox

    • In FireFox you can access the inner Elements from the connectedCallback. In all other Browsers you need that setTimeout. Different tech interpretations of the same W3C standard. And in this case Apple engineers were correct source. Chromium is based on the same engine.

    • Note how (delayed) code from the C connectedCallback (or any method) runs AFTER the disconnectedCallback.
      You need to write code that does not error when DOM elements no longer exist.


    JSFiddle playground: https://jsfiddle.net/WebComponents/67pduja9/