macoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangoutsslackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-services
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.
465 lines
16 KiB
465 lines
16 KiB
9 years ago
|
/**
|
||
|
* 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);
|
||
|
};
|
||
|
});
|