在handlebars.js的{{#if}}条件语句中使用的逻辑运算符

552

在Handlebars JS中,是否有一种方法可以将逻辑运算符合并到标准的handlebars.js条件运算符中?就像这样:

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

我知道我可以编写自己的帮助程序,但首先我想确保我没有重复造轮子。


1
对于“与”逻辑,您可以使用嵌套的if条件,但这很笨拙,并且无法帮助您处理else“除非”或任何“或”逻辑。 - combinatorist
29个回答

576

通过“欺骗”块助手是可能的。这可能违背了开发Handlebars的人的意识形态。

Handlebars.registerHelper('ifCond', function(v1, v2, options) {
  if(v1 === v2) {
    return options.fn(this);
  }
  return options.inverse(this);
});
你可以在模板中像这样调用helper。
{{#ifCond v1 v2}}
    {{v1}} is equal to {{v2}}
{{else}}
    {{v1}} is not equal to {{v2}}
{{/ifCond}}

65
这确实违反了Handlebars/Moustache无逻辑性的特性,但仍然非常有用,谢谢! - Bala Clark
6
请注意,这在绑定属性(例如Ember绑定)中根本不起作用。对于字面值是可以使用的,但无法解析模型属性(已测试使用Ember 1.0.0-rc.8和Handlebars 1.0.0),并且registerBoundHelper无法处理Handlebars语法。解决方法是创建一个自定义视图:https://dev59.com/-nXYa4cB1Zd3GeqP3TGm - Warren Seine
41
@BalaClark它真的是无逻辑的吗?我的意思是,它仍然有"if"语句——尽管它们是故意禁用的,但这并不改变哲学上的含义。 - phreakhead
58
我不明白为什么 Handlebars 不能包含 AND/OR 这些运算符。它并不违反无逻辑性的特点,因为它已经有了 IF、UNLESS 和 EACH... 所以说无逻辑性也就没有那么重要了。 - Asaf
4
限制选择/自我限制可以帮助遵循开发哲学。通过一个可行的开发哲学,实现它的API在解决设计问题时不应该感到受限制,相反地,它应该以一种奇怪的方式赋予力量(有点像节制)。 回到主题,要么Handlebars的哲学是矛盾的,要么这个问题可以通过遵循该哲学的解决方案来解决,要么Handlebars的API实现不良,或者这不是Handlebars旨在解决的问题。 - Matthias
显示剩余16条评论

510

将解决方案进一步完善,增加了比较操作符。

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

像这样在模板中使用它:

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

CoffeeScript 版本

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

23
不要忘记 '||''&&'。我添加了这些情况,它们非常有用。 - Jason
22
作为Handlebars的新手,有一件事情不太清楚,那就是你必须把运算符作为字符串传递,否则编译器在对模板进行标记化时会出错。{{#ifCond true '==' false}} - Joe Holloway
2
我发现对象属性的值没有被计算。添加以下内容可以帮助解决问题:v1 = Ember.Handlebars.get(this, v1, options)v2 = Ember.Handlebars.get(this, v2, options) - ZX12R
4
但如果v1和v2也有一个条件,我该如何使用?例如:if(value == "a" || value == "b") - Krishna
5
你可以用这个来做一个“否则如果”的语句吗? - jbyrd
显示剩余9条评论

226

Handlebars支持嵌套操作,如果我们按照不同的逻辑编写代码,则提供了很大的灵活性(和更清晰的代码)。

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

实际上,我们可以添加各种各样的逻辑:

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

只需注册这些帮助程序:

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

5
请注意,在Ember 1.10中使用HTMLBars后才能实现这一点。此外,如果您愿意,这些帮助程序也作为Ember CLI插件提供:https://github.com/jmurphyau/ember-truth-helpers。 - stephen.hanson
42
我们似乎已经将Lisp内嵌到了Handlebars中。 - jdunning
10
你的 andor 助手仅适用于2个参数,这不是您想要的。我修改了 andor 工具来支持超过两个参数。使用这个一行代码作为 and: return Array.prototype.slice.call(arguments, 0, arguments.length - 1).every(Boolean); 并且使用这个一行代码作为 or: return Array.prototype.slice.call(arguments, 0, arguments.length - 1).some(Boolean); - Luke
7
一份更高级的版本可以在每个运算符上接受多个参数。 - Nate
1
@AgiHammerthief 请尝试:tn: function(v1, v2, v3) { return v1 ? v2 : v3; }。用法:{{#if (tn someCondition valueIfTrue valueIfFalse)}} - kevlened
显示剩余16条评论

95

如果你喜欢冒险,那么这篇文章就是为你准备的。

要点: https://gist.github.com/akhoury/9118682 演示: 下面的代码片段

Handlebars助手: {{#xif EXPRESSION}} {{else}} {{/xif}}

一个执行任何表达式IF语句的助手

  1. EXPRESSION是适当转义的字符串
  2. 是的,你需要适当地转义字符串文字或者只是交替使用单引号和双引号
  3. 你可以访问任何全局函数或属性,例如 encodeURIComponent(property)
  4. 此示例假定您将此上下文传递给handlebars template( {name: 'Sam', age: '20' } ), 注意 age 是一个 string,这样我就可以在本文后面演示 parseInt()

用法:

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

输出

<p>
  BOOM
</p>

JavaScript:(它依赖于另一个助手-继续阅读)

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

Handlebars助手:{{x EXPRESSION}}

一个执行javascript表达式的助手

  1. EXPRESSION是经过适当转义的字符串
  2. 是的,您需要正确转义字符串文字或仅替换单引号和双引号
  3. 您可以访问任何全局函数或属性,例如:parseInt(property)
  4. 此示例假定您将此上下文传递给了Handlebars template({name:'Sam',age:'20'})age是一个string,只是为演示目的,它可以是任何内容..

用法:

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

输出:

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

JavaScript:

这看起来有点长,因为我为了清晰起见扩展了语法并对几乎每一行进行了注释。

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>

更多

如果您想要访问上层作用域,这个稍微有些不同,表达式是所有参数的连接,用法:说上下文数据看起来像这样:

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

16
太好了。Handlebars 不能指示你该做什么 :) - lemiant
1
谢谢,我稍微更新了gist,添加了一些好东西(与此SO问题不直接相关,但是符合在handlebars模板中做你想做的事情的精神)。然而,你应该注意限制,我还没有找到一种从表达式内部访问“上层作用域级别”的方法,比如说你在each作用域中,{{#each}} ... {{xif ' ../name === this.name' }} {{/each}} ... 但我仍在调查中。 - bentael
1
找到了“上级作用域访问”的解决方法,但是使用了一个新的帮助程序,请查看更新的答案,{{z ...}}帮助程序。 - bentael
1
@rickysullivan,尝试将 eval(expression); 替换为 eval('(function(){try{return ' + expression + ';}catch(e){}})();'); - 我知道这看起来很丑陋,但我无法想到更安全的方法 - 请注意,这个执行速度不是很快,所以在大量迭代中不要使用。 - bentael
1
对于NodeJS的使用,您可能希望使用更安全的eval解决方案,例如:https://www.npmjs.com/package/safe-eval,而不是eval()函数。 - BigMan73
显示剩余16条评论

43
有一种简单的方法可以在不编写帮助函数的情况下完成这个任务......可以在模板中完全实现它。
{{#if cond1}}   
  {{#if con2}}   
    <div> and condition completed</div>  
  {{/if}}
{{else}}   
  <div> both conditions weren't true</div>  
{{/if}}

编辑:相反,你可以通过以下方法执行或操作:

{{#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}}

编辑/备注:从handlebars的官网handlebarsjs.com获取,以下是假值:

您可以使用if帮助器有条件地呈现一个块。如果其参数返回false、undefined、null、""或[](一个“falsy”值),那么任何'cond'(如cond1或cond2)都不会被视为真。


13
不完全是,你不是在比较两个值,而是确保它们都存在,这是不同的。 - Cyril N.
3
实际上,Handlebars 的评估方式与 JavaScript 相同。来自该网站的说明:"您可以使用 if 帮助程序有条件地呈现一个块。如果它的参数返回 false、undefined、null、“”或[](即“假值”),Handlebars 将不会呈现该块。" - Jono
1
你是对的,我最初认为这个测试是比较两个值,而不是测试它们是否都存在。我的错,抱歉。 - Cyril N.
4
这个程序不能正常运行。如果cond1为真且cond2为假,就不会有任何内容被打印出来。 - Tessa Lau
1
嘿 @TessaLau,我想我明白你的意思了。不过,如果你画出控制流程图,你会发现在第一行就将控制流程移动到了该条件。 - Jono
显示剩余5条评论

19

这里所有回答的问题是它们不能与绑定属性一起使用,即当涉及的属性更改时,if条件不会重新评估。这是一个稍微高级一点的帮助程序版本,支持绑定。它使用Ember源代码中的 bind 函数,该函数也用于实现正常的Ember #if助手。

这个版本仅限于左侧的单个绑定属性与右侧的常量进行比较,我认为它对于大多数实际目的来说已经足够好了。如果您需要比简单比较更高级的东西,那么也许最好开始声明一些计算属性并使用普通的#if助手。

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

你可以像这样使用它:

{{#ifeq obj.some.property "something"}}
  They are equal!
{{/ifeq}}

如果您正在使用Ember,则这是正确的答案。您提到的其他解决方案只会传递键而不是值。顺便说一句,感谢您,我花了几个小时来思考这个问题。 - J Lee
2
有没有人用HTMLBars/Ember 1.10使其工作? Ember.Handlebars.bind似乎已不再存在。 - Linda
我最初使用registerBoundHelper,但当它的条件发生改变时,它不会切换到else值并返回...这种方法可以在Ember中起作用以进行更改 :) 它应该有更多的支持票数。 - Matt Vukomanovic

12

如果您想检查多个条件,这里有一个解决方案:

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

使用方法:

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

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

喜欢这个的简洁性。谢谢! - John Doherty

12

基本上适用于任何二元运算符的改进解决方案(至少适用于数字,字符串在使用eval时不太有效,注意如果使用非定义运算符与用户输入一起可能会发生脚本注入):

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

你会如何在模板中使用这个?特别是 options.fn 部分? - qodeninja
{{ifCond val1 '||' val2}}如果正确,则返回options.fn(true,即ifCond子句),否则返回options.inverse(false,即else子句)。 - Nick Kitto
2
由于脚本注入的警告,我强烈建议不要使用这个辅助工具。在大型代码库中,这很容易导致严重的安全问题。 - Jordan Sitkin

7

这是我使用的块助手链接:比较块助手。它支持所有标准运算符,并允许您编写如下所示的代码。它真的非常方便。

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

1
这应该是获胜的投票。本地和预期的。我发现几乎每次当你在编写新的帮助程序时,你都会想得太多。 - augurone
2
@augurone 这不是一个原生的帮助程序。如果你跟随链接,你会发现它是一个自定义定义的帮助程序。 - gfullam
@gfullam 你说得对,我在使用Assemble时使用了Handlebars,它本身就包含了这个helper。我的错。 - augurone

5

又一个有问题的三元助手的解决方案:

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

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

或者一个简单的合并辅助函数:

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

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

由于这些字符在handlebars标记中没有特殊的含义,因此您可以自由地将它们用于辅助程序名称。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接