Upgrade to ExtJS 4.0.2 - Released 06/09/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  * @class Ext.Template
17  * <p>Represents an HTML fragment template. Templates may be {@link #compile precompiled}
18  * for greater performance.</p>
19  * An instance of this class may be created by passing to the constructor either
20  * a single argument, or multiple arguments:
21  * <div class="mdetail-params"><ul>
22  * <li><b>single argument</b> : String/Array
23  * <div class="sub-desc">
24  * The single argument may be either a String or an Array:<ul>
25  * <li><tt>String</tt> : </li><pre><code>
26 var t = new Ext.Template("&lt;div>Hello {0}.&lt;/div>");
27 t.{@link #append}('some-element', ['foo']);
28    </code></pre>
29  * <li><tt>Array</tt> : </li>
30  * An Array will be combined with <code>join('')</code>.
31 <pre><code>
32 var t = new Ext.Template([
33     '&lt;div name="{id}"&gt;',
34         '&lt;span class="{cls}"&gt;{name:trim} {value:ellipsis(10)}&lt;/span&gt;',
35     '&lt;/div&gt;',
36 ]);
37 t.{@link #compile}();
38 t.{@link #append}('some-element', {id: 'myid', cls: 'myclass', name: 'foo', value: 'bar'});
39    </code></pre>
40  * </ul></div></li>
41  * <li><b>multiple arguments</b> : String, Object, Array, ...
42  * <div class="sub-desc">
43  * Multiple arguments will be combined with <code>join('')</code>.
44  * <pre><code>
45 var t = new Ext.Template(
46     '&lt;div name="{id}"&gt;',
47         '&lt;span class="{cls}"&gt;{name} {value}&lt;/span&gt;',
48     '&lt;/div&gt;',
49     // a configuration object:
50     {
51         compiled: true,      // {@link #compile} immediately
52     }
53 );
54    </code></pre>
55  * <p><b>Notes</b>:</p>
56  * <div class="mdetail-params"><ul>
57  * <li>For a list of available format functions, see {@link Ext.util.Format}.</li>
58  * <li><code>disableFormats</code> reduces <code>{@link #apply}</code> time
59  * when no formatting is required.</li>
60  * </ul></div>
61  * </div></li>
62  * </ul></div>
63  * @param {Mixed} config
64  */
65
66 Ext.define('Ext.Template', {
67
68     /* Begin Definitions */
69
70     requires: ['Ext.core.DomHelper', 'Ext.util.Format'],
71
72     statics: {
73         /**
74          * Creates a template from the passed element's value (<i>display:none</i> textarea, preferred) or innerHTML.
75          * @param {String/HTMLElement} el A DOM element or its id
76          * @param {Object} config A configuration object
77          * @return {Ext.Template} The created template
78          * @static
79          */
80         from: function(el, config) {
81             el = Ext.getDom(el);
82             return new this(el.value || el.innerHTML, config || '');
83         }
84     },
85
86     /* End Definitions */
87
88     constructor: function(html) {
89         var me = this,
90             args = arguments,
91             buffer = [],
92             i = 0,
93             length = args.length,
94             value;
95
96         me.initialConfig = {};
97
98         if (length > 1) {
99             for (; i < length; i++) {
100                 value = args[i];
101                 if (typeof value == 'object') {
102                     Ext.apply(me.initialConfig, value);
103                     Ext.apply(me, value);
104                 } else {
105                     buffer.push(value);
106                 }
107             }
108             html = buffer.join('');
109         } else {
110             if (Ext.isArray(html)) {
111                 buffer.push(html.join(''));
112             } else {
113                 buffer.push(html);
114             }
115         }
116
117         // @private
118         me.html = buffer.join('');
119
120         if (me.compiled) {
121             me.compile();
122         }
123     },
124     isTemplate: true,
125     /**
126      * @cfg {Boolean} disableFormats true to disable format functions in the template. If the template doesn't contain format functions, setting
127      * disableFormats to true will reduce apply time (defaults to false)
128      */
129     disableFormats: false,
130
131     re: /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
132     /**
133      * Returns an HTML fragment of this template with the specified values applied.
134      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
135      * @return {String} The HTML fragment
136      */
137     applyTemplate: function(values) {
138         var me = this,
139             useFormat = me.disableFormats !== true,
140             fm = Ext.util.Format,
141             tpl = me;
142
143         if (me.compiled) {
144             return me.compiled(values);
145         }
146         function fn(m, name, format, args) {
147             if (format && useFormat) {
148                 if (args) {
149                     args = [values[name]].concat(Ext.functionFactory('return ['+ args +'];')());
150                 } else {
151                     args = [values[name]];
152                 }
153                 if (format.substr(0, 5) == "this.") {
154                     return tpl[format.substr(5)].apply(tpl, args);
155                 }
156                 else {
157                     return fm[format].apply(fm, args);
158                 }
159             }
160             else {
161                 return values[name] !== undefined ? values[name] : "";
162             }
163         }
164         return me.html.replace(me.re, fn);
165     },
166
167     /**
168      * Sets the HTML used as the template and optionally compiles it.
169      * @param {String} html
170      * @param {Boolean} compile (optional) True to compile the template (defaults to undefined)
171      * @return {Ext.Template} this
172      */
173     set: function(html, compile) {
174         var me = this;
175         me.html = html;
176         me.compiled = null;
177         return compile ? me.compile() : me;
178     },
179
180     compileARe: /\\/g,
181     compileBRe: /(\r\n|\n)/g,
182     compileCRe: /'/g,
183     /**
184      * Compiles the template into an internal function, eliminating the RegEx overhead.
185      * @return {Ext.Template} this
186      */
187     compile: function() {
188         var me = this,
189             fm = Ext.util.Format,
190             useFormat = me.disableFormats !== true,
191             body, bodyReturn;
192
193         function fn(m, name, format, args) {
194             if (format && useFormat) {
195                 args = args ? ',' + args: "";
196                 if (format.substr(0, 5) != "this.") {
197                     format = "fm." + format + '(';
198                 }
199                 else {
200                     format = 'this.' + format.substr(5) + '(';
201                 }
202             }
203             else {
204                 args = '';
205                 format = "(values['" + name + "'] == undefined ? '' : ";
206             }
207             return "'," + format + "values['" + name + "']" + args + ") ,'";
208         }
209
210         bodyReturn = me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn);
211         body = "this.compiled = function(values){ return ['" + bodyReturn + "'].join('');};";
212         eval(body);
213         return me;
214     },
215
216     /**
217      * Applies the supplied values to the template and inserts the new node(s) as the first child of el.
218      * @param {Mixed} el The context element
219      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
220      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
221      * @return {HTMLElement/Ext.core.Element} The new node or Element
222      */
223     insertFirst: function(el, values, returnElement) {
224         return this.doInsert('afterBegin', el, values, returnElement);
225     },
226
227     /**
228      * Applies the supplied values to the template and inserts the new node(s) before el.
229      * @param {Mixed} el The context element
230      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
231      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
232      * @return {HTMLElement/Ext.core.Element} The new node or Element
233      */
234     insertBefore: function(el, values, returnElement) {
235         return this.doInsert('beforeBegin', el, values, returnElement);
236     },
237
238     /**
239      * Applies the supplied values to the template and inserts the new node(s) after el.
240      * @param {Mixed} el The context element
241      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
242      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
243      * @return {HTMLElement/Ext.core.Element} The new node or Element
244      */
245     insertAfter: function(el, values, returnElement) {
246         return this.doInsert('afterEnd', el, values, returnElement);
247     },
248
249     /**
250      * Applies the supplied <code>values</code> to the template and appends
251      * the new node(s) to the specified <code>el</code>.
252      * <p>For example usage {@link #Template see the constructor}.</p>
253      * @param {Mixed} el The context element
254      * @param {Object/Array} values
255      * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
256      * or an object (i.e. <code>{foo: 'bar'}</code>).
257      * @param {Boolean} returnElement (optional) true to return an Ext.core.Element (defaults to undefined)
258      * @return {HTMLElement/Ext.core.Element} The new node or Element
259      */
260     append: function(el, values, returnElement) {
261         return this.doInsert('beforeEnd', el, values, returnElement);
262     },
263
264     doInsert: function(where, el, values, returnEl) {
265         el = Ext.getDom(el);
266         var newNode = Ext.core.DomHelper.insertHtml(where, el, this.applyTemplate(values));
267         return returnEl ? Ext.get(newNode, true) : newNode;
268     },
269
270     /**
271      * Applies the supplied values to the template and overwrites the content of el with the new node(s).
272      * @param {Mixed} el The context element
273      * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
274      * @param {Boolean} returnElement (optional) true to return a Ext.core.Element (defaults to undefined)
275      * @return {HTMLElement/Ext.core.Element} The new node or Element
276      */
277     overwrite: function(el, values, returnElement) {
278         el = Ext.getDom(el);
279         el.innerHTML = this.applyTemplate(values);
280         return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
281     }
282 }, function() {
283
284     /**
285      * Alias for {@link #applyTemplate}
286      * Returns an HTML fragment of this template with the specified <code>values</code> applied.
287      * @param {Object/Array} values
288      * The template values. Can be an array if the params are numeric (i.e. <code>{0}</code>)
289      * or an object (i.e. <code>{foo: 'bar'}</code>).
290      * @return {String} The HTML fragment
291      * @member Ext.Template
292      * @method apply
293      */
294     this.createAlias('apply', 'applyTemplate');
295 });
296