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