讓js複用smarty模板php
場景:tabview或者加載更多內容的時候,每每須要從後端獲取數據,而後用js生成相應的html代碼,插入到相應的位置。html
一般方法:前端
1. 後端直接build相應模板,而後輸出到前端。
優勢:smarty模板功能強大,能使用php語法,方便調用php中自定義的處理邏輯,只用寫smarty模板
缺點:加載時傳輸數據大。
2. 前端使用js模板,用後端給的數據build。
優勢:僅傳輸須要的數據
缺點:頁面第一次展示時須要再次發送請求build須要的數據,對於展示的實時性和seo都不是很友好
3:混合使用smarty和js模板
優勢:解決前兩個的問題
缺點:須要維護兩套模板,開發和維護成本太大git
由來:由於上述的問題,我想是否是可讓js複用smarty的模板,這樣就解決了3的問題,因而便有了如下研究github
方法:
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使用的模板app
<script type="text/tmpl" id="test"> {%include file="a.inc"%} </script>
4. 對js模板方法進行改造
我改造的js模板爲qwrap的簡易模板,改造的關鍵主要是將smarty的語法方式改爲js的語法方式。同時將smarty使用到的方法映射成對應的js方法實現。至於具體細節就不細說了(主要修改部分已用「 // 」註釋符合進行說明),直接上改造後的源碼。ide
/*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的代碼。