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