- January 9, 2017
Many developers are still in the process of embracing all of the advances of ES6, CSS3, and HTML5. One of the under-utilized tools, in my ever-so-humble opinion, is the
<template> tag. Today’s post is going to explore what this tag is, some of the inner workings of it, and also demo how you can use it in your every day workflow to improve your web applications.
What it is
<template> tag creates a non-rendered document fragment on your page. There’s a lot of jargon in that sentence, so let’s deconstruct it a bit and de-mystify what that sentence says we are getting with a
Also known as an ‘inert’ document fragment, this is HTML that is reviewed by the browser to ensure that it is valid HTML, but is not added to the overall parent document (DOM).
A document fragment is a minimal document object, like a DOM, but it has no parent. It is designed to hold well-formed markup. Document fragments are also intended to allow developers to clone and/or transfer the children of the fragment into the main document.
What does it all mean?
Putting these terms together means that when we define a
<template> tag on our page, we are embedding a mini-document into our DOM which the browser is not going to render until the contents of the
<template> are moved or copied into the main document itself.
So what does this look like in practice? Let’s consider the following document:
<!doctype html> <html> <head> <title>Playing with Templates</title> </head> <body> <p>This is some rendered content.</p> <template id="list_template"> <ul> <li>One</li> <li>Two</li> </ul> </template> </body> </html>
When this page is loaded, the strings ‘One’ and ‘Two’ in the
<li> tags are not rendered on the page. In fact, none of the content of the
<template> tag is rendered on the page. This can be confirmed with
document.querySelector('ul'); which returns
null. From the perspective of the DOM, there is no
<ul> tag currently on the page. The DOM did not build nodes for that and add it to the overall tree. The template tag, however, is part of the DOM.
Now, the real question becomes, how do we take advantage of this embedded markup and make it relevant for our page. Well, that is going to take some scripting. The first thing to understand is that the document fragment is stored in the
content property of the template. It is important that this
<template> has an ID on it. Theoretically, you could (would) have multiple templates on a page, so there is a need to be able to access each template individually.
In this case, since the template has the ID of
list_template we can access it a variety of ways. Here are a few:
list_template(This works because ID’s create global variables in JS)
Once we have a reference to the template, we can start to work with it. Let’s assume the following code:
const t = document.querySelector("#list_template"); const template = t.content.cloneNode(true);
With this code, we have references to both the template and the document fragment. Of importance is noting that we have cloned the document fragment. This allows us to re-use the template. Otherwise, we would be moving the nodes out of the document fragment and moving them into the DOM, thereby emptying the template for any future use. That would kind of defeat the purpose. By cloning the fragment, we have new nodes that we can modify and use the way we want.
Let’s make this useful to us by adding some real data into the mix. Consider the following code:
const list = ["rubber ducks", "real ducks"]; const t = document.querySelector("#list_template"); const template = t.content.cloneNode(true); const items = template.querySelectorAll("li"); items.forEach((li, index) => li.textContent = list[index]); t.parentNode.appendChild(template);
(Editor’s note: Code does not work in IE11. If you’re using IE11, you can’t use template tags anyway. Please upgrade your browser.)
Walking through this, we have a list of some hardcoded data, though of course you could get data from a server for this. We then get a reference to the template itself, we recursively clone the contents of the template into a new variable. Doing a
querySelector on the contents, we get an array of the
<li> tags which we iterate through and update the
textContent property of each
<li> with the corresponding element of the data array. Our final step is to append the compiled template to the
parentNode of the template tag itself.
As with every web technology, there are some common gotchas. For example, according to the spec for the template tag, it should only be inside of
<colgroup> without a span attribute. You wouldn’t want to next
<template> tags inside of
<div> tags or deep within the DOM of your document. Keep
<template> tags as a top-level tag.
<script> tags inside of
<template> tags is interesting. When the page is loaded, the
<script> tag will NOT be processed by the browser. This is due to the inert nature of the document fragment. However, the script will be evaluated as soon as it is added to the DOM, as you would expect.
As templates are essentially Web Components, it is going to work best if there is one outer tag which contains all of the HTML markup which you are wanting to include. Similar to how a React component would be built, this means that by cloning the document fragment recursively, you get one outer node and all the enclosed children of it. Please note that recursive cloning is the key here. That is what the
true param to the
cloneNode function is accomplishing.
Don’t put an ID attribute on an element in a template unless you are sure that you will ONLY ever be accessing that template and adding it to the DOM once. The ID is not registered as part of the DOM until the template has been cloned and appended to the parent node.
Template tags are non-rendered document fragments which you can access via scripting to be able to populate with any data you wish. Cloning them is the best practice, so that you can re-use them later. By making them top-level tags (children of the body) and assigning an ID to each template, you can embed many page fragments which are effective for being able to populate your page after the initial load.
Please feel free to reach out at don (at) donburks.com with any comments you may have on this post.