Ext util.XTemplate分析

 Ext.Template完成了基本的插值功能,现在Ext.uitl.Xtemplate要完成指令功能。只有这样模板才是完整的。其实Ext.Template插值功能,也是差强人意的,比如没有实现点串(user.name)的插值,其实这个实现起来很简单。在ExtExt.uitl.Xtemplate中,实现点串功能,在{[]}可以使用内嵌的Js代码。实现在for,if,exec指令,

对于for指令,感觉有点不爽是采用命名映射的方式,很多时候我们只要数组中的一部分,比如:a[1..5]。我还要在for的指令内嵌if指令来判断,导致很麻烦。在freemarker就能直接用。而在Xtemplate中,for=”{[]}”这样的都解析不了。有点讲不过去。

 
对于if 指令,判断的条件肯定是js语句的表达式,比如: <tpl if="this.isGirl(name)">',但是不支持else,eles if 等指令。
但是总的来说,Ext.uitl.Xtemplate完成功能蛮强大的。

2.1Xtemplate的构建


上面我们把for,if 这样的操作称为指令。在Xtemplate换了一种称法。称为子模板。Ext.template实现了基本的插值功能。我们可以想像一下,在<tpl for=””></tpl>类似这样的操作中,标签中间的内容肯定都是插值操作,称为叶子模板。如果<tpl for=””></tpl>嵌套<tpl for=””></tpl>呢?那标签中间的内容就不是插值操作了,称为节点模板。

那么能不能把节点模板转化为叶子模板呢,这样一下,就可以重用Ext.Template的插值功能,我们可以把节点模板的子模板做字符变换,变成Template的插值的形式,例如:{xtpl1}。这样一来所的有子模板都可以转换成叶子模板,可以采用Ext.Template的插值操作。

不过上面还有一个问题没有考虑到,那就是每个子模板的标签中的内容,如何保存,如<tpl for=””></tpl>,for属性的内容。如何去知道是for指令,还是if指令的模板呢?这个问题我们可以在转换子模板为叶子模板时,把其相关的内容或信息存在数组中,然后分配一个Id,上面的{xtpl1}中的1就可以是id.这样的就可以找到每个模板的全部信息。
举个例子:

 

'<tpl><p>Name: {name}</p><p>Kids: 
<tpl for="kids"> 
<tpl if="age > 1"><p>{name}</p> <p>Dad: {parent.name}</p> 
</tpl> 
</tpl></p> 
</tpl>

 '


这是嵌套的模板,我们如何把它转换成上面所说的所有子模板都换成叶子模板:第一步,我们可以把最内部的叶子模板采用{xtpl1}代替。

 

'<tpl><p>Name: {name}</p><p>Kids: 
<tpl for="kids"> 
{xtpl1} 
</tpl></p> 
</tpl>' 

 


同时往已建立好一个数组中加{xtpl1}的信息,首先得存标签的内容吧:还有if的这个指令类别,还有age>1这个expr.
Xtp1的对象就应该如这样:{id:1,body: <p>{name}</p> <p>Dad: {parent.name},type:if params: ‘age > 1’};当然我们可以采用更好的方式。

接下第二步就是

 

'<tpl><p>Name: {name}</p><p>Kids: 
{xtpl2}</p> 
</tpl>' 
Xtpl2={id:2,body: {xtpl1},type:for, params: ‘kids’} 

 

再接下来:

'{xtpl3}’ 
Xtpl3={ id:3,body: p>Name: {name}</p><p>Kids: {xtpl2}</p>,type:’’, params: ’’}; 

 
这样就把嵌套的模板分成了可以采用Ext.Template操作的几个子模板。我们只要定位了最后一个模板,就可以通过解析改模板的插值来解决其嵌套的各种子模板。之后就采用递推的方式去解析所有子模板。我们这最后一个模板称为根模板。

那么又如何去实现按上面的遍历的顺序去遍历整个模板内容呢。通过regexp能很方便的实现,只在regexp中定义其中的内容不包括子模板就可以了。只要字符没有<tpl ..就可以了。找到了,把其相关的信息存在数组中,把其生成Id以插值的形式replace改子模板就可以了。之后一直循环,直到没有模板为止。最后一个模板肯定是根模板。

看一下Ext.util.Xtemplate是怎么实现的?

 

//找到叶子模板,把模板中的内容存在$1中。 
var re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/; 
//找到<tpl for=’name’)中name的值,存在$1中 
var nameRe = /^<tpl\b[^>]*?for="(.*?)"/; 
//找到<tpl if =’expr)中expr的值,存在$1中 
var ifRe = /^<tpl\b[^>]*?if="(.*?)"/; 
//找到<tpl if =’exec)中exec的值,存在$1中 
var execRe = /^<tpl\b[^>]*?exec="(.*?)"/; 

var m, id = 0; 
var tpls = []; 

//找到内面没有嵌套的<tpl></tpl>的子模板 
while(m = s.match(re)){ 
var m2 = m[0].match(nameRe); 
var m3 = m[0].match(ifRe); 
var m4 = m[0].match(execRe); 
var exp = null, fn = null, exec = null; 
//m2,m3,m4[1]保存是子匹配的子符串内容。 
var name = m2 && m2[1] ? m2[1] : ''; 
//支持if 指令的子模板 支持if 的判断的表达式操作。 
if(m3){ exp = m3 && m3[1] ? m3[1] : null; 
if(exp){ 
fn = new Function('values', 'parent', 'xindex', 'xcount', 'with(values){ return '+(Ext.util.Format.htmlDecode(exp))+'; }'); 
} 
} 
if(m4){ exp = m4 && m4[1] ? m4[1] : null; 
if(exp){ 
exec = new Function('values', 'parent', 'xindex', 'xcount', with(values){ '+(Ext.util.Format.htmlDecode(exp))+'; }'); 
} 
} 
//支持for 指令的子模板,不过命名只支持.,..,name,不支持表达式。 
if(name){ 
switch(name){ 
case '.': name = new Function('values', 'parent', 'with(values){ return values; }'); break; 
case '..': name = new Function('values', 'parent', 'with(values){ return parent; }'); break; 
default: name = new Function('values', 'parent', 'with(values){ return '+name+'; }'); 
} 
} 
//为每次找到子模板构建相关的信息,存在数组中,还是觉得这种把for,if,exec 
//这三种指令这样写不好,不如用如上面的type呢。 
tpls.push({ 
id: id, 
target: name, 
exec: exec, 
test: fn, 
//m[1]指<tpl></tpl>中间的所有内容。 
body: m[1]||'' 
}); 
//把子模板的内容replace成插值的形式。 
s = s.replace(m[0], '{xtpl'+ id + '}'); 
++id; 
} 

 

2.2compileTpl (tpl)


上面的构建方法中转换就是为了能利用Ext.template的形式或思想来处理插值。Ext.Template为模板编译了complied的方法,Xtemplate的子模板也应该要有每个自己相对应的compiled()方法。因为模板的内容不同,所以方法就不一样了。之后要apply某一个子模板,只在调用其compiled()方法就可以了。

Xtemplate的对插值的解析和template对插值的解析差不多。多了几个功能,不过是多了几个判断的语句。现在一个排在面前的问题是如何解析如{xtpl1}这样的插值。解决这个就解决了模板的嵌套问题。{xtpl}的位置用什么来替换好呢?我们要的是在执行改模板时,就自动执行该模板的子模板,并把相对应的值传到子模板中去。如果子模板还有子模板的话,执行子模板时,又会自动执行子模板的子模板。也就是只要调用根模板的complied(xx,yy)就可以自动执行所有的模板。

这也就是要求在compile模板的过程,得到执行子模板的函数写入取代{xtpl1}的插值的地方。只有这样,在调用只要调用根模板的complied(xx,yy)就可以自动执行所有的子模板了。

 
其形式如下:

 

tpl3: tpl.compiled = function(values, parent, xindex, xcount){ 
return ['<p>Name: ',values['name']===undefined?''values['name'],'</p> <p>Kids: ', this.applySubTemplate(2, values, parent, xindex, xcount),'' ].join('');}; 


tpl2: tpl.compiled = function(values, parent, xindex, xcount){ 
return ['',this.applySubTemplate(1, values, parent, xindex, xcount),''].join('');}; 


tpl1: tpl.compiled = function(values, parent, xindex, xcount){ 
return ['<p>',values['name']===undefined?''values['name'],'</p> <p>Dad:' ,parent.name===undefined?''parent.name,'</p>'].join('');}; 

 

tpl3是根模板,其执行applySubTemplate(2, values, parent, xindex, xcount),'' ],这是tpl2模板,tpl2模板执行this.applySubTemplate(1, values, parent, xindex, xcount)的tpl1模板。

接下仔细分析xtemplate怎么处理插值的,与Template有什么不同。
Xtemplate插值的格式为

 

{name[:][format][(params)][math expr]}

 

可以看得出来,插值操作多了一个math expr的功能,如{age+5}。对于name,

regexp为([\w-\.\#]+),比template多了.,#。

 

.表达当前的Values,#表示当前的行号。如是parent.name责可以表明支持点串。


\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g

 

是分析{}的regexp.
Xtemplate还支持inline的Js 语句,其格式为:


{[js expr]}如:class="{[xindex % 2 === 0 ? "even" : "odd"]}。

compileTpl : function(tpl){ 
var fm = Ext.util.Format; 
var useF = this.disableFormats !== true; 
var sep = Ext.isGecko ? "+" : ","; 
var fn = function(m, name, format, args, math){ 
//该模板中的子模板 
if(name.substr(0, 4) == 'xtpl'){ 
//',this.applySubTemplate(id, values, parent, xindex, xcount),' 
return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent, xindex, xcount)'+sep+"'"; 
} 
var v; 
if(name === '.'){ 
//values仅仅是当前模板中 
v = 'values'; 
}else if(name === '#'){ 
v = 'xindex'; 
}else if(name.indexOf('.') != -1){ 
//支持点串,如parent.name 
v = name; 
}else{ 
v = "values['" + name + "']"; 
} 

if(math){ 
//支持变量的处理, 如:{age+5} 
v = '(' + v + math + ')'; 
} 
if(format && useF){ 
args = args ? ',' + args : ""; 
if(format.substr(0, 5) != "this."){ 
//fm.xxf( 
format = "fm." + format + '('; 
}else{ 
//this.call("isgirl", 
format = 'this.call("'+ format.substr(5) + '", '; 
args = ", values"; 
} 
}else{ 
args= ''; format = "("+v+" === undefined ? '' : "; 
} 

//',fm.xxformat(values['namex']||values||xindex||math Expression,xx,yy),' 
//',this.call('isGirl',values['namex']||values||xindex||math Expression,values),' 
//',(values['namex']||values||xindex||math Expression===undefined?'':values['namex']||values||xindex||math Expression]),' 

return "'"+ sep + format + v + args + ")"+sep+"'"; 
}; 
var codeFn = function(m, code){ 
//',(xx.bb),' 
return "'"+ sep +'('+code+')'+sep+"'"; 
}; 

var body; 
// branched to use + in gecko and [].join() in others 
if(Ext.isGecko){ 
body = "tpl.compiled = function(values, parent, xindex, xcount){ return '" + 
tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn) + 
"';};"; 
}else{ 
//该函数是每个模板中的。 

/* 
* '<tpl><p>Name: {name}</p><p>Kids: 
* <tpl for="kids"> 
* <tpl if="age > 1"><p>{name}</p> <p>Dad: {parent.name}</p> 
* </tpl> 
* </tpl></p> 
* </tpl>' 

tpl1:body:'<p>{name}</p> <p>Dad: {parent.name}</p>', test=age > 1 ids=1 

* '<tpl><p>Name: {name}</p><p>Kids: 
* <tpl for="kids"> 
* {xtpl1} 
* </tpl></p> 
* </tpl>' 

tpl2:body:{xtpl1},name= kids id=2 

* '<tpl><p>Name: {name}</p><p>Kids: {xtpl2}</p> 
* </tpl>' 

tpl3 body= '<p>Name: {name}</p> <p>Kids: {xtpl2}</p>' id=3 

* '{xtpl3}' 

tpl3: tpl.compiled = function(values, parent, xindex, xcount){ 
return ['<p>Name: ',values['name']===undefined?''values['name'],'</p> <p>Kids: ', 
this.applySubTemplate(2, values, parent, xindex, xcount),'' ].join('');}; 

tpl2: tpl.compiled = function(values, parent, xindex, xcount){ 
return ['',this.applySubTemplate(1, values, parent, xindex, xcount),''].join('');}; 

tpl1: tpl.compiled = function(values, parent, xindex, xcount){ 
return ['<p>',values['name']===undefined?''values['name'],'</p> <p>Dad:' 
,parent.name===undefined?''parent.name,'</p>'].join('');}; 

* */ 
body = ["tpl.compiled = function(values, parent, xindex, xcount){ return ['"]; 
body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn)); 
body.push("'].join('');};"); 
body = body.join(''); 
} 
eval(body); 

/* tpl.compiled = function(values, parent, xindex, xcount){ 
return ['',this.applySubTemplate(3, values, parent, xindex, xcount),''].join('');}; 

*/ 
return this; 
}, 

 
2.3applySubTemplate


对于applySubTemplate,我们只要理解一个问题,就是parent,child的关系。对于if,exec指令是不改变value,parent的。因为子模板是通过递规的形式把values, parent, xindex, xcount传入到子模板的子孙。在这种递规是通过调用applySubTemplate实现,values,parent在传递过程就可以改变了。

只有for指令的模板会改变values, parent的值的。对于for 指令的模板,其所有的子模板的parent等于for模板的的values.for 数组之上的values.the模板内容及子模板的values都是等于for的list数组中的当前项。

看起来很方便,其实反而不好理解和使用。还不如使用 freemaker模板的<#list users as u> 通过u的引用的得到数组的当前项。

//处理values,parent,等参数的传递。for子模板把分成父子模板关系了 
applySubTemplate : function(id, values, parent, xindex, xcount){ 
var t = this.tpls[id]; 
//if 中return false,就不处理该if子模板了。 
if(t.test && !t.test.call(this, values, parent, xindex, xcount)){ 
return ''; 
} 
if(t.exec && t.exec.call(this, values, parent, xindex, xcount)){ 
return ''; 
} 
// t.target就是 for的name. 
var vs = (t.target ? t.target.call(this, values, parent) : values); 

//对于for的子模板,parent就指向 for 的 values[]. 
parent =( t.target ? values : parent); 
if(t.target && Ext.isArray(vs)){ 
//对于for loop,loop all element,and join to str . 
var buf = []; 
for(var i = 0, len = vs.length; i < len; i++){ 
buf[buf.length] = t.compiled.call(this, vs[i], parent, i+1, len); 
} 
return buf.join(''); 
} 
return t.compiled.call(this, vs, parent, xindex, xcount); 
}, 

 

                                                                                     prk       2008-07-28

你可能感兴趣的:(JavaScript,freemarker,ext,idea)