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