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 * @class Ext.XTemplate
17 * @extends Ext.Template
18 * <p>A template class that supports advanced functionality like:<div class="mdetail-params"><ul>
19 * <li>Autofilling arrays using templates and sub-templates</li>
20 * <li>Conditional processing with basic comparison operators</li>
21 * <li>Basic math function support</li>
22 * <li>Execute arbitrary inline code with special built-in template variables</li>
23 * <li>Custom member functions</li>
24 * <li>Many special tags and built-in operators that aren't defined as part of
25 * the API, but are supported in the templates that can be created</li>
27 * <p>XTemplate provides the templating mechanism built into:<div class="mdetail-params"><ul>
28 * <li>{@link Ext.view.View}</li>
31 * The {@link Ext.Template} describes
32 * the acceptable parameters to pass to the constructor. The following
33 * examples demonstrate all of the supported features.</p>
35 * <div class="mdetail-params"><ul>
37 * <li><b><u>Sample Data</u></b>
38 * <div class="sub-desc">
39 * <p>This is the data object used for reference in each code example:</p>
43 title: 'Lead Developer',
44 company: 'Sencha Inc.',
45 email: 'tommy@sencha.com',
46 address: '5 Cups Drive',
50 drinks: ['Coffee', 'Soda', 'Water'],
67 * <li><b><u>Auto filling of arrays</u></b>
68 * <div class="sub-desc">
69 * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>for</tt></b> operator are used
70 * to process the provided data object:
72 * <li>If the value specified in <tt>for</tt> is an array, it will auto-fill,
73 * repeating the template block inside the <tt>tpl</tt> tag for each item in the
75 * <li>If <tt>for="."</tt> is specified, the data object provided is examined.</li>
76 * <li>While processing an array, the special variable <tt>{#}</tt>
77 * will provide the current array index + 1 (starts at 1, not 0).</li>
81 <tpl <b>for</b>=".">...</tpl> // loop through array at root node
82 <tpl <b>for</b>="foo">...</tpl> // loop through array at foo node
83 <tpl <b>for</b>="foo.bar">...</tpl> // loop through array at foo.bar node
85 * Using the sample data above:
87 var tpl = new Ext.XTemplate(
89 '<tpl <b>for</b>=".">', // process the data.kids node
90 '<p>{#}. {name}</p>', // use current array index to autonumber
93 tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object
95 * <p>An example illustrating how the <b><tt>for</tt></b> property can be leveraged
96 * to access specified members of the provided data object to populate the template:</p>
98 var tpl = new Ext.XTemplate(
99 '<p>Name: {name}</p>',
100 '<p>Title: {title}</p>',
101 '<p>Company: {company}</p>',
103 '<tpl <b>for="kids"</b>>', // interrogate the kids property within the data
104 '<p>{name}</p>',
107 tpl.overwrite(panel.body, data); // pass the root node of the data object
109 * <p>Flat arrays that contain values (and not objects) can be auto-rendered
110 * using the special <b><tt>{.}</tt></b> variable inside a loop. This variable
111 * will represent the value of the array at the current index:</p>
113 var tpl = new Ext.XTemplate(
114 '<p>{name}\'s favorite beverages:</p>',
115 '<tpl for="drinks">',
116 '<div> - {.}</div>',
119 tpl.overwrite(panel.body, data);
121 * <p>When processing a sub-template, for example while looping through a child array,
122 * you can access the parent object's members via the <b><tt>parent</tt></b> object:</p>
124 var tpl = new Ext.XTemplate(
125 '<p>Name: {name}</p>',
127 '<tpl for="kids">',
128 '<tpl if="age &gt; 1">',
129 '<p>{name}</p>',
130 '<p>Dad: {<b>parent</b>.name}</p>',
134 tpl.overwrite(panel.body, data);
140 * <li><b><u>Conditional processing with basic comparison operators</u></b>
141 * <div class="sub-desc">
142 * <p>The <b><tt>tpl</tt></b> tag and the <b><tt>if</tt></b> operator are used
143 * to provide conditional checks for deciding whether or not to render specific
144 * parts of the template. Notes:<div class="sub-desc"><ul>
145 * <li>Double quotes must be encoded if used within the conditional</li>
146 * <li>There is no <tt>else</tt> operator — if needed, two opposite
147 * <tt>if</tt> statements should be used.</li>
150 <tpl if="age > 1 && age < 10">Child</tpl>
151 <tpl if="age >= 10 && age < 18">Teenager</tpl>
152 <tpl <b>if</b>="this.isGirl(name)">...</tpl>
153 <tpl <b>if</b>="id==\'download\'">...</tpl>
154 <tpl <b>if</b>="needsIcon"><img src="{icon}" class="{iconCls}"/></tpl>
156 <tpl if="name == "Tommy"">Hello</tpl>
157 // encode " if it is part of the condition, e.g.
158 <tpl if="name == &quot;Tommy&quot;">Hello</tpl>
160 * Using the sample data above:
162 var tpl = new Ext.XTemplate(
163 '<p>Name: {name}</p>',
165 '<tpl for="kids">',
166 '<tpl if="age &gt; 1">',
167 '<p>{name}</p>',
171 tpl.overwrite(panel.body, data);
177 * <li><b><u>Basic math support</u></b>
178 * <div class="sub-desc">
179 * <p>The following basic math operators may be applied directly on numeric
180 * data values:</p><pre>
185 var tpl = new Ext.XTemplate(
186 '<p>Name: {name}</p>',
188 '<tpl for="kids">',
189 '<tpl if="age &gt; 1">', // <-- Note that the > is encoded
190 '<p>{#}: {name}</p>', // <-- Auto-number each item
191 '<p>In 5 Years: {age+5}</p>', // <-- Basic math
192 '<p>Dad: {parent.name}</p>',
196 tpl.overwrite(panel.body, data);
202 * <li><b><u>Execute arbitrary inline code with special built-in template variables</u></b>
203 * <div class="sub-desc">
204 * <p>Anything between <code>{[ ... ]}</code> is considered code to be executed
205 * in the scope of the template. There are some special variables available in that code:
207 * <li><b><tt>values</tt></b>: The values in the current scope. If you are using
208 * scope changing sub-templates, you can change what <tt>values</tt> is.</li>
209 * <li><b><tt>parent</tt></b>: The scope (values) of the ancestor template.</li>
210 * <li><b><tt>xindex</tt></b>: If you are in a looping template, the index of the
211 * loop you are in (1-based).</li>
212 * <li><b><tt>xcount</tt></b>: If you are in a looping template, the total length
213 * of the array you are looping.</li>
215 * This example demonstrates basic row striping using an inline code block and the
216 * <tt>xindex</tt> variable:</p>
218 var tpl = new Ext.XTemplate(
219 '<p>Name: {name}</p>',
220 '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>',
222 '<tpl for="kids">',
223 '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">',
228 tpl.overwrite(panel.body, data);
233 * <li><b><u>Template member functions</u></b>
234 * <div class="sub-desc">
235 * <p>One or more member functions can be specified in a configuration
236 * object passed into the XTemplate constructor for more complex processing:</p>
238 var tpl = new Ext.XTemplate(
239 '<p>Name: {name}</p>',
241 '<tpl for="kids">',
242 '<tpl if="this.isGirl(name)">',
243 '<p>Girl: {name} - {age}</p>',
245 // use opposite if statement to simulate 'else' processing:
246 '<tpl if="this.isGirl(name) == false">',
247 '<p>Boy: {name} - {age}</p>',
249 '<tpl if="this.isBaby(age)">',
250 '<p>{name} is a baby!</p>',
254 // XTemplate configuration:
257 isGirl: function(name){
258 return name == 'Sara Grace';
260 isBaby: function(age){
265 tpl.overwrite(panel.body, data);
272 * @param {Mixed} config
275 Ext.define('Ext.XTemplate', {
277 /* Begin Definitions */
279 extend: 'Ext.Template',
283 * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
284 * @param {String/HTMLElement} el A DOM element or its id
285 * @return {Ext.Template} The created template
288 from: function(el, config) {
290 return new this(el.value || el.innerHTML, config || {});
294 /* End Definitions */
296 argsRe: /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
297 nameRe: /^<tpl\b[^>]*?for="(.*?)"/,
298 ifRe: /^<tpl\b[^>]*?if="(.*?)"/,
299 execRe: /^<tpl\b[^>]*?exec="(.*?)"/,
300 constructor: function() {
301 this.callParent(arguments);
316 WITHVALUES = 'with(values){ ',
317 m, matchName, matchIf, matchExec, exp, fn, exec, name, i;
319 html = ['<tpl>', html, '</tpl>'].join('');
321 while ((m = html.match(argsRe))) {
325 matchName = m[0].match(nameRe);
326 matchIf = m[0].match(ifRe);
327 matchExec = m[0].match(execRe);
329 exp = matchIf ? matchIf[1] : null;
331 fn = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + 'try{' + RETURN + Ext.String.htmlDecode(exp) + ';}catch(e){return;}}');
334 exp = matchExec ? matchExec[1] : null;
336 exec = Ext.functionFactory(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + Ext.String.htmlDecode(exp) + ';}');
339 name = matchName ? matchName[1] : null;
343 } else if (name === '..') {
346 name = Ext.functionFactory(VALUES, PARENT, 'try{' + WITHVALUES + RETURN + name + ';}}catch(e){return;}');
357 html = html.replace(m[0], '{xtpl' + id + '}');
361 for (i = tpls.length - 1; i >= 0; --i) {
362 me.compileTpl(tpls[i]);
364 me.master = tpls[tpls.length - 1];
369 applySubTemplate: function(id, values, parent, xindex, xcount) {
370 var me = this, t = me.tpls[id];
371 return t.compiled.call(me, values, parent, xindex, xcount);
374 * @cfg {RegExp} codeRe The regular expression used to match code variables (default: matches <tt>{[expression]}</tt>).
376 codeRe: /\{\[((?:\\\]|.|\n)*?)\]\}/g,
378 re: /\{([\w-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\/]\s?[\d\.\+\-\*\/\(\)]+)?\}/g,
381 compileTpl: function(tpl) {
382 var fm = Ext.util.Format,
384 useFormat = me.disableFormats !== true,
385 body, bodyReturn, evaluatedFn;
387 function fn(m, name, format, args, math) {
389 // name is what is inside the {}
390 // Name begins with xtpl, use a Sub Template
391 if (name.substr(0, 4) == 'xtpl') {
392 return "',this.applySubTemplate(" + name.substr(4) + ", values, parent, xindex, xcount),'";
394 // name = "." - Just use the values object.
396 // filter to not include arrays/objects/nulls
397 v = 'Ext.Array.indexOf(["string", "number", "boolean"], typeof values) > -1 || Ext.isDate(values) ? values : ""';
400 // name = "#" - Use the xindex
401 else if (name == '#') {
404 else if (name.substr(0, 7) == "parent.") {
407 // name has a . in it - Use object literal notation, starting from values
408 else if (name.indexOf('.') != -1) {
409 v = "values." + name;
412 // name is a property of values
414 v = "values['" + name + "']";
417 v = '(' + v + math + ')';
419 if (format && useFormat) {
420 args = args ? ',' + args : "";
421 if (format.substr(0, 5) != "this.") {
422 format = "fm." + format + '(';
425 format = 'this.' + format.substr(5) + '(';
430 format = "(" + v + " === undefined ? '' : ";
432 return "'," + format + v + args + "),'";
435 function codeFn(m, code) {
436 // Single quotes get escaped when the template is compiled, however we want to undo this when running code.
437 return "',(" + code.replace(me.compileARe, "'") + "),'";
440 bodyReturn = tpl.body.replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn).replace(me.codeRe, codeFn);
441 body = "evaluatedFn = function(values, parent, xindex, xcount){return ['" + bodyReturn + "'].join('');};";
444 tpl.compiled = function(values, parent, xindex, xcount) {
450 if (tpl.test && !tpl.test.call(me, values, parent, xindex, xcount)) {
454 vs = tpl.target ? tpl.target.call(me, values, parent) : values;
459 parent = tpl.target ? values : parent;
460 if (tpl.target && Ext.isArray(vs)) {
464 for (i = 0; i < length; i++) {
465 buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
466 tpl.exec.call(me, vs[i], parent, i + 1, length);
469 for (i = 0; i < length; i++) {
470 buffer[buffer.length] = evaluatedFn.call(me, vs[i], parent, i + 1, length);
473 return buffer.join('');
477 tpl.exec.call(me, vs, parent, xindex, xcount);
479 return evaluatedFn.call(me, vs, parent, xindex, xcount);
486 * Returns an HTML fragment of this template with the specified values applied.
487 * @param {Object} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
488 * @return {String} The HTML fragment
490 applyTemplate: function(values) {
491 return this.master.compiled.call(this, values, {}, 1, 1);
495 * Compile the template to a function for optimized performance. Recommended if the template will be used frequently.
496 * @return {Function} The compiled function
498 compile: function() {
503 * Alias for {@link #applyTemplate}
504 * Returns an HTML fragment of this template with the specified values applied.
505 * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
506 * @return {String} The HTML fragment
507 * @member Ext.XTemplate
510 this.createAlias('apply', 'applyTemplate');