使用Handlebars,你可以轻松创建语义化模板,Mustache模板和Handlebars是兼容的,所以你可以将Mustache导入Handlebars以使用 Handlebars 强大的功能。
开始
Handlebars模板看起来和HTML一样,只是嵌入了 handlebars 表达式
- <div class="entry">
- <h1>{{title}}</h1>
- <div class="body">
- {{body}}
- </div>
- </div>
handlebars表达式以{{
开头,中间写一些内容,以}}
结尾。
你可以使用<script>
标签引入handlebars模板:
- <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.runtime.js),避免在浏览器中编译,提高性能,这在移动设备中显得更重要。
传入数据上下文(context),handlebars会执行并生成HTML:
- var context = {title: "My New Post", body: "This is my first post!"}
- var html = template(context);
得到的结果是:
- <div class="entry">
- <h1>My New Post</h1>
- <div class="body">
- This is my first post!
- </div>
- </div>
HTML编码
在handlebars里,{{expression}}
会返回一个经过编码的HTML,如果你不希望被编码,可以使用{{{
- <div class="entry">
- <h1>{{title}}</h1>
- <div class="body">
- {{{body}}}
- </div>
- </div>
使用这样的数据上下文:
- {
- title: "All about <p> Tags",
- body: "<p>This is a post about <p> tags</p>"
- }
结果是:
- <div class="entry">
- <h1>All About <p> Tags</h1>
- <div class="body">
- <p>This is a post about <p> tags</p>
- </div>
- </div>
handlebars不会编码Handlebars.SafeString
。如果你自定义一个helper,返回一段HTML代码,你需要返回new Handlebars.SafeString(result)
。此时,你需要手动对内容进行编码:
- 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);
- });
这里将会对传入的参数进行编码,返回值是“安全的”,所以就算你不使用{{{
,handlebars也不会再次编码了。
块表达式
块表达式允许你定义helper,用不同的数据上下文(context)调用一段模板。下面我们定义一个生成列表的helper:
- {{#list people}}{{firstName}} {{lastName}}{{/list}}
如果我们的数据是这样的:
- {
- people: [
- {firstName: "Yehuda", lastName: "Katz"},
- {firstName: "Carl", lastName: "Lerche"},
- {firstName: "Alan", lastName: "Johnson"}
- ]
- }
我们创建一个叫list
的helper来生成列表,helper接受people
作为第一个参数,一个option对象(hash)作为第二个参数。option包含一个属性fn
,他可以调用一个context就像普通模板一样。
- Handlebars.registerHelper('list', function(items, options) {
- var out = "<ul>";
- for(var i=0, l=items.length; i<l; i++) {
- out = out + "<li>" + options.fn(items[i]) + "</li>";
- }
- return out + "</ul>";
- });
执行后,得到:
- <ul>
- <li>Yehuda Katz</li>
- <li>Carl Lerche</li>
- <li>Alan Johnson</li>
- </ul>
块表达式有很多特性,例如,可以创建一个else
块(内置的if helper就有else块)。另外,如果options.fn(context)
对内容编码过了,handlebars就不会helper内容进行编码了,否则就编码两次了。
Handlebars 路径
Handlebars支持简单的路径
- <p>{{name}}</p>
也支持嵌套路径,可以查找下一级的属性
- <div class="entry">
- <h1>{{title}}</h1>
- <h2>By {{author.name}}</h2>
- <div class="body">
- {{body}}
- </div>
- </div>
此模板使用下面的数据:
- var context = {
- title: "My First Blog Post!",
- author: {
- id: 47,
- name: "Yehuda Katz"
- },
- body: "My first post. Wheeeee!"
- };
嵌套路径同样支持../,
- <h1>Comments</h1>
- <div id="comments">
- {{#each comments}}
- <h2><a href="/posts/{{../permalink}}#{{id}}">{{title}}</a></h2>
- <div>{{body}}</div>
- {{/each}}
- </div>
尽管链接在打印出的时候,是以comments为上下文的,但是它可以访问到上一级的上下文(context)找到permalink。
../引用上一级的作用域,直接看一下上面模板对应的数据就明白了
- var data = {
- permalink:'http://keenwon.com',
- comments: [
- {id:1,title:'链接1',body:'链接1'},
- {id:2,title:'链接2',body:'链接2'}
- ]
- };
Handlebars可以通过this
引用解决helpers和数据命名冲突的问题。
- <p>{{./name}} or {{this/name}} or {{this.name}}</p>
模板注释 {{! }}
or {{!-- --}}
你可以在 handlebars 里加注释:
- <div class="entry">
- {{! only output this author names if an author exists }}
- {{#if author}}
- <h1>{{firstName}} {{lastName}}</h1>
- {{/if}}
- </div>
注释不会出现在输出结果里,如果想要显示出来,可以使用html的注释(会被执行,然后以注释的形式显示,所以如果html注释内有错,还是会报错)
- <div class="entry">
- {{! 这个注释不会显示在输出结果中 }}
- <!-- 会显示 -->
- </div>
所有注释必须包含结束标签}}
,多行注释可以使用{{!-- --}}
Helpers
Handlebars Helpers可以读取到模板中的任何数据上下文,你可以使用Handlebars.registerHelper
注册一个helpers。
- <div class="post">
- <h1>By {{fullName author}}</h1>
- <div class="body">{{body}}</div>
- <h1>Comments</h1>
- {{#each comments}}
- <h2>By {{fullName author}}</h2>
- <div class="body">{{body}}</div>
- {{/each}}
- </div>
然后使用如下的数据上下文和Helpers:
- var context = {
- author: {firstName: "Alan", lastName: "Johnson"},
- body: "I Love Handlebars",
- comments: [{
- author: {firstName: "Yehuda", lastName: "Katz"},
- body: "Me too!"
- }]
- };
- Handlebars.registerHelper('fullName', function(person) {
- return person.firstName + " " + person.lastName;
- });
结果是:
- <div class="post">
- <h1>By Alan Johnson</h1>
- <div class="body">I Love Handlebars</div>
- <h1>Comments</h1>
- <h2>By Yehuda Katz</h2>
- <div class="body">Me Too!</div>
- </div>
使用this
可以访问到当前的上下文
- <ul>
- {{#each items}}
- <li>{{agree_button}}</li>
- {{/each}}
- </ul>
使用如下的Helpers和数据上下文
- var context = {
- items: [
- {name: "Handlebars", emotion: "love"},
- {name: "Mustache", emotion: "enjoy"},
- {name: "Ember", emotion: "want to learn"}
- ]
- };
- Handlebars.registerHelper('agree_button', function() {
- return new Handlebars.SafeString(
- "<button>I agree. I " + this.emotion + " " + this.name + "</button>"
- );
- });
结果是:
- <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>
如果你的helpers返回一个html片段,不想被编码。必须new一个Handlebars.SafeString
返回出来。
内置的Helpers
The with
Block Helper
通常,Handlebars会将数据上下文传入编译方法:
- var source = "<p>{{lastName}}, {{firstName}}</p>";
- var template = Handlebars.compile(source);
- template({firstName: "Alan", lastName: "Johnson"});
结果:
- <p>Johnson, Alan</p>
使用with
可以改变当前的上下文
- <div class="entry">
- <h1>{{title}}</h1>
- {{#with author}}
- <h2>By {{firstName}} {{lastName}}</h2>
- {{/with}}
- </div>
数据上下文如下:
- {
- title: "My first post!",
- author: {
- firstName: "Charles",
- lastName: "Jolley"
- }
- }
结果:
- <div class="entry">
- <h1>My first post!</h1>
- <h2>By Charles Jolley</h2>
- </div>
The each
block helper
你可以使用内置的each
helper生成列表,可以使用this
访问当前项。
- <ul class="people_list">
- {{#each people}}
- <li>{{this}}</li>
- {{/each}}
- </ul>
数据上下文如下:
- {
- people: [
- "Yehuda Katz",
- "Alan Johnson",
- "Charles Jolley"
- ]
- }
结果:
- <ul class="people_list">
- <li>Yehuda Katz</li>
- <li>Alan Johnson</li>
- <li>Charles Jolley</li>
- </ul>
你可以在任何上下文里,使用this
引用当前的上下文
另外,还可以使用{{else}}
块,当列表内容为空的时候会显示{{else}}
的内容
- {{#each paragraphs}}
- <p>{{this}}</p>
- {{else}}
- <p class="empty">暂无内容</p>
- {{/each}}
在each
中循环每一项的时候,可以使用{{@index}}
获取当前的序号。
- {{#each array}}
- {{@index}}: {{this}}
- {{/each}}
对于object,可以使用{{key}}
获取当前的key。
- {{#each object}}
- {{@key}}: {{this}}
- {{/each}}
在迭代的过程中,可以使用@first
和@last
判断当前的第一步和最后一步,对于object,只有@first
可用。
The if
block helper
可以使用if
helper有条件的渲染block,如果是false
, undefined
, null
, ""
或者 []
(a “falsy” value),Handlebars不会渲染此block.
- <div class="entry">
- {{#if author}}
- <h1>{{firstName}} {{lastName}}</h1>
- {{/if}}
- </div>
如果使用的是空的数据上下文(例如{}
),author会返回undefined
,结果是:
- <div class="entry">
- </div>
当使用块表达式,可以使用{{else}}来指定一个“片段”,当结果是 falsy value 的时候呈现
- <div class="entry">
- {{#if author}}
- <h1>{{firstName}} {{lastName}}</h1>
- {{else}}
- <h1>Unknown Author</h1>
- {{/if}}
- </div>
The unless
block helper
unless
的作用和if
刚好相反,但表达式返回falsy value的时候渲染block
- <div class="entry">
- {{#unless license}}
- <h3 class="warning">WARNING: This entry does not have a license!</h3>
- {{/unless}}
- </div>
如果当前上下文的license
返回一个falsy value,Handlebars会输出警告信息,否则什么都不输出。
The log
block helper
log
helper允许执行模板的时候输出当前上下文的状态
- {{log "Look at me!"}}
信息传给Handlebars.logger.log
,重写这个函数可以实现自定义的log。
原文:Learn Handlebars in 10 Minutes or Less
翻译:前端开发whqet, 意译为主,不当之处敬请指正。
作者简介:Danny Markov ,Tutorialzine的bootstrap和html5方面的专家,业余时间喜欢骑自行车或在公园的某个角度码码。
译者的话:据说handlebars是一个流行的模板引擎,可以把html从javascript中分离出来,写更清晰的代码。来不妨一试。
Handlebars.js是一个非常流行的功能强大的模板引擎,简单易用,具备较好的学习社区。它基于 Mustache 模板引擎,并且做了诸多改进。利用Handlebars您可以方便的把html从javascript代码中分离出来,从而书写更清晰的代码。
本文章试图通过十分钟左右的时间带您领略Handlebars的风采,刚开始学的时候可能费点周折,但是您一旦上手,一切将变得很简单。
0.引入项目
在项目中引入Handlebars非常简单,到 http://handlebarsjs.com/下载最新版本(本文写作时,最新版为2.0.0),然后使用script标签引入即可。当然您也可以使用cdn的方式,享受cdn方式的畅快。如代码所示。
- // From File
- <script src="handlebars-v2.0.0.js"></script>
- // From CDN
- <script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0/handlebars.js"></script>
1.Templates
当您引入库之后,我们可以愉快的书写模板了,推荐的方式是通过特殊type的script标签来添加模板,type属性是非常重要的,否则浏览器会将它们看做javascrip解析。
模板具有一个很容易理解的语法,可以使用html、普通文本和表达式,表达式通常被包含在两对或三对花括号里,可以包含变量或功能函数。模板需要编译之后才能使用,如下面代码所示,案例我放在了codepen,大家不妨一看。。注意一点,我们使用了jquery仅仅为了方便dom操作,handlebars可以脱离jquery良好工作。
- <!--模板. -->
- <!--需要数据的地方,用{{}}括起来.-->
- <script id="address-template" type="text/x-handlebars-template">
- <p>You can find me in {{city}}. My address is {{number}} {{street}}.</p>
- </script>
- <!--新的内容在这里展示-->
- <div class="content-placeholder"></div>
- $(function () {
- // 抓取模板数据
- var theTemplateScript = $("#address-template").html();
- // 编译模板
- var theTemplate = Handlebars.compile(theTemplateScript);
- // 定义数据
- var context={
- "city": "London",
- "street": "Baker Street",
- "number": "221B"
- };
- // 把数据传送到模板
- var theCompiledHtml = theTemplate(context);
- // 更新到模板
- $('.content-placeholder').html(theCompiledHtml);
- });
2. Expressions
上面所示的案例,表达式中的任何html代码将会被自动忽略,这是一个非常实用的性能,但是有的时候我们需要解析html,那么就要用三个花括号{{{ }}},如下面代码所示,演示效果在codepen。
另外,handlebars表达式允许嵌套值,可以方便我们访问javascript对象的任何值。
- <script id="expressions-template" type="text/x-handlebars-template">
- {{description.escaped}}
- {{example}}
- <br><br>
- {{description.unescaped}}
- {{{example}}}
- </script>
- <div class="content-placeholder"></div>
- $(function () {
- // <span style="font-family:Arial, Helvetica, sans-serif;">抓取模板数据</span>
- var theTemplateScript = $("#expressions-template").html();
- // 编译模板
- var theTemplate = Handlebars.compile(theTemplateScript);
- // 定义数据
- var context={
- "description": {
- "escaped": "Using {{}} brackets will result in escaped HTML:",
- "unescaped": "Using {{{}}} will leave the context as it is:"
- },
- "example": "<button> Hello </button>"
- };
- // 传送数据
- var theCompiledHtml = theTemplate(context);
- // 展示到页面
- $('.content-placeholder').html(theCompiledHtml);
- });
3. Context
Handlebars利用了Mustache的强大特性,context就是其中之一。我们可以把需要传递的数据放在这个javascript对象中,使用#each、#with等方法可以方便的使用该对象的数据。看了下面这个案例,那就明白了,演示效果在codepen。
- <!-- #each可以遍历数据. -->
- <script id="example-template" type="text/x-handlebars-template">
- <!-- 遍历people -->
- {{#each people}}
- <!-- 直接使用每个people的数据 -->
- <p>{{firstName}} {{lastName}}</p>
- {{/each}}
- </script>
- $(function () {
- var theTemplateScript = $("#example-template").html();
- var theTemplate = Handlebars.compile(theTemplateScript);
- var context = {
- people: [
- { firstName: 'Homer', lastName: 'Simpson' },
- { firstName: 'Peter', lastName: 'Griffin' },
- { firstName: 'Eric', lastName: 'Cartman' },
- { firstName: 'Kenny', lastName: 'McCormick' },
- { firstName: 'Bart', lastName: 'Simpson' }
- ]
- };
- var theCompiledHtml = theTemplate(context);
- $(document.body).append(theCompiledHtml);
- });
4. Helpers
Handlebars不允许在模板中使用javascript,而是提供了一些列的功能函数(helpers),可以在模板中调用,方便代码重用和创造复杂模板。使用表达式调用helpers的格式类似如此,{{helpername}},同时也可以传递参数,{{helpname 12345}}。
开发新的helper,使用registerHelper function,下面代码演示了如何创建新的功能函数,如何使用内置的功能函数,演示文件在codepen。
- <script id="built-in-helpers-template" type="text/x-handlebars-template">
- {{#each animals}}
- <p>
- The {{capitalize this.name}} says
- {{#if this.noise}}
- "{{this.noise}}".
- {{else}}
- nothing since its a {{this.name}}.
- {{/if}}
- </p>
- {{/each}}
- </script>
- <div class="content-placeholder"></div>
- $(function () {
- // 定义a helper
- Handlebars.registerHelper('capitalize', function(str){
- // str is the argument passed to the helper when called
- str = str || '';
- return str.slice(0,1).toUpperCase() + str.slice(1);
- });
- var theTemplateScript = $("#built-in-helpers-template").html();
- var theTemplate = Handlebars.compile(theTemplateScript);
- var context = {
- animals:[
- {
- name: "cow",
- noise: "moooo"
- },
- {
- name: "cat",
- noise: "meow"
- },
- {
- name: "fish",
- noise: ""
- },
- {
- name: "farmer",
- noise: "Get off my property!"
- }
- ]
- };
- var theCompiledHtml = theTemplate(context);
- $('.content-placeholder').html(theCompiledHtml);
- });
5. Block helpers
Block helpers像普通的功能函数一样,但是有开始和结束标签(类似于内置的#if、#each等),可以修改包含的html的内容。创建更为复杂一些,当时功能更加强大。经常使用它们重复使用功能、创建一大段可重用的html等。
同样使用Handlebars.registerHelper()创建block helper,不同的是我们需要使用第二参数,回调函数。看看下面的代码,体会强大功能。
- <script id="block-expressions-template" type="text/x-handlebars-template">
- <p> The <b> {{#uppercase}} konami {{/uppercase}} </b> Code is a cheat code that appears in many video games.</p>
- <p>During the title screen before the game demo begins, the player could press the following sequence of buttons on the game controller to enable the cheat:</p>
- <p>{{#uppercase}}{{code}}{{/uppercase}}</p>
- <p>The code is also present as an Easter egg on a number of websites.</p>
- </script>
- <div class="content-placeholder"></div>
- $(function () {
- var theTemplateScript = $("#block-expressions-template").html();
- // This is our block helper
- // The name of our helper is provided as the first parameter - in this case 'uppercase'
- Handlebars.registerHelper('uppercase', function(options) {
- // "this" is the context that existed when calling the helper.
- // The options object has a special function - fn. This is a
- // compiled version of the template that is contained between the opening and closing
- // blocks of this helper. To get a string, call fn with the context:
- return options.fn(this).toUpperCase();
- });
- var theTemplate = Handlebars.compile(theTemplateScript);
- var context = {
- "code": "up up down down left right left right b a select start"
- };
- var theCompiledHtml = theTemplate(context);
- $('.content-placeholder').html(theCompiledHtml);
- });
6.资源和延伸阅读
现在你基本上了解了handlebars的常用功能,同样再多学点也问题不大,您可以通过以下资源深入学习。
Handlebars.js-官方网站,可以获取更多案例、官方文档
Try Handlebars.js-尝试不同的应用情境(基于老版本)
Handlebars Helpers-handlebars helpers集
SWAG-更多
Handlebars API Reference-api文档
Handlebars
让你能够有能力高效地容易地创立语义化的模版。Handlebars
兼容Mustache
语法,在大多数情况下它可以读取Mustache
的语法并在你当前模板中使用。具体点击这里
安装
- 下载
npm install --save handlebars
bower install --save handlebars
开始使用
Handlebars
模板看起来就像嵌套handlebars
表达式的规范的HTML。
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
handlebars
表达式: {{ cnt }}
你也可以通过<script>
标签包裹handlebars
表达式传递模板给浏览器:
<script id="entry-template" type="text/x-handlebars-template">
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
</script>
你必须把模板放在
<script>
标签里,这很重要。不要直接把它放在HTML中否则HTML的解析会改变模板内容。
在JavaScript
中,使用Handlebars.compile
来编译模板:
var source = $("#entry-template").html();
var template = Handlebars.compile(source);
// ‘entry-template’就是包裹模板的script的id
注意这种方法在产品应用中不推荐使用。更好的方法是预编译你的模版。这将使要求的运行库更小,模板不必在浏览器中编译,显著地节省了时间。这在移动设备上尤为重要。
在JavaScript中,使用Handlebars.compile()方法来预编译模板 例如:(这是一套规则)
//用jquery获取模板
var tpl = $("#tpl").html();
//原生方法
var source = document.getElementById('#tpl').innerHTML;
//预编译模板
var template = Handlebars.compile(source);
//模拟json数据
var context = { name: "zhaoshuai", content: "learn Handlebars"};
//匹配json内容
var html = template(context);
//输入模板
$(body).html(html);
通过解析context
处理handlebars模板
获取HTML内容
:
var context = {title: "My New Post", body: "This is my first post!"};
var html = template(context);
输出html:
<div class="entry">
<h1>My New Post</h1>
<div class="body">
This is my first post!
</div>
</div>
HTML转码
Handlebars
的转码HTML值通过{{expression}}
返回. 如果你不想handlebars转码一个值的话,使用{{{expression}}}
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{{body}}}
</div>
</div>
上下文内容:
{
title: "All about <p> Tags",
body: "<p>This is a post about <p> tags</p>"
}
输出:
<div class="entry">
<h1>All About <p> Tags</h1>
<div class="body">
<p>This is a post about <p> tags</p>
</div>
</div>
Handlebars
不会转义 Handlebars.SafeString
. 如果你写了输出本身所含HTML的辅助 helper
, 你其实想返回一个新的Handlebars.SafeString
.在这种情况下,你想手动拼接参数.
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);
});
这样可以避免字符串被转码,正确响应参数,即使你不适用{{{
也不会被转码。
块级表达式
块级表达式 允许你定义一个可以触发一个与当前不同的上下文来替换模板的相应内容的helper。这些块级辅助helper通过在helper的名字前加#
并在结束时名字前加/
:
{{#list people}}{{firstName}} {{lastName}}{{/list}}
渲染context:
{
people: [
{firstName: "Yehuda", lastName: "Katz"},
{firstName: "Carl", lastName: "Lerche"},
{firstName: "Alan", lastName: "Johnson"}
]
}
我们会创建一个叫list
的helper输出HTML列表。该列表以people
为第一个参数,哈希选项为第二个参数。这些选项里包含一条名为fn的属性,在handlebars模板中通过这些属性名获取值
Handlebars.registerHelper('list', function(items, options) {
var out = "<ul>";
for(var i=0, l=items.length; i<l; i++) {
out = out + "<li>" + options.fn(items[i]) + "</li>";
}
return out + "</ul>";
});
渲染结果:
<ul>
<li>Yehuda Katz</li>
<li>Carl Lerche</li>
<li>Alan Johnson</li>
</ul>
块级辅助helper有很多特点,例如可以创建一个else
部分.因为当你调用options.fn(context)
时块级helper的内容已经被转码过,所以handlebars不会再去转码helper的内容。
handler 的路径
Handlebars
支持简单的路径,就像 Mustache
.
<p>{{name}}</p>
Handlebars 也支持嵌套的属性,比如对象的属性.
<div class="entry">
<h1>{{title}}</h1>
<h2>By {{author.name}}</h2>
<div class="body">
{{body}}
</div>
</div>
模板工作的对象context:
var context = {
title: "My First Blog Post!",
author: {
id: 47,
name: "Yehuda Katz"
},
body: "My first post. Wheeeee!"
};
这使得使用handlebars
模板处理JSON
字符串成为可能。内嵌的handlebars的路径也包括../
语法,相当于当前路径的父级。
<h1>Comments</h1>
<div id="comments">
{{#each comments}}
<h2><a href="/posts/{{../permalink}}#{{id}}">{{title}}</a></h2>
<div>{{body}}</div>
{{/each}}
</div>
{{permalink}}
{{#each comments}}
{{../permalink}}
{{#if title}}
{{../permalink}}
{{/if}}
{{/each}}
这里例子中引用了相同的permalink
即使他们在不同的块中。这种行为是新的,handlebars4支持。
Handlebars的内置块表达式(Block helper)
-
each block helper
你可以使用内置的{{#each}}
helper遍历列表块内容,用this来引用遍历的元素 例如:<ul> {{#each name}} <li>{{this}}</li> {{/each}} </ul>
对应适用的json数据
{ name: ["html","css","javascript"] };
这里的this指的是数组里的每一项元素,和上面的Block很像,但原理是不一样的这里的name是数组,而内置的each就是为了遍历数组用的,更复杂的数据也同样适用。
-
if else block helper
{{#if}}
就你使用JavaScript一样,你可以指定条件渲染DOM,如果它的参数返回false,undefined, null, "" 或者 [] (a "falsy" value)
, Handlebar将不会渲染DOM,如果存在{{#else}}
则执行{{#else}}
后面的渲染。{{#if list}} <ul id="list"> {{#each list}} <li>{{this}}</li> {{/each}} </ul> {{else}} <p>{{error}}</p> {{/if}}
对应适用json数据
var data = {info:['HTML5','CSS3',"WebGL"],"error":"数据取出错误"}
这里
{{#if}}
判断是否存在list数组,如果存在则遍历list,如果不存在输出错误信息 -
unless block helper
{{#unless}}这个语法是反向的if语法也就是当判断的值为false时他会渲染DOM 例如:{{#unless data}} <ul id="list"> {{#each list}} <li>{{this}}</li> {{/each}} </ul> {{else}} <p>{{error}}</p> {{/unless}}
-
with block helper
{{#with}}一般情况下,Handlebars模板会在编译的阶段的时候进行context传递和赋值。使用with的方法,我们可以将context转移到数据的一个section里面(如果你的数据包含section)。 这个方法在操作复杂的template时候非常有用。<div class="entry"> <h1>{{title}}</h1> {{#with author}} <h2>By {{firstName}} {{lastName}}</h2> {{/with}} </div>
对应适用json数据
{ title: "My first post!", author: { firstName: "Charles", lastName: "Jolley" } }
Handlebar的注释(comments)
Handlebars也可以使用注释写法如下
{{! handlebars comments }}
Handlebars的访问(Path)
Handlebar支持路径和mustache,Handlebar还支持嵌套的路径,使得能够查找嵌套低于当前上下文的属性
可以通过.来访问属性也可以使用../,来访问父级属性。 例如:(使用.访问的例子)
<h1>{{author.id}}</h1>
对应json数据
{
title: "My First Blog Post!",
author: {
id: 47,
name: "Yehuda Katz"
},
body: "My first post. Wheeeee!"
};
例如:(使用../访问)
{{#with person}}
<h1>{{../company.name}}</h1>
{{/with}}
对应适用json数据
{
"person":
{ "name": "Alan" },
company:
{"name": "Rad, Inc." }
};
自定义helper
Handlebars,可以从任何上下文可以访问在一个模板,你可以使用Handlebars.registerHelper()方法来注册一个helper。
调试技巧
把下面一段"debug helper"加载到你的JavaScript代码里,然后在模板文件里通过{{debug}}或是{{debug someValue}}方便调试数据
Handlebars.registerHelper("debug", function(optionalValue) {
console.log("Current Context");
console.log("====================");
console.log(this);
if (optionalValue) {
console.log("Value");
console.log("====================");
console.log(optionalValue);
}
});
handlebars的jquery插件
(function($) {
var compiled = {};
$.fn.handlebars = function(template, data) {
if (template instanceof jQuery) {
template = $(template).html();
}
compiled[template] = Handlebars.compile(template);
this.html(compiled[template](data));
};
})(jQuery);
$('#content').handlebars($('#template'), { name: "Alan" });
javascript/jquery模板引擎——Handlebars初体验
Handlebars.js下载地址:http://handlebarsjs.com/
最近自己在建一个站,采用完全的前后端分离的方式,现在正在做前端的部分。其中有项功能是需要ajax调用后端接口,返回json数据后要动态的插入数据。但是一开始我自己是用最"传统"的通过js拼接html字符串,然后再用jq插入到页面中的方式。比如说下面的这个例子,我要显示一个个人信息卡:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Index</title> 6 <script src="js/jquery.js"></script> 7 <style> 8 .person { 9 font-size:40px; 10 float:left; 11 padding:20px; 12 margin:30px; 13 background-color:pink; 14 } 15 </style> 16 </head> 17 18 <body> 19 <div class = "person" id="person_info"> 20 </div> 21 </body> 22 23 <script> 24 var data = { 25 name:'约翰莫里森', 26 home:'美国', 27 job:'摔跤手' 28 }; 29 30 var str = ""; 31 str += "<div>姓名:" + data.name + "</div>"; 32 str += "<div>出生地:" + data.home + "</div>"; 33 str += "<div>职业:" + data.job + "</div>"; 34 35 $('#person_info').html(str); 36 </script> 37 </html>
这里我得用自己"手动"拼接html字符串,况且这只是一个极其简单的例子,如果标签之间的嵌套、属性复杂的话,这种方式写起来相当的麻烦,且标签间没有层次结构,可读性和维护性极差。
后来偶然在个地方了解到了模板引擎,从此这种工作一下简便了许多! 先看下用Handlebars完成上面的例子是如何操作的:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <title>Handlebars</title> 6 <script src="js/jquery.js"></script> 7 <script src="js/handlebars.js"></script> 8 <style> 9 .person { 10 font-size:40px; 11 float:left; 12 padding:10px; 13 margin-left:30px; 14 background-color:pink; 15 } 16 </style> 17 </head> 18 19 <body> 20 <div id = "person_info"> 21 <div class = "person"> 22 <div>姓名:{{name}}</div> 23 <div>出生地:{{home}}</div> 24 <div>职业:{{job}}</div> 25 </div> 26 </div> 27 </body> 28 29 <script> 30 var data ={ 31 name:'约翰莫里森', 32 home:'美国', 33 job:'摔跤手' 34 }; 35 36 var tmpl = $('#person_info').html(); 37 38 var func = Handlebars.compile(tmpl); 39 40 var result = func(data); 41 42 $('#person_info').html(result); 43 </script> 44 45 </html>
先从最下面的js代码讲起,核心代码就这四句:
1 var tmpl = $('#person_info').html(); 2 var func = Handlebars.compile(tmpl); 3 var result = func(data); 4 $('#person_info').html(result);
第一行的 var tmpl = $('#person_info').html(); 就只是基本的jq语法;<body>中<div id="person_info">块中的就是html模板,这里取得了该div块下的html模板内容(对象tmpl)。此时如果用console.log(tmpl)打印该对象的话,得到的内容是
1 <div class = "person"> 2 <div>姓名:{{name}}</div> 3 <div>出生地:{{home}}</div> 4 <div>职业:{{job}}</div> 5 </div>
用两个大括号括起来的变量名会在后面 向函数传入数据(下面会提到) 时进行匹配。
第二行 var func = Handlebars.compile(tmpl); 通过Handlebars的compile()对刚刚取得的html模板进行预编译,返回的是一个函数(现在对象func即为该函数)。
var result = func(data); 这个函数带有一个参数,该参数为用以匹配模板用的数据。我这里事先写死一个数据对象data,然后传入该函数内。返回值是匹配好的html内容,打印出来就是 :
1 <div class = "person"> 2 <div>姓名:约翰莫里森</div> 3 <div>出生地:美国</div> 4 <div>职业:摔跤手</div> 5 </div>
最后用jq将内容插入到页面中: $('#person_info').html(result);
如果有多项数据需要展示,则需要用到{{#each this}} {{/each}}。 语法类似标签,所以要注意别漏了闭合标签{{/each}}。 {{#each this}} ... {{/each}}中间的内容是模板。this 则是指传入函数的数据data。 用在实现上述例子的代码如下:
1 <body> 2 <div id = "person_info"> 3 {{#each this}} 4 <div class = "person"> 5 <div>姓名:{{name}}</div> 6 <div>出生地:{{home}}</div> 7 <div>职业:{{job}}</div> 8 </div> 9 {{/each}} 10 </div> 11 </body> 12 13 <script> 14 var data =[{ 15 name:'约翰莫里森', 16 home:'美国', 17 job:'摔跤手' 18 }, 19 { 20 name:'Faker', 21 home:'韩国', 22 job:'英雄联盟职业选手' 23 }]; 24 25 var tmpl = $('#person_info').html(); 26 var func = Handlebars.compile(tmpl); 27 var result = func(data); 28 $('#person_info').html(result); 29 </script>
如果对象间所包含的数据域有个别不同,则需要分支语句进行判断 即{{#if xxx}} {/if}}。 用例如下:
1 <body> 2 <div id = "person_info"> 3 {{#each this}} 4 <div class = "person"> 5 <div>姓名:{{name}}</div> 6 <div>出生地:{{home}}</div> 7 <div>职业:{{job}}</div> 8 {{#if life}} 9 <div>生涯经历:</div> 10 <ul> 11 {{#each life}} 12 <li>{{this}}</li> 13 {{/each}} 14 </ul> 15 {{/if}} 16 </div> 17 {{/each}} 18 </div> 19 </body> 20 21 <script> 22 var data =[{ 23 name:'约翰莫里森', 24 home:'美国', 25 job:'摔跤手' 26 }, 27 { 28 name:'Faker', 29 home:'韩国', 30 job:'英雄联盟职业选手', 31 life:['S3世界总冠军','S4世界总冠军','S5世界总冠军'] 32 }]; 33 34 var tmpl = $('#person_info').html(); 35 console.log(tmpl); 36 var func = Handlebars.compile(tmpl); 37 var result = func(data); 38 $('#person_info').html(result); 39 </script>
最后再附上一篇关于Handlebars语法比较详细的介绍:http://www.ghostchina.com/introducing-the-handlebars-js-templating-engine/
handlebars.js是javascript的一个语义模板库,它通过将data 与 view分离 来快速构建web模板,加载时预编译,一定程度上提高了代码的复用性和可维护性,提高敲代码的效率,总之,是一个轻量级的js 库,功能与现在流行的react 等相比,功能较为单一,但是,对于小型的数据而言,是一个不错的选择。
① 需要引入handlebars.js库
② 通过script标签创建一个模板template,模板内容自定义
③ 定义一个位置(eg:div),显示模板中的内容
④ 通过script标签, 获取模板对象的内容-->compile预编译模板-->定义json数据--> 匹配json数据-->输出模板
示例代码:
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>handlebarsTest</title>
- <script type="text/javascript" src="js/handlebars-v4.0.5.js"></script>
- </head>
- <body>
- <div id="list"></div> // 模板输出位置
- <!--定义template-->
- <script id="entry-template" type="text/x-handlebars-template">
- <div class="myDiv">
- <h2 class="title"> {{title}} </h2>
- {{#persons}}
- <ul class="myUl">
- <li> 姓名:{{this.name}} ,年龄:{{this.age}} , 生日:{{this.birth}}</li>
- </ul>
- {{/persons}}
- <ul class="myUl">
- </ul>
- </div>
- </script>
- <!--获取模板,模板预编译,定义json数据,json数据匹配后,输出模板-->
- <script type="text/javascript">
- var source = document.getElementById("entry-template").innerHTML;
- var template = Handlebars.compile(source);
- var data = {
- title: "person's information",
- persons:[
- {name:"mike", age:12, birth:"11-12"},
- {name:"mary", age:10, birth:"10-28"},
- {name:"maply", age:16, birth:"2-22"}
- ]
- };
- document.getElementById("list").innerHTML = template(data);
- </script>
- </body>
- lt;/html>
handlebars 有一些基本的语法,如变量调用:{{data}},变量不用转义: {{{data}}},block 局部作用域:{{#data}}开始,{{/data}}
结束, 内置的helper:迭代:each ,条件渲染:if else ,unless ;嵌套式针对某一个数据对象:with
内置的helper相对来说 ,实现某些复杂的功能有些局限性,故可以通过Handlebars.registerHelper()自定义helper,实现较为复杂的
功能,<待续>
这篇文章很不错:http://www.tuicool.com/articles/fqQFN3
一. 为什么要使用模板引擎
关于为什么要使用模板引擎, 就我现在的项目而言,我还停留在进行发送Ajax请求到后台后,利用模板引擎拼接接受到的JSON字符串,展现到页面的地步. 按照我老师的一句话表达:不用重复制造*. 对于为什么要使用模板引擎的解释,我看过最好的回答来自知乎上niko的回答:
模板最本质的作用是【变静为动】一切利用这方面的都是优势,不利于的都是劣势。要很好地实现【变静为动】的目的,有这么几点:
1. 可维护性(后期改起来方便);
2. 可扩展性(想要增加功能,增加需求方便);
3.开发效率提高(程序逻辑组织更好,调试方便);
4.看起来舒服(不容易写错);
从以上四点,你仔细想想,前端模板是不是无论从哪方便优势体现都不是一点两点。其实最重要的一点就是:【视图(包括展示渲染逻辑)与程序逻辑的分离】分离的好处太多了,更好改了,更好加东西了,调试也方便了,看起来也舒服了,应用优秀的开发模式更方便了(mvvc,mvc等).
二. 选择Handlebars的原因
1. 全球最受欢迎的模板引擎
Handlebars是全球使用率最高的模板引擎,所以当之无愧是全球最受欢迎的模板引擎.Handlebars在许多前端框架中都被引入,比如在MUI和AmazeUI等框架,都推荐使用Handlebars.以AmazeUI为例,AmazeUI的文档中专门为Web组件提供了其Handlebars的编译模板
Amaze UI 提供的开发模板中,包含一个 widget.html 文件,里面展示了 Widget 在纯浏览器环境中的使用。
要点如下:
1.引入 Handlebars 模板 handlebars.min.js;
2.引入 Amaze UI Widget helper amui.widget.helper.js;
3.根据需求编写模板 <script type="text/x-handlebars-template" id="amz-tpl">{{>slider slider}}</script>;
4.传入数据,编译模板并插入页面中。
$(function() {
var $tpl = $('#amz-tpl');
var source = $tpl.text();
var template = Handlebars.compile(source);
var data = {};
var html = template(data);
$tpl.before(html);
});
2. 语法简单
Handlebars的基本语法极其简单,使用{{value}}将数据包装起来即可,Handlebars会自动匹配响应的数值和对象.以下是一个最简单的模板:
<div class="demo">
<h1>{{name}}</h1>
<p>{{content}}</p>
</div>
三.如何使用Handlebars
1. 下载Handlebars
- 通过Handlebars官网下载: http://handlebarsjs.com./installation.html
-
通过npm下载:
npm install --save handlebars
-
通过bower下载:
bower install --save handlebars
- 通过Github下载: https://github.com/daaain/Handlebars.git
- 通过CDN引入:https://cdnjs.com/libraries/handlebars.js
2. 引入Handlebars
通过<script>
标签引入即可,和引入jQuery库类似:
<script src="./js/handlebars-1.0.0.beta.6.js"></script>
3. 创建模板
-
步骤一: 通过一个
<script>
将需要的模板包裹起来 -
步骤二: 在
<script>
标签中填入type
和id
-
type
类型可以是除text/javascript
以外的任何MIME类型,但推荐使用type="text/template"
,更加语义化 -
id
是在后面进行编译的时候所使用,让其编译的代码找到该模板.
-
-
步骤三: 在
<script>
标签中插入我们需要的html代码,根据后台给我们的接口文档,修改其需要动态获取的内容
<script type="text/template" id="myTemplate">
<div class="demo">
<h1>{{name}}</h1>
<p>{{content}}</p>
</div>
</script>
4. 在JS代码中编译模板
//用jQuery获取模板
var tpl = $("#myTemplate").html();
//预编译模板
var template = Handlebars.compile(tpl);
//匹配json内容
var html = template(data);
//输入模板
$('#box').html(html);
以上述代码为例进行解释:
-
步骤一: 获取模板的内容放入到tpl中,这里
$("#myTemplate")
中填入的内容为你在上一步创建模板中所用的id
.- 提醒: 这里我使用的
jQuery
的选择器获取,当然,你可以使用原生javascript
的DOM
选择器获取,例如:docuemnt.getElementById('myTemplate')
和document.querySelector('#myTemplate')
- 提醒: 这里我使用的
-
步骤二: 使用
Handlebars.compile()
方法进行预编译,该方法传入的参数即为获取到的模板 -
步骤三: 使用
template()
方法进行编译后得到拼接好的字符串,该方法传入的参数即为上一步预编译的模板. -
步骤四: 将编译好的字符串插入到你所希望插入到的
html
文档中的位置,这里使用的是jQuery
给我们提供的html()
方法.同样,你也可以使用原生的innerHTML
四.案例演示
以下面的慢慢买网站为例,该项目中的手机列表,是通过Ajax动态获取的,我们不可能在html文档中写入全部的手机列表代码,这是不可能的.所以我们需要通过Handlebars来帮我们将后台传递过来的数据动态的显示到html文档中.
1. 在HTML中引入:Handlebars,jQuery和本页的Js代码
<script src="./lib/bootstrap/js/jquery-3.2.1.js"></script> //Handlebars
<script src="./js/handlebars-1.0.0.beta.6.js"></script> //jQuery
<script src="./js/product.js"></script> //本页的Js代码
2. 创建模板
在未插入模板的情况下,页面显示如下,现在我们来使用Handlebars让数据动态的显示在网页上.
<!--定义模板 -->
<script type="text/template" id="product-list-tepl">
{{#each result}}
<li>
<a href="#">
<div class="product-img">
{{{productImg}}}
</div>
<div class="product-text">
<h5>
{{productName}}
</h5>
<p>{{productPrice}}</p>
</div>
<div class="other">
<span>{{productQuote}}</span>
<span>{{productCom}}</span>
</div>
</a>
</li>
{{/each}}
</script>
以上模板中的{{#}}为Handlebars的helper语法,可以实现Javascript中的逻辑和循环运算.更多使用方法可以参考: http://www.ghostchina.com/introducing-the-handlebars-js-templating-engine/
3. 在JS代码中编译模板
//定义getList()函数来发送Ajax请求,传递的参数为后台给的接口文档中定义的参数
function getList(categoryId,pageid){
//调用jQuery的Ajax()方法来发送Ajax请求
$.ajax({
type:'get',
url:'http://182.254.146.100:3000/api/getproductlist',
data:{
pageid:pageid||1,
categoryid:categoryId
},
success:function(data){
//用zepto获取模板
var tpl = $("#product-list-tepl").html();
//预编译模板
var template = Handlebars.compile(tpl);
//匹配json内容
var html = template(data);
//插入模板,到ul中
$('.product-list ul').html(html);
}
})
}
//入口函数
$(function(){
//获取到查询字符串的id
var categoryId = Number(GetQueryString("categoryid")); //getQueryString()是获取上一步传递过来的查询字符串的方法
//调用定义的getList()获取手机列表
getList(categoryId);
})
//获取上一步传递过来的查询字符串的方法
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return unescape(r[2]);
return null;
}
4. 插入模板后的页面如下
通过上面的案例,我相信大家应该能够明白模板引擎的强大,我们只需要在页面中写好一个手机列表的HTML代码,即可动态获取后台传递过来的所有信息,从而在页面中进行展示.
注意: 在实际开发中,我们通过Ajax发送请求时所需要传递的参数,和获取到的JSON或其他格式的数据.皆是需要通过后台给定的接口文档为准.
作者:Lee_tanghui
链接:https://www.jianshu.com/p/2ad73da601fc
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
前端数据模板handlebars与jquery整合
一、简介
handlebars是前端数据模板,在此介绍handlebars主要指令,以及与jquery整合的开发流程。
二、handlebars主要指令
handlebars中的指令与数据变量,都以两个大括号(即{{和}})包围,指令以#开始,以/结尾。
1、循环指令#each
可遍历对象和数组,遍历中或获取的数据有:
a)@index ,遍历过程当前元素的索引;
b)@key ,遍历过程中,当前元素的名称(或数组元素的索引)
c)this ,遍历过程中,当前元素的值;
2、条件判断指令#if和#unless
a)#if /else,当if内的数据有且不为null时,返回true;
b)#unless,与指令相反
3、上下文指令#with
为模板的一块区域定义上下文
三、handlebars与jquery整合的开发流程
1、编写handlebars的jquery插件handlebars-jquery.js ,内容如下:
(function($){ var compiled = {}; $.fn.template = function(data){ var template = $.trim($(this).first().html()); if(compiled[template] == undefined){ compiled[template]=Handlebars.compile(template); } return $(compiled[template](data)); }; })(jQuery)
2、在页面引入jquery包、handlebars包、handlebars的jquery插件包,如下:
<script src="../lib/js/jquery-3.0.0.js"></script> <script src="../lib/js/handlebars-v4.0.10.js"></script> <script src="../lib/js/handlebars-jquery.js"></script>
3、编写数据(实际使用中,可通过ajax从后台获取数据),内容如下:
var data={ stus:[ {id:100,name:"apple1",age:11}, {id:200,name:"apple2"}, {id:300,name:"apple3",age:33}, ], stu:{id:300,name:"apple3",age:33} };
4、编写数据模板(注意:script标签的type类型为text/x-handlebars-template,内容如下:
<script id="stuTempId" type="text/x-handlebars-template"> <section> <ul> {{#each stu}} <li>{{@index}}:{{@key}}:{{this}}</li> {{/each}} </ul> </section> <table border="1px"> <tr><td>序号</td><td>学号</td><td>姓名</td><td>年龄</td><td>序号</td><td>姓名</td><td>年龄</td></tr> {{#each stus}} <tr> <td>{{@index}}</td> <td>{{id}}</td> <td>{{name}}</td> <td>{{#if age}}{{age}}{{else}}未填写{{/if}}</td> <td>{{@key}}</td> <td>{{this.name}}</td> <td>{{#with this}}{{age}}{{/with}}</td> </tr> {{/each}} </table> </script>
5、将数据绑定到模板上,并在页面展示,如:
$("#stuInfoId").empty().append($("#stuTempId").template(data).filter("*")); //filter("*")用于匹配所有html节点,过滤掉文本节点
以上完整的代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>templateStudy</title> <script src="../lib/js/jquery-3.0.0.js"></script> <script src="../lib/js/handlebars-v4.0.10.js"></script> <script src="../lib/js/handlebars-jquery.js"></script> <script id="stuTempId" type="text/x-handlebars-template"> <section> <ul> {{#each stu}} <li>{{@index}}:{{@key}}:{{this}}</li> {{/each}} </ul> </section> <table border="1px"> <tr><td>序号</td><td>学号</td><td>姓名</td><td>年龄</td><td>序号</td><td>姓名</td><td>年龄</td></tr> {{#each stus}} <tr> <td>{{@index}}</td> <td>{{id}}</td> <td>{{name}}</td> <td>{{#if age}}{{age}}{{else}}未填写{{/if}}</td> <td>{{@key}}</td> <td>{{this.name}}</td> <td>{{#with this}}{{age}}{{/with}}</td> </tr> {{/each}} </table> </script> <script type="text/javascript"> $(function () { var data={ stus:[ {id:100,name:"apple1",age:11}, {id:200,name:"apple2"}, {id:300,name:"apple3",age:33}, ], stu:{id:300,name:"apple3",age:33} }; $("#stuInfoId").empty().append($("#stuTempId").template(data).filter("*")); //filter("*")用于匹配所有html节点,过滤掉文本节点 }); </script> </head> <body> <section id="stuInfoId"></section> </body> </html>
输出:
- 0:id:300
- 1:name:apple3
- 2:age:33
序号 | 学号 | 姓名 | 年龄 | 序号 | 姓名 | 年龄 |
0 | 100 | apple1 | 11 | 0 | apple1 | 11 |
1 | 200 | apple2 | 未填写 | 1 | apple2 | |
2 | 300 | apple3 | 33 | 2 | apple3 | 33 |
https://cnodejs.org/topic/56a2e8b1cd415452622eed2d
提示:文中 “{ {” 、“} }”,在实际使用中用双花括号,中间无空格。
写这篇文章也是因为当初我为了实现一个模板分页的某个功能(这个问题以及解决方法最后说),花了不少时间将官网看了几遍,试验了很久之后依然没有成功,然后凭着一股崛起继续钻研终于解决了这个问题。此时发现handlebars的东西基本上也了解得差不多了,所以干脆抽时间写篇文章整理一下。
为什么需要模板引擎
关于前端的模板引擎,我用一个公式来解释
模板引擎
模板 + 数据 ========> html页面
模板引擎就像是html的解析生成器,将对应的模板填充完数据之后生成静态的html页面。它可以在浏览器端(比如angular中指令所用的模板)也可以在服务器端执行,不过一般用于服务器端。因为它的一个作用是抽象公共页面来重用,如果在服务端填充数据,可以减少回填数据给页面的ajax请求,从而提升浏览器端整体页面渲染速度。
那些年我用过的模板引擎
接触过的模板引擎不算多,最早应该是jsp,本质上也是一种模板引擎,再到功能稍微强大的freemarker,这两种都是属于java语系的。js语系的jade和ejs我都有所接触,不过不常用,jade那种类python的语法规则以及较低的解析效率都让我不敢兴趣,Express框架也只是早起将其作为模板引擎。后来换成了强大的ejs,无论是功能还是写法上都接近jsp了。直到最新的Express4发布,默认改为了弱逻辑的比较简洁的模板引擎handlebars。
我使用handlebars有以下几个原因:
- 这次新项目前端框架搭建基于Express4,模板引擎只能在ejs/jade/hogan/hbs中选择一个。
- 默认是handlebars,虽不知道原因,想必有其原因。
- 看过“去哪儿”的前端技术分享,他们就是在handlebars上进行封装的,证明已经有人填过坑了,可以一试。
- 开始比较看好ejs,但是官网文档被强了,相比之下handlebars的文档比较清晰,还有实例,虽然逻辑结构比较混乱,但是基本无障碍。
码解handlebars
运行环境:Express4、hbs4 未接触Express或hbs的可以先看这里
初级玩家:表达式
数据:
{
title: 'Express',
obj:{
version: 'v4.3',
category: 'node',
"date~": '2016'
}
}
模板:
<p>{ {title} }</p>
<p>{ {obj.version} }</p>
<p>{ {obj/category} }</p>
<p>{ {obj.date~} }</p>
html页面:
Express
v4.3
node
handlebars中变量都添加双花括号来表示(类似Angular),对比ejs的"<%%>“来说看起来没什么区别,其实这是很人性化的,想一下你键盘上的位置,再考虑按这几个字符的难易程度你就懂了。 其中要访问变量的属性值时可以用类似json格式的”.",也可以用"/"。
其中变量名不可包含以下字符。如果包含则不被解析,如上的"{{obj.date~}}"。
空格 ! " # % & ' ( ) * + , . / ; < = > @ [ \ ] ^ ` { | } ~
但可以用 " , ’ , [] 来转译这些特殊字符。
这一条规则意味着 “&&”,"||","!"这类逻辑判断是不能出现在表达式中的! (看着这一条是不是觉得弱爆了,要不然怎么叫若逻辑模板引擎呢~哈哈,不过当然有另外的解决办法)。
中级玩家:helper
英语水平有限,实在找不到一个恰当的词来翻译它了。可以理解为它是注入到模板中的一个函数,用来接收参数并进行逻辑处理。
默认helper
if else
{ {#if author} }
<h1>{ {firstName} } { {lastName} }</h1>
{ {else} }
<h1>Unknown Author</h1>
{ {/if} }
{ {#if isActive} }
<img src="star.gif" alt="Active">
{ {else if isInactive} }
<img src="cry.gif" alt="Inactive">
{ {/if} }
和一般的编程语言的 if-else 代码块是差不多的,不过再次重申由于上面提到的特殊字符,所以if条件中是不能有逻辑表达式的,只能是变量或者值。
unless
还是因为上面提到的那些字符,handlebars不支持逻辑非("!"),所以又有了一个与if相反的helper
{ {#unless license} }
<h3 class="warning">WARNING: This entry does not have a license!</h3>
{ {/unless} }
上面这段代码就等价于
{ {#if license} }
{ {else} }
<h3 class="warning">WARNING: This entry does not have a license!</h3>
{ {/if} }
each
都知道each相当于for循环。不过有些地方需要注意:
- 可以用相对路径的方式来获取上一层的上下文。(上下文概念跟js中的上下文差不多,比如在each passage代码块内,每一次循环上下文一次是passage[0],passage[1]…)
- 一些默认变量,@first/@last 当该对象为数组中第一个/最后一个时返回真值。如果数组成员为值而非对象,@index表示当前索引值,可以用@key或者this获取当前值
- 可以用
as |xxx|
的形式给变量起别名,循环中通过别名可以引用父级变量值。当然也可以通过相对路径的方式引用父级变量。
{ {#each passage} }
{ {#each paragraphs} }
{ {@../index} }:{ {@index} }:{ {this} }</p>
{ {else} }
<p class="empty">No content</p>
{ {/each} }
{ {/each} }
{ {#each array as |value, key|} }
{ {#each child as |childValue, childKey|} }
{ {key} } - { {childKey} }. { {childValue} }
{ {/each} }
{ {/each} }
同时也可以用来遍历对象,这时@key表示属性名,this表示对应的值
{ {#each object} }
{ {@key} }: { {this} }
{ {/each} }
with
类似js中的with,可以配合分页使用,限定作用域。
{ {#with author as |myAuthor|} }
<h2>By { {myAuthor.firstName} } { {myAuthor.lastName} }</h2>
{ {else} }
<p class="empty">No content</p>
{ {/with} }
lookup
这个用于以下这种并列数组的情况,可以按照索引来找兄弟变量对应的值。理解起来有些困难,直接看代码
{
groups: [
{id: 1, title: "group1"},
{id: 2, title: "group2"},
],
users: [
{id:1, login: "user1", groupId: 1},
{id:2, login: "user2", groupId: 2},
{id:3, login: "user3", groupId: 1}
],
infos: [
'a','b','c'
]
}
<table>
{ {#each users} }
<tr data-id="{ {id} }">
<td>{ {login} }</td>
<td data-id="{ {groupId} }">{ {lookup ../infos @index} }</td>
</tr>
{ {/each} }
</table>
user1 a
user2 b
user3 c
这里在users数组中按照索引值引用infos数组中对应的值,如果想引用groups中的groupId呢?很简单,用with。
<table>
{ {#each users} }
<tr data-id="{ {id} }">
<td>{ {login} }</td>
<td data-id="{ {groupId} }">{ {#with (lookup ../groups @index)} }{ {title} }{ {/with} }</td>
</tr>
{ {/each} }
</table>
自定义helper
内置的helper不够强大,所以通常需要写js代码自定义helper,先看一个简单的单行helper。
行级helper
传值
数值、字符串、布尔值这种常规数据可以直接传入,同时也可以传递JSON对象(但只能传一个),以key=value这种形式写在后面,最后就可以通过参数的hash属性来访问了。
模板
{ {agree_button "My Text" class="my-class" visible=true counter=4} }
代码
hbs.registerHelper('agree_button', function() {
console.log(arguments[0]);//==>"My Text"
console.log(arguments[1].hash);//==>{class:"my-class",visible:true,conter:4}
}
传变量
传变量时可以用this指针来指代它访问属性,通过逻辑判断后可以返回一段html代码,不过太建议这样做。考虑以后的维护性,这种html代码和js代码混合起来的维护性是比较差的,如果要抽象层组件还是使用分页比较好。
模板:
{ {agree_button person} }
注册helper:
hbs.registerHelper('agree_button', function(p) {
console.log(p===this);//==> true
var blog = hbs.handlebars.escapeExpression(this.person.blog),
name = hbs.handlebars.escapeExpression(this.person.name);
return new hbs.handlebars.SafeString(
"<a href='"+blog+"'>"+ name + "</button>"
);
});
数据:
var context = {
person:{name: "亚里士朱德", blog: "https://yalishizhude.github.io"} };
};
html页面:
<a href="https://yalishizhude.github.io">亚里士朱德</a>
当内容只想做字符串解析的时候可以用 escapeExpression 和 SafetString 函数。
块级helper
块级helper获取参数的方式跟之前差不多,只是最后多了一个参数,这个参数有两个函数fn
和revers
可以和else
搭配使用。后面将会讲解。
模板:
{ {#list nav} }
<a href="{ {url} }">{ {title} }</a>
{ {/list} }
注册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>";
});
数据:
{
nav: [
{ url: "https://yalishihzude.github.io", title: "blog" },
{ url: "https://www.github.com/yalishizhude", title: "github" },
]
}
html页面:
<ul>
<li> <a href="https://yalishizhude.github.io">blog</a> </li>
<li> <a href="https://www.github.com/yalishizhude">github</a> </li>
</ul>
自定义helper
each的index变量比较常用,但是它是从0开始的,往往不符合业务中的需求,这里写个helper来扩展一下。
注册helper:
hbs.registerHelper('eval', function(str, options){
var reg = /\{\{.*?\}\}/g;
var result = false;
var variables = str.match(reg);
var context = this;
//如果是each
if(options.data){
context.first = context.first||options.data.first;
context.last = context.last||options.data.last;
context.index = context.index||options.data.index;
context.key = context.key||options.data.key;
}
_.each(variables, function(v){
var key = v.replace(/{ {|} }/g,"");
var value = typeof context[key]==="string"?('"'+context[key]+'"'):context[key];
str = str.replace(v, value);
});
try{
result = eval(str);
return new hbs.handlebars.SafeString(result);
}catch(e){
return new hbs.handlebars.SafeString('');
console.log(str,'--Handlerbars Helper "eval" deal with wrong expression!');
}
});
模板:
{ {#each list} }
{ {eval '{ {index} }+1'} }
{ {/each} }
上面说到if不支持复杂的表达式,如果是“&&”操作还可以用子表达式来实现,更加复杂的就不好办了,这里我写了一个helper来实现。
注册helper:
hbs.registerHelper('ex', function(str, options) {
var reg = /\{\{.*?\}\}/g;
var result = false;
var variables = str.match(reg);
var context = this;
_.each(variables, function(v){
var key = v.replace(/{ {|} }/g,"");
var value = typeof context[key]==="string"?('"'+context[key]+'"'):context[key];
str = str.replace(v, value);
});
try{
result = eval(str);
if (result) {
return options.fn(this);
} else {
return options.inverse(this);
}
}catch(e){
console.log(str,'--Handlerbars Helper "ex" deal with wrong expression!');
return options.inverse(this);
}
});
模板:
{ {#ex "{ {state} }==='submiting'"} }
<i class="icon cross-danger">1</i>
{ {else} }
<i class="icon cross-success">2</i>
{ {/ex} }
先将整个逻辑表达式作为一个字符串传入,然后替换其中的变量值,最后用eval函数来解析表达式,同时增加异常处理。
高级玩家:partial
比较推崇使用分页来实现组件化。分页跟helper一样需要先注册。在hbs模块中可以批量注册,比较简单。
hbs.registerPartials(__dirname + '/views/partials');
基础引用
用“>”来引用模板,这种情况一般用来处理页头页尾这种简单的分页。后面可以传入参数。 { {> myPartial param} }
当使用块级表达式时,我们通常添加“#”,而分页是“>”,所以块级分页使用“#>”,这里表示如果layout分页不存在则显示块内的内容My Content。
{ {#> layout } }
My Content
{ {/layout} }
动态分页
当然也可以用表达式来代替分页名称
{ {> (whichPartial) } }
当分页中一部分代码是固定的,另一部分是变化的时候,可以在分页中添加“@partial-block”,这时当引用这个分页时,在内部编写代码将会填充到这个位置。
partial.hbs:
亚里士朱德
{ {> [@partial-block](/user/partial-block) } }
模板:
{ {#>partial} }
https:yalishizhude.github.io
{ {/partial} }
html页面:
亚里士朱德
https:yalishizhude.github.io
内联分页
当有多段代码需要填充到分页时,可以用如下方法。分页中内嵌分页变量,模板中通过内联分页的方式传入。
模板:
{ {#> partial} }
{ {#*inline "nav"} }
亚里士朱德
{ {/inline} }
{ {#*inline "content"} }
https://yalishizhude.github.io
{ {/inline} }
{ {/partial} }
partial.hbs:
<div class="nav">
{ {> nav} }
</div>
<div class="content">
{ {> content} }
</div>
html页面:
<div class="nav">
亚里士朱德
</div>
<div class="content">
https://yalishizhude.github.io
</div>
大师级玩家:API
本文列举的只是handlebars中最重要和常用的功能,更多细碎的功能可以去查看 官方API。
开头的问题
我想将导航条写成一个分页(partial),导航条左边的文字标题是可以通过参数传递的,但是右边的内容可能是文字、图片其它元素,需要具体业务自定义实现。我又不想把html代码写在js中,所以希望在模板中将这段未知的模板代码填充到分页中进行展现。我在官网文档中找到了 {{>@partial-block}}来实现此功能,但是本机实验一直解析报错。 解决过程: 这个问题原因可能有两个,一是官方文档有错,二是本机环境的插件有问题(Express用hbs模块,该模块封装了handlebars引擎模块)。为了验证官方文档的正确性,我找到了一个在线handlebars解析器,输入文档中的代码时可以正确解析,那么只可能出现在hbs模块了。这时在github上找到hbs模块最新版本为4,查看本地版本为3,更新后果然可以正常解析了。
总结
handlebars让我们看到一个好的插件应该有的特征:
- 可识别性。接口简单,使用方便,容易上手。
- 高可用性。自带常用一些功能(helper),不求多而求精。
- 可扩展性。复杂的业务逻辑,开发人员可以自定义helper去扩展和实现。
作者:亚里士朱德 博客:http://yalishizhude.github.io