最近一直攻略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,'&') .replace(/</g,'<') .replace(/>/g,'>') .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>
這是我模板引擎生成的函數,里面看不到要轉義的字符串!
