Coen Damen Coen Damen - 27 days ago 6
Javascript Question

re-use imported Web Component template

I have built a Web Component (I am new to this and not a Javascript expert) and injected it into the Shadow DOM. But, I want to use this template multiple times on the page. I found that the private scope of the component is reused, which is not what I want. I want a repeat of the component.

index.html




<body>
<my-template id="data1" data="data.json" ></my-template>
<br/>
<br/>

<my-template id="data2" data="data.json"></my-template>


This is the template Init and Script

my-template.html

(function (doc) {

var data;
var id;
var total = 0;
var template;
var root;
var totalSelected;
var container;

// This element will be registered to index.html
// Because `document` here means the one in index.html
var XComponent = document.registerElement('my-template', {
prototype: Object.create(HTMLElement.prototype, {
createdCallback: {
value: function() {
root = this.createShadowRoot();
// use doc instead of document or document.currentScript.ownerDocument
template = doc.querySelector('#template');
var clone = document.importNode(template.content, true);
root.appendChild(clone);
data = this.getAttribute('data');
id = this.getAttribute('id');
init();
}
}
})
});

function init(){

console.log('init ',id);

// create input field
totalSelected = document.createElement('input');
totalSelected.type = 'text';
totalSelected.id = 'total-selected'+id;
totalSelected.addEventListener("click", showList);

root.appendChild(totalSelected);

// values container
container = document.createElement('div');
container.id = 'container'+id;
container.className = 'container';

for(var i=0 ; i < 3; i++){

var row = document.createElement('div');
row.id = 'row-'+i+'-'+id;
container.appendChild(row);
}

root.appendChild(container);

}

return {
append : append,
showList : showList,
hideList : hideList
}



function showList() {
console.log('showList', container);
container.style.display = 'block';
}

function hideList() {
container.style.display = 'none';
}

})(document.currentScript.ownerDocument); // pass document of x-component.html




Now, when I click in the input field, always "data2" is opened. This means that when clicking on the data1 component, the variables were overwritten by the data2 init :(

When I create a second html and register it as my-template2.html then it obviously all works, but (obviously) I don't want this.

How can I repeat this template, using it's own init? Is it even possible?

Answer

It's because all the variables you've defined:

var data;
var id;
var total = 0;
var template;
var root;
var totalSelected;
var container;

... are global and then shared to all your elements.

To avoid that, there are severals solutions.

1) Set them as properties of the custom element object itself, and attach the methods to the prototype:

prototype: Object.create(HTMLElement.prototype, {
    createdCallback: {
        value: function() {
            this.data = this.getAttributes( 'data' )
            //this.id is automatically set
            ...
            this.init()
        }
    },
    init: {
        value: function() {
            this.container = ...
        }
    }

2) Define an object that will centralize them all:

this.vm = {
    data: this.getAttribute( 'data' )
    id: this.getAttribute( 'id' )
}

3) Put them in a closure, for example in createdCallback:

createdCallback: {
    value: function() {
        var data = this.getAttribute( 'data' )
        var id = this.id  
        init()
        function init() {...}
        function showList() {...}      
    }
}
Comments