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.DomHelper
17 * @alternateClassName Ext.core.DomHelper
19 * <p>The DomHelper class provides a layer of abstraction from DOM and transparently supports creating
20 * elements via DOM or using HTML fragments. It also has the ability to create HTML fragment templates
21 * from your DOM building code.</p>
23 * <p><b><u>DomHelper element specification object</u></b></p>
24 * <p>A specification object is used when creating elements. Attributes of this object
25 * are assumed to be element attributes, except for 4 special attributes:
26 * <div class="mdetail-params"><ul>
27 * <li><b><tt>tag</tt></b> : <div class="sub-desc">The tag name of the element</div></li>
28 * <li><b><tt>children</tt></b> : or <tt>cn</tt><div class="sub-desc">An array of the
29 * same kind of element definition objects to be created and appended. These can be nested
30 * as deep as you want.</div></li>
31 * <li><b><tt>cls</tt></b> : <div class="sub-desc">The class attribute of the element.
32 * This will end up being either the "class" attribute on a HTML fragment or className
33 * for a DOM node, depending on whether DomHelper is using fragments or DOM.</div></li>
34 * <li><b><tt>html</tt></b> : <div class="sub-desc">The innerHTML for the element</div></li>
36 * <p><b>NOTE:</b> For other arbitrary attributes, the value will currently <b>not</b> be automatically
37 * HTML-escaped prior to building the element's HTML string. This means that if your attribute value
38 * contains special characters that would not normally be allowed in a double-quoted attribute value,
39 * you <b>must</b> manually HTML-encode it beforehand (see {@link Ext.String#htmlEncode}) or risk
40 * malformed HTML being created. This behavior may change in a future release.</p>
42 * <p><b><u>Insertion methods</u></b></p>
43 * <p>Commonly used insertion methods:
44 * <div class="mdetail-params"><ul>
45 * <li><tt>{@link #append}</tt> : <div class="sub-desc"></div></li>
46 * <li><tt>{@link #insertBefore}</tt> : <div class="sub-desc"></div></li>
47 * <li><tt>{@link #insertAfter}</tt> : <div class="sub-desc"></div></li>
48 * <li><tt>{@link #overwrite}</tt> : <div class="sub-desc"></div></li>
49 * <li><tt>{@link #createTemplate}</tt> : <div class="sub-desc"></div></li>
50 * <li><tt>{@link #insertHtml}</tt> : <div class="sub-desc"></div></li>
53 * <p><b><u>Example</u></b></p>
54 * <p>This is an example, where an unordered list with 3 children items is appended to an existing
55 * element with id <tt>'my-div'</tt>:<br>
57 var dh = Ext.DomHelper; // create shorthand alias
58 // specification object
63 // append children after creating
64 children: [ // may also specify 'cn' instead of 'children'
65 {tag: 'li', id: 'item0', html: 'List Item 0'},
66 {tag: 'li', id: 'item1', html: 'List Item 1'},
67 {tag: 'li', id: 'item2', html: 'List Item 2'}
71 'my-div', // the context element 'my-div' can either be the id or the actual node
72 spec // the specification object
75 * <p>Element creation specification parameters in this class may also be passed as an Array of
76 * specification objects. This can be used to insert multiple sibling nodes into an existing
77 * container very efficiently. For example, to add more list items to the example above:<pre><code>
79 {tag: 'li', id: 'item3', html: 'List Item 3'},
80 {tag: 'li', id: 'item4', html: 'List Item 4'}
84 * <p><b><u>Templating</u></b></p>
85 * <p>The real power is in the built-in templating. Instead of creating or appending any elements,
86 * <tt>{@link #createTemplate}</tt> returns a Template object which can be used over and over to
87 * insert new elements. Revisiting the example above, we could utilize templating this time:
90 var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
92 var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
94 for(var i = 0; i < 5, i++){
95 tpl.append(list, [i]); // use template to append to the actual node
98 * <p>An example using a template:<pre><code>
99 var html = '<a id="{0}" href="{1}" class="nav">{2}</a>';
101 var tpl = new Ext.DomHelper.createTemplate(html);
102 tpl.append('blog-roll', ['link1', 'http://www.edspencer.net/', "Ed's Site"]);
103 tpl.append('blog-roll', ['link2', 'http://www.dustindiaz.com/', "Dustin's Site"]);
106 * <p>The same example using named parameters:<pre><code>
107 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
109 var tpl = new Ext.DomHelper.createTemplate(html);
110 tpl.append('blog-roll', {
112 url: 'http://www.edspencer.net/',
113 text: "Ed's Site"
115 tpl.append('blog-roll', {
117 url: 'http://www.dustindiaz.com/',
118 text: "Dustin's Site"
122 * <p><b><u>Compiling Templates</u></b></p>
123 * <p>Templates are applied using regular expressions. The performance is great, but if
124 * you are adding a bunch of DOM elements using the same template, you can increase
125 * performance even further by {@link Ext.Template#compile "compiling"} the template.
126 * The way "{@link Ext.Template#compile compile()}" works is the template is parsed and
127 * broken up at the different variable points and a dynamic function is created and eval'ed.
128 * The generated function performs string concatenation of these parts and the passed
129 * variables instead of using regular expressions.
131 var html = '<a id="{id}" href="{url}" class="nav">{text}</a>';
133 var tpl = new Ext.DomHelper.createTemplate(html);
136 //... use template like normal
139 * <p><b><u>Performance Boost</u></b></p>
140 * <p>DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead
141 * of DOM can significantly boost performance.</p>
142 * <p>Element creation specification parameters may also be strings. If {@link #useDom} is <tt>false</tt>,
143 * then the string is used as innerHTML. If {@link #useDom} is <tt>true</tt>, a string specification
144 * results in the creation of a text node. Usage:</p>
146 Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
151 Ext.core.DomHelper = Ext.DomHelper = function(){
152 var tempTableEl = null,
153 emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
154 tableRe = /^table|tbody|tr|td$/i,
155 confRe = /tag|children|cn|html$/i,
156 tableElRe = /td|tr|tbody/i,
159 // kill repeat to save bytes
160 afterbegin = 'afterbegin',
161 afterend = 'afterend',
162 beforebegin = 'beforebegin',
163 beforeend = 'beforeend',
172 function doInsert(el, o, returnElement, pos, sibling, append){
176 newNode = createDom(o, null);
178 el.appendChild(newNode);
180 (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
183 newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
185 return returnElement ? Ext.get(newNode, true) : newNode;
188 function createDom(o, parentNode){
196 if (Ext.isArray(o)) { // Allow Arrays of siblings to be inserted
197 el = doc.createDocumentFragment(); // in one shot using a DocumentFragment
198 for (var i = 0, l = o.length; i < l; i++) {
201 } else if (typeof o == 'string') { // Allow a string as a child spec.
202 el = doc.createTextNode(o);
204 el = doc.createElement( o.tag || 'div' );
205 useSet = !!el.setAttribute; // In IE some elements don't have setAttribute
207 if(!confRe.test(attr)){
213 el.setAttribute(attr, val);
220 Ext.DomHelper.applyStyles(el, o.style);
222 if ((cn = o.children || o.cn)) {
225 el.innerHTML = o.html;
229 parentNode.appendChild(el);
234 // build as innerHTML where available
235 function createHtml(o){
243 if(typeof o == "string"){
245 } else if (Ext.isArray(o)) {
246 for (i=0; i < o.length; i++) {
248 b += createHtml(o[i]);
252 b += '<' + (o.tag = o.tag || 'div');
255 if(!confRe.test(attr)){
256 if (typeof val == "object") {
257 b += ' ' + attr + '="';
259 b += key + ':' + val[key] + ';';
263 b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
267 // Now either just close the tag or try to add children and close the tag.
268 if (emptyTags.test(o.tag)) {
272 if ((cn = o.children || o.cn)) {
277 b += '</' + o.tag + '>';
283 function ieTable(depth, s, h, e){
284 tempTableEl.innerHTML = [s, h, e].join('');
291 // If the result is multiple siblings, then encapsulate them into one fragment.
294 var df = document.createDocumentFragment();
307 * Nasty code for IE's broken table implementation
309 function insertIntoTable(tag, where, el, html) {
313 tempTableEl = tempTableEl || document.createElement('div');
315 if(tag == 'td' && (where == afterbegin || where == beforeend) ||
316 !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
319 before = where == beforebegin ? el :
320 where == afterend ? el.nextSibling :
321 where == afterbegin ? el.firstChild : null;
323 if (where == beforebegin || where == afterend) {
327 if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
328 node = ieTable(4, trs, html, tre);
329 } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
330 (tag == 'tr' && (where == beforebegin || where == afterend))) {
331 node = ieTable(3, tbs, html, tbe);
333 node = ieTable(2, ts, html, te);
335 el.insertBefore(node, before);
341 * Fix for IE9 createContextualFragment missing method
343 function createContextualFragment(html){
344 var div = document.createElement("div"),
345 fragment = document.createDocumentFragment(),
349 div.innerHTML = html;
350 childNodes = div.childNodes;
351 length = childNodes.length;
353 for (; i < length; i++) {
354 fragment.appendChild(childNodes[i].cloneNode(true));
362 * Returns the markup for the passed Element(s) config.
363 * @param {Object} o The DOM object spec (and children)
366 markup : function(o){
367 return createHtml(o);
371 * Applies a style specification to an element.
372 * @param {String/HTMLElement} el The element to apply styles to
373 * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
374 * a function which returns such a specification.
376 applyStyles : function(el, styles){
379 if (typeof styles == "function") {
380 styles = styles.call();
382 if (typeof styles == "string") {
383 styles = Ext.Element.parseStyles(styles);
385 if (typeof styles == "object") {
392 * Inserts an HTML fragment into the DOM.
393 * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
395 * For example take the following HTML: `<div>Contents</div>`
397 * Using different `where` values inserts element to the following places:
399 * - beforeBegin: `<HERE><div>Contents</div>`
400 * - afterBegin: `<div><HERE>Contents</div>`
401 * - beforeEnd: `<div>Contents<HERE></div>`
402 * - afterEnd: `<div>Contents</div><HERE>`
404 * @param {HTMLElement/TextNode} el The context element
405 * @param {String} html The HTML fragment
406 * @return {HTMLElement} The new node
408 insertHtml : function(where, el, html){
417 where = where.toLowerCase();
418 // add these here because they are used in both branches of the condition.
419 hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
420 hash[afterend] = ['AfterEnd', 'nextSibling'];
422 // if IE and context element is an HTMLElement
423 if (el.insertAdjacentHTML) {
424 if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
428 // add these two to the hash.
429 hash[afterbegin] = ['AfterBegin', 'firstChild'];
430 hash[beforeend] = ['BeforeEnd', 'lastChild'];
431 if ((hashVal = hash[where])) {
432 el.insertAdjacentHTML(hashVal[0], html);
433 return el[hashVal[1]];
435 // if (not IE and context element is an HTMLElement) or TextNode
437 // we cannot insert anything inside a textnode so...
438 if (Ext.isTextNode(el)) {
439 where = where === 'afterbegin' ? 'beforebegin' : where;
440 where = where === 'beforeend' ? 'afterend' : where;
442 range = Ext.supports.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
443 setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
447 frag = range.createContextualFragment(html);
449 frag = createContextualFragment(html);
451 el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
452 return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
454 rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
457 range[setStart](el[rangeEl]);
458 frag = range.createContextualFragment(html);
460 frag = createContextualFragment(html);
463 if(where == afterbegin){
464 el.insertBefore(frag, el.firstChild);
466 el.appendChild(frag);
476 sourceClass: 'Ext.DomHelper',
477 sourceMethod: 'insertHtml',
480 msg: 'Illegal insertion point reached: "' + where + '"'
486 * Creates new DOM element(s) and inserts them before el.
487 * @param {String/HTMLElement/Ext.Element} el The context element
488 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
489 * @param {Boolean} returnElement (optional) true to return a Ext.Element
490 * @return {HTMLElement/Ext.Element} The new node
492 insertBefore : function(el, o, returnElement){
493 return doInsert(el, o, returnElement, beforebegin);
497 * Creates new DOM element(s) and inserts them after el.
498 * @param {String/HTMLElement/Ext.Element} el The context element
499 * @param {Object} o The DOM object spec (and children)
500 * @param {Boolean} returnElement (optional) true to return a Ext.Element
501 * @return {HTMLElement/Ext.Element} The new node
503 insertAfter : function(el, o, returnElement){
504 return doInsert(el, o, returnElement, afterend, 'nextSibling');
508 * Creates new DOM element(s) and inserts them as the first child of el.
509 * @param {String/HTMLElement/Ext.Element} el The context element
510 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
511 * @param {Boolean} returnElement (optional) true to return a Ext.Element
512 * @return {HTMLElement/Ext.Element} The new node
514 insertFirst : function(el, o, returnElement){
515 return doInsert(el, o, returnElement, afterbegin, 'firstChild');
519 * Creates new DOM element(s) and appends them to el.
520 * @param {String/HTMLElement/Ext.Element} el The context element
521 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
522 * @param {Boolean} returnElement (optional) true to return a Ext.Element
523 * @return {HTMLElement/Ext.Element} The new node
525 append : function(el, o, returnElement){
526 return doInsert(el, o, returnElement, beforeend, '', true);
530 * Creates new DOM element(s) and overwrites the contents of el with them.
531 * @param {String/HTMLElement/Ext.Element} el The context element
532 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
533 * @param {Boolean} returnElement (optional) true to return a Ext.Element
534 * @return {HTMLElement/Ext.Element} The new node
536 overwrite : function(el, o, returnElement){
538 el.innerHTML = createHtml(o);
539 return returnElement ? Ext.get(el.firstChild) : el.firstChild;
542 createHtml : createHtml,
545 * Creates new DOM element(s) without inserting them to the document.
546 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
547 * @return {HTMLElement} The new uninserted node
550 createDom: createDom,
552 /** True to force the use of DOM instead of html fragments @type Boolean */
556 * Creates a new Ext.Template from the DOM object spec.
557 * @param {Object} o The DOM object spec (and children)
558 * @return {Ext.Template} The new template
560 createTemplate : function(o){
561 var html = Ext.DomHelper.createHtml(o);
562 return Ext.create('Ext.Template', html);