Ruby's Louvre

每天学习一点点算法

导航

javascript模板系统 ejs v3

本版本增添了局部模板功能,并且允许主模板调用局部模板,局部模块调用局部模块,并去掉onsite变量,不再提供解析成毕的文档碎片给使用者。它使用双重缓存,一是缓存那些通过同步请求得到的文本而成的数组,一是整体解析完毕得到的模板函数。模板函数是通过数组元素拼凑动成解析而成的,这是大大提高了效率。不过由于新功能的加入,虽然动用了新的构筑算法也比不上v2的构筑速度了……

有人说不要使用<%与%>做界定符,这个问题我在v1版本已经提出过了,这些都是可以自定义的。本文的例子将演示一下如何使用Django的{{与}}风格。

最后隆重推介一下本版本的新功能。不过由于条件有限,无法演示。现在模板不单单是内嵌于页面的script标签之内,也可以放置在一个独立的文件之内,如html,ejs,text,随你起什么后缀名。这个文件将会用同步请求回来用于构筑模板函数。我们可以用使用url属性,或在模板中使用<%: /template/partail.ejs >实现。一般而言,url是用于主模板,而<%: url %>是用于局部模板。如果我们在配置对象中同时使用selector与url,selector的优先级是高于url的。

   var data = dom.ejs({
                    selector:"tmpl",
                    url:"/template/aaa.html",
                    left:"{{",
                    right:"}}",
                    json: {
                        name:"司徒正美",
                        blog:"ruby louvre"
                        address:"异次元"
                    }
     });

源码:

//司徒正美 javascript template - http://www.cnblogs.com/rubylouvre/ - MIT Licensed

            (function () {

                if(!String.prototype.trim){
                    String.prototype.trim = function(){
                        return this.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
                    }
                }
                var dom = {
                    quote: function (str) {
                        str = str.replace(/[\x00-\x1f\\]/g, function (chr) {
                            var special = metaObject[chr];
                            return special ? special : '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4)
                        });
                        return '"' + str.replace(/"/g, '\\"') + '"';
                    }
                },
                metaObject = {
                    '\b': '\\b',
                    '\t': '\\t',
                    '\n': '\\n',
                    '\f': '\\f',
                    '\r': '\\r',
                    '\\': '\\\\'
                },
                startOfHTML = "\t__views.push(",
                endOfHTML = ");\n";
                (function(){
                    //http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
                    var s = ["XMLHttpRequest",
                        "ActiveXObject('Msxml2.XMLHTTP.6.0')",
                        "ActiveXObject('Msxml2.XMLHTTP.3.0')",
                        "ActiveXObject('Msxml2.XMLHTTP')",
                        "ActiveXObject('Microsoft.XMLHTTP')"];
                    if( eval("''+/*@cc_on"+" @_jscript_version@*/-0")*1 === 5.7 && location.protocol === "file:"){
                        s.shift();
                    }
                    for(var i = 0 ,el;el=s[i++];){
                        try{
                            if(eval("new "+el)){
                                dom.xhr = new Function( "return new "+el)
                                break;
                            }
                        }catch(e){}
                    }
                })();

                dom.partial = function(url){
                    var xhr = dom.xhr();
                    xhr.open("GET",url,false);
                    xhr.setRequestHeader("If-Modified-Since","0");
                    xhr.send(null);
                    return xhr.responseText|| ""
                }

                dom.tmpl = function(str,rLeft,rRight,sRight){
                    var arr = str.trim().split(rLeft),self = arguments.callee,buff = [],url,els,el,i = 0, n= arr.length;
                    while (i<n) {
                        els = arr[i++]; el = els.split(rRight);
                        if(els.indexOf(sRight) !== -1){//这里不使用els.length === 2是为了避开IE的split bug
                            switch (el[0].charAt(0)) {
                                case "#"://处理注释
                                    break;
                                case "="://处理后台返回的变量(输出到页面的);
                                    buff.push(startOfHTML, el[0].substring(1), endOfHTML)
                                    break;
                                case ":"://处理局部模板
                                    url = el[0].substring(1).trim();
                                    //缓存构筑函数的数组
                                    self[url] = self[url] || self.call(null,dom.partial(url),rLeft,rRight,sRight);
                                    buff = buff.concat(dom.tmpl[url] );
                                    break;
                                default:
                                    buff.push(el[0], "\n");
                            };
                            el[1] &&  buff.push(startOfHTML, dom.quote.call(null,el[1]), endOfHTML);
                        }else{
                            buff.push(startOfHTML, dom.quote.call(null,el[0]), endOfHTML);
                        }
                    }
                    return buff;
                }

                dom.ejs = function (obj) {
                    var sLeft = obj.left || "%>",
                    sRight = obj.right || "<%",
                    rLeft = new RegExp("\\s*"+sLeft+"\\s*"),
                    rRight = new RegExp("\\s*"+sRight+"\\s*"),
                    buff = ["var __views = [];\n"],
                    key = obj.selector || obj.url,str;
                    if(obj.selector){
                        var el = document.getElementById(key);
                        if (!el) throw "找不到目标元素";
                        str = el.text;
                    }else{
                        str =  dom.partial(key);
                        if(!str) throw "目标文件不存在";
                    }
                    if(!dom.tmpl[key]){//缓存模板函数
                        buff = buff.concat(dom.tmpl.call(null,str,rLeft,rRight,sRight));
                        dom.tmpl[key] = new Function("json", "with(json){"+buff.join("") + '\t};return __views.join("");');
                    }
                    return dom.tmpl[key](obj.json || {});
                };
                window.dom = dom;

            })();

示例:

<!doctype html>
<html>
    <head>
        <meta charset="utf-8"/>
        <meta content="IE=8" http-equiv="X-UA-Compatible"/>
        <meta name="keywords" content="javascript模板 by 司徒正美" />
        <meta name="description" content="javascript模板 by 司徒正美" />
        <title>javascript模板 by 司徒正美</title>
    </head>
    <body>
        <h1>javascript模板 by 司徒正美</h1>
        <div id="tmplTC">这是容器</div>
        <script id="tmpl" type="tmpl">
                <h2>{{=  name }}{{= name }}</h2>
                {{# 这是注释!!!!!!!!! }}
                <ul>
                {{ for(var i=0; i< uls.length; i++){ }}
                    <li>{{=  uls[i] }}的名字是{{= name }}</li>
                    {{  }  }}
                </ul>
                {{ var color = "color:red;" }}
                <p style="text-indent:2em;{{= color }} ">{{= address }}</p>
           
        </script>
       <script src="dom/ejs.js"></script>
        <script>
            window.onload = function(){
                var els = [];
                for(var i=0;i<1000;i++){
                    els.push("第"+i+"个元素")
                }
                var a = new Date
                var data = dom.ejs({
                    selector:"tmpl",
                    left:"{{",
                    right:"}}",
                    json: {
                        name:"司徒正美",
                        uls:els,
                        address:"异次元"
                    }
                });
                document.getElementById("tmplTC").innerHTML = data;
                alert( new Date-a)
            }
        </script>
    </body>
</html>

现在我有一个考量,就是随着模板规模的膨胀,里面可能夹杂着越来越多变量,我们就很难分辨得清那些后台传过的东西,那些是本地的临时变量,后台的需求一变更,后台的json数据也就要改动。这维护起来非常困难。因此我非常欣赏ruby的变量书写风格,从变量名就知它是实例变量,类变量,普通变量与常量,它还有符号这东西呢,我会v4版本加入重新加入@标识符的。

posted on 2010-08-25 09:42  司徒正美  阅读(3475)  评论(4编辑  收藏  举报