3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * A template class that supports advanced functionality like:
18 * - Autofilling arrays using templates and sub-templates
19 * - Conditional processing with basic comparison operators
20 * - Basic math function support
21 * - Execute arbitrary inline code with special built-in template variables
22 * - Custom member functions
23 * - 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
25 * XTemplate provides the templating mechanism built into:
27 * - {@link Ext.view.View}
29 * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples
30 * demonstrate all of the supported features.
34 * This is the data object used for reference in each code example:
37 * name: 'Tommy Maintz',
38 * title: 'Lead Developer',
39 * company: 'Sencha Inc.',
40 * email: 'tommy@sencha.com',
41 * address: '5 Cups Drive',
45 * drinks: ['Coffee', 'Soda', 'Water'],
62 * # Auto filling of arrays
64 * The **tpl** tag and the **for** operator are used to process the provided data object:
66 * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl
67 * tag for each item in the array.
68 * - If for="." is specified, the data object provided is examined.
69 * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0).
73 * <tpl for=".">...</tpl> // loop through array at root node
74 * <tpl for="foo">...</tpl> // loop through array at foo node
75 * <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node
77 * Using the sample data above:
79 * var tpl = new Ext.XTemplate(
81 * '<tpl for=".">', // process the data.kids node
82 * '<p>{#}. {name}</p>', // use current array index to autonumber
85 * tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
87 * An example illustrating how the **for** property can be leveraged to access specified members of the provided data
88 * object to populate the template:
90 * var tpl = new Ext.XTemplate(
91 * '<p>Name: {name}</p>',
92 * '<p>Title: {title}</p>',
93 * '<p>Company: {company}</p>',
95 * '<tpl for="kids">', // interrogate the kids property within the data
99 * tpl.overwrite(panel.body, data); // pass the root node of the data object
101 * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a
102 * loop. This variable will represent the value of the array at the current index:
104 * var tpl = new Ext.XTemplate(
105 * '<p>{name}\'s favorite beverages:</p>',
106 * '<tpl for="drinks">',
107 * '<div> - {.}</div>',
110 * tpl.overwrite(panel.body, data);
112 * When processing a sub-template, for example while looping through a child array, you can access the parent object's
113 * members via the **parent** object:
115 * var tpl = new Ext.XTemplate(
116 * '<p>Name: {name}</p>',
118 * '<tpl for="kids">',
119 * '<tpl if="age > 1">',
121 * '<p>Dad: {parent.name}</p>',
125 * tpl.overwrite(panel.body, data);
127 * # Conditional processing with basic comparison operators
129 * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render
130 * specific parts of the template. Notes:
132 * - Double quotes must be encoded if used within the conditional
133 * - There is no else operator -- if needed, two opposite if statements should be used.
137 * <tpl if="age > 1 && age < 10">Child</tpl>
138 * <tpl if="age >= 10 && age < 18">Teenager</tpl>
139 * <tpl if="this.isGirl(name)">...</tpl>
140 * <tpl if="id==\'download\'">...</tpl>
141 * <tpl if="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
143 * <tpl if="name == "Tommy"">Hello</tpl>
144 * // encode " if it is part of the condition, e.g.
145 * <tpl if="name == "Tommy"">Hello</tpl>
147 * Using the sample data above:
149 * var tpl = new Ext.XTemplate(
150 * '<p>Name: {name}</p>',
152 * '<tpl for="kids">',
153 * '<tpl if="age > 1">',
158 * tpl.overwrite(panel.body, data);
160 * # Basic math support
162 * The following basic math operators may be applied directly on numeric data values:
168 * var tpl = new Ext.XTemplate(
169 * '<p>Name: {name}</p>',
171 * '<tpl for="kids">',
172 * '<tpl if="age > 1">', // <-- Note that the > is encoded
173 * '<p>{#}: {name}</p>', // <-- Auto-number each item
174 * '<p>In 5 Years: {age+5}</p>', // <-- Basic math
175 * '<p>Dad: {parent.name}</p>',
179 * tpl.overwrite(panel.body, data);
181 * # Execute arbitrary inline code with special built-in template variables
183 * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. There are some special
184 * variables available in that code:
186 * - **values**: The values in the current scope. If you are using scope changing sub-templates,
187 * you can change what values is.
188 * - **parent**: The scope (values) of the ancestor template.
189 * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based).
190 * - **xcount**: If you are in a looping template, the total length of the array you are looping.
192 * This example demonstrates basic row striping using an inline code block and the xindex variable:
194 * var tpl = new Ext.XTemplate(
195 * '<p>Name: {name}</p>',
196 * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
198 * '<tpl for="kids">',
199 * '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
204 * tpl.overwrite(panel.body, data);
206 * # Template member functions
208 * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for
209 * more complex processing:
211 * var tpl = new Ext.XTemplate(
212 * '<p>Name: {name}</p>',
214 * '<tpl for="kids">',
215 * '<tpl if="this.isGirl(name)">',
216 * '<p>Girl: {name} - {age}</p>',
218 * // use opposite if statement to simulate 'else' processing:
219 * '<tpl if="this.isGirl(name) == false">',
220 * '<p>Boy: {name} - {age}</p>',
222 * '<tpl if="this.isBaby(age)">',
223 * '<p>{name} is a baby!</p>',
227 * // XTemplate configuration:
228 * disableFormats: true,
229 * // member functions:
230 * isGirl: function(name){
231 * return name == 'Sara Grace';
233 * isBaby: function(age){
238 * tpl.overwrite(panel.body, data);
240 Ext.define('Ext.XTemplate', {
242 /* Begin Definitions */
244 extend: 'Ext.Template',
246 /* End Definitions */
248 argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
249 nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
250 ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
251 execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
252 constructor: function() {
253 this.callParent(arguments);
268 WITHVALUES = 'with(values){ ',
269 m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
271 html = ['<tpl>', html, '</tpl>'].join('');
273 while ((m = html.match(argsRe))) {
277 matchName = m[0].match(nameRe);
278 matchIf = m[0].match(ifRe);
279 matchExec = m[0].match(execRe);
281 exp = matchIf ? matchIf[1] : null;
283 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
286 exp = matchExec ? matchExec[1] : null;
288 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
291 name = matchName ? matchName[1] : null;
295 } else if (name === '..') {
298 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
309 html = html.replace(m[0], '{xtpl' + id + '}');
313 for (i = tpls.length - 1; i >= 0; --i) {
314 me.compileTpl(tpls[i]);
316 me.master = tpls[tpls.length - 1];
321 applySubTemplate: function(id, values, parent, xindex, xcount) {
322 var me = this, t = me.tpls[id];
323 return t.compiled.call(me, values, parent, xindex, xcount);
327 * @cfg {RegExp} codeRe
328 * The regular expression used to match code variables. Default: matches {[expression]}.
330 codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
333 * @cfg {Boolean} compiled
334 * Only applies to {@link Ext.Template}, XTemplates are compiled automatically.
337 re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
340 compileTpl: function(tpl) {
341 var fm = Ext.util.Format,
343 useFormat = me.disableFormats !== true,
344 body, bodyReturn, evaluatedFn;
346 function fn(m, name, format, args, math) {
348 // name is what is inside the {}
349 // Name begins with xtpl, use a Sub Template
350 if (name.substr(0, 4) == 'xtpl') {
351 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
353 // name = "." - Just use the values object.
355 // filter to not include arrays/objects/nulls
356 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
359 // name = "#" - Use the xindex
360 else if (name == '#') {
363 else if (name.substr(0, 7) == "parent.") {
366 // name has a . in it - Use object literal notation, starting from values
367 else if (name.indexOf('.') != -1) {
368 v = "values." + name;
371 // name is a property of values
373 v = "values['" + name + "']";
376 v = '(' + v + math + ')';
378 if (format && useFormat) {
379 args = args ? ',' + args : "";
380 if (format.substr(0, 5) != "this.") {
381 format = "fm." + format + '(';
384 format = 'this.' + format.substr(5) + '(';
389 format = "(" + v + " === undefined ? '' : ";
391 return "'," + format + v + args + "),'";
394 function codeFn(m, code) {
395 // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
396 return "',(" + code.replace(me.compileARe, "'") + "),'";
399 bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
400 body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
403 tpl.compiled = function(values, parent, xindex, xcount) {
409 if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
413 vs = tpl.target ? tpl.target.call(me, values, parent) : values;
418 parent = tpl.target ? values : parent;
419 if (tpl.target && Ext.isArray(vs)) {
423 for (i = 0; i < length; i++) {
424 buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
425 tpl.exec.call(me, vs[i], parent, i + 1, length);
428 for (i = 0; i < length; i++) {
429 buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
432 return buffer.join('');
436 tpl.exec.call(me, vs, parent, xindex, xcount);
438 return evaluatedFn.call(me, vs, parent, xindex, xcount);
444 // inherit docs from Ext.Template
445 applyTemplate: function(values) {
446 return this.master.compiled.call(this, values, {}, 1, 1);
450 * Does nothing. XTemplates are compiled automatically, so this function simply returns this.
451 * @return {Ext.XTemplate} this
453 compile: function() {
457 // re-create the alias, inheriting it from Ext.Template doesn't work as intended.
458 this.createAlias('apply', 'applyTemplate');