Handlebars is the templating system that I chose when writing Concussion, the engine that powers this blog. It’s one of the big names (I’d heard of it even though I’d never done any Node work before) and it has built-in support for Express via handlebars-express. Since it’s obviously so widely used, I figured it would be fine for my purposes. And while everything turned out fine in the end, I had to change the arrangement of responsibilities in my app to get around some unexpected problems, because as it turns out, Handlebars is logicless.1
The README points this out right at the top:
Handlebars.js is an extension to the Mustache templating language created by Chris Wanstrath. Handlebars.js and Mustache are both logicless templating languages that keep the view and the code separated like we all know they should be.
Which is all it has to say about the matter. Seems reasonable enough. If you’re doing anything approaching MVC, the view should be responsible solely for how the data is presented to the user. It shouldn’t do anything.
Handlebars takes this to a bit of an extreme.
There’s logicless, and there’s Logicless.
I think we can all agree that views (“templates” in Handlebars) shouldn’t directly act on models and shouldn’t implement business logic, but I do think that they should be masters of their own domain when it comes to turning data into something the user can see and interact with. By definition, they are a transform on a set of data. Handlebars recognizes this, and allows you to use all sorts of helper functions. You can include or omit a block of content based on the presence of a certain variable, call a function to properly format a value, or shell out to a sub-view (or “partial”).
if block, although you wouldn’t know it by reading the docs:
You can use the if helper to conditionally render a block. If its argument returns
, Handlebars will not render the block.
if block can only accept a value (which could be a variable or the output of another helper). And because you can’t use an expression, you can’t do comparisons.
This should be a requirement of any templating engine.
Maybe I’ve just been spoiled by using PHP in the past, where (at least in CakePHP, the framework I’ve worked in) the templates are just regular PHP files, but I think having the basic ability to perform comparisons is absolutely central to a view’s ability to do its job. I want to be able to show (or not show) a section based on the view’s own choosing, not because a value is truthy or falsey.
This is a super simple example. Here’s how I want to accomplish a pagination view:
- If the current page is greater than 1, show a link that navigates to page
current - 1
- If the current page is less than the number of pages, show a link that navigates to page
current + 1
However, in Handlebars I have to do extra work in the controller to accomplish this:
- If the current page is greater than 1, create a new variable
previousPage = current - 1
- If the current page is less than the number of pages, create a new variable
nextPage = current + 1
Then in the template:
previousPageexists, show a link that navigates to page
nextPageexists, show a link that navigates to page
In addition to making more steps, this increases coupling between the controller and the view, something we should be always striving to reduce.2
There’s a long and contentious discussion on Handlebars’ GitHub repository. The maintainers of Handlebars are gently sticking to their guns, which is understandable. Some of the commenters on that discussion are extremely dismissive. But there are many commenters who wish for more expressiveness in
if blocks. Mike O’Brien sums it up best:
An if helper is there, IT IS LOGIC. So the whole logic-less argument is a total straw man
I totally agree with and understand not having business logic in templates. But unless you’re doing extremely trivial templates your going to have some view logic there. Handlebars obviously recognizes this since the if helper exists. Working around a hamstringed if helper by adding a bunch of view logic in your code is not the answer […]
- Prepare your data in such a way that your templates don’t need to perform any math or comparisons. This is what I’ve done – basically what I showed in the pagination example.
- Build an unwieldy set of helpers (or one multi-purpose helper) as shown here.
Think I’m wrong? Got something to add? Let me know on Twitter!
Although you have to make it to their GitHub README to find out. Their homepage makes no mention of this limitation. ↩
In this particular example there’s not a huge difference in how coupled things are, but the fact that I had to change what happens in my controller in order to make my view happy irks me enormously. We didn’t need to provide more data, the view just required it to happen in a certain way. That’s coupling. ↩