2018-07-23 Handlebars inline and block helpers

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.)

CategoryEnglish, CategoryBlog, CategoryJavaScript