# 块助手

块助手使得定义自定义迭代器和其他功能成为可能,这些功能可以使用新的上下文调用传递的块。

# 基本块

出于演示目的,我们定义一个块助手来调用该块,就好像不存在助手一样。

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

noop 助手("无操作" 的缩写)将收到选项哈希。 此选项哈希包含一个函数 (options.fn),其行为类似于正常编译的 Handlebars 模板。 具体来说,该函数将获取上下文并返回一个字符串。

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

Handlebars 始终使用当前上下文作为 this 调用辅助程序,因此你可以使用 this 调用该块来评估当前上下文中的块。

以这种方式定义的任何助手将优先于上下文中定义的字段。 为了访问被助手屏蔽的字段,可以使用路径引用。 在上面的示例中,将使用以下命令引用上下文对象上名为 noop 的字段:

{{./noop}}

# 基本块变化

为了更好地说明语法,让我们定义另一个块助手,它向封装文本添加一些标记。

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

粗体助手将添加标记以使其文本变为粗体。 和以前一样,该函数将采用上下文作为输入并返回一个字符串。

Handlebars.registerHelper("bold", function(options) {
  return new Handlebars.SafeString('<div class="mybold">' + options.fn(this) + "</div>");
});

# with 助手

with 辅助程序演示了如何将参数传递给你的辅助程序。 当使用参数调用助手时,它会使用模板传入的任何上下文来调用。

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

如果 JSON 对象的一部分包含深度嵌套的属性,并且你希望避免重复父名称,你可能会发现这样的辅助程序很有用。 上面的模板对于如下 JSON 很有用:

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

实现这样的助手很像实现 noop 助手。 助手可以接受参数,并且参数的计算就像直接在 {{mustache}} 块内使用的表达式一样。

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

参数按照传递的顺序传递给助手,后跟选项哈希。

# 简单迭代器

块助手的一个常见用例是使用它们来定义自定义迭代器。 事实上,所有 Handlebars 内置助手都被定义为常规 Handlebars 块助手。 我们来看看内置的 each 助手是如何工作的。

<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" }
  ];
}

该助手与原始 each 助手类似。

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 的运行时库这样的库可以让这个变得更好看。 例如,使用 SproutCore 的运行时库可能如下所示:

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

# 条件句

块助手的另一个常见用例是评估条件语句。 与迭代器一样,Handlebars 的内置 ifunless 控制结构是作为常规 Handlebars 助手实现的。

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

控制结构通常不会更改当前上下文,而是根据某些变量决定是否调用该块。

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

编写条件时,你通常希望模板能够提供一个 HTML 块,如果条件计算结果为 false,则助手应插入该 HTML 块。 Handlebars 通过为块助手提供通用 else 功能来解决这个问题。

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

Handlebars 为 else 片段提供块作为 options.inverse。 你不需要检查 else 片段是否存在: Handlebars 会自动检测并注册 "noop" 功能。

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

Handlebars 通过将其附加为选项哈希的属性来为块助手提供额外的元数据。 继续阅读更多示例。

条件语句也可以通过在 else 小胡子中包含后续的辅助程序调用来链接。

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

没有必要在后续调用中使用相同的辅助程序,除非辅助程序可以像任何其他辅助程序一样在 else 部分中使用。 当辅助程序值不同时,结束胡须应与开始辅助程序名称匹配。

# 哈希参数

与常规助手一样,块助手可以接受可选的哈希作为其最终参数。 让我们重新审视 list 辅助程序,使我们能够向我们将创建的 <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 = Object.keys(options.hash)
    .map(function(key) {
      return key + '="' + options.hash[key] + '"';
    })
    .join(" ");

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

哈希参数提供了一种强大的方法,可以向块助手提供许多可选参数,而不会产生位置参数带来的复杂性。

块助手还可以将私有变量注入到其子模板中。 这对于添加原始上下文数据中没有的额外信息非常有用。

例如,当迭代列表时,你可以提供当前索引作为私有变量。

{{#list array}}
  {{@index}}. {{title}}
{{/list}}
Handlebars.registerHelper("list", function(context, options) {
  var out = "<ul>",
    data;

  if (options.data) {
    data = Handlebars.createFrame(options.data);
  }

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

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

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

通过 data 选项提供的私有变量在所有后代作用域中都可用。

父作用域中定义的私有变量可以通过路径查询来访问。 为了访问父迭代器的 index 字段,可以使用 @../index

确保在每个助手中创建一个新的数据框来分配自己的数据。 否则,下游助手可能会意外地改变上游变量。

还要确保在尝试与现有数据对象交互之前定义 data 字段。 私有变量行为是有条件编译的,某些模板可能不会创建此字段。

# 块参数

Handlebars 3.0 中的新增功能可以从支持助手接收命名参数。

{{#each users as |user userId|}}
  Id: {{userId}} Name: {{user.name}}
{{/each}}

在此特定示例中,user 将具有与当前上下文相同的值,而 userId 将具有迭代的索引值。

这允许嵌套辅助程序避免私有变量可能发生的名称冲突。

{{#each users as |user userId|}}
  {{#each user.book as |book bookId|}}
    User Id: {{userId}} Book Id: {{bookId}}
  {{/each}}
{{/each}}

许多 内置助手 支持块参数和任何自定义辅助程序都可以通过 blockParams 选项字段提供它们。

助手可以通过 options.fn.blockParams 字段确定模板引用的块参数的数量,这是一个整数。 该值表示子模板可以引用的块参数的数量。 超出此计数的参数将永远不会被引用,并且如果需要,辅助程序可以安全地省略。 这是可选的,传递给模板的任何其他参数都将被默默忽略。

# 原始块

原始块可用于需要处理未处理的胡须块的模板。

template
{{{{raw-loud}}}}
  {{bar}}
{{{{/raw-loud}}}}
{{{{raw-helper}}}}
  {{bar}}
{{{{/raw-helper}}}}

将执行助手 raw 辅助程序而不解释内容。

Handlebars.registerHelper("raw-helper", function(options) {
  return options.fn();
});

将渲染

{{bar}}
Last Updated: 2023/9/14 11:12:03