378 lines
14 KiB
JavaScript
378 lines
14 KiB
JavaScript
/**
|
|
* 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;
|
|
}
|
|
}
|
|
});
|