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