IE9 - refine createContextualFragment fix
[roojs1] / Roo / DomHelper.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11
12
13 // nasty IE9 hack - what a pile of crap that is..
14
15 <<<<<<< HEAD
16  if (typeof Range.prototype.createContextualFragment == "undefined") {
17 =======
18  if (typeof Range != "undefined" && typeof Range.prototype.createContextualFragment == "undefined") {
19 >>>>>>> cba88e023db3bc6c12affc2e25a149cdac04fd17
20     Range.prototype.createContextualFragment = function (html) {
21         var doc = window.document;
22         var container = doc.createElement("div");
23         container.innerHTML = html;
24         var frag = doc.createDocumentFragment(), n;
25         while ((n = container.firstChild)) {
26             frag.appendChild(n);
27         }
28         return frag;
29     };
30 }
31
32 /**
33  * @class Roo.DomHelper
34  * Utility class for working with DOM and/or Templates. It transparently supports using HTML fragments or DOM.
35  * For more information see <a href="http://www.jackslocum.com/yui/2006/10/06/domhelper-create-elements-using-dom-html-fragments-or-templates/">this blog post with examples</a>.
36  * @singleton
37  */
38 Roo.DomHelper = function(){
39     var tempTableEl = null;
40     var emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i;
41     var tableRe = /^table|tbody|tr|td$/i;
42     var xmlns = {};
43     // build as innerHTML where available
44     /** @ignore */
45     var createHtml = function(o){
46         if(typeof o == 'string'){
47             return o;
48         }
49         var b = "";
50         if(!o.tag){
51             o.tag = "div";
52         }
53         b += "<" + o.tag;
54         for(var attr in o){
55             if(attr == "tag" || attr == "children" || attr == "cn" || attr == "html" || typeof o[attr] == "function") continue;
56             if(attr == "style"){
57                 var s = o["style"];
58                 if(typeof s == "function"){
59                     s = s.call();
60                 }
61                 if(typeof s == "string"){
62                     b += ' style="' + s + '"';
63                 }else if(typeof s == "object"){
64                     b += ' style="';
65                     for(var key in s){
66                         if(typeof s[key] != "function"){
67                             b += key + ":" + s[key] + ";";
68                         }
69                     }
70                     b += '"';
71                 }
72             }else{
73                 if(attr == "cls"){
74                     b += ' class="' + o["cls"] + '"';
75                 }else if(attr == "htmlFor"){
76                     b += ' for="' + o["htmlFor"] + '"';
77                 }else{
78                     b += " " + attr + '="' + o[attr] + '"';
79                 }
80             }
81         }
82         if(emptyTags.test(o.tag)){
83             b += "/>";
84         }else{
85             b += ">";
86             var cn = o.children || o.cn;
87             if(cn){
88                 //http://bugs.kde.org/show_bug.cgi?id=71506
89                 if((cn instanceof Array) || (Roo.isSafari && typeof(cn.join) == "function")){
90                     for(var i = 0, len = cn.length; i < len; i++) {
91                         b += createHtml(cn[i], b);
92                     }
93                 }else{
94                     b += createHtml(cn, b);
95                 }
96             }
97             if(o.html){
98                 b += o.html;
99             }
100             b += "</" + o.tag + ">";
101         }
102         return b;
103     };
104
105     // build as dom
106     /** @ignore */
107     var createDom = function(o, parentNode){
108          
109         // defininition craeted..
110         var ns = false;
111         if (o.ns && o.ns != 'html') {
112                
113             if (o.xmlns && typeof(xmlns[o.ns]) == 'undefined') {
114                 xmlns[o.ns] = o.xmlns;
115                 ns = o.xmlns;
116             }
117             if (typeof(xmlns[o.ns]) == 'undefined') {
118                 console.log("Trying to create namespace element " + o.ns + ", however no xmlns was sent to builder previously");
119             }
120             ns = xmlns[o.ns];
121         }
122         
123         
124         if (typeof(o) == 'string') {
125             return parentNode.appendChild(document.createTextNode(o));
126         }
127         o.tag = o.tag || div;
128         if (o.ns && Roo.isIE) {
129             ns = false;
130             o.tag = o.ns + ':' + o.tag;
131             
132         }
133         var el = ns ? document.createElementNS( ns, o.tag||'div') :  document.createElement(o.tag||'div');
134         var useSet = el.setAttribute ? true : false; // In IE some elements don't have setAttribute
135         for(var attr in o){
136             
137             if(attr == "tag" || attr == "ns" ||attr == "xmlns" ||attr == "children" || attr == "cn" || attr == "html" || 
138                     attr == "style" || typeof o[attr] == "function") continue;
139                     
140             if(attr=="cls" && Roo.isIE){
141                 el.className = o["cls"];
142             }else{
143                 if(useSet) el.setAttribute(attr=="cls" ? 'class' : attr, o[attr]);
144                 else el[attr] = o[attr];
145             }
146         }
147         Roo.DomHelper.applyStyles(el, o.style);
148         var cn = o.children || o.cn;
149         if(cn){
150             //http://bugs.kde.org/show_bug.cgi?id=71506
151              if((cn instanceof Array) || (Roo.isSafari && typeof(cn.join) == "function")){
152                 for(var i = 0, len = cn.length; i < len; i++) {
153                     createDom(cn[i], el);
154                 }
155             }else{
156                 createDom(cn, el);
157             }
158         }
159         if(o.html){
160             el.innerHTML = o.html;
161         }
162         if(parentNode){
163            parentNode.appendChild(el);
164         }
165         return el;
166     };
167
168     var ieTable = function(depth, s, h, e){
169         tempTableEl.innerHTML = [s, h, e].join('');
170         var i = -1, el = tempTableEl;
171         while(++i < depth){
172             el = el.firstChild;
173         }
174         return el;
175     };
176
177     // kill repeat to save bytes
178     var ts = '<table>',
179         te = '</table>',
180         tbs = ts+'<tbody>',
181         tbe = '</tbody>'+te,
182         trs = tbs + '<tr>',
183         tre = '</tr>'+tbe;
184
185     /**
186      * @ignore
187      * Nasty code for IE's broken table implementation
188      */
189     var insertIntoTable = function(tag, where, el, html){
190         if(!tempTableEl){
191             tempTableEl = document.createElement('div');
192         }
193         var node;
194         var before = null;
195         if(tag == 'td'){
196             if(where == 'afterbegin' || where == 'beforeend'){ // INTO a TD
197                 return;
198             }
199             if(where == 'beforebegin'){
200                 before = el;
201                 el = el.parentNode;
202             } else{
203                 before = el.nextSibling;
204                 el = el.parentNode;
205             }
206             node = ieTable(4, trs, html, tre);
207         }
208         else if(tag == 'tr'){
209             if(where == 'beforebegin'){
210                 before = el;
211                 el = el.parentNode;
212                 node = ieTable(3, tbs, html, tbe);
213             } else if(where == 'afterend'){
214                 before = el.nextSibling;
215                 el = el.parentNode;
216                 node = ieTable(3, tbs, html, tbe);
217             } else{ // INTO a TR
218                 if(where == 'afterbegin'){
219                     before = el.firstChild;
220                 }
221                 node = ieTable(4, trs, html, tre);
222             }
223         } else if(tag == 'tbody'){
224             if(where == 'beforebegin'){
225                 before = el;
226                 el = el.parentNode;
227                 node = ieTable(2, ts, html, te);
228             } else if(where == 'afterend'){
229                 before = el.nextSibling;
230                 el = el.parentNode;
231                 node = ieTable(2, ts, html, te);
232             } else{
233                 if(where == 'afterbegin'){
234                     before = el.firstChild;
235                 }
236                 node = ieTable(3, tbs, html, tbe);
237             }
238         } else{ // TABLE
239             if(where == 'beforebegin' || where == 'afterend'){ // OUTSIDE the table
240                 return;
241             }
242             if(where == 'afterbegin'){
243                 before = el.firstChild;
244             }
245             node = ieTable(2, ts, html, te);
246         }
247         el.insertBefore(node, before);
248         return node;
249     };
250
251     return {
252     /** True to force the use of DOM instead of html fragments @type Boolean */
253     useDom : false,
254
255     /**
256      * Returns the markup for the passed Element(s) config
257      * @param {Object} o The Dom object spec (and children)
258      * @return {String}
259      */
260     markup : function(o){
261         return createHtml(o);
262     },
263
264     /**
265      * Applies a style specification to an element
266      * @param {String/HTMLElement} el The element to apply styles to
267      * @param {String/Object/Function} styles A style specification string eg "width:100px", or object in the form {width:"100px"}, or
268      * a function which returns such a specification.
269      */
270     applyStyles : function(el, styles){
271         if(styles){
272            el = Roo.fly(el);
273            if(typeof styles == "string"){
274                var re = /\s?([a-z\-]*)\:\s?([^;]*);?/gi;
275                var matches;
276                while ((matches = re.exec(styles)) != null){
277                    el.setStyle(matches[1], matches[2]);
278                }
279            }else if (typeof styles == "object"){
280                for (var style in styles){
281                   el.setStyle(style, styles[style]);
282                }
283            }else if (typeof styles == "function"){
284                 Roo.DomHelper.applyStyles(el, styles.call());
285            }
286         }
287     },
288
289     /**
290      * Inserts an HTML fragment into the Dom
291      * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
292      * @param {HTMLElement} el The context element
293      * @param {String} html The HTML fragmenet
294      * @return {HTMLElement} The new node
295      */
296     insertHtml : function(where, el, html){
297         where = where.toLowerCase();
298         if(el.insertAdjacentHTML){
299             if(tableRe.test(el.tagName)){
300                 var rs;
301                 if(rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html)){
302                     return rs;
303                 }
304             }
305             switch(where){
306                 case "beforebegin":
307                     el.insertAdjacentHTML('BeforeBegin', html);
308                     return el.previousSibling;
309                 case "afterbegin":
310                     el.insertAdjacentHTML('AfterBegin', html);
311                     return el.firstChild;
312                 case "beforeend":
313                     el.insertAdjacentHTML('BeforeEnd', html);
314                     return el.lastChild;
315                 case "afterend":
316                     el.insertAdjacentHTML('AfterEnd', html);
317                     return el.nextSibling;
318             }
319             throw 'Illegal insertion point -> "' + where + '"';
320         }
321         var range = el.ownerDocument.createRange();
322         var frag;
323         switch(where){
324              case "beforebegin":
325                 range.setStartBefore(el);
326                 frag = range.createContextualFragment(html);
327                 el.parentNode.insertBefore(frag, el);
328                 return el.previousSibling;
329              case "afterbegin":
330                 if(el.firstChild){
331                     range.setStartBefore(el.firstChild);
332                     frag = range.createContextualFragment(html);
333                     el.insertBefore(frag, el.firstChild);
334                     return el.firstChild;
335                 }else{
336                     el.innerHTML = html;
337                     return el.firstChild;
338                 }
339             case "beforeend":
340                 if(el.lastChild){
341                     range.setStartAfter(el.lastChild);
342                     frag = range.createContextualFragment(html);
343                     el.appendChild(frag);
344                     return el.lastChild;
345                 }else{
346                     el.innerHTML = html;
347                     return el.lastChild;
348                 }
349             case "afterend":
350                 range.setStartAfter(el);
351                 frag = range.createContextualFragment(html);
352                 el.parentNode.insertBefore(frag, el.nextSibling);
353                 return el.nextSibling;
354             }
355             throw 'Illegal insertion point -> "' + where + '"';
356     },
357
358     /**
359      * Creates new Dom element(s) and inserts them before el
360      * @param {String/HTMLElement/Element} 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 Roo.Element
363      * @return {HTMLElement/Roo.Element} The new node
364      */
365     insertBefore : function(el, o, returnElement){
366         return this.doInsert(el, o, returnElement, "beforeBegin");
367     },
368
369     /**
370      * Creates new Dom element(s) and inserts them after el
371      * @param {String/HTMLElement/Element} el The context element
372      * @param {Object} o The Dom object spec (and children)
373      * @param {Boolean} returnElement (optional) true to return a Roo.Element
374      * @return {HTMLElement/Roo.Element} The new node
375      */
376     insertAfter : function(el, o, returnElement){
377         return this.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 {String/HTMLElement/Element} 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 Roo.Element
385      * @return {HTMLElement/Roo.Element} The new node
386      */
387     insertFirst : function(el, o, returnElement){
388         return this.doInsert(el, o, returnElement, "afterBegin");
389     },
390
391     // private
392     doInsert : function(el, o, returnElement, pos, sibling){
393         el = Roo.getDom(el);
394         var newNode;
395         if(this.useDom || o.ns){
396             newNode = createDom(o, null);
397             el.parentNode.insertBefore(newNode, sibling ? el[sibling] : el);
398         }else{
399             var html = createHtml(o);
400             newNode = this.insertHtml(pos, el, html);
401         }
402         return returnElement ? Roo.get(newNode, true) : newNode;
403     },
404
405     /**
406      * Creates new Dom element(s) and appends them to el
407      * @param {String/HTMLElement/Element} el The context element
408      * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
409      * @param {Boolean} returnElement (optional) true to return a Roo.Element
410      * @return {HTMLElement/Roo.Element} The new node
411      */
412     append : function(el, o, returnElement){
413         el = Roo.getDom(el);
414         var newNode;
415         if(this.useDom || o.ns){
416             newNode = createDom(o, null);
417             el.appendChild(newNode);
418         }else{
419             var html = createHtml(o);
420             newNode = this.insertHtml("beforeEnd", el, html);
421         }
422         return returnElement ? Roo.get(newNode, true) : newNode;
423     },
424
425     /**
426      * Creates new Dom element(s) and overwrites the contents of el with them
427      * @param {String/HTMLElement/Element} el The context element
428      * @param {Object/String} o The Dom object spec (and children) or raw HTML blob
429      * @param {Boolean} returnElement (optional) true to return a Roo.Element
430      * @return {HTMLElement/Roo.Element} The new node
431      */
432     overwrite : function(el, o, returnElement){
433         el = Roo.getDom(el);
434         if (o.ns) {
435           
436             while (el.childNodes.length) {
437                 el.removeChild(el.firstChild);
438             }
439             createDom(o, el);
440         } else {
441             el.innerHTML = createHtml(o);   
442         }
443         
444         return returnElement ? Roo.get(el.firstChild, true) : el.firstChild;
445     },
446
447     /**
448      * Creates a new Roo.DomHelper.Template from the Dom object spec
449      * @param {Object} o The Dom object spec (and children)
450      * @return {Roo.DomHelper.Template} The new template
451      */
452     createTemplate : function(o){
453         var html = createHtml(o);
454         return new Roo.Template(html);
455     }
456     };
457 }();