5

原文在此:original article in here

译注:
1. 一般来说context,翻译为【上下文】,在 JS 中多数时候就是指函数执行时的 this 的引用。
2. 看,不是最重要的,多写代码,照着例子写写,再改改,看看结果,就知道怎么回事了。


块级 helpers 使用自定义的迭代器,其他的 helpers 都可以使用一个新的上下文来执行内部的代码块。

基础

先定义一个简单的块级 helper,它只是简单的执行一下这块代码,就跟没有用这个helper一样。

<div class="entry">
  <h1>{{title}}</h1>
  <div class="body">
    {{#noop}}{{body}}{{/noop}}
  </div>
</div>

这个 noop helper 接受一个 options 对象。这个对象有一个叫 options.fn 的方法,这个方法的用法就跟普通的编译过的 Hanldebars 模板的用法一样。所不同的就是,这个函数执行时会带有一个上下文,并且会返回一个字符串。

Handlebars.registerHelper('noop', function(options) {
  return options.fn(this);
});

Handlebars 在执行 helpers 的时候,总是会将 this 指向当前的上下文,所以,你只需用 this 来调用这一块代码,就可以在当前的上下文中求值了。

with helper

在上面介绍的 noop helper 的基础上,很容易的就能想到应该如何实现 with helper 了。这里呢,不是用当前的上下文来执行代码块,我们可以使用任意的模板所传递进来的上下文。

<div class="entry">
  <h1>{{title}}</h1>
  {{#with story}}
    <div class="intro">{{{intro}}}</div>
    <div class="body">{{{body}}}</div>
  {{/with}}
</div>

在你使用的JSON嵌套非常深的时候,就会觉得这个helper 非常有帮助了,否则的话,每次都需要先写上很长的父级属性。比如上面的模板可以使用下面的这段JSON:

{
  title: "First Post",
  story: {
    intro: "Before the jump",
    body: "After the jump"
  }
}

实现这种helper,就跟实现一堆 noop helper 没什么区别。要注意的是,helpers 可以接受参数,并且对参数的求值就跟直接使用 {{mustache}} 表达式一样一样地。

Handlebars.registerHelper('with', function(context, options) {
  return options.fn(context);
});

helpers 接受参数的顺序跟传递进来的顺序相同。

简单的迭代器

使用块级 helpers 的一个非常常见的目的就是定义一个自定义的迭代器。实际上,所有的 Handlebars 内置的 helpers 就是一些常规的 块级 helpers。我们一起来看看内置的 each helper 是如何工作的:

<div class="entry">
  <h1>{{title}}</h1>
  {{#with story}}
    <div class="intro">{{{intro}}}</div>
    <div class="body">{{{body}}}</div>
  {{/with}}
</div>
<div class="comments">
  {{#each comments}}
    <div class="comment">
      <h2>{{subject}}</h2>
      {{{body}}}
    </div>
  {{/each}}
</div>

在这里的情况下,我们希望对 comments 数组的每一项都执行 传递给 each 的这一块代码。

Handlebars.registerHelper('each', function(context, options) {
  var ret = "";

  for(var i=0, j=context.length; i<j; i++) {
    ret = ret + options.fn(context[i]);
  }

  return ret;
});

这样,我们迭代传进来的参数的每一个项,并在每一次迭代的时候执行一次块中的代码。在我们迭代的过程中,还构建了一个字符串,并最终返回这个字符串。

可以看出,你也可以很容易的实现一个更高级的迭代器。例如,我们创建一个迭代器,可以生成 <ul> 的包裹容器,并且把每一个元素的计算结果包裹到<li> 中去。

{{#list nav}}
  <a href="{{url}}">{{title}}</a>
{{/list}}

可以使用类似下面的上下文来执行模板:

{
  nav: [
    { url: "http://www.yehudakatz.com", title: "Katz Got Your Tongue" },
    { url: "http://www.sproutcore.com/block", title: "SproutCore Blog" },
  ]
}

这个 helper 跟内置的 each helper 没有太大的不同。

Handlebars.registerHelper('list', function(context, options) {
  var ret = "<ul>";

  for(var i=0, j=context.length; i<j; i++) {
    ret = ret + "<li>" + options.fn(context[i]) + "</li>";
  }

  return ret + "</ul>";
});

你当然也可以使用类似 underscore.js 或 SproutCore's 的类库,让代码看起来更精简漂亮些。使用 SproutCore's 的示例如下:

Handlebars.registerHelper('list', function(context, options) {
  return "<ul>" + context.map(function(item) {
    return "<li>" + options.fn(item) + "</li>";
  }).join("\n") + "</ul>";
});

条件

使用 helpers 的另一个常见需求就是实现条件判断。
再次重申一遍,Handlebars 内置的 ifunless 控制结构就是使用常规的 helpers 来实现的。

{{#if isActive}}
  <img src="star.gif" alt="Active">
{{/if}}

控制结构一般不会改变上下文,只是要根据一些变量来决定是否要执行这一块代码。

Handlebars.registerHelper('if', function(conditional, options) {
  if(conditional) {
    return options.fn(this);
  }
});

在写控制结构的时候,经常需要在模板中提供一块 条件求值返回false时 执行的模板代码。Handlebars 给 helpers 提供了通用的 else 功能来解决这个问题。

{{#if isActive}}
  <img src="star.gif" alt="Active">
{{else}}
  <img src="cry.gif" alt="Inactive">
{{/if}}

Handlebars 以 options.inverse 的形式来支持 else 块中的代码。如果模板没有提供条件取反的模板,Handlebars会自动的创建一个 空函数,这样你就不必去检查 inverse 是否存在了。

Handlebars.registerHelper('if', function(conditional, options) {
  if(conditional) {
    return options.fn(this);
  } else {
    return options.inverse(this);
  }
});

Handlebars 还给 helpers 提供了额外的元数据,附加在 options 对象上面。继续阅读下面的例子。

哈希参数

就像普通的 helpers,块级 helpers 可以接受一个可选的对象作为最后一个参数。我们一起重温一下 list helper,并让他可以接受任意数量的可选属性,并且把添加到我们要创建的 <ul> 元素上。

{{#list nav id="nav-bar" class="top"}}
  <a href="{{url}}">{{title}}</a>
{{/list}}

Handlebars 以 options.hash 的形式把最后传进来的键值对挂在上来。这样就更容易接受不定数量的参数了,同时接受一个可选的对象。如果模板没有提供哈希参数,Handlebars 会自动的传一个空对象({}),这样你就不必检查哈希参数是否存在了。

Handlebars.registerHelper('list', function(context, options) {
  var attrs = Em.keys(options.hash).map(function(key) {
    key + '="' + options.hash[key] + '"';
  }).join(" ");

  return "<ul " + attrs + ">" + context.map(function(item) {
    return "<li>" + options.fn(item) + "</li>";
  }).join("\n") + "</ul>";
});

哈希参数提供了一个强大的方法来给 helper 提供任意数量的可选参数,而且避免了由可选性导致的复杂语法。

块级helper还可以往它的子模板中注入私有变量。在一些情况下会很有帮助,比如需要把一些不在原来上下文中的数据插进来。

例如,在循环一个列表的时候,你需要以私有变量的形式提供当前的索引index值。

{{#list array}}
  {{@index}}. {{title}}
{{/list}}
Handlebars.registerHelper('list', function(context, options) {
  var out = '

<

ul>', data;

  for (var i=0; i<context.length; i++) {
    if (options.data) {
      data = Handlebars.createFrame(options.data || {});
      data.index = i;
    }

    out += "<li>" + options.fn(context[i], { data: data }) + "</li>";
  }

  out += "</ul>";
  return out;
});

私有属性通过 data 选项来提供,并且在所有的内部作用域内都可以使用。

要保证每次带着 data 执行代码块的时候都生成一个新的 data 帧。否则的话,后面的 helpers 可能会意外的改变前面的变量。

还要确保在试图跟已存在的 data 对象交互之前 data 对象已经存在了。私有变量的行为是有条件编译的,有一些模板可能并没有创建这个字段。

空白管理

模板中的空白可以忽略,mustache声明的两边都可以,只需添加一个 ~ 字符即可。写了这个之后,这一边的所有空白都会被移除,直到最近的Handlebars表达式或这一边的非空白字符。

{{#each nav ~}}
  <a href="{{url}}">
    {{~#if test}}
      {{~title}}
    {{~^~}}
      Empty
    {{~/if~}}
  </a>
{{~/each}}

用这个上下文:

{
  nav: [
    {url: 'foo', test: true, title: 'bar'},
    {url: 'bar'}
  ]
}

得出的结果没有换行,也没有格式化用的空白符:

<a href="foo">bar</a><a href="bar">Empty</a>

转载请注明来自超2真人
本文链接:http://www.peichao01.com/static_content/doc/html/introduce-handlebars-helpers.html


超2真神
1.1k 声望104 粉丝

关注设计、漫画、动画,前端、javascript、iOS还有任何好玩的事情