In the following (reduced) code the first Tester instance uses a predefined template whereas the second one uses a directly coded one. The interpolation works for the second one since the html contains the proper HTML code. The first Tester instance still has the innerHTML of <test-component> during the time of the interpolation. How can I change the code concept to also interpolate the first example?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Web Components Demo</title>
</head>
<body>
<div id="tc"></div>
<div id="tc2"></div>
<script>
class TestComponent extends HTMLElement {
constructor () {
super();
this.attachShadow({mode: "open"});
this.shadowRoot.appendChild(this.createTemplate().content.cloneNode(true));
}
createTemplate () {
const template = document.createElement("template");
template.innerHTML = "<h1>{{title}}</h1><p>{{text}}</p>";
return template;
}
}
window.customElements.define("test-component", TestComponent);
class Tester {
constructor ({selector, stuff, template}) {
this.selector = Array.from(document.querySelectorAll(selector));
this.template = template || null;
this.stuff = stuff;
this.render();
this.interpolate();
}
render () {
this.selector.forEach(s => {if (this.template) s.innerHTML = this.template});
}
interpolate () {
this.selector.forEach(s => {
for (let key in this.stuff) {
const regex = new RegExp(`{{ *${key} *}}`, "g");
s.innerHTML = s.innerHTML.replace(regex, this.stuff[key]);;
}
});
}
}
new Tester ({
selector: "#tc",
stuff: {title: "Test Title", text: "Lorem ipsum dolor sit amet"},
template: "<test-component>"
});
new Tester ({
selector: "#tc2",
stuff: {title: "Title that works", text: "Lorem ipsum dolor sit amet"},
template: "<h1>{{title}}</h1><p>{{text}}</p>"
})
</script>
</body>
</html>
Your main problem is:
s.innerHTML = s.innerHTML.replace(regex, this.stuff[key]);
s
is the outer DIV, not the <test-component>
innerHTML
effectively destroying and re-creating <test-component>
for every key
Thus the constructor()
always sets your default template as (element) innerHTML again:
constructor () {
super();
this.attachShadow({mode: "open"});
this.shadowRoot.appendChild(this.createTemplate().content.cloneNode(true));
}
createTemplate () {
const template = document.createElement("template");
template.innerHTML = "<h1>{{title}}</h1><p>{{text}}</p>";
return template;
}
is a bit bloated: You are creating HTML inside a Template, then adding the cloned content of the Template (which is: HTML) to the empty shadowRoot innerHTML
constructor () {
super() // returns 'this'
.attachShadow({mode: "open"}) // returns shadowRoot
.innerHTML = "<h1>{{title}}</h1><p>{{text}}</p>";
}
You only have to clone (Templates) when you need to re-use the original (Templates)
<test-component id=One></test-component>
<test-component id=Two></test-component>
<script>
window.customElements.define("test-component", class extends HTMLElement {
constructor() {
super().attachShadow({ mode: "open" });
this.setTemplate(`<b>{{title}}</b> {{text}}`);
}
setTemplate(html, data = {}) {
this.shadowRoot.innerHTML = html;
this.parse(data);
}
parse(data) {
let html = this.shadowRoot.innerHTML;
for (let key in data) {
const regex = new RegExp(`{{${key}}}`, "g");
html = html.replace(regex, data[key]);
}
this.shadowRoot.innerHTML = html;
}
});
One.parse({
title: "Test Title",
text: "Lorem ipsum dolor sit amet"
})
Two.setTemplate('<h4>{{title}}<h4>{{subtitle}}', {
title: "Test Two",
subtitle: "a Sub title"
})
let Three = document.createElement('test-component');
Three.parse({//parsed IN shadowDOM innerHTML!
title: "Title Three",
text: "text three"
})
document.body.append(Three)
</script>