Skip to content

Instantly share code, notes, and snippets.

@pheuter
Created August 29, 2012 17:33
Show Gist options
  • Save pheuter/3515945 to your computer and use it in GitHub Desktop.
Save pheuter/3515945 to your computer and use it in GitHub Desktop.
Handlebars.js equality check in #if conditional

Handlebars.js is a template framework for Javascript environments. It allows the construction of HTML elements using HTML and expressions wrapped in {{ }}

Limitations of {{if}}

One of the conditional block helpers Handlebars offers is the {{#if}}.

For example:

<div class="entry">
  {{#if author}}
    <h1>{{firstName}} {{lastName}}</h1>
  {{else}}
    <h1>Unknown Author</h1>
  {{/if}}
</div>

Unfortunately, that's about all you can do with the if out of the box. Luckily, Handlebars provides means of working with such expressions as well as adding your own. Using the registerHelper method, we will change the if block to support testing for property equality.

registerHelper

To do so, we will pass registerHelper a modified version of the if function where we utilize Handlebars' ability to parse key-value attributes in expressions:

Handlebars.registerHelper("if", function(conditional, options) {
  if (options.hash.desired === options.hash.type) {
    options.fn(this);
  } else {
    options.inverse(this);
  }
});

Now, our modified if expressions look something like this:

{{#if type desired="image" type=type}}
  <div class="msg img">
    <strong>{{user}}</strong> <i>shared an image</i>
    <br />
    <a href="{{file}}"><img src="{{file}}" /></a>
  </div>
{{/if}}

where type is a property of this in our current scope, and "image" is a possible value for type.

Of course, you may want to define your own block expression instead of overriding the default {{#if}}, but the idea holds.

@mikeal
Copy link

mikeal commented Jun 17, 2013

this isn't working for me.

handlebars.registerHelper('ifvalue', function (conditional, options) {
  if (options.hash.value === conditional) {
    options.fn(this)
  } else {
    options.inverse(this);
  }
})
handlebars.compile('{{#ifvalue type value="test"}} blah {{/ifvalue}} ')({type:'test'})
' '

I did some debugging and options.fn(this) is getting called, it just isn't actually returning the data in the block.

handlebars 1.0.12 on node.js

@jwarby
Copy link

jwarby commented Jun 19, 2013

@mikeal You need to return the result of calling options.fn or options.inverse.

@rpocklin
Copy link

This would be useful, but I can see why it wouldn't be included in the core library. How hard is it to define a boolean in your data, which you pass in to your tempalte and is evaluated by an {{if}} statement?

@Psykoral
Copy link

handlebars.registerHelper('ifvalue', function (conditional, options) {
  if (options.hash.value === conditional) {
    return options.fn(this)
  } else {
    return options.inverse(this);
  }
});

HBS:

{{#ifvalue variable value="hero"}}
    it matches, so do some stuff
{{/ifvalue}}

@tricki
Copy link

tricki commented Jun 18, 2014

I just found this alternative which is a lot more flexible and doesn't override the if helper. The version in the comments is even easier to use (see "Edit 2").

@henrypenny
Copy link

I think perhaps the design goal of eliminating all logic from the templates is actually leading to another 'sin' as it were. Pushing presentation logic back into the controller or model layers. Does anyone think like I do? Handlebars should have fully featured if/else functionality.

@matthew-dean
Copy link

@henrypenny Agreed. How is a boolean check fundamentally different from an equality check?

@smasala
Copy link

smasala commented Sep 2, 2014

Yea this sort of basic functionality (conditionals) should be in handlebars from the start

@kellyrmilligan
Copy link

agreed

@briandela
Copy link

@Romanulus It's not always possible to do that in a reasonable way. For example, if you are in a loop, and for certain items in the loop you want to change the behaviour, maybe add a new css class. Without an conditionals with equality in the handlebars, you are required to pull this presentation logic back int your handler/controller layer. It can easily be done like that, but now you're mixing presentation logic with business/data logic.

Being able to do something like loop over this data, and when you see one with property x=y, display it like this seems like presentation logic and reasonable to be in the handlerbars template.

@KennethMgntx
Copy link

I rewrote it to fit my use, and fit my brain. It's more logic to me this way.

Data/context

{"url": "image.png", "type": "image"}

Helper

Handlebars.registerHelper("ifvalue", function(conditional, options) {
    if (conditional == options.hash.equals) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }
});

Template

<script id="entry-template" type="text/x-handlebars-template">
    {{#ifvalue type equals="image"}}
        {{title}}
    {{/ifvalue}}
</script>

Just posted this, if it could help anyone.

@thomasvanlankveld
Copy link

tl;dr Comparison operators don't bloat templates with logic, more likely they will make both your templates and your javascript simpler.

Consider my use case: An admin portal with a page that displays information about a specific user. One panel on this page displays other users to which this user is somehow related. And now for the tricky part: The user that the page is displaying, he could be in that list himself. But he might not. Like an optional relationship to himself. And if he himself is in that list, we want to display some text. (Or maybe remove an href, because why would we link to the page we're already on?)

With #if defaulting to === for two arguments, the template would be:

{{#each relatedUser in pageUser.relatedUsers}}
  <p>{{relatedUser.fullName}}</p>
  {{#if relatedUser.id pageUser.id}}
    <p>Himself<p>
  {{/if}}
{{/each}}

While now I have to do:

{{#if pageUser.hasRelationToHimself}}
  <p>{{pageUser.fullName}}</p>
  <p>Himself<p>
{{/if}}

{{#each relatedUser in pageUser.relatedUsersWithoutHimself}}
  <p>{{relatedUser.fullName}}</p>
{{/each}}

With the second option now also requiring me to add hasRelationToHimself and relatedUsersWithoutHimself in my javascript somewhere else. This makes me a sad panda because more code = more bugs.

This is solved with the 'compare' helper by Mike Griffin mentioned in a comment above:

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

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

But I would prefer an implementation of the if helper itself that takes either 1 (Boolean), 2 (Equality) or 3 (Anything) arguments.

P.S. Here is Mike's code:

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);
    }

});

@milpas999
Copy link

Hey, I am new to express and handlebars. I am aslo facing the same issue. I understand the above solution, but where to put Handlebars.registerHelper code.

@brundonsmith
Copy link

brundonsmith commented Sep 23, 2016

I generally consider myself to be fairly idealistic in my coding. But this is the first time overidealism in a software project has been so absurd as to make me genuinely angry. After converting most of my project from SWIG (now dead) to Handlebars, I'm now having to convert it all over to Nunjucks.

I made sure to check for "includes basic comparison operators out of the box" under the features section. I never thought I would be so excited to see that.

@minexew
Copy link

minexew commented Sep 23, 2016

@brundonsmith is not wrong

@silicahd
Copy link

Handlebars is a piece of s....t and I for one, completely refuse to use it and also divert anybody form ever using it. The first statement on this page sounds like a corporate bullcrap that you see everywhere. "Luckily, Handlebars provides means of working with such expressions". WAIT WHAT?????? the simplest operator comparison function in any langues was omitted but you allow me to add that functionality, by writing an extra 6 lines of code for every time I need this done. WOW thanks for such a privilege. DO YOURSELF A FAVOR AND DO NOT USE THIS TEMPLATING ENGINE. even JADE with its god awful white space fiasco is better than this turd. From the bottom of my heart. --virgil

@brennancheung
Copy link

I get not having business logic in the template, but having template logic in the template makes perfect sense. Like {{#if page === currentPage}}...{{/if}}. That doesn't belong in a controller or a helper. Having to make a helper for that situation is overkill and not the right place for it.

Also, adding a helper is only an option if you have control over everything. What about if you want to expose Handlebars as a templating engine for 3rd party users. You're not going to give random strangers full access to write whatever JS they want on your node server.

If you can't anticipate every helper a customer is going to need you are going to have a support nightmare and have to do new builds each time you add a helper.

I like the compare helper above.

If you just need === you can do:

Handlebars.registerHelper('ifeq', (a, b, options) => {
  if (a === b) {
    return options.fn(this)
  }
  return options.inverse(this)
})

You can implement them for ifneq, ifgt, ifgte, etc.

@rohitsharma00013
Copy link

Where has to put Handlebars.registerhelper)........ code ?

@NagatoPeinI1
Copy link

@thomasvanlankv I tried your method but it is giving me an error ::: result = operators[operator](lvalue, rvalue); is not a function.

@NagatoPeinI1
Copy link

Can anyone can explain how this handlebars.registerHelper are able to get the value for that function.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment