Upgrade to ExtJS 4.0.7 - Released 10/19/2011
[extjs.git] / src / Template.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * Represents an HTML fragment template. Templates may be {@link #compile precompiled} for greater performance.
17  *
18  * An instance of this class may be created by passing to the constructor either a single argument, or multiple
19  * arguments:
20  *
21  * # Single argument: String/Array
22  *
23  * The single argument may be either a String or an Array:
24  *
25  * - String:
26  *
27  *       var t = new Ext.Template("<div>Hello {0}.</div>");
28  *       t.{@link #append}('some-element', ['foo']);
29  *
30  * - Array:
31  *
32  *   An Array will be combined with `join('')`.
33  *
34  *       var t = new Ext.Template([
35  *           '<div name="{id}">',
36  *               '<span class="{cls}">{name:trim} {value:ellipsis(10)}</span>',
37  *           '</div>',
38  *       ]);
39  *       t.{@link #compile}();
40  *       t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
41  *
42  * # Multiple arguments: String, Object, Array, ...
43  *
44  * Multiple arguments will be combined with `join('')`.
45  *
46  *     var t = new Ext.Template(
47  *         '<div name="{id}">',
48  *             '<span class="{cls}">{name} {value}</span>',
49  *         '</div>',
50  *         // a configuration object:
51  *         {
52  *             compiled: true,      // {@link #compile} immediately
53  *         }
54  *     );
55  *
56  * # Notes
57  *
58  * - For a list of available format functions, see {@link Ext.util.Format}.
59  * - `disableFormats` reduces `{@link #apply}` time when no formatting is required.
60  */
61 Ext.define('Ext.Template', {
62
63     /* Begin Definitions */
64
65     requires: ['Ext.DomHelper', 'Ext.util.Format'],
66
67     inheritableStatics: {
68         /**
69          * Creates a template from the passed element's value (_display:none_ textarea, preferred) or innerHTML.
70          * @param {String/HTMLElement} el A DOM element or its id
71          * @param {Object} config (optional) Config object
72          * @return {Ext.Template} The created template
73          * @static
74          * @inheritable
75          */
76         from: function(el, config) {
77             el = Ext.getDom(el);
78             return new this(el.value || el.innerHTML, config || '');
79         }
80     },
81
82     /* End Definitions */
83
84     /**
85      * Creates new template.
86      * 
87      * @param {String...} html List of strings to be concatenated into template.
88      * Alternatively an array of strings can be given, but then no config object may be passed.
89      * @param {Object} config (optional) Config object
90      */
91     constructor: function(html) {
92         var me = this,
93             args = arguments,
94             buffer = [],
95             i = 0,
96             length = args.length,
97             value;
98
99         me.initialConfig = {};
100
101         if (length > 1) {
102             for (; i < length; i++) {
103                 value = args[i];
104                 if (typeof value == 'object') {
105                     Ext.apply(me.initialConfig, value);
106                     Ext.apply(me, value);
107                 } else {
108                     buffer.push(value);
109                 }
110             }
111             html = buffer.join('');
112         } else {
113             if (Ext.isArray(html)) {
114                 buffer.push(html.join(''));
115             } else {
116                 buffer.push(html);
117             }
118         }
119
120         // @private
121         me.html = buffer.join('');
122
123         if (me.compiled) {
124             me.compile();
125         }
126     },
127
128     isTemplate: true,
129
130     /**
131      * @cfg {Boolean} compiled
132      * True to immediately compile the template. Defaults to false.
133      */
134
135     /**
136      * @cfg {Boolean} disableFormats
137      * True to disable format functions in the template. If the template doesn't contain
138      * format functions, setting disableFormats to true will reduce apply time. Defaults to false.
139      */
140     disableFormats: false,
141
142     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
143
144     /**
145      * Returns an HTML fragment of this template with the specified values applied.
146      *
147      * @param {Object/Array} values The template values. Can be an array if your params are numeric:
148      *
149      *     var tpl = new Ext.Template('Name: {0}, Age: {1}');
150      *     tpl.applyTemplate(['John', 25]);
151      *
152      * or an object:
153      *
154      *     var tpl = new Ext.Template('Name: {name}, Age: {age}');
155      *     tpl.applyTemplate({name: 'John', age: 25});
156      *
157      * @return {String} The HTML fragment
158      */
159     applyTemplate: function(values) {
160         var me = this,
161             useFormat = me.disableFormats !== true,
162             fm = Ext.util.Format,
163             tpl = me;
164
165         if (me.compiled) {
166             return me.compiled(values);
167         }
168         function fn(m, name, format, args) {
169             if (format && useFormat) {
170                 if (args) {
171                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
172                 } else {
173                     args = [values[name]];
174                 }
175                 if (format.substr(0, 5) == "this.") {
176                     return tpl[format.substr(5)].apply(tpl, args);
177                 }
178                 else {
179                     return fm[format].apply(fm, args);
180                 }
181             }
182             else {
183                 return values[name] !== undefined ? values[name] : "";
184             }
185         }
186         return me.html.replace(me.re, fn);
187     },
188
189     /**
190      * Sets the HTML used as the template and optionally compiles it.
191      * @param {String} html
192      * @param {Boolean} compile (optional) True to compile the template.
193      * @return {Ext.Template} this
194      */
195     set: function(html, compile) {
196         var me = this;
197         me.html = html;
198         me.compiled = null;
199         return compile ? me.compile() : me;
200     },
201
202     compileARe: /\\/g,
203     compileBRe: /(\r\n|\n)/g,
204     compileCRe: /'/g,
205
206     /**
207      * Compiles the template into an internal function, eliminating the RegEx overhead.
208      * @return {Ext.Template} this
209      */
210     compile: function() {
211         var me = this,
212             fm = Ext.util.Format,
213             useFormat = me.disableFormats !== true,
214             body, bodyReturn;
215
216         function fn(m, name, format, args) {
217             if (format && useFormat) {
218                 args = args ? ',' + args: "";
219                 if (format.substr(0, 5) != "this.") {
220                     format = "fm." + format + '(';
221                 }
222                 else {
223                     format = 'this.' + format.substr(5) + '(';
224                 }
225             }
226             else {
227                 args = '';
228                 format = "(values['" + name + "'] == undefined ? '' : ";
229             }
230             return "'," + format + "values['" + name + "']" + args + ") ,'";
231         }
232
233         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
234         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
235         eval(body);
236         return me;
237     },
238
239     /**
240      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
241      *
242      * @param {String/HTMLElement/Ext.Element} el The context element
243      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
244      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
245      * @return {HTMLElement/Ext.Element} The new node or Element
246      */
247     insertFirst: function(el, values, returnElement) {
248         return this.doInsert('afterBegin', el, values, returnElement);
249     },
250
251     /**
252      * Applies the supplied values to the template and inserts the new node(s) before el.
253      *
254      * @param {String/HTMLElement/Ext.Element} el The context element
255      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
256      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
257      * @return {HTMLElement/Ext.Element} The new node or Element
258      */
259     insertBefore: function(el, values, returnElement) {
260         return this.doInsert('beforeBegin', el, values, returnElement);
261     },
262
263     /**
264      * Applies the supplied values to the template and inserts the new node(s) after el.
265      *
266      * @param {String/HTMLElement/Ext.Element} el The context element
267      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
268      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
269      * @return {HTMLElement/Ext.Element} The new node or Element
270      */
271     insertAfter: function(el, values, returnElement) {
272         return this.doInsert('afterEnd', el, values, returnElement);
273     },
274
275     /**
276      * Applies the supplied `values` to the template and appends the new node(s) to the specified `el`.
277      *
278      * For example usage see {@link Ext.Template Ext.Template class docs}.
279      *
280      * @param {String/HTMLElement/Ext.Element} el The context element
281      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
282      * @param {Boolean} returnElement (optional) true to return an Ext.Element.
283      * @return {HTMLElement/Ext.Element} The new node or Element
284      */
285     append: function(el, values, returnElement) {
286         return this.doInsert('beforeEnd', el, values, returnElement);
287     },
288
289     doInsert: function(where, el, values, returnEl) {
290         el = Ext.getDom(el);
291         var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
292         return returnEl ? Ext.get(newNode, true) : newNode;
293     },
294
295     /**
296      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
297      *
298      * @param {String/HTMLElement/Ext.Element} el The context element
299      * @param {Object/Array} values The template values. See {@link #applyTemplate} for details.
300      * @param {Boolean} returnElement (optional) true to return a Ext.Element.
301      * @return {HTMLElement/Ext.Element} The new node or Element
302      */
303     overwrite: function(el, values, returnElement) {
304         el = Ext.getDom(el);
305         el.innerHTML = this.applyTemplate(values);
306         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
307     }
308 }, function() {
309
310     /**
311      * @method apply
312      * @member Ext.Template
313      * Alias for {@link #applyTemplate}.
314      * @alias Ext.Template#applyTemplate
315      */
316     this.createAlias('apply', 'applyTemplate');
317 });
318