Upgrade to ExtJS 4.0.0 - Released 04/26/2011
[extjs.git] / pkgs / extras.js
1 /*
2 Ext JS - JavaScript Library
3 Copyright (c) 2006-2011, Sencha Inc.
4 All rights reserved.
5 licensing@sencha.com
6 */
7 /**
8  * @class Ext.JSON
9  * Modified version of Douglas Crockford"s json.js that doesn"t
10  * mess with the Object prototype
11  * http://www.json.org/js.html
12  * @singleton
13  */
14 Ext.JSON = new(function() {
15     var useHasOwn = !! {}.hasOwnProperty,
16     isNative = function() {
17         var useNative = null;
18
19         return function() {
20             if (useNative === null) {
21                 useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
22             }
23
24             return useNative;
25         };
26     }(),
27     pad = function(n) {
28         return n < 10 ? "0" + n : n;
29     },
30     doDecode = function(json) {
31         return eval("(" + json + ')');
32     },
33     doEncode = function(o) {
34         if (!Ext.isDefined(o) || o === null) {
35             return "null";
36         } else if (Ext.isArray(o)) {
37             return encodeArray(o);
38         } else if (Ext.isDate(o)) {
39             return Ext.JSON.encodeDate(o);
40         } else if (Ext.isString(o)) {
41             return encodeString(o);
42         } else if (typeof o == "number") {
43             //don't use isNumber here, since finite checks happen inside isNumber
44             return isFinite(o) ? String(o) : "null";
45         } else if (Ext.isBoolean(o)) {
46             return String(o);
47         } else if (Ext.isObject(o)) {
48             return encodeObject(o);
49         } else if (typeof o === "function") {
50             return "null";
51         }
52         return 'undefined';
53     },
54     m = {
55         "\b": '\\b',
56         "\t": '\\t',
57         "\n": '\\n',
58         "\f": '\\f',
59         "\r": '\\r',
60         '"': '\\"',
61         "\\": '\\\\',
62         '\x0b': '\\u000b' //ie doesn't handle \v
63     },
64     charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g,
65     encodeString = function(s) {
66         return '"' + s.replace(charToReplace, function(a) {
67             var c = m[a];
68             return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
69         }) + '"';
70     },
71     encodeArray = function(o) {
72         var a = ["[", ""],
73         // Note empty string in case there are no serializable members.
74         len = o.length,
75         i;
76         for (i = 0; i < len; i += 1) {
77             a.push(doEncode(o[i]), ',');
78         }
79         // Overwrite trailing comma (or empty string)
80         a[a.length - 1] = ']';
81         return a.join("");
82     },
83     encodeObject = function(o) {
84         var a = ["{", ""],
85         // Note empty string in case there are no serializable members.
86         i;
87         for (i in o) {
88             if (!useHasOwn || o.hasOwnProperty(i)) {
89                 a.push(doEncode(i), ":", doEncode(o[i]), ',');
90             }
91         }
92         // Overwrite trailing comma (or empty string)
93         a[a.length - 1] = '}';
94         return a.join("");
95     };
96
97     /**
98      * <p>Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression.
99      * <b>The returned value includes enclosing double quotation marks.</b></p>
100      * <p>The default return format is "yyyy-mm-ddThh:mm:ss".</p>
101      * <p>To override this:</p><pre><code>
102      Ext.JSON.encodeDate = function(d) {
103      return d.format('"Y-m-d"');
104      };
105      </code></pre>
106      * @param {Date} d The Date to encode
107      * @return {String} The string literal to use in a JSON string.
108      */
109     this.encodeDate = function(o) {
110         return '"' + o.getFullYear() + "-" 
111         + pad(o.getMonth() + 1) + "-"
112         + pad(o.getDate()) + "T"
113         + pad(o.getHours()) + ":"
114         + pad(o.getMinutes()) + ":"
115         + pad(o.getSeconds()) + '"';
116     };
117
118     /**
119      * Encodes an Object, Array or other value
120      * @param {Mixed} o The variable to encode
121      * @return {String} The JSON string
122      */
123     this.encode = function() {
124         var ec;
125         return function(o) {
126             if (!ec) {
127                 // setup encoding function on first access
128                 ec = isNative() ? JSON.stringify : doEncode;
129             }
130             return ec(o);
131         };
132     }();
133
134
135     /**
136      * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a SyntaxError unless the safe option is set.
137      * @param {String} json The JSON string
138      * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
139      * @return {Object} The resulting object
140      */
141     this.decode = function() {
142         var dc;
143         return function(json, safe) {
144             if (!dc) {
145                 // setup decoding function on first access
146                 dc = isNative() ? JSON.parse : doDecode;
147             }
148             try {
149                 return dc(json);
150             } catch (e) {
151                 if (safe === true) {
152                     return null;
153                 }
154                 Ext.Error.raise({
155                     sourceClass: "Ext.JSON",
156                     sourceMethod: "decode",
157                     msg: "You're trying to decode and invalid JSON String: " + json
158                 });
159             }
160         };
161     }();
162
163 })();
164 /**
165  * Shorthand for {@link Ext.JSON#encode}
166  * @param {Mixed} o The variable to encode
167  * @return {String} The JSON string
168  * @member Ext
169  * @method encode
170  */
171 Ext.encode = Ext.JSON.encode;
172 /**
173  * Shorthand for {@link Ext.JSON#decode}
174  * @param {String} json The JSON string
175  * @param {Boolean} safe (optional) Whether to return null or throw an exception if the JSON is invalid.
176  * @return {Object} The resulting object
177  * @member Ext
178  * @method decode
179  */
180 Ext.decode = Ext.JSON.decode;
181
182
183 /**
184  * @class Ext
185
186  The Ext namespace (global object) encapsulates all classes, singletons, and utility methods provided by Sencha's libraries.</p>
187  Most user interface Components are at a lower level of nesting in the namespace, but many common utility functions are provided
188  as direct properties of the Ext namespace.
189
190  Also many frequently used methods from other classes are provided as shortcuts within the Ext namespace.
191  For example {@link Ext#getCmp Ext.getCmp} aliases {@link Ext.ComponentManager#get Ext.ComponentManager.get}.
192
193  Many applications are initiated with {@link Ext#onReady Ext.onReady} which is called once the DOM is ready.
194  This ensures all scripts have been loaded, preventing dependency issues. For example
195
196      Ext.onReady(function(){
197          new Ext.Component({
198              renderTo: document.body,
199              html: 'DOM ready!'
200          });
201      });
202
203 For more information about how to use the Ext classes, see
204
205 * <a href="http://www.sencha.com/learn/">The Learning Center</a>
206 * <a href="http://www.sencha.com/learn/Ext_FAQ">The FAQ</a>
207 * <a href="http://www.sencha.com/forum/">The forums</a>
208
209  * @singleton
210  * @markdown
211  */
212 Ext.apply(Ext, {
213     userAgent: navigator.userAgent.toLowerCase(),
214     cache: {},
215     idSeed: 1000,
216     BLANK_IMAGE_URL : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
217     isStrict: document.compatMode == "CSS1Compat",
218     windowId: 'ext-window',
219     documentId: 'ext-document',
220
221     /**
222      * True when the document is fully initialized and ready for action
223      * @type Boolean
224      */
225     isReady: false,
226
227     /**
228      * True to automatically uncache orphaned Ext.core.Elements periodically (defaults to true)
229      * @type Boolean
230      */
231     enableGarbageCollector: true,
232
233     /**
234      * True to automatically purge event listeners during garbageCollection (defaults to true).
235      * @type Boolean
236      */
237     enableListenerCollection: true,
238
239     /**
240      * Generates unique ids. If the element already has an id, it is unchanged
241      * @param {Mixed} el (optional) The element to generate an id for
242      * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
243      * @return {String} The generated Id.
244      */
245     id: function(el, prefix) {
246         el = Ext.getDom(el, true) || {};
247         if (el === document) {
248             el.id = this.documentId;
249         }
250         else if (el === window) {
251             el.id = this.windowId;
252         }
253         if (!el.id) {
254             el.id = (prefix || "ext-gen") + (++Ext.idSeed);
255         }
256         return el.id;
257     },
258
259     /**
260      * Returns the current document body as an {@link Ext.core.Element}.
261      * @return Ext.core.Element The document body
262      */
263     getBody: function() {
264         return Ext.get(document.body || false);
265     },
266
267     /**
268      * Returns the current document head as an {@link Ext.core.Element}.
269      * @return Ext.core.Element The document head
270      */
271     getHead: function() {
272         var head;
273
274         return function() {
275             if (head == undefined) {
276                 head = Ext.get(document.getElementsByTagName("head")[0]);
277             }
278
279             return head;
280         };
281     }(),
282
283     /**
284      * Returns the current HTML document object as an {@link Ext.core.Element}.
285      * @return Ext.core.Element The document
286      */
287     getDoc: function() {
288         return Ext.get(document);
289     },
290
291     /**
292      * This is shorthand reference to {@link Ext.ComponentManager#get}.
293      * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#id id}
294      * @param {String} id The component {@link Ext.Component#id id}
295      * @return Ext.Component The Component, <tt>undefined</tt> if not found, or <tt>null</tt> if a
296      * Class was found.
297     */
298     getCmp: function(id) {
299         return Ext.ComponentManager.get(id);
300     },
301
302     /**
303      * Returns the current orientation of the mobile device
304      * @return {String} Either 'portrait' or 'landscape'
305      */
306     getOrientation: function() {
307         return window.innerHeight > window.innerWidth ? 'portrait' : 'landscape';
308     },
309
310     /**
311      * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the
312      * DOM (if applicable) and calling their destroy functions (if available).  This method is primarily
313      * intended for arguments of type {@link Ext.core.Element} and {@link Ext.Component}, but any subclass of
314      * {@link Ext.util.Observable} can be passed in.  Any number of elements and/or components can be
315      * passed into this function in a single call as separate arguments.
316      * @param {Mixed} arg1 An {@link Ext.core.Element}, {@link Ext.Component}, or an Array of either of these to destroy
317      * @param {Mixed} arg2 (optional)
318      * @param {Mixed} etc... (optional)
319      */
320     destroy: function() {
321         var ln = arguments.length,
322         i, arg;
323
324         for (i = 0; i < ln; i++) {
325             arg = arguments[i];
326             if (arg) {
327                 if (Ext.isArray(arg)) {
328                     this.destroy.apply(this, arg);
329                 }
330                 else if (Ext.isFunction(arg.destroy)) {
331                     arg.destroy();
332                 }
333                 else if (arg.dom) {
334                     arg.remove();
335                 }
336             }
337         }
338     },
339
340     /**
341      * Execute a callback function in a particular scope. If no function is passed the call is ignored.
342      * @param {Function} callback The callback to execute
343      * @param {Object} scope (optional) The scope to execute in
344      * @param {Array} args (optional) The arguments to pass to the function
345      * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds.
346      */
347     callback: function(callback, scope, args, delay){
348         if(Ext.isFunction(callback)){
349             args = args || [];
350             scope = scope || window;
351             if (delay) {
352                 Ext.defer(callback, delay, scope, args);
353             } else {
354                 callback.apply(scope, args);
355             }
356         }
357     },
358
359     /**
360      * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
361      * @param {String} value The string to encode
362      * @return {String} The encoded text
363      */
364     htmlEncode : function(value) {
365         return Ext.String.htmlEncode(value);
366     },
367
368     /**
369      * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
370      * @param {String} value The string to decode
371      * @return {String} The decoded text
372      */
373     htmlDecode : function(value) {
374          return Ext.String.htmlDecode(value);
375     },
376
377     /**
378      * Appends content to the query string of a URL, handling logic for whether to place
379      * a question mark or ampersand.
380      * @param {String} url The URL to append to.
381      * @param {String} s The content to append to the URL.
382      * @return (String) The resulting URL
383      */
384     urlAppend : function(url, s) {
385         if (!Ext.isEmpty(s)) {
386             return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
387         }
388         return url;
389     }
390 });
391
392
393 Ext.ns = Ext.namespace;
394
395 // for old browsers
396 window.undefined = window.undefined;
397 /**
398  * @class Ext
399  * Ext core utilities and functions.
400  * @singleton
401  */
402 (function(){
403     var check = function(regex){
404             return regex.test(Ext.userAgent);
405         },
406         docMode = document.documentMode,
407         isOpera = check(/opera/),
408         isOpera10_5 = isOpera && check(/version\/10\.5/),
409         isChrome = check(/\bchrome\b/),
410         isWebKit = check(/webkit/),
411         isSafari = !isChrome && check(/safari/),
412         isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
413         isSafari3 = isSafari && check(/version\/3/),
414         isSafari4 = isSafari && check(/version\/4/),
415         isIE = !isOpera && check(/msie/),
416         isIE7 = isIE && (check(/msie 7/) || docMode == 7),
417         isIE8 = isIE && (check(/msie 8/) && docMode != 7 && docMode != 9 || docMode == 8),
418         isIE9 = isIE && (check(/msie 9/) && docMode != 7 && docMode != 8 || docMode == 9),
419         isIE6 = isIE && check(/msie 6/),
420         isGecko = !isWebKit && check(/gecko/),
421         isGecko3 = isGecko && check(/rv:1\.9/),
422         isGecko4 = isGecko && check(/rv:2\.0/),
423         isFF3_0 = isGecko3 && check(/rv:1\.9\.0/),
424         isFF3_5 = isGecko3 && check(/rv:1\.9\.1/),
425         isFF3_6 = isGecko3 && check(/rv:1\.9\.2/),
426         isWindows = check(/windows|win32/),
427         isMac = check(/macintosh|mac os x/),
428         isLinux = check(/linux/),
429         scrollWidth = null;
430
431     // remove css image flicker
432     try {
433         document.execCommand("BackgroundImageCache", false, true);
434     } catch(e) {}
435
436     Ext.setVersion('extjs', '4.0.0');
437     Ext.apply(Ext, {
438         /**
439          * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
440          * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
441          * @type String
442          */
443         SSL_SECURE_URL : Ext.isSecure && isIE ? 'javascript:""' : 'about:blank',
444
445         /**
446          * True if the {@link Ext.fx.Anim} Class is available
447          * @type Boolean
448          * @property enableFx
449          */
450
451         /**
452          * True to scope the reset CSS to be just applied to Ext components. Note that this wraps root containers
453          * with an additional element. Also remember that when you turn on this option, you have to use ext-all-scoped {
454          * unless you use the bootstrap.js to load your javascript, in which case it will be handled for you.
455          * @type Boolean
456          */
457         scopeResetCSS : Ext.buildSettings.scopeResetCSS,
458
459         /**
460          * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed.
461          * Currently not optimized for performance.
462          * @type Boolean
463          */
464         enableNestedListenerRemoval : false,
465
466         /**
467          * Indicates whether to use native browser parsing for JSON methods.
468          * This option is ignored if the browser does not support native JSON methods.
469          * <b>Note: Native JSON methods will not work with objects that have functions.
470          * Also, property names must be quoted, otherwise the data will not parse.</b> (Defaults to false)
471          * @type Boolean
472          */
473         USE_NATIVE_JSON : false,
474
475         /**
476          * Return the dom node for the passed String (id), dom node, or Ext.core.Element.
477          * Optional 'strict' flag is needed for IE since it can return 'name' and
478          * 'id' elements by using getElementById.
479          * Here are some examples:
480          * <pre><code>
481 // gets dom node based on id
482 var elDom = Ext.getDom('elId');
483 // gets dom node based on the dom node
484 var elDom1 = Ext.getDom(elDom);
485
486 // If we don&#39;t know if we are working with an
487 // Ext.core.Element or a dom node use Ext.getDom
488 function(el){
489     var dom = Ext.getDom(el);
490     // do something with the dom node
491 }
492          * </code></pre>
493          * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc)
494          * when this method is called to be successful.
495          * @param {Mixed} el
496          * @return HTMLElement
497          */
498         getDom : function(el, strict) {
499             if (!el || !document) {
500                 return null;
501             }
502             if (el.dom) {
503                 return el.dom;
504             } else {
505                 if (typeof el == 'string') {
506                     var e = document.getElementById(el);
507                     // IE returns elements with the 'name' and 'id' attribute.
508                     // we do a strict check to return the element with only the id attribute
509                     if (e && isIE && strict) {
510                         if (el == e.getAttribute('id')) {
511                             return e;
512                         } else {
513                             return null;
514                         }
515                     }
516                     return e;
517                 } else {
518                     return el;
519                 }
520             }
521         },
522
523         /**
524          * Removes a DOM node from the document.
525          * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
526          * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval Ext.enableNestedListenerRemoval} is
527          * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node
528          * will be ignored if passed in.</p>
529          * @param {HTMLElement} node The node to remove
530          */
531         removeNode : isIE6 || isIE7 ? function() {
532             var d;
533             return function(n){
534                 if(n && n.tagName != 'BODY'){
535                     (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
536                     d = d || document.createElement('div');
537                     d.appendChild(n);
538                     d.innerHTML = '';
539                     delete Ext.cache[n.id];
540                 }
541             };
542         }() : function(n) {
543             if (n && n.parentNode && n.tagName != 'BODY') {
544                 (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n) : Ext.EventManager.removeAll(n);
545                 n.parentNode.removeChild(n);
546                 delete Ext.cache[n.id];
547             }
548         },
549
550         /**
551          * True if the detected browser is Opera.
552          * @type Boolean
553          */
554         isOpera : isOpera,
555
556         /**
557          * True if the detected browser is Opera 10.5x.
558          * @type Boolean
559          */
560         isOpera10_5 : isOpera10_5,
561
562         /**
563          * True if the detected browser uses WebKit.
564          * @type Boolean
565          */
566         isWebKit : isWebKit,
567
568         /**
569          * True if the detected browser is Chrome.
570          * @type Boolean
571          */
572         isChrome : isChrome,
573
574         /**
575          * True if the detected browser is Safari.
576          * @type Boolean
577          */
578         isSafari : isSafari,
579
580         /**
581          * True if the detected browser is Safari 3.x.
582          * @type Boolean
583          */
584         isSafari3 : isSafari3,
585
586         /**
587          * True if the detected browser is Safari 4.x.
588          * @type Boolean
589          */
590         isSafari4 : isSafari4,
591
592         /**
593          * True if the detected browser is Safari 2.x.
594          * @type Boolean
595          */
596         isSafari2 : isSafari2,
597
598         /**
599          * True if the detected browser is Internet Explorer.
600          * @type Boolean
601          */
602         isIE : isIE,
603
604         /**
605          * True if the detected browser is Internet Explorer 6.x.
606          * @type Boolean
607          */
608         isIE6 : isIE6,
609
610         /**
611          * True if the detected browser is Internet Explorer 7.x.
612          * @type Boolean
613          */
614         isIE7 : isIE7,
615
616         /**
617          * True if the detected browser is Internet Explorer 8.x.
618          * @type Boolean
619          */
620         isIE8 : isIE8,
621
622         /**
623          * True if the detected browser is Internet Explorer 9.x.
624          * @type Boolean
625          */
626         isIE9 : isIE9,
627
628         /**
629          * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
630          * @type Boolean
631          */
632         isGecko : isGecko,
633
634         /**
635          * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
636          * @type Boolean
637          */
638         isGecko3 : isGecko3,
639
640         /**
641          * True if the detected browser uses a Gecko 2.0+ layout engine (e.g. Firefox 4.x).
642          * @type Boolean
643          */
644         isGecko4 : isGecko4,
645
646         /**
647          * True if the detected browser uses FireFox 3.0
648          * @type Boolean
649          */
650
651         isFF3_0 : isFF3_0,
652         /**
653          * True if the detected browser uses FireFox 3.5
654          * @type Boolean
655          */
656
657         isFF3_5 : isFF3_5,
658         /**
659          * True if the detected browser uses FireFox 3.6
660          * @type Boolean
661          */
662         isFF3_6 : isFF3_6,
663
664         /**
665          * True if the detected platform is Linux.
666          * @type Boolean
667          */
668         isLinux : isLinux,
669
670         /**
671          * True if the detected platform is Windows.
672          * @type Boolean
673          */
674         isWindows : isWindows,
675
676         /**
677          * True if the detected platform is Mac OS.
678          * @type Boolean
679          */
680         isMac : isMac,
681
682         /**
683          * URL to a 1x1 transparent gif image used by Ext to create inline icons with CSS background images.
684          * In older versions of IE, this defaults to "http://sencha.com/s.gif" and you should change this to a URL on your server.
685          * For other browsers it uses an inline data URL.
686          * @type String
687          */
688         BLANK_IMAGE_URL : (isIE6 || isIE7) ? 'http:/' + '/www.sencha.com/s.gif' : 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
689
690         /**
691          * <p>Utility method for returning a default value if the passed value is empty.</p>
692          * <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
693          * <li>null</li>
694          * <li>undefined</li>
695          * <li>an empty array</li>
696          * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
697          * </ul></div>
698          * @param {Mixed} value The value to test
699          * @param {Mixed} defaultValue The value to return if the original value is empty
700          * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
701          * @return {Mixed} value, if non-empty, else defaultValue
702          * @deprecated 4.0.0 Use {Ext#valueFrom} instead
703          */
704         value : function(v, defaultValue, allowBlank){
705             return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
706         },
707
708         /**
709          * Escapes the passed string for use in a regular expression
710          * @param {String} str
711          * @return {String}
712          * @deprecated 4.0.0 Use {@link Ext.String#escapeRegex} instead
713          */
714         escapeRe : function(s) {
715             return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
716         },
717
718         /**
719          * Applies event listeners to elements by selectors when the document is ready.
720          * The event name is specified with an <tt>&#64;</tt> suffix.
721          * <pre><code>
722 Ext.addBehaviors({
723     // add a listener for click on all anchors in element with id foo
724     '#foo a&#64;click' : function(e, t){
725         // do something
726     },
727
728     // add the same listener to multiple selectors (separated by comma BEFORE the &#64;)
729     '#foo a, #bar span.some-class&#64;mouseover' : function(){
730         // do something
731     }
732 });
733          * </code></pre>
734          * @param {Object} obj The list of behaviors to apply
735          */
736         addBehaviors : function(o){
737             if(!Ext.isReady){
738                 Ext.onReady(function(){
739                     Ext.addBehaviors(o);
740                 });
741             } else {
742                 var cache = {}, // simple cache for applying multiple behaviors to same selector does query multiple times
743                     parts,
744                     b,
745                     s;
746                 for (b in o) {
747                     if ((parts = b.split('@'))[1]) { // for Object prototype breakers
748                         s = parts[0];
749                         if(!cache[s]){
750                             cache[s] = Ext.select(s);
751                         }
752                         cache[s].on(parts[1], o[b]);
753                     }
754                 }
755                 cache = null;
756             }
757         },
758
759         /**
760          * Utility method for getting the width of the browser scrollbar. This can differ depending on
761          * operating system settings, such as the theme or font size.
762          * @param {Boolean} force (optional) true to force a recalculation of the value.
763          * @return {Number} The width of the scrollbar.
764          */
765         getScrollBarWidth: function(force){
766             if(!Ext.isReady){
767                 return 0;
768             }
769
770             if(force === true || scrollWidth === null){
771                 // BrowserBug: IE9
772                 // When IE9 positions an element offscreen via offsets, the offsetWidth is
773                 // inaccurately reported. For IE9 only, we render on screen before removing.
774                 var cssClass = Ext.isIE9 ? '' : Ext.baseCSSPrefix + 'hide-offsets';
775                     // Append our div, do our calculation and then remove it
776                 var div = Ext.getBody().createChild('<div class="' + cssClass + '" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
777                     child = div.child('div', true);
778                 var w1 = child.offsetWidth;
779                 div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
780                 var w2 = child.offsetWidth;
781                 div.remove();
782                 // Need to add 2 to ensure we leave enough space
783                 scrollWidth = w1 - w2 + 2;
784             }
785             return scrollWidth;
786         },
787
788         /**
789          * Copies a set of named properties fom the source object to the destination object.
790          * <p>example:<pre><code>
791 ImageComponent = Ext.extend(Ext.Component, {
792     initComponent: function() {
793         this.autoEl = { tag: 'img' };
794         MyComponent.superclass.initComponent.apply(this, arguments);
795         this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height');
796     }
797 });
798          * </code></pre>
799          * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead.
800          * @param {Object} dest The destination object.
801          * @param {Object} source The source object.
802          * @param {Array/String} names Either an Array of property names, or a comma-delimited list
803          * of property names to copy.
804          * @param {Boolean} usePrototypeKeys (Optional) Defaults to false. Pass true to copy keys off of the prototype as well as the instance.
805          * @return {Object} The modified object.
806         */
807         copyTo : function(dest, source, names, usePrototypeKeys){
808             if(typeof names == 'string'){
809                 names = names.split(/[,;\s]/);
810             }
811             Ext.each(names, function(name){
812                 if(usePrototypeKeys || source.hasOwnProperty(name)){
813                     dest[name] = source[name];
814                 }
815             }, this);
816             return dest;
817         },
818
819         /**
820          * Attempts to destroy and then remove a set of named properties of the passed object.
821          * @param {Object} o The object (most likely a Component) who's properties you wish to destroy.
822          * @param {Mixed} arg1 The name of the property to destroy and remove from the object.
823          * @param {Mixed} etc... More property names to destroy and remove.
824          */
825         destroyMembers : function(o, arg1, arg2, etc){
826             for (var i = 1, a = arguments, len = a.length; i < len; i++) {
827                 Ext.destroy(o[a[i]]);
828                 delete o[a[i]];
829             }
830         },
831
832         /**
833          * Partitions the set into two sets: a true set and a false set.
834          * Example:
835          * Example2:
836          * <pre><code>
837 // Example 1:
838 Ext.partition([true, false, true, true, false]); // [[true, true, true], [false, false]]
839
840 // Example 2:
841 Ext.partition(
842     Ext.query("p"),
843     function(val){
844         return val.className == "class1"
845     }
846 );
847 // true are those paragraph elements with a className of "class1",
848 // false set are those that do not have that className.
849          * </code></pre>
850          * @param {Array|NodeList} arr The array to partition
851          * @param {Function} truth (optional) a function to determine truth.  If this is omitted the element
852          *                   itself must be able to be evaluated for its truthfulness.
853          * @return {Array} [true<Array>,false<Array>]
854          * @deprecated 4.0.0 Will be removed in the next major version
855          */
856         partition : function(arr, truth){
857             var ret = [[],[]];
858             Ext.each(arr, function(v, i, a) {
859                 ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
860             });
861             return ret;
862         },
863
864         /**
865          * Invokes a method on each item in an Array.
866          * <pre><code>
867 // Example:
868 Ext.invoke(Ext.query("p"), "getAttribute", "id");
869 // [el1.getAttribute("id"), el2.getAttribute("id"), ..., elN.getAttribute("id")]
870          * </code></pre>
871          * @param {Array|NodeList} arr The Array of items to invoke the method on.
872          * @param {String} methodName The method name to invoke.
873          * @param {...*} args Arguments to send into the method invocation.
874          * @return {Array} The results of invoking the method on each item in the array.
875          * @deprecated 4.0.0 Will be removed in the next major version
876          */
877         invoke : function(arr, methodName){
878             var ret = [],
879                 args = Array.prototype.slice.call(arguments, 2);
880             Ext.each(arr, function(v,i) {
881                 if (v && typeof v[methodName] == 'function') {
882                     ret.push(v[methodName].apply(v, args));
883                 } else {
884                     ret.push(undefined);
885                 }
886             });
887             return ret;
888         },
889
890         /**
891          * <p>Zips N sets together.</p>
892          * <pre><code>
893 // Example 1:
894 Ext.zip([1,2,3],[4,5,6]); // [[1,4],[2,5],[3,6]]
895 // Example 2:
896 Ext.zip(
897     [ "+", "-", "+"],
898     [  12,  10,  22],
899     [  43,  15,  96],
900     function(a, b, c){
901         return "$" + a + "" + b + "." + c
902     }
903 ); // ["$+12.43", "$-10.15", "$+22.96"]
904          * </code></pre>
905          * @param {Arrays|NodeLists} arr This argument may be repeated. Array(s) to contribute values.
906          * @param {Function} zipper (optional) The last item in the argument list. This will drive how the items are zipped together.
907          * @return {Array} The zipped set.
908          * @deprecated 4.0.0 Will be removed in the next major version
909          */
910         zip : function(){
911             var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
912                 arrs = parts[0],
913                 fn = parts[1][0],
914                 len = Ext.max(Ext.pluck(arrs, "length")),
915                 ret = [];
916
917             for (var i = 0; i < len; i++) {
918                 ret[i] = [];
919                 if(fn){
920                     ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
921                 }else{
922                     for (var j = 0, aLen = arrs.length; j < aLen; j++){
923                         ret[i].push( arrs[j][i] );
924                     }
925                 }
926             }
927             return ret;
928         },
929
930         /**
931          * Turns an array into a sentence, joined by a specified connector - e.g.:
932          * Ext.toSentence(['Adama', 'Tigh', 'Roslin']); //'Adama, Tigh and Roslin'
933          * Ext.toSentence(['Adama', 'Tigh', 'Roslin'], 'or'); //'Adama, Tigh or Roslin'
934          * @param {Array} items The array to create a sentence from
935          * @param {String} connector The string to use to connect the last two words. Usually 'and' or 'or' - defaults to 'and'.
936          * @return {String} The sentence string
937          * @deprecated 4.0.0 Will be removed in the next major version
938          */
939         toSentence: function(items, connector) {
940             var length = items.length;
941
942             if (length <= 1) {
943                 return items[0];
944             } else {
945                 var head = items.slice(0, length - 1),
946                     tail = items[length - 1];
947
948                 return Ext.util.Format.format("{0} {1} {2}", head.join(", "), connector || 'and', tail);
949             }
950         },
951
952         /**
953          * By default, Ext intelligently decides whether floating elements should be shimmed. If you are using flash,
954          * you may want to set this to true.
955          * @type Boolean
956          */
957         useShims: isIE6
958     });
959 })();
960
961 /**
962  * TBD
963  * @type Function
964  * @param {Object} config
965  */
966 Ext.application = function(config) {
967     Ext.require('Ext.app.Application');
968
969     Ext.onReady(function() {
970         Ext.create('Ext.app.Application', config);
971     });
972 };
973
974 /**
975  * @class Ext.util.Format
976
977 This class is a centralized place for formatting functions inside the library. It includes
978 functions to format various different types of data, such as text, dates and numeric values.
979
980 __Localization__
981 This class contains several options for localization. These can be set once the library has loaded,
982 all calls to the functions from that point will use the locale settings that were specified.
983 Options include:
984 - thousandSeparator
985 - decimalSeparator
986 - currenyPrecision
987 - currencySign
988 - currencyAtEnd
989 This class also uses the default date format defined here: {@link Ext.date#defaultFormat}.
990
991 __Using with renderers__
992 There are two helper functions that return a new function that can be used in conjunction with 
993 grid renderers:
994
995     columns: [{
996         dataIndex: 'date',
997         renderer: Ext.util.Format.dateRenderer('Y-m-d')
998     }, {
999         dataIndex: 'time',
1000         renderer: Ext.util.Format.numberRenderer('0.000')
1001     }]
1002     
1003 Functions that only take a single argument can also be passed directly:
1004     columns: [{
1005         dataIndex: 'cost',
1006         renderer: Ext.util.Format.usMoney
1007     }, {
1008         dataIndex: 'productCode',
1009         renderer: Ext.util.Format.uppercase
1010     }]
1011     
1012 __Using with XTemplates__
1013 XTemplates can also directly use Ext.util.Format functions:
1014
1015     new Ext.XTemplate([
1016         'Date: {startDate:date("Y-m-d")}',
1017         'Cost: {cost:usMoney}'
1018     ]);
1019
1020  * @markdown
1021  * @singleton
1022  */
1023 (function() {
1024     Ext.ns('Ext.util');
1025
1026     Ext.util.Format = {};
1027     var UtilFormat     = Ext.util.Format,
1028         stripTagsRE    = /<\/?[^>]+>/gi,
1029         stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
1030         nl2brRe        = /\r?\n/g,
1031
1032         // A RegExp to remove from a number format string, all characters except digits and '.'
1033         formatCleanRe  = /[^\d\.]/g,
1034
1035         // A RegExp to remove from a number format string, all characters except digits and the local decimal separator.
1036         // Created on first use. The local decimal separator character must be initialized for this to be created.
1037         I18NFormatCleanRe;
1038
1039     Ext.apply(UtilFormat, {
1040         /**
1041          * @type String
1042          * @property thousandSeparator
1043          * <p>The character that the {@link #number} function uses as a thousand separator.</p>
1044          * <p>This defaults to <code>,</code>, but may be overridden in a locale file.</p>
1045          */
1046         thousandSeparator: ',',
1047
1048         /**
1049          * @type String
1050          * @property decimalSeparator
1051          * <p>The character that the {@link #number} function uses as a decimal point.</p>
1052          * <p>This defaults to <code>.</code>, but may be overridden in a locale file.</p>
1053          */
1054         decimalSeparator: '.',
1055
1056         /**
1057          * @type Number
1058          * @property currencyPrecision
1059          * <p>The number of decimal places that the {@link #currency} function displays.</p>
1060          * <p>This defaults to <code>2</code>, but may be overridden in a locale file.</p>
1061          */
1062         currencyPrecision: 2,
1063
1064         /**
1065          * @type String
1066          * @property currencySign
1067          * <p>The currency sign that the {@link #currency} function displays.</p>
1068          * <p>This defaults to <code>$</code>, but may be overridden in a locale file.</p>
1069          */
1070         currencySign: '$',
1071
1072         /**
1073          * @type Boolean
1074          * @property currencyAtEnd
1075          * <p>This may be set to <code>true</code> to make the {@link #currency} function
1076          * append the currency sign to the formatted value.</p>
1077          * <p>This defaults to <code>false</code>, but may be overridden in a locale file.</p>
1078          */
1079         currencyAtEnd: false,
1080
1081         /**
1082          * Checks a reference and converts it to empty string if it is undefined
1083          * @param {Mixed} value Reference to check
1084          * @return {Mixed} Empty string if converted, otherwise the original value
1085          */
1086         undef : function(value) {
1087             return value !== undefined ? value : "";
1088         },
1089
1090         /**
1091          * Checks a reference and converts it to the default value if it's empty
1092          * @param {Mixed} value Reference to check
1093          * @param {String} defaultValue The value to insert of it's undefined (defaults to "")
1094          * @return {String}
1095          */
1096         defaultValue : function(value, defaultValue) {
1097             return value !== undefined && value !== '' ? value : defaultValue;
1098         },
1099
1100         /**
1101          * Returns a substring from within an original string
1102          * @param {String} value The original text
1103          * @param {Number} start The start index of the substring
1104          * @param {Number} length The length of the substring
1105          * @return {String} The substring
1106          */
1107         substr : function(value, start, length) {
1108             return String(value).substr(start, length);
1109         },
1110
1111         /**
1112          * Converts a string to all lower case letters
1113          * @param {String} value The text to convert
1114          * @return {String} The converted text
1115          */
1116         lowercase : function(value) {
1117             return String(value).toLowerCase();
1118         },
1119
1120         /**
1121          * Converts a string to all upper case letters
1122          * @param {String} value The text to convert
1123          * @return {String} The converted text
1124          */
1125         uppercase : function(value) {
1126             return String(value).toUpperCase();
1127         },
1128
1129         /**
1130          * Format a number as US currency
1131          * @param {Number/String} value The numeric value to format
1132          * @return {String} The formatted currency string
1133          */
1134         usMoney : function(v) {
1135             return UtilFormat.currency(v, '$', 2);
1136         },
1137
1138         /**
1139          * Format a number as a currency
1140          * @param {Number/String} value The numeric value to format
1141          * @param {String} sign The currency sign to use (defaults to {@link #currencySign})
1142          * @param {Number} decimals The number of decimals to use for the currency (defaults to {@link #currencyPrecision})
1143          * @param {Boolean} end True if the currency sign should be at the end of the string (defaults to {@link #currencyAtEnd})
1144          * @return {String} The formatted currency string
1145          */
1146         currency: function(v, currencySign, decimals, end) {
1147             var negativeSign = '',
1148                 format = ",0",
1149                 i = 0;
1150             v = v - 0;
1151             if (v < 0) {
1152                 v = -v;
1153                 negativeSign = '-';
1154             }
1155             decimals = decimals || UtilFormat.currencyPrecision;
1156             format += format + (decimals > 0 ? '.' : '');
1157             for (; i < decimals; i++) {
1158                 format += '0';
1159             }
1160             v = UtilFormat.number(v, format); 
1161             if ((end || UtilFormat.currencyAtEnd) === true) {
1162                 return Ext.String.format("{0}{1}{2}", negativeSign, v, currencySign || UtilFormat.currencySign);
1163             } else {
1164                 return Ext.String.format("{0}{1}{2}", negativeSign, currencySign || UtilFormat.currencySign, v);
1165             }
1166         },
1167
1168         /**
1169          * Formats the passed date using the specified format pattern.
1170          * @param {String/Date} value The value to format. If a string is passed, it is converted to a Date by the Javascript
1171          * Date object's <a href="http://www.w3schools.com/jsref/jsref_parse.asp">parse()</a> method.
1172          * @param {String} format (Optional) Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
1173          * @return {String} The formatted date string.
1174          */
1175         date: function(v, format) {
1176             if (!v) {
1177                 return "";
1178             }
1179             if (!Ext.isDate(v)) {
1180                 v = new Date(Date.parse(v));
1181             }
1182             return Ext.Date.dateFormat(v, format || Ext.Date.defaultFormat);
1183         },
1184
1185         /**
1186          * Returns a date rendering function that can be reused to apply a date format multiple times efficiently
1187          * @param {String} format Any valid date format string. Defaults to {@link Ext.Date#defaultFormat}.
1188          * @return {Function} The date formatting function
1189          */
1190         dateRenderer : function(format) {
1191             return function(v) {
1192                 return UtilFormat.date(v, format);
1193             };
1194         },
1195
1196         /**
1197          * Strips all HTML tags
1198          * @param {Mixed} value The text from which to strip tags
1199          * @return {String} The stripped text
1200          */
1201         stripTags : function(v) {
1202             return !v ? v : String(v).replace(stripTagsRE, "");
1203         },
1204
1205         /**
1206          * Strips all script tags
1207          * @param {Mixed} value The text from which to strip script tags
1208          * @return {String} The stripped text
1209          */
1210         stripScripts : function(v) {
1211             return !v ? v : String(v).replace(stripScriptsRe, "");
1212         },
1213
1214         /**
1215          * Simple format for a file size (xxx bytes, xxx KB, xxx MB)
1216          * @param {Number/String} size The numeric value to format
1217          * @return {String} The formatted file size
1218          */
1219         fileSize : function(size) {
1220             if (size < 1024) {
1221                 return size + " bytes";
1222             } else if (size < 1048576) {
1223                 return (Math.round(((size*10) / 1024))/10) + " KB";
1224             } else {
1225                 return (Math.round(((size*10) / 1048576))/10) + " MB";
1226             }
1227         },
1228
1229         /**
1230          * It does simple math for use in a template, for example:<pre><code>
1231          * var tpl = new Ext.Template('{value} * 10 = {value:math("* 10")}');
1232          * </code></pre>
1233          * @return {Function} A function that operates on the passed value.
1234          */
1235         math : function(){
1236             var fns = {};
1237
1238             return function(v, a){
1239                 if (!fns[a]) {
1240                     fns[a] = Ext.functionFactory('v', 'return v ' + a + ';');
1241                 }
1242                 return fns[a](v);
1243             };
1244         }(),
1245
1246         /**
1247          * Rounds the passed number to the required decimal precision.
1248          * @param {Number/String} value The numeric value to round.
1249          * @param {Number} precision The number of decimal places to which to round the first parameter's value.
1250          * @return {Number} The rounded value.
1251          */
1252         round : function(value, precision) {
1253             var result = Number(value);
1254             if (typeof precision == 'number') {
1255                 precision = Math.pow(10, precision);
1256                 result = Math.round(value * precision) / precision;
1257             }
1258             return result;
1259         },
1260
1261         /**
1262          * <p>Formats the passed number according to the passed format string.</p>
1263          * <p>The number of digits after the decimal separator character specifies the number of
1264          * decimal places in the resulting string. The <u>local-specific</u> decimal character is used in the result.</p>
1265          * <p>The <i>presence</i> of a thousand separator character in the format string specifies that
1266          * the <u>locale-specific</u> thousand separator (if any) is inserted separating thousand groups.</p>
1267          * <p>By default, "," is expected as the thousand separator, and "." is expected as the decimal separator.</p>
1268          * <p><b>New to Ext4</b></p>
1269          * <p>Locale-specific characters are always used in the formatted output when inserting
1270          * thousand and decimal separators.</p>
1271          * <p>The format string must specify separator characters according to US/UK conventions ("," as the
1272          * thousand separator, and "." as the decimal separator)</p>
1273          * <p>To allow specification of format strings according to local conventions for separator characters, add
1274          * the string <code>/i</code> to the end of the format string.</p>
1275          * <div style="margin-left:40px">examples (123456.789):
1276          * <div style="margin-left:10px">
1277          * 0 - (123456) show only digits, no precision<br>
1278          * 0.00 - (123456.78) show only digits, 2 precision<br>
1279          * 0.0000 - (123456.7890) show only digits, 4 precision<br>
1280          * 0,000 - (123,456) show comma and digits, no precision<br>
1281          * 0,000.00 - (123,456.78) show comma and digits, 2 precision<br>
1282          * 0,0.00 - (123,456.78) shortcut method, show comma and digits, 2 precision<br>
1283          * To allow specification of the formatting string using UK/US grouping characters (,) and decimal (.) for international numbers, add /i to the end.
1284          * For example: 0.000,00/i
1285          * </div></div>
1286          * @param {Number} v The number to format.
1287          * @param {String} format The way you would like to format this text.
1288          * @return {String} The formatted number.
1289          */
1290         number:
1291             function(v, formatString) {
1292             if (!formatString) {
1293                 return v;
1294             }
1295             v = Ext.Number.from(v, NaN);
1296             if (isNaN(v)) {
1297                 return '';
1298             }
1299             var comma = UtilFormat.thousandSeparator,
1300                 dec   = UtilFormat.decimalSeparator,
1301                 i18n  = false,
1302                 neg   = v < 0,
1303                 hasComma,
1304                 psplit;
1305
1306             v = Math.abs(v);
1307
1308             // The "/i" suffix allows caller to use a locale-specific formatting string.
1309             // Clean the format string by removing all but numerals and the decimal separator.
1310             // Then split the format string into pre and post decimal segments according to *what* the
1311             // decimal separator is. If they are specifying "/i", they are using the local convention in the format string.
1312             if (formatString.substr(formatString.length - 2) == '/i') {
1313                 if (!I18NFormatCleanRe) {
1314                     I18NFormatCleanRe = new RegExp('[^\\d\\' + UtilFormat.decimalSeparator + ']','g');
1315                 }
1316                 formatString = formatString.substr(0, formatString.length - 2);
1317                 i18n   = true;
1318                 hasComma = formatString.indexOf(comma) != -1;
1319                 psplit = formatString.replace(I18NFormatCleanRe, '').split(dec);
1320             } else {
1321                 hasComma = formatString.indexOf(',') != -1;
1322                 psplit = formatString.replace(formatCleanRe, '').split('.');
1323             }
1324
1325             if (1 < psplit.length) {
1326                 v = v.toFixed(psplit[1].length);
1327             } else if(2 < psplit.length) {
1328                 //<debug>
1329                 Ext.Error.raise({
1330                     sourceClass: "Ext.util.Format",
1331                     sourceMethod: "number",
1332                     value: v,
1333                     formatString: formatString,
1334                     msg: "Invalid number format, should have no more than 1 decimal"
1335                 });
1336                 //</debug>
1337             } else {
1338                 v = v.toFixed(0);
1339             }
1340
1341             var fnum = v.toString();
1342
1343             psplit = fnum.split('.');
1344
1345             if (hasComma) {
1346                 var cnum = psplit[0],
1347                     parr = [],
1348                     j    = cnum.length,
1349                     m    = Math.floor(j / 3),
1350                     n    = cnum.length % 3 || 3,
1351                     i;
1352
1353                 for (i = 0; i < j; i += n) {
1354                     if (i !== 0) {
1355                         n = 3;
1356                     }
1357
1358                     parr[parr.length] = cnum.substr(i, n);
1359                     m -= 1;
1360                 }
1361                 fnum = parr.join(comma);
1362                 if (psplit[1]) {
1363                     fnum += dec + psplit[1];
1364                 }
1365             } else {
1366                 if (psplit[1]) {
1367                     fnum = psplit[0] + dec + psplit[1];
1368                 }
1369             }
1370
1371             return (neg ? '-' : '') + formatString.replace(/[\d,?\.?]+/, fnum);
1372         },
1373
1374         /**
1375          * Returns a number rendering function that can be reused to apply a number format multiple times efficiently
1376          * @param {String} format Any valid number format string for {@link #number}
1377          * @return {Function} The number formatting function
1378          */
1379         numberRenderer : function(format) {
1380             return function(v) {
1381                 return UtilFormat.number(v, format);
1382             };
1383         },
1384
1385         /**
1386          * Selectively do a plural form of a word based on a numeric value. For example, in a template,
1387          * {commentCount:plural("Comment")}  would result in "1 Comment" if commentCount was 1 or would be "x Comments"
1388          * if the value is 0 or greater than 1.
1389          * @param {Number} value The value to compare against
1390          * @param {String} singular The singular form of the word
1391          * @param {String} plural (optional) The plural form of the word (defaults to the singular with an "s")
1392          */
1393         plural : function(v, s, p) {
1394             return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
1395         },
1396
1397         /**
1398          * Converts newline characters to the HTML tag &lt;br/>
1399          * @param {String} The string value to format.
1400          * @return {String} The string with embedded &lt;br/> tags in place of newlines.
1401          */
1402         nl2br : function(v) {
1403             return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
1404         },
1405
1406         /**
1407          * Capitalize the given string. See {@link Ext.String#capitalize}.
1408          */
1409         capitalize: Ext.String.capitalize,
1410
1411         /**
1412          * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length.
1413          * See {@link Ext.String#ellipsis}.
1414          */
1415         ellipsis: Ext.String.ellipsis,
1416
1417         /**
1418          * Formats to a string. See {@link Ext.String#format}
1419          */
1420         format: Ext.String.format,
1421
1422         /**
1423          * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
1424          * See {@link Ext.string#htmlDecode}.
1425          */
1426         htmlDecode: Ext.String.htmlDecode,
1427
1428         /**
1429          * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
1430          * See {@link Ext.String#htmlEncode}.
1431          */
1432         htmlEncode: Ext.String.htmlEncode,
1433
1434         /**
1435          * Adds left padding to a string. See {@link Ext.String#leftPad}
1436          */
1437         leftPad: Ext.String.leftPad,
1438
1439         /**
1440          * Trims any whitespace from either side of a string. See {@link Ext.String#trim}.
1441          */
1442         trim : Ext.String.trim,
1443
1444         /**
1445          * Parses a number or string representing margin sizes into an object. Supports CSS-style margin declarations
1446          * (e.g. 10, "10", "10 10", "10 10 10" and "10 10 10 10" are all valid options and would return the same result)
1447          * @param {Number|String} v The encoded margins
1448          * @return {Object} An object with margin sizes for top, right, bottom and left
1449          */
1450         parseBox : function(box) {
1451             if (Ext.isNumber(box)) {
1452                 box = box.toString();
1453             }
1454             var parts  = box.split(' '),
1455                 ln = parts.length;
1456
1457             if (ln == 1) {
1458                 parts[1] = parts[2] = parts[3] = parts[0];
1459             }
1460             else if (ln == 2) {
1461                 parts[2] = parts[0];
1462                 parts[3] = parts[1];
1463             }
1464             else if (ln == 3) {
1465                 parts[3] = parts[1];
1466             }
1467
1468             return {
1469                 top   :parseInt(parts[0], 10) || 0,
1470                 right :parseInt(parts[1], 10) || 0,
1471                 bottom:parseInt(parts[2], 10) || 0,
1472                 left  :parseInt(parts[3], 10) || 0
1473             };
1474         },
1475
1476         /**
1477          * Escapes the passed string for use in a regular expression
1478          * @param {String} str
1479          * @return {String}
1480          */
1481         escapeRegex : function(s) {
1482             return s.replace(/([\-.*+?\^${}()|\[\]\/\\])/g, "\\$1");
1483         }
1484     });
1485 })();
1486
1487 /**
1488  * @class Ext.util.TaskRunner
1489  * Provides the ability to execute one or more arbitrary tasks in a multithreaded
1490  * manner.  Generally, you can use the singleton {@link Ext.TaskManager} instead, but
1491  * if needed, you can create separate instances of TaskRunner.  Any number of
1492  * separate tasks can be started at any time and will run independently of each
1493  * other. Example usage:
1494  * <pre><code>
1495 // Start a simple clock task that updates a div once per second
1496 var updateClock = function(){
1497     Ext.fly('clock').update(new Date().format('g:i:s A'));
1498
1499 var task = {
1500     run: updateClock,
1501     interval: 1000 //1 second
1502 }
1503 var runner = new Ext.util.TaskRunner();
1504 runner.start(task);
1505
1506 // equivalent using TaskManager
1507 Ext.TaskManager.start({
1508     run: updateClock,
1509     interval: 1000
1510 });
1511
1512  * </code></pre>
1513  * <p>See the {@link #start} method for details about how to configure a task object.</p>
1514  * Also see {@link Ext.util.DelayedTask}. 
1515  * 
1516  * @constructor
1517  * @param {Number} interval (optional) The minimum precision in milliseconds supported by this TaskRunner instance
1518  * (defaults to 10)
1519  */
1520 Ext.ns('Ext.util');
1521
1522 Ext.util.TaskRunner = function(interval) {
1523     interval = interval || 10;
1524     var tasks = [],
1525     removeQueue = [],
1526     id = 0,
1527     running = false,
1528
1529     // private
1530     stopThread = function() {
1531         running = false;
1532         clearInterval(id);
1533         id = 0;
1534     },
1535
1536     // private
1537     startThread = function() {
1538         if (!running) {
1539             running = true;
1540             id = setInterval(runTasks, interval);
1541         }
1542     },
1543
1544     // private
1545     removeTask = function(t) {
1546         removeQueue.push(t);
1547         if (t.onStop) {
1548             t.onStop.apply(t.scope || t);
1549         }
1550     },
1551
1552     // private
1553     runTasks = function() {
1554         var rqLen = removeQueue.length,
1555             now = new Date().getTime(),
1556             i;
1557
1558         if (rqLen > 0) {
1559             for (i = 0; i < rqLen; i++) {
1560                 Ext.Array.remove(tasks, removeQueue[i]);
1561             }
1562             removeQueue = [];
1563             if (tasks.length < 1) {
1564                 stopThread();
1565                 return;
1566             }
1567         }
1568         i = 0;
1569         var t,
1570             itime,
1571             rt,
1572             len = tasks.length;
1573         for (; i < len; ++i) {
1574             t = tasks[i];
1575             itime = now - t.taskRunTime;
1576             if (t.interval <= itime) {
1577                 rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
1578                 t.taskRunTime = now;
1579                 if (rt === false || t.taskRunCount === t.repeat) {
1580                     removeTask(t);
1581                     return;
1582                 }
1583             }
1584             if (t.duration && t.duration <= (now - t.taskStartTime)) {
1585                 removeTask(t);
1586             }
1587         }
1588     };
1589
1590     /**
1591      * Starts a new task.
1592      * @method start
1593      * @param {Object} task <p>A config object that supports the following properties:<ul>
1594      * <li><code>run</code> : Function<div class="sub-desc"><p>The function to execute each time the task is invoked. The
1595      * function will be called at each interval and passed the <code>args</code> argument if specified, and the
1596      * current invocation count if not.</p>
1597      * <p>If a particular scope (<code>this</code> reference) is required, be sure to specify it using the <code>scope</code> argument.</p>
1598      * <p>Return <code>false</code> from this function to terminate the task.</p></div></li>
1599      * <li><code>interval</code> : Number<div class="sub-desc">The frequency in milliseconds with which the task
1600      * should be invoked.</div></li>
1601      * <li><code>args</code> : Array<div class="sub-desc">(optional) An array of arguments to be passed to the function
1602      * specified by <code>run</code>. If not specified, the current invocation count is passed.</div></li>
1603      * <li><code>scope</code> : Object<div class="sub-desc">(optional) The scope (<tt>this</tt> reference) in which to execute the
1604      * <code>run</code> function. Defaults to the task config object.</div></li>
1605      * <li><code>duration</code> : Number<div class="sub-desc">(optional) The length of time in milliseconds to invoke
1606      * the task before stopping automatically (defaults to indefinite).</div></li>
1607      * <li><code>repeat</code> : Number<div class="sub-desc">(optional) The number of times to invoke the task before
1608      * stopping automatically (defaults to indefinite).</div></li>
1609      * </ul></p>
1610      * <p>Before each invocation, Ext injects the property <code>taskRunCount</code> into the task object so
1611      * that calculations based on the repeat count can be performed.</p>
1612      * @return {Object} The task
1613      */
1614     this.start = function(task) {
1615         tasks.push(task);
1616         task.taskStartTime = new Date().getTime();
1617         task.taskRunTime = 0;
1618         task.taskRunCount = 0;
1619         startThread();
1620         return task;
1621     };
1622
1623     /**
1624      * Stops an existing running task.
1625      * @method stop
1626      * @param {Object} task The task to stop
1627      * @return {Object} The task
1628      */
1629     this.stop = function(task) {
1630         removeTask(task);
1631         return task;
1632     };
1633
1634     /**
1635      * Stops all tasks that are currently running.
1636      * @method stopAll
1637      */
1638     this.stopAll = function() {
1639         stopThread();
1640         for (var i = 0, len = tasks.length; i < len; i++) {
1641             if (tasks[i].onStop) {
1642                 tasks[i].onStop();
1643             }
1644         }
1645         tasks = [];
1646         removeQueue = [];
1647     };
1648 };
1649
1650 /**
1651  * @class Ext.TaskManager
1652  * @extends Ext.util.TaskRunner
1653  * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks.  See
1654  * {@link Ext.util.TaskRunner} for supported methods and task config properties.
1655  * <pre><code>
1656 // Start a simple clock task that updates a div once per second
1657 var task = {
1658     run: function(){
1659         Ext.fly('clock').update(new Date().format('g:i:s A'));
1660     },
1661     interval: 1000 //1 second
1662 }
1663 Ext.TaskManager.start(task);
1664 </code></pre>
1665  * <p>See the {@link #start} method for details about how to configure a task object.</p>
1666  * @singleton
1667  */
1668 Ext.TaskManager = Ext.create('Ext.util.TaskRunner');
1669 /**
1670  * @class Ext.is
1671  * 
1672  * Determines information about the current platform the application is running on.
1673  * 
1674  * @singleton
1675  */
1676 Ext.is = {
1677     init : function(navigator) {
1678         var platforms = this.platforms,
1679             ln = platforms.length,
1680             i, platform;
1681
1682         navigator = navigator || window.navigator;
1683
1684         for (i = 0; i < ln; i++) {
1685             platform = platforms[i];
1686             this[platform.identity] = platform.regex.test(navigator[platform.property]);
1687         }
1688
1689         /**
1690          * @property Desktop True if the browser is running on a desktop machine
1691          * @type {Boolean}
1692          */
1693         this.Desktop = this.Mac || this.Windows || (this.Linux && !this.Android);
1694         /**
1695          * @property Tablet True if the browser is running on a tablet (iPad)
1696          */
1697         this.Tablet = this.iPad;
1698         /**
1699          * @property Phone True if the browser is running on a phone.
1700          * @type {Boolean}
1701          */
1702         this.Phone = !this.Desktop && !this.Tablet;
1703         /**
1704          * @property iOS True if the browser is running on iOS
1705          * @type {Boolean}
1706          */
1707         this.iOS = this.iPhone || this.iPad || this.iPod;
1708         
1709         /**
1710          * @property Standalone Detects when application has been saved to homescreen.
1711          * @type {Boolean}
1712          */
1713         this.Standalone = !!window.navigator.standalone;
1714     },
1715     
1716     /**
1717      * @property iPhone True when the browser is running on a iPhone
1718      * @type {Boolean}
1719      */
1720     platforms: [{
1721         property: 'platform',
1722         regex: /iPhone/i,
1723         identity: 'iPhone'
1724     },
1725     
1726     /**
1727      * @property iPod True when the browser is running on a iPod
1728      * @type {Boolean}
1729      */
1730     {
1731         property: 'platform',
1732         regex: /iPod/i,
1733         identity: 'iPod'
1734     },
1735     
1736     /**
1737      * @property iPad True when the browser is running on a iPad
1738      * @type {Boolean}
1739      */
1740     {
1741         property: 'userAgent',
1742         regex: /iPad/i,
1743         identity: 'iPad'
1744     },
1745     
1746     /**
1747      * @property Blackberry True when the browser is running on a Blackberry
1748      * @type {Boolean}
1749      */
1750     {
1751         property: 'userAgent',
1752         regex: /Blackberry/i,
1753         identity: 'Blackberry'
1754     },
1755     
1756     /**
1757      * @property Android True when the browser is running on an Android device
1758      * @type {Boolean}
1759      */
1760     {
1761         property: 'userAgent',
1762         regex: /Android/i,
1763         identity: 'Android'
1764     },
1765     
1766     /**
1767      * @property Mac True when the browser is running on a Mac
1768      * @type {Boolean}
1769      */
1770     {
1771         property: 'platform',
1772         regex: /Mac/i,
1773         identity: 'Mac'
1774     },
1775     
1776     /**
1777      * @property Windows True when the browser is running on Windows
1778      * @type {Boolean}
1779      */
1780     {
1781         property: 'platform',
1782         regex: /Win/i,
1783         identity: 'Windows'
1784     },
1785     
1786     /**
1787      * @property Linux True when the browser is running on Linux
1788      * @type {Boolean}
1789      */
1790     {
1791         property: 'platform',
1792         regex: /Linux/i,
1793         identity: 'Linux'
1794     }]
1795 };
1796
1797 Ext.is.init();
1798
1799 /**
1800  * @class Ext.supports
1801  *
1802  * Determines information about features are supported in the current environment
1803  * 
1804  * @singleton
1805  */
1806 Ext.supports = {
1807     init : function() {
1808         var doc = document,
1809             div = doc.createElement('div'),
1810             tests = this.tests,
1811             ln = tests.length,
1812             i, test;
1813
1814         div.innerHTML = [
1815             '<div style="height:30px;width:50px;">',
1816                 '<div style="height:20px;width:20px;"></div>',
1817             '</div>',
1818             '<div style="width: 200px; height: 200px; position: relative; padding: 5px;">',
1819                 '<div style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;"></div>',
1820             '</div>',
1821             '<div style="float:left; background-color:transparent;"></div>'
1822         ].join('');
1823
1824         doc.body.appendChild(div);
1825
1826         for (i = 0; i < ln; i++) {
1827             test = tests[i];
1828             this[test.identity] = test.fn.call(this, doc, div);
1829         }
1830
1831         doc.body.removeChild(div);
1832     },
1833
1834     /**
1835      * @property CSS3BoxShadow True if document environment supports the CSS3 box-shadow style.
1836      * @type {Boolean}
1837      */
1838     CSS3BoxShadow: Ext.isDefined(document.documentElement.style.boxShadow),
1839
1840     /**
1841      * @property ClassList True if document environment supports the HTML5 classList API.
1842      * @type {Boolean}
1843      */
1844     ClassList: !!document.documentElement.classList,
1845
1846     /**
1847      * @property OrientationChange True if the device supports orientation change
1848      * @type {Boolean}
1849      */
1850     OrientationChange: ((typeof window.orientation != 'undefined') && ('onorientationchange' in window)),
1851     
1852     /**
1853      * @property DeviceMotion True if the device supports device motion (acceleration and rotation rate)
1854      * @type {Boolean}
1855      */
1856     DeviceMotion: ('ondevicemotion' in window),
1857     
1858     /**
1859      * @property Touch True if the device supports touch
1860      * @type {Boolean}
1861      */
1862     // is.Desktop is needed due to the bug in Chrome 5.0.375, Safari 3.1.2
1863     // and Safari 4.0 (they all have 'ontouchstart' in the window object).
1864     Touch: ('ontouchstart' in window) && (!Ext.is.Desktop),
1865
1866     tests: [
1867         /**
1868          * @property Transitions True if the device supports CSS3 Transitions
1869          * @type {Boolean}
1870          */
1871         {
1872             identity: 'Transitions',
1873             fn: function(doc, div) {
1874                 var prefix = [
1875                         'webkit',
1876                         'Moz',
1877                         'o',
1878                         'ms',
1879                         'khtml'
1880                     ],
1881                     TE = 'TransitionEnd',
1882                     transitionEndName = [
1883                         prefix[0] + TE,
1884                         'transitionend', //Moz bucks the prefixing convention
1885                         prefix[2] + TE,
1886                         prefix[3] + TE,
1887                         prefix[4] + TE
1888                     ],
1889                     ln = prefix.length,
1890                     i = 0,
1891                     out = false;
1892                 div = Ext.get(div);
1893                 for (; i < ln; i++) {
1894                     if (div.getStyle(prefix[i] + "TransitionProperty")) {
1895                         Ext.supports.CSS3Prefix = prefix[i];
1896                         Ext.supports.CSS3TransitionEnd = transitionEndName[i];
1897                         out = true;
1898                         break;
1899                     }
1900                 }
1901                 return out;
1902             }
1903         },
1904         
1905         /**
1906          * @property RightMargin True if the device supports right margin.
1907          * See https://bugs.webkit.org/show_bug.cgi?id=13343 for why this is needed.
1908          * @type {Boolean}
1909          */
1910         {
1911             identity: 'RightMargin',
1912             fn: function(doc, div, view) {
1913                 view = doc.defaultView;
1914                 return !(view && view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px');
1915             }
1916         },
1917         
1918         /**
1919          * @property TransparentColor True if the device supports transparent color
1920          * @type {Boolean}
1921          */
1922         {
1923             identity: 'TransparentColor',
1924             fn: function(doc, div, view) {
1925                 view = doc.defaultView;
1926                 return !(view && view.getComputedStyle(div.lastChild, null).backgroundColor != 'transparent');
1927             }
1928         },
1929
1930         /**
1931          * @property ComputedStyle True if the browser supports document.defaultView.getComputedStyle()
1932          * @type {Boolean}
1933          */
1934         {
1935             identity: 'ComputedStyle',
1936             fn: function(doc, div, view) {
1937                 view = doc.defaultView;
1938                 return view && view.getComputedStyle;
1939             }
1940         },
1941         
1942         /**
1943          * @property SVG True if the device supports SVG
1944          * @type {Boolean}
1945          */
1946         {
1947             identity: 'Svg',
1948             fn: function(doc) {
1949                 return !!doc.createElementNS && !!doc.createElementNS( "http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect;
1950             }
1951         },
1952     
1953         /**
1954          * @property Canvas True if the device supports Canvas
1955          * @type {Boolean}
1956          */
1957         {
1958             identity: 'Canvas',
1959             fn: function(doc) {
1960                 return !!doc.createElement('canvas').getContext;
1961             }
1962         },
1963         
1964         /**
1965          * @property VML True if the device supports VML
1966          * @type {Boolean}
1967          */
1968         {
1969             identity: 'Vml',
1970             fn: function(doc) {
1971                 var d = doc.createElement("div");
1972                 d.innerHTML = "<!--[if vml]><br><br><![endif]-->";
1973                 return (d.childNodes.length == 2);
1974             }
1975         },
1976         
1977         /**
1978          * @property Float True if the device supports CSS float
1979          * @type {Boolean}
1980          */
1981         {
1982             identity: 'Float',
1983             fn: function(doc, div) {
1984                 return !!div.lastChild.style.cssFloat;
1985             }
1986         },
1987         
1988         /**
1989          * @property AudioTag True if the device supports the HTML5 audio tag
1990          * @type {Boolean}
1991          */
1992         {
1993             identity: 'AudioTag',
1994             fn: function(doc) {
1995                 return !!doc.createElement('audio').canPlayType;
1996             }
1997         },
1998         
1999         /**
2000          * @property History True if the device supports HTML5 history
2001          * @type {Boolean}
2002          */
2003         {
2004             identity: 'History',
2005             fn: function() {
2006                 return !!(window.history && history.pushState);
2007             }
2008         },
2009         
2010         /**
2011          * @property CSS3DTransform True if the device supports CSS3DTransform
2012          * @type {Boolean}
2013          */
2014         {
2015             identity: 'CSS3DTransform',
2016             fn: function() {
2017                 return (typeof WebKitCSSMatrix != 'undefined' && new WebKitCSSMatrix().hasOwnProperty('m41'));
2018             }
2019         },
2020
2021                 /**
2022          * @property CSS3LinearGradient True if the device supports CSS3 linear gradients
2023          * @type {Boolean}
2024          */
2025         {
2026             identity: 'CSS3LinearGradient',
2027             fn: function(doc, div) {
2028                 var property = 'background-image:',
2029                     webkit   = '-webkit-gradient(linear, left top, right bottom, from(black), to(white))',
2030                     w3c      = 'linear-gradient(left top, black, white)',
2031                     moz      = '-moz-' + w3c,
2032                     options  = [property + webkit, property + w3c, property + moz];
2033                 
2034                 div.style.cssText = options.join(';');
2035                 
2036                 return ("" + div.style.backgroundImage).indexOf('gradient') !== -1;
2037             }
2038         },
2039         
2040         /**
2041          * @property CSS3BorderRadius True if the device supports CSS3 border radius
2042          * @type {Boolean}
2043          */
2044         {
2045             identity: 'CSS3BorderRadius',
2046             fn: function(doc, div) {
2047                 var domPrefixes = ['borderRadius', 'BorderRadius', 'MozBorderRadius', 'WebkitBorderRadius', 'OBorderRadius', 'KhtmlBorderRadius'],
2048                     pass = false,
2049                     i;
2050                 for (i = 0; i < domPrefixes.length; i++) {
2051                     if (document.body.style[domPrefixes[i]] !== undefined) {
2052                         return true;
2053                     }
2054                 }
2055                 return pass;
2056             }
2057         },
2058         
2059         /**
2060          * @property GeoLocation True if the device supports GeoLocation
2061          * @type {Boolean}
2062          */
2063         {
2064             identity: 'GeoLocation',
2065             fn: function() {
2066                 return (typeof navigator != 'undefined' && typeof navigator.geolocation != 'undefined') || (typeof google != 'undefined' && typeof google.gears != 'undefined');
2067             }
2068         },
2069         /**
2070          * @property MouseEnterLeave True if the browser supports mouseenter and mouseleave events
2071          * @type {Boolean}
2072          */
2073         {
2074             identity: 'MouseEnterLeave',
2075             fn: function(doc, div){
2076                 return ('onmouseenter' in div && 'onmouseleave' in div);
2077             }
2078         },
2079         /**
2080          * @property MouseWheel True if the browser supports the mousewheel event
2081          * @type {Boolean}
2082          */
2083         {
2084             identity: 'MouseWheel',
2085             fn: function(doc, div) {
2086                 return ('onmousewheel' in div);
2087             }
2088         },
2089         /**
2090          * @property Opacity True if the browser supports normal css opacity
2091          * @type {Boolean}
2092          */
2093         {
2094             identity: 'Opacity',
2095             fn: function(doc, div){
2096                 // Not a strict equal comparison in case opacity can be converted to a number.
2097                 if (Ext.isIE6 || Ext.isIE7 || Ext.isIE8) {
2098                     return false;
2099                 }
2100                 div.firstChild.style.cssText = 'opacity:0.73';
2101                 return div.firstChild.style.opacity == '0.73';
2102             }
2103         },
2104         /**
2105          * @property Placeholder True if the browser supports the HTML5 placeholder attribute on inputs
2106          * @type {Boolean}
2107          */
2108         {
2109             identity: 'Placeholder',
2110             fn: function(doc) {
2111                 return 'placeholder' in doc.createElement('input');
2112             }
2113         },
2114         
2115         /**
2116          * @property Direct2DBug True if when asking for an element's dimension via offsetWidth or offsetHeight, 
2117          * getBoundingClientRect, etc. the browser returns the subpixel width rounded to the nearest pixel.
2118          * @type {Boolean}
2119          */
2120         {
2121             identity: 'Direct2DBug',
2122             fn: function() {
2123                 return Ext.isString(document.body.style.msTransformOrigin);
2124             }
2125         },
2126         /**
2127          * @property BoundingClientRect True if the browser supports the getBoundingClientRect method on elements
2128          * @type {Boolean}
2129          */
2130         {
2131             identity: 'BoundingClientRect',
2132             fn: function(doc, div) {
2133                 return Ext.isFunction(div.getBoundingClientRect);
2134             }
2135         },
2136         {
2137             identity: 'IncludePaddingInWidthCalculation',
2138             fn: function(doc, div){
2139                 var el = Ext.get(div.childNodes[1].firstChild);
2140                 return el.getWidth() == 210;
2141             }
2142         },
2143         {
2144             identity: 'IncludePaddingInHeightCalculation',
2145             fn: function(doc, div){
2146                 var el = Ext.get(div.childNodes[1].firstChild);
2147                 return el.getHeight() == 210;
2148             }
2149         },
2150         
2151         /**
2152          * @property ArraySort True if the Array sort native method isn't bugged.
2153          * @type {Boolean}
2154          */
2155         {
2156             identity: 'ArraySort',
2157             fn: function() {
2158                 var a = [1,2,3,4,5].sort(function(){ return 0; });
2159                 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
2160             }
2161         },
2162         /**
2163          * @property Range True if browser support document.createRange native method.
2164          * @type {Boolean}
2165          */
2166         {
2167             identity: 'Range',
2168             fn: function() {
2169                 return !!document.createRange;
2170             }
2171         },
2172         /**
2173          * @property CreateContextualFragment True if browser support CreateContextualFragment range native methods.
2174          * @type {Boolean}
2175          */
2176         {
2177             identity: 'CreateContextualFragment',
2178             fn: function() {
2179                 var range = Ext.supports.Range ? document.createRange() : false;
2180                 
2181                 return range && !!range.createContextualFragment;
2182             }
2183         }
2184         
2185     ]
2186 };
2187
2188