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