Handlebars 文档笔记

官方文档:http://handlebarsjs.com/
笔记:涂鸦码龙

Handlebars 兼容 Mustache 模板

对比了几个 Node.js 常用模板,什么 EJS 、Jade 等等,还是感觉 Handlebars 比较顺手,模板只做数据展示,前端逻辑的东西通过 helper 实现,HTML 中没有掺杂太多 JS 的东西,看起来整洁一些。

Express 中引入 Handlebars 模板的话,需要引入hbs 模块


handlebars 表达式

1
<h1>&#123;&#123;title}}</h1>

在上下文中找 title 属性,获取它的值

点分割表达式

1
<h1>&#123;&#123;article.title}}</h1>

当前上下文找 article 属性,再找它的 title 属性

标识符可以是除了以下字符以外的 unicode 字符
Whitespace ! “ # % & ‘ ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~

不合法的标识符用 “[]” 包装

1
2
3
4
5
6
&#123;&#123;#each articles.[10].[#comments]}}
<h1>&#123;&#123;subject}}</h1>
<div>
&#123;&#123;body}}
</div>
&#123;&#123;/each}}

不转义

1
&#123;&#123;&#123;foo}}}

Helpers

0或多个参数,用空格分割,每个参数是个 handlebars 表达式

1
&#123;&#123;&#123;link story}}}

link 是 helper 名字,story 是 helper 参数。

注册 helper

1
2
3
4
5
Handlebars.registerHelper('link', function(object) {
return new Handlebars.SafeString(
"<a href='" + object.url + "'>" + object.text + "</a>"
);
});

helper 返回 HTML ,不想被转义,用 Handlebars.SafeString()

helper 把接收的上下文作为 this 上下文

1
2
3
4
5
<ul>
&#123;&#123;#each items}}
<li>&#123;&#123;agree_button}}</li>
&#123;&#123;/each}}
</ul>

上下文和 helper:

1
2
3
4
5
6
7
var context = {
items: [
{name: "Handlebars", emotion: "love"},
{name: "Mustache", emotion: "enjoy"},
{name: "Ember", emotion: "want to learn"}
]
};
1
2
3
4
5
Handlebars.registerHelper('agree_button', function() {
return new Handlebars.SafeString(
"<button>I agree. I " + this.emotion + " " + this.name + "</button>"
);
});

输出结果:

1
2
3
4
5
<ul>
<li><button>I agree. I love Handlebars</button></li>
<li><button>I agree. I enjoy Mustache</button></li>
<li><button>I agree. I want to learn Ember</button></li>
</ul>

也可以直接传字符串参数

1
&#123;&#123;&#123;link "See more..." story.url}}}

等价于

1
&#123;&#123;&#123;link story.text story.url}}}
1
2
3
4
5
Handlebars.registerHelper('link', function(text, url) {
return new Handlebars.SafeString(
"<a href='" + url + "'>" + text + "</a>"
);
});

helper 最后一个参数也可以接收可选的键值对序列(文档提到的 hash 参数)

1
&#123;&#123;&#123;link "See more..." href=story.url class="story"}}}

hash 参数的 key 必须是简单的标识符,value 是 Handlebars 表达式, value 可以是简单的标识符,路径,或者字符串。

1
2
3
4
5
6
7
8
9
10
11
Handlebars.registerHelper('link', function(text, options) {
var attrs = [];
for(var prop in options.hash) {
attrs.push(prop + '="' + options.hash[prop] + '"');
}
return new Handlebars.SafeString(
"<a " + attrs.join(" ") + ">" + text + "</a>"
);
});

基础 Blocks

1
2
3
4
5
6
<div class="entry">
<h1>&#123;&#123;title}}</h1>
<div class="body">
&#123;&#123;#noop}}&#123;&#123;body}}&#123;&#123;/noop}}
</div>
</div>
1
2
3
Handlebars.registerHelper('noop', function(options) {
return options.fn(this);
});

noop helper 实际跟没有 helper 类似,只是传递上下文,返回字符串。Handlebars 把当前的上下文作为 this

内建 helper

with helper

根据模板传递的上下文解析模板

1
2
3
4
5
6
7
<div class="entry">
<h1>&#123;&#123;title}}</h1>
&#123;&#123;#with story}}
<div class="intro">&#123;&#123;{intro}}}</div>
<div class="body">&#123;&#123;{body}}}</div>
&#123;&#123;/with}}
</div>

当 JSON 对象包含嵌套属性时,不必再三重复父属性的名字。比如以下数据:

1
2
3
4
5
6
7
{
title: "First Post",
story: {
intro: "Before the jump",
body: "After the jump"
}
}

helper 接收参数,参数为 JSON 属性的 上下文。

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

简单迭代器 each helper

Handlebars 内建了 each 迭代器

1
2
3
4
5
6
7
8
<div class="comments">
&#123;&#123;#each comments}}
<div class="comment">
<h2>&#123;&#123;subject}}</h2>
&#123;&#123;{body}}}
</div>
&#123;&#123;/each}}
</div>

实现原理如下: 把 comments 数组的每一个元素作为上下文解析模板

1
2
3
4
5
6
7
8
9
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;
});

可以用 this 引用迭代元素

1
2
3
4
5
<ul class="people_list">
&#123;&#123;#each people}}
<li>&#123;&#123;this}}</li>
&#123;&#123;/each}}
</ul>

上下文:

1
2
3
4
5
6
7
{
people: [
"Yehuda Katz",
"Alan Johnson",
"Charles Jolley"
]
}

