Handlebars学习(1)- Getting Started

Handlebars让我们可以更加高效地创建语义化的模板(semantic templates)。

Handlebars可以与Mustache templates近乎完美的兼容。大多数情况下,你可以通过使用Handlebars替换(swap out)Mustache从而继续使用自己当前的模板(即,可以把Mustache模板导入到Handlebars中,并开始使用Handlebars所提供的更丰富的功能)。你可以在这里找到完整的细节。

使用内嵌的Handlebars 表达式,Handlebars模板看起来几乎和HTML一样:

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

 

1.模板 -- Handlebars表达式

Handlebars表达式的格式是:{{ contents }}

变量外面加双重大括号,handlebars模板会自动匹配相应的变量——数值,对象甚至是函数。

表达式是Handlebars模板的基本组成单元(unit)。你可以在{{mustache}}中单独的使用表达式、把它们传给Handlebars helper,或者在hash arguments中作为值使用。

 

1.1 基本用法

(1)单一的标示符是最简单的Handlebars 表达式:

  <h1>{{title}}</lt>

这个表达式的意思是:在当前的上下文中查找title属性。Block helpers可以调整当前的上下文,但是它们不能改变表达式基本的意思。

(2)Handlebars 表达式也可以采用点分割的访问方式:

  <h1>{{article.title}}</lt>

这个表达式的意思是:首先在当前的上下文中查找article属性,然后在查询结果中再查找title属性

Handlebars也同样支持"/"分割的方式(与点分割意思相同),你可以像下面这样重写上面的模板:

  <h1>{{article/title}}</lt>

(3)标示符可以是任何unicode字符,除了下面的:

空格 ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~

