This content originally appeared on Go Make Things and was authored by Go Make Things
One of my new subscribers asked me how to use a chunk of HTML as a template that you can add data from another source (like an API) to. So today, we’re going to explore that.
Let’s dig in!
The template
element
A few years back, HTML added the template
element. It’s purpose is to hold HTML that shouldn’t be rendered as-is, but is intended to be used as a template with JavaScript later.
Here, the #list-item
element contains a list item that can be used to generate a list of wizards. It contains an li
element with a .wizard
class on it.
<div id="app"></div>
<template id="list-item">
<li class="wizard"></li>
</template>
In your JavaScript, you might use the document.querySelector()
method to get the #app
element (where you want to add your list) and the #list-item
element (the template).
There’s also an array of wizards
.
// My starting JS
let app = document.querySelector('#app');
let listItem = document.querySelector('#list-item');
let wizards = ['Merlin', 'Gandalf', 'Neville'];
To do this, first, I would create a ul
element with the document.createElement()
method.
// Create a list element
let list = document.createElement('ul');
Next, I would use a for...of
loop to loop through my wizards
.
Inside the loop, we want to use the listItem
template for the HTML. The template
element makes this absurdly difficult for something that’s supposed to make templating easier.
The content inside is a document fragment that you can access with the content
property. Because we’re going to be using it more than once, we want to create a clone of it with the Element.cloneNode()
method.
// Loop through each wizard and add it to the list
for (let wizard of wizards) {
// Get and clone the inner content
let li = listItem.content.cloneNode(true);
}
At this point, li
is a copy of document fragment in our template
. We want the li
element. For that, we need to use the document.querySelector()
method to find it.
// Loop through each wizard and add it to the list
for (let wizard of wizards) {
// Get and clone the inner content
// Then, find the li element
let li = listItem.content.cloneNode(true).querySelector('li');
}
Now, we can add the wizard
to it with the Node.textContent
property, and add it to the list
element with the Element.append()
method.
// Loop through each wizard and add it to the list
for (let wizard of wizards) {
// Get and clone the inner content
// Then, find the li element
let li = listItem.content.cloneNode(true).querySelector('li');
// Add the wizard text, then add it to the list
li.textContent = wizard;
list.append(li);
}
Then, finally, we can append the entire list to the DOM.
// Add the list to the UI
app.append(list);
As you can see, this kind of sucks.
The template
element is even worse with lots of items
Imagine if your template
actually looked like this instead.
<template id="list-item">
<div class="wizard">
<strong id="wizard-name"></strong>
</div>
</template>
First, I would create an array to hold the elements I’m going to create from my template.
// Create an array of elements
let elems = [];
Next, I’d loop through each wizard
, get the template content, clone it, and look for the div
inside.
// Loop through each wizard
for (let wizard of wizards) {
// Get the content and clone it
// Then, find the div element
let div = listItem.content.cloneNode(true).querySelector('div');
}
I’d use the document.querySelector()
to find the strong
element inside the div
, and set it’s textContent
property to the value of the wizard
.
Then, I’d use the Array.push()
method to add the div
to the elems
array.
// Loop through each wizard
for (let wizard of wizards) {
// Get the content and clone it
// Then, find the div element
let div = listItem.content.cloneNode(true).querySelector('div');
// Get the strong element inside the div
let strong = div.querySelector('strong');
// Add the wizard name
strong.textContent = wizard;
// Add the div to the elems array
elems.push(div);
}
Finally, I would use the Element.append()
method to add each element, using the spread syntax operator to pass the whole array in at once.
// Add the elements to the UI
app.append(...elems);
As you can see, this is a huge pain in the ass, and that’s just with one element! Imagine doing this with multiple pieces of data across multiple elements.
This is, in my opinion, a big part of why the template
element never really saw widespread use. It’s just not very functional.
Creating HTML with JavaScript
As a result of these challenges, it’s a lot more common for people to use template literals and generate their HTML entirely in JavaScript. With this approach, you skip the template
element entirely.
There are a few ways to approach it, but the one beginners find the most readable is to first create an empty string to hold the html
.
// Create an HTML string
let html = '';
On each loop, you append (or concatenate) the html
string with another HTML string. Template literals make it really easy to add variable content inside the string.
When you’re done, you can use the Element.innerHTML
property to add your html
string to the UI.
(If you’re using third-party data, don’t forget to sanitize your HTML string first to protect against XSS attacks.)
// Loop through each wizard
for (let wizard of wizards) {
html +=
`<div class="wizard">
<strong>${wizard}</strong>
</div>`;
}
// Add the HTML to the UI
app.innerHTML = html;
Here’s a demo of this technique.
This approach is clean and simple, and as a result, far more common.
A hybrid approach
There is one technique you can use that combines both of these approaches.
The template
element does not support string interpolation (replacing variables with data) like template literals do. But, you can use a little bit of JavaScript to use it that way.
A while back, I found a little helper function on StackOverflow that interpolates a plain old string like a template literal.
/**
* Get a template from a string
* https://stackoverflow.com/a/41015840
* @param {String} str The string to interpolate
* @param {Object} params The parameters
* @return {String} The interpolated string
*/
function interpolate (str, params) {
let names = Object.keys(params);
let vals = Object.values(params);
return new Function(...names, `return \`${str}\`;`)(...vals);
}
Pass in the string as the first argument, and an object of parameters as the second.
To use it, we would setup our template
element with variables in it just like we would a template literal.
<template id="list-item">
<div class="wizard">
<strong>${wizard}</strong>
</div>
</template>
Inside our JavaScript, we would pass the template
elements Element.innerHTML
property value into the interpolate()
function as the string.
The params
need to be an object, so we’ll wrap our wizard
in an object and pass that in as the second argument. We’ll concatenate the string that’s returned back to the html
string.
// Create an HTML string
let html = '';
// Loop through each wizard
for (let wizard of wizards) {
html += interpolate(listItem.innerHTML, {wizard});
}
// Add the HTML to the UI
app.innerHTML = html;
The value of this is pretty minimal in our simple example, but can be pretty awesome with larger templates.
There are a few drawbacks to this approach, though. The biggest one is that it uses the Function()
method, which can expose you to cross-site scripting attacks with third-party data.
Also, unlike a real template literal, it cannot support logic (if...else
, loops, and so on) inside the template.
This is a cool trick for special circumstances, but I would generally recommend against using it.
🔥 Black Friday Sale! Starting Monday, save 50% on every pocket guide, video course, and enrollment in the Vanilla JS Academy. If you buy a pocket guide bundle or join Academy, you'll also get $436 in free bonus gifts.
This content originally appeared on Go Make Things and was authored by Go Make Things
Go Make Things | Sciencx (2021-11-19T15:30:00+00:00) HTML templates with vanilla JavaScript. Retrieved from https://www.scien.cx/2021/11/19/html-templates-with-vanilla-javascript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.