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。
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增加了一些标记给预先包装的文本。
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同一个参数一起被调用时,无论模板传入什么样的上下文它都会被调用。
如果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);
结果:

浙公网安备 33010602011771号