结果:

1
2
3
4
5
<ul class="people_list">
<li>Yehuda Katz</li>
<li>Alan Johnson</li>
<li>Charles Jolley</li>
</ul>

当某一项为空时,可以用

1
&#123;&#123;else}}

表达式

1
2
3
4
5
&#123;&#123;#each paragraphs}}
<p>&#123;&#123;this}}</p>
&#123;&#123;else}}
<p class="empty">No content</p>
&#123;&#123;/each}}

通过

1
&#123;&#123;@index}}

可以引用当前的循环索引

1
2
3
&#123;&#123;#each array}}
&#123;&#123;@index}}: &#123;&#123;this}}
&#123;&#123;/each}}


1
2
3
4
5
6
7
8
&#123;&#123;@key}}
```
引用当前的键名:
```html
&#123;&#123;#each object}}
&#123;&#123;@key}}: &#123;&#123;this}}
&#123;&#123;/each}}

数组迭代的第一步和最后一步用 @first@last 变量表示, 对象迭代时仅 @first 可用。

条件语句 if helper

如果条件参数返回 false, undefined, null, ""[](非真的值)时,Handlebars 将不渲染该块

Handlebars 内建了 ifunless 语句

1
2
3
&#123;&#123;#if isActive}}
<img src="star.gif" alt="Active">
&#123;&#123;/if}}

实现原理:根据传入的条件参数,判断是否解析模板

1
2
3
4
5
Handlebars.registerHelper('if', function(conditional, options) {
if(conditional) {
return options.fn(this);
}
});

Handlebars 还提供了 else 语句

1
2
3
4
5
&#123;&#123;#if isActive}}
<img src="star.gif" alt="Active">
&#123;&#123;else}}
<img src="cry.gif" alt="Inactive">
&#123;&#123;/if}}

unless helper

unlessif 正好相反,如果表达式返回 false ,模板将被渲染。

1
2
3
4
5
<div class="entry">
&#123;&#123;#unless license}}
<h3 class="warning">WARNING: This entry does not have a license!</h3>
&#123;&#123;/unless}}
</div>

当 license 返回 false,Handlebars 将渲染 warning 。

log helper

记录上下文状态

1
&#123;&#123;log "Look at me!"}}

JavaScript 编译模板

模板可以包含在特殊的 <script> 里:

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

然后用 Handlebars.compile 编译模板

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

获取编译后的 HTML 模板,用 JSON 数据填充模板

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

最终结果:

1
2
3
4
5
6
<div class="entry">
<h1>My New Post</h1>
<div class="body">
This is my first post!
</div>
</div>

HTML 转义

不想转义用

1
&#123;&#123;&#123;

模板:

1
2
3
4
5
6
<div class="entry">
<h1>&#123;&#123;title}}</h1>
<div class="body">
&#123;&#123;{body}}}
</div>
</div>

上下文数据:

1
2
3
4
{
title: "All about <p> Tags",
body: "<p>This is a post about &lt;p&gt; tags</p>"
}

最终结果:

1
2
3
4
5
6
<div class="entry">
<h1>All About &lt;p&gt; Tags</h1>
<div class="body">
<p>This is a post about &lt;p&gt; tags</p>
</div>
</div>

Handlebars.SafeString 方法不做转义,通常返回 new Handlebars.SafeString(result)。此种情形,你可能想手动转义参数:

1
2
3
4
5
6
7
8
Handlebars.registerHelper('link', function(text, url) {
text = Handlebars.Utils.escapeExpression(text);
url = Handlebars.Utils.escapeExpression(url);
var result = '<a href="' + url + '">' + text + '</a>';
return new Handlebars.SafeString(result);
});

模板注释

1
2
3
4
5
6
&#123;&#123;! }}
```
或者
```html
&#123;&#123;!-- --}}

1
2
3
4
5
6
<div class="entry">
&#123;&#123;! only output this author names if an author exists }}
&#123;&#123;#if author}}
<h1>&#123;&#123;firstName}} &#123;&#123;lastName}}</h1>
&#123;&#123;/if}}
</div>

模板注释不会输出,HTML 注释会输出

1
2
3
4
<div class="entry">
&#123;&#123;! This comment will not be in the output }}
<!-- This comment will be in the output -->
</div>

Partials 局部模板


1
2
3
4
5
6
&#123;&#123;> partialName}}
```
引入局部模板,局部模板可以使字符串,也可以是编译模板的函数。
```js
var source = "<ul>&#123;&#123;#people}}<li>&#123;&#123;> link}}</li>&#123;&#123;/people}}</ul>";

1
2
Handlebars.registerPartial('link', '<a href="/people/&#123;&#123;id}}">&#123;&#123;name}}</a>')
var template = Handlebars.compile(source);
1
2
3
4
5
6
7
8
9
10
11
12
var data = { "people": [
{ "name": "Alan", "id": 1 },
{ "name": "Yehuda", "id": 2 }
]};
template(data);
// Should render:
// <ul>
// <li><a href="/people/1">Alan</a></li>
// <li><a href="/people/2">Yehuda</a></li>
// </ul>

内建工具

转义字符串

1
Handlebars.Utils.escapeExpression(string)

判断空值

1
Handlebars.Utils.isEmpty(value)

扩展对象

1
Handlebars.Utils.extend(foo, {bar: true})

转字符串

1
Handlebars.Utils.toString(obj)

判断数组

1
Handlebars.Utils.isArray(obj)

判断函数

1
Handlebars.Utils.isFunction(obj)