In a project I’m involved in, we use a templating library called Handlebars.js. It is quite nice, and has a slightly less nice documentation. Since I’ve spent a considerable amount of time recently digging through the Internet to find some information on the so-called helpers, I thought I’ll share it here. Expect a series of (not necessarily consecutive) blog posts about Handlebars!
Before we start, please note that if you happen to know that anything here is incorrect, please leave information in the comments – as I said, the official docs are not so good, and some of what I know I know from trial and error.
Let us start with a simple template to warm ourselves up. Here’s a minimal package.json
file:
{ "dependencies": { "handlebars": "^4.0.11" } }
After running npm install
or yarn
, we have our node_modules
and we can start playing with Handlebars. (Normally, we would use them as part of a larger setup, and instead of invoking templates manually, we would probably plug the template machinery into Express.js. Here we just want to feed Handlebars some strings and get some other strings from them.)
const Handlebars = require('handlebars'); let template = Handlebars.compile('{{greet}} {{name}}!'); let context = {greet: 'Hello', name: 'world'}; console.log(template(context));
Run the above script with node
and enjoy your first working template! As you can see, we first require
Handlebars, then we define (“compile”) our template, and finally we console.log
an example, just to see that everything works.
Notice that “compilation” of a template means creating a function which, given the object with the needed variables, returns a string with everything substituted. (Apart from “compilation”, there is also “precompilation”, which is a whole different subject I’m going to blog about another time.)
Now that we have a basic Handlebars setup, we can start experimenting. For the sake of a (contrived) example, assume that we want to add a second sentence to the greeting, but only if we meet someone nice. Let us define a nice
helper, accepting one argument.
const Handlebars = require('handlebars'); Handlebars.registerHelper('nice', function(name) { if (name === 'mbork') { return ' Nice to meet you, mbork!'; } }) let template = Handlebars.compile('{{greet}} {{name}}!{{nice name}}'); console.log(template({greet: 'Hello', name: 'world'})); console.log(template({greet: 'Hello', name: 'mbork'}));
Here, we define a helper called nice
, which checks if the name given is a name of a nice person, and if yes, outputs the second sentence. Notice that if we return undefined
from it (which we implicitly do when name !== 'mbork'
), we get an empty string and not the word “undefined” in the output.
However, the docs of Handlebars mention something called block helpers, like
{{#if ...}} ... {{else}} ... {{/if}}
It turns out that defining those is slightly different than inline helpers like above. (Some information is given in the docs.) The helper function is invoked internally by Handlebars with one parameter more than given in the template. (This is also true for inline helpers, though in that case it’s not necessarily essential.) This parameter is customarily called options
and is an object containining a few useful things: the name of the template (property name
), a property called hash
with named parameters, functions fn
and inverse
(which do very useful stuff we’ll show in a moment) and an object data
, which is still somewhat mysterious to me (but we are currently not interested in it anyway).
The functions fn
and inverse
are used like compiled templates – they accept a context and return a string. The point is in what they return: fn
uses everything from {{#block}}
to {{/block}}
(or {{else}}
), and inverse
uses everything from {{else}}
to {{/block}}
. Let’s look at an example. We’ll generalize our nice
helper to be able to greet the nice person in a special way and everyone else in a different way.
Handlebars.registerHelper('special-greeting', function(name, options) { if (name === 'mbork') { return options.fn(this); } else { return options.inverse(this); } }) let template = Handlebars.compile(` {{#special-greeting name}} Make yourself at home, dear mbork! {{else}} Get off my lawn, {{name}}! {{/special-greeting}} `) console.log(template({name: 'mbork'})); console.log(template({name: 'intruder'}));
As you can see, the output contains a lot of newlines. Usually this is not a problem, since we feed the results of calling templates to HTML anyway.
By the way, now that I’ve written all this down, I can see most of this information in the docs. Still, it might be useful to have it written down explicitly, in one place and in a way I understand them. I hope it might be useful for someone else, too. (And I’m going to write another post on Handlebars or two in the future.)