Handlebars conditional
The Handlebars JavaScript templating engine provide a single if-conditional with an optional else, but that if-statement can only handle a single value, not an expression. You can write
{{#if name}} .. {{/if}}
but you cannot write
{{#if name == 'Foo'}} .. {{/if}}
Let's create a Handlebars helper that will provide this functionality.
if conditional
Before creating the helper though, let's see a full example using the plain if statement. There are two values in the data object: cond1 and cond2, true and false respectively. The rest of the JavaScript code is just fetching the template and letting Handlebars process the data.
examples/js/handlebars_if.js
var data = { 'cond1' : true, 'cond2' : false, }; document.getElementById('show').addEventListener('click', function () { var source = document.getElementById('text-template').innerHTML; var template = Handlebars.compile(source); var html = template(data); document.getElementById('content').innerHTML = html; });
The template itself has two entries like this using each one of the variables:
{{#if cond1}} true {{else}} false {{/if}}
The full html code is this:
examples/js/handlebars_if.html
<html> <head> <title>Handlebars conditionals</title> <script id="text-template" type="text/x-handlebars-template"> <br>#if cond1 (expected true) {{#if cond1}} true {{else}} false {{/if}} <br>#if cond2 (expected false) {{#if cond2}} true {{else}} false {{/if}} </script> </head> <body> <button id="show">Show</button> <hr> <div id="content"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script> <script src="/try/examples/js/handlebars_helpers_if_eq.js"></script> </body> </html>Try!
You can try it by clicking on the Try link. In the new page the "show" button will trigger the process.
if_eq
In the next example we have implemented a Handlebars helper called if_eq. It expects two parameters and will compare them using ==. The helper itself looks like this:
Handlebars.registerHelper('if_eq', function(a, b, opts) { if (a == b) { return opts.fn(this); } else { return opts.inverse(this); } });
The template using this looks like this: (name is an attribute passed to the template function.)
{{#if_eq name 'Foo'}} true {{else}} false {{/if_eq}}
The full JavaScript file also contains the data object and the code we had earlier combining the template with the data:
examples/js/handlebars_helpers_if_eq.js
var data = { 'name' : 'Foo', }; document.getElementById('show').addEventListener('click', function () { var source = document.getElementById('text-template').innerHTML; var template = Handlebars.compile(source); var html = template(data); document.getElementById('content').innerHTML = html; }); Handlebars.registerHelper('if_eq', function(a, b, opts) { if (a == b) { return opts.fn(this); } else { return opts.inverse(this); } });
The full HTML file including the template looks like this:
examples/js/handlebars_helpers_if_eq.html
<html> <head> <title>Handlebars conditionals</title> <script id="text-template" type="text/x-handlebars-template"> <br>#if_eq name Foo (expected true) {{#if_eq name 'Foo'}} true {{else}} false {{/if_eq}} <br>#if_eq name 'Bar' (expected false) {{#if_eq name 'Bar'}} true {{else}} false {{/if_eq}} </script> </head> <body> <button id="show">Show</button> <hr> <div id="content"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script> <script src="/try/examples/js/handlebars_helpers_if_eq.js"></script> </body> </html>Try!
You can try it after clicking on the "Try" link.
Uncaught Error: if_eq doesn't match if - 3:7
When I encountered this error it took me quite a while to figure out what went wrong. It might have been just something blocking my mind, I am not sure. Can you spot the problem in the next example:
examples/js/handlebars_helpers_if_eq_typo.html
<html> <head> <title>Handlebars conditionals</title> <script id="text-template" type="text/x-handlebars-template"> <br>#if_eq name Foo (expected true) {{#if_eq name 'Foo'}} true {{else}} false {{/if}} <br>#if_eq name 'Bar' (expected false) {{#if_eq name 'Bar'}} true {{else}} false {{/if_eq}} </script> </head> <body> <button id="show">Show</button> <hr> <div id="content"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script> <script src="/try/examples/js/handlebars_helpers_if_eq.js"></script> </body> </html>Try!
This happened when I started to convert my if conditional to an if_eq conditional, but I only changed the opening expression from {{#if ...}} to {{#if_eq ...}} but not the closing expression that was left as {{/if}}. Hence the error telling us that if_eq does not match if. Maybe if the error message had the keywords stand out, it would have been easier.
Anyway, look out for such typos. They are a waste of time. Make some more interesting bugs!
iff - for other conditionals
Finally we got to build the more generic helper for conditional expressions. I called it iff. I am aware of the mathematical meaning of it, but it just looked cute and short to be used for a generic comparision helper. The idea is that I'd like to be able to write expressions like these:
{{#iff name '==' 'Foo'}}
and like this:
{{#iff answer '>' 40}}
In the sample I've created two templates. In the first template I used there are 3 such conditionals. In the second template there is a single conditional: {{#iff 4 '*' 5}} that I included just to show what will happen if we supply an operator that is not supported by the iff helper. I also added two buttons, one to process and show the first template and one to process and show the second template.
examples/js/handlebars_conditionals.html
<html> <head> <title>Handlebars conditionals</title> <script id="text-template" type="text/x-handlebars-template"> <br>#iff name '==' 'Foo' (expected true) {{#iff name '==' 'Foo'}} true {{else}} false {{/iff}} <br>#iff 42 > 40 (expected true) {{#iff answer '>' 40}} true {{else}} false {{/iff}} <br>#iff 42 > 50 (expected false) {{#iff answer '>' 50}} true {{else}} false {{/iff}} </script> <script id="text2-template" type="text/x-handlebars-template"> <br>Exception! {{#iff 4 '*' 5}} true {{else}} false {{/iff}} </script> </head> <body> <button id="show">Show</button> <button id="show2">Try invalid</button> <hr> <div id="content"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/3.0.1/handlebars.min.js"></script> <script src="/try/examples/js/handlebars_conditionals.js"></script> </body> </html>Try!
The JavaScript file has the data, the code reacting to clicks and processing the templates and the the partial(!) implementation of the iff helper. Basically it is just a giant switch statement with a separate case for each valid operator. The default behavior, when the given operator is not handled by any of the case statements, is to throw an exception.
examples/js/handlebars_conditionals.js
var data = { 'cond1' : true, 'cond2' : false, 'name' : 'Foo', 'answer' : 42 }; document.getElementById('show').addEventListener('click', function () { var source = document.getElementById('text-template').innerHTML; var template = Handlebars.compile(source); var html = template(data); document.getElementById('content').innerHTML = html; }); document.getElementById('show2').addEventListener('click', function () { document.getElementById('content').innerHTML = 'Look at the console!'; var source = document.getElementById('text2-template').innerHTML; var template = Handlebars.compile(source); var html = template(data); document.getElementById('content').innerHTML = html; }); Handlebars.registerHelper('iff', function(a, operator, b, opts) { var bool = false; switch(operator) { case '==': bool = a == b; break; case '>': bool = a > b; break; case '<': bool = a < b; break; default: throw "Unknown operator " + operator; } if (bool) { return opts.fn(this); } else { return opts.inverse(this); } });
#compare
After reaching this point I found out that there is already an implementation of such a helper called #compare. It can be found among the comparison helpers.
In any case I think it was interesting to see how to build and use this.
Published on 2015-04-22