Search code examples
javascripthtmlcustom-element

Instantiating custom built-in element in JavaScript does not work


I've crafted a custom built-in element that recursively invokes itself. However, the child elements generated in JavaScript are not inheriting from the custom built-in element. Given that the initial invocation from HTML is functioning correctly, I suspect the issue may stem from the page lifecycle when adding controls via JavaScript. Any thoughts on why this could be occurring?

html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <ul is="test-list" id="testList">

    </ul>
    <script type="module" src="/js/test-list-element.js"></script>

    <script>
        let data = [
            {
                name: 'a',
                data: [{
                    name: 'a',
                    data: []
                },
                {
                    name: 'b',
                    data: []
                },
                {
                    name: 'c',
                    data: []
                },
                {
                    name: 'd',
                    data: []
                }]
            },
            {
                name: 'b',
                data: [{
                    name: 'a',
                    data: []
                },
                {
                    name: 'b',
                    data: []
                },
                {
                    name: 'c',
                    data: []
                },
                {
                    name: 'd',
                    data: []
                }]
            },
            {
                name: 'c',
                data: [{
                    name: 'a',
                    data: []
                },
                {
                    name: 'b',
                    data: []
                },
                {
                    name: 'c',
                    data: []
                },
                {
                    name: 'd',
                    data: []
                }]
            },
            {
                name: 'd',
                data: [{
                    name: 'a',
                    data: []
                },
                {
                    name: 'b',
                    data: []
                },
                {
                    name: 'c',
                    data: []
                },
                {
                    name: 'd',
                    data: []
                }]
            },
        ];

        window.onload = (event) => {
            let listElement = document.getElementById("testList");
            listElement.data = data;
        }
    </script>

</body>

</html>

test-list-element.js

class testList extends HTMLUListElement {
    constructor() {
      super();
      this.render = this.render.bind(this);
    }
  
    #data = null;
    set data(value) {
      this.#data = value;
      this.render();
    }

    render(){
        if(!this.#data) return;

        this.#data.forEach(element => {
            let newItem = document.createElement('li');
            newItem.innerText = element.name;
            this.appendChild(newItem);

            if(element.data)
            {
                let newlist = document.createElement('ul');
                newlist.setAttribute('is','test-list')            
                newlist.data = data;
                newItem.appendChild(newlist);
            }
        });
    }
}

    
customElements.define('test-list', testList, { extends: 'ul' });
  

enter image description here


Solution

  • Don't use

    const newlist = document.createElement('ul');
    newlist.setAttribute('is','test-list');
    

    to create the element, as the is part must be present on construction time; setting it after construction has no effect.

    The correct way to instantiate a custom element is either the createElement options parameter

    const newlist = document.createElement('ul', {is: 'test-list'});
    

    or just the constructor itself:

    const newlist = new TestList();