Comparison block helper for handlebars templates

I really like the handlebars template system, it’s straight to the point, efficient, and the added logic layer to mustache makes it practical for most scenarios.

One limitation i’ve found was that if block helpers can’t actually evaluate an expression, it only tests if the passed value is already true or false, there’s no evaluation of it.

so this:

{{#if unicorns == ponies }}
That's amazing, unicorns are actually undercover ponies
{{/if}}

doesn’t work, bummer.

I initially needed to do just that (the == bit, not the unicorns & ponies assertion) so I wrote a basic block helper that checks if two values are equal.

This is done quite simply like this:

Handlebars.registerHelper('equal', function(lvalue, rvalue, options) {
    if (arguments.length < 3)
        throw new Error("Handlebars Helper equal needs 2 parameters");
    if( lvalue!=rvalue ) {
        return options.inverse(this);
    } else {
        return options.fn(this);
    }
});

You can then rewrite the handlebar template code above as:

{{#equal unicorns ponies }}
That's amazing, unicorns are actually undercover ponies
{{/equal}}

Then I thought, what if I want to check if the unicorns are bigger than the ponies, or if the ponies and unicorns are the same type.. ?

You could easily write a helper for each comparison operator but that would be a bit long.

Handlebars allows you to pass extra named attributes called hashed arguments to the block helper. These can then be retrieved inside the helper through the options.hash attributes.

So I made the equal helper a more generic compare helper. It still takes 2 parameters, but you can also pass an optional operator hashed argument. If you omit the operator argument it default to ==.

Here’s the full code:

Handlebars.registerHelper('compare', function(lvalue, rvalue, options) {

    if (arguments.length < 3)
        throw new Error("Handlerbars Helper 'compare' needs 2 parameters");

    operator = options.hash.operator || "==";

    var operators = {
        '==':       function(l,r) { return l == r; },
        '===':      function(l,r) { return l === r; },
        '!=':       function(l,r) { return l != r; },
        '<':        function(l,r) { return l < r; },
        '>':        function(l,r) { return l > r; },
        '<=':       function(l,r) { return l <= r; },
        '>=':       function(l,r) { return l >= r; },
        'typeof':   function(l,r) { return typeof l == r; }
    }

    if (!operators[operator])
        throw new Error("Handlerbars Helper 'compare' doesn't know the operator "+operator);

    var result = operators[operator](lvalue,rvalue);

    if( result ) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }

});

Gist

And now you can do this:

{{#compare unicorns ponies operator="<"}}
I knew it, unicorns are just low-quality ponies!
{{/compare}}

Have fun!

Edit: Thanks to paul for pointing out in the comments section the misuse of the helper syntax, it should have been a block helper starting with a #.

Edit 2: Mike Griffin has a nice alternative version of this in the comments

47 thoughts on “Comparison block helper for handlebars templates

  1. Just goes to show that the “logicless views” concept is tragically misunderstood concept that has been taken to the extreme.

    Truth is, there are two different types of logic, “view logic” and “data logic”. Yes, you want to keep “data logic” out of your views. But “view logic” –which is logic that simply determines rendering by querying the data, is perfectly sensible.

    Your workaround for handlebars is case in point.

  2. I just tried to implement this helper, but it seems to be failing for me. Do I need to place it anywhere special in the code for Handlebars to register it?

    • Hey Matt,

      You should register the helper after Handlebars is loaded and before you use it in a template.

      What do you mean by “failing”? not working at all, or not comparing properly.

      Check your javascript error log maybe, there might be some clues as to why it’s failing.

      Otherwise, write back with some relevant code you have…

    • Matt,

      you might just be missing the hash character when calling the compare function.

      {{#compare unicorns ponies operator=”<”}} I knew it, unicorns are just low-quality ponies! {{/compare}}

      • @paul.

        No you don’t need the hash for these helpers.

        {{#compare}} and {{compare}} calls the helper with different arguments. If you use the hash “block helper” mode the functions won’t work as-is (afaik)

        I take that back, there definitely should be a #

  3. bendog,

    Oh ok – sorry i only started using handlebars yesterday. Could you pls tell me what the main difference is between {{#compare}} and {{compare}}..

    Contrary to the doco, my experience has been that {{compare}} will evaluate the expression and {{#compare}} calls a helper function?

    I haven’t been able to find a good practical definition – even on thinkvitamin, handlebarjs etc tx paul

    • @paul..

      actually, no you’re right.. should be #compare.. my mistake.

      The # syntax is a block helper, which means it can take an inner context (which must be closed with / )

      So you’ll have either:

      {{somehelper someparam}}

      or

      {{#someblockhelper someparam}} Some inner content {{/someblock_helper}}

      I’ll amend the copy now, thanks for pointing out the mistake in the copy :)

  4. Pingback: Comparison block helper for handlebars templates | doginthehat | Ember.js Reporter | Scoop.it

  5. Here’s a way that’s more readable … (I think anyway)

    Look how the syntax reads …

    =====================================

    
    {{#compare Database.Tables.Count ">" 5}}
    There are more than 5 tables
    {{/compare}}
    

    
    {{#compare "Test" "Test"}}
    Default comparison of "==="
    {{/compare}}
    

    =====================================

    
    Handlebars.registerHelper('compare', function (lvalue, operator, rvalue, options) {
    
        var operators, result;
        
        if (arguments.length < 3) {
            throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
        }
        
        if (options === undefined) {
            options = rvalue;
            rvalue = operator;
            operator = "===";
        }
        
        operators = {
            '==': function (l, r) { return l == r; },
            '===': function (l, r) { return l === r; },
            '!=': function (l, r) { return l != r; },
            '!==': function (l, r) { return l !== r; },
            '<': function (l, r) { return l < r; },
            '>': function (l, r) { return l > r; },
            '<=': function (l, r) { return l <= r; },
            '>=': function (l, r) { return l >= r; },
            'typeof': function (l, r) { return typeof l == r; }
        };
        
        if (!operators[operator]) {
            throw new Error("Handlerbars Helper 'compare' doesn't know the operator " + operator);
        }
        
        result = operators[operator](lvalue, rvalue);
        
        if (result) {
            return options.fn(this);
        } else {
            return options.inverse(this);
        }
    
    });
    
    • Where should I place this piece of code in my app? In app.js (and thus the operators are shared across all views?)? Or the view itself where I want to use the operator?

        • Hi Ben,

          Nope, not ember, just express… I’m a complete n00b with node :-)

          For the clueless like me… place this in your app.js or server.js root file…

          // set handlebars as the templating engine :-} app.set(‘view engine’, ‘hbs’); app.set(‘views’, __dirname + ‘/app/views’);

          // hbs.handlebars is the handlebars module hbs.handlebars === require(‘handlebars’);

          // register handlebars helpers ============

          hbs.registerHelper(‘compare’, function (lvalue, operator, rvalue, options) {

          (( continue with Mike’s code above ))

  6. Hi ,

    I think there is an issue with this helper

    Handlebars.registerHelper('compare', function(lvalue, rvalue, options) {

    This considers lvalue and rvalue as strings. It does not evaluate the expression.For example This fails to compile. {{#compare 5 6 operator="=="}}Check equality{{/compare}} The above one throws an error saying “Object 2 has no method replace”

    But this works {{#compare "5" "6" operator="=="}}Check equality{{/compare}}

    • Hi Arun,

      Thanks. That’s a fair remark. Haven’t had a chance to test this but I trust you’re correct.

      Actually, I just tried and I don’t seem to get the same problem.

      Here’s a test js fiddle that works as expected.

      Which browser are your testing this in?

      Maybe try to reduce your code to a limited test example to see if it works for you. If it does then maybe the problem is elsewhere.. ?

      Interested to know your feedback.

    • Hi Matías

      That should work fine.

      The helper uses an inverse, which is what the {{else}} triggers

      
      if( result ) {
            return options.fn(this);
      } else {
              return options.inverse(this);
      }
      
      

      You can also use {{^}} which does the same thing as else.

  7. how to compare numeric value , i am facing hard time comparing the following:

    {{#compare GroupType ABC.Constants.DashboardGroupType.UserDefinedGroup}}

    Where GroupType value is coming from Model and UserDefinedGroup value is coming from javascript variable.

    as soon as I change the statement to {{#compare GroupType 1}} it starts working. Seems to be an issue while fetching value from javascript file. Please help!!!

    • Hi Tarun,

      (Sorry for the late comment approval, hadn’t checked in a while).

      I’m assuming you’ve checked that ABC.Constants.DashboardGroupType.UserDefinedGroup is actually set to a correct value. Right? :)

      The default operator is === which is strict equality, which means it won’t work if one of your operand is a number and the other one a string.. maybe that’s the problem here?

      If that’s the case, you should either change your variables type or use the == operator, which is more permisible..

  8. For a condensed version of operators :

    var operators = new Function("return {"+ "==,===,!=,!==,<,>,<=,>=" .replace(/([^,]+)/g, "'$1': function (l, r) { return l $1 r; }\n")+ ",'typeof': function (l, r) { return typeof l == r; } }")();

    • Ah yeah, I guess, if your into that kind of things :)

      I’m not sure if it would be that much more efficient though..

  9. Hi,

    This doesn’t seem to work inside a requirejs wired template file. I get the same problem as Arun reported – it doesnt evaluate the variable expressions.

    I have something like..

    
    {{#compare messageType "==" "Info"}}
    
            <a href="#" rel="nofollow">&times;</a>
            {{message}}
    
    {{/compare}}
    

    The lvalue I am getting inside the function is – “messageType”, instead of its actual value. I printed the expression outside the block, it shows the proper value.

    • Hi Shameer,

      I haven’t tried it with requirejs. Maybe there is some sort of escaping going on when it’s compiled?

      Also, your example syntax looks incorrect, it should be something like:

      
      

      {{#compare messageType "Info" operator="=="}} ... {{/compare}}

      Hope this helps

  10. I dont know why but i always see in console this:

    Exception from Deps recompute: Error: Handlerbars Helper ‘compare’ needs 2 parameters

    I put the code in /client/helpers/handlebars.js… where should it go?

    • Sounds like it’s detected properly but you may be using it wrong. That errors comes from the helper’s check at the beginning. You might be missing an argument in your template code.

      Can you post an example of how you’re using it?

  11. Hi, that looks nice, but I have a problem with implementation to ember. I have:

    {{#each controller}} {{id}} {{#compare id 3 operator=”==”}}Active{{else}}Error{{/compare}} {{/each}}

    I can’t set lvalue from id parametr of controller content. Does anyone help me?

  12. Awesome helper, but you may want to move

    var operators = { ... }
    

    part out of the helper, for performance reasons, so it would be something like

    var operators = {
        ...
    };
    
    Handlebars.registerHelper('compare', ... )
    
    • Thanks Aziz.

      I’ve merged your two comments.

      Should definitely highlight one of the comments in your gist: you NEED to properly escape the string literals

  13. Hy guys, can anybody explain how to compare the dynamic strings using above helper Eample: {{#compare {{data}} test operator=”===”}} gives error where data comes from the list dynamicaaly

    {{#compare test test operator=”===”}} works as desired

    Thanks for the help in advance

  14. The values passed from the model is not passed to the custom helpers. From the each helper I am calling the ifCond registered helper.

    
        <ul>
        {{#each item in model}}
            {{#ifCond item '==' 'red'}}
                
  15. {{item}}
  16. {{/ifCond}} {{/each}} </ul>

    
    
    App.IndexRoute = Ember.Route.extend({
        model: function() {
            return ['red', 'yellow', 'blue', 'red', 'blue', 'pink'];
        }
    });
    
    
    
    
    
    Handlebars.registerHelper('ifCond', function (v1, operator, v2, options) {
        console.log(v1); //item - Here the value is coming as such and not the model value.
        console.log(v2);  // red
        console.log(operator); // ==
        console.log(options);
        switch (operator) {
        case '==':
            return (v1 == v2) ? options.fn(this) : options.inverse(this);
        case '===':
            return (v1 === v2) ? options.fn(this) : options.inverse(this);
        case '<':
            return (v1 < v2) ? options.fn(this) : options.inverse(this);
        case '<=':
            return (v1 <= v2) ? options.fn(this) : options.inverse(this);
        case '>':
            return (v1 > v2) ? options.fn(this) : options.inverse(this);
        case '>=':
            return (v1 >= v2) ? options.fn(this) : options.inverse(this);
        case '&&':
            return (v1 && v2) ? options.fn(this) : options.inverse(this);
        case '||':
            return (v1 || v2) ? options.fn(this) : options.inverse(this);
        default:
            return options.inverse(this);
        }
    });
    
    
    

    Here the value from the model is not passed to the custom helper.

    JSBin link –> http://emberjs.jsbin.com/mifoy/3/edit

    Thanks in advance jeevi

  17. I also had problem of variables not being evaluated. This fixed the problem

    lvalue = Ember.Handlebars.get(this, lvalue) || lvalue;
    rvalue = Ember.Handlebars.get(this, rvalue) || rvalue;

  18. This is my final code that works for me and is invoked like this:

    
    {{compare a b}}
    {{compare a '<' b}}
    
    
    
    Ember.Handlebars.registerHelper('compare', function(lvalue, operator, rvalue, options) {
        var operators, result;
    
    if (arguments.length < 3) {
        throw new Error("Handlerbars Helper 'compare' needs 2 parameters");
    }
    
    if (options === undefined) {
        options = rvalue;
        rvalue = operator;
        operator = "===";
    }
    
    lvalue = Ember.Handlebars.get(this, lvalue, options) || lvalue;
    operator = Ember.Handlebars.get(this, operator, options) || operator;
    rvalue = Ember.Handlebars.get(this, rvalue, options) || rvalue;
    
    operators = {
        '==': function(l, r) {
            return l == r;
        },
        '===': function(l, r) {
            return l === r;
        },
        '!=': function(l, r) {
            return l != r;
        },
        '!==': function(l, r) {
            return l !== r;
        },
        '<': function(l, r) {
            return l < r;
        },
        '>': function(l, r) {
            return l > r;
        },
        '<=': function(l, r) {
            return l <= r;
        },
        '>=': function(l, r) {
            return l >= r;
        },
        'typeof': function(l, r) {
            return typeof l == r;
        }
    };
    
    if (!operators[operator]) {
        throw new Error("Handlerbars Helper 'compare' doesn't know the operator " + operator);
    }
    
    result = operators[operator](lvalue, rvalue);
    
    if (result) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }
    
    
    });

  19. When I originally commented I clicked the “Notify me when new comments are added” checkbox and now each time a comment is added I get four e-mails with the same comment. Is there any way you can remove people from that service? Thanks!

    Look at my web page; Is Stevia Safe?

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>