讓js複用smarty模板

讓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, "&quot;").replace(/'/g, "&#039;");
    }
};
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;
    };
}());
View Code

結果:改造僅僅針對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的代碼。

ps:https://github.com/snadn/jsTemplateLikeSmarty

相關文章
相關標籤/搜索