Upgrade to ExtJS 3.2.0 - Released 03/30/2010
[extjs.git] / src / ext-core / src / core / DomHelper.js
1 /*!
2  * Ext JS Library 3.2.0
3  * Copyright(c) 2006-2010 Ext JS, Inc.
4  * licensing@extjs.com
5  * http://www.extjs.com/license
6  */
7 /**
8  * @class Ext.DomHelper
9  * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
10  * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
11  * from your DOM building code.</p>
12  *
13  * <p><b><u>DomHelper element specification object</u></b></p>
14  * <p>A specification object is used when creating elements. Attributes of this object
15  * are assumed to be element attributes, except for 4 special attributes:
16  * <div class="mdetail-params"><ul>
17  * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
18  * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
19  * same kind of element definition objects to be created and appended. These can be nested
20  * as deep as you want.</div></li>
21  * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
22  * This will end up being either the "class" attribute on a HTML fragment or className
23  * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
24  * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
25  * </ul></div></p>
26  *
27  * <p><b><u>Insertion methods</u></b></p>
28  * <p>Commonly used insertion methods:
29  * <div class="mdetail-params"><ul>
30  * <li><b><tt>{@link #append}</tt></b> : <div class="sub-desc"></div></li>
31  * <li><b><tt>{@link #insertBefore}</tt></b> : <div class="sub-desc"></div></li>
32  * <li><b><tt>{@link #insertAfter}</tt></b> : <div class="sub-desc"></div></li>
33  * <li><b><tt>{@link #overwrite}</tt></b> : <div class="sub-desc"></div></li>
34  * <li><b><tt>{@link #createTemplate}</tt></b> : <div class="sub-desc"></div></li>
35  * <li><b><tt>{@link #insertHtml}</tt></b> : <div class="sub-desc"></div></li>
36  * </ul></div></p>
37  *
38  * <p><b><u>Example</u></b></p>
39  * <p>This is an example, where an unordered list with 3 children items is appended to an existing
40  * element with id <tt>'my-div'</tt>:<br>
41  <pre><code>
42 var dh = Ext.DomHelper; // create shorthand alias
43 // specification object
44 var spec = {
45     id: 'my-ul',
46     tag: 'ul',
47     cls: 'my-list',
48     // append children after creating
49     children: [     // may also specify 'cn' instead of 'children'
50         {tag: 'li', id: 'item0', html: 'List Item 0'},
51         {tag: 'li', id: 'item1', html: 'List Item 1'},
52         {tag: 'li', id: 'item2', html: 'List Item 2'}
53     ]
54 };
55 var list = dh.append(
56     'my-div', // the context element 'my-div' can either be the id or the actual node
57     spec      // the specification object
58 );
59  </code></pre></p>
60  * <p>Element creation specification parameters in this class may also be passed as an Array of
61  * specification objects. This can be used to insert multiple sibling nodes into an existing
62  * container very efficiently. For example, to add more list items to the example above:<pre><code>
63 dh.append('my-ul', [
64     {tag: 'li', id: 'item3', html: 'List Item 3'},
65     {tag: 'li', id: 'item4', html: 'List Item 4'}
66 ]);
67  * </code></pre></p>
68  *
69  * <p><b><u>Templating</u></b></p>
70  * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
71  * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
72  * insert new elements. Revisiting the example above, we could utilize templating this time:
73  * <pre><code>
74 // create the node
75 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
76 // get template
77 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
78
79 for(var i = 0; i < 5, i++){
80     tpl.append(list, [i]); // use template to append to the actual node
81 }
82  * </code></pre></p>
83  * <p>An example using a template:<pre><code>
84 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
85
86 var tpl = new Ext.DomHelper.createTemplate(html);
87 tpl.append('blog-roll', ['link1', 'http://www.jackslocum.com/', "Jack&#39;s Site"]);
88 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin&#39;s Site"]);
89  * </code></pre></p>
90  *
91  * <p>The same example using named parameters:<pre><code>
92 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
93
94 var tpl = new Ext.DomHelper.createTemplate(html);
95 tpl.append('blog-roll', {
96     id: 'link1',
97     url: 'http://www.jackslocum.com/',
98     text: "Jack&#39;s Site"
99 });
100 tpl.append('blog-roll', {
101     id: 'link2',
102     url: 'http://www.dustindiaz.com/',
103     text: "Dustin&#39;s Site"
104 });
105  * </code></pre></p>
106  *
107  * <p><b><u>Compiling Templates</u></b></p>
108  * <p>Templates are applied using regular expressions. The performance is great, but if
109  * you are adding a bunch of DOM elements using the same template, you can increase
110  * performance even further by {@link Ext.Template#compile "compiling"} the template.
111  * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
112  * broken up at the different variable points and a dynamic function is created and eval'ed.
113  * The generated function performs string concatenation of these parts and the passed
114  * variables instead of using regular expressions.
115  * <pre><code>
116 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
117
118 var tpl = new Ext.DomHelper.createTemplate(html);
119 tpl.compile();
120
121 //... use template like normal
122  * </code></pre></p>
123  *
124  * <p><b><u>Performance Boost</u></b></p>
125  * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
126  * of DOM can significantly boost performance.</p>
127  * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
128  * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
129  * results in the creation of a text node. Usage:</p>
130  * <pre><code>
131 Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
132  * </code></pre>
133  * @singleton
134  */
135 Ext.DomHelper = function(){
136     var tempTableEl = null,
137         emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
138         tableRe = /^table|tbody|tr|td$/i,
139         pub,
140         // kill repeat to save bytes
141         afterbegin = 'afterbegin',
142         afterend = 'afterend',
143         beforebegin = 'beforebegin',
144         beforeend = 'beforeend',
145         ts = '<table>',
146         te = '</table>',
147         tbs = ts+'<tbody>',
148         tbe = '</tbody>'+te,
149         trs = tbs + '<tr>',
150         tre = '</tr>'+tbe;
151
152     // private
153     function doInsert(el, o, returnElement, pos, sibling, append){
154         var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o));
155         return returnElement ? Ext.get(newNode, true) : newNode;
156     }
157
158     // build as innerHTML where available
159     function createHtml(o){
160         var b = '',
161             attr,
162             val,
163             key,
164             keyVal,
165             cn;
166
167         if(Ext.isString(o)){
168             b = o;
169         } else if (Ext.isArray(o)) {
170             for (var i=0; i < o.length; i++) {
171                 if(o[i]) {
172                     b += createHtml(o[i]);
173                 }
174             };
175         } else {
176             b += '<' + (o.tag = o.tag || 'div');
177             Ext.iterate(o, function(attr, val){
178                 if(!/tag|children|cn|html$/i.test(attr)){
179                     if (Ext.isObject(val)) {
180                         b += ' ' + attr + '="';
181                         Ext.iterate(val, function(key, keyVal){
182                             b += key + ':' + keyVal + ';';
183                         });
184                         b += '"';
185                     }else{
186                         b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
187                     }
188                 }
189             });
190             // Now either just close the tag or try to add children and close the tag.
191             if (emptyTags.test(o.tag)) {
192                 b += '/>';
193             } else {
194                 b += '>';
195                 if ((cn = o.children || o.cn)) {
196                     b += createHtml(cn);
197                 } else if(o.html){
198                     b += o.html;
199                 }
200                 b += '</' + o.tag + '>';
201             }
202         }
203         return b;
204     }
205
206     function ieTable(depth, s, h, e){
207         tempTableEl.innerHTML = [s, h, e].join('');
208         var i = -1,
209             el = tempTableEl,
210             ns;
211         while(++i < depth){
212             el = el.firstChild;
213         }
214 //      If the result is multiple siblings, then encapsulate them into one fragment.
215         if(ns = el.nextSibling){
216             var df = document.createDocumentFragment();
217             while(el){
218                 ns = el.nextSibling;
219                 df.appendChild(el);
220                 el = ns;
221             }
222             el = df;
223         }
224         return el;
225     }
226
227     /**
228      * @ignore
229      * Nasty code for IE's broken table implementation
230      */
231     function insertIntoTable(tag, where, el, html) {
232         var node,
233             before;
234
235         tempTableEl = tempTableEl || document.createElement('div');
236
237         if(tag == 'td' && (where == afterbegin || where == beforeend) ||
238            !/td|tr|tbody/i.test(tag) && (where == beforebegin || where == afterend)) {
239             return;
240         }
241         before = where == beforebegin ? el :
242                  where == afterend ? el.nextSibling :
243                  where == afterbegin ? el.firstChild : null;
244
245         if (where == beforebegin || where == afterend) {
246             el = el.parentNode;
247         }
248
249         if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
250             node = ieTable(4, trs, html, tre);
251         } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
252                    (tag == 'tr' && (where == beforebegin || where == afterend))) {
253             node = ieTable(3, tbs, html, tbe);
254         } else {
255             node = ieTable(2, ts, html, te);
256         }
257         el.insertBefore(node, before);
258         return node;
259     }
260
261
262     pub = {
263         /**
264          * Returns the markup for the passed Element(s) config.
265          * @param {Object} o The DOM object spec (and children)
266          * @return {String}
267          */
268         markup : function(o){
269             return createHtml(o);
270         },
271         
272         /**
273          * Applies a style specification to an element.
274          * @param {String/HTMLElement} el The element to apply styles to
275          * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
276          * a function which returns such a specification.
277          */
278         applyStyles : function(el, styles){
279             if(styles){
280                 var i = 0,
281                     len,
282                     style;
283
284                 el = Ext.fly(el);
285                 if(Ext.isFunction(styles)){
286                     styles = styles.call();
287                 }
288                 if(Ext.isString(styles)){
289                     styles = styles.trim().split(/\s*(?::|;)\s*/);
290                     for(len = styles.length; i < len;){
291                         el.setStyle(styles[i++], styles[i++]);
292                     }
293                 }else if (Ext.isObject(styles)){
294                     el.setStyle(styles);
295                 }
296             }
297         },
298
299         /**
300          * Inserts an HTML fragment into the DOM.
301          * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
302          * @param {HTMLElement} el The context element
303          * @param {String} html The HTML fragment
304          * @return {HTMLElement} The new node
305          */
306         insertHtml : function(where, el, html){
307             var hash = {},
308                 hashVal,
309                 setStart,
310                 range,
311                 frag,
312                 rangeEl,
313                 rs;
314
315             where = where.toLowerCase();
316             // add these here because they are used in both branches of the condition.
317             hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
318             hash[afterend] = ['AfterEnd', 'nextSibling'];
319
320             if (el.insertAdjacentHTML) {
321                 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
322                     return rs;
323                 }
324                 // add these two to the hash.
325                 hash[afterbegin] = ['AfterBegin', 'firstChild'];
326                 hash[beforeend] = ['BeforeEnd', 'lastChild'];
327                 if ((hashVal = hash[where])) {
328                     el.insertAdjacentHTML(hashVal[0], html);
329                     return el[hashVal[1]];
330                 }
331             } else {
332                 range = el.ownerDocument.createRange();
333                 setStart = 'setStart' + (/end/i.test(where) ? 'After' : 'Before');
334                 if (hash[where]) {
335                     range[setStart](el);
336                     frag = range.createContextualFragment(html);
337                     el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
338                     return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
339                 } else {
340                     rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
341                     if (el.firstChild) {
342                         range[setStart](el[rangeEl]);
343                         frag = range.createContextualFragment(html);
344                         if(where == afterbegin){
345                             el.insertBefore(frag, el.firstChild);
346                         }else{
347                             el.appendChild(frag);
348                         }
349                     } else {
350                         el.innerHTML = html;
351                     }
352                     return el[rangeEl];
353                 }
354             }
355             throw 'Illegal insertion point -> "' + where + '"';
356         },
357
358         /**
359          * Creates new DOM element(s) and inserts them before el.
360          * @param {Mixed} el The context element
361          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
362          * @param {Boolean} returnElement (optional) true to return a Ext.Element
363          * @return {HTMLElement/Ext.Element} The new node
364          */
365         insertBefore : function(el, o, returnElement){
366             return doInsert(el, o, returnElement, beforebegin);
367         },
368
369         /**
370          * Creates new DOM element(s) and inserts them after el.
371          * @param {Mixed} el The context element
372          * @param {Object} o The DOM object spec (and children)
373          * @param {Boolean} returnElement (optional) true to return a Ext.Element
374          * @return {HTMLElement/Ext.Element} The new node
375          */
376         insertAfter : function(el, o, returnElement){
377             return doInsert(el, o, returnElement, afterend, 'nextSibling');
378         },
379
380         /**
381          * Creates new DOM element(s) and inserts them as the first child of el.
382          * @param {Mixed} el The context element
383          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
384          * @param {Boolean} returnElement (optional) true to return a Ext.Element
385          * @return {HTMLElement/Ext.Element} The new node
386          */
387         insertFirst : function(el, o, returnElement){
388             return doInsert(el, o, returnElement, afterbegin, 'firstChild');
389         },
390
391         /**
392          * Creates new DOM element(s) and appends them to el.
393          * @param {Mixed} el The context element
394          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
395          * @param {Boolean} returnElement (optional) true to return a Ext.Element
396          * @return {HTMLElement/Ext.Element} The new node
397          */
398         append : function(el, o, returnElement){
399             return doInsert(el, o, returnElement, beforeend, '', true);
400         },
401
402         /**
403          * Creates new DOM element(s) and overwrites the contents of el with them.
404          * @param {Mixed} el The context element
405          * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
406          * @param {Boolean} returnElement (optional) true to return a Ext.Element
407          * @return {HTMLElement/Ext.Element} The new node
408          */
409         overwrite : function(el, o, returnElement){
410             el = Ext.getDom(el);
411             el.innerHTML = createHtml(o);
412             return returnElement ? Ext.get(el.firstChild) : el.firstChild;
413         },
414
415         createHtml : createHtml
416     };
417     return pub;
418 }();