With HTML Templates it makes it easy to stamp out snippets of html.
What is a sane way of populating the stamped out templates? In the MDN link above they render the template as follows:
td = clone2.querySelectorAll("td");
td[0].textContent = "0384928528";
td[1].textContent = "Acme Kidney Beans 2";
This obviously only works if all the elements are the same tag, and ordered in the same way, which is very brittle.
What if I had a template like this:
<template>
<div>
<h2>__heading__</h2>
<label>__label1__</label><input type="text" value="__value1__">
<label>__label2__</label><input type="text" value="__value2__">
<div>__instruction__</div>
<label>__label3__</label><input type="text" value="__value3__">
</div>
</template>
And say one had this data to render it with:
{
__heading__: 'Lots of things',
__label1__: 'label 1',
__value1__: 'value 1',
__label2__: 'label 2',
__value2__: 'value 2',
__instruction__: 'Do the thing',
__label3__: 'label 3',
__value3__: 'value 3',
}
Then the rendered result would be:
<div>
<h2>Lots of things</h2>
<label>label 1</label><input type="text" value="value 1">
<label>label 2</label><input type="text" value="value 2">
<div>Do the thing</div>
<label>label 3</label><input type="text" value="value 3">
</div>
How would one render the template? PS if this is a XY question, you can use some other means to instead of the dunder fields.
I can only think of adding classes or attributes to each element which has a field to populate, and then perform lots of clonedNode.querySelector()
...seems very unelegant.
CodePudding user response:
Note 9/9/2022: There is a proposal for DOM Parts on the table: https://github.com/WICG/webcomponents/blob/gh-pages/proposals/DOM-Parts.md
You could replace content with Template Literal String notation
string:
<h1>Run ${name}! Run!</h1>
Ain't <b>${tooling}</b> great!!
property Object:
{
name: this.getAttribute("name"),
tooling: "Web Components",
}
- The
parse(str,v)
function - creates a
new Function
- with a String literal (note the back-ticks)
- then executes that Function
- passing all
v
Object values - and Returns the parsed String literal
<template id="MY-ELEMENT">
<h1 style="background:${color}"> Run ${name}! Run!</h1>
Ain't <b>${tooling}</b> great!!
</template>
<my-element name="Forrest"></my-element>
<script>
function parse(str, v = {}) {
try {
return new Function("v",
"return((" Object.keys(v).join(",") ")=>`"
str
"`)(...Object.values(v))")(v) || "";
} catch (e) {
console.error(e);
}
};
customElements.define("my-element", class extends HTMLElement {
color = "gold";
constructor() {
super().attachShadow({mode: 'open'}).innerHTML = parse(
document.getElementById(this.nodeName).innerHTML, // String
{ // properties
name: this.getAttribute("name"),
tooling: "Web Components",
...this, // to show you can add anything you want to the property bag
});
}
})
</script>
CodePudding user response:
One of the possible solution is manipulation of the HTML string itself. There is not much stuff you can do with content inside DOM tree without some special markup (like you mentioned in your question).
So, you clone the template and modify inner HTML of every child inside the template:
// Gather the template from DOM and clone it
const template = document.querySelector('#target_template');
const clonedDocumentFragment = template.content.cloneNode(true);
// Looks like there is no better way to modify HTML of the whole
// DocumentFragment, so we modify HTML of each child node:
Array
.from(clonedDocumentFragment.children)
.forEach(childElement => renderInnerVariables(
childElement,
{
// Pass your values here
}
));
// And here example of the method replacing those values
function renderInnerVariables(targetElement, variables = {}) {
// Reminder: it can be really unsafe to put user data here
targetElement.innerHTML = targetElement.innerHTML.replace(
// Instead of looping through variables, we can use regexp
// to get all the variables in content
/__([\w_] )__/,
(original, variableName) => {
// Check if variables passed and target variable exists
return variables && variables.hasOwnProperty(variableName)
// Pass the variable value
? variables[variableName]
// Or pass the original string
: original;
}
);
}
But since it's just a string content modification, you can pretty much just use string templates instead.
CodePudding user response:
If I understood correctly you want to create a template based on object.. if yes you can use this as a start..
What I did was to create a function getTagName(key)
which will return a specific string based on the object key (it can be made with a switch most probably but eh.. :( switch on regex ) For example __value2__
will return input
Then I created a function createHtmlElementwith
with some conditions in order to set the text/value to my element then I iterate through all the properties and append the newly created element
const data = {
__heading__: 'Lots of things',
__label1__: 'label 1',
__value1__: 'value 1',
__label2__: 'label 2',
__value2__: 'value 2',
__instruction__: 'Do the thing',
__label3__: 'label 3',
__value3__: 'value 3',
}
let container = document.getElementById('container');
// this can be done with switch i think, but for demo i did if statements :(
function getTagName(key) {
key = key.replace('__', ''); //remove __ from front
if (/^heading/.test(key)) return "h2";
if (/^label/.test(key)) return "label";
if (/^value/.test(key)) return "input";
if (/^instruction/.test(key)) return "div";
}
function createHtmlElement(name, key, value) {
let element = document.createElement(name);
if (name === 'label' || name === "h2" || name === "div") {
element.innerHTML = value;
}
if (name === "input") {
element.type = "text";
//element.value = key; // this is not working for i don't know what reason :(
element.setAttribute('value', key);
}
// console.log(element) // decomment if you want to see the element as plain html
return element;
}
for (const [key, value] of Object.entries(data)) {
let tagName = getTagName(key);
let element = createHtmlElement(tagName, key, value);
let brTag = document.createElement("br");
container.appendChild(element);
container.appendChild(brTag);
}
<div id="container">
</div>
<!--
<h2>__heading__</h2>
<label>__label1__</label>
<input type="text" value="__value1__">
<label>__label2__</label>
<input type="text" value="__value2__">
<div>__instruction__</div>
<label>__label3__</label>
<input type="text" value="__value3__">
-->