508

Is there a way in handlebars JS to incorporate logical operators into the standard handlebars.js conditional operator? Something like this:

{{#if section1 || section2}}
.. content
{{/if}}

I know I could write my own helper, but first I'd like to make sure I'm not reinventing the wheel.

Mike Robinson
  • 24,069
  • 8
  • 59
  • 83
  • For "and" logic, you can do nested if conditions, but it's clunky and doesn't help you with the else "unless", or any "or" logic per your question above. – combinatorist May 03 '21 at 00:50

26 Answers26

540

This is possible by 'cheating' with a block helper. This probably goes against the Ideology of the people who developed Handlebars.

Handlebars.registerHelper('ifCond', function(v1, v2, options) {
  if(v1 === v2) {
    return options.fn(this);
  }
  return options.inverse(this);
});

You can then call the helper in the template like this

{{#ifCond v1 v2}}
    {{v1}} is equal to {{v2}}
{{else}}
    {{v1}} is not equal to {{v2}}
{{/ifCond}}
Peter Bratton
  • 6,024
  • 5
  • 35
  • 60
Nick Kitto
  • 5,628
  • 2
  • 13
  • 23
  • 56
    This does go against the logicless nature of Handlebars / Moustache, but is certainly useful nonetheless, thanks! – Bala Clark Jun 29 '12 at 11:09
  • 6
    Note that this simply doesn't work with bound properties (read, Ember bindings). It's fine with literal values, but doesn't resolve model properties (tested with Ember 1.0.0-rc.8 and Handlebars 1.0.0), and `registerBoundHelper` can't deal with Handlebars syntax. The workaround is to create a custom view: http://stackoverflow.com/questions/18005111/custom-handlebars-helper-parameter-is-not-resolved – Warren Seine Aug 29 '13 at 14:41
  • In Ember you should create a computed property for the value instead and use that property in your template. That way the bindings will work correctly. – Karl-Johan Sjögren Sep 21 '13 at 16:12
  • See [my answer](http://stackoverflow.com/a/21072419/140951) for a simple if equal helper supporting bound properties. – devongovett Jan 12 '14 at 07:51
  • Although it is very helpful, this introduces already some logic in your templates. I imagine everything goes to hell from here on. – Justus Romijn Feb 04 '14 at 09:14
  • if you want **unlimited-logic-hell**, see this [answer](http://stackoverflow.com/a/21915381/493756). The accepted answer only accepts 2 values and handles one (or a limited set of) operand(s) – bentael Feb 20 '14 at 19:00
  • 31
    @BalaClark is it really logicless though? I mean it still has "if" statements - just because they are intentionally crippled doesn't change the philosophy. – phreakhead Feb 25 '14 at 07:19
  • this seems to add logic to the handlebars templating system. If you want to run some ajax with handlebars and have page sections for a microtemplate, this makes awesome logic. I'll be using this with node.js express framework! – jemiloii Mar 24 '14 at 08:12
  • May be a dumb question but what are the `options` for? – cschuff May 07 '14 at 09:44
  • The last variable is just a special object that Handlebars passes in. In the code I originally based it off they called it options. Options.fn is the main block, and options.inverse is the else block. It has some other functions attached I believe (put a breakpoint in there and check it out). – Nick Kitto May 07 '14 at 21:31
  • Are using CLI by chance? http://stackoverflow.com/questions/23851551/emberjs-pass-params-to-a-view-then-to-helper We have similar issues. – Chris Hough May 25 '14 at 04:32
  • Warning: Object 1 has no method 'fn'. – Oliver Dixon Jul 17 '14 at 02:31
  • Is it possible to to something like this (with 3 conditions)? {{#if cond1 || cond2 || cond3}}{{/if}} – Stanislav Ostapenko Mar 27 '15 at 19:55
  • You could definitely do it with a new helper that takes in the extra parameters, or takes in an array that has a set of conditions. Other solutions are using sub templates and doing the test several times, using some of the other custom helpers here (ie bentaels answer), or doing the actual solving before hand and just having a single conditional (like handlebars intended). – Nick Kitto Mar 30 '15 at 21:12
  • I get `First argument must be a function, to be called on the rest of the arguments; found STRING` – Maximus S Apr 13 '15 at 07:45
  • 45
    I don't understand why AND / OR can't be a part of Handlebars. It doesn't go against the logicless nature since there's already IF, UNLESS, and EACH... so much for logicless – Asaf Sep 07 '15 at 20:01
  • 1
    How could I use this to do an `else ifCond` condition? – alexw Sep 27 '15 at 01:13
  • 2
    if this is a scripting language, and you have to define your own if condition logic, isn't it like going into a restaurant and cook your own potato? – Zack Xu Dec 23 '15 at 16:39
  • 4
    @StackOverflowed Limiting your options/handicaping yourself can help to follow a development philosophy. With a working development philosophy, the API implementing it shouldn't feel handicapping when you are trying solve a problem it is designed to solve. Instead, it should feel empowering in a strange way :) (kinda like abstinence). To get back on topic, either the Handlebars philosophy is contradictory, the problem described in this question could be solved with a solution following it, the handlebars API is implemented poorly or this is not a problem handlebars is designed to solve. – Matthias Feb 25 '16 at 19:52
  • 2
    "I never understood why developers like to handicap themselves" - I don't understand why someone would choose a tool obviously not meant for the job when there are _plenty_ of alternatives. – aaaaaa Apr 20 '17 at 02:06
  • This helped me tremendously. I had a situation with a MongoDb based comment system where I needed to essentially map values from a 'posts' collection to a 'comments' collection. In the 'comments' collection I saved the MongoDB _id of the 'post' the comment was written for. It was very difficult (or so it seemed) to only display specific comments under the correct posts on a handlebars page with only true/false conditionals accepted by default, this helper saved the day, thank you! – ViaTech Feb 16 '18 at 03:27
469

Taking the solution one step further. This adds the compare operator.

Handlebars.registerHelper('ifCond', function (v1, operator, v2, 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);
        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);
    }
});

Use it in a template like this:

{{#ifCond var1 '==' var2}}

Coffee Script version

Handlebars.registerHelper 'ifCond', (v1, operator, v2, options) ->
    switch operator
        when '==', '===', 'is'
            return if v1 is v2 then options.fn this else options.inverse this
        when '!=', '!=='
            return if v1 != v2 then options.fn this else options.inverse this
        when '<'
            return if v1 < v2 then options.fn this else options.inverse this
        when '<='
            return if v1 <= v2 then options.fn this else options.inverse this
        when '>'
            return if v1 > v2 then options.fn this else options.inverse this
        when '>='
            return if v1 >= v2 then options.fn this else options.inverse this
        when '&&', 'and'
            return if v1 and v2 then options.fn this else options.inverse this
        when '||', 'or'
            return if v1 or v2 then options.fn this else options.inverse this
        else
            return options.inverse this
Ryne Everett
  • 5,215
  • 3
  • 32
  • 42
Jim
  • 812
  • 1
  • 8
  • 16
  • 21
    don't forget about `'||'` and `'&&'`. I added these cases and they're very useful. – Jason Jun 20 '13 at 17:38
  • 20
    Being a noob to handlebars one thing that wasn't clear is that you have to pass the operator as a string or else the compiler will error out while tokenizing your template. {{#ifCond true '==' false}} – Joe Holloway Jul 11 '13 at 20:11
  • If I am using ember with rails, where should I define this? which file should I place this code? Thanks. – lionel Sep 04 '13 at 04:46
  • @Jason Those would be helpful. Would you mind adding them to the example code? – bfcoder Sep 05 '13 at 17:56
  • how would you use this in a template? especially the options.fn part? – qodeninja Feb 10 '14 at 22:38
  • What should options be set to? How is this passed in? I get a `TypeError: opts is undefined` error. – Martyn Jan 20 '15 at 03:56
  • 2
    I found the values to be not evaluated for object attributes. Adding the following helps `v1 = Ember.Handlebars.get(this, v1, options)` `v2 = Ember.Handlebars.get(this, v2, options)` – ZX12R Mar 03 '15 at 06:54
  • 4
    but if v1 and v2 also one condition then how can i use? for ex: if(value == "a" || value == "b") – Krishna Sep 07 '16 at 12:11
  • Nice compact version: http://codegists.com/snippet/javascript/handlebarsifcondjs_danrichards_javascript – jbyrd Nov 18 '16 at 20:10
  • 3
    Can you do an else if with this? – jbyrd Dec 22 '16 at 21:57
  • 1
    context variables passed to this function are undefined – dude Jul 13 '17 at 13:27
  • When I try to `console.log(operator);` inside this helper and use this condition `{{#ifCond charge_id '>' 2}}`. I've got this `>` instead of this `>` – vee Jan 18 '20 at 06:21
  • I used `eval` for this (in TypeScript): ```ts function hIfBinaryExpr( this: any, left: unknown, operator: string, right: unknown, options: Handlebars.HelperOptions ) { // eslint-disable-next-line no-eval const evaluation: boolean = eval(`left ${operator} right`); return evaluation ? options.fn(this) : options.inverse(this); } ``` – Dico Jul 06 '20 at 23:02
189

Handlebars supports nested operations. This provides a lot of flexibility (and cleaner code) if we write our logic a little differently.

{{#if (or section1 section2)}}
.. content
{{/if}}

In fact, we can add all sorts of logic:

{{#if (or 
        (eq section1 "foo")
        (ne section2 "bar"))}}
.. content
{{/if}}

Just register these helpers:

Handlebars.registerHelper({
    eq: (v1, v2) => v1 === v2,
    ne: (v1, v2) => v1 !== v2,
    lt: (v1, v2) => v1 < v2,
    gt: (v1, v2) => v1 > v2,
    lte: (v1, v2) => v1 <= v2,
    gte: (v1, v2) => v1 >= v2,
    and() {
        return Array.prototype.every.call(arguments, Boolean);
    },
    or() {
        return Array.prototype.slice.call(arguments, 0, -1).some(Boolean);
    }
});
kevlened
  • 9,314
  • 4
  • 18
  • 15
  • 5
    Note that this wasn't possible until HTMLBars in Ember 1.10. Also, those helpers come as an Ember CLI addon if you'd rather: https://github.com/jmurphyau/ember-truth-helpers. – stephen.hanson Jul 31 '15 at 15:06
  • 1
    This way, with no `options` execution is better to keeps with `if` method and easier to test. Thanks! – Washington Botelho Dec 25 '15 at 06:15
  • 27
    We seem to have built Lisp into Handlebars. – jdunning Apr 15 '16 at 16:40
  • For some reason this syntax doesn't work for me using Handlebars 4.0.5. I get an error `handlebars-v4.0.5.js:518 Uncaught Error: if doesn't match eq - 12:5` for an expression like: `{{#if (eq shipperTypeId "1")}}` – IcedDante Jun 27 '16 at 14:46
  • 10
    Your `and` and `or` helpers only work with 2 parameters, which is not what you want. I managed to rework both the `and` and `or` helpers to support more than two parameters. Use this one-liner for `and`: `return Array.prototype.slice.call(arguments, 0, arguments.length - 1).every(Boolean);` and use this one-liner for `or`: `return Array.prototype.slice.call(arguments, 0, arguments.length - 1).some(Boolean);`. – Luke Aug 15 '16 at 05:39
  • thumbs up for using native functionality. .... thumbs down for having to use LISP-like expressions (but that's not your fault heh?.... ) – user2173403 Oct 20 '16 at 08:59
  • there are some problems with expression like this `{{#if (or (ne Can.DoSomething true) (gt someNumber 1))}}` the final call to `or: function (v1, v2)` have `v2` parameter assigned to object `Can` instead of result of `(ne Can.DoSomething true)`. Is there a fix for this except using only plain parameters? @Luke , @kevlened – Mikhail Apr 06 '17 at 13:29
  • @Mikhail, have you tried wrapping it in parens? `(ne (Can.DoSomething) true)` – kevlened Apr 06 '17 at 18:34
  • @kevlened Hi, in this case it starts thinking that `Can` is a function, I assume because it in parens – Mikhail Apr 11 '17 at 07:35
  • @Mikhail I can't reproduce your original issue: https://codepen.io/anon/pen/RVbdwa – kevlened Apr 12 '17 at 02:06
  • @kevlened looks like problem with template compiler, your example works as expected, will continue research. Thanks. – Mikhail Apr 12 '17 at 07:06
  • I took the ideas here and made a small "library" of helper functions: https://github.com/plaa/handlebars-logic – Sampo Dec 06 '17 at 05:40
  • 4
    A [little more advanced version](https://gist.github.com/servel333/21e1eedbd70db5a7cfff327526c72bc5) that can take multiple parameters on each operator. – Nate Feb 15 '18 at 15:40
  • @Luke Thanks for the clever one-liners! I've updated the answer to support >2 args based on your comment. – kevlened Apr 24 '18 at 18:37
  • This solution doesn't work for me because my #1 arg is checked against all other optional ones, e.g.: `type == a || type = b || type == c || ..`. See this question: https://stackoverflow.com/questions/50190854/logical-or-and-handlebars-js-helper-multiple-arguments-first-one-always-being Do you know how to solve this? – gene b. May 05 '18 at 15:33
  • @geneb. The `or` operator isn't written to work that way. If you want to compare all the items against the first, here's how to do it with this helper: `or(eq(type,a), eq(type,b), eq(type,c))` – kevlened May 06 '18 at 00:30
  • @Kevlened -- BTW, in your answer, the And is wrong because you're not subtacting -1 from the arg list. The last arg is the function name. Your OR is correct though. – gene b. May 06 '18 at 02:22
  • @geneb. The last arg will always evaluate to true (the metadata handlebars passes is truthy). "and" ensures that all arguments are truthy, so removing the last argument doesn't change the result. – kevlened May 06 '18 at 16:51
  • How should a ternary operator (let's call it `tn`) be implemented? I'm not sure if it's the same as the `or` operator: `tn: function (v1, v2) { return (v1 || v2? v1 : v2); }` – Agi Hammerthief Jan 18 '19 at 11:37
  • 1
    @AgiHammerthief Try: `tn: function (v1, v2, v3) { return v1 ? v2 : v3; }`. Usage: `{{#if (tn someCondition valueIfTrue valueIfFalse) }}` – kevlened Jan 18 '19 at 15:52
89

taking this one up a notch, for those of you who live on the edge.

gist: https://gist.github.com/akhoury/9118682 Demo: Code snippet below

Handlebars Helper: {{#xif EXPRESSION}} {{else}} {{/xif}}

a helper to execute an IF statement with any expression

  1. EXPRESSION is a properly escaped String
  2. Yes you NEED to properly escape the string literals or just alternate single and double quotes
  3. you can access any global function or property i.e. encodeURIComponent(property)
  4. this example assumes you passed this context to your handlebars template( {name: 'Sam', age: '20' } ), notice age is a string, just for so I can demo parseInt() later in this post

Usage:

<p>
 {{#xif " name == 'Sam' && age === '12' " }}
   BOOM
 {{else}}
   BAMM
 {{/xif}}
</p>

Output

<p>
  BOOM
</p>

JavaScript: (it depends on another helper- keep reading)

 Handlebars.registerHelper("xif", function (expression, options) {
    return Handlebars.helpers["x"].apply(this, [expression, options]) ? options.fn(this) : options.inverse(this);
  });

Handlebars Helper: {{x EXPRESSION}}

A helper to execute javascript expressions

  1. EXPRESSION is a properly escaped String
  2. Yes you NEED to properly escape the string literals or just alternate single and double quotes
  3. you can access any global function or property i.e. parseInt(property)
  4. this example assumes you passed this context to your handlebars template( {name: 'Sam', age: '20' } ), age is a string for demo purpose, it can be anything..

Usage:

<p>Url: {{x "'hi' + name + ', ' + window.location.href + ' <---- this is your href,' + ' your Age is:' + parseInt(this.age, 10)"}}</p>

Output:

<p>Url: hi Sam, http://example.com <---- this is your href, your Age is: 20</p>

JavaScript:

This looks a little large because I expanded syntax and commented over almost each line for clarity purposes

Handlebars.registerHelper("x", function(expression, options) {
  var result;

  // you can change the context, or merge it with options.data, options.hash
  var context = this;

  // yup, i use 'with' here to expose the context's properties as block variables
  // you don't need to do {{x 'this.age + 2'}}
  // but you can also do {{x 'age + 2'}}
  // HOWEVER including an UNINITIALIZED var in a expression will return undefined as the result.
  with(context) {
    result = (function() {
      try {
        return eval(expression);
      } catch (e) {
        console.warn('•Expression: {{x \'' + expression + '\'}}\n•JS-Error: ', e, '\n•Context: ', context);
      }
    }).call(context); // to make eval's lexical this=context
  }
  return result;
});

Handlebars.registerHelper("xif", function(expression, options) {
  return Handlebars.helpers["x"].apply(this, [expression, options]) ? options.fn(this) : options.inverse(this);
});

var data = [{
  firstName: 'Joan',
  age: '21',
  email: 'joan@aaa.bbb'
}, {
  firstName: 'Sam',
  age: '18',
  email: 'sam@aaa.bbb'
}, {
  firstName: 'Perter',
  lastName: 'Smith',
  age: '25',
  email: 'joseph@aaa.bbb'
}];

var source = $("#template").html();
var template = Handlebars.compile(source);
$("#main").html(template(data));
h1 {
  font-size: large;
}
.content {
  padding: 10px;
}
.person {
  padding: 5px;
  margin: 5px;
  border: 1px solid grey;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/handlebars.js/1.0.0/handlebars.min.js"></script>

<script id="template" type="text/x-handlebars-template">
  <div class="content">
    {{#each this}}
    <div class="person">
      <h1>{{x  "'Hi ' + firstName"}}, {{x 'lastName'}}</h1>
      <div>{{x '"you were born in " + ((new Date()).getFullYear() - parseInt(this.age, 10)) '}}</div>
      {{#xif 'parseInt(age) >= 21'}} login here:
      <a href="http://foo.bar?email={{x 'encodeURIComponent(email)'}}">
         http://foo.bar?email={{x 'encodeURIComponent(email)'}}
        </a>
      {{else}} Please go back when you grow up. {{/xif}}
    </div>
    {{/each}}
  </div>
</script>

<div id="main"></div>

Moar

if you want access upper level scope, this one is slightly different, the expression is the JOIN of all arguments, usage: say context data looks like this:

// data
{name: 'Sam', age: '20', address: { city: 'yomomaz' } }

// in template
// notice how the expression wrap all the string with quotes, and even the variables
// as they will become strings by the time they hit the helper
// play with it, you will immediately see the errored expressions and figure it out

{{#with address}}
    {{z '"hi " + "' ../this.name '" + " you live with " + "' city '"' }}
{{/with}}

Javascript:

Handlebars.registerHelper("z", function () {
    var options = arguments[arguments.length - 1]
    delete arguments[arguments.length - 1];
    return Handlebars.helpers["x"].apply(this, [Array.prototype.slice.call(arguments, 0).join(''), options]);
});

Handlebars.registerHelper("zif", function () {
    var options = arguments[arguments.length - 1]
    delete arguments[arguments.length - 1];
    return Handlebars.helpers["x"].apply(this, [Array.prototype.slice.call(arguments, 0).join(''), options]) ? options.fn(this) : options.inverse(this);
});
bentael
  • 1,798
  • 1
  • 19
  • 27
  • 15
    This is great. Handlebars can't tell you what to do :) – lemiant Mar 11 '14 at 14:47
  • 1
    :) thanks, I updated the [gist](https://gist.github.com/akhoury/9118682) a little to add few more goodies (not directly related to this SO question - but in the spirit of doing what you want inside a handlebars template) However, you should watch out for the limitations, I haven't found a way to access "upper scope levels" from within the expression, say you're in a each scope, `{{#each}} ... {{xif ' ../name === this.name' }} {{/each}}` ... but I am still investigating.. – bentael Mar 12 '14 at 16:05
  • 1
    found a solution for "upper scope access" but using a new helper, see updated answer, `{{z ...}}` helper – bentael Mar 13 '14 at 05:44
  • BOOM ! Awesome, now I can do real condition [inside my partial](http://stackoverflow.com/a/18934897/1486230) thanks ! – Tancrede Chazallet Apr 03 '14 at 15:58
  • Can you setup an example of zif? I am not sure if my problem is the fact that the parent is an each statement as well, but can't get it working. I get Expecting 'ID', got 'undefined' error. Thanks. – fanfavorite Jun 04 '14 at 02:14
  • Just a heads up that this can really slow down the loading of a page. – fanfavorite Oct 15 '14 at 14:48
  • if you are using it in a large iteration, sure, I would recommend creating a custom helper that does just exactly what it needs in a iteration. – bentael Oct 15 '14 at 15:03
  • I'm trying to render out mandrill templates using handlebars in node. How can I drop the 'this' from my expressions? ```{{#if `this.triggerPromotionExists != null`}}``` – rickysullivan Sep 28 '16 at 03:35
  • you don't need the `this` - also, you can't use the ticks you must use a valid string, node will interpret this as the ES2015 Template literals. Also, you're using `#if` not `#xif` - I think the correct syntax should be `{{#xif 'triggerPromotionExists != null' }}` – bentael Sep 28 '16 at 15:08
  • @bentael, I'm using https://mandrill.zendesk.com/hc/en-us/articles/205582537-Using-Handlebars-for-Dynamic-Content. I'm using '#if' because my template still needs to conform to Mandrill's templating system. The reason I'm doing this, is because their API lacks an endpoint to render hbs templates. I'm regex replacing back ticks with single quotes, `replace(/elseif/g, 'else if')` and other hacks. – rickysullivan Oct 10 '16 at 06:32
  • @rickysullivan so you want to reuse the same handlebars-like templates that you've used in Mandrill with another basic handlebars-renderer somewhere else and trying to convert them? can you provide an example of one of your templates somewhere? – bentael Oct 10 '16 at 16:24
  • @bentael I've made a simple demo here: http://codepen.io/rickysullivan/pen/pEVqJR. As you can see it's logging `{{x gender == "male"}} hit a runtime error ReferenceError: gender is not defined` Where as line 30 of the html: `{{#if \`this.stampsAvail <= 1\`}}` returns fine because of the `this`. – rickysullivan Oct 12 '16 at 03:18
  • @rickysullivan so the ask is to have it with, with or without the `this`? if so, check my fork: http://codepen.io/akhoury/pen/pEKyvL (I only touched the `x` helper block) - this is a good case to use the javascript `with` statement, combined with `eval` - let me know if that's what you wanted, I don't like using `with`, but sometimes you gotta do what you gotta do. – bentael Oct 12 '16 at 15:04
  • @bentael Correct, it was how to drop the `this`. It works great, but I do see your comment about the `with` statement. I've tried using es2015 destructing, but can't figure it out. So, for now it works. Thankyou. – rickysullivan Oct 13 '16 at 00:01
  • @bentael hit a snag, if a variable doesn't exist it throws an error, where it should just return false. http://codepen.io/rickysullivan/pen/ALdxob – rickysullivan Oct 13 '16 at 04:20
  • 1
    @rickysullivan try replace your `eval(expression);` with this: `eval('(function(){try{return ' + expression + ';}catch(e){}})();');` - i know this is getting ugly, but can't think of a safe way to do this - please be aware that this is not very "fast" to execute, I wouldn't do this in huge iteration. – bentael Oct 13 '16 at 18:48
  • here: http://codepen.io/akhoury/pen/NRBKqp I expanded the expression inside of eval so you can read it better, also added a hacky debug option if you want to see those errors anyways. – bentael Oct 13 '16 at 19:02
  • however, for invalid javascript, you would still see the errors, which is fine, you dont want to hide those. Also, you dont want the `x` helper to return `false`, if should return `undefined`, which is `falsy` anyways, but there is a difference, since `x` is not condition checker really, it just interprets the expressions. – bentael Oct 13 '16 at 19:14
  • @rickysullivan i just changed the main answer to this thread to use the solution for you issue, except that the `with(this)` over `this` not over `options.data.root` like your case – bentael Oct 15 '16 at 01:02
  • 1
    For NodeJS usage you might want to use a safer eval solution such as: https://www.npmjs.com/package/safe-eval instead of the eval() function – BigMan73 Oct 30 '20 at 14:43
  • @BigMan73 nice! – bentael Oct 31 '20 at 17:00
34

There is a simple way of doing this without writing a helper function... It can be done within the template completely.

{{#if cond1}}   
  {{#if con2}}   
    <div> and condition completed</div>  
  {{/if}}
{{else}}   
  <div> both conditions weren't true</div>  
{{/if}}

Edit: Conversely you can do or's by doing this:

{{#if cond1}}  
  <div> or condition completed</div>    
{{else}}   
  {{#if cond2}}  
    <div> or condition completed</div>  
  {{else}}      
    <div> neither of the conditions were true</div>    
  {{/if}}  
{{/if}}

Edit/Note: From the handlebar's website: handlebarsjs.com here are the falsy values:

You can use the if helper to conditionally render a block. If its argument returns false, undefined, null, "" or [] (a "falsy" value), Then any 'cond' (like cond1 or cond2) will not be counted as true.

Jono
  • 2,883
  • 4
  • 29
  • 40
  • 10
    Not really, you don't compare two value, you just ensure both of them exists, it's different. – Cyril N. Sep 02 '13 at 13:43
  • 3
    Actually it evaluates it the same way javascript does, from the website: "You can use the if helper to conditionally render a block. If its argument returns false, undefined, null, "" or [ ] (a "falsy" value), Handlebars will not render the block." – Jono Sep 04 '13 at 15:40
  • 1
    You are right, I firstly thought the test was to compare two values, not testing of both existed. My bad, sorry. – Cyril N. Sep 04 '13 at 20:00
  • 4
    This doesn't work correctly. If cond1 is true and con2 is false, nothing will be printed. – Tessa Lau Apr 01 '14 at 22:47
  • 1
    Hey @TessaLau, I think I see where you're coming from. However if you draw out a control flow you'll see that in the very first line moves the control flow to that condition. – Jono Apr 03 '14 at 14:40
  • @jQwierdy are you sure [] is a falsey value? `!![] -> true` – Theo Belaire Apr 09 '14 at 22:33
  • @Tyr, I don't honestly see why that matters I wasn't giving an answer for an array. I understand my answer doesn't completely address all possible solutions, however it does address 'AND' and 'OR' properties which in most languages is what the main boolean operators are (with the glaring exception of JS). – Jono Apr 11 '14 at 04:17
  • 1
    @jQwierdy Well, I don't really know handlebars that well, and I recently got burned coming from Python with the fact that [] is truthy, so I have no idea what'll happen if you return [] from an argument now, since they spell out that it'll work with [], but it's not "falsy", so I'm rather confused. I'd rather the record be set straight one way or the other. – Theo Belaire Apr 11 '14 at 21:53
  • @Tyr gotcha, sorry about that. I didn't realize where that had come from, context helps ;) I've posted in the above answer the results of what I found (tl;dr - [] is a falsy value). – Jono Apr 13 '14 at 04:39
  • I believe @TessaLau is right. Due to symmetry, both AND and OR require one of the expressions to be repeated. The correct expression for AND is `{{# if cond1}} {{#if cond2}} AND is true {{else}} AND is false {{/if}} {{else}} AND is false {{/if}}` – Clement Cherlin Feb 13 '20 at 13:46
19

One problem with all of the answers posted here is that they don't work with bound properties, i.e. the if condition is not re-evaluated when the properties involved change. Here's a slightly more advanced version of the helper supporting bindings. It uses the bind function from the Ember source, which is also used to implement the normal Ember #if helper.

This one is limited to a single bound property on the left-hand side, comparing to a constant on the right-hand side, which I think is good enough for most practical purposes. If you need something more advanced than a simple comparison, then perhaps it would be good to start declaring some computed properties and using the normal #if helper instead.

Ember.Handlebars.registerHelper('ifeq', function(a, b, options) {
  return Ember.Handlebars.bind.call(options.contexts[0], a, options, true, function(result) {
    return result === b;
  });
});

You can use it like this:

{{#ifeq obj.some.property "something"}}
  They are equal!
{{/ifeq}}
devongovett
  • 4,605
  • 5
  • 30
  • 34
  • This is the appropriate answer if you are using Ember. The other solutions as you mentioned will just pass the key instead of the value. BTW thanks for this, spent a few hours wracking my head. – J Lee Jan 12 '15 at 13:59
  • 2
    Has anyone gotten this working with HTMLBars/Ember 1.10? Ember.Handlebars.bind no longer seems to exist. – Linda Jan 18 '15 at 06:25
  • I originally used registerBoundHelper originally but then when it changed condition it wouldn't change to the else value and back.. This method works with ember for changing :) It should have a lot more votes up – Matt Vukomanovic May 18 '15 at 09:47
12

Improved solution that basically work with any binary operator (at least numbers, strings doesn't work well with eval, TAKE CARE OF POSSIBLE SCRIPT INJECTION IF USING A NON DEFINED OPERATOR WITH USER INPUTS):

Handlebars.registerHelper("ifCond",function(v1,operator,v2,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);

        case ">":
            return (v1>v2)?options.fn(this):options.inverse(this);

        case ">=":
         return (v1>=v2)?options.fn(this):options.inverse(this);

        default:
            return eval(""+v1+operator+v2)?options.fn(this):options.inverse(this);
    }
});
Vincent
  • 881
  • 13
  • 17
  • how would you use this in a template? especially the options.fn part? – qodeninja Feb 10 '14 at 22:37
  • {{ifCond val1 '||' val2}}true{{else}}false{{/if}} it returns options.fn (true, the ifCond clause) if its correct, otherwise it returns options.inverse (false, the else clause) if incorrect. – Nick Kitto Mar 31 '14 at 20:37
  • 1
    Due to the caveat mentioned about script injection, I would strongly recommend against using this helper. In a large codebase this could easily be responsible for a nasty security problem down the line – Jordan Sitkin Apr 23 '14 at 21:28
8

Here's a solution if you want to check multiple conditions:

/* Handler to check multiple conditions
   */
  Handlebars.registerHelper('checkIf', function (v1,o1,v2,mainOperator,v3,o2,v4,options) {
      var operators = {
           '==': function(a, b){ return a==b},
           '===': function(a, b){ return a===b},
           '!=': function(a, b){ return a!=b},
           '!==': function(a, b){ return a!==b},
           '<': function(a, b){ return a<b},
           '<=': function(a, b){ return a<=b},
           '>': function(a, b){ return a>b},
           '>=': function(a, b){ return a>=b},
           '&&': function(a, b){ return a&&b},
           '||': function(a, b){ return a||b},
        }
      var a1 = operators[o1](v1,v2);
      var a2 = operators[o2](v3,v4);
      var isTrue = operators[mainOperator](a1, a2);
      return isTrue ? options.fn(this) : options.inverse(this);
  });

Usage:

/* if(list.length>0 && public){}*/

{{#checkIf list.length '>' 0 '&&' public '==' true}} <p>condition satisfied</p>{{/checkIf}}
7

Here's a link to the block helper I use: comparison block helper. It supports all the standard operators and lets you write code as shown below. It's really quite handy.

{{#compare Database.Tables.Count ">" 5}}
There are more than 5 tables
{{/compare}}
CleanTheRuck
  • 641
  • 1
  • 8
  • 17
  • 1
    This should be the winning vote. Native and intended. I find almost every time that if you're writing a new helper you're overthinking it. – augurone Feb 17 '15 at 22:28
  • 2
    @augurone this isn't a native helper. If you follow the link you'll see that it is a custom defined helper. – gfullam Mar 25 '15 at 13:38
  • @gfullam you're right, I was using handlebars in assemble, which includes this helper natively. My bad. – augurone Mar 25 '15 at 23:06
4

Similar to Jim's answer but a using a bit of creativity we could also do something like this:

Handlebars.registerHelper( "compare", function( v1, op, v2, options ) {

  var c = {
    "eq": function( v1, v2 ) {
      return v1 == v2;
    },
    "neq": function( v1, v2 ) {
      return v1 != v2;
    },
    ...
  }

  if( Object.prototype.hasOwnProperty.call( c, op ) ) {
    return c[ op ].call( this, v1, v2 ) ? options.fn( this ) : options.inverse( this );
  }
  return options.inverse( this );
} );

Then to use it we get something like:

{{#compare numberone "eq" numbretwo}}
  do something
{{else}}
  do something else
{{/compare}}

I would suggest moving the object out of the function for better performance but otherwise you can add any compare function you want, including "and" and "or".

ars265
  • 1,809
  • 3
  • 20
  • 35
4

Yet another crooked solution for a ternary helper:

'?:' ( condition, first, second ) {
  return condition ? first : second;
}

<span>{{?: fooExists 'found it' 'nope, sorry'}}</span>

Or a simple coalesce helper:

'??' ( first, second ) {
  return first ? first : second;
}

<span>{{?? foo bar}}</span>

Since these characters don't have a special meaning in handlebars markup, you're free to use them for helper names.

Moritz Friedrich
  • 1,113
  • 18
  • 29
3

One other alternative is to use function name in #if. The #if will detect if the parameter is function and if it is then it will call it and use its return for truthyness check. Below myFunction gets current context as this.

{{#if myFunction}}
  I'm Happy!
{{/if}}
Shital Shah
  • 47,549
  • 10
  • 193
  • 157
  • So you would need to add a function into the context? Executing code from a context is a security hole, as the source of the code could be unknown. This can be exploited for an XSS attack. – T Nguyen Dec 04 '13 at 15:58
  • Yes, you need to add function into the context. In a badly designed website, yes, this could be security hole. But in that case, there would be many others. – Shital Shah Dec 05 '13 at 02:39
  • This is the preferred way.. TY. – elad.chen Dec 29 '14 at 12:30
3

Unfortunately none of these solutions solve the problem of "OR" operator "cond1 || cond2".

  1. Check if first value is true
  2. Use "^" (or) and check if otherwise cond2 is true

    {{#if cond1}} DO THE ACTION {{^}} {{#if cond2}} DO THE ACTION {{/if}} {{/if}}

It breaks DRY rule. So why not use partial to make it less messy

{{#if cond1}}
    {{> subTemplate}}
{{^}}
    {{#if cond2}}
        {{> subTemplate}}
    {{/if}}
{{/if}}
Pawel
  • 10,190
  • 4
  • 56
  • 60
3

I can understand why you would want to create a helper for situations where you have a large number of varied comparisons to perform within your template, but for a relatively small number of comparisons (or even one, which was what brought me to this page in the first place), it would probably just be easier to define a new handlebars variable in your view-rendering function call, like:

Pass to handlebars on render:

var context= {
    'section1' : section1,
    'section2' : section2,
    'section1or2' : (section1)||(section2)
};

and then within your handlebars template:

{{#if section1or2}}
    .. content
{{/if}}

I mention this for simplicity's sake, and also because it's an answer that may be quick and helpful while still complying with the logicless nature of Handlebars.

Programmer Dan
  • 259
  • 1
  • 8
3

Install Ember Truth Helpers addon by running the below command

ember install ember-truth-helpers

you can start use most of the logical operators(eq,not-eq,not,and,or,gt,gte,lt,lte,xor).

{{#if (or section1 section2)}}  
...content  
{{/if}}

You can even include subexpression to go further,

{{#if (or (eq section1 "section1") (eq section2 "section2") ) }}  
...content  
{{/if}}
Ember Freak
  • 12,538
  • 3
  • 20
  • 47
2

I have found a npm package made with CoffeeScript that has a lot of incredible useful helpers for Handlebars. Take a look of the documentation in the following URL:

https://npmjs.org/package/handlebars-helpers

You can do a wget http://registry.npmjs.org/handlebars-helpers/-/handlebars-helpers-0.2.6.tgz to download them and see the contents of the package.

You will be abled to do things like {{#is number 5}} or {{formatDate date "%m/%d/%Y"}}

Cristian Rojas
  • 2,288
  • 5
  • 25
  • 37
1

if you just want to check if one or the other element are present you can use this custom helper

Handlebars.registerHelper('if_or', function(elem1, elem2, options) {
  if (Handlebars.Utils.isEmpty(elem1) && Handlebars.Utils.isEmpty(elem2)) {
    return options.inverse(this);
  } else {
    return options.fn(this);
  }
});

like this

{{#if_or elem1 elem2}}
  {{elem1}} or {{elem2}} are present
{{else}}
  not present
{{/if_or}}

if you also need to be able to have an "or" to compare function return values I would rather add another property that returns the desired result.

The templates should be logicless after all!

Community
  • 1
  • 1
deepflame
  • 858
  • 1
  • 7
  • 17
1

For those having problems comparing object properties, inside the helper add this solution

Ember.js helper not properly recognizing a parameter

Community
  • 1
  • 1
1

Here we have vanilla handlebars for multiple logical && and || (and or):

Handlebars.registerHelper("and",function() {
    var args = Array.prototype.slice.call(arguments);
    var options = args[args.length-1];

    for(var i=0; i<args.length-1; i++){
        if( !args[i] ){
            return options.inverse(this);
        }
    }

    return options.fn(this);
});


Handlebars.registerHelper("or",function() {
    var args = Array.prototype.slice.call(arguments);
    var options = args[args.length-1];

    for(var i=0; i<args.length-1; i++){
        if( args[i] ){
            return options.fn(this);
        }
    }

    return options.inverse(this);
}

// Results
// {{#and foo bar sally bob}} yup {{else}} nope {{/and}} // yup
// {{#or foo bar "" sally bob}} yup {{else}} nope {{/or}} // yup

// {{#and foo bar "" sally bob}} yup {{else}} nope {{/and}} // nope
// {{#or "" "" "" "" ""}} yup {{else}} nope {{/or}} // nope

Not so sure if it's "safe" to use "and" and "or"... maybe change to something like "op_and" and "op_or"?

Community
  • 1
  • 1
bob
  • 6,069
  • 1
  • 39
  • 35
1

Just came to this post from a google search on how to check if a string equals another string.

I use HandlebarsJS in NodeJS server-side, but I also use the same template files on the front-end using the browser version of HandlebarsJS to parse it. This meant that if I wanted a custom helper, I'd have to define it in 2 separate places, or assign a function to the object in question - too much effort!!

What people forget is that certain objects have inherit functions that can be used in the moustache template. In the case of a string:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match

An Array containing the entire match result and any parentheses-captured matched results; null if there were no matches.

We can use this method to return either an array of matches, or null if no matches were found. This is perfect, because looking at the HandlebarsJS documentation http://handlebarsjs.com/builtin_helpers.html

You can use the if helper to conditionally render a block. If its argument returns false, undefined, null, "", 0, or [], Handlebars will not render the block.

So...

{{#if your_string.match "what_youre_looking_for"}} 
String found :)
{{else}}
No match found :(
{{/if}}

UPDATE:

After testing on all browsers, this doesn't work on Firefox. HandlebarsJS passes other arguments to a function call, meaning that when String.prototype.match is called, the second argument (i.e. the Regexp flags for the match function call as per above documentation) appears to be being passed. Firefox sees this as a deprecated use of String.prototype.match, and so breaks.

A workaround is to declare a new functional prototype for the String JS object, and use that instead:

if(typeof String.includes !== 'function') {
    String.prototype.includes = function(str) {
        if(!(str instanceof RegExp))
            str = new RegExp((str+'').escapeRegExp(),'g');
        return str.test(this);
    }
}

Ensure this JS code is included before you run your Handlebars.compile() function, then in your template...

{{#your_string}}
    {{#if (includes "what_youre_looking_for")}} 
        String found :)
    {{else}}
        No match found :(
    {{/if}}
{{/your_string}}
Jon
  • 101
  • 1
  • 4
1

You can do it simply by using the logical operator like this shown below:

{{#if (or(eq firstValue 'String_to_compare_value') (eq secondValue 'String_to_compare_value'))}}business logic goes here{{/if}}

{{#if (and(eq firstValue 'String_to_compare_value') (eq secondValue 'String_to_compare_value'))}}business logic goes here{{/if}}

Before closing if you can write your business logic

1

Correct Solution for AND/OR

Handlebars.registerHelper('and', function () {
    // Get function args and remove last one (function name)
    return Array.prototype.slice.call(arguments, 0, arguments.length - 1).every(Boolean);
});
Handlebars.registerHelper('or', function () {
    // Get function args and remove last one (function name)
    return Array.prototype.slice.call(arguments, 0, arguments.length - 1).some(Boolean);
}); 

Then call as follows

{{#if (or (eq questionType 'STARTTIME') (eq questionType 'ENDTIME') (..) ) }}

BTW: Note that the solution given here is incorrect, he's not subtracting the last argument which is the function name. https://stackoverflow.com/a/31632215/1005607

His original AND/OR was based on the full list of arguments

   and: function () {
        return Array.prototype.slice.call(arguments).every(Boolean);
    },
    or: function () {
        return Array.prototype.slice.call(arguments).some(Boolean);
    }

Can someone change that answer? I just wasted an hour trying to fix something in an answer recommended by 86 people. The fix is to filter out the last argument which is the function name. Array.prototype.slice.call(arguments, 0, arguments.length - 1)

gene b.
  • 6,760
  • 9
  • 49
  • 122
0

Following these 2 guides a-way-to-let-users-define-custom-made-bound-if-statements and custom bound helpers I was able to adjust my shared views in this post on stackoverflow to use this instead of the standard #if statement. This should be more secure than just tossing an #if in there.

The custom bound helpers in that gist are outstanding.

<li>
    <a href="{{unbound view.varProductSocialBlog}}">
        {{#if-equal view.showDiv "true"}}<div>{{/if-equal}}<i class="fa fa-rss-square"></i>{{#if-equal view.showDiv "true"}}</div>{{/if-equal}}
        {{#if-equal view.showTitle "true"}}Blog{{/if-equal}}
    </a>
</li>

I am using the ember cli project to build my ember application.

Current setup at the time of this post:

DEBUG: -------------------------------
DEBUG: Ember      : 1.5.1
DEBUG: Ember Data : 1.0.0-beta.7+canary.b45e23ba
DEBUG: Handlebars : 1.3.0
DEBUG: jQuery     : 2.1.1
DEBUG: -------------------------------
Community
  • 1
  • 1
Chris Hough
  • 3,417
  • 2
  • 35
  • 68
0

In Ember.js you can use inline if helper in if block helper. It can replace || logical operator, for example:

{{#if (if firstCondition firstCondition secondCondition)}}
  (firstCondition || (or) secondCondition) === true
{{/if}}
Daniel Kmak
  • 16,209
  • 7
  • 65
  • 83
0

You can use the following code:

{{#if selection1}}
    doSomething1
{{else}}
   {{#if selection2}}
       doSomething2
   {{/if}}
{{/if}}
  • Please explain more about your thoughts and process, it can be very hard for people to understand your solution if they don't know the context or are new to the language. – mrhn Mar 31 '20 at 17:26
-1

Here's an approach I'm using for ember 1.10 and ember-cli 2.0.

// app/helpers/js-x.js
export default Ember.HTMLBars.makeBoundHelper(function (params) {
  var paramNames = params.slice(1).map(function(val, idx) { return "p" + idx; });
  var func = Function.apply(this, paramNames.concat("return " + params[0] + ";"))
  return func.apply(params[1] === undefined ? this : params[1], params.slice(1));
});

Then you can use it in your templates like this:

// used as sub-expression
{{#each item in model}}
  {{#if (js-x "this.section1 || this.section2" item)}}
  {{/if}}
{{/each}}

// used normally
{{js-x "p0 || p1" model.name model.offer.name}}

Where the arguments to the expression are passed in as p0,p1,p2 etc and p0 can also be referenced as this.

Michael
  • 9,761
  • 3
  • 57
  • 56