/** * A template class that supports advanced functionality like: * * - Autofilling arrays using templates and sub-templates * - Conditional processing with basic comparison operators * - Basic math function support * - Execute arbitrary inline code with special built-in template variables * - Custom member functions * - 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 * * XTemplate provides the templating mechanism built into {@link Ext.DataView}. * * The {@link Ext.Template} describes the acceptable parameters to pass to the constructor. The following examples * demonstrate all of the supported features. * * # Sample Data * * This is the data object used for reference in each code example: * * var data = { * name: 'Don Griffin', * title: 'Senior Technomage', * company: 'Sencha Inc.', * drinks: ['Coffee', 'Water', 'More Coffee'], * kids: [ * { name: 'Aubrey', age: 17 }, * { name: 'Joshua', age: 13 }, * { name: 'Cale', age: 10 }, * { name: 'Nikol', age: 5 }, * { name: 'Solomon', age: 0 } * ] * }; * * # Auto filling of arrays * * The **tpl** tag and the **for** operator are used to process the provided data object: * * - If the value specified in for is an array, it will auto-fill, repeating the template block inside the tpl * tag for each item in the array. * - If for="." is specified, the data object provided is examined. * - While processing an array, the special variable {#} will provide the current array index + 1 (starts at 1, not 0). * * Examples: * * <tpl for=".">...</tpl> // loop through array at root node * <tpl for="foo">...</tpl> // loop through array at foo node * <tpl for="foo.bar">...</tpl> // loop through array at foo.bar node * * Using the sample data above: * * var tpl = new Ext.XTemplate( * '<p>Kids: ', * '<tpl for=".">', // process the data.kids node * '<p>{#}. {name}</p>', // use current array index to autonumber * '</tpl></p>' * ); * tpl.overwrite(panel.body, data.kids); // pass the kids property of the data object * * An example illustrating how the **for** property can be leveraged to access specified members of the provided data * object to populate the template: * * var tpl = new Ext.XTemplate( * '<p>Name: {name}</p>', * '<p>Title: {title}</p>', * '<p>Company: {company}</p>', * '<p>Kids: ', * '<tpl for="kids">', // interrogate the kids property within the data * '<p>{name}</p>', * '</tpl></p>' * ); * tpl.overwrite(panel.body, data); // pass the root node of the data object * * Flat arrays that contain values (and not objects) can be auto-rendered using the special **`{.}`** variable inside a * loop. This variable will represent the value of the array at the current index: * * var tpl = new Ext.XTemplate( * '<p>{name}\'s favorite beverages:</p>', * '<tpl for="drinks">', * '<div> - {.}</div>', * '</tpl>' * ); * tpl.overwrite(panel.body, data); * * When processing a sub-template, for example while looping through a child array, you can access the parent object's * members via the **parent** object: * * var tpl = new Ext.XTemplate( * '<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.overwrite(panel.body, data); * * # Conditional processing with basic comparison operators * * The **tpl** tag and the **if** operator are used to provide conditional checks for deciding whether or not to render * specific parts of the template. * * Using the sample data above: * * var tpl = new Ext.XTemplate( * '<p>Name: {name}</p>', * '<p>Kids: ', * '<tpl for="kids">', * '<tpl if="age > 1">', * '<p>{name}</p>', * '</tpl>', * '</tpl></p>' * ); * tpl.overwrite(panel.body, data); * * More advanced conditionals are also supported: * * var tpl = new Ext.XTemplate( * '<p>Name: {name}</p>', * '<p>Kids: ', * '<tpl for="kids">', * '<p>{name} is a ', * '<tpl if="age >= 13">', * '<p>teenager</p>', * '<tpl elseif="age >= 2">', * '<p>kid</p>', * '<tpl else>', * '<p>baby</p>', * '</tpl>', * '</tpl></p>' * ); * * var tpl = new Ext.XTemplate( * '<p>Name: {name}</p>', * '<p>Kids: ', * '<tpl for="kids">', * '<p>{name} is a ', * '<tpl switch="name">', * '<tpl case="Aubrey" case="Nikol">', * '<p>girl</p>', * '<tpl default">', * '<p>boy</p>', * '</tpl>', * '</tpl></p>' * ); * * A `break` is implied between each case and default, however, multiple cases can be listed * in a single <tpl> tag. * * # Using double quotes * * Examples: * * var tpl = new Ext.XTemplate( * "<tpl if='age > 1 && age < 10'>Child</tpl>", * "<tpl if='age >= 10 && age < 18'>Teenager</tpl>", * "<tpl if='this.isGirl(name)'>...</tpl>", * '<tpl if="id == \'download\'">...</tpl>', * "<tpl if='needsIcon'><img src='{icon}' class='{iconCls}'/></tpl>", * "<tpl if='name == \"Don\"'>Hello</tpl>" * ); * * # Basic math support * * The following basic math operators may be applied directly on numeric data values: * * + - * / * * For example: * * var tpl = new Ext.XTemplate( * '<p>Name: {name}</p>', * '<p>Kids: ', * '<tpl for="kids">', * '<tpl if="age > 1">', // <-- Note that the > is encoded * '<p>{#}: {name}</p>', // <-- Auto-number each item * '<p>In 5 Years: {age+5}</p>', // <-- Basic math * '<p>Dad: {parent.name}</p>', * '</tpl>', * '</tpl></p>' * ); * tpl.overwrite(panel.body, data); * * # Execute arbitrary inline code with special built-in template variables * * Anything between `{[ ... ]}` is considered code to be executed in the scope of the template. * The expression is evaluated and the result is included in the generated result. There are * some special variables available in that code: * * - **out**: The output array into which the template is being appended (using `push` to later * `join`). * - **values**: The values in the current scope. If you are using scope changing sub-templates, * you can change what values is. * - **parent**: The scope (values) of the ancestor template. * - **xindex**: If you are in a looping template, the index of the loop you are in (1-based). * - **xcount**: If you are in a looping template, the total length of the array you are looping. * * This example demonstrates basic row striping using an inline code block and the xindex variable: * * var tpl = new Ext.XTemplate( * '<p>Name: {name}</p>', * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>', * '<p>Kids: ', * '<tpl for="kids">', * '<div class="{[xindex % 2 === 0 ? "even" : "odd"]}">', * '{name}', * '</div>', * '</tpl></p>' * ); * * Any code contained in "verbatim" blocks (using "{% ... %}") will be inserted directly in * the generated code for the template. These blocks are not included in the output. This * can be used for simple things like break/continue in a loop, or control structures or * method calls (when they don't produce output). The `this` references the template instance. * * var tpl = new Ext.XTemplate( * '<p>Name: {name}</p>', * '<p>Company: {[values.company.toUpperCase() + ", " + values.title]}</p>', * '<p>Kids: ', * '<tpl for="kids">', * '{% if (xindex % 2 === 0) continue; %}', * '{name}', * '{% if (xindex > 100) break; %}', * '</div>', * '</tpl></p>' * ); * * # Template member functions * * One or more member functions can be specified in a configuration object passed into the XTemplate constructor for * more complex processing: * * var tpl = new Ext.XTemplate( * '<p>Name: {name}</p>', * '<p>Kids: ', * '<tpl for="kids">', * '<tpl if="this.isGirl(name)">', * '<p>Girl: {name} - {age}</p>', * '<tpl else>', * '<p>Boy: {name} - {age}</p>', * '</tpl>', * '<tpl if="this.isBaby(age)">', * '<p>{name} is a baby!</p>', * '</tpl>', * '</tpl></p>', * { * // XTemplate configuration: * disableFormats: true, * // member functions: * isGirl: function(name){ * return name == 'Sara Grace'; * }, * isBaby: function(age){ * return age < 1; * } * } * ); * tpl.overwrite(panel.body, data); */ Ext.define('Ext.XTemplate', { extend: 'Ext.Template', requires: 'Ext.XTemplateCompiler', /** * @private */ emptyObj: {}, /** * @cfg {Boolean} compiled * Only applies to {@link Ext.Template}, XTemplates are compiled automatically on the * first call to {@link #apply} or {@link #applyOut}. * @hide */ apply: function(values) { return this.applyOut(values, []).join(''); }, /** * Appends the result of this template to the provided output array. * @param {Object/Array} values The template values. See {@link #apply}. * @param {Array} out The array to which output is pushed. * @param {Object} parent * @return {Array} The given out array. */ applyOut: function(values, out, parent) { var me = this, xindex = values.xindex, xcount = values.xcount, compiler; if (!me.fn) { compiler = new Ext.XTemplateCompiler({ useFormat : me.disableFormats !== true, definitions : me.definitions }); me.fn = compiler.compile(me.html); } try { xindex = typeof xindex === 'number' ? xindex : 1; xcount = typeof xcount === 'number' ? xcount : 1; me.fn.call(me, out, values, parent || me.emptyObj, xindex, xcount); } catch (e) { //<debug> Ext.Logger.log('Error: ' + e.message); //</debug> } return out; }, /** * Does nothing. XTemplates are compiled automatically, so this function simply returns this. * @return {Ext.XTemplate} this */ compile: function() { return this; }, statics: { /** * Gets an `XTemplate` from an object (an instance of an {@link Ext#define}'d class). * Many times, templates are configured high in the class hierarchy and are to be * shared by all classes that derive from that base. To further complicate matters, * these templates are seldom actual instances but are rather configurations. For * example: * * Ext.define('MyApp.Class', { * someTpl: [ * 'tpl text here' * ] * }); * * The goal being to share that template definition with all instances and even * instances of derived classes, until `someTpl` is overridden. This method will * "upgrade" these configurations to be real `XTemplate` instances *in place* (to * avoid creating one instance per object). * * @param {Object} instance The object from which to get the `XTemplate` (must be * an instance of an {@link Ext#define}'d class). * @param {String} name The name of the property by which to get the `XTemplate`. * @return {Ext.XTemplate} The `XTemplate` instance or null if not found. * @protected */ getTpl: function (instance, name) { var tpl = instance[name], // go for it! 99% of the time we will get it! proto; if (tpl && !tpl.isTemplate) { // tpl is just a configuration (not an instance) // create the template instance from the configuration: tpl = Ext.ClassManager.dynInstantiate('Ext.XTemplate', tpl); // and replace the reference with the new instance: if (instance.hasOwnProperty(name)) { // the tpl is on the instance instance[name] = tpl; } else { // must be somewhere in the prototype chain for (proto = instance.self.prototype; proto; proto = proto.superclass) { if (proto.hasOwnProperty(name)) { proto[name] = tpl; break; } } } } // else !tpl (no such tpl) or the tpl is an instance already... either way, tpl // is ready to return return tpl || null; } } });