让js复用smarty模板
让js复用smarty模板
场景:tabview或者加载更多内容的时候,往往需要从后端获取数据,然后用js生成相应的html代码,插入到相应的位置。
通常方法:
1. 后端直接build相应模板,然后输出到前端。
优点:smarty模板功能强大,能使用php语法,方便调用php中自定义的处理逻辑,只用写smarty模板
缺点:加载时传输数据大。
2. 前端使用js模板,用后端给的数据build。
优点:仅传输需要的数据
缺点:页面第一次展现时需要再次发送请求build需要的数据,对于展现的实时性和seo都不是很友好
3:混合使用smarty和js模板
优点:解决前两个的问题
缺点:需要维护两套模板,开发和维护成本太大
由来:因为上述的问题,我想是不是可以让js复用smarty的模板,这样就解决了3的问题,于是便有了以下研究
方法:
1. 定义smarty模板文件,如a.inc。模板内容用{%literal%}标签包裹。
{%literal%} {%foreach $array as $item%} {%if !empty($item%) || $item@first} {%$item%} {%else%} {%$item|default: 'test'%} {%/if%} {%/foreach%} {%literal%}
2. 在smarty中使用模板方法
{%capture "template_string"%} {%include file="a.inc"%} {%/capture%} {%$template_string = $smarty.capture.template_string%} {%include file="string:$template_string"%}
3. 引入js使用的模板
<script type="text/tmpl" id="test"> {%include file="a.inc"%} </script>
4. 对js模板方法进行改造
我改造的js模板为qwrap的简易模板,改造的关键主要是将smarty的语法方式改成js的语法方式。同时将smarty使用到的方法映射成对应的js方法实现。至于具体细节就不细说了(主要修改部分已用“ // ”注释符合进行说明),直接上改造后的源码。
/*smarty函数和js函数进行转换*/ var StringH = { encode4Html: function(s) { var el = document.createElement('pre'); var text = document.createTextNode(s); el.appendChild(text); return el.innerHTML; }, encode4HtmlValue: function(s) { return StringH.encode4Html(s).replace(/"/g, """).replace(/'/g, "'"); } }; window.foreach = function(arr, callback, pThis) { for (var i = 0, len = arr.length; i < len; i++) { if (i in arr) { callback.call(pThis, arr[i], i, arr); } } }; window.empty = function(a){ return !a; }; String.prototype.getDefault = function(a){ return this.toString() || a; }; String.prototype.escape = function(a){ if (a.toLowerCase() == 'html') { return StringH.encode4HtmlValue(this.toString()); } return this.toString(); }; var Tmpl = (function() { /* sArrName 拼接字符串的变量名。 */ var sArrName = "sArrCMX", sLeft = sArrName + '.push("'; /* tag:模板标签,各属性含义: tagG: tag系列 isBgn: 是开始类型的标签 isEnd: 是结束类型的标签 cond: 标签条件 rlt: 标签结果 sBgn: 开始字符串 sEnd: 结束字符串 trans: 对标签中的内容进行转换处理 // 修改新增 */ var tags = { 'if': { tagG: 'if', isBgn: 1, rlt: 1, sBgn: '");if(', sEnd: '){' + sLeft }, 'elseif': { tagG: 'if', cond: 1, rlt: 1, sBgn: '");} else if(', sEnd: '){' + sLeft }, 'else': { tagG: 'if', cond: 1, rlt: 2, sEnd: '");}else{' + sLeft }, '/if': { tagG: 'if', isEnd: 1, sEnd: '");}' + sLeft }, 'foreach': { tagG: 'foreach', isBgn: 1, rlt: 1, sBgn: '");foreach(', // 修改 trans: function(e){ return e.replace(/as\s*([$\w]+)/, function($a, $b){ return ',function(' + $b + ',' + $b + '_index' + ',' + $b + '_arr'; }); // 修改新增 }, sEnd: '){' + sLeft // 修改 }, '/foreach': { tagG: 'foreach', isEnd: 1, sEnd: '")});' + sLeft } }; return function(sTmpl, optsName) { var N = -1, NStat = []; /*语句堆栈;*/ var ss = [ [/\{strip\}([\s\S]*?)\{\/strip\}/g, function(a, b) { return b.replace(/[\r\n]\s*\}/g, " }").replace(/[\r\n]\s*/g, ""); }], [/\\/g, '\\\\'], [/"/g, '\\"'], [/\r/g, '\\r'], [/\n/g, '\\n'], /*为js作转码.*/ [ /\{%[\s\S]*?\S\%}/g, /*js里使用}时,前面要加空格。*/ // 按情况修改标签分隔符 function(a) { a = a.substr(2, a.length-2-2); for (var i = 0; i < ss2.length; i++) {a = a.replace(ss2[i][0], ss2[i][1]); } var tagName = a; if (/^(.\w+)\W/.test(tagName)) {tagName = RegExp.$1; } var tag = tags[tagName]; if (tag) { if (tag.isBgn) { var stat = NStat[++N] = { tagG: tag.tagG, rlt: tag.rlt }; } if (tag.isEnd) { if (N < 0) {throw new Error("Unexpected Tag: " + a); } stat = NStat[N--]; if (stat.tagG != tag.tagG) {throw new Error("Unmatch Tags: " + stat.tagG + "--" + tagName); } } else if (!tag.isBgn) { if (N < 0) {throw new Error("Unexpected Tag:" + a); } stat = NStat[N]; if (stat.tagG != tag.tagG) {throw new Error("Unmatch Tags: " + stat.tagG + "--" + tagName); } if (tag.cond && !(tag.cond & stat.rlt)) {throw new Error("Unexpected Tag: " + tagName); } stat.rlt = tag.rlt; } var tmp = a.substr(tagName.length); if(!!tag.trans){ tmp = tag.trans(tmp); } // 修改新增标签转换 for (var i = 0; i < ss3.length; i++) {tmp = tmp.replace(ss3[i][0], ss3[i][1]); } // 修改新增标签转换 return (tag.sBgn || '') + tmp + (tag.sEnd || ''); } else { for (var i = 0; i < ss3.length; i++) {a = a.replace(ss3[i][0], ss3[i][1]); } // 修改新增标签转换 return '",(' + a + '),"'; } } ] ]; var ss2 = [ [/\\n/g, '\n'], [/\\r/g, '\r'], [/\\"/g, '"'], [/\\\\/g, '\\'], [/print\(/g, sArrName + '.push('] ]; // 修改新增标签转换方法 var ss3 = [ [/\|\s*default\s*:\s*([^\s|]*)/, function(a,b){ return '.getDefault(' + b + ')'; }], [/\|\s*escape\s*:\s*([^\s|]*)/, function(a,b){ return '.escape(' + b + ')'; }], [/([$\w]+)@first/, function(a,b){ return '(' + b + '_index == 0)'; }], [/([$\w]+)@last/, function(a,b){ return '(' + b + '_index == ' + b + '.length - 1)'; }], [/([$\w]+)@index/, function(a,b){ return '(' + b + '_index)'; }] ]; for (var i = 0; i < ss.length; i++) { sTmpl = sTmpl.replace(ss[i][0], ss[i][1]); } if (N >= 0) {throw new Error("Lose end Tag: " + NStat[N].tagG); } sTmpl = sTmpl.replace(/##7b/g,'{').replace(/##7d/g,'}').replace(/##23/g,'#'); /*替换特殊符号{}#*/ sTmpl = 'var ' + sArrName + '=[];' + sLeft + sTmpl + '");return ' + sArrName + '.join("");'; /*console.log('转化结果\n'+sTmpl);*/ try{ var fun = new Function(optsName, sTmpl); } catch (e){ console.log && console.log("tmpl error"); throw new Error("tmpl error"); } return fun; }; }());
结果:改造仅仅针对foreach,if这两个常用标签进行了转换。对变量调节器,如default进行了转换。对smarty用的php方法,如empty进行了转换。
若需要用的更多的smarty的东西,也可以按类似思路进行添加。
不过,使用该方式还是有不少问题。
1. 毕竟smarty和js的语法有差异,部分语法可能很难实现转换。所以,若有很复杂的逻辑,那还是考虑使用单一的一个模板吧。但根据我个人的编码经验来看,改造后的模板还是能够应对绝大多数的情况的。
2. 对js运行环境的污染。(7.21新增:可以考虑为foreach,以及加在prototype上的等方法添加命名空间进行解决。如smarty.foreach,相应的为模板函数里的foreach标签也添加上smarty命名空间就行了)
3. 安全方面的问题。使用该方法也就意味着把smarty的代码暴露出来。不过个人认为模板里实现的本来就是很简单的,循环、判断、展现的功能,也无所谓暴不暴露了。
ps:该文系临时赶工而出,有啥不对的地方欢迎指正。文章格式就等有空再调了。(话说调blog的格式貌似是我的软肋)
ps:感谢qwrap的代码。