讓Mustache支持簡單的IF語句


轉載:https://blog.csdn.net/iteye_16732/article/details/82070065

Mustache是一種Logic-less templates.不支持if這類條件判斷是Logic-less的顯著特征之一.Mustache的另一個特征是體積小,不依賴其他前端類庫,在瀏覽器端和NodeJS中都可以運行.

並非Logic-less.Mustache的體積小,無依賴,前后兼容才是我們當前的項目選擇這套模板系統的真正原因.沒有IF有時候感覺並不給力,所以就想辦法簡單擴展下Mustache,讓其具有一些通用的條件判斷能力.

比如如下的應用場景,我們需要根據某一字段的值,決定輸出有意義的中文,並用顏色加以修飾.

status=="P" ==> <b style="color:green">通過</b>
status=="W" ==> 等待
status=="R" ==> <b style="color:red">拒絕</b>

Logic-less模板實現這個功能就需要在數據上下功夫,如下.

 1 data = {
 2     list:[
 3         { id:"1",status"P"},
 4         { id:"2",status"W"},
 5         { id:"3",status"R"}
 6     ],
 7     statusRenderer:function(){
 8         if(this.status=="P"){
 9             return '<b style="color:green">通過</b>'
10         }else if(this.status=="W"){
11             return '等待'
12         }else{
13             return '<b style="color:red">拒絕</b>'
14         }
15     }
16 }

這里的statusRenderer就是在數據這邊擴展做的工作,{{{statusRenderer}}}在渲染時,this指向當前context,在取得status之后,經過判斷,return正確的渲染字符串.
於是配合下面的模板就可以滿足我們的要求

1 <ul>
2 {{#list}}
3 <li>ID:{{id}},status:{{{statusRenderer}}}</li>
4 {{/list}}
5 </ul>

項目是很復雜的,如果需要寫無數statusRenderer那會非常累,所以我想Renderer功能強大,在遇到各種特殊情況時,單獨寫一下未嘗不可,但是想上面這種常見需求是需要抽象一下的.
比如我們希望如下這樣的模板,完成同樣的需求.

1 <ul>
2 {{#list}}
3 {{#if(status==P)}}<li>ID:{{id}},status:<b style='color:green'>通過</b></li>{{/endif}}
4 {{#if(status==W)}}<li>ID:{{id}},status:等待</li>{{/endif}}
5 {{#if(status==R)}}<li>ID:{{id}},status:<b style='color:red'>拒絕</b></li>{{/endif}}
6 {{/list}}
7 </ul>

這個改造看起來一定是會傷筋動骨的,因為完全打破了{{#xxx}}{{/xxx}}這種Mustache的嵌套模式.改過之后Mustache就不再是Mustache了.
於是我們在這里妥協下,把{{/endif}}改為{{/if(status==W)}},這樣{{#if(status==W)}}{{/if(status==W)}}就配起對來了.
接下來我們就只要想辦法從模板中把這類標簽正則出來,然后為這類配對自動添加Renderer即可.
模板變成了下面這樣:

1 <ul>
2 {{#list}}
3 {{#if(status==P)}}<li>ID:{{id}},status:<b style='color:green'>通過</b></li>{{/if(status==P)}}
4 {{#if(status==W)}}<li>ID:{{id}},status:等待</li>{{/if(status==W)}}
5 {{#if(status==R)}}<li>ID:{{id}},status:<b style='color:red'>拒絕</b></li>{{/if(status==R)}}
6 {{/list}}
7 </ul>

而輸入給to_html方法的數據則變成如下這樣(里邊的Render為自動生成):

 1 data = {
 2     list:[
 3         { id:"1",status"P"},
 4         { id:"2",status"W"},
 5         { id:"3",status"R"}
 6     ],
 7     //下面Renderer為自動生成.
 8     "if(status==P)":function(){
 9         if(this.status=="P"){
10             return true;
11         }
12         return false;
13     },
14     "if(status==W)":function(){
15         if(this.status=="W"){
16             return true;
17         }
18         return false;
19     },
20     "if(status==R)":function(){
21         if(this.status=="R"){
22             return true;
23         }
24         return false;
25     }
26 }

整個改造的大體流程如下,首先從模板中取出if(x.y.z==abc)這樣的key,然后自動生成以"if(x.y.z==abc)"為名字的Renderer.全部代碼如下:

 1     function addFns(template, data){
 2         var ifs = getConditions(template);
 3         var key = "";
 4         for (var i = 0; i < ifs.length; i++) {
 5             key = "if(" + ifs[i] + ")";
 6             if (data[key]) {
 7                 continue;
 8             }
 9             else {
10                 data[key] = buildFn(ifs[i]);
11             }
12         }
13     }
14     function getConditions(template){
15         var ifregexp_ig = /\{{2,3}[\^#]?if\((.*?)\)\}{2,3}?/ig;
16         var ifregexp_i = /\{{2,3}[\^#]?if\((.*?)\)\}{2,3}?/i;
17         var gx = template.match(ifregexp_ig);
18         var ret = [];
19         if (gx) {
20             for (var i = 0; i < gx.length; i++) {
21                 ret.push(gx[i].match(ifregexp_i)[1]);
22             }
23         }
24         return ret;
25     }
26     function buildFn(key){
27         key = key.split("==");
28         var res = function(){
29             var ns = key[0].split("."), value = key[1];
30             var curData = this;
31             for (var i = ns.length - 1; i > -1; i--) {
32                 var cns = ns.slice(i);
33                 var d = curData;
34                 try {
35                     for (var j = 0; j < cns.length - 1; j++) {
36                         d = d[cns[j]];
37                     }
38                     if (cns[cns.length - 1] in d) {
39                         if (d[cns[cns.length - 1]].toString() === value) {
40                             return true;
41                         }
42                         else {
43                             return false;
44                         }
45                     }
46                 } 
47                 catch (err) {
48                 }
49             }
50             return false;
51         };
52         return res;
53     }
54     // new to_html for exports
55     function to_html(template, data){
56         addFns(template, data);
57         return Mustache.to_html.apply(this, arguments);    
58     }

看起來這樣做的好處是保持了Mustache的配對風格,並且繼續無縫支持原生的嵌套以及否定等語法.
但看起來模板挺丑的,性能損耗也一定是有不少的.

后續的擴展,我想elseif肯定不能支持了,if中帶"與""或"判斷倒是還方便添加的.
當然還可以做很多擴展,比如給數組增加一些內置屬性如"_index_", "_first_", "_last_", "_odd_", "_even_".

Mustache仍然足夠簡單,它本身就具有循環和否定判斷等特性,增加了IF后,稍微加了點邏輯,但能少寫很多Renderer.

重要的是我們依然保有我們所看重的東西:體積小,無依賴,前后兼容.
在我的項目里破壞了Logic-less是我的事情並不接受批判,但請大家根據自己實際情況謹慎選擇.

剛剛接觸Mustache,各種特性還在學習摸索中,現在看起來Lambda和子模板等特性,讓Mustache的JS實現小巧卻功能強大.

或許今天的需求還有更好的解決方案,如果有同學知道還望不吝賜教.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM