3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
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.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
16 * @class Ext.core.DomHelper
17 * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
18 * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
19 * from your DOM building code.</p>
21 * <p><b><u>DomHelper element specification object</u></b></p>
22 * <p>A specification object is used when creating elements. Attributes of this object
23 * are assumed to be element attributes, except for 4 special attributes:
24 * <div class="mdetail-params"><ul>
25 * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
26 * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
27 * same kind of element definition objects to be created and appended. These can be nested
28 * as deep as you want.</div></li>
29 * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
30 * This will end up being either the "class" attribute on a HTML fragment or className
31 * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
32 * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
34 * <p><b>NOTE:</b> For other arbitrary attributes, the value will currently <b>not</b> be automatically
35 * HTML-escaped prior to building the element's HTML string. This means that if your attribute value
36 * contains special characters that would not normally be allowed in a double-quoted attribute value,
37 * you <b>must</b> manually HTML-encode it beforehand (see {@link Ext.String#htmlEncode}) or risk
38 * malformed HTML being created. This behavior may change in a future release.</p>
40 * <p><b><u>Insertion methods</u></b></p>
41 * <p>Commonly used insertion methods:
42 * <div class="mdetail-params"><ul>
43 * <li><b><tt>{@link #append}</tt></b> : <div class="sub-desc"></div></li>
44 * <li><b><tt>{@link #insertBefore}</tt></b> : <div class="sub-desc"></div></li>
45 * <li><b><tt>{@link #insertAfter}</tt></b> : <div class="sub-desc"></div></li>
46 * <li><b><tt>{@link #overwrite}</tt></b> : <div class="sub-desc"></div></li>
47 * <li><b><tt>{@link #createTemplate}</tt></b> : <div class="sub-desc"></div></li>
48 * <li><b><tt>{@link #insertHtml}</tt></b> : <div class="sub-desc"></div></li>
51 * <p><b><u>Example</u></b></p>
52 * <p>This is an example, where an unordered list with 3 children items is appended to an existing
53 * element with id <tt>'my-div'</tt>:<br>
55 var dh = Ext.core.DomHelper; // create shorthand alias
56 // specification object
61 // append children after creating
62 children: [ // may also specify 'cn' instead of 'children'
63 {tag: 'li', id: 'item0', html: 'List Item 0'},
64 {tag: 'li', id: 'item1', html: 'List Item 1'},
65 {tag: 'li', id: 'item2', html: 'List Item 2'}
69 'my-div', // the context element 'my-div' can either be the id or the actual node
70 spec // the specification object
73 * <p>Element creation specification parameters in this class may also be passed as an Array of
74 * specification objects. This can be used to insert multiple sibling nodes into an existing
75 * container very efficiently. For example, to add more list items to the example above:<pre><code>
77 {tag: 'li', id: 'item3', html: 'List Item 3'},
78 {tag: 'li', id: 'item4', html: 'List Item 4'}
82 * <p><b><u>Templating</u></b></p>
83 * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
84 * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
85 * insert new elements. Revisiting the example above, we could utilize templating this time:
88 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
90 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
92 for(var i = 0; i < 5, i++){
93 tpl.append(list, [i]); // use template to append to the actual node
96 * <p>An example using a template:<pre><code>
97 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
99 var tpl = new Ext.core.DomHelper.createTemplate(html);
100 tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed's Site"]);
101 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
104 * <p>The same example using named parameters:<pre><code>
105 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
107 var tpl = new Ext.core.DomHelper.createTemplate(html);
108 tpl.append('blog-roll', {
110 url: 'http://www.edspencer.net/',
111 text: "Ed's Site"
113 tpl.append('blog-roll', {
115 url: 'http://www.dustindiaz.com/',
116 text: "Dustin's Site"
120 * <p><b><u>Compiling Templates</u></b></p>
121 * <p>Templates are applied using regular expressions. The performance is great, but if
122 * you are adding a bunch of DOM elements using the same template, you can increase
123 * performance even further by {@link Ext.Template#compile "compiling"} the template.
124 * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
125 * broken up at the different variable points and a dynamic function is created and eval'ed.
126 * The generated function performs string concatenation of these parts and the passed
127 * variables instead of using regular expressions.
129 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
131 var tpl = new Ext.core.DomHelper.createTemplate(html);
134 //... use template like normal
137 * <p><b><u>Performance Boost</u></b></p>
138 * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
139 * of DOM can significantly boost performance.</p>
140 * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
141 * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
142 * results in the creation of a text node. Usage:</p>
144 Ext.core.DomHelper.useDom = true; // force it to use DOM; reduces performance
149 Ext.core.DomHelper = function(){
150 var tempTableEl = null,
151 emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
152 tableRe = /^table|tbody|tr|td$/i,
153 confRe = /tag|children|cn|html$/i,
154 tableElRe = /td|tr|tbody/i,
157 // kill repeat to save bytes
158 afterbegin = 'afterbegin',
159 afterend = 'afterend',
160 beforebegin = 'beforebegin',
161 beforeend = 'beforeend',
170 function doInsert(el, o, returnElement, pos, sibling, append){
174 newNode = createDom(o, null);
176 el.appendChild(newNode);
178 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
181 newNode = Ext.core.DomHelper.insertHtml(pos, el, Ext.core.DomHelper.createHtml(o));
183 return returnElement ? Ext.get(newNode, true) : newNode;
186 function createDom(o, parentNode){
194 if (Ext.isArray(o)) { // Allow Arrays of siblings to be inserted
195 el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
196 for (var i = 0, l = o.length; i < l; i++) {
199 } else if (typeof o == 'string') { // Allow a string as a child spec.
200 el = doc.createTextNode(o);
202 el = doc.createElement( o.tag || 'div' );
203 useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
205 if(!confRe.test(attr)){
211 el.setAttribute(attr, val);
218 Ext.core.DomHelper.applyStyles(el, o.style);
220 if ((cn = o.children || o.cn)) {
223 el.innerHTML = o.html;
227 parentNode.appendChild(el);
232 // build as innerHTML where available
233 function createHtml(o){
241 if(typeof o == "string"){
243 } else if (Ext.isArray(o)) {
244 for (i=0; i < o.length; i++) {
246 b += createHtml(o[i]);
250 b += '<' + (o.tag = o.tag || 'div');
253 if(!confRe.test(attr)){
254 if (typeof val == "object") {
255 b += ' ' + attr + '="';
257 b += key + ':' + val[key] + ';';
261 b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
265 // Now either just close the tag or try to add children and close the tag.
266 if (emptyTags.test(o.tag)) {
270 if ((cn = o.children || o.cn)) {
275 b += '</' + o.tag + '>';
281 function ieTable(depth, s, h, e){
282 tempTableEl.innerHTML = [s, h, e].join('');
289 // If the result is multiple siblings, then encapsulate them into one fragment.
292 var df = document.createDocumentFragment();
305 * Nasty code for IE's broken table implementation
307 function insertIntoTable(tag, where, el, html) {
311 tempTableEl = tempTableEl || document.createElement('div');
313 if(tag == 'td' && (where == afterbegin || where == beforeend) ||
314 !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
317 before = where == beforebegin ? el :
318 where == afterend ? el.nextSibling :
319 where == afterbegin ? el.firstChild : null;
321 if (where == beforebegin || where == afterend) {
325 if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
326 node = ieTable(4, trs, html, tre);
327 } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
328 (tag == 'tr' && (where == beforebegin || where == afterend))) {
329 node = ieTable(3, tbs, html, tbe);
331 node = ieTable(2, ts, html, te);
333 el.insertBefore(node, before);
339 * Fix for IE9 createContextualFragment missing method
341 function createContextualFragment(html){
342 var div = document.createElement("div"),
343 fragment = document.createDocumentFragment(),
347 div.innerHTML = html;
348 childNodes = div.childNodes;
349 length = childNodes.length;
351 for (; i < length; i++) {
352 fragment.appendChild(childNodes[i].cloneNode(true));
360 * Returns the markup for the passed Element(s) config.
361 * @param {Object} o The DOM object spec (and children)
364 markup : function(o){
365 return createHtml(o);
369 * Applies a style specification to an element.
370 * @param {String/HTMLElement} el The element to apply styles to
371 * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
372 * a function which returns such a specification.
374 applyStyles : function(el, styles){
377 if (typeof styles == "function") {
378 styles = styles.call();
380 if (typeof styles == "string") {
381 styles = Ext.core.Element.parseStyles(styles);
383 if (typeof styles == "object") {
390 * Inserts an HTML fragment into the DOM.
391 * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
392 * @param {HTMLElement/TextNode} el The context element
393 * @param {String} html The HTML fragment
394 * @return {HTMLElement} The new node
396 insertHtml : function(where, el, html){
405 where = where.toLowerCase();
406 // add these here because they are used in both branches of the condition.
407 hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
408 hash[afterend] = ['AfterEnd', 'nextSibling'];
410 // if IE and context element is an HTMLElement
411 if (el.insertAdjacentHTML) {
412 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
416 // add these two to the hash.
417 hash[afterbegin] = ['AfterBegin', 'firstChild'];
418 hash[beforeend] = ['BeforeEnd', 'lastChild'];
419 if ((hashVal = hash[where])) {
420 el.insertAdjacentHTML(hashVal[0], html);
421 return el[hashVal[1]];
423 // if (not IE and context element is an HTMLElement) or TextNode
425 // we cannot insert anything inside a textnode so...
426 if (Ext.isTextNode(el)) {
427 where = where === 'afterbegin' ? 'beforebegin' : where;
428 where = where === 'beforeend' ? 'afterend' : where;
430 range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
431 setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
435 frag = range.createContextualFragment(html);
437 frag = createContextualFragment(html);
439 el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
440 return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
442 rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
445 range[setStart](el[rangeEl]);
446 frag = range.createContextualFragment(html);
448 frag = createContextualFragment(html);
451 if(where == afterbegin){
452 el.insertBefore(frag, el.firstChild);
454 el.appendChild(frag);
464 sourceClass: 'Ext.core.DomHelper',
465 sourceMethod: 'insertHtml',
468 msg: 'Illegal insertion point reached: "' + where + '"'
474 * Creates new DOM element(s) and inserts them before el.
475 * @param {Mixed} el The context element
476 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
477 * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
478 * @return {HTMLElement/Ext.core.Element} The new node
480 insertBefore : function(el, o, returnElement){
481 return doInsert(el, o, returnElement, beforebegin);
485 * Creates new DOM element(s) and inserts them after el.
486 * @param {Mixed} el The context element
487 * @param {Object} o The DOM object spec (and children)
488 * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
489 * @return {HTMLElement/Ext.core.Element} The new node
491 insertAfter : function(el, o, returnElement){
492 return doInsert(el, o, returnElement, afterend, 'nextSibling');
496 * Creates new DOM element(s) and inserts them as the first child of el.
497 * @param {Mixed} el The context element
498 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
499 * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
500 * @return {HTMLElement/Ext.core.Element} The new node
502 insertFirst : function(el, o, returnElement){
503 return doInsert(el, o, returnElement, afterbegin, 'firstChild');
507 * Creates new DOM element(s) and appends them to el.
508 * @param {Mixed} el The context element
509 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
510 * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
511 * @return {HTMLElement/Ext.core.Element} The new node
513 append : function(el, o, returnElement){
514 return doInsert(el, o, returnElement, beforeend, '', true);
518 * Creates new DOM element(s) and overwrites the contents of el with them.
519 * @param {Mixed} el The context element
520 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
521 * @param {Boolean} returnElement (optional) true to return a Ext.core.Element
522 * @return {HTMLElement/Ext.core.Element} The new node
524 overwrite : function(el, o, returnElement){
526 el.innerHTML = createHtml(o);
527 return returnElement ? Ext.get(el.firstChild) : el.firstChild;
530 createHtml : createHtml,
533 * Creates new DOM element(s) without inserting them to the document.
534 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
535 * @return {HTMLElement} The new uninserted node
538 createDom: createDom,
540 /** True to force the use of DOM instead of html fragments @type Boolean */
544 * Creates a new Ext.Template from the DOM object spec.
545 * @param {Object} o The DOM object spec (and children)
546 * @return {Ext.Template} The new template
548 createTemplate : function(o){
549 var html = Ext.core.DomHelper.createHtml(o);
550 return Ext.create('Ext.Template', html);