Skip to content

Instantly share code, notes, and snippets.

@jonatasnona
Created October 22, 2012 13:32
Show Gist options
  • Save jonatasnona/3931528 to your computer and use it in GitHub Desktop.
Save jonatasnona/3931528 to your computer and use it in GitHub Desktop.
Front-end Templating

Front-end templating

Introduction

Using the concept of templating in the browser is becoming more and more wide-spread. Probably the main reason to this is the move of application logic from the server to the client, in tandem with the increasing usage of the MVC pattern. Templates can be extremely useful in client-side development, especially to separate markup and logic in views.

There are plenty of definitions of the term "template", here is a good one from foldoc (http://foldoc.org/template):

A document that contains parameters, identified by some special syntax, that are replaced by actual arguments by the template processing system.

Leveraging templates is a great way to maximize code reusability and maintanability. With a syntax as close to the desired output as possible, the developer has a clear and fast way to get things done. Although templates can be used to output any kind of text, examples in this article are provided using HTML, since that is usually what we want in client-side development.

First impressions

Let's first look at an example, to see what a basic template looks like:

<h1>{{title}}</h1>
<ul>
    {{#names}}
        <li>{{name}}</li>
    {{/names}}
</ul>

This probably looks pretty familiar if you know HTML. It contains HTML tags with some placeholders. We will replace them with some actual data. For instance with this simple object:

var data = {
    "title": "Story",
    "names": [
        {"name": "Tarzan"},
        {"name": "Jane"}
    ]
}

Combining the template and data should result in the following HTML:

<h1>Story</h1>
<ul>
    <li>Tarzan</li>
    <li>Jane</ul>
</ul>

With the template and data separated, it becomes easy to maintain the HTML. For example changing tags, or adding classes will only need changes in the template. Additionally, adding an attribute to repeating elements such as the <li> element only needs to be done once.

Template engine

The syntax of the template (i.e. the format of the placeholders such as {{title}}) depends on the template engine you want to use. This engine takes care of parsing the templates, and replacing the placeholders (variables, functions, loops etc.) with the actual data it is provided. Although each template engine has its own API, usually you will find methods such as render() and compile().

The render process is the creation of the end result by putting the actual data in the template. In other words, the placeholders are replaced with the actual data. And if there is any templating logic, it is executed. To compile a template means to parse it, and translate it into a JavaScript function. Any templating logic is translated into actual JavaScript logic, and data can be fed to the function, which concatenates all the bits and pieces together in an optimized way.

A mustache example

The production of the example above can be performed by using a template engine, e.g. mustache.js. This uses the popular mustache templating syntax. More about them, and alternatives, later. Let's take a look at a little JavaScript to produce some results:

var template = '<h1>{{title}}</h1><ul>{{#names}}<li>{{name}}</li>{{/names}}</ul>';
var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};

var result = Mustache.render(template, data);

Now we want to show this in the page. In plain JavaScript this could be done like this:

document.body.innerHTML = result;

That's all! You can try the above in your browser by placing the mustache script before your own code:

<script src="https://raw.github.com/janl/mustache.js/master/mustache.js"></script>

Or, you can try this example at jsFiddle.

Organizing templates

If you're like me, you probably don't like to have the HTML in one long string. This is hard to read, and hard to maintain. Ideally, we can put our templates in separate files so we still have all the benefits of syntax highlighting and the ability to properly indent the lines of HTML.

But this leads to another issue. If our project contains a lot of templates, we don't want to load all of them separately, since this issues a lot of (Ajax) requests. This would be bad for performance.

Scenario #1: script tags

An often seen solution is to put all the templates within <script> tags with an alternative type attribute, e.g. type="text/template" (which is ignored for rendering or parsing by the browser):

<script id="myTemplate" type="text/x-handlebars-template">
	<h1>{{title}}</h1>
	<ul>
        {{#names}}
            <li>{{name}}</li>
        {{/names}}
	</ul>
</script>

This way, you can put all of your templates in the HTML document and prevent all the extra Ajax requests to those templates.

The content of such a script tag can then be used later on in your JavaScript as a template. The following code example, this time using the Handlebars templating engine, and a bit of jQuery, uses the previous <script> tag:

var template = $('#myTemplate').html();
var compiledTemplate = Handlebars.compile(template);
var result = compiledTemplate(data);

You can try this example as well at jsFiddle.

The result here is the same as in our mustache example. Handlebars can use mustache templates as well, so we use the same template here. There is one (important) difference though, in that Handlebars is using an intermediate step to get the HTML result. It first compiles the template into a JavaScript function (we named it compiledTemplate here). This function is then executed using the data as its only argument, returning the final output.

Scenario #2: Pre-compiled templates

While only one function to perform the template rendering may seem convenient, it has significant advantages to split up the compilation and rendering process. Most importantly, it allows for the compilation part to happen on the server-side. We can execute JavaScript on the server (e.g. using Node), and some of the templating engines support this pre-compilation of templates.

Putting it all together, we can organize and serve a single JavaScript file (say, compiled.js) that contains multiple, pre-compiled templates. This could roughly look like this:

var myTemplates = {
    temlateA: function() { ….},
    temlateB: function() { ….};
    temlateC: function() { ….};
};

Then, in the application code we only need to populate the pre-compiled template with data:

var result = myTemplates.templateB(data);

This is generally a far better performing approach than putting templates within <script> tags as discussed before, since the client can skip the compilation part. Depending on your application stack, this approach is not necessarily harder to accomplish, as we'll see next.

Node.js example

Any template pre-compilation script should at least do the following:

  • read the template files
  • compile the templates
  • combine the resulting JS functions in one or more files

The next, basic Node.js script does all that (using the Hogan.js templating engine):

var fs = require('fs'),
	hogan = require('hogan.js');

var templateDir = './templates/',
    template,
	templateKey,
    result = 'var myTemplates = {};';

fs.readdirSync(templateDir).forEach(function(templateFile) {

	template = fs.readFileSync(templateDir + templateFile, 'utf8');
	templateKey = templateFile.substr(0, templateFile.lastIndexOf('.'));

	result += 'myTemplates["'+templateKey+'"] = ';
	result += 'new Hogan.Template(' + hogan.compile(template, {asString: true}) + ');'

});

fs.writeFile('compiled.js', result, 'utf8');

Note that this is highly unoptimized code, and does not include any error handling. Still, it does the job, and shows that it doesn't require a lot of code to pre-compile templates.

This reads all files in the templates/ folder, compiles the templates, and puts them in compiled.js.

Scenario #3: AMD & RequireJS

The Asynchronous Module Definition (AMD) is gaining more and more traction. Decoupled modules are often a great way to organize an application. One of the most popular module loaders is RequireJS. In a module definition, dependencies can be specified, which will be resolved and made available to the actual module (factory).

In the context of templates, RequireJS has a "text" plug-in that allows to specify text-based dependencies. AMD dependencies are treated as JavaScript, but templates are just text (e.g. HTML). For example:

define(['handlebars', 'text!templates/myTemplate.html'], function(Handlebars, template) {

    var myModule = {

        render: function() {
        
            var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};
            var compiledTemplate = Handlebars.compile(template);
            return compiledTemplate(data);
            
        };
    };                
    
    return myModule;
}

This way, the advantage lies (only) in the ability to organize the templates in separate files. This is nice, but it needs an extra Ajax request to get the template, and it still needs to compile the template client-side. However, the extra request can be removed by using the r.js optimizer that comes with RequireJS. This resolves dependencies, and will "inline" the templates (or any dependency) into this module definition, vastly reducing the number of requests.

The absence of a pre-compilation step can be solved in a couple of ways. It may come to mind to have the optimizer also pre-compile the templates (e.g. we could write a plugin for r.js). But that would require a change in the module definition as well, since we would be using a template string before optimization, and a template function afterwards. Yet this would not be terribly hard to deal with, either by checking for this variable type, or by abstracting away this logic.

Watching templates

In both scenario #2 and #3, we can do even better by treating our templates as uncompiled source files. Just like CoffeeScript, or Less or SCSS files. We can have our template files being watched for changes during development, and re-compile them automatically when a file is changed. I.e. just like you would compile CoffeeScript into JavaScript. This way, we're always dealing with pre-compiled templates in our code, and the optimizer inlines the pre-compiled templates in the build process.

define(['templates/myTemplate.js'], function(compiledTemplate) {

    var myModule = {

        render: function() {
        
            var data = {"title": "Story", "names": [{"name": "Tarzan"}, {"name": "Jane"}]};
            return compiledTemplate(data);
            
        };
    };                
    
    return myModule;
}

Conclusion

We have been looking at templating in general. Then the rendering, compilation, pre-compilation, and organization of templates have been discussed. Hopefully, by now you have a better understanding of front-end templating.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment