/**
* 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.view.View}.
*
* 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:
*
* ... // loop through array at root node
* ... // loop through array at foo node
* ... // loop through array at foo.bar node
*
* Using the sample data above:
*
* var tpl = new Ext.XTemplate(
* '
Kids: ',
* '', // process the data.kids node
* '{#}. {name}
', // use current array index to autonumber
* '
'
* );
* 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(
* 'Name: {name}
',
* 'Title: {title}
',
* 'Company: {company}
',
* 'Kids: ',
* '', // interrogate the kids property within the data
* '{name}
',
* '
'
* );
* 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(
* '{name}\'s favorite beverages:
',
* '',
* ' - {.}
',
* ''
* );
* 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(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '',
* '{name}
',
* 'Dad: {parent.name}
',
* '',
* '
'
* );
* 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(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '',
* '{name}
',
* '',
* '
'
* );
* tpl.overwrite(panel.body, data);
*
* More advanced conditionals are also supported:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '{name} is a ',
* '',
* 'teenager
',
* '',
* 'kid
',
* '',
* 'baby
',
* '',
* '
'
* );
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '{name} is a ',
* '',
* '',
* 'girl
',
* '',
* 'boy
',
* '',
* '
'
* );
*
* 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(
* "Child",
* "Teenager",
* "...",
* '...',
* "",
* "Hello"
* );
*
* # Basic math support
*
* The following basic math operators may be applied directly on numeric data values:
*
* + - * /
*
* For example:
*
* var tpl = new Ext.XTemplate(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '', // <-- Note that the > is encoded
* '{#}: {name}
', // <-- Auto-number each item
* 'In 5 Years: {age+5}
', // <-- Basic math
* 'Dad: {parent.name}
',
* '',
* '
'
* );
* 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(
* 'Name: {name}
',
* 'Company: {[values.company.toUpperCase() + ", " + values.title]}
',
* 'Kids: ',
* '',
* '',
* '{name}',
* '
',
* '
'
* );
*
* 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(
* 'Name: {name}
',
* 'Company: {[values.company.toUpperCase() + ", " + values.title]}
',
* 'Kids: ',
* '',
* '{% if (xindex % 2 === 0) continue; %}',
* '{name}',
* '{% if (xindex > 100) break; %}',
* '',
* '
'
* );
*
* # 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(
* 'Name: {name}
',
* 'Kids: ',
* '',
* '',
* 'Girl: {name} - {age}
',
* '',
* 'Boy: {name} - {age}
',
* '',
* '',
* '{name} is a baby!
',
* '',
* '
',
* {
* // XTemplate configuration:
* disableFormats: true,
* // member functions:
* isGirl: function(name){
* return name == 'Aubrey' || name == 'Nikol';
* },
* 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}.
*/
/**
* @cfg {String/Array} definitions
* Optional. A statement, or array of statements which set up `var`s which may then
* be accessed within the scope of the generated function.
*/
apply: function(values, parent) {
return this.applyOut(values, [], parent).join('');
},
applyOut: function(values, out, parent) {
var me = this,
compiler;
if (!me.fn) {
compiler = new Ext.XTemplateCompiler({
useFormat: me.disableFormats !== true,
definitions: me.definitions
});
me.fn = compiler.compile(me.html);
}
try {
me.fn.call(me, out, values, parent || me.emptyObj, 1, 1);
} catch (e) {
//
Ext.log('Error: ' + e.message);
//
}
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;
}
}
});