
jQuery Templates : Beyond The Basics : The Why And How
Web applications are getting more and more advanced and with that complexity and much off what drives this new era in web development is JavaScript. So, we have all encountered the pain of having to concatenate long strings of JavaScript variables and HTML to build up the dynamic section of the DOM we wish to manipulate and what a maintenance nightmare this can quickly become. Anyone that has been writing JavaScript for some time remembers the days of the document.write lines that littered many a JavaScript file and, anyone that has been involved with Java Servlets knows how quickly those bad boys can be become a tangled mess.
Luckily those days are over as a couple of days ago jQuery in a joint announcement by Microsoft announced the three new official plugins that has now become part of jQuery. These plugins have been in the works for some time, has been available for download from Github and, has been overseen by John Resig and other members of the jQuery core team to a large extent. What the announcement means is that these plugins are now part of the jQuery ‘core’ and as such is going to be fully supported, will continue to be enhanced and compatibility with all future versions of jQuery and jQueryUI is guaranteed. During the initial development phase it was also ensures that the developers from Microsoft adhered to the standards set forth by the jQuery project and that the code will be released under the same non-restrictive license that jQuery itself is released under. So all and all, this is good.
But Why? Oh Why?
But why a templating plugin? I believe I already gave a pretty convincing argument above but, if you are still not convinced, here are some more reasons. Templating is all about merging data with structure now, the data structure that you will be using within your templates echoes the format of JSON. What this means is that you server side code no longer needs to send you a pre-populated HTML string but can merely return a JSON object with the key, value pairs that needs to be merged into the HTML structure.
Makes collaboration between designers and developers easier. Once the a naming convention has been established, the designers can go ahead and built the templates and add the hooks into which you will populate the data. You will see what I mean by this a little later when we look at actual examples. Lastly because the templates are not JavaScript, they are cached client side and are not parsed every time the page loads, further saving bandwidth and increasing the performance of your web application.
The How
Now that we have talked about the why, and I have hopefully convinced you that this is a worthwhile endeavor, let’s look at how you would use these templates. I believe the best way to look at this is to approach it from a past and present perspective so, I will first go over a sample of some of the common scenarios you might face on a day to day basis and how it is currently handled and then move on to look at how templates makes your job easier and more maintainable.
But before we jump into rendering templates we need to take a step back. In the article I posted a couple of days ago, I went over the basics of creating, compiling and rendering a template. Today I want to go into a bit more depth about this aspect.
.template
After you wrote your template the first step is to compile your template. So what is a template compiled into? Each template is compiled into a function which is then executed during the render phase, and in the case of the .template() and $.template() functions they are stored in the $.templates map. So, for a template such as this:
<script id="feedTmpl" type="text/x-jquery-tmpl">
<li>${title}</li>
</script>
The compiled output would be as follows:
function anonymous(jQuery,$item) {
var $=jQuery,call,_=[],$data=$item.data;with($data){_.push('<li>');if(typeof(title)!=='undefined' && (title)!=null){_.push($.encode((typeof(title)==='function'?(title).call($item):(title))));}_.push('</li>');}return _;
}
There is a couple of different ways you can use the above two functions to compile a template and there are also different ways in which you can define your templates and pass them into the template functions. Your first option is using and inline template as above. The way you would compile the template would be as follows:
$.template("feedTmpl", $("#feedTmpl"));
Alternatively you could use the following syntax:
$("#feedTmpl").template("feedTmpl");
Once you have compiled the template as above you can reference the template using the name you specified. The template never needs to be recompiled and instead a compiled, cached instance will be returned to the render function. You could also compile your template and store it as a variable.
var feedTmpl = $.template("#feedTmpl");
or, using the alternate syntax:
var feedTmpl = $("#feedTmpl").template();
You can now use this variable name to refer to your compiled template and, as is the case with examples above, no recompilation of the template is needed and a compiled, cached version will be returned to your render function. So, I also mentioned that there are different ways you can define your template and pass it in for compilation. Besides the inline option as demonstrated above, you can create a plain external JavaScript file and define your template as a string variable. Inside your template function call, you then simply pass in the variable of the template you wish to compile, for example, inside a file called templates.js add the following:
var feedTmpl = "<li>${title}</li>";
Include this JavaScript file above your script that will be compiling the template:
<script src="tmpl/templates.js"></script>
Note that I am not giving this script include an id nor am I specifying the type as text/x-jquery-tmpl. The reason is because this time, we actually want the browser to recognize the script as JavaScript and parse it so that our variables inside this external file are made available to the rest of our page. Now we are ready to compile the template. To do this we use the same code as before with one slight change:
$.template("feedTmpl", feedTmpl);
As you can see from the code above, we are simply referencing the variable feedTmpl from our external JavaScript file with the end result being exactly the same. There is one more way to load templates, so to speak, and that is by making an Ajax call that will return the template markup as a string. You can of course also have an Ajax call that returns a JSON object populated with key:value pairs where the value of each key is a string defining separate templates, you can of course also define more than one template inside your external JavaScript file using different variable names, for example:
$.getJSON("tmpl_service.py", function(data) {
$.template("feedTmpl", data.feedTmpl);
});
Right, now that we have explored the different ways in which you can get hold of your templates and how we can compile them, let’s look at rendering these templates.
.tmpl
So, where a call to the template function compiles your template, adds it to the $.templates map, caches it and returns the compiled template, a call to the tmpl function renders your template in the browser but, there is a slight catch. If the template you pass to the .tmpl function is not a compiled template it will will first be compiled before it is rendered. Combine this with a data set that is, for example, an array of say 200 items, your template will be compiled 200 times, once before each render phase, leading to slow rendering that uses a lot of memory.
Best practice therefore suggests that you compile your template before render and then pass the compiled version to the .tmpl function. This is true for all instances except for inline templates which are implicitly cached. Let’s render a template then already. For our first example we will be using Google’s Ajax Feed API to grab the RSS feed of the web design sub-reddit and display the result using jQuery Templates. We are going to be displaying the title, published date and content of the items in the feed. Go ahead and create a HTML page and import jQuery and the jQuery template plugin.
Next add the following to the body of your document:
<article id="feed_container">
<script id="feedTmpl" type="text/x-jquery-tmpl">
<h2>${title}</h2>
<p>${publishedDate}</p>
<div id="content">${content}</div>
</script>
</article>
That is our template, plain and simple and as you can see, this is going to be much easier to maintain going forward. To make use of the Google Ajax Feed API we need one more import. Add the following as the last include statement:
<script src="http://www.google.com/jsapi?key=your-API-key"></script>
Next add the following script block:
<script>
google.load("feeds", "1");
$().ready(function() {
var feed = new google.feeds.Feed("http://www.reddit.com/r/web_design/.rss");
feed.load(function(result) {
});
});
</script>
This takes care of getting the RSS feed to us so let’s display that data. Even though the template we are currently using is an inline template let’s make use of pre-compilation to speed up our rendering. Inside the feed.load function add the following:
$.template("feedTmpl", $("#feedTmpl"));
With the above line we now have a named template that has been compiled and added to the $.templates map. Next, we call the tmpl function passing it the name of the template as well as the data and then telling where we want the results to be added to in terms of the DOM:
$.tmpl("feedTmpl", result.feed.entries).appendTo("#feed_container");
With that one line you are done. We do not have to iterate over each item in the array of feed entries and pass them in one by one to the .tmpl function, jQuery will take care of this internally, you simply pass it the array. Go ahead and open the demo page and enjoy the fruits of your labor.
{{html}}
When you render the template above you might say great, but what about the fact that the above renders the HTML as plain text. That is where the {{html}} template tag comes in. If you log the compiled function before and after wrapping it with the html template tag you will see the difference in rendering, without html it renders as:
function anonymous(jQuery,$item) {
var $=jQuery,call,_=[],$data=$item.data;with($data){_.push('<h2>');if(typeof(title)!=='undefined' && (title)!=null){_.push($.encode((typeof(title)==='function'?(title).call($item):(title))));}_.push('</h2><p>');if(typeof(publishedDate)!=='undefined' && (publishedDate)!=null){_.push($.encode((typeof(publishedDate)==='function'?(publishedDate).call($item):(publishedDate))));}_.push('</p><div id="content"> ');if(typeof(content)!=='undefined' && (content)!=null){_.push($.encode((typeof(content)==='function'?(content).call($item):(content))));}_.push('</div>');}return _;
}
And after:
function anonymous(jQuery,$item) {
var $=jQuery,call,_=[],$data=$item.data;with($data){_.push('<h2>');if(typeof(title)!=='undefined' && (title)!=null){_.push($.encode((typeof(title)==='function'?(title).call($item):(title))));}_.push('</h2><p>');if(typeof(publishedDate)!=='undefined' && (publishedDate)!=null){_.push($.encode((typeof(publishedDate)==='function'?(publishedDate).call($item):(publishedDate))));}_.push('</p><div id="content">');if(typeof(content)!=='undefined' && (content)!=null){_.push((typeof(content)==='function'?(content).call($item):(content)));}_.push('</div>');}return _;
The secret is, the encode function is not called when using the {{html}} template tag and this will cause any HTML that forms part of the data to be rendered as HTML. With this let’s change our template to the following:
<article id="feed_container">
<script id="feedTmpl" type="text/x-jquery-tmpl">
<h2>${title}</h2>
<p>${publishedDate}</p>
<div id="content">{{html content}}</div>
</script>
</article>
Now let’s open the new demo page again. This time any HTML that is part of the return value is rendered as HTML and not plain text. When rendering the template, besides passing in the data, you can also pass in an optional options map that will extend the default tmplItem data structure and is thus available to the template. Let’s have a look at a sample where we make use of this ability and at the same time look at how much cleaner building tables with templates can be.
Create a new HTML document and include the same JavaScript files as for the previous example. For this example we are going to be using some local data in the form off an Object array. Add the following script block to your new page:
<script>
$().ready(function() {
var countryLanguages = [
{Country: "South Africa", Languages: ["English", "Afrikaans", "Ndebele", "Sotho"]},
{Country: "Seychelles", Languages: ["Seychellois Creole", "English", "French"]},
{Country: "Switzerland", Languages: ["German", "French", "Italian", "Romansh"]}
];
});
</script>
Inside the body of your document add the following table:
<table id="countries"> <thead> <tr> <th>Country</th> <th>Number of Languages</th> <th>Languages</th> </tr> </thead> <tbody id="coutry_data"> </tbody> </table>
Next we need to create our template. We will be using an inline template again as in the previous examples so, inside the tbody of the table add the following template:
<script id="countriesTemplate" type="text/x-jquery-tmpl">
<tr>
<td>${Country}</td>
<td>${Languages.length}</td>
<td>${$item.getLanguages(" : ")}</td>
</tr>
</script>
I believe from the above you can already see how much cleaner and more maintainable this code is. Ok, let’s hook everything up. First we will compile our template:
$.template("countriesTmpl", $("#countriesTemplate"));
With our template compiled we can now move to the render phase:
$.tmpl("countriesTmpl", countryLanguages).appendTo("#coutry_data");
Now normally this would be it but as I mentioned, when using the tmpl function you can also pass in an options map. If you look back at the template you will see in the last of the table columns there is a function call made, but where does this function come from? Well, if you were to open the page now, it will cause an exception and the template will not render. That is because we still need to write that function and make it available to the template.
Change the tmpl function to the following:
$.tmpl("countriesTmpl", countryLanguages, {
getLanguages: function(separator) {
return this.data.Languages.join(separator);
}
}).appendTo("#coutry_data");
With the above we have now extended the tmplItem data map and added a function called getLanguages that accepts a parameter that will be the separator between the various languages. The options map do not have to consist of functions though and you can pass in variables in the same way. The next question you may have is where does $item come from. Each time a data item is rendered with the template a template item is create which is a associated with the tmplItem data structure. As mentioned before, when you pass in an options map with the tmpl function the tmplItem data structure is the one that is extended with the new values.
You can access these using either the $.tmplItem(), the .tmplItem() function or the $item template varialbe and this is then how we are able to call $item.getLanguages(” : “) and recieve a string containing all of the languages for the current country. With that, let’s open the demo page and see this in action. To make things look a little better, you can add the following style rules for your table:
table {
margin-bottom:.7em;
border-collapse:collapse;
}
th {
background-color:#1f4348;
color:#fff;
padding:.5em;
border-bottom:2px solid #102224;
}
td {
background-color:#edecec;
padding:.5em;
border-top:1px solid #fff;
}
We have gone over a lot so far but there is still more. To close out the article we are going to look over an example in three iterations. First without the use of templates, then with the use of templates and finally explore the {{each}} and {{if}} template tags. We’re going to pull remote data from Flickr and integrate a simple jQuery plugin I wrote a while ago to overlay some attribution information over the images.
Flickr Image List
Create a new HTML page and include the following in the header:
<link rel="stylesheet" href="css/default.css" media="screen" /> <link rel="stylesheet" href="css/attribute.css" media="screen" /> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script> <script src="js/jquery.attribute.js"></script>
Inside the body of your document add the following:
<article id="img_list"></article>
To load our data from Flickr add the following script block:
<script>
$.getJSON('http://api.flickr.com/services/feeds/photos_public.gne?tags=border collie&format=json&jsoncallback=?', function(data) {
$(data.items).each(function(i, flickrData) {
});
});
</script>
Inside our callback we are going to iterate over each of the items and build up our list of images. Add the following line inside the each block to build our HTML:
$("#img_list").append("<p><img src='" + flickrData.media.m + "' title='" + flickrData.title + "' rel='attribute|" + flickUser(flickrData.author) + "|" + flickrData.link + "' class='flickr_img' /></p>");
Above you can see the familiar string concatenation that would be the current way you would handle this scenario, especially the rel attribute’s values are a bit tricky. One last thing we want to add is to limit the amount of results we render to five:
if(i == 5) return false;
As with the languages in the previous example we here also need a function to extract just the author’s name from the author object property. Add the following function to your script block:
var flickUser = function(author) {
return author.substring(author.indexOf("(") + 1, author.lastIndexOf(")"));
}
Lastly we need to call the attribute plugin. Because the attribute plugin needs to act on the images we load from Flickr and they will only be available once the Ajax call has completed we need to wrap our plugin call in the ajaxComplete function:
$(document).ajaxComplete(function() {
$("body").attribute({message: ""});
});
With that we are ready to test out the page. Now that you have gone through the first iteration and saw what it is we work towards let’s change this to use the jQuery Template plugin. Firs thing we need to do as add the import for the plugin:
<script src="js/jquery.tmpl.js"></script>
Inside the body tag where you have the image list article tag add the following template:
<script id="imagesTmpl" type="text/x-jquery-tmpl">
<p><img src='${media.m}' title='${title}' rel='attribute|${$item.getAuthor(author)}|${link}' class='flickr_img' /></p>
</script>
Already from the above you can see the huge improvement in syntax. Next we need to change the JavaScript to make use of our template. Inside the Ajax callback change the current code to the following:
$.template("imageTmpl", $("#imagesTmpl"));
$.tmpl("imageTmpl", data.items, {
getAuthor: function(author) {
return author.substring(author.indexOf("(") + 1, author.lastIndexOf(")"));
}
}).appendTo("#img_list");
Nothing new above and as you can see we added the function to the tmplItem map and can therefore use it inside the template as we do above. That is all the changes we need to make. With this much cleaner code base we are ready to test out the our new page. One thing you will notice however is that we are loading much more then just five images and we need to remedy this.
To only load the five images we are going to explore two of the template tags that is available to us. The first small change we need to make is to pass the Object into the tmpl function as apposed to the array, that is because we are going to use the {{each}} template tag to iterate over the items in the Object. Let’s then go into the code in the Ajax callback and make the required change:
$.tmpl("imageTmpl", data, {
getAuthor: function(author) {
return author.substring(author.indexOf("(") + 1, author.lastIndexOf(")"));
}
}).appendTo("#img_list");
As you can see from the above the only thing we are changes is to pass in the data Object returned from Flickr and data.items. That is it as far as the changes we need here, the rest will all happen in the template. First thing we need to do is wrap our paragraph that contains the image tag with the {{each}} tag as follows:
{{each items}}
<p><img src='${$value.media.m}' title='${$value.title}' rel='attribute|${$item.getAuthor($value.author)}|${$value.link}' class='flickr_img' /></p>
{{/each}}
Straightforward enough. One thing you will notice when looking at the HTML in between the {{each}} tags is the $value variable, this along with the $index variable is automatically exposed by the jQuery Template so $value, is like item. With the above we will still be loading more then the five items we want so we need to further refine our template. I mentioned that another variable that is exposed by {{each}} is the $index variable, we are going to be using this in combination with the {{if}} template tag to ensure we only load five items.
Add the following inside the {{each}} tags:
{{if $index <= 4}}
<p><img src='${$value.media.m}' title='${$value.title}' rel='attribute|${$item.getAuthor($value.author)}|${$value.link}' class='flickr_img' /></p>
{{/if}}
The $index variable is zero based so we use less than or equal to 4 to ensure we are only loading five items. With that you can fire up the final page. Below is then the complete source for this final page.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Flickr Images with jQuery Templates</title>
<link rel="stylesheet" href="css/default.css" media="screen" />
<link rel="stylesheet" href="css/attribute.css" media="screen" />
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script src="js/jquery.tmpl.js"></script>
<script src="js/jquery.attribute.js"></script>
<script>
$().ready(function() {
$(document).ajaxComplete(function() {
$("body").attribute({message: ""});
});
$.getJSON('http://api.flickr.com/services/feeds/photos_public.gne?tags=border collie&format=json&jsoncallback=?', function(data) {
$.template("imageTmpl", $("#imagesTmpl"));
$.tmpl("imageTmpl", data, {
getAuthor: function(author) {
return author.substring(author.indexOf("(") + 1, author.lastIndexOf(")"));
}
}).appendTo("#img_list");
});
});
</script>
</head>
<body>
<article id="img_list">
<script id="imagesTmpl" type="text/x-jquery-tmpl">
{{each items}}
{{if $index <= 4}}
<p><img src='${$value.media.m}' title='${$value.title}' rel='attribute|${$item.getAuthor($value.author)}|${$value.link}' class='flickr_img' /></p>
{{/if}}
{{/each}}
</script>
</article>
</body>
</html>
I believe strongly that this is a much better way to handle these types of dynamically generated HTML and content with JavaScript and the samples above is not really near to some of the more complex situations one run into. You can download the source for all of these example from the following link. I look forward to your comments.
