HTML Templates

Up to this point, we've been generating our HTML pages by concatenating snippets of html, i.e.:

function buildGallery(imageTags) { var html = '<!doctype html>'; html += '<head>'; html += '<title>' + config.title + '</title>'; html += '<link href="gallery.css" rel="stylesheet" type="text/css">' html += '</head>'; html += '<body>'; html += ' <h1>' + config.title + '</h1>'; html += ' <form method="GET" action="">'; html += ' <input type="text" name="title">'; html += ' <input type="submit" value="Change Gallery Title">'; html += ' </form>'; html += imageNamesToTags(imageTags).join(''); html += ' <form action="" method="POST" enctype="multipart/form-data">'; html += ' <input type="file" name="image">'; html += ' <input type="submit" value="Upload Image">'; html += ' </form>'; html += '</body>'; return html; }

While this approach works, it has many drawbacks. It is much harder to type HTML quickly and accurately when we have to make sure it is also properly formatted as a string, and the development tools for HTML (like syntax highlighting) aren't available. And it just feels clunky.

What we'd like to do is write our page in HTML, and somehow insert the results of JavaScript execution, i.e.:

<!doctype html> <html> <head> <title> <%= title %></title> </head> <body> </body> </html>

Where <%= title %> is replaced with the value of title. This is the basis of HTML templates - HTML files with a little bit of embedded JavaScript (or other programming language) which allow us the dynamic generation features of a programming language with the simplicity of writing HTML.

Template Approaches

There are many different ways of denoting the embedded script currently in use. The <%= %> is common to PHP, ASP, embedded Ruby, and embedded JavaScript. Additionally, for many of these templates, a <% %> indicates a block of code to execute but not insert into the final html, and some also add <%~ %> to differentiate between inserting HTML escaped and unescaped strings.

Alternative formats include {{ }} used by Mustache, Jade, Handlebars, and the @ of Microsoft's Razor. Additionally, some template approaches seek to simplify the writing of HTML as well, as does HAML:

%html %head %title=Gallery %body

While this kind of extreme template requires learning a new syntax for defining HTML pages, its advocates claim it speeds up typing HTML considerably.

Defining a Template

Let's add template-based rendering to our gallery project. First, we need to create a template to be our HTML - gallery.html, and let's place it into a separate templates directory.

<!doctype html> <html> <head> <title><%= context.title %></title> <link href="gallery.css" rel="stylesheet" type="text/css"> </head> <body> <h1><%= context.title %></h1> <form method="GET" action=""> <input type="text" name="title"> <input type="submit" value="Change Gallery Title"> </form> <%= context.imageTags %> <form action="" method="POST" enctype="multipart/form-data"> <input type="file" name="image"> <input type="submit" value="Upload Image"> </form> </body> </html>

Notice we changed the function calls that had previously created our imageTags to a member of a context object. We'll use this context to pass values into our render function.

Rendering a Template

Let's create a new module for our template library, in template.js. Since we'll be loading templates from file, we need to require the filesystem. We'll also only export one method for now, a render() method.

/** @module template * A module for rendering html templates */ "use strict"; var fs = require('fs'); module.exports = { render: render }

Let's go ahead and define the render() method. It takes two arguments - the name of the template to render, and the context object (the same one from before). We want it to load the template from its file, and render it:

/** @function render * Renders the supplied template using the supplied * context object. * @param {string} template - the name of the template to * render, i.e. 'gallery.html' * @param {object} context - the context (variables) to * use in rendering * @return the template as an HTML snippet */ function render(template, context) { var html = fs.readFileSync('templates/' + template); return html; }

Now we can alter our buildGallery() function in server.js to use our newly minted template library:

// in server.js var template = require('./template'); function buildGallery(imageTags) { return template.render('gallery.html', { title: config.title, imageTags: imageNamesToTags(imageTags).join('') }); }

Note that we're passing, as the second argument, an object with title and imageTags members. This is our context object in the render method.

But if we run our server now, we'll see that our web page has <%= context.title %> and <%= context.imageTags %> printed into it, instead of being evaluated. We'll tackle that next.

