# 区块助手

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

# 基本区块

为了演示目的,让我们定义一个区块助手,它像没有助手一样调用区块。

<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。Handlebars 通过为区块助手提供通用的 else 功能来解决此问题。

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

Handlebars 将 else 片段的区块提供为 options.inverse。您不需要检查 else 片段是否存在:Handlebars 会自动检测它并注册一个“无操作”函数。

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

Handlebars 通过将它们附加为选项哈希的属性,为区块助手提供额外的元数据。继续阅读以获取更多示例。

条件语句也可以通过在 else mustache 中包含后续助手调用来链接。

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

在后续调用中使用相同的助手不是必需的,就像任何其他助手一样,可以在 else 部分使用 unless 助手。当助手值不同时,结束 mustache 应与开始助手名称匹配。

# 哈希参数

与常规助手一样,区块助手可以接受一个可选的哈希作为其最后一个参数。让我们重新审视 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 字段确定模板引用的区块参数数量,该字段是一个整数计数。此值表示子模板可能引用的区块参数数量。超出此计数的参数永远不会被引用,如果需要,助手可以安全地省略它们。这是可选的,传递给模板的任何其他参数将被静默忽略。

# 原始区块

原始区块可用于需要处理未处理的 mustache 块的模板。

模板
{{{{raw-loud}}}}
  {{bar}}
{{{{/raw-loud}}}}
{{{{raw-helper}}}}
  {{bar}}
{{{{/raw-helper}}}}

将执行助手 raw-helper 而不解释内容。

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

将呈现

{{bar}}
上次更新: 2021 年 10 月 20 日,上午 9:33:06