Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / docs / source / XTemplate.html
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-XTemplate'>/**
19 </span> * A template class that supports advanced functionality like:
20  *
21  * - Autofilling arrays using templates and sub-templates
22  * - Conditional processing with basic comparison operators
23  * - Basic math function support
24  * - Execute arbitrary inline code with special built-in template variables
25  * - Custom member functions
26  * - Many special tags and built-in operators that aren't defined as part of the API, but are supported in the templates that can be created
27  *
28  * XTemplate provides the templating mechanism built into:
29  *
30  * - {@link Ext.view.View}
31  *
32  * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
33  * demonstrate all of the supported features.
34  *
35  * # Sample Data
36  *
37  * This is the data object used for reference in each code example:
38  *
39  *     var data = {
40  *         name: 'Tommy Maintz',
41  *         title: 'Lead Developer',
42  *         company: 'Sencha Inc.',
43  *         email: 'tommy@sencha.com',
44  *         address: '5 Cups Drive',
45  *         city: 'Palo Alto',
46  *         state: 'CA',
47  *         zip: '44102',
48  *         drinks: ['Coffee', 'Soda', 'Water'],
49  *         kids: [
50  *             {
51  *                 name: 'Joshua',
52  *                 age:3
53  *             },
54  *             {
55  *                 name: 'Matthew',
56  *                 age:2
57  *             },
58  *             {
59  *                 name: 'Solomon',
60  *                 age:0
61  *             }
62  *         ]
63  *     };
64  *
65  * # Auto filling of arrays
66  *
67  * The **tpl** tag and the **for** operator are used to process the provided data object:
68  *
69  * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
70  *   tag for each item in the array.
71  * - If for=&quot;.&quot; is specified, the data object provided is examined.
72  * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
73  *
74  * Examples:
75  *
76  *     &lt;tpl for=&quot;.&quot;&gt;...&lt;/tpl&gt;       // loop through array at root node
77  *     &lt;tpl for=&quot;foo&quot;&gt;...&lt;/tpl&gt;     // loop through array at foo node
78  *     &lt;tpl for=&quot;foo.bar&quot;&gt;...&lt;/tpl&gt; // loop through array at foo.bar node
79  *
80  * Using the sample data above:
81  *
82  *     var tpl = new Ext.XTemplate(
83  *         '&lt;p&gt;Kids: ',
84  *         '&lt;tpl for=&quot;.&quot;&gt;',       // process the data.kids node
85  *             '&lt;p&gt;{#}. {name}&lt;/p&gt;',  // use current array index to autonumber
86  *         '&lt;/tpl&gt;&lt;/p&gt;'
87  *     );
88  *     tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
89  *
90  * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
91  * object to populate the template:
92  *
93  *     var tpl = new Ext.XTemplate(
94  *         '&lt;p&gt;Name: {name}&lt;/p&gt;',
95  *         '&lt;p&gt;Title: {title}&lt;/p&gt;',
96  *         '&lt;p&gt;Company: {company}&lt;/p&gt;',
97  *         '&lt;p&gt;Kids: ',
98  *         '&lt;tpl for=&quot;kids&quot;&gt;',     // interrogate the kids property within the data
99  *             '&lt;p&gt;{name}&lt;/p&gt;',
100  *         '&lt;/tpl&gt;&lt;/p&gt;'
101  *     );
102  *     tpl.overwrite(panel.body, data);  // pass the root node of the data object
103  *
104  * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
105  * loop. This variable will represent the value of the array at the current index:
106  *
107  *     var tpl = new Ext.XTemplate(
108  *         '&lt;p&gt;{name}\'s favorite beverages:&lt;/p&gt;',
109  *         '&lt;tpl for=&quot;drinks&quot;&gt;',
110  *             '&lt;div&gt; - {.}&lt;/div&gt;',
111  *         '&lt;/tpl&gt;'
112  *     );
113  *     tpl.overwrite(panel.body, data);
114  *
115  * When processing a sub-template, for example while looping through a child array, you can access the parent object's
116  * members via the **parent** object:
117  *
118  *     var tpl = new Ext.XTemplate(
119  *         '&lt;p&gt;Name: {name}&lt;/p&gt;',
120  *         '&lt;p&gt;Kids: ',
121  *         '&lt;tpl for=&quot;kids&quot;&gt;',
122  *             '&lt;tpl if=&quot;age &amp;gt; 1&quot;&gt;',
123  *                 '&lt;p&gt;{name}&lt;/p&gt;',
124  *                 '&lt;p&gt;Dad: {parent.name}&lt;/p&gt;',
125  *             '&lt;/tpl&gt;',
126  *         '&lt;/tpl&gt;&lt;/p&gt;'
127  *     );
128  *     tpl.overwrite(panel.body, data);
129  *
130  * # Conditional processing with basic comparison operators
131  *
132  * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
133  * specific parts of the template. Notes:
134  *
135  * - Double quotes must be encoded if used within the conditional
136  * - There is no else operator -- if needed, two opposite if statements should be used.
137  *
138  * Examples:
139  *
140  *     &lt;tpl if=&quot;age &gt; 1 &amp;&amp; age &lt; 10&quot;&gt;Child&lt;/tpl&gt;
141  *     &lt;tpl if=&quot;age &gt;= 10 &amp;&amp; age &lt; 18&quot;&gt;Teenager&lt;/tpl&gt;
142  *     &lt;tpl if=&quot;this.isGirl(name)&quot;&gt;...&lt;/tpl&gt;
143  *     &lt;tpl if=&quot;id==\'download\'&quot;&gt;...&lt;/tpl&gt;
144  *     &lt;tpl if=&quot;needsIcon&quot;&gt;&lt;img src=&quot;{icon}&quot; class=&quot;{iconCls}&quot;/&gt;&lt;/tpl&gt;
145  *     // no good:
146  *     &lt;tpl if=&quot;name == &quot;Tommy&quot;&quot;&gt;Hello&lt;/tpl&gt;
147  *     // encode &quot; if it is part of the condition, e.g.
148  *     &lt;tpl if=&quot;name == &amp;quot;Tommy&amp;quot;&quot;&gt;Hello&lt;/tpl&gt;
149  *
150  * Using the sample data above:
151  *
152  *     var tpl = new Ext.XTemplate(
153  *         '&lt;p&gt;Name: {name}&lt;/p&gt;',
154  *         '&lt;p&gt;Kids: ',
155  *         '&lt;tpl for=&quot;kids&quot;&gt;',
156  *             '&lt;tpl if=&quot;age &amp;gt; 1&quot;&gt;',
157  *                 '&lt;p&gt;{name}&lt;/p&gt;',
158  *             '&lt;/tpl&gt;',
159  *         '&lt;/tpl&gt;&lt;/p&gt;'
160  *     );
161  *     tpl.overwrite(panel.body, data);
162  *
163  * # Basic math support
164  *
165  * The following basic math operators may be applied directly on numeric data values:
166  *
167  *     + - * /
168  *
169  * For example:
170  *
171  *     var tpl = new Ext.XTemplate(
172  *         '&lt;p&gt;Name: {name}&lt;/p&gt;',
173  *         '&lt;p&gt;Kids: ',
174  *         '&lt;tpl for=&quot;kids&quot;&gt;',
175  *             '&lt;tpl if=&quot;age &amp;gt; 1&quot;&gt;',  // &lt;-- Note that the &gt; is encoded
176  *                 '&lt;p&gt;{#}: {name}&lt;/p&gt;',  // &lt;-- Auto-number each item
177  *                 '&lt;p&gt;In 5 Years: {age+5}&lt;/p&gt;',  // &lt;-- Basic math
178  *                 '&lt;p&gt;Dad: {parent.name}&lt;/p&gt;',
179  *             '&lt;/tpl&gt;',
180  *         '&lt;/tpl&gt;&lt;/p&gt;'
181  *     );
182  *     tpl.overwrite(panel.body, data);
183  *
184  * # Execute arbitrary inline code with special built-in template variables
185  *
186  * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
187  * variables available in that code:
188  *
189  * - **values**: The values in the current scope. If you are using scope changing sub-templates,
190  *   you can change what values is.
191  * - **parent**: The scope (values) of the ancestor template.
192  * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
193  * - **xcount**: If you are in a looping template, the total length of the array you are looping.
194  *
195  * This example demonstrates basic row striping using an inline code block and the xindex variable:
196  *
197  *     var tpl = new Ext.XTemplate(
198  *         '&lt;p&gt;Name: {name}&lt;/p&gt;',
199  *         '&lt;p&gt;Company: {[values.company.toUpperCase() + &quot;, &quot; + values.title]}&lt;/p&gt;',
200  *         '&lt;p&gt;Kids: ',
201  *         '&lt;tpl for=&quot;kids&quot;&gt;',
202  *             '&lt;div class=&quot;{[xindex % 2 === 0 ? &quot;even&quot; : &quot;odd&quot;]}&quot;&gt;',
203  *             '{name}',
204  *             '&lt;/div&gt;',
205  *         '&lt;/tpl&gt;&lt;/p&gt;'
206  *      );
207  *     tpl.overwrite(panel.body, data);
208  *
209  * # Template member functions
210  *
211  * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
212  * more complex processing:
213  *
214  *     var tpl = new Ext.XTemplate(
215  *         '&lt;p&gt;Name: {name}&lt;/p&gt;',
216  *         '&lt;p&gt;Kids: ',
217  *         '&lt;tpl for=&quot;kids&quot;&gt;',
218  *             '&lt;tpl if=&quot;this.isGirl(name)&quot;&gt;',
219  *                 '&lt;p&gt;Girl: {name} - {age}&lt;/p&gt;',
220  *             '&lt;/tpl&gt;',
221  *              // use opposite if statement to simulate 'else' processing:
222  *             '&lt;tpl if=&quot;this.isGirl(name) == false&quot;&gt;',
223  *                 '&lt;p&gt;Boy: {name} - {age}&lt;/p&gt;',
224  *             '&lt;/tpl&gt;',
225  *             '&lt;tpl if=&quot;this.isBaby(age)&quot;&gt;',
226  *                 '&lt;p&gt;{name} is a baby!&lt;/p&gt;',
227  *             '&lt;/tpl&gt;',
228  *         '&lt;/tpl&gt;&lt;/p&gt;',
229  *         {
230  *             // XTemplate configuration:
231  *             disableFormats: true,
232  *             // member functions:
233  *             isGirl: function(name){
234  *                return name == 'Sara Grace';
235  *             },
236  *             isBaby: function(age){
237  *                return age &lt; 1;
238  *             }
239  *         }
240  *     );
241  *     tpl.overwrite(panel.body, data);
242  */
243 Ext.define('Ext.XTemplate', {
244
245     /* Begin Definitions */
246
247     extend: 'Ext.Template',
248
249     /* End Definitions */
250
251     argsRe: /&lt;tpl\b[^&gt;]*&gt;((?:(?=([^&lt;]+))\2|&lt;(?!tpl\b[^&gt;]*&gt;))*?)&lt;\/tpl&gt;/,
252     nameRe: /^&lt;tpl\b[^&gt;]*?for=&quot;(.*?)&quot;/,
253     ifRe: /^&lt;tpl\b[^&gt;]*?if=&quot;(.*?)&quot;/,
254     execRe: /^&lt;tpl\b[^&gt;]*?exec=&quot;(.*?)&quot;/,
255     constructor: function() {
256         this.callParent(arguments);
257
258         var me = this,
259             html = me.html,
260             argsRe = me.argsRe,
261             nameRe = me.nameRe,
262             ifRe = me.ifRe,
263             execRe = me.execRe,
264             id = 0,
265             tpls = [],
266             VALUES = 'values',
267             PARENT = 'parent',
268             XINDEX = 'xindex',
269             XCOUNT = 'xcount',
270             RETURN = 'return ',
271             WITHVALUES = 'with(values){ ',
272             m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
273
274         html = ['&lt;tpl&gt;', html, '&lt;/tpl&gt;'].join('');
275
276         while ((m = html.match(argsRe))) {
277             exp = null;
278             fn = null;
279             exec = null;
280             matchName = m[0].match(nameRe);
281             matchIf = m[0].match(ifRe);
282             matchExec = m[0].match(execRe);
283
284             exp = matchIf ? matchIf[1] : null;
285             if (exp) {
286                 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
287             }
288
289             exp = matchExec ? matchExec[1] : null;
290             if (exp) {
291                 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
292             }
293
294             name = matchName ? matchName[1] : null;
295             if (name) {
296                 if (name === '.') {
297                     name = VALUES;
298                 } else if (name === '..') {
299                     name = PARENT;
300                 }
301                 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
302             }
303
304             tpls.push({
305                 id: id,
306                 target: name,
307                 exec: exec,
308                 test: fn,
309                 body: m[1] || ''
310             });
311
312             html = html.replace(m[0], '{xtpl' + id + '}');
313             id = id + 1;
314         }
315
316         for (i = tpls.length - 1; i &gt;= 0; --i) {
317             me.compileTpl(tpls[i]);
318         }
319         me.master = tpls[tpls.length - 1];
320         me.tpls = tpls;
321     },
322
323     // @private
324     applySubTemplate: function(id, values, parent, xindex, xcount) {
325         var me = this, t = me.tpls[id];
326         return t.compiled.call(me, values, parent, xindex, xcount);
327     },
328
329 <span id='Ext-XTemplate-cfg-codeRe'>    /**
330 </span>     * @cfg {RegExp} codeRe
331      * The regular expression used to match code variables. Default: matches {[expression]}.
332      */
333     codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
334
335 <span id='Ext-XTemplate-cfg-compiled'>    /**
336 </span>     * @cfg {Boolean} compiled
337      * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
338      */
339
340     re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
341
342     // @private
343     compileTpl: function(tpl) {
344         var fm = Ext.util.Format,
345             me = this,
346             useFormat = me.disableFormats !== true,
347             body, bodyReturn, evaluatedFn;
348
349         function fn(m, name, format, args, math) {
350             var v;
351             // name is what is inside the {}
352             // Name begins with xtpl, use a Sub Template
353             if (name.substr(0, 4) == 'xtpl') {
354                 return &quot;',this.applySubTemplate(&quot; + name.substr(4) + &quot;, values, parent, xindex, xcount),'&quot;;
355             }
356             // name = &quot;.&quot; - Just use the values object.
357             if (name == '.') {
358                 // filter to not include arrays/objects/nulls
359                 v = 'Ext.Array.indexOf([&quot;string&quot;, &quot;number&quot;, &quot;boolean&quot;], typeof values) &gt; -1 || Ext.isDate(values) ? values : &quot;&quot;';
360             }
361
362             // name = &quot;#&quot; - Use the xindex
363             else if (name == '#') {
364                 v = 'xindex';
365             }
366             else if (name.substr(0, 7) == &quot;parent.&quot;) {
367                 v = name;
368             }
369             // name has a . in it - Use object literal notation, starting from values
370             else if (name.indexOf('.') != -1) {
371                 v = &quot;values.&quot; + name;
372             }
373
374             // name is a property of values
375             else {
376                 v = &quot;values['&quot; + name + &quot;']&quot;;
377             }
378             if (math) {
379                 v = '(' + v + math + ')';
380             }
381             if (format &amp;&amp; useFormat) {
382                 args = args ? ',' + args : &quot;&quot;;
383                 if (format.substr(0, 5) != &quot;this.&quot;) {
384                     format = &quot;fm.&quot; + format + '(';
385                 }
386                 else {
387                     format = 'this.' + format.substr(5) + '(';
388                 }
389             }
390             else {
391                 args = '';
392                 format = &quot;(&quot; + v + &quot; === undefined ? '' : &quot;;
393             }
394             return &quot;',&quot; + format + v + args + &quot;),'&quot;;
395         }
396
397         function codeFn(m, code) {
398             // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
399             return &quot;',(&quot; + code.replace(me.compileARe, &quot;'&quot;) + &quot;),'&quot;;
400         }
401
402         bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, &quot;\\'&quot;).replace(me.re, fn).replace(me.codeRe, codeFn);
403         body = &quot;evaluatedFn = function(values, parent, xindex, xcount){return ['&quot; + bodyReturn + &quot;'].join('');};&quot;;
404         eval(body);
405
406         tpl.compiled = function(values, parent, xindex, xcount) {
407             var vs,
408                 length,
409                 buffer,
410                 i;
411
412             if (tpl.test &amp;&amp; !tpl.test.call(me, values, parent, xindex, xcount)) {
413                 return '';
414             }
415
416             vs = tpl.target ? tpl.target.call(me, values, parent) : values;
417             if (!vs) {
418                return '';
419             }
420
421             parent = tpl.target ? values : parent;
422             if (tpl.target &amp;&amp; Ext.isArray(vs)) {
423                 buffer = [];
424                 length = vs.length;
425                 if (tpl.exec) {
426                     for (i = 0; i &lt; length; i++) {
427                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
428                         tpl.exec.call(me, vs[i], parent, i + 1, length);
429                     }
430                 } else {
431                     for (i = 0; i &lt; length; i++) {
432                         buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
433                     }
434                 }
435                 return buffer.join('');
436             }
437
438             if (tpl.exec) {
439                 tpl.exec.call(me, vs, parent, xindex, xcount);
440             }
441             return evaluatedFn.call(me, vs, parent, xindex, xcount);
442         };
443
444         return this;
445     },
446
447     // inherit docs from Ext.Template
448     applyTemplate: function(values) {
449         return this.master.compiled.call(this, values, {}, 1, 1);
450     },
451
452 <span id='Ext-XTemplate-method-compile'>    /**
453 </span>     * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
454      * @return {Ext.XTemplate} this
455      */
456     compile: function() {
457         return this;
458     }
459 }, function() {
460     // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
461     this.createAlias('apply', 'applyTemplate');
462 });
463 </pre>
464 </body>
465 </html>