如果想要采用不合理的标示符来引用属性,你可以使用"[":

  {{#each articles.[10].[#comments]}}
    <h1>{{subject}}</lt>
    <div>
      {{body}}
    </div>
  {{/each}}

在上面的实例中,模板会采用基本等同于articles[10]['#comments']的方式来处理each参数,如果没有包含结束标记"]",其它所有的字符都会失去意义。

(4)Handlebars通过{{expression}}返回转义HTML值。如果你不希望Handlebars转义某个值,可以使用"{{{"的方式

  {{{foo}}}

 

1.2 Helpers

Handlebars helper命令是一个简单的标示符,后面有0个或多个通过空格分开的参数。每一个参数都是一个Handlebars表达式:

  {{{link story}}}

在这个例子中,link是Handlebars helper的名子,story是helper的一个参数。Handlebars采用与上面在基本用法中描述的完全相同的方式处理参数。

  Handlebars.registerHelper('link', function(object) {
    var url = Handlebars.escapeExpression(object.url),
        text = Handlebars.escapeExpression(object.text);

    return new Handlebars.SafeString(
      "<a href='" + url + "'>" + objecttext + "</a>"
    );
  });

当从helper返回HTML时,如果你不想它被默认值取代,你应该返回一个SafeString(Handlebars.SafeString)。当使用safeString时,所有未知或不安全的数据都应该手动的用escapeExpression (Handlebars.escapeExpression)方法处理。

你也可以传递一个简单的字符串、数字或者布尔值(作为一个参数)给Handlebars helper。

  {{{link "See more..."  story.url}}}

在这里,Handlebars将会传递给link helper两个参数:字符串"See more..."以及当前上下文中story.url的值。

  Handlebars.registerHelper('link', function(text, url) {
    url = Handlebars.escapeExpression(url);
    text = Handlebars.escapeExpression(text);

    return new Handlebars.SafeString(
      "<a href='" + url + "'>" + text + "</a>"
    );
  });

你可以使用完全相同的helper,在helper中采用基于story.text值的动态文本。

  {{{link story.text story.url}}}

Handlebars helpers也可以接受任意的键-值对序列作为最终的参数(在记录中像hash arguments一样引用):

  {{{link "See more..."  href=story.url  class="story"}}}

Hash arguments中的每一个键都必须是简单的标示符,同时,值必须是Handlebars 表达式。也就是说,值必须是简单的标示符、路径或者字符串。

  Handlebars.registerHelper('link', function(text, options) {
    var attrs = [];

    for (var prop in options.hash) {
      attrs.push(
        Handlebars.escapeExpression(prop) + '="'
        + Handlebars.escapeExpression(options.hash[prop]) + '"');
    }

    return new Handlebars.SafeString(
      "<a " + attrs.join(" ") + ">" + Handlebars.escapeExpression(text) + "</a>"
    );
  });

Handlebars像Hash arguments一样提供了附加的meta数据给作为最终参数的helpers。

 

Handlebars也提供了一种采用模板块的方式使用helper的机制。Block helpers可以在其选择的任何上下文中任意次的调用模板块。

使用Block helpers我们可以定义自定义的迭代器,其它的功能可以在一个新的上下文中调用传递的block。

1.2.1 基础模块(Basic Blocks)

为了验证,我们定义一个在没有helper存在的前提下调用模块的block helper。

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

Noop helper("no operation"的缩写)将会接受一个options hash,这个options hash包含一个函数(options.fn),这个函数类似一个正常编译的Handlebars模板。需要指明的是,这个函数将会获得上下文并返回一个字符串。

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

Handlebars总是在当前的上下文(即this)调用helpers,因此,你可以在当前的上下文中调用block和this以求取block的值。

1.2.2 基础模块变量

为了更好的说明语法,我们定义了另一个block helper,这个helper增加了一些标记给预先包装的文本。

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

Bold helper将会增加标记以使它的文本变粗。函数依然会获得上下文,同时,返回一个字符串。

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

1.2.3 With helper

With helper用于说明如何传递一个参数给你的helper。当一个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,参数后面是函数块。

1.2.4 简单的迭代器

Block helpers的最常用的的方式是用于定义用户自定义的迭代器。事实上,所有Handlebars内置的helpers也被定义为普通的Handlebars block helpers。下面,我们看一看怎样让内置的each helper运行。

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

这里,为了遍历comments数组中的每一个元素,我们将要调用传递给each的block。

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

这里,我们调用被传递参数和block中的每一项。随着迭代,我们构建了一个结果字符串,然后返回这个字符串。

这个模式可以被用来实现更多改进的迭代器。举个例子,我们创建了一个迭代器,它可以创建一个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 runtime 库可以让其更加优美。例如,使用SproutCore's runtime 库时:

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

1.2.5 条件语句

另一个经常被用到的就是条件语句,包括if和unless控制结构。

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

典型的控制语句不会改变当前的上下文,它们用于描述是否调用基于一些变量的block。

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

使用条件语句时,我们可以使用else来创建当if条件语句判断为false时返回的HTML代码块。

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

通过将meta数据作为hash选项的属性,Handlebars可以为block helpers提供额外的meta数据。

1.2.6 Hash Arguments

与常规的helpers相似,block helpers可以接受一个可选择hash作为它最终的参数。让我们重写list helper使其可以为我们添加一些可选择的特性给我们将要创建的<ul>元素。

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

Handlebars提供了最终的hash作为options.hash。这让它容易接受一些变化的参数,同时也可以接收一个可选择的hash。假如模板不提供hash参数,Handlebars将会自动传递一个空对象({}),所以,你不需要去检查hash arguments是否存在。

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

Hash arguments提供一种为block helper提供一定数量可选择参数的强有力的方式,而且不会因为不同位置的参数增加复杂度。

Block helpers也可以为其子模板引入私有变量。这有利于引入原本并不存在上下文数据中的额外信息。

例如,当迭代一个列表时,你可以提供当前的索引(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调用一个block时,确保你已经创建了一个新的data框架。否则,下游的helpers可能无法按预期的改变上游的变量。

同时,确保在尝试与一个已存在的data对象互动前,data域已经被定义。私有变量行为是被有条件的编译,同时,一些模板不可能创建这个域。

1.2.7 原生blocks

原生blocks适用于未经处理的mustache blocks.

  {{{raw-helper}}}
    {{bar}}
  {{{/raw-helper}}}

我们在没有说明上下文的情况下执行raw-helper。

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

将输出:

  {{bar}}

 

1.3 子表达式

Handlebars支持子表达式,允许你在单一的mustache中调用多重helpers,同时,在内部helper的结果中传递作为arguments的请求给外部的helper。子表达式可以用圆括号分割。

  {{outer-helper (inner-helper 'abc') 'def'}}

在这里,将调用带有abc参数的inner-helper,无论inner-helper函数返回什么,返回值都会作为第一参数传递给outer-helper(def将会作为第二参数传递给outer-helper)。

 

1.4 空白控制

通过增加一个紧挨着大括号的"~"号,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>

这扩展了作为"单一"helpers的stripping lines的默认行为(仅仅一个块注释,或者部分和空格)。

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

将输出:

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

 

1.5 Id跟踪

路径被用来查找一个作为被给值的参数,可以任意的将路径传递给helpers。这个模型可以通过跟踪trackId编译器标示。

  {{foo bar.baz}}

将会告诉helper foo的值是bar.baz,但是在关于设置参数的ids区域也包括原义字符串"bar.baz"。

这对后来查询参数应该是有用的,但是也确实增加了额外的耗费。

当这个模型开启后,所有内建的helpers将会产生一个@contextPath变量,这个变量代表当前上下文的查询路径。It's highly recommended that generic helpers provide such a variable if they modify the context when executing their children.

 

2.预编译

你可以通过添加模板到<script>标签的方式从而传递模板给浏览器。

  <script id="entry-template" type="text/x-handlebars-template">
    template content
  </script>

在Javascript中通过使用Handlebars.compile编译一个模板。

  var source = $("#entry-template").html();
  var template = Handlebars.compile(source);

它也能够预编译你的模板。因为不用在浏览器中编译模板,预编译将会使代码执行的更快。当在移动设备中工作时,这种处理方式是十分必要的。

使用Handlebars预编译器,你可以预编译你的Handlebars模板,这不仅降低了代码在客户端运行的时间,也同时降低Handlebars库运行的时间。

 

2.1 准备开始

首先,你需要创建node和nom。在OSX上:

  $ brew install node
  $ curl https://npmjs.org/install.sh | sh

这是假设你已经创建了Homebrew,如果还没有,需要首先建立它:

  $ /usr/bin/ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

然后,建立Handlebars npm包:

  $ npm install handlebars -g

使用-g标志建立全局包,所以,它可以在任何项目中使用。

 

现在,你即将使用预编译程序:

  $ handlebars <input> -f 

编译程序将模板插入Handlebars.templates。假如你输入的文件是person.handlebars,编译器会将其插入Handlebars.templates.person。这个模板将会是一个函数,它可以像本地被编译的模板那样,执行相同的动作。

  Handlebars.templates.person(context, options);

假如你正在使用预编译模板,你不需要将编译程序安装到已配置的应用上,相反,你可以使用耗时更少的方式来实现。

  <script src="/libs/handlebars.runtime.js"></script>

为了降低下载容量,消除客户端编译将会有利于加快代码执行,因为在Handlebars中,编译是最耗时的部分。

 

2.2 最优化

因为你正在预编译模板,你也可以为编译程序应用一些优化。

首先,允许你为编译程序指定一系列已经知道的helpers。

  handlebars <input> -f  -k each -k if -k unless

为提高性能,Handlebars 编译程序将会优化那些helpers。

 

3.执行

执行拥有上下文的Handlebars模板,得到结果HTML。

  var context = {title: "My New Post", body: "This is my first post!"}
  var html = template(context);

结果:

  <h1>My New Post</lt>
  This is my first post!
posted @ 2014-12-13 17:08  South Wind  Views(369)  Comments(0)    收藏  举报