让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实现小巧却功能强大.
或许今天的需求还有更好的解决方案,如果有同学知道还望不吝赐教.