/ext-4.1.0_b3/docs/source/XTemplateCompiler.html
HTML | 376 lines | 316 code | 60 blank | 0 comment | 0 complexity | f11290d9525b8b268f6148a16ae20107 MD5 | raw file
1<!DOCTYPE html>
2<html>
3<head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <title>The source code</title>
6 <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
7 <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
8 <style type="text/css">
9 .highlight { display: block; background-color: #ddd; }
10 </style>
11 <script type="text/javascript">
12 function highlight() {
13 document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
14 }
15 </script>
16</head>
17<body onload="prettyPrint(); highlight();">
18 <pre class="prettyprint lang-js"><span id='Ext-XTemplateCompiler'>/**
19</span> * This class compiles the XTemplate syntax into a function object. The function is used
20 * like so:
21 *
22 * function (out, values, parent, xindex, xcount) {
23 * // out is the output array to store results
24 * // values, parent, xindex and xcount have their historical meaning
25 * }
26 *
27 * @markdown
28 * @private
29 */
30Ext.define('Ext.XTemplateCompiler', {
31 extend: 'Ext.XTemplateParser',
32
33 // Chrome really likes "new Function" to realize the code block (as in it is
34 // 2x-3x faster to call it than using eval), but Firefox chokes on it badly.
35 // IE and Opera are also fine with the "new Function" technique.
36 useEval: Ext.isGecko,
37
38 // See http://jsperf.com/nige-array-append for quickest way to append to an array of unknown length
39 // (Due to arbitrary code execution inside a template, we cannot easily track the length in var)
40 // On IE6 and 7 myArray[myArray.length]='foo' is better. On other browsers myArray.push('foo') is better.
41 useIndex: Ext.isIE6 || Ext.isIE7,
42
43 useFormat: true,
44
45 propNameRe: /^[\w\d\$]*$/,
46
47 compile: function (tpl) {
48 var me = this,
49 code = me.generate(tpl);
50
51 // When using "new Function", we have to pass our "Ext" variable to it in order to
52 // support sandboxing. If we did not, the generated function would use the global
53 // "Ext", not the "Ext" from our sandbox (scope chain).
54 //
55 return me.useEval ? me.evalTpl(code) : (new Function('Ext', code))(Ext);
56 },
57
58 generate: function (tpl) {
59 var me = this;
60
61 me.body = [
62 'var c0=values, a0 = Ext.isArray(c0), p0=parent, n0=xcount, i0=xindex, v;\n'
63 ];
64 me.funcs = [
65 // note: Ext here is properly sandboxed
66 'var fm=Ext.util.Format;'
67 ];
68 me.switches = [];
69
70 me.parse(tpl);
71
72 me.funcs.push(
73 (me.useEval ? '$=' : 'return') + ' function (' + me.fnArgs + ') {',
74 me.body.join(''),
75 '}'
76 );
77
78 var code = me.funcs.join('\n');
79
80 return code;
81 },
82
83 //-----------------------------------
84 // XTemplateParser callouts
85
86 doText: function (text) {
87 var me = this,
88 out = me.body;
89
90 text = text.replace(me.aposRe, "\\'").replace(me.newLineRe, '\\n');
91 if (me.useIndex) {
92 out.push('out[out.length]=\'', text, '\'\n');
93 } else {
94 out.push('out.push(\'', text, '\')\n');
95 }
96 },
97
98 doExpr: function (expr) {
99 var out = this.body;
100 out.push('if ((v=' + expr + ')!==undefined) out');
101 if (this.useIndex) {
102 out.push('[out.length]=String(v)\n');
103 } else {
104 out.push('.push(String(v))\n');
105 }
106 },
107
108 doTag: function (tag) {
109 this.doExpr(this.parseTag(tag));
110 },
111
112 doElse: function () {
113 this.body.push('} else {\n');
114 },
115
116 doEval: function (text) {
117 this.body.push(text, '\n');
118 },
119
120 doIf: function (action, actions) {
121 var me = this;
122
123 // If it's just a propName, use it directly in the if
124 if (me.propNameRe.test(action)) {
125 me.body.push('if (', me.parseTag(action), ') {\n');
126 }
127 // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
128 else {
129 me.body.push('if (', me.addFn(action), me.callFn, ') {\n');
130 }
131 if (actions.exec) {
132 me.doExec(actions.exec);
133 }
134 },
135
136 doElseIf: function (action, actions) {
137 var me = this;
138
139 // If it's just a propName, use it directly in the else if
140 if (me.propNameRe.test(action)) {
141 me.body.push('} else if (', me.parseTag(action), ') {\n');
142 }
143 // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
144 else {
145 me.body.push('} else if (', me.addFn(action), me.callFn, ') {\n');
146 }
147 if (actions.exec) {
148 me.doExec(actions.exec);
149 }
150 },
151
152 doSwitch: function (action) {
153 var me = this;
154
155 // If it's just a propName, use it directly in the switch
156 if (me.propNameRe.test(action)) {
157 me.body.push('switch (', me.parseTag(action), ') {\n');
158 }
159 // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values)
160 else {
161 me.body.push('switch (', me.addFn(action), me.callFn, ') {\n');
162 }
163 me.switches.push(0);
164 },
165
166 doCase: function (action) {
167 var me = this,
168 cases = Ext.isArray(action) ? action : [action],
169 n = me.switches.length - 1,
170 match, i;
171
172 if (me.switches[n]) {
173 me.body.push('break;\n');
174 } else {
175 me.switches[n]++;
176 }
177
178 for (i = 0, n = cases.length; i < n; ++i) {
179 match = me.intRe.exec(cases[i]);
180 cases[i] = match ? match[1] : ("'" + cases[i].replace(me.aposRe,"\\'") + "'");
181 }
182
183 me.body.push('case ', cases.join(': case '), ':\n');
184 },
185
186 doDefault: function () {
187 var me = this,
188 n = me.switches.length - 1;
189
190 if (me.switches[n]) {
191 me.body.push('break;\n');
192 } else {
193 me.switches[n]++;
194 }
195
196 me.body.push('default:\n');
197 },
198
199 doEnd: function (type, actions) {
200 var me = this,
201 L = me.level-1;
202
203 if (type == 'for') {
204 /*
205 To exit a for loop we must restore the outer loop's context. The code looks
206 like this (which goes with that produced by doFor:
207
208 for (...) { // the part generated by doFor
209 ... // the body of the for loop
210
211 // ... any tpl for exec statement goes here...
212 }
213 parent = p1;
214 values = r2;
215 xcount = n1;
216 xindex = i1
217 */
218 if (actions.exec) {
219 me.doExec(actions.exec);
220 }
221
222 me.body.push('}\n');
223 me.body.push('parent=p',L,';values=r',L+1,';xcount=n',L,';xindex=i',L,'\n');
224 } else if (type == 'if' || type == 'switch') {
225 me.body.push('}\n');
226 }
227 },
228
229 doFor: function (action, actions) {
230 var me = this,
231 s = me.addFn(action),
232 L = me.level,
233 up = L-1;
234
235 /*
236 We are trying to produce a block of code that looks like below. We use the nesting
237 level to uniquely name the control variables.
238
239 var c2 = f5.call(this, out, values, parent, xindex, xcount),
240 // c2 is the context object for the for loop
241 a2 = Ext.isArray(c2),
242 // a2 is the isArray result for the context
243 p2 = c1,
244 // p2 is the parent context (of the outer for loop)
245 r2 = values
246 // r2 is the values object to
247
248 parent = a1 ? c1[i1] : p2 // set parent
249 // i2 is the loop index and n2 is the number (xcount) of this for loop
250 for (var i2 = 0, n2 = a2 ? c2.length : (c2 ? 1 : 0), xcount = n2; i2 < n2; ++i2) {
251 values = a2 ? c2[i2] : c2 // adjust special vars to inner scope
252 xindex = i2 + 1 // xindex is 1-based
253
254 The body of the loop is whatever comes between the tpl and /tpl statements (which
255 is handled by doEnd).
256 */
257
258 me.body.push('var c',L,'=',s,me.callFn,', a',L,'=Ext.isArray(c',L,'), p',L,'=c',up,',r',L,'=values\n',
259 'parent=a',up,'?c',up,'[i',up,']:p',L,'\n',
260 'for (var i',L,'=0,n',L,'=a',L,'?c',L,'.length:(c',L,'?1:0), xcount=n',L,';i',L,'<n'+L+';++i',L,'){\n',
261 'values=a',L,'?c',L,'[i',L,']:c',L,'\n',
262 'xindex=i',L,'+1\n');
263 },
264
265 doExec: function (action, actions) {
266 var me = this,
267 name = 'f' + me.funcs.length;
268
269 me.funcs.push('function ' + name + '(' + me.fnArgs + ') {',
270 ' try { with(values) {',
271 ' ' + action,
272 ' }} catch(e) {}',
273 '}');
274
275 me.body.push(name + me.callFn + '\n');
276 },
277
278 //-----------------------------------
279 // Internal
280
281 addFn: function (body) {
282 var me = this,
283 name = 'f' + me.funcs.length;
284
285 if (body === '.') {
286 me.funcs.push('function ' + name + '(' + me.fnArgs + ') {',
287 ' return values',
288 '}');
289 } else if (body === '..') {
290 me.funcs.push('function ' + name + '(' + me.fnArgs + ') {',
291 ' return parent',
292 '}');
293 } else {
294 me.funcs.push('function ' + name + '(' + me.fnArgs + ') {',
295 ' try { with(values) {',
296 ' return(' + body + ')',
297 ' }} catch(e) {}',
298 '}');
299 }
300
301 return name;
302 },
303
304 parseTag: function (tag) {
305 var m = this.tagRe.exec(tag),
306 name = m[1],
307 format = m[2],
308 args = m[3],
309 math = m[4],
310 v;
311
312 // name = "." - Just use the values object.
313 if (name == '.') {
314 // filter to not include arrays/objects/nulls
315 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
316 }
317 // name = "#" - Use the xindex
318 else if (name == '#') {
319 v = 'xindex';
320 }
321 else if (name.substr(0, 7) == "parent.") {
322 v = name;
323 }
324 // compound Javascript property name (e.g., "foo.bar")
325 else if (isNaN(name) && name.indexOf('-') == -1 && name.indexOf('.') != -1) {
326 v = "values." + name;
327 }
328 // number or a '-' in it or a single word (maybe a keyword): use array notation
329 // (http://jsperf.com/string-property-access/4)
330 else {
331 v = "values['" + name + "']";
332 }
333
334 if (math) {
335 v = '(' + v + math + ')';
336 }
337
338 if (format && this.useFormat) {
339 args = args ? ',' + args : "";
340 if (format.substr(0, 5) != "this.") {
341 format = "fm." + format + '(';
342 } else {
343 format += '(';
344 }
345 } else {
346 return v;
347 }
348
349 return format + v + args + ')';
350 },
351
352 // @private
353 evalTpl: function ($) {
354
355 // We have to use eval to realize the code block and capture the inner func we also
356 // don't want a deep scope chain. We only do this in Firefox and it is also unhappy
357 // with eval containing a return statement, so instead we assign to "$" and return
358 // that. Because we use "eval", we are automatically sandboxed properly.
359 eval($);
360 return $;
361 },
362
363 newLineRe: /\r\n|\r|\n/g,
364 aposRe: /[']/g,
365 intRe: /^\s*(\d+)\s*$/,
366 tagRe: /([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?/
367
368}, function () {
369 var proto = this.prototype;
370
371 proto.fnArgs = 'out,values,parent,xindex,xcount';
372 proto.callFn = '.call(this,' + proto.fnArgs + ')';
373});
374</pre>
375</body>
376</html>