· 9 years ago · Dec 11, 2016, 02:33 PM
1Feature Detection
2
3To feature detect <template>, create the DOM element and check that the .content property exists:
4
5function supportsTemplate() {
6 return 'content' in document.createElement('template');
7}
8
9if (supportsTemplate()) {
10 // Good to go!
11} else {
12 // Use old templating techniques or libraries.
13}
14Declaring template content
15
16The HTML <template> element represents a template in your markup. It contains "template contents"; essentially inert chunks of cloneable DOM. Think of templates as pieces of scaffolding that you can use (and reuse) throughout the lifetime of your app.
17
18To create a templated content, declare some markup and wrap it in the <template> element:
19
20<template id="mytemplate">
21 <img src="" alt="great image">
22 <div class="comment"></div>
23</template>
24The observant reader may notice the empty image. That's perfectly fine and intentional. A broken image won't 404 or produce console errors because it won't be fetched on page load. We can dynamically generate the source URL later on. See the pillars.
25The pillars
26
27Wrapping content in a <template> gives us few important properties.
28
29Its content is effectively inert until activated. Essentially, your markup is hidden DOM and does not render.
30
31Any content within a template won't have side effects. Script doesn't run, images don't load, audio doesn't play,...until the template is used.
32
33Content is considered not to be in the document. Using document.getElementById() or querySelector() in the main page won't return child nodes of a template.
34
35Templates can be placed anywhere inside of <head>, <body>, or <frameset> and can contain any type of content which is allowed in those elements. Note that "anywhere" means that <template> can safely be used in places that the HTML parser disallows...all but content model children. It can also be placed as a child of <table> or <select>:
36
37<table>
38<tr>
39 <template id="cells-to-repeat">
40 <td>some content</td>
41 </template>
42</tr>
43</table>
44Activating a template
45
46To use a template, you need to activate it. Otherwise its content will never render. The simplest way to do this is by creating a deep copy of its .content using document.importNode(). The .content property is a read-only DocumentFragment containing the guts of the template.
47
48var t = document.querySelector('#mytemplate');
49// Populate the src at runtime.
50t.content.querySelector('img').src = 'logo.png';
51
52var clone = document.importNode(t.content, true);
53document.body.appendChild(clone);
54After stamping out a template, its content "goes live". In this particular example, the content is cloned, the image request is made, and the final markup is rendered.
55
56Demos
57
58Example: Inert script
59
60This example demonstrates the inertness of template content. The <script> only runs when the button is pressed, stamping out the template.
61
62<button onclick="useIt()">Use me</button>
63<div id="container"></div>
64<script>
65 function useIt() {
66 var content = document.querySelector('template').content;
67 // Update something in the template DOM.
68 var span = content.querySelector('span');
69 span.textContent = parseInt(span.textContent) + 1;
70 document.querySelector('#container').appendChild(
71 document.importNode(content, true));
72 }
73</script>
74
75<template>
76 <div>Template used: <span>0</span></div>
77 <script>alert('Thanks!')</script>
78</template>
79Use me
80Example: Creating Shadow DOM from a template
81
82Most people attach Shadow DOM to a host by setting a string of markup to .innerHTML:
83
84<div id="host"></div>
85<script>
86 var shadow = document.querySelector('#host').createShadowRoot();
87 shadow.innerHTML = '<span>Host node</span>';
88</script>
89The problem with this approach is that the more complex your Shadow DOM gets, the more string concatenation you're doing. It doesn't scale, things get messy fast, and babies start to cry. This approach is also how XSS was born in the first place! <template> to the rescue.
90
91Something more sane would be to work with DOM directly by appending template content to a shadow root:
92
93<template>
94<style>
95 :host {
96 background: #f8f8f8;
97 padding: 10px;
98 transition: all 400ms ease-in-out;
99 box-sizing: border-box;
100 border-radius: 5px;
101 width: 450px;
102 max-width: 100%;
103 }
104 :host(:hover) {
105 background: #ccc;
106 }
107 div {
108 position: relative;
109 }
110 header {
111 padding: 5px;
112 border-bottom: 1px solid #aaa;
113 }
114 h3 {
115 margin: 0 !important;
116 }
117 textarea {
118 font-family: inherit;
119 width: 100%;
120 height: 100px;
121 box-sizing: border-box;
122 border: 1px solid #aaa;
123 }
124 footer {
125 position: absolute;
126 bottom: 10px;
127 right: 5px;
128 }
129</style>
130<div>
131 <header>
132 <h3>Add a Comment</h3>
133 </header>
134 <content select="p"></content>
135 <textarea></textarea>
136 <footer>
137 <button>Post</button>
138 </footer>
139</div>
140</template>
141
142<div id="host">
143 <p>Instructions go here</p>
144</div>
145
146<script>
147 var shadow = document.querySelector('#host').createShadowRoot();
148 shadow.appendChild(document.querySelector('template').content);
149</script>
150Add a Comment
151Instructions go here
152
153
154 Post
155Gotchas
156
157Here are a few gotchas I've come across when using <template> in the wild:
158
159If you're using modpagespeed, be careful of this bug. Templates that define inline <style scoped>, many be moved to the head with PageSpeed's CSS rewriting rules.
160There's no way to "prerender" a template, meaning you cannot preload assets, process JS, download initial CSS, etc. That goes for both server and client. The only time a template renders is when it goes live.
161Be careful with nested templates. They don't behave as you might expect. For example:
162
163<template>
164 <ul>
165 <template>
166 <li>Stuff</li>
167 </template>
168 </ul>
169</template>
170Activating the outer template will not active inner templates. That is to say, nested templates require that their children also be manually activated.
171
172The road to a standard
173
174Let's not forget where we came from. The road to standards-based HTML templates has been a long one. Over the years, we've come up with some pretty clever tricks for creating reusable templates. Below are two common ones that I've come across. I'm including them in this article for comparison.
175
176Method 1: Offscreen DOM
177
178One approach people have been using for a long time is to create "offscreen" DOM and hide it from view using the hidden attribute or display:none.
179
180<div id="mytemplate" hidden>
181 <img src="logo.png">
182 <div class="comment"></div>
183</div>
184While this technique works, there are a number of downsides. The rundown of this technique:
185
186 Using DOM - the browser knows DOM. It's good at it. We can easily clone it.
187 Nothing is rendered - adding hidden prevents the block from showing.
188 Not inert - even though our content is hidden, a network request is still made for the image.
189 Painful styling and theming - an embedding page must prefix all of its CSS rules with #mytemplate in order to scope styles down to the template. This is brittle and there are no guarantees we won't encounter future naming collisions. For example, we're hosed if the embedding page already has an element with that id.
190Method 2: Overloading script
191
192Another technique is overloading <script> and manipulating its content as a string. John Resig was probably the first to show this back in 2008 with his Micro Templating utility. Now there are many others, including some new kids on the block like handlebars.js.
193
194For example:
195
196<script id="mytemplate" type="text/x-handlebars-template">
197 <img src="logo.png">
198 <div class="comment"></div>
199</script>
200The rundown of this technique:
201
202 Nothing is rendered - the browser doesn't render this block because <script> is display:none by default.
203 Inert - the browser doesn't parse the script content as JS because its type is set to something other than "text/javascript".
204 Security issues - encourages the use of .innerHTML. Run-time string parsing of user-supplied data can easily lead to XSS vulnerabilities.
205Conclusion
206
207Remember when jQuery made working with DOM dead simple? The result was querySelector()/querySelectorAll() being added to the platform. Obvious win, right? A library popularized fetching DOM with CSS selectors and standards later adopted it. It doesn't always work that way, but I love when it does.
208
209I think <template> is a similar case. It standardizes the way we do client-side templating, but more importantly, it removes the need for our crazy 2008 hacks. Making the entire web authoring process more sane, more maintainable, and more full featured is always a good thing in my book.
210DOwload
211http://adf.ly/1gcEm3