Simple, maintainable templating with JavaScript
One of my principles of Maintainable JavaScript is to keep HTML out of JavaScript. The idea behind this principle is that all markup should be located in one place. It’s much easier to debug markup issues when you have only one place to check. I always cringe when I see code such as this:
function addItem(list, id, text){
var item = document.createElement("li");
item.innerHTML = "<a href=\"/view/" + id + "\">" + text + "</a>"; //ick
item.id = "item" + id;
list.appendChild(item);
}
Whenever I see HTML embedded inside of JavaScript like this, I foresee a time when there’s a markup issue and it takes far longer than it should to track down because you’re checking the templates when the real problem is in the JavaScript.
These days, there are some really excellent templating systems that work both in the browser and on the server, such as Mustache and Handlebars. Such templating systems allow all markup to live in the same template files while enabling rendering either on the client or on the server or both. There is a little bit of overhead to this in setup and preparation time, but ultimately the end result is a more maintainable system.
However, sometimes it’s just not possible or worthwhile to change to a completely new templating system. In these situations, I like to embed the template into the actual HTML itself. How do I do that without adding junk markup to the page that may or may not be used? I use a familiar but under-appreciated part of HTML: comments.
A lot of developers are unaware that comments are actually part of the DOM. Each comment is represented as a node in the DOM and can be manipulated just like any other node. You can get the text of any comment by using the nodeValue property. For example, consider a simple page:
<!DOCTYPE html>
<html>
<body><!--Hello world!--></body>
</html>
You can grab the text inside of the comment via:
var commentText = document.body.firstChild.nodeValue;
The value of commentText is simply, “Hello world!”. So the DOM is kind enough to remove the opening and closing comment indicators. This, plus the fact that comments are completely innocuous within markup, make them the ideal place to put simple template strings.
Consider a dynamic list, one where you can add new items and the UI is instantly updated. In this case, I like to put the template comment as the first child of the <ul> or <ol> so its location isn’t affected by other changes:
<ul id="mylist"><!--<li id="item%s"><a href="/item/%s">%s</a></li>-->
<li id="item1"><a href="/item/1">First item</a></li>
<li id="item2"><a href="/item/2">Second item</a></li>
<li id="item3"><a href="/item/3">Third item</a></li>
</ul>
When I need to add another item to the list, I just grab the template out of the comment and format it using a very simple sprintf() implementation:
/*
* This function does not attempt to implement all of sprintf, just %s,
* which is the only one that I ever use.
*/
function sprintf(text){
var i=1, args=arguments;
return text.replace(/%s/g, function(pattern){
return (i < args.length) ? args[i++] : "";
});
}
This is a very minimal sprintf() implementation that only supports the use of %s for replacement. In practice, this is the only one I ever use, so I don’t bother with more complex handling. You may want to use a different format or function for doing the replacing – this is really just a matter of preference.
With this out of the way, I am left with a fairly simple way of adding a new item:
function addItem(list, id, text){
var template = list.firstChild.nodeValue,
result = sprintf(template, id, id, text),
div = document.createElement("div");
div.innerHTML = result;
list.appendChild(div.firstChild);
}
This function retrieves the template text and formats it into result. Then, a new <div> is created as a container for the new DOM elements. The result is injected into the <div>, which creates the DOM elements, and then the result is added to the list.
Using this technique, your markup still lives in the exact same place, whether that be a PHP file or a Java servlet. The most important thing is that the HTML is not embedded inside of the JavaScript.
There are also very simple ways to augment this solution if it’s not quite right for you:
- If you’re using YUI, you may want to use
Y.substitute()instead ofsprintf()function. - You may want to put the template into a
<script>tag with a custom value fortype(similar to Handlebars). You can retrieve the template text by using thetextproperty.
This is, of course, a very simplistic example. If you need more complex functionality such as conditions and loops, you’ll probably want to go with a full templating solution.
Disclaimer: Any viewpoints and opinions expressed in this article are those of Nicholas C. Zakas and do not, in any way, reflect those of my employer, my colleagues, Wrox Publishing, O'Reilly Publishing, or anyone else. I speak only for myself, not for them.
Both comments and pings are currently closed.




19 Comments
Interesting article. Thanks!
Are there any benefits using comment node over script tag?
...Andrey on October 11th, 2011 at 7:08 am
A template is something that is necessary to the proper functioning of a piece of code.
I’m not sure I’m convinced that it is a good idea to squirrel away necessary information inside a comment. In most scenarios, comments serve two purposes: to explain the code, and sometimes to disable code temporarily. Here you are suggesting a comment that adds a third purpose, one that will be unexpected and unfamiliar to fresh eyes looking at your code.
What will your successor do, when they come to maintain this code, and see large hunks of HTML that are commented out? It would not be unreasonable for them to start deleting what they perceive as stale code. Why should they expect that other code would depend on a COMMENT to function correctly?
Your initial problem is valid, I think: embedding template bits as strings in JavaScript is ugly and needs a proper solution. However, I don’t think this is the right way to go either.
Chris Nielsen on October 11th, 2011 at 8:33 am
@Andrey – I don’t think there are any measurable benefits. It’s just a personal preference of mine. Your mileage may vary.
Nicholas C. Zakas on October 11th, 2011 at 9:36 am
@Chris – Maintainability is about leaving repeatable and familiar patterns in the code. If the product uses comments to aid templating, then it becomes team knowledge. All patterns are unfamiliar when first encountered, so it’s important that there’s documentation and socialization of the pattern to get over that hump. Case in point: if you ever see this in a site, you’ll now know exactly what it is. If you’re worried about confusion with regular comments, you can always prefix the template, maybe just with the word TEMPLATE and then strip it out.
Nicholas C. Zakas on October 11th, 2011 at 9:40 am
When the template is a little more complicated, I use a hidden block of HTML code instead of comments. Really easy to work with and maintain.
fpiat on October 11th, 2011 at 9:46 am
Nicholas, creative idea and a well-writen article as always, but I’m afraid using comments for this matter provides no pros over using
script type="something"like most js templating libraries do nowadays.So although your prefix suggestion might help mitigate Chris’s valid points, why bother? What do I miss?
Ronny Orbach on October 11th, 2011 at 9:51 am
@fpiat – I used to do that until a colleague pointed out that having potentially unused markup in the document breaks progressive enhancement. Without
display:none, you end up with all kinds of confusing content on the page. With comments, that content is never displayed regardless of CSS.Nicholas C. Zakas on October 11th, 2011 at 9:54 am
I like the concept! I agree though that using comments in a way they weren’t intended will probably cause more issues. No reason you can’t use things for new purposes of course! My one thought, was if a system is set up to remove comments from pages it serves up, it would break this solution. Not sure how common that might be, but just a thought.
Always looking for new ideas and new uses for things though!
twobee on October 11th, 2011 at 10:14 am
I’m sure I’ve become biased by my history with Y.substitute, Y.Lang.sub, and similar object=>{placeholder} systems, but it seems to be more maintainable to use a substitution method based on values in an object hash. Markup changes could result in the order of %s tokens changing, resulting in a requirement to change both the markup template and code that uses the template. %s couples the template to the code that consumes it.
Luke Smith on October 11th, 2011 at 10:19 am
What about using script tags with a custom type attribute? This gets around at least having visible blocks if CSS is disabled or not available.
I really enjoy the idea of placing the template near the target, at least in one-off usages. I wonder how browsers deal with a script tag inside of a ?
Andrew Petersen on October 11th, 2011 at 10:39 am
@Luke – You got me. I was just looking for an excuse to put my
sprintf()function into a blog post. I also preferY.substitute(), which is why I mentioned it towards the bottom.@Ronny,Ronny – Yes, that’s another approach, which is why I mentioned it towards the bottom of the post. I still prefer comments for smaller chunks of HTML, but there’s nothing preventing you from using
scriptwith a custom type.Nicholas C. Zakas on October 11th, 2011 at 11:02 am
We’ve been using the commentText pattern for awhile at Zillow, it’s pretty handy. One big potential pitfall is unescaped double-dashes inside the template, which ends the SGML comment too early and results in fragmentary non-commented output (usually an error). Make sure your wrapper component (if you use one, as we do) escapes the double-dashes of the content passed in.
For example,
<div><!-- I don't like em-dashes -- seriously! --></div>would be<div><!-- I don't like em-dashes \-\- seriously! --></div>when properly escaped. (Otherwise, the div would contain the text “seriously!” when rendered)Daniel Stockman on October 11th, 2011 at 2:40 pm
Supplants provide a much better solution.
Source : http://javascript.crockford.com/remedial.html
Define a teplates object and fill teplates into it.
var templates = {
box : “{title} {fullname}{viewcount}”
};
var box = templates.box.supplant(myobj); // my obj could be an ajax loaded json with data
load box into dom
jaseem abid on October 12th, 2011 at 1:17 am
@Nicolas Zakas. You are right for progressive enhancement and display: none. But I noticed that in previous version of FF (don’t remember exactly the number), multilines comments were buggy.
fpiat on October 12th, 2011 at 4:26 am
@Jaseem – I’ve actually found that to be horrible to use. The big problem is in needing to escape the JavaScript string, which means you don’t get to write HTML as you would normally. Otherwise, you need to handcode it in your JavaScript, which leads back to the original maintainability problem. In fact, this pattern is exactly what led me to use the comment pattern I describe in this post.
Nicholas C. Zakas on October 12th, 2011 at 9:17 am
This is interesting because I was planning to write something on accessing comments with JS… But this does remind me of James Padolsey’s “JSHTML”, which might be of interest here:
http://james.padolsey.com/javascript/introducing-jshtml/
Yeah, James is like 20 years younger than me and can kick my ass any day of the week in anything related to JavaScript.
Louis on October 12th, 2011 at 7:38 pm
The article is helpful, thanks for sharing. After looking at your 3 line sprintf implementation, I am loving JavaScript even more!
Vishal on October 13th, 2011 at 12:37 am
One should keep in mind that XML parsers are allowed to strip all comments from a document when building the DOM – so while this approach may work fine as long as you’re using a content-type
text/html, it might break when you switch to atext/xmlcontent-type (in the future).It might even break today for example when you use AJAX/XMLHttpRequest to load document parts in the background and use
text/xmlfor that.ChrisB on October 13th, 2011 at 1:41 am
You could quite easily work around that, if you modify the sprintf function a little, so that it supports “argument swapping”, similar to for example PHP’s version of sprintf:
function sprintf(text){var args=arguments;
return text.replace(/%([0-9]+)s/g, function(pattern, num){
return (num >= 1 && num < args.length) ? args[num] : pattern;
});
}
alert(sprintf("The %1s contains %2s monkeys.\nThat's a nice %1s full of %2s monkeys.\nAnd this: %3s is not replaced, because there's no value for that.", "tree", 3));
Added benefit, analog to PHP’s version, is that you don’t have to repeat arguments that you want to use more than once in your replacement.
ChrisB on October 13th, 2011 at 2:09 am
Comments are automatically closed after 14 days.