Форк Rambox
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

464 lines
16 KiB

/**
* Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
*
* An instance of this class may be created by passing to the constructor either a single argument, or multiple
* arguments:
*
* # Single argument: String/Array
*
* The single argument may be either a String or an Array:
*
* - String:
*
* var t = new Ext.Template("<div>Hello {0}.</div>");
* t.{@link #append}('some-element', ['foo']);
*
* - Array:
*
* An Array will be combined with `join('')`.
*
* var t = new Ext.Template([
* '<div name="{id}">',
* '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
* '</div>',
* ]);
* t.{@link #compile}();
* t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
*
* # Multiple arguments: String, Object, Array, ...
*
* Multiple arguments will be combined with `join('')`.
*
* var t = new Ext.Template(
* '<div name="{id}">',
* '<span class="{cls}">{name} {value}</span>',
* '</div>',
* // a configuration object:
* {
* compiled: true, // {@link #compile} immediately
* }
* );
*
* # Notes
*
* - For a list of available format functions, see {@link Ext.util.Format}.
* - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
*/
Ext.define('Ext.Template', {
// @define Ext.String.format
// @define Ext.util.Format.format
requires: [
//'Ext.dom.Helper',
'Ext.util.Format'
],
inheritableStatics: {
/**
* Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
* @param {String/HTMLElement} el A DOM element or its id
* @param {Object} config (optional) Config object
* @return {Ext.Template} The created template
* @static
* @inheritable
*/
from: function(el, config) {
el = Ext.getDom(el);
return new this(el.value || el.innerHTML, config || '');
}
},
// Chrome really likes "new Function" to realize the code block (as in it is
// 2x-3x faster to call it than using eval), but Firefox chokes on it badly.
// IE and Opera are also fine with the "new Function" technique.
useEval: Ext.isGecko,
/* End Definitions */
/**
* Creates new template.
*
* @param {String...} html List of strings to be concatenated into template.
* Alternatively an array of strings can be given, but then no config object may be passed.
* @param {Object} config (optional) Config object
*/
constructor: function(html) {
var me = this,
args = arguments,
buffer = [],
i,
length = args.length,
value;
me.initialConfig = {};
// Allow an array to be passed here so we can
// pass an array of strings and an object
// at the end
if (length === 1 && Ext.isArray(html)) {
args = html;
length = args.length;
}
if (length > 1) {
for (i = 0; i < length; i++) {
value = args[i];
if (typeof value === 'object') {
Ext.apply(me.initialConfig, value);
Ext.apply(me, value);
} else {
buffer.push(value);
}
}
} else {
buffer.push(html);
}
// @private
me.html = buffer.join('');
},
/**
* @property {Boolean} isTemplate
* `true` in this class to identify an object as an instantiated Template, or subclass thereof.
*/
isTemplate: true,
/**
* @cfg {Boolean} compiled
* True to immediately compile the template. Defaults to false.
*/
/**
* @cfg {Boolean} disableFormats
* True to disable format functions in the template. If the template doesn't contain
* format functions, setting disableFormats to true will reduce apply time. Defaults to false.
*/
disableFormats: false,
/**
* @property {RegExp} re
* Regular expression used to extract tokens.
*
* Finds the following expressions within a format string
*
* {AND?}
* / \
* / \
* / \
* / \
* OR AND?
* / \ / \
* / \ / \
* / \ / \
* (\d+) ([a-z_][\w\-]*) / \
* index name / \
* / \
* / \
* \:([a-z_\.]*) (?:\((.*?)?\))?
* formatFn args
*
* Numeric index or (name followed by optional formatting function and args)
* @private
*/
tokenRe: /\{(?:(?:(\d+)|([a-z_][\w\-]*))(?::([a-z_\.]+)(?:\(([^\)]*?)?\))?)?)\}/gi,
/**
* Returns an HTML fragment of this template with the specified values applied.
*
* @param {Object/Array} values The template values. Can be an array if your params are numeric:
*
* var tpl = new Ext.Template('Name: {0}, Age: {1}');
* tpl.apply(['John', 25]);
*
* or an object:
*
* var tpl = new Ext.Template('Name: {name}, Age: {age}');
* tpl.apply({name: 'John', age: 25});
*
* @return {String} The HTML fragment
*/
apply: function(values) {
var me = this;
if (me.compiled) {
if (!me.fn) {
me.compile();
}
return me.fn(values).join('');
}
return me.evaluate(values);
},
// Private
// Do not create the substitution closure on every apply call
evaluate: function(values) {
var me = this,
useFormat = !me.disableFormats,
fm = Ext.util.Format,
tpl = me;
function fn(match, index, name, formatFn, args) {
// Calculate the correct name extracted from the {}
// Certain browser pass unmatched parameters as undefined, some as an empty string.
if (name == null || name === '') {
name = index;
}
if (formatFn && useFormat) {
if (args) {
args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
} else {
args = [values[name]];
}
// Caller used '{0:this.bold}'. Create a call to tpl member function
if (formatFn.substr(0, 5) === "this.") {
return tpl[formatFn.substr(5)].apply(tpl, args);
}
// Caller used '{0:number("0.00")}'. Create a call to Ext.util.Format function
else if (fm[formatFn]) {
return fm[formatFn].apply(fm, args);
}
// Caller used '{0:someRandomText}'. We must return it unchanged
else {
return match;
}
}
else {
return values[name] !== undefined ? values[name] : "";
}
}
return me.html.replace(me.tokenRe, fn);
},
/**
* 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.
* @return {Array} The given out array.
*/
applyOut: function(values, out) {
var me = this;
if (me.compiled) {
if (!me.fn) {
me.compile();
}
out.push.apply(out, me.fn(values));
} else {
out.push(me.apply(values));
}
return out;
},
/**
* @method applyTemplate
* @member Ext.Template
* Alias for {@link #apply}.
* @inheritdoc Ext.Template#apply
*/
applyTemplate: function () {
return this.apply.apply(this, arguments);
},
/**
* Sets the HTML used as the template and optionally compiles it.
* @param {String} html
* @param {Boolean} compile (optional) True to compile the template.
* @return {Ext.Template} this
*/
set: function(html, compile) {
var me = this;
me.html = html;
me.compiled = !!compile;
me.fn = null;
return me;
},
compileARe: /\\/g,
compileBRe: /(\r\n|\n)/g,
compileCRe: /'/g,
/**
* Compiles the template into an internal function, eliminating the RegEx overhead.
* @return {Ext.Template} this
*/
compile: function() {
var me = this,
code;
code = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').
replace(me.compileCRe, "\\'").replace(me.tokenRe, me.regexReplaceFn.bind(me));
code = (this.disableFormats !== true ? 'var fm=Ext.util.Format;' : '') +
(me.useEval ? '$=' : 'return') +
" function(v){return ['" + code + "'];};";
me.fn = me.useEval ? me.evalCompiled(code) : (new Function('Ext', code))(Ext); // jshint ignore:line
me.compiled = true;
return me;
},
// @private
evalCompiled: function($) {
// We have to use eval to realize the code block and capture the inner func we also
// don't want a deep scope chain. We only do this in Firefox and it is also unhappy
// with eval containing a return statement, so instead we assign to "$" and return
// that. Because we use "eval", we are automatically sandboxed properly.
eval($); // jshint ignore:line
return $;
},
regexReplaceFn: function (match, index, name, formatFn, args) {
// Calculate the correct expression to use to index into the values object/array
// index may be a numeric string, or a quoted alphanumeric string.
// Certain browser pass unmatched parameters as undefined, some as an empty string.
if (index == null || index === '') {
index = '"' + name + '"';
}
// If we are being used as a formatter for Ext.String.format, we must skip the string itself in the argument list.
// Doing this enables String.format to omit the Array slice call.
else if (this.stringFormat) {
index = parseInt(index) + 1;
}
if (formatFn && this.disableFormats !== true) {
args = args ? ',' + args: "";
// Caller used '{0:this.bold}'. Create a call to member function
if (formatFn.substr(0, 5) === "this.") {
formatFn = formatFn + '(';
}
// Caller used '{0:number("0.00")}'. Create a call to Ext.util.Format function
else if (Ext.util.Format[formatFn]) {
formatFn = "fm." + formatFn + '(';
}
// Caller used '{0:someRandomText}'. We must pass it through unchanged
else {
return match;
}
return "'," + formatFn + "v[" + index + "]" + args + "),'";
}
else {
return "',v[" + index + "] == undefined ? '' : v[" + index + "],'";
}
},
/**
* Applies the supplied values to the template and inserts the new node(s) as the first child of el.
*
* @param {String/HTMLElement/Ext.dom.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return a Ext.Element.
* @return {HTMLElement/Ext.dom.Element} The new node or Element
*/
insertFirst: function(el, values, returnElement) {
return this.doInsert('afterBegin', el, values, returnElement);
},
/**
* Applies the supplied values to the template and inserts the new node(s) before el.
*
* @param {String/HTMLElement/Ext.dom.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return a Ext.Element.
* @return {HTMLElement/Ext.dom.Element} The new node or Element
*/
insertBefore: function(el, values, returnElement) {
return this.doInsert('beforeBegin', el, values, returnElement);
},
/**
* Applies the supplied values to the template and inserts the new node(s) after el.
*
* @param {String/HTMLElement/Ext.dom.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return a Ext.Element.
* @return {HTMLElement/Ext.dom.Element} The new node or Element
*/
insertAfter: function(el, values, returnElement) {
return this.doInsert('afterEnd', el, values, returnElement);
},
/**
* Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
*
* For example usage see {@link Ext.Template Ext.Template class docs}.
*
* @param {String/HTMLElement/Ext.dom.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return an Ext.Element.
* @return {HTMLElement/Ext.dom.Element} The new node or Element
*/
append: function(el, values, returnElement) {
return this.doInsert('beforeEnd', el, values, returnElement);
},
doInsert: function(where, el, values, returnElement) {
var newNode = Ext.DomHelper.insertHtml(where, Ext.getDom(el), this.apply(values));
return returnElement ? Ext.get(newNode) : newNode;
},
/**
* Applies the supplied values to the template and overwrites the content of el with the new node(s).
*
* @param {String/HTMLElement/Ext.dom.Element} el The context element
* @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
* @param {Boolean} returnElement (optional) true to return a Ext.Element.
* @return {HTMLElement/Ext.dom.Element} The new node or Element
*/
overwrite: function(el, values, returnElement) {
var newNode = Ext.DomHelper.overwrite(Ext.getDom(el), this.apply(values));
return returnElement ? Ext.get(newNode) : newNode;
}
}, function(Template){
var formatRe = /\{\d+\}/,
generateFormatFn = function(format) {
// Generate a function which substitutes value tokens
if (formatRe.test(format)) {
format = new Template(format, formatTplConfig);
return function() {
return format.apply(arguments);
};
}
// No value tokens
else {
return function() {
return format;
};
}
},
// Flags for the template compile process.
// stringFormat means that token 0 consumes argument 1 etc.
// So that String.format does not have to slice the argument list.
formatTplConfig = { useFormat: false, compiled: true, stringFormat: true },
formatFns = {};
/**
* Alias for {@link Ext.String#format}.
* @method format
* @inheritdoc Ext.String#format
* @member Ext.util.Format
*/
/**
* Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
* token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
*
* var cls = 'my-class',
* text = 'Some text';
* var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
* // s now contains the string: '<div class="my-class">Some text</div>'
*
* @param {String} string The tokenized string to be formatted.
* @param {Mixed...} values The values to replace tokens `{0}`, `{1}`, etc in order.
* @return {String} The formatted string.
* @member Ext.String
*/
Ext.String.format = Ext.util.Format.format = function(format) {
var formatFn = formatFns[format] || (formatFns[format] = generateFormatFn(format));
return formatFn.apply(this, arguments);
};
});