Logical operator in a handlebars.js {{#if}} conditional
Asked Answered
M

28

555

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.

Malposition answered 13/1, 2012 at 15:59 Comment(1)
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.Weidner
M
579

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}}
Mele answered 22/2, 2012 at 23:51 Comment(21)
This does go against the logicless nature of Handlebars / Moustache, but is certainly useful nonetheless, thanks!Kimura
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: #18005611Kandicekandinsky
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.Hizar
See my answer for a simple if equal helper supporting bound properties.Tabular
Although it is very helpful, this introduces already some logic in your templates. I imagine everything goes to hell from here on.Monarchy
if you want unlimited-logic-hell, see this answer. The accepted answer only accepts 2 values and handles one (or a limited set of) operand(s)Muskrat
@BalaClark is it really logicless though? I mean it still has "if" statements - just because they are intentionally crippled doesn't change the philosophy.Jacquenette
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!Patrizia
May be a dumb question but what are the options for?Brickey
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).Mele
Are using CLI by chance? #23852051 We have similar issues.Maddeu
Warning: Object 1 has no method 'fn'.Lecherous
Is it possible to to something like this (with 3 conditions)? {{#if cond1 || cond2 || cond3}}{{/if}}Anitaanitra
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).Mele
I get First argument must be a function, to be called on the rest of the arguments; found STRINGPeppy
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 logiclessBehn
How could I use this to do an else ifCond condition?Psychosexual
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?Unravel
@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.Austriahungary
"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.Thermal
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!Bakker
P
514

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
Psilocybin answered 13/1, 2012 at 15:59 Comment(14)
don't forget about '||' and '&&'. I added these cases and they're very useful.Rivas
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}}Guaiacum
If I am using ember with rails, where should I define this? which file should I place this code? Thanks.Breathless
@Rivas Those would be helpful. Would you mind adding them to the example code?Cosher
how would you use this in a template? especially the options.fn part?Forsythia
What should options be set to? How is this passed in? I get a TypeError: opts is undefined error.Pardoes
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)Simonsimona
but if v1 and v2 also one condition then how can i use? for ex: if(value == "a" || value == "b")Suitcase
Nice compact version: codegists.com/snippet/javascript/…Hargis
Can you do an else if with this?Hargis
context variables passed to this function are undefinedVeasey
When I try to console.log(operator); inside this helper and use this condition {{#ifCond charge_id '>' 2}}. I've got this &gt; instead of this >Ragg
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); } Jacoby
Years too late here, but it's important to consider the likelyhood of non-boolean variables. Something like undefined || "some string" will yield the string value rather than a boolean.Histogram
P
232

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);
    }
});
Palaeography answered 25/7, 2015 at 23:48 Comment(19)
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: github.com/jmurphyau/ember-truth-helpers.Giorgione
This way, with no options execution is better to keeps with if method and easier to test. Thanks!Goahead
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")}}Upswing
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);.Derryberry
thumbs up for using native functionality. .... thumbs down for having to use LISP-like expressions (but that's not your fault heh?.... )Depalma
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? @Derryberry , @PalaeographyRosary
@Mikhail, have you tried wrapping it in parens? (ne (Can.DoSomething) true)Palaeography
@Palaeography Hi, in this case it starts thinking that Can is a function, I assume because it in parensRosary
@Rosary I can't reproduce your original issue: codepen.io/anon/pen/RVbdwaPalaeography
@Palaeography looks like problem with template compiler, your example works as expected, will continue research. Thanks.Rosary
I took the ideas here and made a small "library" of helper functions: github.com/plaa/handlebars-logicAlewife
A little more advanced version that can take multiple parameters on each operator.Lincolnlincolnshire
@Derryberry Thanks for the clever one-liners! I've updated the answer to support >2 args based on your comment.Palaeography
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: #50191354 Do you know how to solve this?Lalia
@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))Palaeography
@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.Lalia
@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.Palaeography
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); }Logway
@AgiHammerthief Try: tn: function (v1, v2, v3) { return v1 ? v2 : v3; }. Usage: {{#if (tn someCondition valueIfTrue valueIfFalse) }}Palaeography
M
95

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: '[email protected]'
}, {
  firstName: 'Sam',
  age: '18',
  email: '[email protected]'
}, {
  firstName: 'Perter',
  lastName: 'Smith',
  age: '25',
  email: '[email protected]'
}];

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);
});
Muskrat answered 20/2, 2014 at 17:35 Comment(18)
:) thanks, I updated the gist 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..Muskrat
found a solution for "upper scope access" but using a new helper, see updated answer, {{z ...}} helperMuskrat
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.Behind
Just a heads up that this can really slow down the loading of a page.Behind
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.Muskrat
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`}}Feckless
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' }}Muskrat
@bentael, I'm using mandrill.zendesk.com/hc/en-us/articles/…. 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.Feckless
@Feckless 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?Muskrat
@Muskrat I've made a simple demo here: 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.Feckless
@Feckless so the ask is to have it with, with or without the this? if so, check my fork: 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.Muskrat
@Muskrat 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.Feckless
@Muskrat hit a snag, if a variable doesn't exist it throws an error, where it should just return false. codepen.io/rickysullivan/pen/ALdxobFeckless
@Feckless 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.Muskrat
here: 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.Muskrat
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.Muskrat
@Feckless 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 caseMuskrat
For NodeJS usage you might want to use a safer eval solution such as: npmjs.com/package/safe-eval instead of the eval() functionRecoil
L
44

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.

Livvy answered 3/7, 2013 at 21:29 Comment(10)
Not really, you don't compare two value, you just ensure both of them exists, it's different.Figurine
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."Livvy
You are right, I firstly thought the test was to compare two values, not testing of both existed. My bad, sorry.Figurine
This doesn't work correctly. If cond1 is true and con2 is false, nothing will be printed.Federate
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.Livvy
@jQwierdy are you sure [] is a falsey value? !![] -> trueHabile
@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).Livvy
@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.Habile
@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).Livvy
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}}Arwood
T
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}}
Tabular answered 12/1, 2014 at 7:41 Comment(3)
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.Kauslick
Has anyone gotten this working with HTMLBars/Ember 1.10? Ember.Handlebars.bind no longer seems to exist.Chiropractic
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 upCytotaxonomy
U
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);
    }
});
Ulrikaumeko answered 7/9, 2013 at 19:11 Comment(3)
how would you use this in a template? especially the options.fn part?Forsythia
{{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.Mele
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 lineCorpus
S
12

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}}
Sanatory answered 7/11, 2017 at 10:55 Comment(0)
E
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}}
Elena answered 24/10, 2013 at 14:40 Comment(3)
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.Gastongastralgia
@Gastongastralgia this isn't a native helper. If you follow the link you'll see that it is a custom defined helper.Boesch
@Boesch you're right, I was using handlebars in assemble, which includes this helper natively. My bad.Gastongastralgia
W
5

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}}
Wrench answered 14/5, 2016 at 6:16 Comment(2)
Looks like this works without ember-truth-helpers. Or something like this embedded into handlebars. Checked in 2022Firdausi
Yeah. Thanks for the heads-up. It seems In this RFC discussed and implemented gradually.Wrench
U
5

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.

Unlikely answered 15/5, 2018 at 13:1 Comment(0)
D
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" numbertwo}}
  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".

Dogleg answered 19/7, 2013 at 4:12 Comment(0)
M
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}}
Micrography answered 8/11, 2013 at 8:57 Comment(2)
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.Pert
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.Micrography
B
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}}
Bonina answered 3/12, 2013 at 14:13 Comment(0)
I
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.

Incurrent answered 25/4, 2014 at 19:0 Comment(0)
E
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"}}

Escurial answered 11/7, 2013 at 15:21 Comment(0)
L
2

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://mcmap.net/q/73388/-logical-operator-in-a-handlebars-js-if-conditional

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)

Lalia answered 5/5, 2018 at 16:6 Comment(0)
V
2

You can use the following code:

{{#if selection1}}
    doSomething1
{{else}}
   {{#if selection2}}
       doSomething2
   {{/if}}
{{/if}}
Vaishnava answered 31/3, 2020 at 15:42 Comment(1)
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.Juan
D
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!

Dogeatdog answered 8/8, 2013 at 7:11 Comment(0)
G
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"?

Ganister answered 20/12, 2016 at 5:38 Comment(0)
R
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}}
Reareace answered 12/4, 2017 at 3:27 Comment(1)
Updated for Firefox, this is what I'm currently usingReareace
S
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

School answered 2/8, 2017 at 1:1 Comment(0)
P
1

you cannot write your expressions inside handlebars template but all of your logic (expressions) in express.js

app.js

res.render("view.hbs", {expression: section1 || section2})

view.hbs

{{#if expression}}
  <h1> My Expression Returned True </h1>
{{ else }}
  <h2>My Expression Returned False</h2>
{{/if}} <!-- End IF -->
Piccaninny answered 3/2, 2022 at 22:8 Comment(0)
M
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: -------------------------------
Maddeu answered 26/5, 2014 at 3:14 Comment(0)
S
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}}
Selfcontradiction answered 12/7, 2015 at 10:53 Comment(0)
C
0

This can used to check all the basic conditions.

const operators = {
    '==': (a, b) => a == b,
    '===': (a, b) => a === b,
    '!=': (a, b) => a != b,
    '!==': (a, b) => a !== b,
    '<': (a, b) => a < b,
    '<=': (a, b) => a <= b,
    '>': (a, b) => a > b,
    '>=': (a, b) => a >= b,
    '&&': (a, b) => a && b,
    '||': (a, b) => a || b,
};

Object.entries(operators).forEach(([k, v]) => {
    Handlebars.registerHelper(k, v);
});

name = null;

<title>{{"||" name "APF"}}</title> // APF
Caution answered 30/3, 2023 at 13:12 Comment(0)
S
0

example of or helper method:

helpers.or = function() {
  var len = arguments.length - 1;
  var options = arguments[len];
  var val = false;

  for (var i = 0; i < len; i++) {
    if (arguments[i]) {
      val = true;
      break;
    }
  }
  return util.value(val, this, options);
};
Sahara answered 18/4, 2023 at 18:24 Comment(0)
B
-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.

Bazaar answered 25/3, 2015 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.