Evaluating Embedded JavaScript

We want to replace everything between the <%= and %> tags (and the tags themselves) with the result of evaluating those contents. To this end, we turn to the eval() method of JavaScript. This method evaluates JavaScript code passed to it as an argument.

Ideally, we want to to pull the code out of the <%= %> blocks, use eval() to evaluate it, and then replace it back into the template string. Thus, we'll also look at String.prototype.replace(). This function works much like the replace() functions you've seen before - it can replace all instances of a substring in a string with a new string. But JavaScript's replace() has some even more powerful features.

First, instead of searching for a substring, we can search for a pattern using regular expressions. A good regular expression for our needs is /<%=(.*)%>/g. This captures every character between an opening <%= and closing %>. The . indicates any character, and the * indicates 0 or more characters. The * is also lazy, meaning it will try to match the shortest possible series of characters. Finally, the trailing g indicates this is a global match, so replace will replace all instances of the pattern with a new substring.

Let's see how that works by tweaking our render() method:

function render(template, context) { var html = fs.readFileSync('templates/' + template); return html.replace(/<%=(.*)%>/g, 'code...'); }

Now if we run our server and visit the gallery page, our dynamic parts show the string 'code...'. If we go back to our String.prototype.replace() documentation, we can also note that instead of a substring, the second argument can be a function, in which case the replacement substring is the return value of that function. Further, the first argument supplied to the function by replace is the matched pattern, and subsequent arguments are the capture groups. This means we could evaluate each capture group separately!

function render(template, context) { var html = fs.readFileSync('templates/' + template); return html.replace(/<%=(.*)%>/g, function(match, code){ return eval(code); }); }

But we're not done yet, as eval() operates within a sandbox - it has no access to outside variables. So when our template has <%= context.title %>, context will be undefined. We need to define the context object within the code our eval() call sees. An easy trick to do so is to use JSON.stringify() to convert context to a JSON string, which is a stricter form of the JavaScript object literal notation - so we can treat it as a literal object and assign it to a variable, i.e.:

function render(template, context) { var html = fs.readFileSync('templates/' + template); return html.replace(/<%=(.*)%>/g, function(match, code){ return eval('var context = ' + JSON.stringify(context) + ';' + code); }); }

And voilá, our embedded code works!

Caching Templates

Of course, we're doing something we know we shouldn't - using fs.readFileSync() on each render() request. Since this is a synchronous blocking function that depends on access to the filesystem, it will be slow and prevent us from handling new requests until finished. And since we expect most requests to our server to trigger template rendering... we've just made for a very unscalable web site.

We could swap the fs.readFileSync() for the asynchronous fs.readFile(). This would certainly help, but we can go a step farther. Consider how often templates will change - pretty much only with a new version of the server code. And they will be small, and frequently used. In other words, a great candidate for caching in memory.

Let's add a loadDir() method to our template library to load a whole directory of templates and cache them as an associative array:

module.exports = { render: render, loadDir: loadDir } var templates = {}; /** @function loadDir * Loads the specified directory of templates * and caches them for later use of render(). * @param {string} directory - the directory to load */ function loadDir(directory) { var dir = fs.readdirSync(directory); dir.forEach(function(file) { var path = directory + '/' + file; var stats = fs.statSync(path); if(stats.isFile()) { templates[file] = fs.readFileSync(path).toString(); } }); }

Loading the template directory works much like loading the image directory from building our gallery. First we read the directory contents into an array. Then we get a fs.stats object for each entry, to check that it actually is a file. If it is, we read it and store it into our associate array templates, using its filename as the key.

We use all synchronous methods, as we want the cache completely built before we render any of the templates. That means we'll need to call this method early in our server.js code, before we start listening for requests:

// in server.js, towards the top template.load("templates");

And we'll need to refactor our render() function in the templates.js library to take advantage of the cache:

function render(template, context) { return templates[template].replace(/<%=(.+)%>/g, function(match, code){ return eval('var context = ' + JSON.stringify(context) + ';' + code); }); }