Templating in Javascript
In the last few weeks I was doing some work with client side templates. Since most of the webbies seemed to be unfamiliar with the concept and had a wee bit of bother wrapping their heads around it, here’s a short explanation of the whole idea.
Code for this post can be downloaded here or seen here.
Formatting output with javascript
In most (all?) web applications, you’re going to have to display some formatted data at some point. It could be as simple as a personalized greeting, or as complex as a dynamic chart with bells and whistles. Doesn’t matter – at the core, you’re still going to have to convert your data object into some kind of html.
The time honoured – and wrong – way to do this is to concatenate strings together using the + operator. You know, like
$('#greeting').html('Hello ' + user.name+'!');
That looks innocuous enough, but it can quickly get complex – suppose you need to include more variables in there, or simply make it localizable – the placement of words does not follow the same order in every language, so you’d need a different pattern per language. In my experience, most translators are not technical people, so having them translate the expressions is really not a great idea. The concatenated string is also more difficult for you to read, so if you have to make any changes, or end up with a really long chain of text, you’re in for a headache. Add in conditions, collections, and other fun stuff, and it’s really a pain.
How not to go nuts
Most javascript frameworks provide some sort of templating engine, either built-in or as a plugin. For this example I’m going to use Mustache, because it supports some spiffy features which others don’t. The concepts, however, remain pretty much the same, with the exception of the syntax.
Defining a template
The most common way of defining a template is to use a script tag. By giving it a type of “text/template” (some engines use different types, but it shouldn’t make too much of a difference), you tell the browser that it shouldn’t consider the contents of the script tag as javascript, so it won’t try to run it and go boom. In fact, it will quietly ignore the element, allowing you to define your template inside, for example:
<script type='text/template' id='article'> <article data-key='{{id}}' data-permalink='{{permalink}}'> <h1>{{title}}</h1> <h2>by {{author.firstName}} {{author.lastName}}</h2> <section class='summary'>{{summary}}</section> <section class='content'> {{content}} </section> </article> </script>
as you can see, it looks just like normal, plain html – the equivalent as a concatenation would be:
'<article data-key=\'' +id + '\' data-permalink=\'' + permalink + '\'><h1>' + title + '</h1><h2>by ' + author.firstName + ' ' + author.lastName + '</h2><section>' + summary + '</section><section>' + content + '</section></article>'
When you want to use the template, you can get the contents of the script element, and use them. Mustache lets you use it right away; other templating systems might require you to create a template instance first, so check your chosen system’s documentation.
To get the template source, you can use any method of accessing the element’s content, whether it’s jQuery’s .html() function or the standard js innerHTML field. You can then feed this, along with your data to your templating engine. In our case we’re going to use a simple object:
var data = { id: 0, permalink:'http://xyz.com/0', title:'Ender's Game', author:{ firstName:'Orson Scott', lastName:'Card'}, summary: 'Some random text here', content: 'A bit more information here.' }
so by feeding this into the template:
var formattedData = Mustache.to_html($('#article').html(), data);
We get a nice string which we can then put anywhere in our document:
<article data-key='0' data-permalink='http://xyz.com/0'> <h1>Ender's Game</h1> <h2>by Orson Scott Card</h2> <section class='summary'>Some random text here</section> <section class='content'> A bit more information here. </section> </article>
Here you can see that all the placeholders have been replaced with the values from the data object. Mustache is clever enough to use nested objects too, as in the case of the author field; you can access properties inside a child object like you would in plain js.
Another handy feature
Apart from handling nested objects, Mustache can also handle nested collections. This is obviously handy as applications almost always have to deal with collections of things. We just need to tell Mustache that a placeholder is for a collection by prefixing the placeholder with a #, and define a subtemplate for it, like so:
<aside class='related'> <h2>Related articles</h2> {{#related}} <article data-key='{{id}}' data-permalink='{{permalink}}'> <h1>{{title}}</h1> <h2>by {{author.firstName}} {{author.lastName}}</h2> </article> {{/related}} </aside>
Using the data:
var data = { ... related:[ { id: 1, permalink: 'http://xyz.com/1', title: 'Speaker for the dead', author:{ firstName:'Orson Scott', lastName:'Card'}}, { id: 2, permalink: 'http://xyz.com/2', title: 'The Fires of Heaven', author:{ firstName:'Robert', lastName:'Jordan'}} ] }
We get
<aside class='related'> <h2>Related articles</h2> <article data-permalink='http://xyz.com/1' data-key='1'> <h1>Speaker for the dead</h1> <h2>by Orson Scott Card</h2> </article> <article data-permalink='http://xyz.com/2' data-key='2'> <h1>The Fires of Heaven</h1> <h2>by Robert Jordan</h2> </article> </aside>
Not all template engines provide this kind of support, but it’s generally easy to work around it by templating while iterating a collection and appending to a placeholder.
Some gotchas
The template syntax might not be appreciated by the server side language you are using in some cases – for example, JSPs don’t like the Prototype template syntax. You may be able to customize or configure your templating engine to work around this if necessary.
One thing to keep in mind is the difference between the template source, the template instance (if there is one) and the templated data – I’ve seen a few people try to use the template itself as output, which won’t work. So: The template source is the markup with the placeholders that we defined earlier. The template instance is something which will take our data and our template source and put them together, and finally, templated data is the final product we want to use, with the values in place. An example which sounds like I pulled it out my butt (because that’s exactly what happened), but somehow helped some people remember, is this: the output is a cookie; the template is a cookie shaper, and the template source is the design of the shaper. Remember which one you want to eat.
Closing off
If you didn’t already use templates, give them a try for your own sake. They’re much, much easier to handle than concatenations.
“And if you do not listen, then the hell with you!”
-Conan The Barbarian
Reference: | Templating in Javascript from our WCG partner Karl Agius at the The Simple Part blog. |