Handlebars玩起来
2016-11-17 00:00
316 查看
摘要: 模板引擎就像是html的解析生成器,将对应的模板填充完数据之后生成静态的html页面。
模板引擎就像是html的解析生成器,将对应的模板填充完数据之后生成静态的html页面。它可以在浏览器端(比如angular中指令所用的模板)也可以在服务器端执行,不过一般用于服务器端。因为它的一个作用是抽象公共页面来重用,如果在服务端填充数据,可以减少回填数据给页面的ajax请求,从而提升浏览器端整体页面渲染速度。
模板:
handlebars中变量都添加双花括号来表示(类似Angular),对比ejs的”<%%>”来说看起来没什么区别,其实这是很人性化的,想一下你键盘上的位置,再考虑按这几个字符的难易程度你就懂了。其中要访问变量的属性值时可以用类似json格式的”.”,也可以用”/“。
其中变量名不可包含以下字符。如果包含则不被解析,如上的”“。
但可以用 “ , ‘ , [] 来转译这些特殊字符。
这一条规则意味着 “&&”,”||”,”!”这类逻辑判断是不能出现在表达式中的! (看着这一条是不是觉得弱爆了,要不然怎么叫若逻辑模板引擎呢~哈哈,不过当然有另外的解决办法)。
和一般的编程语言的 if-else 代码块是差不多的,不过再次重申由于上面提到的特殊字符,所以if条件中是不能有逻辑表达式的,只能是变量或者值。
上面这段代码就等价于
each
都知道each相当于for循环。不过有些地方需要注意:
可以用相对路径的方式来获取上一层的上下文。(上下文概念跟js中的上下文差不多,比如在each passage代码块内,每一次循环上下文一次是passage[0],passage[1]…)
一些默认变量,@first/@last 当该对象为数组中第一个/最后一个时返回真值。如果数组成员为值而非对象,@index表示当前索引值,可以用@key或者this获取当前值
可以用
同时也可以用来遍历对象,这时@key表示属性名,this表示对应的值
接下,俺们来实际操作一下:
handlebars_example.html页面 HTML:
handlebars_example.html页面 JS
结果:
这里在users数组中按照索引值引用infos数组中对应的值,如果想引用groups中的groupId呢?很简单,用with。
数值、字符串、布尔值这种常规数据可以直接传入,同时也可以传递JSON对象(但只能传一个),以key=value这种形式写在后面,最后就可以通过参数的hash属性来访问了。
注:以 key=value 的形式数据,必须写在最后,key=value 串中不能再有 单独的 “数值、字符串、布尔值”
模板
代码
传变量
传变量时可以用this指针来指代它访问属性,通过逻辑判断后可以返回一段html代码,不过不建议这样做。考虑以后的维护性,这种html代码和js代码混合起来的维护性是比较差的,如果要抽象成组件,还是使用分页比较好。
模板:
注册helper:
数据:
html页面:
当内容只想做字符串解析的时候可以用 escapeExpression 和 SafetString 函数。
模板:
注册helper:
数据:
html页面:
方案一:
方案二:
模板:
上面说到if不支持复杂的表达式,如果是“&&”操作还可以用子表达式来实现,更加复杂的就不好办了,这里我写了一个helper来实现。
注册helper:
html页面:
context:
先将整个逻辑表达式作为一个字符串传入,然后替换其中的变量值,最后用eval函数来解析表达式,同时增加异常处理。
为什么需要模板引擎
关于前端的模板引擎,我用一个公式来解释模板引擎 模板 + 数据 ========> html页面
模板引擎就像是html的解析生成器,将对应的模板填充完数据之后生成静态的html页面。它可以在浏览器端(比如angular中指令所用的模板)也可以在服务器端执行,不过一般用于服务器端。因为它的一个作用是抽象公共页面来重用,如果在服务端填充数据,可以减少回填数据给页面的ajax请求,从而提升浏览器端整体页面渲染速度。
初级玩家:表达式
数据:{ 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>
handlebars中变量都添加双花括号来表示(类似Angular),对比ejs的”<%%>”来说看起来没什么区别,其实这是很人性化的,想一下你键盘上的位置,再考虑按这几个字符的难易程度你就懂了。其中要访问变量的属性值时可以用类似json格式的”.”,也可以用”/“。
其中变量名不可包含以下字符。如果包含则不被解析,如上的”“。
空格 ! " # % & ' ( ) * + , . / ; < = > @ [ / ] ^ ` { | } ~
但可以用 “ , ‘ , [] 来转译这些特殊字符。
这一条规则意味着 “&&”,”||”,”!”这类逻辑判断是不能出现在表达式中的! (看着这一条是不是觉得弱爆了,要不然怎么叫若逻辑模板引擎呢~哈哈,不过当然有另外的解决办法)。
中级玩家: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} }
接下,俺们来实际操作一下:
handlebars_example.html页面 HTML:
<script id="entry-template-1" type="text/x-handlebars-template"> <div class="entry"> <h1>{{title}}</h1> <div class="body"> {{body}} </div> </div> with: {{#with withauthor as |myAuthor|}} <h2>By {{myAuthor.firstName}} {{myAuthor.lastName}}</h2> {{else}} <p class="empty">No content</p> {{/with}} if-else: {{#if author}} <h1>{{author}}</h1> {{else}} <h1>Unknown Author</h1> {{/if}} if-else-if: {{#if isActive}} <h1>{{isActive}}</h1> {{else if isInactive}} <h1>{{isInactive}}</h1> {{else}} <h1>Unknown Author</h1> {{/if}} </script> <script id="entry-template-2" type="text/x-handlebars-template"> {{#each passage}} {{#each paragraphs}} <p>{{@../index}}:{{@index}}:{{this}}</p> {{#each this}} {{@key}}--{{this}} {{/each}} {{else}} <p class="empty">No content</p> {{/each}} {{/each}} </script>
handlebars_example.html页面 JS
# 上述实例实现过程 var context = {"title": "My New Post", "body": "This is my first post!","author":"liming","isInactive":"true","withauthor":{"firstName":"ming","lastName":"li"}}; var source = $("#entry-template-1").html(); var template = Handlebars.compile(source); var html = template(context); var context = {"passage":[{"paragraphs":[{"ppkey1":"ppval1"},{"ppkey1":"ppval2"},{"ppkey1":"ppval3"}]},{"pkey":"pval1"},{"pkey":"pval2" var source = $("#entry-template-2").html(); var template = Handlebars.compile(source); var html = template(context); console.log(html);
<div class="entry"> <h1>My New Post</h1> <div class="body"> This is my first post! </div> </div> with: <h2>By ming li</h2> if-else: <h1>liming</h1> if-else-if: <h1>true</h1> each-this-object: <p>0:0:[object Object]</p> ppkey1--ppval1 <p>0:1:[object Object]</p> ppkey1--ppval2 <p>0:2:[object Object]</p> ppkey1--ppval3 <p class="empty">No content</p> <p class="empty">No content</p>
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属性来访问了。
注:以 key=value 的形式数据,必须写在最后,key=value 串中不能再有 单独的 “数值、字符串、布尔值”
模板
{{agree_button "My Text" true 111 class="my-class" visible=true conter=4 }}
代码
Handlebars.registerHelper('agree_button', function() { console.log(arguments[0]);//==>"My Text" console.log(arguments[1]);//==> true console.log(arguments[2]);//==> 111 console.log(arguments[3].hash);//==>{class:"my-class",visible:true,conter:4} }
传变量
传变量时可以用this指针来指代它访问属性,通过逻辑判断后可以返回一段html代码,不过不建议这样做。考虑以后的维护性,这种html代码和js代码混合起来的维护性是比较差的,如果要抽象成组件,还是使用分页比较好。
模板:
{{agree_button person}}
注册helper:
Handlebars.registerHelper('agree_button', function(p) { console.log(p===this);//==> true /* person: Object blog:"blog_value" name::"name_value" */ var blog = Handlebars.Utils.escapeExpression(this.person.blog), name = Handlebars.Utils.escapeExpression(this.person.name); return new Handlebars.SafeString("<button type='button'>my blog is:"+blog+",my name is:"+ name + "</button>"); });
数据:
var context = {"person":{"blog":"blog_value","name:":"name_value"}};
html页面:
<button type='button'>my blog is:blog_value,my name is:</button>
当内容只想做字符串解析的时候可以用 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://url1", title: "blog" }, { url: "https://url2", title: "github" }, ] }
html页面:
<ul> <li> <a href="https://url1">blog</a> </li> <li> <a href="https://url2">github</a> </li> </ul>
自定义helper
each的index变量比较常用,但是它是从0开始的,往往不符合业务中的需求,这里写个helper来扩展一下。方案一:
Handlebars.registerHelper("addOne",function(index,options){ return parseInt(index)+1 ; });
方案二:
Handlebars.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(i,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 Handlebars.SafeString(result); }catch(e){ return new Handlebars.SafeString(''); console.log(str,'--Handlerbars Helper "eval" deal with wrong expression!'); } });
模板:
{{#each list}} {{eval '{{index}}+1'}} {{/each}}
上面说到if不支持复杂的表达式,如果是“&&”操作还可以用子表达式来实现,更加复杂的就不好办了,这里我写了一个helper来实现。
注册helper:
/* 主要思想是使用eval执行想要的逻辑。以拼接字符的模式来进行逻辑判断理论上可以如同EL表达式一样处理页面上的大部分逻辑。 如:{{#expression a '==' b '&&' c '>' 0}} ...{{else}}.. {{/expression}} */ Handlebars.registerHelper('expression', function(str,options) { # 过滤出以 {{expression string}} 表达的数组,最终variables显示 :["{{state}}", "{{number}}"] var reg = /\{\{.*?\}\}/g; var result = false; var variables = str.match(reg); #console.log(variables); var context = this; $.each(variables,function(i,v){ #console.log(v); var key = v.replace(/{{|}}/g,""); var value = typeof context[key]==="string"?('"'+context[key]+'"'):context[key]; str = str.replace(v, value); }); #用this可以取到当前的上下文主体,此处就是我们的定义好的数据对象了。 #另外一个比较重要的就是options.fn方法,此方法可以将你传入的上下文主体编译到模板,返回编译后的结果, #在helper中,我们把this传了进去,于是在模板中也可以引用到它。最终options.fn返回编译后的结果。 #也可以为options.fn传入其他的上下文对象,比如你要写一个迭代器,可以把数组的元素依次传入。 #另一个方法,options.inverse,它是取相反的意思,对应了我们模板中的{{else}}标签, #它会编译{{else}}中的的内容并返回结果,如果我们的helper中需要带else逻辑,用它就可以了。 try{ result = eval(str); if (result) { return options.fn(this); } else { return options.inverse(this); # 输出:no sub } }catch(e){ console.log(str,'--Handlerbars Helper "ex" deal with wrong expression!'); return options.inverse(this); } });
html页面:
{{#expression "{{state}}==='sub' && {{number}}>10" }} sub {{else}} no sub {{/expression}}
context:
var context={"state":"sub","number":2}
先将整个逻辑表达式作为一个字符串传入,然后替换其中的变量值,最后用eval函数来解析表达式,同时增加异常处理。
相关文章推荐
- SecureCrt ssh 玩起来
- Spring Cloud在国内中小型公司能用起来吗?
- Spring Cloud在国内中小型公司能用起来吗?
- 火狐浏览器是如何又变快起来的?
- 洛谷2915 usaco08nov 奶牛混合起来 Mixed Up Cows
- Linux Centos 6.5网络启动不起来Bringing up interface eth0: Error:Connection activation failed:Device not man
- 从svn上下载下来的项目跑不起来
- 让你ViewPager的滑动顺滑起来!
- 这些东西实现起来很容易
- 这样讲解 yield//把一个不是很好理解的概念和一个很常见的实例联系起来,轻松引出概念【再次推荐这个Python教材】
- 简单到让人发指 配置起来确让人头痛 (思科)
- 如何解决Android Studio 使用起来很卡
- 异常:hadoop启动时namenode节点找不到以至于启动不起来
- ubuntu与xshell连接不起来
- 将解决问题的方法封装起来的重要性
- Spring Boot JPA如何把ORM统一起来
- 如何将outlook自动分割的附件拼合起来
- JavaScript页面模板库handlebars的简单用法
- 天气真的热起来了
- 制做像QQ那种自动停靠.自动收缩的窗口,好东西呀,收藏起来,方便以后使用。谢谢了。