Search code examples
javascripthtmlweb-componentshadow-dom

Attribute doesnt change inside ShadowDOM


In my following code a change of the value doesnt update the input value: Why?

class textfield extends HTMLElement {

    get value()
    {
        return this.getAttribute("value");
    }

    set value(val)
    {
        this.setAttribute('value', val);
    }

    get readonly() {
        return this.hasAttribute("readonly");
    }

    set readonly(val)
    {
        if (val)
        {
            this.setAttribute("readonly", '');
        }
        else
        {
            this.removeAttribute("readonly");
        }
    }

    static get observedAttributes() {
        return ['readonly', 'value'];
    }

    attributeChangedCallback(name, oldValue, newValue) {
        console.log(name, newValue) ;
    }

    constructor()
    {
        super();
        this.attachShadow({ "mode": "open" });
    }

    connectedCallback()
    {
        this.shadowRoot.innerHTML =  
        /*html*/`
        <div>
            <div>
                Lable
            </div>
            <div>
                <input id="vi-input" type="text" value="${this.getAttribute("value")}" ${ this.hasAttribute("readonly") ? "readonly" : "" } />
            </div>
            <div>
                <span>Error</span>
            </div>
        </div>
        `;
    }
}

window.customElements.define("vi-textfield", textfield);

And in my index.html:

.... 
        <vi-textfield id="myInput" value="2" validation="^\d{4}$" ></vi-textfield>
....

When I update it by JS, lets say by:

document.getElementById("myInput").value = 3;

there will be the given console.log of attributeChangedCallback but the value itself doesnt change....


Solution

  • Your input doesn't update because they are 2 different elements.

     set value(val) {
       this.setAttribute('value', val);
     }
    

    sets the value attribute on

    <vi-textfield value="[val]">

    not the value attribute on

    <input id="vi-input" type="text" value="${this.getAttribute("value")}"/>
    

    which is another element in your elements shadowDOM

    value="${this.getAttribute("value")}" runs once, because connectedCallback() runs once

    If you want to set both, you can do:

     set value(val) {
       this.setAttribute('value', val);
       this.shadowRoot.getElementById("vi-input").setAttribute("value", val);
     }
    

    If you want them in sync, you also need setAttribute code for when value on the input changes.

    PS.

    set readonly(val) {
            if (val) {
                this.setAttribute("readonly", '');
            } else {
                this.removeAttribute("readonly");
            }
        }
    

    can be written as:

    set readonly(val) {
            this.toggleAttribute("readonly", val);
    }