]> git.proxmox.com Git - sencha-touch.git/blob - src/src/dom/Helper.js
import Sencha Touch 2.4.2 source
[sencha-touch.git] / src / src / dom / Helper.js
1 //@tag dom,core
2 //@define Ext.DomHelper
3 //@require Ext.dom.Query
4
5 /**
6 * @class Ext.DomHelper
7 * @alternateClassName Ext.dom.Helper
8 * @singleton
9 *
10 * The DomHelper class provides a layer of abstraction from DOM and transparently supports creating elements via DOM or
11 * using HTML fragments. It also has the ability to create HTML fragment templates from your DOM building code.
12 *
13 * ## DomHelper element specification object
14 *
15 * A specification object is used when creating elements. Attributes of this object are assumed to be element
16 * attributes, except for 4 special attributes:
17 *
18 * * **tag**: The tag name of the element
19 * * **children (or cn)**: An array of the same kind of element definition objects to be created and appended. These
20 * can be nested as deep as you want.
21 * * **cls**: The class attribute of the element. This will end up being either the "class" attribute on a HTML
22 * fragment or className for a DOM node, depending on whether DomHelper is using fragments or DOM.
23 * * **html**: The innerHTML for the element
24 *
25 * ## Insertion methods
26 *
27 * Commonly used insertion methods:
28 *
29 * * {@link #append}
30 * * {@link #insertBefore}
31 * * {@link #insertAfter}
32 * * {@link #overwrite}
33 * * {@link #insertHtml}
34 *
35 * ## Example
36 *
37 * This is an example, where an unordered list with 3 children items is appended to an existing element with id
38 * 'my-div':
39 *
40 * var dh = Ext.DomHelper; // create shorthand alias
41 * // specification object
42 * var spec = {
43 * id: 'my-ul',
44 * tag: 'ul',
45 * cls: 'my-list',
46 * // append children after creating
47 * children: [ // may also specify 'cn' instead of 'children'
48 * {tag: 'li', id: 'item0', html: 'List Item 0'},
49 * {tag: 'li', id: 'item1', html: 'List Item 1'},
50 * {tag: 'li', id: 'item2', html: 'List Item 2'}
51 * ]
52 * };
53 * var list = dh.append(
54 * 'my-div', // the context element 'my-div' can either be the id or the actual node
55 * spec // the specification object
56 * );
57 *
58 * Element creation specification parameters in this class may also be passed as an Array of specification objects.
59 * This can be used to insert multiple sibling nodes into an existing container very efficiently. For example, to add
60 * more list items to the example above:
61 *
62 * dh.append('my-ul', [
63 * {tag: 'li', id: 'item3', html: 'List Item 3'},
64 * {tag: 'li', id: 'item4', html: 'List Item 4'}
65 * ]);
66 *
67 * ## Templating
68 *
69 * The real power is in the built-in templating. Instead of creating or appending any elements, createTemplate returns
70 * a Template object which can be used over and over to insert new elements. Revisiting the example above, we could
71 * utilize templating this time:
72 *
73 * // create the node
74 * var list = dh.append('my-div', {tag: 'ul', cls: 'my-list'});
75 * // get template
76 * var tpl = dh.createTemplate({tag: 'li', id: 'item{0}', html: 'List Item {0}'});
77 *
78 * for(var i = 0; i < 5; i++){
79 * tpl.append(list, i); // use template to append to the actual node
80 * }
81 *
82 * An example using a template:
83 *
84 * var html = '"{0}" href="{1}" class="nav">{2}';
85 *
86 * var tpl = new Ext.DomHelper.createTemplate(html);
87 * tpl.append('blog-roll', ['link1', 'http://www.tommymaintz.com/', "Tommy's Site"]);
88 * tpl.append('blog-roll', ['link2', 'http://www.avins.org/', "Jamie's Site"]);
89 *
90 * The same example using named parameters:
91 *
92 * var html = '"{id}" href="{url}" class="nav">{text}';
93 *
94 * var tpl = new Ext.DomHelper.createTemplate(html);
95 * tpl.append('blog-roll', {
96 * id: 'link1',
97 * url: 'http://www.tommymaintz.com/',
98 * text: "Tommy's Site"
99 * });
100 * tpl.append('blog-roll', {
101 * id: 'link2',
102 * url: 'http://www.avins.org/',
103 * text: "Jamie's Site"
104 * });
105 *
106 * ## Compiling Templates
107 *
108 * Templates are applied using regular expressions. The performance is great, but if you are adding a bunch of DOM
109 * elements using the same template, you can increase performance even further by "compiling" the template. The way
110 * "compile()" works is the template is parsed and broken up at the different variable points and a dynamic function is
111 * created and eval'ed. The generated function performs string concatenation of these parts and the passed variables
112 * instead of using regular expressions.
113 *
114 * var html = '"{id}" href="{url}" class="nav">{text}';
115 *
116 * var tpl = new Ext.DomHelper.createTemplate(html);
117 * tpl.compile();
118 *
119 * // ... use template like normal
120 *
121 * ## Performance Boost
122 *
123 * DomHelper will transparently create HTML fragments when it can. Using HTML fragments instead of DOM can
124 * significantly boost performance.
125 *
126 * Element creation specification parameters may also be strings. If useDom is false, then the string is used as
127 * innerHTML. If useDom is true, a string specification results in the creation of a text node. Usage:
128 *
129 * Ext.DomHelper.useDom = true; // force it to use DOM; reduces performance
130 *
131 */
132 Ext.define('Ext.dom.Helper', {
133 emptyTags : /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
134 confRe : /tag|children|cn|html|tpl|tplData$/i,
135 endRe : /end/i,
136
137 attribXlat: { cls : 'class', htmlFor : 'for' },
138
139 closeTags: {},
140
141 decamelizeName : function () {
142 var camelCaseRe = /([a-z])([A-Z])/g,
143 cache = {};
144
145 function decamel (match, p1, p2) {
146 return p1 + '-' + p2.toLowerCase();
147 }
148
149 return function (s) {
150 return cache[s] || (cache[s] = s.replace(camelCaseRe, decamel));
151 };
152 }(),
153
154 generateMarkup: function(spec, buffer) {
155 var me = this,
156 attr, val, tag, i, closeTags;
157
158 if (typeof spec == "string") {
159 buffer.push(spec);
160 } else if (Ext.isArray(spec)) {
161 for (i = 0; i < spec.length; i++) {
162 if (spec[i]) {
163 me.generateMarkup(spec[i], buffer);
164 }
165 }
166 } else {
167 tag = spec.tag || 'div';
168 buffer.push('<', tag);
169
170 for (attr in spec) {
171 if (spec.hasOwnProperty(attr)) {
172 val = spec[attr];
173 if (!me.confRe.test(attr)) {
174 if (typeof val == "object") {
175 buffer.push(' ', attr, '="');
176 me.generateStyles(val, buffer).push('"');
177 } else {
178 buffer.push(' ', me.attribXlat[attr] || attr, '="', val, '"');
179 }
180 }
181 }
182 }
183
184 // Now either just close the tag or try to add children and close the tag.
185 if (me.emptyTags.test(tag)) {
186 buffer.push('/>');
187 } else {
188 buffer.push('>');
189
190 // Apply the tpl html, and cn specifications
191 if ((val = spec.tpl)) {
192 val.applyOut(spec.tplData, buffer);
193 }
194 if ((val = spec.html)) {
195 buffer.push(val);
196 }
197 if ((val = spec.cn || spec.children)) {
198 me.generateMarkup(val, buffer);
199 }
200
201 // we generate a lot of close tags, so cache them rather than push 3 parts
202 closeTags = me.closeTags;
203 buffer.push(closeTags[tag] || (closeTags[tag] = '</' + tag + '>'));
204 }
205 }
206
207 return buffer;
208 },
209
210 /**
211 * Converts the styles from the given object to text. The styles are CSS style names
212 * with their associated value.
213 *
214 * The basic form of this method returns a string:
215 *
216 * var s = Ext.DomHelper.generateStyles({
217 * backgroundColor: 'red'
218 * });
219 *
220 * // s = 'background-color:red;'
221 *
222 * Alternatively, this method can append to an output array.
223 *
224 * var buf = [];
225 *
226 * // ...
227 *
228 * Ext.DomHelper.generateStyles({
229 * backgroundColor: 'red'
230 * }, buf);
231 *
232 * In this case, the style text is pushed on to the array and the array is returned.
233 *
234 * @param {Object} styles The object describing the styles.
235 * @param {String[]} [buffer] The output buffer.
236 * @return {String/String[]} If buffer is passed, it is returned. Otherwise the style
237 * string is returned.
238 */
239 generateStyles: function (styles, buffer) {
240 var a = buffer || [],
241 name;
242
243 for (name in styles) {
244 if (styles.hasOwnProperty(name)) {
245 a.push(this.decamelizeName(name), ':', styles[name], ';');
246 }
247 }
248
249 return buffer || a.join('');
250 },
251
252 /**
253 * Returns the markup for the passed Element(s) config.
254 * @param {Object} spec The DOM object spec (and children).
255 * @return {String}
256 */
257 markup: function(spec) {
258 if (typeof spec == "string") {
259 return spec;
260 }
261
262 var buf = this.generateMarkup(spec, []);
263 return buf.join('');
264 },
265
266 /**
267 * Applies a style specification to an element.
268 * @param {String/HTMLElement} el The element to apply styles to
269 * @param {String/Object/Function} styles A style specification string e.g. 'width:100px', or object in the form {width:'100px'}, or
270 * a function which returns such a specification.
271 */
272 applyStyles: function(el, styles) {
273 Ext.fly(el).applyStyles(styles);
274 },
275
276 /**
277 * @private
278 * Fix for browsers which no longer support createContextualFragment
279 */
280 createContextualFragment: function(html){
281 var div = document.createElement("div"),
282 fragment = document.createDocumentFragment(),
283 i = 0,
284 length, childNodes;
285
286 div.innerHTML = html;
287 childNodes = div.childNodes;
288 length = childNodes.length;
289
290 for (; i < length; i++) {
291 fragment.appendChild(childNodes[i].cloneNode(true));
292 }
293
294 return fragment;
295 },
296
297 /**
298 * Inserts an HTML fragment into the DOM.
299 * @param {String} where Where to insert the html in relation to el - beforeBegin, afterBegin, beforeEnd, afterEnd.
300 *
301 * For example take the following HTML: `<div>Contents</div>`
302 *
303 * Using different `where` values inserts element to the following places:
304 *
305 * - beforeBegin: `<HERE><div>Contents</div>`
306 * - afterBegin: `<div><HERE>Contents</div>`
307 * - beforeEnd: `<div>Contents<HERE></div>`
308 * - afterEnd: `<div>Contents</div><HERE>`
309 *
310 * @param {HTMLElement/TextNode} el The context element
311 * @param {String} html The HTML fragment
312 * @return {HTMLElement} The new node
313 */
314 insertHtml: function(where, el, html) {
315 var setStart, range, frag, rangeEl, isBeforeBegin, isAfterBegin;
316
317 where = where.toLowerCase();
318
319 if (Ext.isTextNode(el)) {
320 if (where == 'afterbegin' ) {
321 where = 'beforebegin';
322 }
323 else if (where == 'beforeend') {
324 where = 'afterend';
325 }
326 }
327
328 isBeforeBegin = where == 'beforebegin';
329 isAfterBegin = where == 'afterbegin';
330
331 range = Ext.feature.has.CreateContextualFragment ? el.ownerDocument.createRange() : undefined;
332 setStart = 'setStart' + (this.endRe.test(where) ? 'After' : 'Before');
333
334 if (isBeforeBegin || where == 'afterend') {
335 if (range) {
336 range[setStart](el);
337 frag = range.createContextualFragment(html);
338 }
339 else {
340 frag = this.createContextualFragment(html);
341 }
342 el.parentNode.insertBefore(frag, isBeforeBegin ? el : el.nextSibling);
343 return el[(isBeforeBegin ? 'previous' : 'next') + 'Sibling'];
344 }
345 else {
346 rangeEl = (isAfterBegin ? 'first' : 'last') + 'Child';
347 if (el.firstChild) {
348 if (range) {
349 // Creating ranges on a hidden element throws an error, checking for the element being painted is
350 // VERY expensive, so we'll catch the error and fall back to using the full fragment
351 try {
352 range[setStart](el[rangeEl]);
353 frag = range.createContextualFragment(html);
354 }
355 catch(e) {
356 frag = this.createContextualFragment(html);
357 }
358 } else {
359 frag = this.createContextualFragment(html);
360 }
361
362 if (isAfterBegin) {
363 el.insertBefore(frag, el.firstChild);
364 } else {
365 el.appendChild(frag);
366 }
367 } else {
368 el.innerHTML = html;
369 }
370 return el[rangeEl];
371 }
372 },
373
374 /**
375 * Creates new DOM element(s) and inserts them before el.
376 * @param {String/HTMLElement/Ext.Element} el The context element
377 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
378 * @param {Boolean} [returnElement] true to return a Ext.Element
379 * @return {HTMLElement/Ext.Element} The new node
380 */
381 insertBefore: function(el, o, returnElement) {
382 return this.doInsert(el, o, returnElement, 'beforebegin');
383 },
384
385 /**
386 * Creates new DOM element(s) and inserts them after el.
387 * @param {String/HTMLElement/Ext.Element} el The context element
388 * @param {Object} o The DOM object spec (and children)
389 * @param {Boolean} [returnElement] true to return a Ext.Element
390 * @return {HTMLElement/Ext.Element} The new node
391 */
392 insertAfter: function(el, o, returnElement) {
393 return this.doInsert(el, o, returnElement, 'afterend');
394 },
395
396 /**
397 * Creates new DOM element(s) and inserts them as the first child of el.
398 * @param {String/HTMLElement/Ext.Element} el The context element
399 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
400 * @param {Boolean} [returnElement] true to return a Ext.Element
401 * @return {HTMLElement/Ext.Element} The new node
402 */
403 insertFirst: function(el, o, returnElement) {
404 return this.doInsert(el, o, returnElement, 'afterbegin');
405 },
406
407 /**
408 * Creates new DOM element(s) and appends them to el.
409 * @param {String/HTMLElement/Ext.Element} el The context element
410 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
411 * @param {Boolean} [returnElement] true to return a Ext.Element
412 * @return {HTMLElement/Ext.Element} The new node
413 */
414 append: function(el, o, returnElement) {
415 return this.doInsert(el, o, returnElement, 'beforeend');
416 },
417
418 /**
419 * Creates new DOM element(s) and overwrites the contents of el with them.
420 * @param {String/HTMLElement/Ext.Element} el The context element
421 * @param {Object/String} o The DOM object spec (and children) or raw HTML blob
422 * @param {Boolean} [returnElement] true to return a Ext.Element
423 * @return {HTMLElement/Ext.Element} The new node
424 */
425 overwrite: function(el, o, returnElement) {
426 el = Ext.getDom(el);
427 el.innerHTML = this.markup(o);
428 return returnElement ? Ext.get(el.firstChild) : el.firstChild;
429 },
430
431 doInsert: function(el, o, returnElement, pos) {
432 var newNode = this.insertHtml(pos, Ext.getDom(el), this.markup(o));
433 return returnElement ? Ext.get(newNode, true) : newNode;
434 },
435
436 /**
437 * Creates a new Ext.Template from the DOM object spec.
438 * @param {Object} o The DOM object spec (and children)
439 * @return {Ext.Template} The new template
440 */
441 createTemplate: function(o) {
442 var html = this.markup(o);
443 return new Ext.Template(html);
444 }
445 }, function() {
446 Ext.ns('Ext.core');
447 Ext.core.DomHelper = Ext.DomHelper = new this;
448 });