Search for '{{search_term}}'

Handlebars conditional

CMOS is the Code-Maven Open Source podcast that also includes video interviews. Subscribe to this feed RSS feed with your Podcast listener app or via iTunes iTunes.

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 &gt; 40 (expected true)
    {{#iff answer '>' 40}}
      true
    {{else}}
      false
    {{/iff}}

    <br>#iff 42 &gt; 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.

Comments

In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.
comments powered by Disqus