Handlebars玩起来

余生长醉 提交于 2020-11-11 00:41:57

为什么需要模板引擎

关于前端的模板引擎,我用一个公式来解释

            模板引擎  模板 + 数据 ========> 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函数来解析表达式,同时增加异常处理。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!