Templated content is huge on the web. Most sites do at least some form of templating either on the server side or the client side, and it's essentially mandatory on the client side if you're writing a single-page application (and wish to retain your sanity).
Outside of sites using Web Components though, any templating on the client side is almost always done using either a dedicated templating library, or an application framework that supports templating.
There is, however, another way: The HTML5 <template>
element.
What exactly is the <template>
element?
Put simply, it provides an easy way to define a reusable fragment of HTML that can be manipulated just like you would the contents of the document itself, but without the overhead of actually updating the DOM or having to compile and parse strings of HTML.
Anything inside of a <template>
tag gets parsed just like regular HTML, except:
- It doesn't get rendered.
-
<script>
tags inside of it don't get run. -
<style>
tags inside of it don't get evaluated. - It doesn't load any external resources (so you won't see any requests for the contents of
<img>
or<embed>
tags). - It can be accessed as a
DocumentFragment
instance via the specialcontent
property of the<template>
element.
That content
property is the powerful bit here. DocumentFragment
instances provide an API for manipulating their contents that's largely the same as the global document
object, so you can manipulate them like their own separate document. On top of that, inserting a DocumentFragment
instance into the DOM is really fast compared to manipulating an element's innerHTML
property (or using insertAdjacentHTML()
), because the DOM structure already exists in memory, so it just needs to get linked into the DOM tree.
So what can you do with it?
A simple example might look like this:
<template id='#closeTemplate'>
<button type="button" class="close" data-dismiss='modal' aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</template>
This should be pretty recognizable to anybody who's used Bootstrap 4 before. It just defines a template for a close icon for a Bootstrap 4 modal.
const closeTemplate = document.querySelector('#closeTemplate').content
for (const modalHeader of document.querySelectorAll('.modal-header')) {
modalHeader.append(closeTemplate.cloneNode(true))
}
This then takes that template, and inserts it's contents at the end of each element with the modal-header
class (right where it should be if you're using regular Bootstrap 4 modals).
The most important thing here is the call to the cloneNode
method of the template content. This method, when called with true
as an argument, creates a completely new DocumentFragment
instance that's an exact copy of the original (this sounds like an expensive operation, but it's actually not that bad, especially for a small template like this). This is important because a given DocumentFragment
can only exist in the DOM in one place, so subsequent calls to any method that would insert it into the DOM will fail (without raising an exception for some reason that I cannot fathom). By creating a copy on each loop iteration, and inserting that into the DOM instead of the original, we avoid this issue.
OK, but what about using it for actual templates?
Of course, most templating isn't as trivially simple as that. In real life, you usually have to put something into the template for it to be useful, and you usually need to make sure certain attributes are set correctly before it gets put in the DOM.
This is where the DocumentFragment
class having an interface almost the same as the global document
object comes in. With this, you can call querySelector
on the fragment just like you would to find elements in the page, and get a real Element
or NodeList
back that you can then manipulate just like if you had requested elements in the page itself.
Consider the following example code:
<template id='modalTemplate'>
<div class='modal fade' tabindex='-1' role='dialog' aria-hidden='true'>
<div class='modal-dialog' role='document'>
<div class='modal-content'>
<div class='modal-header'>
<h5 class='modal-title'></h5>
<button type='button' class='close' data-dismiss='modal' aria-label='close'>
<span aria-hidden='true'>×</span>
</button>
</div>
<div class='modal-body'></div>
<div class='modal-footer'>
<button type='button' class='btn btn-secondary' data-dismiss='modal'>Close</button>
<button type='button' class='btn btn-primary'>Save</button>
</div>
</div>
</div>
</div>
</template>
Now our template is a basic skeleton for a Bootstrap 4 modal. Note that there are no ID attributes (or anything that references them) here. Template contents still need to meet the uniqueness requirement for ID attributes across the whole page, so it's safest to just avoid them in the template itself and populate them from your code as you're using the template.
const modalTemplate = document.querySelector('#modalTemplate')
function createModal(id, title, body) {
const node = modalTemplate.cloneNode(true)
const modal = node.querySelector('.modal')
const modalTitle = node.querySelector('.modal-title')
const modalBody = node.querySelector('.modal-body')
modal.id = id
modal.setAttribute('aria-labelledby', `${id}Title`)
modalTitle.id = `${id}Title`
modalTitle.textContent = title
modalBody.innerHTML = body
document.body.append(node)
return document.querySelector(`#${id}`)
}
And here's a function to turn that template into an actual modal in the document. This adds appropriate id
and aria-labelledby
attributes to the root element of the modal, an appropriate id
and text contents to the title bar, and then adds whatever needs to be in the body of the modal before adding the modal itself to the end of the document's <body>
element.
But wait, there's more!
Because you're doing all of this template construction from JavaScript, you also have the full power of JavaScript available for flow control logic. You'll notice in the example above that we used JavaScript template strings to compute the correct values for the title's ID and the modal's aria-labelledby
attribute, but you're not even limited to that. You can easily do complex flow-control using loops, conditionals, and even try/catch statements.
On top of that, because DocumentFragment
works almost the same as the DOM, you can inject HTML5 templates into other HTML5 templates. For example, the function above could easily be extended to instead accept a DocumentFragment
instance for the modal body, which could itself be created from another HTML template.
By leveraging these tow facts, you can create complex layouts composed of multiple HTML5 templates with little effort.
Additionally, the templates (including the associated JavaScript) tend to be smaller than the equivalent pre-compiled template code for a lot of JavaScript templating libraries (given my own experience, it's about 5-25% smaller than an equivalent pre-compiled lodash or underscore template), they render faster when inserted into the DOM, and you need no special tools to deal with building or checking them, because they're just plain HTML and plain JavaScript.
But surely it's not widely supported?
The HTML5 <template>
element is actually very well supported. Every major browser released in the last 3 years fully supports it, and most released in the past 5 years do too. You can check the exact support data on Can I Use?. As of writing this, it's just short of 95% market share for availability.
The caveat here is that Internet Explorer lacks support (and Opera Mini and the BlackBerry browser, but neither of those has enough market share to matter in many cases). However, there are a number of polyfills out there that will get you proper support in IE 11, and rudimentary support in IE 9 and IE 10, as well as smoothing over some of the issues with the older implementations (the Can I Use? link above includes links to a couple of good polyfills, and there's also one included with the Web Components project).
Now, obviously, this is only really any good if you're not using a full application framework. Most of them do the templating themselves because it's just easier, and thus don't do all that well when you mix them with other templating techniques. On the other hand, if you don't need a full application framework for anything other than templating, this may be an easy alternative that would let you eliminate one of your dependencies (and probably speed up your app too).
Top comments (0)