• javascript模板系统 ejs v10


    最近一直攻略node.js,发现ejsv9在后端的视图层有点力不从心。

    后端是模板的最大用户,因此拼字符串必须会死翘翘。通常来说,我们一个action对应一个模板,它应该是只含body部分的HTML,另外,还有一个layout,它是包含head与body的底部。它们两个加起来,加个模型层的数据生成一个真正的页面返给前端。但生成这页面不像普通的挖坑填数字的过程,像ejs、mustache、micro-Templating、doT.js就是如此。不过有的模板可以套嵌大量的逻辑,有的不能,像mustache就号称Logic-less templates,目的不想让模板也成为代码的意大利面条,这是JSP时代的教训。但javaer在struct建立王者统治的年代,腾出许多精力来研究各种东西,对视图层提出两大解决方案,视图helper与标签库。其中标签库因为学习成本过高,因此当其他语言发展出的web framework时,视图helper几乎是他们唯一的选择。视图helper也是返回一段HTML字符串,只不过它与业务层打交道非常频繁而独立出来,而它们独立出来则大大减少视图乱堆代码的现象。helper机制就是ejs v10引入的一种重要改进了。

    另外,模板里面的内容有两个地方需要输出到页面,一个是定界符左边与右边的HTML片断,另一个是来自JS逻辑的某些数据,如<%= aaa %>这样的写法(不同每个模板的语法都不一样!)PHP著名的smarty模板有个语法糖,可以对这些将要输出的变量进行进一步加工,即在变量后面加一个|号,后面跟着此过滤器的名字与其传参,而且可以跟多个|跟其他的过滤器。ejs v10也引进此语法糖。

    <%$data.circle.title|escape:"html"%>
    

    在其他重大改进,模板构建算法改进,不再使用转义。传统的前端模板都会对源码中的“'”,“"”,“\\”,“\n”,“\t”,“\f”,“\b”,“\r”进行处理,也就是所谓加双号过程,双引号里面的数据进行转义,防止破坏动态生成的模板函数的结构。ejs v10则将这个HTML数组放到模板函数的外面,从此一劳永逸了!@前缀变量也保证ejs不使用with机制进行对象绑定的。

    helper机制的使用,helper函数应该一开始就与模板函数绑在一起的,因此ejs v10的模板函数最初是一个curry函数,它负责传入HTML数组,filter,与helpers。

    //helper机制的使用
     var fn = $.ejs.compile(source, helper);
    

    helper是一个对象,里面尽是函数。

    生成的是模板函数,如果你是这样调用$.ejs(id, data),它就百分之百缓存了这函数。它里面是调用了$.ejs.compile

    现在ejs作为我的newland.js项目的一个模块而存在.

    更改日志:

    
    v1
    
    默认界定符为<% %>,当然也可以自定义界定符,只支持当前页面的script元素做模板
    
    http://www.cnblogs.com/rubylouvre/archive/2010/08/10/1796383.html
    
    v2
    
    改进构建算法提速,比John Resig的 Micro-Templating模板更能应对复杂的模板
    
    http://www.cnblogs.com/rubylouvre/archive/2010/08/22/1805914.html
    
    v3
    
    http://www.cnblogs.com/rubylouvre/archive/2010/08/25/1807789.html
    
    增添了局部模板功能
    
    v4
    
    http://www.cnblogs.com/rubylouvre/archive/2010/08/31/1813122.html
    
    对v3的结构进行优化,支持远程的独立文件做模板
    
    v5
    
    http://www.cnblogs.com/rubylouvre/archive/2010/08/31/1813122.html
    
    尝试新的算法
    
    v6
    
    http://www.cnblogs.com/rubylouvre/archive/2010/10/05/1841933.html
    
    更新默认界定符为<& &>,添加新的操作符<&~,对数据源的第一层属性名添加@前缀
    
    v7
    
    对参数进行多态化,因ejs天生支持模板的相互调用便去除<&:与<&~操作符
    
    v8
    去掉去掉参数多态化,现在只有两个参数。第一个参数为script标签的ID,第二个参数对数据对象
    去掉@标识符,网友反映这东西很怪
    去掉远程模板支持,因为怎么远程也一定要同域才行,要不AJAX获取不到,鸡肋。以后模板统一写到type为"text/html"的scrpt元素中。
    优化quote函数。网上有许多JS模板都是直接用正则进行全文转义,但怎么说也不比上quote函数安全。
    使用apply对传参进行优化。indexOf判定优化。
    
    v9
    改回v6的形态,后端数据还是带@前缀才方便查看与调试
    对输出代码进行修正,去掉用户误写的逗号或分号
    
    v10
    helper机制
    filter机制
    新的构建算法
    
    

    我已经整成jQuery插件了,随便拿去用吧!点我下载

                ;;(function($){
                    // by 司徒正美
                    //http://www.cnblogs.com/rubylouvre/archive/2012/08/06/2624970.html
                    /**
                     * 入口函数
                     * @param {string} id CSS表达式(用于模取元素然后取得里面的innerHTML作为源码)
                     * @param {Object} data  数据包
                     * @return  {Object} opts 可选参数,可以自由制定你的定界符
                     */
                    $.ejs = function( id,data,opts){
                        var el, source
                        if( !$.ejs.cache[ id] ){
                            opts = opts || {}
                            var doc = opts.doc || document;
                            data = data || {};
                            el = $(id, doc)[0];
                            if(! el )
                                throw "can not find the target element";
                            source = el.innerHTML;
                            if(!(/script|textarea/i.test(el.tagName))){
                                source = $.ejs.filters.unescape( source );
                            }
                            var fn = $.ejs.compile( source, opts );
                            $.ejs.cache[ id ] = fn;
                        }
                        return $.ejs.cache[ id ]( data );
                    }
                    var isNodejs = typeof exports == "object";
                    $.ejs.cache = {};
                    $.ejs.filters = {
                        //自己可以在这里添加更多过滤器,或者可以到这里面自由提取你喜欢的工具函数
                        //https://github.com/RubyLouvre/newland/blob/master/system/lang.js
                        escape:  function (target) {
                            return target.replace(/&/g,'&amp;')
                            .replace(/</g,'&lt;')
                            .replace(/>/g,'&gt;')
                            .replace(/"/g, """)
                            .replace(/'/g, "'");
                        },
                        unescape: function(target){
                            return  target.replace(/"/g,'"')
                            .replace(/</g,'<')
                            .replace(/>/g,'>')
                            .replace(/&/g, "&"); //处理转义的中文和实体字符
                            return target.replace(/&#([\d]+);/g, function($0, $1){
                                return String.fromCharCode(parseInt($1, 10));
                            });
                        }
                    };
                    $.ejs.compile = function( source, opts){
                        opts = opts || {}
                        var open  = opts.open  || isNodejs ? "<%" : "<&";
                        var close = opts.close || isNodejs ? "%>" : "&>";
                        var helperNames = [], helpers = []
                        for(var name in opts){
                            if(opts.hasOwnProperty(name) && typeof opts[name] == "function"){
                                helperNames.push(name)
                                helpers.push( opts[name] )
                            }
                        }
                        var flag = true;//判定是否位于前定界符的左边
                        var codes = []; //用于放置源码模板中普通文本片断
                        var time = new Date * 1;// 时间截,用于构建codes数组的引用变量
                        var prefix = " ;r += txt"+ time +"[" //渲染函数输出部分的前面
                        var postfix = "];"//渲染函数输出部分的后面
                        var t = "return function(data){ try{var r = '',line"+time+" = 0;";//渲染函数的最开始部分
                        var rAt = /(^|[^\w\u00c0-\uFFFF_])(@)(?=\w)/g;
                        var rstr = /(['"])(?:\\[\s\S]|[^\ \\r\n])*?\1/g 
                        var rtrim = /(^-|-$)/g;
                        var rmass = /mass/
                        var js = []
                        var pre = 0, cur, code, trim
                        for(var i = 0, n = source.length; i < n; ){
                            cur = source.indexOf( flag ? open : close, i);
                            if( cur < pre){
                                if( flag ){//取得最末尾的HTML片断
                                   t += prefix + codes.length + postfix
                                   if(cur == -1 && i == 0){
                                       code = source
                                   }else{
                                      code = source.slice( pre+ close.length );
                                   }
                                    if(trim){
                                        code = $.trim(code)
                                        trim = false;
                                    }
                                    codes.push( code );
                                }else{
                                    $.error("发生错误了");
                                }
                                break;
                            }
                            code = source.slice(i, cur );//截取前后定界符之间的片断
                            pre = cur;
                            if( flag ){//取得HTML片断
                                t += prefix + codes.length + postfix;
                                if(trim){
                                    code = $.trim(code);
                                    trim = false;
                                }
                                codes.push( code );
                                i = cur + open.length;
                            }else{//取得javascript罗辑
                                js.push(code)
                                t += ";line"+time+"=" +js.length+";"
                                switch(code.charAt(0)){
                                    case "="://直接输出
                                        code = code.replace(rtrim,function(){
                                            trim = true;
                                            return ""
                                        });
                                        code = code.replace(rAt,"$1data.");
                                        if( code.indexOf("|") > 1 ){//使用过滤器
                                            var arr = []
                                            var str = code.replace(rstr, function(str){
                                                arr.push(str);//先收拾所有字符串字面量
                                                return 'mass'
                                            }).replace(/\|\|/g,"@");//再收拾所有短路或
                                            if(str.indexOf("|") > 1){
                                                var segments = str.split("|")
                                                var filtered = segments.shift().replace(/\@/g,"||").replace(rmass, function(){
                                                    return arr.shift();
                                                });
                                                for( var filter;filter = arr.shift();){
                                                    segments = filter.split(":");
                                                    name = segments[0];
                                                    args = "";
                                                    if(segments[1]){
                                                        args = ', ' + segments[1].replace(rmass, function(){
                                                            return arr.shift();//还原
                                                        })
                                                    }
                                                    filtered = "$.ejs.filters."+ name +"(" +filtered + args+")"
                                                }
                                                code = "="+ filtered
                                            }
                                        }
                                        t += " ;r +" +code +";"
                                        break;
                                    case "#"://注释,不输出
                                        break
                                    case "-":
                                    default://普通逻辑,不输出
                                        code = code.replace(rtrim,function(){
                                            trim = true;
                                            return ""
                                        });
                                        t += code.replace(rAt,"$1data.")
                                        break
                                }
                                i = cur + close.length;
                            }
                            flag = !flag;
                        }
                        t += " return r; }catch(e){ $.log(e);\n$.log(js"+time+"[line"+time+"-1]) }}"
                        var body = ["txt"+time,"js"+time, "filters"]
                        var fn = Function.apply(Function, body.concat(helperNames,t) );
                        var args = [codes, js, $.ejs.filters];
                        //console.log(fn+"")
                        return fn.apply(this, args.concat(helpers));
                    }
                    return $.ejs;
                })(jQuery)
    

    <script type="tmpl" id="table_tmpl">
            <&= title() &>
            <table border=1>
            <&- for(var i=0,tl = @trs.length,tr;i<tl;i++){  -&>
                <&- tr = @trs[i]; -&>
                <tr>
                <td><&= tr.name;; &></td> <td><&= tr.age; &></td> <td><&= tr.sex || "男" &></td>
                </tr>
                <& } &>
            </table>
            <&# 怎么可能不支持图片 &>
            <img src="<&= @href &>">
    </script>
    <script>
              
        $(function(){
            var trs = [
                {name:"隐形杀手",age:29,sex:"男"},
                {name:"索拉",age:22,sex:"男"},
                {name:"fesyo",age:23,sex:"女"},
                {name:"恋妖壶",age:18,sex:"男"},
                {name:"竜崎",age:25,sex:"男"},
                {name:"你不懂的",age:30,sex:"女"}
            ]
    
            var html = $.ejs("#table_tmpl",{
                trs: trs,
                href: "http://images.cnblogs.com/cnblogs_com/rubylouvre/202906/o_type4.jpg"
            },{
                title: function(){
                    return "<p>这是使用视图helper输出的代码片断</p>"
                }
                       
            });
            $("#ejsv10").html(html)
    
        })
    </script>
    
    
    输出结果如下:

    这是我模板引擎生成的函数,里面看不到要转义的字符串!

  • 相关阅读:
    软件测试homework2
    软件测试homework1
    ubuntu14 安装QUME+xv6
    判断两线段是否相交 模板
    并查集 HDU1558
    并查集 HDU1272
    并查集 HDU1232
    数据结构之单向链表 UVa11988
    Java大数 字符串处理 HDU2100
    Java大数中的小数 HDU1753
  • 原文地址:https://www.cnblogs.com/rubylouvre/p/2624970.html
Copyright © 2020-2023  润新知