让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, "&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

posted @ 2013-07-19 21:20  snadn  阅读(948)  评论(0编辑  收藏  举报