2 Ext JS - JavaScript Library
3 Copyright (c) 2006-2011, Sencha Inc.
13 objectPrototype = Object.prototype,
14 toString = Object.prototype.toString,
16 enumerablesTest = { toString: 1 },
19 if (typeof Ext === 'undefined') {
25 for (i in enumerablesTest) {
30 enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
31 'toLocaleString', 'toString', 'constructor'];
35 * An array containing extra enumerables for old browsers
38 Ext.enumerables = enumerables;
41 * Copies all the properties of config to the specified object.
42 * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
43 * {@link Ext.Object#merge} instead.
44 * @param {Object} object The receiver of the properties
45 * @param {Object} config The source of the properties
46 * @param {Object} defaults A different object that will also be applied for default values
47 * @return {Object} returns obj
49 Ext.apply = function(object, config, defaults) {
51 Ext.apply(object, defaults);
54 if (object && config && typeof config === 'object') {
58 object[i] = config[i];
62 for (j = enumerables.length; j--;) {
64 if (config.hasOwnProperty(k)) {
65 object[k] = config[k];
74 Ext.buildSettings = Ext.apply({
77 }, Ext.buildSettings || {});
81 * A reusable empty function
83 emptyFn: function() {},
85 baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
88 * Copies all the properties of config to object if they don't already exist.
90 * @param {Object} object The receiver of the properties
91 * @param {Object} config The source of the properties
92 * @return {Object} returns obj
94 applyIf: function(object, config) {
98 for (property in config) {
99 if (object[property] === undefined) {
100 object[property] = config[property];
109 * Iterates either an array or an object. This method delegates to
110 * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
112 * @param {Object/Array} object The object or array to be iterated.
113 * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
114 * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
115 * type that is being iterated.
116 * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
117 * Defaults to the object being iterated itself.
120 iterate: function(object, fn, scope) {
121 if (Ext.isEmpty(object)) {
125 if (scope === undefined) {
129 if (Ext.isIterable(object)) {
130 Ext.Array.each.call(Ext.Array, object, fn, scope);
133 Ext.Object.each.call(Ext.Object, object, fn, scope);
141 * This method deprecated. Use {@link Ext#define Ext.define} instead.
143 * @param {Function} superclass
144 * @param {Object} overrides
145 * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
146 * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
150 var objectConstructor = objectPrototype.constructor,
151 inlineOverrides = function(o) {
153 if (!o.hasOwnProperty(m)) {
160 return function(subclass, superclass, overrides) {
161 // First we check if the user passed in just the superClass with overrides
162 if (Ext.isObject(superclass)) {
163 overrides = superclass;
164 superclass = subclass;
165 subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
166 superclass.apply(this, arguments);
174 sourceMethod: 'extend',
175 msg: 'Attempting to extend from a class which has not been loaded on the page.'
180 // We create a new temporary class
181 var F = function() {},
182 subclassProto, superclassProto = superclass.prototype;
184 F.prototype = superclassProto;
185 subclassProto = subclass.prototype = new F();
186 subclassProto.constructor = subclass;
187 subclass.superclass = superclassProto;
189 if (superclassProto.constructor === objectConstructor) {
190 superclassProto.constructor = superclass;
193 subclass.override = function(overrides) {
194 Ext.override(subclass, overrides);
197 subclassProto.override = inlineOverrides;
198 subclassProto.proto = subclassProto;
200 subclass.override(overrides);
201 subclass.extend = function(o) {
202 return Ext.extend(subclass, o);
210 * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
212 Ext.define('My.cool.Class', {
218 Ext.override(My.cool.Class, {
220 alert('About to say...');
222 this.callOverridden();
226 var cool = new My.cool.Class();
227 cool.sayHi(); // alerts 'About to say...'
230 * Please note that `this.callOverridden()` only works if the class was previously
231 * created with {@link Ext#define)
233 * @param {Object} cls The class to override
234 * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
235 * containing one or more methods.
239 override: function(cls, overrides) {
240 if (cls.prototype.$className) {
241 return cls.override(overrides);
244 Ext.apply(cls.prototype, overrides);
249 // A full set of static methods to do type checking
253 * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
254 * value (second argument) otherwise.
256 * @param {Mixed} value The value to test
257 * @param {Mixed} defaultValue The value to return if the original value is empty
258 * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
259 * @return {Mixed} value, if non-empty, else defaultValue
261 valueFrom: function(value, defaultValue, allowBlank){
262 return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
266 * Returns the type of the given variable in string format. List of possible values are:
268 * - `undefined`: If the given value is `undefined`
269 * - `null`: If the given value is `null`
270 * - `string`: If the given value is a string
271 * - `number`: If the given value is a number
272 * - `boolean`: If the given value is a boolean value
273 * - `date`: If the given value is a `Date` object
274 * - `function`: If the given value is a function reference
275 * - `object`: If the given value is an object
276 * - `array`: If the given value is an array
277 * - `regexp`: If the given value is a regular expression
278 * - `element`: If the given value is a DOM Element
279 * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
280 * - `whitespace`: If the given value is a DOM text node and contains only whitespace
282 * @param {Mixed} value
286 typeOf: function(value) {
287 if (value === null) {
291 var type = typeof value;
293 if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
297 var typeToString = toString.call(value);
299 switch(typeToString) {
300 case '[object Array]':
302 case '[object Date]':
304 case '[object Boolean]':
306 case '[object Number]':
308 case '[object RegExp]':
312 if (type === 'function') {
316 if (type === 'object') {
317 if (value.nodeType !== undefined) {
318 if (value.nodeType === 3) {
319 return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
332 sourceMethod: 'typeOf',
333 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
339 * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
343 * - a zero-length array
344 * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
346 * @param {Mixed} value The value to test
347 * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
351 isEmpty: function(value, allowEmptyString) {
352 return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
356 * Returns true if the passed value is a JavaScript Array, false otherwise.
358 * @param {Mixed} target The target to test
362 isArray: ('isArray' in Array) ? Array.isArray : function(value) {
363 return toString.call(value) === '[object Array]';
367 * Returns true if the passed value is a JavaScript Date object, false otherwise.
368 * @param {Object} object The object to test
371 isDate: function(value) {
372 return toString.call(value) === '[object Date]';
376 * Returns true if the passed value is a JavaScript Object, false otherwise.
377 * @param {Mixed} value The value to test
381 isObject: (toString.call(null) === '[object Object]') ?
383 return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.nodeType === undefined;
386 return toString.call(value) === '[object Object]';
390 * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
391 * @param {Mixed} value The value to test
394 isPrimitive: function(value) {
395 var type = typeof value;
397 return type === 'string' || type === 'number' || type === 'boolean';
401 * Returns true if the passed value is a JavaScript Function, false otherwise.
402 * @param {Mixed} value The value to test
407 // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
408 // Object.prorotype.toString (slower)
409 (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
410 return toString.call(value) === '[object Function]';
411 } : function(value) {
412 return typeof value === 'function';
416 * Returns true if the passed value is a number. Returns false for non-finite numbers.
417 * @param {Mixed} value The value to test
420 isNumber: function(value) {
421 return typeof value === 'number' && isFinite(value);
425 * Validates that a value is numeric.
426 * @param {Mixed} value Examples: 1, '1', '2.34'
427 * @return {Boolean} True if numeric, false otherwise
429 isNumeric: function(value) {
430 return !isNaN(parseFloat(value)) && isFinite(value);
434 * Returns true if the passed value is a string.
435 * @param {Mixed} value The value to test
438 isString: function(value) {
439 return typeof value === 'string';
443 * Returns true if the passed value is a boolean.
445 * @param {Mixed} value The value to test
448 isBoolean: function(value) {
449 return typeof value === 'boolean';
453 * Returns true if the passed value is an HTMLElement
454 * @param {Mixed} value The value to test
457 isElement: function(value) {
458 return value ? value.nodeType === 1 : false;
462 * Returns true if the passed value is a TextNode
463 * @param {Mixed} value The value to test
466 isTextNode: function(value) {
467 return value ? value.nodeName === "#text" : false;
471 * Returns true if the passed value is defined.
472 * @param {Mixed} value The value to test
475 isDefined: function(value) {
476 return typeof value !== 'undefined';
480 * Returns true if the passed value is iterable, false otherwise
481 * @param {Mixed} value The value to test
484 isIterable: function(value) {
485 return (value && typeof value !== 'string') ? value.length !== undefined : false;
492 * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
493 * @param {Mixed} item The variable to clone
494 * @return {Mixed} clone
496 clone: function(item) {
497 if (item === null || item === undefined) {
502 // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
504 if (item.nodeType && item.cloneNode) {
505 return item.cloneNode(true);
508 var type = toString.call(item);
511 if (type === '[object Date]') {
512 return new Date(item.getTime());
515 var i, j, k, clone, key;
518 if (type === '[object Array]') {
524 clone[i] = Ext.clone(item[i]);
528 else if (type === '[object Object]' && item.constructor === Object) {
532 clone[key] = Ext.clone(item[key]);
536 for (j = enumerables.length; j--;) {
543 return clone || item;
548 * Generate a unique reference of Ext in the global scope, useful for sandboxing
550 getUniqueGlobalNamespace: function() {
551 var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
553 if (uniqueGlobalNamespace === undefined) {
557 uniqueGlobalNamespace = 'ExtSandbox' + (++i);
558 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
560 Ext.global[uniqueGlobalNamespace] = Ext;
561 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
564 return uniqueGlobalNamespace;
570 functionFactory: function() {
571 var args = Array.prototype.slice.call(arguments);
573 if (args.length > 0) {
574 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
575 args[args.length - 1];
578 return Function.prototype.constructor.apply(Function.prototype, args);
583 * Old alias to {@link Ext#typeOf}
584 * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
587 Ext.type = Ext.typeOf;
592 * @author Jacky Nguyen <jacky@sencha.com>
593 * @docauthor Jacky Nguyen <jacky@sencha.com>
596 * A utility class that wrap around a string version number and provide convenient
597 * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
599 var version = new Ext.Version('1.0.2beta');
600 console.log("Version is " + version); // Version is 1.0.2beta
602 console.log(version.getMajor()); // 1
603 console.log(version.getMinor()); // 0
604 console.log(version.getPatch()); // 2
605 console.log(version.getBuild()); // 0
606 console.log(version.getRelease()); // beta
608 console.log(version.isGreaterThan('1.0.1')); // True
609 console.log(version.isGreaterThan('1.0.2alpha')); // True
610 console.log(version.isGreaterThan('1.0.2RC')); // False
611 console.log(version.isGreaterThan('1.0.2')); // False
612 console.log(version.isLessThan('1.0.2')); // True
614 console.log(version.match(1.0)); // True
615 console.log(version.match('1.0.2')); // True
621 // Current core version
622 var version = '4.0.1', Version;
623 Ext.Version = Version = Ext.extend(Object, {
627 * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
628 * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
629 * @return {Ext.Version} this
632 constructor: function(version) {
633 var parts, releaseStartIndex;
635 if (version instanceof Version) {
639 this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
641 releaseStartIndex = this.version.search(/([^\d\.])/);
643 if (releaseStartIndex !== -1) {
644 this.release = this.version.substr(releaseStartIndex, version.length);
645 this.shortVersion = this.version.substr(0, releaseStartIndex);
648 this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
650 parts = this.version.split('.');
652 this.major = parseInt(parts.shift() || 0, 10);
653 this.minor = parseInt(parts.shift() || 0, 10);
654 this.patch = parseInt(parts.shift() || 0, 10);
655 this.build = parseInt(parts.shift() || 0, 10);
661 * Override the native toString method
663 * @return {String} version
665 toString: function() {
670 * Override the native valueOf method
672 * @return {String} version
674 valueOf: function() {
679 * Returns the major component value
680 * @return {Number} major
682 getMajor: function() {
683 return this.major || 0;
687 * Returns the minor component value
688 * @return {Number} minor
690 getMinor: function() {
691 return this.minor || 0;
695 * Returns the patch component value
696 * @return {Number} patch
698 getPatch: function() {
699 return this.patch || 0;
703 * Returns the build component value
704 * @return {Number} build
706 getBuild: function() {
707 return this.build || 0;
711 * Returns the release component value
712 * @return {Number} release
714 getRelease: function() {
715 return this.release || '';
719 * Returns whether this version if greater than the supplied argument
720 * @param {String/Number} target The version to compare with
721 * @return {Boolean} True if this version if greater than the target, false otherwise
723 isGreaterThan: function(target) {
724 return Version.compare(this.version, target) === 1;
728 * Returns whether this version if smaller than the supplied argument
729 * @param {String/Number} target The version to compare with
730 * @return {Boolean} True if this version if smaller than the target, false otherwise
732 isLessThan: function(target) {
733 return Version.compare(this.version, target) === -1;
737 * Returns whether this version equals to the supplied argument
738 * @param {String/Number} target The version to compare with
739 * @return {Boolean} True if this version equals to the target, false otherwise
741 equals: function(target) {
742 return Version.compare(this.version, target) === 0;
746 * Returns whether this version matches the supplied argument. Example:
748 * var version = new Ext.Version('1.0.2beta');
749 * console.log(version.match(1)); // True
750 * console.log(version.match(1.0)); // True
751 * console.log(version.match('1.0.2')); // True
752 * console.log(version.match('1.0.2RC')); // False
754 * @param {String/Number} target The version to compare with
755 * @return {Boolean} True if this version matches the target, false otherwise
757 match: function(target) {
758 target = String(target);
759 return this.version.substr(0, target.length) === target;
763 * Returns this format: [major, minor, patch, build, release]. Useful for comparison
766 toArray: function() {
767 return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
771 * Returns shortVersion version without dots and release
774 getShortVersion: function() {
775 return this.shortVersion;
794 * Converts a version component to a comparable value
797 * @param {Mixed} value The value to convert
800 getComponentValue: function(value) {
801 return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
805 * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
806 * they are handled in the following order:
807 * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
810 * @param {String} current The current version to compare to
811 * @param {String} target The target version to compare to
812 * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
814 compare: function(current, target) {
815 var currentValue, targetValue, i;
817 current = new Version(current).toArray();
818 target = new Version(target).toArray();
820 for (i = 0; i < Math.max(current.length, target.length); i++) {
821 currentValue = this.getComponentValue(current[i]);
822 targetValue = this.getComponentValue(target[i]);
824 if (currentValue < targetValue) {
826 } else if (currentValue > targetValue) {
844 lastRegisteredVersion: null,
847 * Set version number for the given package name.
849 * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
850 * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
853 setVersion: function(packageName, version) {
854 Ext.versions[packageName] = new Version(version);
855 Ext.lastRegisteredVersion = Ext.versions[packageName];
861 * Get the version number of the supplied package name; will return the last registered version
862 * (last Ext.setVersion call) if there's no package name given.
864 * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
865 * @return {Ext.Version} The version
867 getVersion: function(packageName) {
868 if (packageName === undefined) {
869 return Ext.lastRegisteredVersion;
872 return Ext.versions[packageName];
876 * Create a closure for deprecated code.
878 // This means Ext.oldMethod is only supported in 4.0.0beta and older.
879 // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
880 // the closure will not be invoked
881 Ext.deprecate('extjs', '4.0.0beta', function() {
882 Ext.oldMethod = Ext.newMethod;
887 * @param {String} packageName The package name
888 * @param {String} since The last version before it's deprecated
889 * @param {Function} closure The callback function to be executed with the specified version is less than the current version
890 * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
893 deprecate: function(packageName, since, closure, scope) {
894 if (Version.compare(Ext.getVersion(packageName), since) < 1) {
898 }); // End Versioning
900 Ext.setVersion('core', version);
907 * A collection of useful static methods to deal with strings
912 trimRegex: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
914 formatRe: /\{(\d+)\}/g,
915 escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
918 * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
919 * @param {String} value The string to encode
920 * @return {String} The encoded text
923 htmlEncode: (function() {
929 }, keys = [], p, regex;
931 for (p in entities) {
935 regex = new RegExp('(' + keys.join('|') + ')', 'g');
937 return function(value) {
938 return (!value) ? value : String(value).replace(regex, function(match, capture) {
939 return entities[capture];
945 * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
946 * @param {String} value The string to decode
947 * @return {String} The decoded text
950 htmlDecode: (function() {
956 }, keys = [], p, regex;
958 for (p in entities) {
962 regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
964 return function(value) {
965 return (!value) ? value : String(value).replace(regex, function(match, capture) {
966 if (capture in entities) {
967 return entities[capture];
969 return String.fromCharCode(parseInt(capture.substr(2), 10));
976 * Appends content to the query string of a URL, handling logic for whether to place
977 * a question mark or ampersand.
978 * @param {String} url The URL to append to.
979 * @param {String} string The content to append to the URL.
980 * @return (String) The resulting URL
982 urlAppend : function(url, string) {
983 if (!Ext.isEmpty(string)) {
984 return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
991 * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
994 alert('-' + s + '-'); //alerts "- foo bar -"
995 alert('-' + Ext.String.trim(s) + '-'); //alerts "-foo bar-"
997 * @param {String} string The string to escape
998 * @return {String} The trimmed string
1000 trim: function(string) {
1001 return string.replace(Ext.String.trimRegex, "");
1005 * Capitalize the given string
1006 * @param {String} string
1009 capitalize: function(string) {
1010 return string.charAt(0).toUpperCase() + string.substr(1);
1014 * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1015 * @param {String} value The string to truncate
1016 * @param {Number} length The maximum length to allow before truncating
1017 * @param {Boolean} word True to try to find a common word break
1018 * @return {String} The converted text
1020 ellipsis: function(value, len, word) {
1021 if (value && value.length > len) {
1023 var vs = value.substr(0, len - 2),
1024 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1025 if (index !== -1 && index >= (len - 15)) {
1026 return vs.substr(0, index) + "...";
1029 return value.substr(0, len - 3) + "...";
1035 * Escapes the passed string for use in a regular expression
1036 * @param {String} string
1039 escapeRegex: function(string) {
1040 return string.replace(Ext.String.escapeRegexRe, "\\$1");
1044 * Escapes the passed string for ' and \
1045 * @param {String} string The string to escape
1046 * @return {String} The escaped string
1048 escape: function(string) {
1049 return string.replace(Ext.String.escapeRe, "\\$1");
1053 * Utility function that allows you to easily switch a string between two alternating values. The passed value
1054 * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
1055 * they are already different, the first value passed in is returned. Note that this method returns the new value
1056 * but does not change the current string.
1058 // alternate sort directions
1059 sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1061 // instead of conditional logic:
1062 sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1064 * @param {String} string The current string
1065 * @param {String} value The value to compare to the current string
1066 * @param {String} other The new value to use if the string already equals the first value passed in
1067 * @return {String} The new value
1069 toggle: function(string, value, other) {
1070 return string === value ? other : value;
1074 * Pads the left side of a string with a specified character. This is especially useful
1075 * for normalizing number and date strings. Example usage:
1078 var s = Ext.String.leftPad('123', 5, '0');
1079 // s now contains the string: '00123'
1081 * @param {String} string The original string
1082 * @param {Number} size The total length of the output string
1083 * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1084 * @return {String} The padded string
1086 leftPad: function(string, size, character) {
1087 var result = String(string);
1088 character = character || " ";
1089 while (result.length < size) {
1090 result = character + result;
1096 * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
1097 * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
1099 var cls = 'my-class', text = 'Some text';
1100 var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
1101 // s now contains the string: '<div class="my-class">Some text</div>'
1103 * @param {String} string The tokenized string to be formatted
1104 * @param {String} value1 The value to replace token {0}
1105 * @param {String} value2 Etc...
1106 * @return {String} The formatted string
1108 format: function(format) {
1109 var args = Ext.Array.toArray(arguments, 1);
1110 return format.replace(Ext.String.formatRe, function(m, i) {
1119 * A collection of useful static methods to deal with numbers
1125 var isToFixedBroken = (0.9).toFixed() !== '1';
1129 * Checks whether or not the current number is within a desired range. If the number is already within the
1130 * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1131 * exceeded. Note that this method returns the constrained value but does not change the current number.
1132 * @param {Number} number The number to check
1133 * @param {Number} min The minimum number in the range
1134 * @param {Number} max The maximum number in the range
1135 * @return {Number} The constrained value if outside the range, otherwise the current value
1137 constrain: function(number, min, max) {
1138 number = parseFloat(number);
1141 number = Math.max(number, min);
1144 number = Math.min(number, max);
1150 * Formats a number using fixed-point notation
1151 * @param {Number} value The number to format
1152 * @param {Number} precision The number of digits to show after the decimal point
1154 toFixed: function(value, precision) {
1155 if (isToFixedBroken) {
1156 precision = precision || 0;
1157 var pow = Math.pow(10, precision);
1158 return (Math.round(value * pow) / pow).toFixed(precision);
1161 return value.toFixed(precision);
1165 * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1168 Ext.Number.from('1.23', 1); // returns 1.23
1169 Ext.Number.from('abc', 1); // returns 1
1171 * @param {Mixed} value
1172 * @param {Number} defaultValue The value to return if the original value is non-numeric
1173 * @return {Number} value, if numeric, defaultValue otherwise
1175 from: function(value, defaultValue) {
1176 if (isFinite(value)) {
1177 value = parseFloat(value);
1180 return !isNaN(value) ? value : defaultValue;
1187 * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
1189 * @deprecated 4.0.0 Replaced by Ext.Number.from
1193 Ext.num = function() {
1194 return Ext.Number.from.apply(this, arguments);
1197 * @author Jacky Nguyen <jacky@sencha.com>
1198 * @docauthor Jacky Nguyen <jacky@sencha.com>
1201 * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1208 var arrayPrototype = Array.prototype,
1209 slice = arrayPrototype.slice,
1210 supportsForEach = 'forEach' in arrayPrototype,
1211 supportsMap = 'map' in arrayPrototype,
1212 supportsIndexOf = 'indexOf' in arrayPrototype,
1213 supportsEvery = 'every' in arrayPrototype,
1214 supportsSome = 'some' in arrayPrototype,
1215 supportsFilter = 'filter' in arrayPrototype,
1216 supportsSort = function() {
1217 var a = [1,2,3,4,5].sort(function(){ return 0; });
1218 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1220 supportsSliceOnNodeList = true,
1223 // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1224 if (typeof document !== 'undefined') {
1225 slice.call(document.getElementsByTagName('body'));
1228 supportsSliceOnNodeList = false;
1231 ExtArray = Ext.Array = {
1233 * Iterates an array or an iterable value and invoke the given callback function for each item.
1235 var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1237 Ext.Array.each(countries, function(name, index, countriesItSelf) {
1241 var sum = function() {
1244 Ext.Array.each(arguments, function(value) {
1251 sum(1, 2, 3); // returns 6
1253 * The iteration can be stopped by returning false in the function callback.
1255 Ext.Array.each(countries, function(name, index, countriesItSelf) {
1256 if (name === 'Singapore') {
1257 return false; // break here
1261 * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
1262 * argument is not iterable, the callback function is called once.
1263 * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1264 * the current `index`. Arguments passed to this callback function are:
1266 - `item`: {Mixed} The item at the current `index` in the passed `array`
1267 - `index`: {Number} The current `index` within the `array`
1268 - `allItems`: {Array/NodeList/Mixed} The `array` passed as the first argument to `Ext.Array.each`
1270 * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1271 * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1273 * @return {Boolean} See description for the `fn` parameter.
1276 each: function(array, fn, scope, reverse) {
1277 array = ExtArray.from(array);
1282 if (reverse !== true) {
1283 for (i = 0; i < ln; i++) {
1284 if (fn.call(scope || array[i], array[i], i, array) === false) {
1290 for (i = ln - 1; i > -1; i--) {
1291 if (fn.call(scope || array[i], array[i], i, array) === false) {
1301 * Iterates an array and invoke the given callback function for each item. Note that this will simply
1302 * delegate to the native Array.prototype.forEach method if supported.
1303 * It doesn't support stopping the iteration by returning false in the callback function like
1304 * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
1305 * {@link Ext.Array#each}
1307 * @param {Array} array The array to iterate
1308 * @param {Function} fn The function callback, to be invoked these arguments:
1310 - `item`: {Mixed} The item at the current `index` in the passed `array`
1311 - `index`: {Number} The current `index` within the `array`
1312 - `allItems`: {Array} The `array` itself which was passed as the first argument
1314 * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1317 forEach: function(array, fn, scope) {
1318 if (supportsForEach) {
1319 return array.forEach(fn, scope);
1325 for (; i < ln; i++) {
1326 fn.call(scope, array[i], i, array);
1331 * Get the index of the provided `item` in the given `array`, a supplement for the
1332 * missing arrayPrototype.indexOf in Internet Explorer.
1334 * @param {Array} array The array to check
1335 * @param {Mixed} item The item to look for
1336 * @param {Number} from (Optional) The index at which to begin the search
1337 * @return {Number} The index of item in the array (or -1 if it is not found)
1340 indexOf: function(array, item, from) {
1341 if (supportsIndexOf) {
1342 return array.indexOf(item, from);
1345 var i, length = array.length;
1347 for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1348 if (array[i] === item) {
1357 * Checks whether or not the given `array` contains the specified `item`
1359 * @param {Array} array The array to check
1360 * @param {Mixed} item The item to look for
1361 * @return {Boolean} True if the array contains the item, false otherwise
1364 contains: function(array, item) {
1365 if (supportsIndexOf) {
1366 return array.indexOf(item) !== -1;
1371 for (i = 0, ln = array.length; i < ln; i++) {
1372 if (array[i] === item) {
1381 * Converts any iterable (numeric indices and a length property) into a true array.
1384 var args = Ext.Array.toArray(arguments),
1385 fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1387 alert(args.join(' '));
1388 alert(fromSecondToLastArgs.join(' '));
1391 test('just', 'testing', 'here'); // alerts 'just testing here';
1392 // alerts 'testing here';
1394 Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1395 Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1396 Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1398 * @param {Mixed} iterable the iterable object to be turned into a true Array.
1399 * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1400 * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1401 * index of the iterable value
1402 * @return {Array} array
1405 toArray: function(iterable, start, end){
1406 if (!iterable || !iterable.length) {
1410 if (typeof iterable === 'string') {
1411 iterable = iterable.split('');
1414 if (supportsSliceOnNodeList) {
1415 return slice.call(iterable, start || 0, end || iterable.length);
1422 end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1424 for (i = start; i < end; i++) {
1425 array.push(iterable[i]);
1432 * Plucks the value of a property from each item in the Array. Example:
1434 Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1436 * @param {Array|NodeList} array The Array of items to pluck the value from.
1437 * @param {String} propertyName The property name to pluck from each element.
1438 * @return {Array} The value from each item in the Array.
1440 pluck: function(array, propertyName) {
1444 for (i = 0, ln = array.length; i < ln; i++) {
1447 ret.push(item[propertyName]);
1454 * Creates a new array with the results of calling a provided function on every element in this array.
1455 * @param {Array} array
1456 * @param {Function} fn Callback function for each item
1457 * @param {Object} scope Callback function scope
1458 * @return {Array} results
1460 map: function(array, fn, scope) {
1462 return array.map(fn, scope);
1469 for (; i < len; i++) {
1470 results[i] = fn.call(scope, array[i], i, array);
1477 * Executes the specified function for each array element until the function returns a falsy value.
1478 * If such an item is found, the function will return false immediately.
1479 * Otherwise, it will return true.
1481 * @param {Array} array
1482 * @param {Function} fn Callback function for each item
1483 * @param {Object} scope Callback function scope
1484 * @return {Boolean} True if no false value is returned by the callback function.
1486 every: function(array, fn, scope) {
1489 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
1492 if (supportsEvery) {
1493 return array.every(fn, scope);
1499 for (; i < ln; ++i) {
1500 if (!fn.call(scope, array[i], i, array)) {
1509 * Executes the specified function for each array element until the function returns a truthy value.
1510 * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1512 * @param {Array} array
1513 * @param {Function} fn Callback function for each item
1514 * @param {Object} scope Callback function scope
1515 * @return {Boolean} True if the callback function returns a truthy value.
1517 some: function(array, fn, scope) {
1520 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
1524 return array.some(fn, scope);
1530 for (; i < ln; ++i) {
1531 if (fn.call(scope, array[i], i, array)) {
1540 * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1542 * @see Ext.Array.filter
1543 * @param {Array} array
1544 * @return {Array} results
1546 clean: function(array) {
1552 for (; i < ln; i++) {
1555 if (!Ext.isEmpty(item)) {
1564 * Returns a new array with unique items
1566 * @param {Array} array
1567 * @return {Array} results
1569 unique: function(array) {
1575 for (; i < ln; i++) {
1578 if (ExtArray.indexOf(clone, item) === -1) {
1587 * Creates a new array with all of the elements of this array for which
1588 * the provided filtering function returns true.
1589 * @param {Array} array
1590 * @param {Function} fn Callback function for each item
1591 * @param {Object} scope Callback function scope
1592 * @return {Array} results
1594 filter: function(array, fn, scope) {
1595 if (supportsFilter) {
1596 return array.filter(fn, scope);
1603 for (; i < ln; i++) {
1604 if (fn.call(scope, array[i], i, array)) {
1605 results.push(array[i]);
1613 * Converts a value to an array if it's not already an array; returns:
1615 * - An empty array if given value is `undefined` or `null`
1616 * - Itself if given value is already an array
1617 * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1618 * - An array with one item which is the given value, otherwise
1620 * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
1621 * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
1623 * @return {Array} array
1626 from: function(value, newReference) {
1627 if (value === undefined || value === null) {
1631 if (Ext.isArray(value)) {
1632 return (newReference) ? slice.call(value) : value;
1635 if (value && value.length !== undefined && typeof value !== 'string') {
1636 return Ext.toArray(value);
1643 * Removes the specified item from the array if it exists
1645 * @param {Array} array The array
1646 * @param {Mixed} item The item to remove
1647 * @return {Array} The passed array itself
1649 remove: function(array, item) {
1650 var index = ExtArray.indexOf(array, item);
1653 array.splice(index, 1);
1660 * Push an item into the array only if the array doesn't contain it yet
1662 * @param {Array} array The array
1663 * @param {Mixed} item The item to include
1664 * @return {Array} The passed array itself
1666 include: function(array, item) {
1667 if (!ExtArray.contains(array, item)) {
1673 * Clone a flat array without referencing the previous one. Note that this is different
1674 * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1675 * for Array.prototype.slice.call(array)
1677 * @param {Array} array The array
1678 * @return {Array} The clone array
1680 clone: function(array) {
1681 return slice.call(array);
1685 * Merge multiple arrays into one with unique items. Alias to {@link Ext.Array#union}.
1687 * @param {Array} array,...
1688 * @return {Array} merged
1691 var args = slice.call(arguments),
1695 for (i = 0, ln = args.length; i < ln; i++) {
1696 array = array.concat(args[i]);
1699 return ExtArray.unique(array);
1703 * Merge multiple arrays into one with unique items that exist in all of the arrays.
1705 * @param {Array} array,...
1706 * @return {Array} intersect
1708 intersect: function() {
1710 arrays = slice.call(arguments),
1711 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1713 if (!arrays.length) {
1717 // Find the smallest array
1718 for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1719 if (!minArray || array.length < minArray.length) {
1725 minArray = Ext.Array.unique(minArray);
1726 arrays.splice(x, 1);
1728 // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1729 // an item in the small array, we're likely to find it before reaching the end
1730 // of the inner loop and can terminate the search early.
1731 for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1734 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1735 for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1743 if (count === arraysLn) {
1752 * Perform a set difference A-B by subtracting all items in array B from array A.
1754 * @param {Array} array A
1755 * @param {Array} array B
1756 * @return {Array} difference
1758 difference: function(arrayA, arrayB) {
1759 var clone = slice.call(arrayA),
1763 for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1764 for (j = 0; j < ln; j++) {
1765 if (clone[j] === arrayB[i]) {
1777 * Sorts the elements of an Array.
1778 * By default, this method sorts the elements alphabetically and ascending.
1780 * @param {Array} array The array to sort.
1781 * @param {Function} sortFn (optional) The comparison function.
1782 * @return {Array} The sorted array.
1784 sort: function(array, sortFn) {
1787 return array.sort(sortFn);
1789 return array.sort();
1793 var length = array.length,
1798 for (; i < length; i++) {
1800 for (j = i + 1; j < length; j++) {
1802 comparison = sortFn(array[j], array[min]);
1803 if (comparison < 0) {
1806 } else if (array[j] < array[min]) {
1812 array[i] = array[min];
1821 * Recursively flattens into 1-d Array. Injects Arrays inline.
1822 * @param {Array} array The array to flatten
1823 * @return {Array} The new, flattened array.
1825 flatten: function(array) {
1828 function rFlatten(a) {
1831 for (i = 0, ln = a.length; i < ln; i++) {
1834 if (Ext.isArray(v)) {
1844 return rFlatten(array);
1848 * Returns the minimum value in the Array.
1849 * @param {Array|NodeList} array The Array from which to select the minimum value.
1850 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
1851 * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
1852 * @return {Mixed} minValue The minimum value
1854 min: function(array, comparisonFn) {
1858 for (i = 0, ln = array.length; i < ln; i++) {
1862 if (comparisonFn(min, item) === 1) {
1877 * Returns the maximum value in the Array
1878 * @param {Array|NodeList} array The Array from which to select the maximum value.
1879 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
1880 * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
1881 * @return {Mixed} maxValue The maximum value
1883 max: function(array, comparisonFn) {
1887 for (i = 0, ln = array.length; i < ln; i++) {
1891 if (comparisonFn(max, item) === -1) {
1906 * Calculates the mean of all items in the array
1907 * @param {Array} array The Array to calculate the mean value of.
1908 * @return {Number} The mean.
1910 mean: function(array) {
1911 return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
1915 * Calculates the sum of all items in the given array
1916 * @param {Array} array The Array to calculate the sum value of.
1917 * @return {Number} The sum.
1919 sum: function(array) {
1923 for (i = 0,ln = array.length; i < ln; i++) {
1935 * Convenient alias to {@link Ext.Array#each}
1939 Ext.each = Ext.Array.each;
1942 * Alias to {@link Ext.Array#merge}.
1946 Ext.Array.union = Ext.Array.merge;
1949 * Old alias to {@link Ext.Array#min}
1950 * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
1954 Ext.min = Ext.Array.min;
1957 * Old alias to {@link Ext.Array#max}
1958 * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
1962 Ext.max = Ext.Array.max;
1965 * Old alias to {@link Ext.Array#sum}
1966 * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
1970 Ext.sum = Ext.Array.sum;
1973 * Old alias to {@link Ext.Array#mean}
1974 * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
1978 Ext.mean = Ext.Array.mean;
1981 * Old alias to {@link Ext.Array#flatten}
1982 * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
1986 Ext.flatten = Ext.Array.flatten;
1989 * Old alias to {@link Ext.Array#clean Ext.Array.clean}
1990 * @deprecated 4.0.0 Use {@link Ext.Array.clean} instead
1994 Ext.clean = Ext.Array.clean;
1997 * Old alias to {@link Ext.Array#unique Ext.Array.unique}
1998 * @deprecated 4.0.0 Use {@link Ext.Array.unique} instead
2002 Ext.unique = Ext.Array.unique;
2005 * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
2006 * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
2010 Ext.pluck = Ext.Array.pluck;
2013 * Convenient alias to {@link Ext.Array#toArray Ext.Array.toArray}
2014 * @param {Iterable} the iterable object to be turned into a true Array.
2017 * @return {Array} array
2019 Ext.toArray = function() {
2020 return ExtArray.toArray.apply(ExtArray, arguments);
2025 * @class Ext.Function
2027 * A collection of useful static methods to deal with function callbacks
2034 * A very commonly used method throughout the framework. It acts as a wrapper around another method
2035 * which originally accepts 2 arguments for <code>name</code> and <code>value</code>.
2036 * The wrapped function then allows "flexible" value setting of either:
2039 * <li><code>name</code> and <code>value</code> as 2 arguments</li>
2040 * <li>one single object argument with multiple key - value pairs</li>
2045 var setValue = Ext.Function.flexSetter(function(name, value) {
2050 // Setting a single name - value
2051 setValue('name1', 'value1');
2053 // Settings multiple name - value pairs
2060 * @param {Function} setter
2061 * @returns {Function} flexSetter
2063 flexSetter: function(fn) {
2064 return function(a, b) {
2071 if (typeof a !== 'string') {
2073 if (a.hasOwnProperty(k)) {
2074 fn.call(this, k, a[k]);
2078 if (Ext.enumerables) {
2079 for (i = Ext.enumerables.length; i--;) {
2080 k = Ext.enumerables[i];
2081 if (a.hasOwnProperty(k)) {
2082 fn.call(this, k, a[k]);
2087 fn.call(this, a, b);
2095 * Create a new function from the provided <code>fn</code>, change <code>this</code> to the provided scope, optionally
2096 * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2098 * @param {Function} fn The function to delegate.
2099 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2100 * <b>If omitted, defaults to the browser window.</b>
2101 * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2102 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2103 * if a number the args are inserted at the specified position
2104 * @return {Function} The new function
2106 bind: function(fn, scope, args, appendArgs) {
2111 var callArgs = args || arguments;
2113 if (appendArgs === true) {
2114 callArgs = Array.prototype.slice.call(arguments, 0);
2115 callArgs = callArgs.concat(args);
2117 else if (Ext.isNumber(appendArgs)) {
2118 callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
2119 applyArgs = [appendArgs, 0].concat(args); // create method call params
2120 Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
2123 return method.apply(scope || window, callArgs);
2128 * Create a new function from the provided <code>fn</code>, the arguments of which are pre-set to `args`.
2129 * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2130 * This is especially useful when creating callbacks.
2133 var originalFunction = function(){
2134 alert(Ext.Array.from(arguments).join(' '));
2137 var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2139 callback(); // alerts 'Hello World'
2140 callback('by Me'); // alerts 'Hello World by Me'
2142 * @param {Function} fn The original function
2143 * @param {Array} args The arguments to pass to new callback
2144 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2145 * @return {Function} The new callback function
2147 pass: function(fn, args, scope) {
2149 args = Ext.Array.from(args);
2153 return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2158 * Create an alias to the provided method property with name <code>methodName</code> of <code>object</code>.
2159 * Note that the execution scope will still be bound to the provided <code>object</code> itself.
2161 * @param {Object/Function} object
2162 * @param {String} methodName
2163 * @return {Function} aliasFn
2165 alias: function(object, methodName) {
2167 return object[methodName].apply(object, arguments);
2172 * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2173 * the original one is not called. The resulting function returns the results of the original function.
2174 * The passed function is called with the parameters of the original function. Example usage:
2176 var sayHi = function(name){
2177 alert('Hi, ' + name);
2180 sayHi('Fred'); // alerts "Hi, Fred"
2182 // create a new function that validates input without
2183 // directly modifying the original function:
2184 var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2185 return name == 'Brian';
2188 sayHiToFriend('Fred'); // no alert
2189 sayHiToFriend('Brian'); // alerts "Hi, Brian"
2191 * @param {Function} origFn The original function.
2192 * @param {Function} newFn The function to call before the original
2193 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
2194 * <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
2195 * @param {Mixed} returnValue (optional) The value to return if the passed function return false (defaults to null).
2196 * @return {Function} The new function
2198 createInterceptor: function(origFn, newFn, scope, returnValue) {
2199 var method = origFn;
2200 if (!Ext.isFunction(newFn)) {
2208 newFn.method = origFn;
2209 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2215 * Creates a delegate (callback) which, when called, executes after a specific delay.
2216 * @param {Function} fn The function which will be called on a delay when the returned function is called.
2217 * Optionally, a replacement (or additional) argument list may be specified.
2218 * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2219 * @param {Object} scope (optional) The scope (<code>this</code> reference) used by the function at execution time.
2220 * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2221 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2222 * if a number the args are inserted at the specified position.
2223 * @return {Function} A function which, when called, executes the original function after the specified delay.
2225 createDelayed: function(fn, delay, scope, args, appendArgs) {
2226 if (scope || args) {
2227 fn = Ext.Function.bind(fn, scope, args, appendArgs);
2231 setTimeout(function() {
2232 fn.apply(me, arguments);
2238 * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2240 var sayHi = function(name){
2241 alert('Hi, ' + name);
2244 // executes immediately:
2247 // executes after 2 seconds:
2248 Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2250 // this syntax is sometimes useful for deferring
2251 // execution of an anonymous function:
2252 Ext.Function.defer(function(){
2256 * @param {Function} fn The function to defer.
2257 * @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
2258 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2259 * <b>If omitted, defaults to the browser window.</b>
2260 * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2261 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2262 * if a number the args are inserted at the specified position
2263 * @return {Number} The timeout id that can be used with clearTimeout
2265 defer: function(fn, millis, obj, args, appendArgs) {
2266 fn = Ext.Function.bind(fn, obj, args, appendArgs);
2268 return setTimeout(fn, millis);
2275 * Create a combined function call sequence of the original function + the passed function.
2276 * The resulting function returns the results of the original function.
2277 * The passed function is called with the parameters of the original function. Example usage:
2280 var sayHi = function(name){
2281 alert('Hi, ' + name);
2284 sayHi('Fred'); // alerts "Hi, Fred"
2286 var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2287 alert('Bye, ' + name);
2290 sayGoodbye('Fred'); // both alerts show
2293 * @param {Function} origFn The original function.
2294 * @param {Function} newFn The function to sequence
2295 * @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
2296 * If omitted, defaults to the scope in which the original function is called or the browser window.
2297 * @return {Function} The new function
2299 createSequence: function(origFn, newFn, scope) {
2300 if (!Ext.isFunction(newFn)) {
2305 var retval = origFn.apply(this || window, arguments);
2306 newFn.apply(scope || this || window, arguments);
2313 * <p>Creates a delegate function, optionally with a bound scope which, when called, buffers
2314 * the execution of the passed function for the configured number of milliseconds.
2315 * If called again within that period, the impending invocation will be canceled, and the
2316 * timeout period will begin again.</p>
2318 * @param {Function} fn The function to invoke on a buffered timer.
2319 * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2321 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which
2322 * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2323 * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2324 * passed by the caller.
2325 * @return {Function} A function which invokes the passed function after buffering for the specified time.
2327 createBuffered: function(fn, buffer, scope, args) {
2333 clearInterval(timerId);
2336 timerId = setTimeout(function(){
2337 fn.apply(scope || me, args || arguments);
2344 * <p>Creates a throttled version of the passed function which, when called repeatedly and
2345 * rapidly, invokes the passed function only after a certain interval has elapsed since the
2346 * previous invocation.</p>
2348 * <p>This is useful for wrapping functions which may be called repeatedly, such as
2349 * a handler of a mouse move event when the processing is expensive.</p>
2351 * @param fn {Function} The function to execute at a regular time interval.
2352 * @param interval {Number} The interval <b>in milliseconds</b> on which the passed function is executed.
2353 * @param scope (optional) The scope (<code><b>this</b></code> reference) in which
2354 * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2355 * @returns {Function} A function which invokes the passed function at the specified interval.
2357 createThrottled: function(fn, interval, scope) {
2358 var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2359 fn.apply(scope || this, lastArgs);
2360 lastCallTime = new Date().getTime();
2364 elapsed = new Date().getTime() - lastCallTime;
2365 lastArgs = arguments;
2367 clearTimeout(timer);
2368 if (!lastCallTime || (elapsed >= interval)) {
2371 timer = setTimeout(execute, interval - elapsed);
2378 * Shorthand for {@link Ext.Function#defer}
2382 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2385 * Shorthand for {@link Ext.Function#pass}
2389 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2392 * Shorthand for {@link Ext.Function#bind}
2396 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2399 * @author Jacky Nguyen <jacky@sencha.com>
2400 * @docauthor Jacky Nguyen <jacky@sencha.com>
2403 * A collection of useful static methods to deal with objects
2410 var ExtObject = Ext.Object = {
2413 * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
2414 * query strings. For example:
2416 var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2418 // objects then equals:
2420 { name: 'hobbies', value: 'reading' },
2421 { name: 'hobbies', value: 'cooking' },
2422 { name: 'hobbies', value: 'swimming' },
2425 var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2433 }, true); // Recursive
2435 // objects then equals:
2437 { name: 'dateOfBirth[day]', value: 3 },
2438 { name: 'dateOfBirth[month]', value: 8 },
2439 { name: 'dateOfBirth[year]', value: 1987 },
2440 { name: 'dateOfBirth[extra][hour]', value: 4 },
2441 { name: 'dateOfBirth[extra][minute]', value: 30 },
2444 * @param {String} name
2445 * @param {Mixed} value
2446 * @param {Boolean} recursive
2449 toQueryObjects: function(name, value, recursive) {
2450 var self = ExtObject.toQueryObjects,
2454 if (Ext.isArray(value)) {
2455 for (i = 0, ln = value.length; i < ln; i++) {
2457 objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2467 else if (Ext.isObject(value)) {
2469 if (value.hasOwnProperty(i)) {
2471 objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2493 * Takes an object and converts it to an encoded query string
2497 Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2498 Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2499 Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2500 Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2501 Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2505 Ext.Object.toQueryString({
2512 hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2513 }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2515 // &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2516 // &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2519 * @param {Object} object The object to encode
2520 * @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
2521 * (PHP / Ruby on Rails servers and similar). Defaults to false
2522 * @return {String} queryString
2525 toQueryString: function(object, recursive) {
2526 var paramObjects = [],
2528 i, j, ln, paramObject, value;
2531 if (object.hasOwnProperty(i)) {
2532 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2536 for (j = 0, ln = paramObjects.length; j < ln; j++) {
2537 paramObject = paramObjects[j];
2538 value = paramObject.value;
2540 if (Ext.isEmpty(value)) {
2543 else if (Ext.isDate(value)) {
2544 value = Ext.Date.toString(value);
2547 params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2550 return params.join('&');
2554 * Converts a query string back into an object.
2558 Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2559 Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2560 Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2561 Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2565 Ext.Object.fromQueryString("username=Jacky&dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911&hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff", true);
2575 hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2578 * @param {String} queryString The query string to decode
2579 * @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
2580 * PHP / Ruby on Rails servers and similar. Defaults to false
2583 fromQueryString: function(queryString, recursive) {
2584 var parts = queryString.replace(/^\?/, '').split('&'),
2586 temp, components, name, value, i, ln,
2587 part, j, subLn, matchedKeys, matchedName,
2590 for (i = 0, ln = parts.length; i < ln; i++) {
2593 if (part.length > 0) {
2594 components = part.split('=');
2595 name = decodeURIComponent(components[0]);
2596 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2599 if (object.hasOwnProperty(name)) {
2600 if (!Ext.isArray(object[name])) {
2601 object[name] = [object[name]];
2604 object[name].push(value);
2607 object[name] = value;
2611 matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2612 matchedName = name.match(/^([^\[]+)/);
2617 sourceClass: "Ext.Object",
2618 sourceMethod: "fromQueryString",
2619 queryString: queryString,
2620 recursive: recursive,
2621 msg: 'Malformed query string given, failed parsing name from "' + part + '"'
2626 name = matchedName[0];
2629 if (matchedKeys === null) {
2630 object[name] = value;
2634 for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
2635 key = matchedKeys[j];
2636 key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
2644 for (j = 0, subLn = keys.length; j < subLn; j++) {
2647 if (j === subLn - 1) {
2648 if (Ext.isArray(temp) && key === '') {
2656 if (temp[key] === undefined || typeof temp[key] === 'string') {
2657 nextKey = keys[j+1];
2659 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
2673 * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
2674 * by returning `false` in the callback function. For example:
2679 loves: ['food', 'sleeping', 'wife']
2682 Ext.Object.each(person, function(key, value, myself) {
2683 console.log(key + ":" + value);
2685 if (key === 'hairColor') {
2686 return false; // stop the iteration
2690 * @param {Object} object The object to iterate
2691 * @param {Function} fn The callback function. Passed arguments for each iteration are:
2695 - {Object} `object` The object itself
2697 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
2700 each: function(object, fn, scope) {
2701 for (var property in object) {
2702 if (object.hasOwnProperty(property)) {
2703 if (fn.call(scope || object, property, object[property], object) === false) {
2711 * Merges any number of objects recursively without referencing them or their children.
2714 companyName: 'Ext JS',
2715 products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
2719 location: 'Palo Alto',
2725 companyName: 'Sencha Inc.',
2726 products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2729 location: 'Redwood City'
2733 var sencha = Ext.Object.merge(extjs, newStuff);
2735 // extjs and sencha then equals to
2737 companyName: 'Sencha Inc.',
2738 products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2742 location: 'Redwood City'
2747 * @param {Object} object,...
2748 * @return {Object} merged The object that is created as a result of merging all the objects passed in.
2751 merge: function(source, key, value) {
2752 if (typeof key === 'string') {
2753 if (value && value.constructor === Object) {
2754 if (source[key] && source[key].constructor === Object) {
2755 ExtObject.merge(source[key], value);
2758 source[key] = Ext.clone(value);
2762 source[key] = value;
2769 ln = arguments.length,
2772 for (; i < ln; i++) {
2773 object = arguments[i];
2775 for (property in object) {
2776 if (object.hasOwnProperty(property)) {
2777 ExtObject.merge(source, property, object[property]);
2786 * Returns the first matching key corresponding to the given value.
2787 * If no matching value is found, null is returned.
2794 alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'
2796 * @param {Object} object
2797 * @param {Object} value The value to find
2800 getKey: function(object, value) {
2801 for (var property in object) {
2802 if (object.hasOwnProperty(property) && object[property] === value) {
2811 * Gets all values of the given object as an array.
2813 var values = Ext.Object.getValues({
2816 }); // ['Jacky', 'food']
2818 * @param {Object} object
2819 * @return {Array} An array of values from the object
2822 getValues: function(object) {
2826 for (property in object) {
2827 if (object.hasOwnProperty(property)) {
2828 values.push(object[property]);
2836 * Gets all keys of the given object as an array.
2838 var values = Ext.Object.getKeys({
2841 }); // ['name', 'loves']
2843 * @param {Object} object
2844 * @return {Array} An array of keys from the object
2847 getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
2851 for (property in object) {
2852 if (object.hasOwnProperty(property)) {
2853 keys.push(property);
2861 * Gets the total number of this object's own properties
2863 var size = Ext.Object.getSize({
2866 }); // size equals 2
2868 * @param {Object} object
2869 * @return {Number} size
2872 getSize: function(object) {
2876 for (property in object) {
2877 if (object.hasOwnProperty(property)) {
2888 * A convenient alias method for {@link Ext.Object#merge}
2893 Ext.merge = Ext.Object.merge;
2896 * A convenient alias method for {@link Ext.Object#toQueryString}
2900 * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
2902 Ext.urlEncode = function() {
2903 var args = Ext.Array.from(arguments),
2906 // Support for the old `pre` argument
2907 if ((typeof args[1] === 'string')) {
2908 prefix = args[1] + '&';
2912 return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
2916 * A convenient alias method for {@link Ext.Object#fromQueryString}
2920 * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
2922 Ext.urlDecode = function() {
2923 return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
2930 * A set of useful static methods to deal with date
2931 * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
2932 * this object for convenience
2934 * The date parsing and formatting syntax contains a subset of
2935 * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
2936 * supported will provide results equivalent to their PHP versions.
2938 * The following is a list of all currently supported formats:
2940 Format Description Example returned values
2941 ------ ----------------------------------------------------------------------- -----------------------
2942 d Day of the month, 2 digits with leading zeros 01 to 31
2943 D A short textual representation of the day of the week Mon to Sun
2944 j Day of the month without leading zeros 1 to 31
2945 l A full textual representation of the day of the week Sunday to Saturday
2946 N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
2947 S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
2948 w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
2949 z The day of the year (starting from 0) 0 to 364 (365 in leap years)
2950 W ISO-8601 week number of year, weeks starting on Monday 01 to 53
2951 F A full textual representation of a month, such as January or March January to December
2952 m Numeric representation of a month, with leading zeros 01 to 12
2953 M A short textual representation of a month Jan to Dec
2954 n Numeric representation of a month, without leading zeros 1 to 12
2955 t Number of days in the given month 28 to 31
2956 L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
2957 o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
2958 belongs to the previous or next year, that year is used instead)
2959 Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
2960 y A two digit representation of a year Examples: 99 or 03
2961 a Lowercase Ante meridiem and Post meridiem am or pm
2962 A Uppercase Ante meridiem and Post meridiem AM or PM
2963 g 12-hour format of an hour without leading zeros 1 to 12
2964 G 24-hour format of an hour without leading zeros 0 to 23
2965 h 12-hour format of an hour with leading zeros 01 to 12
2966 H 24-hour format of an hour with leading zeros 00 to 23
2967 i Minutes, with leading zeros 00 to 59
2968 s Seconds, with leading zeros 00 to 59
2969 u Decimal fraction of a second Examples:
2970 (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
2971 100 (i.e. 0.100s) or
2972 999 (i.e. 0.999s) or
2973 999876543210 (i.e. 0.999876543210s)
2974 O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
2975 P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
2976 T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
2977 Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
2980 1) If unspecified, the month / day defaults to the current month / day, 1991 or
2981 the time defaults to midnight, while the timezone defaults to the 1992-10 or
2982 browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
2983 and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
2984 are optional. 1995-07-18T17:21:28-02:00 or
2985 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
2986 least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
2987 of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
2988 Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
2989 date-time granularity which are supported, or see 2000-02-13T21:25:33
2990 http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
2991 U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
2992 MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
2993 \/Date(1238606590509+0800)\/
2996 * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
2999 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
3001 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
3002 console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
3003 console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
3004 console.log(Ext.Date.format(dt, 'l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
3007 * Here are some standard date/time patterns that you might find helpful. They
3008 * are not part of the source of Ext.Date, but to use them you can simply copy this
3009 * block of code into any script that is included after Ext.Date and they will also become
3010 * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
3012 Ext.Date.patterns = {
3013 ISO8601Long:"Y-m-d H:i:s",
3014 ISO8601Short:"Y-m-d",
3016 LongDate: "l, F d, Y",
3017 FullDateTime: "l, F d, Y g:i:s A",
3020 LongTime: "g:i:s A",
3021 SortableDateTime: "Y-m-d\\TH:i:s",
3022 UniversalSortableDateTime: "Y-m-d H:i:sO",
3029 var dt = new Date();
3030 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3032 * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3033 * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3038 * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3039 * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3040 * They generate precompiled functions from format patterns instead of parsing and
3041 * processing each pattern every time a date is formatted. These functions are available
3042 * on every Date object.
3047 // create private copy of Ext's Ext.util.Format.format() method
3048 // - to remove unnecessary dependency
3049 // - to resolve namespace conflict with MS-Ajax's implementation
3050 function xf(format) {
3051 var args = Array.prototype.slice.call(arguments, 1);
3052 return format.replace(/\{(\d+)\}/g, function(m, i) {
3059 * Returns the current timestamp
3060 * @return {Date} The current timestamp
3063 now: Date.now || function() {
3071 toString: function(date) {
3072 var pad = Ext.String.leftPad;
3074 return date.getFullYear() + "-"
3075 + pad(date.getMonth() + 1, 2, '0') + "-"
3076 + pad(date.getDate(), 2, '0') + "T"
3077 + pad(date.getHours(), 2, '0') + ":"
3078 + pad(date.getMinutes(), 2, '0') + ":"
3079 + pad(date.getSeconds(), 2, '0');
3083 * Returns the number of milliseconds between two dates
3084 * @param {Date} dateA The first date
3085 * @param {Date} dateB (optional) The second date, defaults to now
3086 * @return {Number} The difference in milliseconds
3088 getElapsed: function(dateA, dateB) {
3089 return Math.abs(dateA - (dateB || new Date()));
3093 * Global flag which determines if strict date parsing should be used.
3094 * Strict date parsing will not roll-over invalid dates, which is the
3095 * default behaviour of javascript Date objects.
3096 * (see {@link #parse} for more information)
3097 * Defaults to <tt>false</tt>.
3104 formatCodeToRegex: function(character, currentGroup) {
3105 // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3106 var p = utilDate.parseCodes[character];
3109 p = typeof p == 'function'? p() : p;
3110 utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3113 return p ? Ext.applyIf({
3114 c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3118 s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3123 * <p>An object hash in which each property is a date parsing function. The property name is the
3124 * format string which that function parses.</p>
3125 * <p>This object is automatically populated with date parsing functions as
3126 * date formats are requested for Ext standard formatting strings.</p>
3127 * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3128 * may be used as a format string to {@link #parse}.<p>
3129 * <p>Example:</p><pre><code>
3130 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3132 * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3133 * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3134 * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3135 * (i.e. prevent javascript Date "rollover") (The default must be false).
3136 * Invalid date strings should return null when parsed.</div></li>
3138 * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3139 * formatting function must be placed into the {@link #formatFunctions} property.
3140 * @property parseFunctions
3145 "MS": function(input, strict) {
3146 // note: the timezone offset is ignored since the MS Ajax server sends
3147 // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3148 var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3149 var r = (input || '').match(re);
3150 return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3156 * <p>An object hash in which each property is a date formatting function. The property name is the
3157 * format string which corresponds to the produced formatted date string.</p>
3158 * <p>This object is automatically populated with date formatting functions as
3159 * date formats are requested for Ext standard formatting strings.</p>
3160 * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3161 * may be used as a format string to {@link #format}. Example:</p><pre><code>
3162 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3164 * <p>A formatting function should return a string representation of the passed Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3165 * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3167 * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3168 * parsing function must be placed into the {@link #parseFunctions} property.
3169 * @property formatFunctions
3175 // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3176 return '\\/Date(' + this.getTime() + ')\\/';
3183 * Date interval constant
3190 * Date interval constant
3197 * Date interval constant
3203 /** Date interval constant
3210 * Date interval constant
3217 * Date interval constant
3224 * Date interval constant
3231 * <p>An object hash containing default date values used during date parsing.</p>
3232 * <p>The following properties are available:<div class="mdetail-params"><ul>
3233 * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3234 * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3235 * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3236 * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3237 * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3238 * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3239 * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3241 * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3242 * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3243 * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3244 * It is the responsiblity of the developer to account for this.</b></p>
3247 // set default day value to the first day of the month
3248 Ext.Date.defaults.d = 1;
3250 // parse a February date string containing only year and month values.
3251 // setting the default day value to 1 prevents weird date rollover issues
3252 // when attempting to parse the following date string on, for example, March 31st 2009.
3253 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3255 * @property defaults
3262 * An array of textual day names.
3263 * Override these values for international dates.
3266 Ext.Date.dayNames = [
3286 * An array of textual month names.
3287 * Override these values for international dates.
3290 Ext.Date.monthNames = [
3315 * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3316 * Override these values for international dates.
3319 Ext.Date.monthNumbers = {
3320 'ShortJanNameInYourLang':0,
3321 'ShortFebNameInYourLang':1,
3343 * <p>The date format string that the {@link #dateRenderer} and {@link #date} functions use.
3344 * see {@link #Date} for details.</p>
3345 * <p>This defaults to <code>m/d/Y</code>, but may be overridden in a locale file.</p>
3346 * @property defaultFormat
3350 defaultFormat : "m/d/Y",
3352 * Get the short month name for the given month number.
3353 * Override this function for international dates.
3354 * @param {Number} month A zero-based javascript month number.
3355 * @return {String} The short month name.
3358 getShortMonthName : function(month) {
3359 return utilDate.monthNames[month].substring(0, 3);
3363 * Get the short day name for the given day number.
3364 * Override this function for international dates.
3365 * @param {Number} day A zero-based javascript day number.
3366 * @return {String} The short day name.
3369 getShortDayName : function(day) {
3370 return utilDate.dayNames[day].substring(0, 3);
3374 * Get the zero-based javascript month number for the given short/full month name.
3375 * Override this function for international dates.
3376 * @param {String} name The short/full month name.
3377 * @return {Number} The zero-based javascript month number.
3380 getMonthNumber : function(name) {
3381 // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3382 return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3386 * Checks if the specified format contains hour information
3387 * @param {String} format The format to check
3388 * @return {Boolean} True if the format contains hour information
3392 formatContainsHourInfo : (function(){
3393 var stripEscapeRe = /(\\.)/g,
3394 hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3395 return function(format){
3396 return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3401 * Checks if the specified format contains information about
3402 * anything other than the time.
3403 * @param {String} format The format to check
3404 * @return {Boolean} True if the format contains information about
3405 * date/day information.
3409 formatContainsDateInfo : (function(){
3410 var stripEscapeRe = /(\\.)/g,
3411 dateInfoRe = /([djzmnYycU]|MS)/;
3413 return function(format){
3414 return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3419 * The base format-code to formatting-function hashmap used by the {@link #format} method.
3420 * Formatting functions are strings (or functions which return strings) which
3421 * will return the appropriate value when evaluated in the context of the Date object
3422 * from which the {@link #format} method is called.
3423 * Add to / override these mappings for custom date formatting.
3424 * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3427 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3428 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3434 d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3435 D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3436 j: "this.getDate()",
3437 l: "Ext.Date.dayNames[this.getDay()]",
3438 N: "(this.getDay() ? this.getDay() : 7)",
3439 S: "Ext.Date.getSuffix(this)",
3441 z: "Ext.Date.getDayOfYear(this)",
3442 W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3443 F: "Ext.Date.monthNames[this.getMonth()]",
3444 m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3445 M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3446 n: "(this.getMonth() + 1)",
3447 t: "Ext.Date.getDaysInMonth(this)",
3448 L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3449 o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3450 Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3451 y: "('' + this.getFullYear()).substring(2, 4)",
3452 a: "(this.getHours() < 12 ? 'am' : 'pm')",
3453 A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3454 g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3455 G: "this.getHours()",
3456 h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3457 H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3458 i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3459 s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3460 u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3461 O: "Ext.Date.getGMTOffset(this)",
3462 P: "Ext.Date.getGMTOffset(this, true)",
3463 T: "Ext.Date.getTimezone(this)",
3464 Z: "(this.getTimezoneOffset() * -60)",
3466 c: function() { // ISO-8601 -- GMT format
3467 for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3468 var e = c.charAt(i);
3469 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3471 return code.join(" + ");
3474 c: function() { // ISO-8601 -- UTC format
3476 "this.getUTCFullYear()", "'-'",
3477 "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3478 "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3480 "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3481 "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3482 "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3488 U: "Math.round(this.getTime() / 1000)"
3492 * Checks if the passed Date parameters will cause a javascript Date "rollover".
3493 * @param {Number} year 4-digit year
3494 * @param {Number} month 1-based month-of-year
3495 * @param {Number} day Day of month
3496 * @param {Number} hour (optional) Hour
3497 * @param {Number} minute (optional) Minute
3498 * @param {Number} second (optional) Second
3499 * @param {Number} millisecond (optional) Millisecond
3500 * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3503 isValid : function(y, m, d, h, i, s, ms) {
3510 // Special handling for year < 100
3511 var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3513 return y == dt.getFullYear() &&
3514 m == dt.getMonth() + 1 &&
3515 d == dt.getDate() &&
3516 h == dt.getHours() &&
3517 i == dt.getMinutes() &&
3518 s == dt.getSeconds() &&
3519 ms == dt.getMilliseconds();
3523 * Parses the passed string using the specified date format.
3524 * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3525 * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3526 * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3527 * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3528 * Keep in mind that the input date string must precisely match the specified format string
3529 * in order for the parse operation to be successful (failed parse operations return a null value).
3530 * <p>Example:</p><pre><code>
3531 //dt = Fri May 25 2007 (current date)
3532 var dt = new Date();
3534 //dt = Thu May 25 2006 (today's month/day in 2006)
3535 dt = Ext.Date.parse("2006", "Y");
3537 //dt = Sun Jan 15 2006 (all date parts specified)
3538 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3540 //dt = Sun Jan 15 2006 15:20:01
3541 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3543 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3544 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3546 * @param {String} input The raw date string.
3547 * @param {String} format The expected date string format.
3548 * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3549 (defaults to false). Invalid date strings will return null when parsed.
3550 * @return {Date} The parsed Date.
3553 parse : function(input, format, strict) {
3554 var p = utilDate.parseFunctions;
3555 if (p[format] == null) {
3556 utilDate.createParser(format);
3558 return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3562 parseDate: function(input, format, strict){
3563 return utilDate.parse(input, format, strict);
3568 getFormatCode : function(character) {
3569 var f = utilDate.formatCodes[character];
3572 f = typeof f == 'function'? f() : f;
3573 utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3576 // note: unknown characters are treated as literals
3577 return f || ("'" + Ext.String.escape(character) + "'");
3581 createFormat : function(format) {
3586 for (var i = 0; i < format.length; ++i) {
3587 ch = format.charAt(i);
3588 if (!special && ch == "\\") {
3590 } else if (special) {
3592 code.push("'" + Ext.String.escape(ch) + "'");
3594 code.push(utilDate.getFormatCode(ch));
3597 utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3601 createParser : (function() {
3603 "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3604 "def = Ext.Date.defaults,",
3605 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3610 "if(u != null){", // i.e. unix time is defined
3611 "v = new Date(u * 1000);", // give top priority to UNIX time
3613 // create Date object representing midnight of the current day;
3614 // this will provide us with our date defaults
3615 // (note: clearTime() handles Daylight Saving Time automatically)
3616 "dt = Ext.Date.clearTime(new Date);",
3618 // date calculations (note: these calculations create a dependency on Ext.Number.from())
3619 "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3620 "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3621 "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3623 // time calculations (note: these calculations create a dependency on Ext.Number.from())
3624 "h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3625 "i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3626 "s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3627 "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3629 "if(z >= 0 && y >= 0){",
3630 // both the year and zero-based day of year are defined and >= 0.
3631 // these 2 values alone provide sufficient info to create a full date object
3633 // create Date object representing January 1st for the given year
3634 // handle years < 100 appropriately
3635 "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3637 // then add day of year, checking for Date "rollover" if necessary
3638 "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3639 "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3640 "v = null;", // invalid date, so return null
3642 // plain old Date object
3643 // handle years < 100 properly
3644 "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3650 // favour UTC offset over GMT offset
3652 // reset to UTC, then add offset
3653 "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3655 // reset to GMT, then add offset
3656 "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
3663 return function(format) {
3664 var regexNum = utilDate.parseRegexes.length,
3671 for (var i = 0; i < format.length; ++i) {
3672 ch = format.charAt(i);
3673 if (!special && ch == "\\") {
3675 } else if (special) {
3677 regex.push(Ext.String.escape(ch));
3679 var obj = utilDate.formatCodeToRegex(ch, currentGroup);
3680 currentGroup += obj.g;
3682 if (obj.g && obj.c) {
3688 utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
3689 utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
3697 * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
3698 * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
3699 * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
3703 c:"d = parseInt(results[{0}], 10);\n",
3704 s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
3708 c:"d = parseInt(results[{0}], 10);\n",
3709 s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
3712 for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
3716 s:"(?:" + a.join("|") +")"
3723 s:"(?:" + utilDate.dayNames.join("|") + ")"
3729 s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
3739 s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
3743 c:"z = parseInt(results[{0}], 10);\n",
3744 s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
3749 s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
3754 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
3755 s:"(" + utilDate.monthNames.join("|") + ")"
3759 for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
3760 return Ext.applyIf({
3761 s:"(" + a.join("|") + ")"
3762 }, utilDate.formatCodeToRegex("F"));
3766 c:"m = parseInt(results[{0}], 10) - 1;\n",
3767 s:"(\\d{2})" // month number with leading zeros (01 - 12)
3771 c:"m = parseInt(results[{0}], 10) - 1;\n",
3772 s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
3777 s:"(?:\\d{2})" // no. of days in the month (28 - 31)
3785 return utilDate.formatCodeToRegex("Y");
3789 c:"y = parseInt(results[{0}], 10);\n",
3790 s:"(\\d{4})" // 4-digit year
3794 c:"var ty = parseInt(results[{0}], 10);\n"
3795 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
3799 * In the am/pm parsing routines, we allow both upper and lower case
3800 * even though it doesn't exactly match the spec. It gives much more flexibility
3801 * in being able to specify case insensitive regexes.
3805 c:"if (/(am)/i.test(results[{0}])) {\n"
3806 + "if (!h || h == 12) { h = 0; }\n"
3807 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
3812 c:"if (/(am)/i.test(results[{0}])) {\n"
3813 + "if (!h || h == 12) { h = 0; }\n"
3814 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
3818 return utilDate.formatCodeToRegex("G");
3822 c:"h = parseInt(results[{0}], 10);\n",
3823 s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
3826 return utilDate.formatCodeToRegex("H");
3830 c:"h = parseInt(results[{0}], 10);\n",
3831 s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
3835 c:"i = parseInt(results[{0}], 10);\n",
3836 s:"(\\d{2})" // minutes with leading zeros (00 - 59)
3840 c:"s = parseInt(results[{0}], 10);\n",
3841 s:"(\\d{2})" // seconds with leading zeros (00 - 59)
3845 c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
3846 s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
3851 "o = results[{0}];",
3852 "var sn = o.substring(0,1),", // get + / - sign
3853 "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
3854 "mn = o.substring(3,5) % 60;", // get minutes
3855 "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
3857 s: "([+\-]\\d{4})" // GMT offset in hrs and mins
3862 "o = results[{0}];",
3863 "var sn = o.substring(0,1),", // get + / - sign
3864 "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
3865 "mn = o.substring(4,6) % 60;", // get minutes
3866 "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
3868 s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
3873 s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
3877 c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
3878 + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
3879 s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
3884 utilDate.formatCodeToRegex("Y", 1), // year
3885 utilDate.formatCodeToRegex("m", 2), // month
3886 utilDate.formatCodeToRegex("d", 3), // day
3887 utilDate.formatCodeToRegex("h", 4), // hour
3888 utilDate.formatCodeToRegex("i", 5), // minute
3889 utilDate.formatCodeToRegex("s", 6), // second
3890 {c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
3891 {c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
3892 "if(results[8]) {", // timezone specified
3893 "if(results[8] == 'Z'){",
3895 "}else if (results[8].indexOf(':') > -1){",
3896 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
3898 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
3904 for (var i = 0, l = arr.length; i < l; ++i) {
3905 calc.push(arr[i].c);
3912 arr[0].s, // year (required)
3913 "(?:", "-", arr[1].s, // month (optional)
3914 "(?:", "-", arr[2].s, // day (optional)
3916 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
3917 arr[3].s, ":", arr[4].s, // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
3918 "(?::", arr[5].s, ")?", // seconds (optional)
3919 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
3920 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
3929 c:"u = parseInt(results[{0}], 10);\n",
3930 s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
3934 //Old Ext.Date prototype methods.
3936 dateFormat: function(date, format) {
3937 return utilDate.format(date, format);
3941 * Formats a date given the supplied format string.
3942 * @param {Date} date The date to format
3943 * @param {String} format The format string
3944 * @return {String} The formatted date
3946 format: function(date, format) {
3947 if (utilDate.formatFunctions[format] == null) {
3948 utilDate.createFormat(format);
3950 var result = utilDate.formatFunctions[format].call(date);
3955 * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
3957 * Note: The date string returned by the javascript Date object's toString() method varies
3958 * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
3959 * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
3960 * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
3961 * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
3962 * from the GMT offset portion of the date string.
3963 * @param {Date} date The date
3964 * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
3966 getTimezone : function(date) {
3967 // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
3969 // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
3970 // Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
3971 // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
3972 // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
3973 // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
3975 // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
3976 // step 1: (?:\((.*)\) -- find timezone in parentheses
3977 // step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
3978 // step 3: remove all non uppercase characters found in step 1 and 2
3979 return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
3983 * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
3984 * @param {Date} date The date
3985 * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
3986 * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
3988 getGMTOffset : function(date, colon) {
3989 var offset = date.getTimezoneOffset();
3990 return (offset > 0 ? "-" : "+")
3991 + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
3992 + (colon ? ":" : "")
3993 + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
3997 * Get the numeric day number of the year, adjusted for leap year.
3998 * @param {Date} date The date
3999 * @return {Number} 0 to 364 (365 in leap years).
4001 getDayOfYear: function(date) {
4003 d = Ext.Date.clone(date),
4004 m = date.getMonth(),
4007 for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
4008 num += utilDate.getDaysInMonth(d);
4010 return num + date.getDate() - 1;
4014 * Get the numeric ISO-8601 week number of the year.
4015 * (equivalent to the format specifier 'W', but without a leading zero).
4016 * @param {Date} date The date
4017 * @return {Number} 1 to 53
4020 getWeekOfYear : (function() {
4021 // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4022 var ms1d = 864e5, // milliseconds in a day
4023 ms7d = 7 * ms1d; // milliseconds in a week
4025 return function(date) { // return a closure so constants get calculated only once
4026 var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4027 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4028 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4030 return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4035 * Checks if the current date falls within a leap year.
4036 * @param {Date} date The date
4037 * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4039 isLeapYear : function(date) {
4040 var year = date.getFullYear();
4041 return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4045 * Get the first day of the current month, adjusted for leap year. The returned value
4046 * is the numeric day index within the week (0-6) which can be used in conjunction with
4047 * the {@link #monthNames} array to retrieve the textual day name.
4050 var dt = new Date('1/10/2007'),
4051 firstDay = Ext.Date.getFirstDayOfMonth(dt);
4052 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4054 * @param {Date} date The date
4055 * @return {Number} The day number (0-6).
4057 getFirstDayOfMonth : function(date) {
4058 var day = (date.getDay() - (date.getDate() - 1)) % 7;
4059 return (day < 0) ? (day + 7) : day;
4063 * Get the last day of the current month, adjusted for leap year. The returned value
4064 * is the numeric day index within the week (0-6) which can be used in conjunction with
4065 * the {@link #monthNames} array to retrieve the textual day name.
4068 var dt = new Date('1/10/2007'),
4069 lastDay = Ext.Date.getLastDayOfMonth(dt);
4070 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4072 * @param {Date} date The date
4073 * @return {Number} The day number (0-6).
4075 getLastDayOfMonth : function(date) {
4076 return utilDate.getLastDateOfMonth(date).getDay();
4081 * Get the date of the first day of the month in which this date resides.
4082 * @param {Date} date The date
4085 getFirstDateOfMonth : function(date) {
4086 return new Date(date.getFullYear(), date.getMonth(), 1);
4090 * Get the date of the last day of the month in which this date resides.
4091 * @param {Date} date The date
4094 getLastDateOfMonth : function(date) {
4095 return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4099 * Get the number of days in the current month, adjusted for leap year.
4100 * @param {Date} date The date
4101 * @return {Number} The number of days in the month.
4104 getDaysInMonth: (function() {
4105 var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4107 return function(date) { // return a closure for efficiency
4108 var m = date.getMonth();
4110 return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4115 * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4116 * @param {Date} date The date
4117 * @return {String} 'st, 'nd', 'rd' or 'th'.
4119 getSuffix : function(date) {
4120 switch (date.getDate()) {
4137 * Creates and returns a new Date instance with the exact same date value as the called instance.
4138 * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4139 * variable will also be changed. When the intention is to create a new variable that will not
4140 * modify the original instance, you should create a clone.
4142 * Example of correctly cloning a date:
4145 var orig = new Date('10/1/2006');
4148 console.log(orig); //returns 'Thu Oct 05 2006'!
4151 var orig = new Date('10/1/2006'),
4152 copy = Ext.Date.clone(orig);
4154 console.log(orig); //returns 'Thu Oct 01 2006'
4156 * @param {Date} date The date
4157 * @return {Date} The new Date instance.
4159 clone : function(date) {
4160 return new Date(date.getTime());
4164 * Checks if the current date is affected by Daylight Saving Time (DST).
4165 * @param {Date} date The date
4166 * @return {Boolean} True if the current date is affected by DST.
4168 isDST : function(date) {
4169 // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4170 // courtesy of @geoffrey.mcgill
4171 return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4175 * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4176 * automatically adjusting for Daylight Saving Time (DST) where applicable.
4177 * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4178 * @param {Date} date The date
4179 * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4180 * @return {Date} this or the clone.
4182 clearTime : function(date, clone) {
4184 return Ext.Date.clearTime(Ext.Date.clone(date));
4187 // get current date before clearing time
4188 var d = date.getDate();
4194 date.setMilliseconds(0);
4196 if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4197 // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4198 // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4200 // increment hour until cloned date == current date
4201 for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4204 date.setHours(c.getHours());
4211 * Provides a convenient method for performing basic date arithmetic. This method
4212 * does not modify the Date instance being called - it creates and returns
4213 * a new Date instance containing the resulting date value.
4218 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4219 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4221 // Negative values will be subtracted:
4222 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4223 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4227 * @param {Date} date The date to modify
4228 * @param {String} interval A valid date interval enum value.
4229 * @param {Number} value The amount to add to the current date.
4230 * @return {Date} The new Date instance.
4232 add : function(date, interval, value) {
4233 var d = Ext.Date.clone(date),
4235 if (!interval || value === 0) return d;
4237 switch(interval.toLowerCase()) {
4238 case Ext.Date.MILLI:
4239 d.setMilliseconds(d.getMilliseconds() + value);
4241 case Ext.Date.SECOND:
4242 d.setSeconds(d.getSeconds() + value);
4244 case Ext.Date.MINUTE:
4245 d.setMinutes(d.getMinutes() + value);
4248 d.setHours(d.getHours() + value);
4251 d.setDate(d.getDate() + value);
4253 case Ext.Date.MONTH:
4254 var day = date.getDate();
4256 day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4259 d.setMonth(date.getMonth() + value);
4262 d.setFullYear(date.getFullYear() + value);
4269 * Checks if a date falls on or between the given start and end dates.
4270 * @param {Date} date The date to check
4271 * @param {Date} start Start date
4272 * @param {Date} end End date
4273 * @return {Boolean} true if this date falls on or between the given start and end dates.
4275 between : function(date, start, end) {
4276 var t = date.getTime();
4277 return start.getTime() <= t && t <= end.getTime();
4280 //Maintains compatibility with old static and prototype window.Date methods.
4281 compat: function() {
4282 var nativeDate = window.Date,
4284 statics = ['useStrict', 'formatCodeToRegex', 'parseFunctions', 'parseRegexes', 'formatFunctions', 'y2kYear', 'MILLI', 'SECOND', 'MINUTE', 'HOUR', 'DAY', 'MONTH', 'YEAR', 'defaults', 'dayNames', 'monthNames', 'monthNumbers', 'getShortMonthName', 'getShortDayName', 'getMonthNumber', 'formatCodes', 'isValid', 'parseDate', 'getFormatCode', 'createFormat', 'createParser', 'parseCodes'],
4285 proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4288 Ext.Array.forEach(statics, function(s) {
4289 nativeDate[s] = utilDate[s];
4292 //Append to prototype
4293 Ext.Array.forEach(proto, function(s) {
4294 nativeDate.prototype[s] = function() {
4295 var args = Array.prototype.slice.call(arguments);
4297 return utilDate[s].apply(utilDate, args);
4303 var utilDate = Ext.Date;
4308 * @author Jacky Nguyen <jacky@sencha.com>
4309 * @docauthor Jacky Nguyen <jacky@sencha.com>
4312 * The root of all classes created with {@link Ext#define}
4313 * All prototype and static members of this class are inherited by any other class
4316 (function(flexSetter) {
4318 var Base = Ext.Base = function() {};
4320 $className: 'Ext.Base',
4325 * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4326 * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4327 * for a detailed comparison
4329 * Ext.define('My.Cat', {
4331 * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4334 * constructor: function() {
4335 * alert(this.self.speciesName); / dependent on 'this'
4340 * clone: function() {
4341 * return new this.self();
4346 * Ext.define('My.SnowLeopard', {
4349 * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
4353 * var cat = new My.Cat(); // alerts 'Cat'
4354 * var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
4356 * var clone = snowLeopard.clone();
4357 * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
4366 * Default constructor, simply returns `this`
4370 * @return {Object} this
4372 constructor: function() {
4377 * Initialize configuration for this class. a typical example:
4379 * Ext.define('My.awesome.Class', {
4380 * // The default config
4386 * constructor: function(config) {
4387 * this.initConfig(config);
4393 * var awesome = new My.awesome.Class({
4394 * name: 'Super Awesome'
4397 * alert(awesome.getName()); // 'Super Awesome'
4400 * @param {Object} config
4401 * @return {Object} mixins The mixin prototypes as key - value pairs
4404 initConfig: function(config) {
4405 if (!this.$configInited) {
4406 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4408 this.applyConfig(this.config);
4410 this.$configInited = true;
4419 setConfig: function(config) {
4420 this.applyConfig(config || {});
4428 applyConfig: flexSetter(function(name, value) {
4429 var setter = 'set' + Ext.String.capitalize(name);
4431 if (typeof this[setter] === 'function') {
4432 this[setter].call(this, value);
4439 * Call the parent's overridden method. For example:
4441 * Ext.define('My.own.A', {
4442 * constructor: function(test) {
4447 * Ext.define('My.own.B', {
4448 * extend: 'My.own.A',
4450 * constructor: function(test) {
4453 * this.callParent([test + 1]);
4457 * Ext.define('My.own.C', {
4458 * extend: 'My.own.B',
4460 * constructor: function() {
4461 * alert("Going to call parent's overriden constructor...");
4463 * this.callParent(arguments);
4467 * var a = new My.own.A(1); // alerts '1'
4468 * var b = new My.own.B(1); // alerts '1', then alerts '2'
4469 * var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4470 * // alerts '2', then alerts '3'
4473 * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4474 * from the current method, for example: `this.callParent(arguments)`
4475 * @return {Mixed} Returns the result from the superclass' method
4478 callParent: function(args) {
4479 var method = this.callParent.caller,
4480 parentClass, methodName;
4482 if (!method.$owner) {
4484 if (!method.caller) {
4486 sourceClass: Ext.getClassName(this),
4487 sourceMethod: "callParent",
4488 msg: "Attempting to call a protected method from the public scope, which is not allowed"
4493 method = method.caller;
4496 parentClass = method.$owner.superclass;
4497 methodName = method.$name;
4500 if (!(methodName in parentClass)) {
4502 sourceClass: Ext.getClassName(this),
4503 sourceMethod: methodName,
4504 msg: "this.callParent() was called but there's no such method (" + methodName +
4505 ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
4510 return parentClass[methodName].apply(this, args || []);
4515 * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4516 * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4517 * `this` points to during run-time
4519 * Ext.define('My.Cat', {
4522 * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4525 * constructor: function() {
4526 * var statics = this.statics();
4528 * alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
4529 * // equivalent to: My.Cat.speciesName
4531 * alert(this.self.speciesName); // dependent on 'this'
4533 * statics.totalCreated++;
4538 * clone: function() {
4539 * var cloned = new this.self; // dependent on 'this'
4541 * cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
4548 * Ext.define('My.SnowLeopard', {
4552 * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
4555 * constructor: function() {
4556 * this.callParent();
4560 * var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
4562 * var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4564 * var clone = snowLeopard.clone();
4565 * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
4566 * alert(clone.groupName); // alerts 'Cat'
4568 * alert(My.Cat.totalCreated); // alerts 3
4574 statics: function() {
4575 var method = this.statics.caller,
4582 return method.$owner;
4586 * Call the original method that was previously overridden with {@link Ext.Base#override}
4588 * Ext.define('My.Cat', {
4589 * constructor: function() {
4590 * alert("I'm a cat!");
4597 * constructor: function() {
4598 * alert("I'm going to be a cat!");
4600 * var instance = this.callOverridden();
4602 * alert("Meeeeoooowwww");
4608 * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4609 * // alerts "I'm a cat!"
4610 * // alerts "Meeeeoooowwww"
4612 * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4613 * @return {Mixed} Returns the result after calling the overridden method
4616 callOverridden: function(args) {
4617 var method = this.callOverridden.caller;
4620 if (!method.$owner) {
4622 sourceClass: Ext.getClassName(this),
4623 sourceMethod: "callOverridden",
4624 msg: "Attempting to call a protected method from the public scope, which is not allowed"
4628 if (!method.$previous) {
4630 sourceClass: Ext.getClassName(this),
4631 sourceMethod: "callOverridden",
4632 msg: "this.callOverridden was called in '" + method.$name +
4633 "' but this method has never been overridden"
4638 return method.$previous.apply(this, args || []);
4641 destroy: function() {}
4644 // These static properties will be copied to every newly created class with {@link Ext#define}
4645 Ext.apply(Ext.Base, {
4647 * Create a new instance of this Class.
4649 * Ext.define('My.cool.Class', {
4653 * My.cool.Class.create({
4662 create: function() {
4663 return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4669 own: flexSetter(function(name, value) {
4670 if (typeof value === 'function') {
4671 this.ownMethod(name, value);
4674 this.prototype[name] = value;
4681 ownMethod: function(name, fn) {
4684 if (fn.$owner !== undefined && fn !== Ext.emptyFn) {
4688 return originalFn.apply(this, arguments);
4694 className = Ext.getClassName(this);
4696 fn.displayName = className + '#' + name;
4702 this.prototype[name] = fn;
4706 * Add / override static properties of this class.
4708 * Ext.define('My.cool.Class', {
4712 * My.cool.Class.addStatics({
4713 * someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
4714 * method1: function() { ... }, // My.cool.Class.method1 = function() { ... };
4715 * method2: function() { ... } // My.cool.Class.method2 = function() { ... };
4718 * @property addStatics
4721 * @param {Object} members
4724 addStatics: function(members) {
4725 for (var name in members) {
4726 if (members.hasOwnProperty(name)) {
4727 this[name] = members[name];
4735 * Add methods / properties to the prototype of this class.
4737 * Ext.define('My.awesome.Cat', {
4738 * constructor: function() {
4743 * My.awesome.Cat.implement({
4744 * meow: function() {
4745 * alert('Meowww...');
4749 * var kitty = new My.awesome.Cat;
4752 * @property implement
4755 * @param {Object} members
4758 implement: function(members) {
4759 var prototype = this.prototype,
4760 name, i, member, previous;
4762 var className = Ext.getClassName(this);
4764 for (name in members) {
4765 if (members.hasOwnProperty(name)) {
4766 member = members[name];
4768 if (typeof member === 'function') {
4769 member.$owner = this;
4770 member.$name = name;
4773 member.displayName = className + '#' + name;
4778 prototype[name] = member;
4782 if (Ext.enumerables) {
4783 var enumerables = Ext.enumerables;
4785 for (i = enumerables.length; i--;) {
4786 name = enumerables[i];
4788 if (members.hasOwnProperty(name)) {
4789 member = members[name];
4790 member.$owner = this;
4791 member.$name = name;
4792 prototype[name] = member;
4799 * Borrow another class' members to the prototype of this class.
4801 * Ext.define('Bank', {
4803 * printMoney: function() {
4808 * Ext.define('Thief', {
4812 * Thief.borrow(Bank, ['money', 'printMoney']);
4814 * var steve = new Thief();
4816 * alert(steve.money); // alerts '$$$'
4817 * steve.printMoney(); // alerts '$$$$$$$'
4822 * @param {Ext.Base} fromClass The class to borrow members from
4823 * @param {Array/String} members The names of the members to borrow
4824 * @return {Ext.Base} this
4827 borrow: function(fromClass, members) {
4828 var fromPrototype = fromClass.prototype,
4831 members = Ext.Array.from(members);
4833 for (i = 0, ln = members.length; i < ln; i++) {
4834 member = members[i];
4836 this.own(member, fromPrototype[member]);
4843 * Override prototype members of this class. Overridden methods can be invoked via
4844 * {@link Ext.Base#callOverridden}
4846 * Ext.define('My.Cat', {
4847 * constructor: function() {
4848 * alert("I'm a cat!");
4855 * constructor: function() {
4856 * alert("I'm going to be a cat!");
4858 * var instance = this.callOverridden();
4860 * alert("Meeeeoooowwww");
4866 * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4867 * // alerts "I'm a cat!"
4868 * // alerts "Meeeeoooowwww"
4870 * @property override
4873 * @param {Object} members
4874 * @return {Ext.Base} this
4877 override: function(members) {
4878 var prototype = this.prototype,
4879 name, i, member, previous;
4881 for (name in members) {
4882 if (members.hasOwnProperty(name)) {
4883 member = members[name];
4885 if (typeof member === 'function') {
4886 if (typeof prototype[name] === 'function') {
4887 previous = prototype[name];
4888 member.$previous = previous;
4891 this.ownMethod(name, member);
4894 prototype[name] = member;
4899 if (Ext.enumerables) {
4900 var enumerables = Ext.enumerables;
4902 for (i = enumerables.length; i--;) {
4903 name = enumerables[i];
4905 if (members.hasOwnProperty(name)) {
4906 if (prototype[name] !== undefined) {
4907 previous = prototype[name];
4908 members[name].$previous = previous;
4911 this.ownMethod(name, members[name]);
4920 * Used internally by the mixins pre-processor
4923 mixin: flexSetter(function(name, cls) {
4924 var mixin = cls.prototype,
4925 my = this.prototype,
4929 if (mixin.hasOwnProperty(i)) {
4930 if (my[i] === undefined) {
4931 if (typeof mixin[i] === 'function') {
4934 if (fn.$owner === undefined) {
4935 this.ownMethod(i, fn);
4945 else if (i === 'config' && my.config && mixin.config) {
4946 Ext.Object.merge(my.config, mixin.config);
4951 if (my.mixins === undefined) {
4955 my.mixins[name] = mixin;
4959 * Get the current class' name in string format.
4961 * Ext.define('My.cool.Class', {
4962 * constructor: function() {
4963 * alert(this.self.getName()); // alerts 'My.cool.Class'
4967 * My.cool.Class.getName(); // 'My.cool.Class'
4969 * @return {String} className
4972 getName: function() {
4973 return Ext.getClassName(this);
4977 * Create aliases for existing prototype methods. Example:
4979 * Ext.define('My.cool.Class', {
4980 * method1: function() { ... },
4981 * method2: function() { ... }
4984 * var test = new My.cool.Class();
4986 * My.cool.Class.createAlias({
4987 * method3: 'method1',
4988 * method4: 'method2'
4991 * test.method3(); // test.method1()
4993 * My.cool.Class.createAlias('method5', 'method3');
4995 * test.method5(); // test.method3() -> test.method1()
4997 * @property createAlias
5000 * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
5001 * {@link Ext.Function#flexSetter flexSetter}
5002 * @param {String/Object} origin The original method name
5005 createAlias: flexSetter(function(alias, origin) {
5006 this.prototype[alias] = this.prototype[origin];
5010 })(Ext.Function.flexSetter);
5013 * @author Jacky Nguyen <jacky@sencha.com>
5014 * @docauthor Jacky Nguyen <jacky@sencha.com>
5017 * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
5018 * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
5019 * to enable namespacing and dynamic dependency resolution.
5023 * Ext.define(className, properties);
5025 * in which `properties` is an object represent a collection of properties that apply to the class. See
5026 * {@link Ext.ClassManager#create} for more detailed instructions.
5028 * Ext.define('Person', {
5031 * constructor: function(name) {
5039 * eat: function(foodType) {
5040 * alert("I'm eating: " + foodType);
5046 * var aaron = new Person("Aaron");
5047 * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5049 * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5050 * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5054 * Ext.define('Developer', {
5057 * constructor: function(name, isGeek) {
5058 * this.isGeek = isGeek;
5060 * // Apply a method from the parent class' prototype
5061 * this.callParent([name]);
5067 * code: function(language) {
5068 * alert("I'm coding in: " + language);
5076 * var jacky = new Developer("Jacky", true);
5077 * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
5078 * // alert("I'm eating: Bugs");
5080 * See {@link Ext.Base#callParent} for more details on calling superclass' methods
5084 * Ext.define('CanPlayGuitar', {
5085 * playGuitar: function() {
5086 * alert("F#...G...D...A");
5090 * Ext.define('CanComposeSongs', {
5091 * composeSongs: function() { ... }
5094 * Ext.define('CanSing', {
5095 * sing: function() {
5096 * alert("I'm on the highway to hell...")
5100 * Ext.define('Musician', {
5104 * canPlayGuitar: 'CanPlayGuitar',
5105 * canComposeSongs: 'CanComposeSongs',
5106 * canSing: 'CanSing'
5110 * Ext.define('CoolPerson', {
5114 * canPlayGuitar: 'CanPlayGuitar',
5115 * canSing: 'CanSing'
5118 * sing: function() {
5119 * alert("Ahem....");
5121 * this.mixins.canSing.sing.call(this);
5123 * alert("[Playing guitar at the same time...]");
5125 * this.playGuitar();
5129 * var me = new CoolPerson("Jacky");
5131 * me.sing(); // alert("Ahem...");
5132 * // alert("I'm on the highway to hell...");
5133 * // alert("[Playing guitar at the same time...]");
5134 * // alert("F#...G...D...A");
5138 * Ext.define('SmartPhone', {
5140 * hasTouchScreen: false,
5141 * operatingSystem: 'Other',
5145 * isExpensive: false,
5147 * constructor: function(config) {
5148 * this.initConfig(config);
5153 * applyPrice: function(price) {
5154 * this.isExpensive = (price > 500);
5159 * applyOperatingSystem: function(operatingSystem) {
5160 * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
5164 * return operatingSystem;
5168 * var iPhone = new SmartPhone({
5169 * hasTouchScreen: true,
5170 * operatingSystem: 'iOS'
5173 * iPhone.getPrice(); // 500;
5174 * iPhone.getOperatingSystem(); // 'iOS'
5175 * iPhone.getHasTouchScreen(); // true;
5176 * iPhone.hasTouchScreen(); // true
5178 * iPhone.isExpensive; // false;
5179 * iPhone.setPrice(600);
5180 * iPhone.getPrice(); // 600
5181 * iPhone.isExpensive; // true;
5183 * iPhone.setOperatingSystem('AlienOS');
5184 * iPhone.getOperatingSystem(); // 'Other'
5188 * Ext.define('Computer', {
5190 * factory: function(brand) {
5191 * // 'this' in static methods refer to the class itself
5192 * return new this(brand);
5196 * constructor: function() { ... }
5199 * var dellComputer = Computer.factory('Dell');
5201 * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
5202 * static properties within class methods
5209 baseStaticProperties = [],
5212 for (baseStaticProperty in Base) {
5213 if (Base.hasOwnProperty(baseStaticProperty)) {
5214 baseStaticProperties.push(baseStaticProperty);
5220 * @param {Object} classData An object represent the properties of this class
5221 * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
5222 * Note that the creation process can be asynchronous depending on the pre-processors used.
5223 * @return {Ext.Base} The newly created class
5225 Ext.Class = Class = function(newClass, classData, onClassCreated) {
5226 if (typeof newClass !== 'function') {
5227 onClassCreated = classData;
5228 classData = newClass;
5229 newClass = function() {
5230 return this.constructor.apply(this, arguments);
5238 var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5239 registeredPreprocessors = Class.getPreprocessors(),
5242 preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
5244 for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5245 staticPropertyName = baseStaticProperties[i];
5246 newClass[staticPropertyName] = Base[staticPropertyName];
5249 delete classData.preprocessors;
5251 for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5252 preprocessor = preprocessorStack[j];
5254 if (typeof preprocessor === 'string') {
5255 preprocessor = registeredPreprocessors[preprocessor];
5257 if (!preprocessor.always) {
5258 if (classData.hasOwnProperty(preprocessor.name)) {
5259 preprocessors.push(preprocessor.fn);
5263 preprocessors.push(preprocessor.fn);
5267 preprocessors.push(preprocessor);
5271 classData.onClassCreated = onClassCreated;
5273 classData.onBeforeClassCreated = function(cls, data) {
5274 onClassCreated = data.onClassCreated;
5276 delete data.onBeforeClassCreated;
5277 delete data.onClassCreated;
5279 cls.implement(data);
5281 if (onClassCreated) {
5282 onClassCreated.call(cls, cls);
5286 process = function(cls, data) {
5287 preprocessor = preprocessors[index++];
5289 if (!preprocessor) {
5290 data.onBeforeClassCreated.apply(this, arguments);
5294 if (preprocessor.call(this, cls, data, process) !== false) {
5295 process.apply(this, arguments);
5299 process.call(Class, newClass, classData);
5310 * Register a new pre-processor to be used during the class creation process
5312 * @member Ext.Class registerPreprocessor
5313 * @param {String} name The pre-processor's name
5314 * @param {Function} fn The callback function to be executed. Typical format:
5316 function(cls, data, fn) {
5319 // Execute this when the processing is finished.
5320 // Asynchronous processing is perfectly ok
5322 fn.call(this, cls, data);
5326 * Passed arguments for this function are:
5328 * - `{Function} cls`: The created class
5329 * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
5330 * - `{Function} fn`: The callback function that <b>must</b> to be executed when this pre-processor finishes,
5331 * regardless of whether the processing is synchronous or aynchronous
5333 * @return {Ext.Class} this
5336 registerPreprocessor: function(name, fn, always) {
5337 this.preprocessors[name] = {
5339 always: always || false,
5347 * Retrieve a pre-processor callback function by its name, which has been registered before
5349 * @param {String} name
5350 * @return {Function} preprocessor
5352 getPreprocessor: function(name) {
5353 return this.preprocessors[name];
5356 getPreprocessors: function() {
5357 return this.preprocessors;
5361 * Retrieve the array stack of default pre-processors
5363 * @return {Function} defaultPreprocessors
5365 getDefaultPreprocessors: function() {
5366 return this.defaultPreprocessors || [];
5370 * Set the default array stack of default pre-processors
5372 * @param {Array} preprocessors
5373 * @return {Ext.Class} this
5375 setDefaultPreprocessors: function(preprocessors) {
5376 this.defaultPreprocessors = Ext.Array.from(preprocessors);
5382 * Insert this pre-processor at a specific position in the stack, optionally relative to
5383 * any existing pre-processor. For example:
5385 Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5389 fn.call(this, cls, data);
5391 }).insertDefaultPreprocessor('debug', 'last');
5393 * @param {String} name The pre-processor name. Note that it needs to be registered with
5394 * {@link Ext#registerPreprocessor registerPreprocessor} before this
5395 * @param {String} offset The insertion position. Four possible values are:
5396 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5397 * @param {String} relativeName
5398 * @return {Ext.Class} this
5401 setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5402 var defaultPreprocessors = this.defaultPreprocessors,
5405 if (typeof offset === 'string') {
5406 if (offset === 'first') {
5407 defaultPreprocessors.unshift(name);
5411 else if (offset === 'last') {
5412 defaultPreprocessors.push(name);
5417 offset = (offset === 'after') ? 1 : -1;
5420 index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5423 defaultPreprocessors.splice(Math.max(0, index + offset), 0, name);
5430 Class.registerPreprocessor('extend', function(cls, data) {
5431 var extend = data.extend,
5433 basePrototype = base.prototype,
5434 prototype = function() {},
5435 parent, i, k, ln, staticName, parentStatics,
5436 parentPrototype, clsPrototype;
5438 if (extend && extend !== Object) {
5445 parentPrototype = parent.prototype;
5447 prototype.prototype = parentPrototype;
5448 clsPrototype = cls.prototype = new prototype();
5450 if (!('$class' in parent)) {
5451 for (i in basePrototype) {
5452 if (!parentPrototype[i]) {
5453 parentPrototype[i] = basePrototype[i];
5458 clsPrototype.self = cls;
5460 cls.superclass = clsPrototype.superclass = parentPrototype;
5464 // Statics inheritance
5465 parentStatics = parentPrototype.$inheritableStatics;
5467 if (parentStatics) {
5468 for (k = 0, ln = parentStatics.length; k < ln; k++) {
5469 staticName = parentStatics[k];
5471 if (!cls.hasOwnProperty(staticName)) {
5472 cls[staticName] = parent[staticName];
5477 // Merge the parent class' config object without referencing it
5478 if (parentPrototype.config) {
5479 clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5482 clsPrototype.config = {};
5485 if (clsPrototype.$onExtended) {
5486 clsPrototype.$onExtended.call(cls, cls, data);
5489 if (data.onClassExtended) {
5490 clsPrototype.$onExtended = data.onClassExtended;
5491 delete data.onClassExtended;
5496 Class.registerPreprocessor('statics', function(cls, data) {
5497 var statics = data.statics,
5500 for (name in statics) {
5501 if (statics.hasOwnProperty(name)) {
5502 cls[name] = statics[name];
5506 delete data.statics;
5509 Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5510 var statics = data.inheritableStatics,
5512 prototype = cls.prototype,
5515 inheritableStatics = prototype.$inheritableStatics;
5517 if (!inheritableStatics) {
5518 inheritableStatics = prototype.$inheritableStatics = [];
5521 for (name in statics) {
5522 if (statics.hasOwnProperty(name)) {
5523 cls[name] = statics[name];
5524 inheritableStatics.push(name);
5528 delete data.inheritableStatics;
5531 Class.registerPreprocessor('mixins', function(cls, data) {
5532 cls.mixin(data.mixins);
5537 Class.registerPreprocessor('config', function(cls, data) {
5538 var prototype = cls.prototype;
5540 Ext.Object.each(data.config, function(name) {
5541 var cName = name.charAt(0).toUpperCase() + name.substr(1),
5543 apply = 'apply' + cName,
5544 setter = 'set' + cName,
5545 getter = 'get' + cName;
5547 if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5548 data[apply] = function(val) {
5553 if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5554 data[setter] = function(val) {
5555 var ret = this[apply].call(this, val, this[pName]);
5557 if (ret !== undefined) {
5565 if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5566 data[getter] = function() {
5572 Ext.Object.merge(prototype.config, data.config);
5576 Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
5578 // Backwards compatible
5579 Ext.extend = function(subclass, superclass, members) {
5580 if (arguments.length === 2 && Ext.isObject(superclass)) {
5581 members = superclass;
5582 superclass = subclass;
5589 Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5592 members.extend = superclass;
5593 members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
5596 cls = new Class(subclass, members);
5599 cls = new Class(members);
5602 cls.prototype.override = function(o) {
5604 if (o.hasOwnProperty(m)) {
5616 * @author Jacky Nguyen <jacky@sencha.com>
5617 * @docauthor Jacky Nguyen <jacky@sencha.com>
5618 * @class Ext.ClassManager
5620 Ext.ClassManager manages all classes and handles mapping from string class name to
5621 actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5622 these convenient shorthands:
5624 - {@link Ext#define Ext.define}
5625 - {@link Ext#create Ext.create}
5626 - {@link Ext#widget Ext.widget}
5627 - {@link Ext#getClass Ext.getClass}
5628 - {@link Ext#getClassName Ext.getClassName}
5633 (function(Class, alias) {
5635 var slice = Array.prototype.slice;
5637 var Manager = Ext.ClassManager = {
5642 * All classes which were defined through the ClassManager. Keys are the
5643 * name of the classes and the values are references to the classes.
5656 namespaceRewrites: [{
5665 alternateToName: {},
5671 enableNamespaceParseCache: true,
5674 namespaceParseCache: {},
5681 instantiationCounts: {},
5685 * Checks if a class has already been created.
5687 * @param {String} className
5688 * @return {Boolean} exist
5690 isCreated: function(className) {
5691 var i, ln, part, root, parts;
5694 if (typeof className !== 'string' || className.length < 1) {
5696 sourceClass: "Ext.ClassManager",
5697 sourceMethod: "exist",
5698 msg: "Invalid classname, must be a string and must not be empty"
5703 if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
5708 parts = this.parseNamespace(className);
5710 for (i = 0, ln = parts.length; i < ln; i++) {
5713 if (typeof part !== 'string') {
5716 if (!root || !root[part]) {
5724 Ext.Loader.historyPush(className);
5726 this.existCache[className] = true;
5732 * Supports namespace rewriting
5735 parseNamespace: function(namespace) {
5737 if (typeof namespace !== 'string') {
5739 sourceClass: "Ext.ClassManager",
5740 sourceMethod: "parseNamespace",
5741 msg: "Invalid namespace, must be a string"
5746 var cache = this.namespaceParseCache;
5748 if (this.enableNamespaceParseCache) {
5749 if (cache.hasOwnProperty(namespace)) {
5750 return cache[namespace];
5755 rewrites = this.namespaceRewrites,
5756 rewrite, from, to, i, ln, root = Ext.global;
5758 for (i = 0, ln = rewrites.length; i < ln; i++) {
5759 rewrite = rewrites[i];
5760 from = rewrite.from;
5763 if (namespace === from || namespace.substring(0, from.length) === from) {
5764 namespace = namespace.substring(from.length);
5766 if (typeof to !== 'string') {
5769 parts = parts.concat(to.split('.'));
5778 parts = parts.concat(namespace.split('.'));
5780 if (this.enableNamespaceParseCache) {
5781 cache[namespace] = parts;
5788 * Creates a namespace and assign the `value` to the created object
5790 Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
5792 alert(MyCompany.pkg.Example === someObject); // alerts true
5794 * @param {String} name
5795 * @param {Mixed} value
5798 setNamespace: function(name, value) {
5799 var root = Ext.global,
5800 parts = this.parseNamespace(name),
5804 for (i = 0, ln = parts.length; i < ln; i++) {
5807 if (typeof part !== 'string') {
5824 * The new Ext.ns, supports namespace rewriting
5827 createNamespaces: function() {
5828 var root = Ext.global,
5829 parts, part, i, j, ln, subLn;
5831 for (i = 0, ln = arguments.length; i < ln; i++) {
5832 parts = this.parseNamespace(arguments[i]);
5834 for (j = 0, subLn = parts.length; j < subLn; j++) {
5837 if (typeof part !== 'string') {
5853 * Sets a name reference to a class.
5855 * @param {String} name
5856 * @param {Object} value
5857 * @return {Ext.ClassManager} this
5859 set: function(name, value) {
5860 var targetName = this.getName(value);
5862 this.classes[name] = this.setNamespace(name, value);
5864 if (targetName && targetName !== name) {
5865 this.maps.alternateToName[name] = targetName;
5872 * Retrieve a class by its name.
5874 * @param {String} name
5875 * @return {Class} class
5877 get: function(name) {
5878 if (this.classes.hasOwnProperty(name)) {
5879 return this.classes[name];
5882 var root = Ext.global,
5883 parts = this.parseNamespace(name),
5886 for (i = 0, ln = parts.length; i < ln; i++) {
5889 if (typeof part !== 'string') {
5892 if (!root || !root[part]) {
5904 * Register the alias for a class.
5906 * @param {Class/String} cls a reference to a class or a className
5907 * @param {String} alias Alias to use when referring to this class
5909 setAlias: function(cls, alias) {
5910 var aliasToNameMap = this.maps.aliasToName,
5911 nameToAliasesMap = this.maps.nameToAliases,
5914 if (typeof cls === 'string') {
5917 className = this.getName(cls);
5920 if (alias && aliasToNameMap[alias] !== className) {
5922 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
5923 Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
5924 "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
5928 aliasToNameMap[alias] = className;
5931 if (!nameToAliasesMap[className]) {
5932 nameToAliasesMap[className] = [];
5936 Ext.Array.include(nameToAliasesMap[className], alias);
5943 * Get a reference to the class by its alias.
5945 * @param {String} alias
5946 * @return {Class} class
5948 getByAlias: function(alias) {
5949 return this.get(this.getNameByAlias(alias));
5953 * Get the name of a class by its alias.
5955 * @param {String} alias
5956 * @return {String} className
5958 getNameByAlias: function(alias) {
5959 return this.maps.aliasToName[alias] || '';
5963 * Get the name of a class by its alternate name.
5965 * @param {String} alternate
5966 * @return {String} className
5968 getNameByAlternate: function(alternate) {
5969 return this.maps.alternateToName[alternate] || '';
5973 * Get the aliases of a class by the class name
5975 * @param {String} name
5976 * @return {Array} aliases
5978 getAliasesByName: function(name) {
5979 return this.maps.nameToAliases[name] || [];
5983 * Get the name of the class by its reference or its instance;
5984 * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
5986 Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
5988 * @param {Class/Object} object
5989 * @return {String} className
5992 getName: function(object) {
5993 return object && object.$className || '';
5997 * Get the class of the provided object; returns null if it's not an instance
5998 * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}
6000 var component = new Ext.Component();
6002 Ext.ClassManager.getClass(component); // returns Ext.Component
6004 * @param {Object} object
6005 * @return {Class} class
6008 getClass: function(object) {
6009 return object && object.self || null;
6013 * Defines a class. This is usually invoked via the alias {@link Ext#define Ext.define}
6015 Ext.ClassManager.create('My.awesome.Class', {
6016 someProperty: 'something',
6017 someMethod: function() { ... }
6022 alert(this === My.awesome.Class); // alerts true
6024 var myInstance = new this();
6027 * @param {String} className The class name to create in string dot-namespaced format, for example:
6028 * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
6029 * It is highly recommended to follow this simple convention:
6031 - The root and the class name are 'CamelCased'
6032 - Everything else is lower-cased
6034 * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
6035 * strings, except those in the reserved listed below:
6043 - `alternateClassName`
6045 * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
6046 * (`this`) will be the newly created class itself.
6047 * @return {Ext.Base}
6050 create: function(className, data, createdFn) {
6054 if (typeof className !== 'string') {
6057 sourceMethod: "define",
6058 msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
6063 data.$className = className;
6065 return new Class(data, function() {
6066 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6067 registeredPostprocessors = manager.postprocessors,
6069 postprocessors = [],
6070 postprocessor, postprocessors, process, i, ln;
6072 delete data.postprocessors;
6074 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6075 postprocessor = postprocessorStack[i];
6077 if (typeof postprocessor === 'string') {
6078 postprocessor = registeredPostprocessors[postprocessor];
6080 if (!postprocessor.always) {
6081 if (data[postprocessor.name] !== undefined) {
6082 postprocessors.push(postprocessor.fn);
6086 postprocessors.push(postprocessor.fn);
6090 postprocessors.push(postprocessor);
6094 process = function(clsName, cls, clsData) {
6095 postprocessor = postprocessors[index++];
6097 if (!postprocessor) {
6098 manager.set(className, cls);
6100 Ext.Loader.historyPush(className);
6103 createdFn.call(cls, cls);
6109 if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6110 process.apply(this, arguments);
6114 process.call(manager, className, this, data);
6119 * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
6120 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6121 * attempt to load the class via synchronous loading.
6123 var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6125 * @param {String} alias
6126 * @param {Mixed} args,... Additional arguments after the alias will be passed to the
6127 * class constructor.
6128 * @return {Object} instance
6131 instantiateByAlias: function() {
6132 var alias = arguments[0],
6133 args = slice.call(arguments),
6134 className = this.getNameByAlias(alias);
6137 className = this.maps.aliasToName[alias];
6143 sourceMethod: "createByAlias",
6144 msg: "Cannot create an instance of unrecognized alias: " + alias
6150 if (Ext.global.console) {
6151 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
6152 "Ext.require('" + alias + "') above Ext.onReady");
6156 Ext.syncRequire(className);
6159 args[0] = className;
6161 return this.instantiate.apply(this, args);
6165 * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
6166 * shorthand {@link Ext#create Ext.create}
6168 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6169 * attempt to load the class via synchronous loading.
6171 * For example, all these three lines return the same result:
6174 var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6177 var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6180 var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6182 * @param {String} name
6183 * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
6184 * @return {Object} instance
6187 instantiate: function() {
6188 var name = arguments[0],
6189 args = slice.call(arguments, 1),
6193 if (typeof name !== 'function') {
6195 if ((typeof name !== 'string' || name.length < 1)) {
6198 sourceMethod: "create",
6199 msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
6204 cls = this.get(name);
6210 // No record of this class name, it's possibly an alias, so look it up
6212 possibleName = this.getNameByAlias(name);
6215 name = possibleName;
6217 cls = this.get(name);
6221 // Still no record of this class name, it's possibly an alternate name, so look it up
6223 possibleName = this.getNameByAlternate(name);
6226 name = possibleName;
6228 cls = this.get(name);
6232 // Still not existing at this point, try to load it via synchronous mode as the last resort
6235 if (Ext.global.console) {
6236 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
6237 "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
6241 Ext.syncRequire(name);
6243 cls = this.get(name);
6250 sourceMethod: "create",
6251 msg: "Cannot create an instance of unrecognized class name / alias: " + alias
6255 if (typeof cls !== 'function') {
6258 sourceMethod: "create",
6259 msg: "'" + name + "' is a singleton and cannot be instantiated"
6265 if (!this.instantiationCounts[name]) {
6266 this.instantiationCounts[name] = 0;
6269 this.instantiationCounts[name]++;
6272 return this.getInstantiator(args.length)(cls, args);
6280 dynInstantiate: function(name, args) {
6281 args = Ext.Array.from(args, true);
6284 return this.instantiate.apply(this, args);
6291 getInstantiator: function(length) {
6292 if (!this.instantiators[length]) {
6296 for (i = 0; i < length; i++) {
6297 args.push('a['+i+']');
6300 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6303 return this.instantiators[length];
6314 defaultPostprocessors: [],
6317 * Register a post-processor function.
6319 * @param {String} name
6320 * @param {Function} postprocessor
6322 registerPostprocessor: function(name, fn, always) {
6323 this.postprocessors[name] = {
6325 always: always || false,
6333 * Set the default post processors array stack which are applied to every class.
6335 * @param {String/Array} The name of a registered post processor or an array of registered names.
6336 * @return {Ext.ClassManager} this
6338 setDefaultPostprocessors: function(postprocessors) {
6339 this.defaultPostprocessors = Ext.Array.from(postprocessors);
6345 * Insert this post-processor at a specific position in the stack, optionally relative to
6346 * any existing post-processor
6348 * @param {String} name The post-processor name. Note that it needs to be registered with
6349 * {@link Ext.ClassManager#registerPostprocessor} before this
6350 * @param {String} offset The insertion position. Four possible values are:
6351 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6352 * @param {String} relativeName
6353 * @return {Ext.ClassManager} this
6355 setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6356 var defaultPostprocessors = this.defaultPostprocessors,
6359 if (typeof offset === 'string') {
6360 if (offset === 'first') {
6361 defaultPostprocessors.unshift(name);
6365 else if (offset === 'last') {
6366 defaultPostprocessors.push(name);
6371 offset = (offset === 'after') ? 1 : -1;
6374 index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6377 defaultPostprocessors.splice(Math.max(0, index + offset), 0, name);
6384 * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6385 * or class names. Expressions support wildcards:
6387 // returns ['Ext.window.Window']
6388 var window = Ext.ClassManager.getNamesByExpression('widget.window');
6390 // returns ['widget.panel', 'widget.window', ...]
6391 var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6393 // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6394 var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6396 * @param {String} expression
6397 * @return {Array} classNames
6400 getNamesByExpression: function(expression) {
6401 var nameToAliasesMap = this.maps.nameToAliases,
6403 name, alias, aliases, possibleName, regex, i, ln;
6406 if (typeof expression !== 'string' || expression.length < 1) {
6408 sourceClass: "Ext.ClassManager",
6409 sourceMethod: "getNamesByExpression",
6410 msg: "Expression " + expression + " is invalid, must be a non-empty string"
6415 if (expression.indexOf('*') !== -1) {
6416 expression = expression.replace(/\*/g, '(.*?)');
6417 regex = new RegExp('^' + expression + '$');
6419 for (name in nameToAliasesMap) {
6420 if (nameToAliasesMap.hasOwnProperty(name)) {
6421 aliases = nameToAliasesMap[name];
6423 if (name.search(regex) !== -1) {
6427 for (i = 0, ln = aliases.length; i < ln; i++) {
6430 if (alias.search(regex) !== -1) {
6440 possibleName = this.getNameByAlias(expression);
6443 names.push(possibleName);
6445 possibleName = this.getNameByAlternate(expression);
6448 names.push(possibleName);
6450 names.push(expression);
6459 Manager.registerPostprocessor('alias', function(name, cls, data) {
6460 var aliases = data.alias,
6461 widgetPrefix = 'widget.',
6464 if (!(aliases instanceof Array)) {
6465 aliases = [aliases];
6468 for (i = 0, ln = aliases.length; i < ln; i++) {
6472 if (typeof alias !== 'string') {
6475 sourceMethod: "define",
6476 msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
6481 this.setAlias(cls, alias);
6484 // This is ugly, will change to make use of parseNamespace for alias later on
6485 for (i = 0, ln = aliases.length; i < ln; i++) {
6488 if (alias.substring(0, widgetPrefix.length) === widgetPrefix) {
6489 // Only the first alias with 'widget.' prefix will be used for xtype
6490 cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length);
6496 Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
6497 fn.call(this, name, new cls(), data);
6501 Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
6502 var alternates = data.alternateClassName,
6505 if (!(alternates instanceof Array)) {
6506 alternates = [alternates];
6509 for (i = 0, ln = alternates.length; i < ln; i++) {
6510 alternate = alternates[i];
6513 if (typeof alternate !== 'string') {
6516 sourceMethod: "define",
6517 msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
6522 this.set(alternate, cls);
6526 Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
6530 * Convenient shorthand, see {@link Ext.ClassManager#instantiate}
6534 create: alias(Manager, 'instantiate'),
6538 * API to be stablized
6540 * @param {Mixed} item
6541 * @param {String} namespace
6543 factory: function(item, namespace) {
6544 if (item instanceof Array) {
6547 for (i = 0, ln = item.length; i < ln; i++) {
6548 item[i] = Ext.factory(item[i], namespace);
6554 var isString = (typeof item === 'string');
6556 if (isString || (item instanceof Object && item.constructor === Object)) {
6557 var name, config = {};
6563 name = item.className;
6565 delete config.className;
6568 if (namespace !== undefined && name.indexOf(namespace) === -1) {
6569 name = namespace + '.' + Ext.String.capitalize(name);
6572 return Ext.create(name, config);
6575 if (typeof item === 'function') {
6576 return Ext.create(item);
6583 * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
6585 var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
6586 var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
6592 widget: function(name) {
6593 var args = slice.call(arguments);
6594 args[0] = 'widget.' + name;
6596 return Manager.instantiateByAlias.apply(Manager, args);
6600 * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}
6602 * @method createByAlias
6604 createByAlias: alias(Manager, 'instantiateByAlias'),
6607 * Convenient shorthand for {@link Ext.ClassManager#create}, see detailed {@link Ext.Class explanation}
6611 define: alias(Manager, 'create'),
6614 * Convenient shorthand, see {@link Ext.ClassManager#getName}
6616 * @method getClassName
6618 getClassName: alias(Manager, 'getName'),
6622 * @param {Mixed} object
6624 getDisplayName: function(object) {
6625 if (object.displayName) {
6626 return object.displayName;
6629 if (object.$name && object.$class) {
6630 return Ext.getClassName(object.$class) + '#' + object.$name;
6633 if (object.$className) {
6634 return object.$className;
6641 * Convenient shorthand, see {@link Ext.ClassManager#getClass}
6643 * @method getClassName
6645 getClass: alias(Manager, 'getClass'),
6648 * Creates namespaces to be used for scoping variables and classes so that they are not global.
6649 * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
6651 Ext.namespace('Company', 'Company.data');
6653 // equivalent and preferable to the above syntax
6654 Ext.namespace('Company.data');
6656 Company.Widget = function() { ... };
6658 Company.data.CustomStore = function(config) { ... };
6660 * @param {String} namespace1
6661 * @param {String} namespace2
6662 * @param {String} etc
6663 * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
6669 namespace: alias(Manager, 'createNamespaces')
6672 Ext.createWidget = Ext.widget;
6675 * Convenient alias for {@link Ext#namespace Ext.namespace}
6679 Ext.ns = Ext.namespace;
6681 Class.registerPreprocessor('className', function(cls, data) {
6682 if (data.$className) {
6683 cls.$className = data.$className;
6685 cls.displayName = cls.$className;
6690 Class.setDefaultPreprocessorPosition('className', 'first');
6692 })(Ext.Class, Ext.Function.alias);
6695 * @author Jacky Nguyen <jacky@sencha.com>
6696 * @docauthor Jacky Nguyen <jacky@sencha.com>
6700 Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
6701 via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
6702 approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
6704 # Asynchronous Loading #
6708 + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
6710 + Best possible debugging experience: error messages come with the exact file name and line number
6713 + Dependencies need to be specified before-hand
6715 ### Method 1: Explicitly include what you need: ###
6718 Ext.require({String/Array} expressions);
6720 // Example: Single alias
6721 Ext.require('widget.window');
6723 // Example: Single class name
6724 Ext.require('Ext.window.Window');
6726 // Example: Multiple aliases / class names mix
6727 Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
6730 Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
6732 ### Method 2: Explicitly exclude what you don't need: ###
6734 // Syntax: Note that it must be in this chaining format.
6735 Ext.exclude({String/Array} expressions)
6736 .require({String/Array} expressions);
6738 // Include everything except Ext.data.*
6739 Ext.exclude('Ext.data.*').require('*');Â
6741 // Include all widgets except widget.checkbox*,
6742 // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
6743 Ext.exclude('widget.checkbox*').require('widget.*');
6745 # Synchronous Loading on Demand #
6748 + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
6752 + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
6753 + Must be from the same domain due to XHR restriction
6754 + Need a web server, same reason as above
6756 There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
6758 Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
6760 Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
6762 Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
6764 Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
6765 existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
6766 class and all its dependencies.
6768 # Hybrid Loading - The Best of Both Worlds #
6770 It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
6772 ### Step 1: Start writing your application using synchronous approach. Ext.Loader will automatically fetch all
6773 dependencies on demand as they're needed during run-time. For example: ###
6775 Ext.onReady(function(){
6776 var window = Ext.createWidget('window', {
6783 title: 'Hello Dialog',
6785 title: 'Navigation',
6800 ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
6802 [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
6804 [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
6806 Simply copy and paste the suggested code above `Ext.onReady`, i.e:
6808 Ext.require('Ext.window.Window');
6809 Ext.require('Ext.layout.container.Border');
6813 Everything should now load via asynchronous mode.
6817 It's important to note that dynamic loading should only be used during development on your local machines.
6818 During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
6819 the whole process of transitioning from / to between development / maintenance and production as easy as
6820 possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
6821 needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
6822 then include it on top of your application.
6824 This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
6830 (function(Manager, Class, flexSetter, alias) {
6834 isNonBrowser = typeof window === 'undefined',
6835 isNodeJS = isNonBrowser && (typeof require === 'function'),
6836 isPhantomJS = (typeof phantom !== 'undefined' && phantom.fs),
6838 dependencyProperties = ['extend', 'mixins', 'requires'],
6841 Loader = Ext.Loader = {
6845 documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
6848 * Flag indicating whether there are still files being loaded
6854 * Maintain the queue for all dependencies. Each item in the array is an object of the format:
6856 * requires: [...], // The required classes for this queue item
6857 * callback: function() { ... } // The function to execute when all classes specified in requires exist
6864 * Maintain the list of files that have already been handled so that they never get double-loaded
6870 * Maintain the list of listeners to execute when all required scripts are fully loaded
6876 * Contains optional dependencies to be loaded last
6879 optionalRequires: [],
6882 * Map of fully qualified class names to an array of dependent classes.
6898 hasFileLoadError: false,
6903 classNameToFilePathMap: {},
6906 * An array of class names to keep track of the dependency loading order.
6907 * This is not guaranteed to be the same everytime due to the asynchronous
6908 * nature of the Loader.
6921 * Whether or not to enable the dynamic dependency loading feature
6923 * @cfg {Boolean} enabled
6928 * @cfg {Boolean} disableCaching
6929 * Appends current timestamp to script files to prevent caching
6932 disableCaching: true,
6935 * @cfg {String} disableCachingParam
6936 * The get parameter name for the cache buster's timestamp.
6939 disableCachingParam: '_dc',
6942 * @cfg {Object} paths
6943 * The mapping from namespaces to file paths
6945 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
6946 // loaded from ./layout/Container.js
6948 'My': './src/my_own_folder' // My.layout.Container will be loaded from
6949 // ./src/my_own_folder/layout/Container.js
6951 * Note that all relative paths are relative to the current HTML document.
6952 * If not being specified, for example, <code>Other.awesome.Class</code>
6953 * will simply be loaded from <code>./Other/awesome/Class.js</code>
6961 * Set the configuration for the loader. This should be called right after ext-core.js
6962 * (or ext-core-debug.js) is included in the page, i.e:
6964 <script type="text/javascript" src="ext-core-debug.js"></script>
6965 <script type="text/javascript">
6966 Ext.Loader.setConfig({
6973 <script type="text/javascript">
6976 Ext.onReady(function() {
6977 // application code here
6981 * Refer to {@link Ext.Loader#configs} for the list of possible properties
6983 * @param {Object} config The config object to override the default values in {@link Ext.Loader#config}
6984 * @return {Ext.Loader} this
6987 setConfig: function(name, value) {
6988 if (Ext.isObject(name) && arguments.length === 1) {
6989 Ext.Object.merge(this.config, name);
6992 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
6999 * Get the config value corresponding to the specified name. If no name is given, will return the config object
7000 * @param {String} name The config property name
7001 * @return {Object/Mixed}
7003 getConfig: function(name) {
7005 return this.config[name];
7012 * Sets the path of a namespace.
7015 Ext.Loader.setPath('Ext', '.');
7017 * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
7018 * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
7019 * @return {Ext.Loader} this
7023 setPath: flexSetter(function(name, path) {
7027 path = require('fs').realpathSync(path);
7031 this.config.paths[name] = path;
7037 * Translates a className to a file path by adding the
7038 * the proper prefix and converting the .'s to /'s. For example:
7040 Ext.Loader.setPath('My', '/path/to/My');
7042 alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
7044 * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
7046 Ext.Loader.setPath({
7047 'My': '/path/to/lib',
7048 'My.awesome': '/other/path/for/awesome/stuff',
7049 'My.awesome.more': '/more/awesome/path'
7052 alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
7054 alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
7056 alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
7058 alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
7060 * @param {String} className
7061 * @return {String} path
7064 getPath: function(className) {
7066 paths = this.config.paths,
7067 prefix = this.getPrefix(className);
7069 if (prefix.length > 0) {
7070 if (prefix === className) {
7071 return paths[prefix];
7074 path = paths[prefix];
7075 className = className.substring(prefix.length + 1);
7078 if (path.length > 0) {
7082 return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7087 * @param {String} className
7089 getPrefix: function(className) {
7090 var paths = this.config.paths,
7091 prefix, deepestPrefix = '';
7093 if (paths.hasOwnProperty(className)) {
7097 for (prefix in paths) {
7098 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7099 if (prefix.length > deepestPrefix.length) {
7100 deepestPrefix = prefix;
7105 return deepestPrefix;
7109 * Refresh all items in the queue. If all dependencies for an item exist during looping,
7110 * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7114 refreshQueue: function() {
7115 var ln = this.queue.length,
7116 i, item, j, requires;
7119 this.triggerReady();
7123 for (i = 0; i < ln; i++) {
7124 item = this.queue[i];
7127 requires = item.requires;
7129 // Don't bother checking when the number of files loaded
7130 // is still less than the array length
7131 if (requires.length > this.numLoadedFiles) {
7138 if (Manager.isCreated(requires[j])) {
7139 // Take out from the queue
7140 requires.splice(j, 1);
7145 } while (j < requires.length);
7147 if (item.requires.length === 0) {
7148 this.queue.splice(i, 1);
7149 item.callback.call(item.scope);
7150 this.refreshQueue();
7160 * Inject a script element to document's head, call onLoad and onError accordingly
7163 injectScriptElement: function(url, onLoad, onError, scope) {
7164 var script = document.createElement('script'),
7166 onLoadFn = function() {
7167 me.cleanupScriptElement(script);
7170 onErrorFn = function() {
7171 me.cleanupScriptElement(script);
7172 onError.call(scope);
7175 script.type = 'text/javascript';
7177 script.onload = onLoadFn;
7178 script.onerror = onErrorFn;
7179 script.onreadystatechange = function() {
7180 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7185 this.documentHead.appendChild(script);
7193 cleanupScriptElement: function(script) {
7194 script.onload = null;
7195 script.onreadystatechange = null;
7196 script.onerror = null;
7202 * Load a script file, supports both asynchronous and synchronous approaches
7204 * @param {String} url
7205 * @param {Function} onLoad
7206 * @param {Scope} scope
7207 * @param {Boolean} synchronous
7210 loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7212 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7213 fileName = url.split('/').pop(),
7214 isCrossOriginRestricted = false,
7215 xhr, status, onScriptError;
7217 scope = scope || this;
7219 this.isLoading = true;
7222 onScriptError = function() {
7223 onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7226 if (!Ext.isReady && Ext.onDocumentReady) {
7227 Ext.onDocumentReady(function() {
7228 me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7232 this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7236 if (typeof XMLHttpRequest !== 'undefined') {
7237 xhr = new XMLHttpRequest();
7239 xhr = new ActiveXObject('Microsoft.XMLHTTP');
7243 xhr.open('GET', noCacheUrl, false);
7246 isCrossOriginRestricted = true;
7249 status = (xhr.status === 1223) ? 204 : xhr.status;
7251 if (!isCrossOriginRestricted) {
7252 isCrossOriginRestricted = (status === 0);
7255 if (isCrossOriginRestricted
7260 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7261 "being loaded from a different domain or from the local file system whereby cross origin " +
7262 "requests are not allowed due to security reasons. Use asynchronous loading with " +
7263 "Ext.require instead.", synchronous);
7265 else if (status >= 200 && status < 300
7270 // Firebug friendly, file names are still shown even though they're eval'ed code
7271 new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7276 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7277 "verify that the file exists. " +
7278 "XHR status code: " + status, synchronous);
7281 // Prevent potential IE memory leak
7287 * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
7288 * Can be chained with more `require` and `exclude` methods, eg:
7290 Ext.exclude('Ext.data.*').require('*');
7292 Ext.exclude('widget.button*').require('widget.*');
7294 * @param {Array} excludes
7295 * @return {Object} object contains `require` method for chaining
7298 exclude: function(excludes) {
7302 require: function(expressions, fn, scope) {
7303 return me.require(expressions, fn, scope, excludes);
7306 syncRequire: function(expressions, fn, scope) {
7307 return me.syncRequire(expressions, fn, scope, excludes);
7313 * Synchronously loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when finishes, within the optional scope. This method is aliased by {@link Ext#syncRequire} for convenience
7314 * @param {String/Array} expressions Can either be a string or an array of string
7315 * @param {Function} fn (Optional) The callback function
7316 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7317 * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
7320 syncRequire: function() {
7321 this.syncModeEnabled = true;
7322 this.require.apply(this, arguments);
7323 this.refreshQueue();
7324 this.syncModeEnabled = false;
7328 * Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
7329 * finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience
7330 * @param {String/Array} expressions Can either be a string or an array of string
7331 * @param {Function} fn (Optional) The callback function
7332 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7333 * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
7336 require: function(expressions, fn, scope, excludes) {
7337 var filePath, expression, exclude, className, excluded = {},
7338 excludedClassNames = [],
7339 possibleClassNames = [],
7340 possibleClassName, classNames = [],
7343 expressions = Ext.Array.from(expressions);
7344 excludes = Ext.Array.from(excludes);
7346 fn = fn || Ext.emptyFn;
7348 scope = scope || Ext.global;
7350 for (i = 0, ln = excludes.length; i < ln; i++) {
7351 exclude = excludes[i];
7353 if (typeof exclude === 'string' && exclude.length > 0) {
7354 excludedClassNames = Manager.getNamesByExpression(exclude);
7356 for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
7357 excluded[excludedClassNames[j]] = true;
7362 for (i = 0, ln = expressions.length; i < ln; i++) {
7363 expression = expressions[i];
7365 if (typeof expression === 'string' && expression.length > 0) {
7366 possibleClassNames = Manager.getNamesByExpression(expression);
7368 for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
7369 possibleClassName = possibleClassNames[j];
7371 if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
7372 Ext.Array.include(classNames, possibleClassName);
7378 // If the dynamic dependency feature is not being used, throw an error
7379 // if the dependencies are not defined
7380 if (!this.config.enabled) {
7381 if (classNames.length > 0) {
7383 sourceClass: "Ext.Loader",
7384 sourceMethod: "require",
7385 msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
7386 "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
7391 if (classNames.length === 0) {
7397 requires: classNames,
7402 classNames = classNames.slice();
7404 for (i = 0, ln = classNames.length; i < ln; i++) {
7405 className = classNames[i];
7407 if (!this.isFileLoaded.hasOwnProperty(className)) {
7408 this.isFileLoaded[className] = false;
7410 filePath = this.getPath(className);
7412 this.classNameToFilePathMap[className] = filePath;
7414 this.numPendingFiles++;
7421 // Temporary support for hammerjs
7423 var f = fs.open(filePath),
7428 line = f.readLine();
7429 if (line.length === 0) {
7439 this.onFileLoaded(className, filePath);
7442 return Manager.get(className);
7448 this.loadScriptFile(
7450 Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
7451 Ext.Function.pass(this.onFileLoadError, [className, filePath]),
7453 this.syncModeEnabled
7463 * @param {String} className
7464 * @param {String} filePath
7466 onFileLoaded: function(className, filePath) {
7467 this.numLoadedFiles++;
7469 this.isFileLoaded[className] = true;
7471 this.numPendingFiles--;
7473 if (this.numPendingFiles === 0) {
7474 this.refreshQueue();
7478 if (this.numPendingFiles <= 1) {
7479 window.status = "Finished loading all dependencies, onReady fired!";
7482 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
7487 if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
7488 var queue = this.queue,
7490 i, ln, j, subLn, missingClasses = [], missingPaths = [];
7492 for (i = 0, ln = queue.length; i < ln; i++) {
7493 requires = queue[i].requires;
7495 for (j = 0, subLn = requires.length; j < ln; j++) {
7496 if (this.isFileLoaded[requires[j]]) {
7497 missingClasses.push(requires[j]);
7502 if (missingClasses.length < 1) {
7506 missingClasses = Ext.Array.filter(missingClasses, function(item) {
7507 return !this.requiresMap.hasOwnProperty(item);
7510 for (i = 0,ln = missingClasses.length; i < ln; i++) {
7511 missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
7515 sourceClass: "Ext.Loader",
7516 sourceMethod: "onFileLoaded",
7517 msg: "The following classes are not declared even if their files have been " +
7518 "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
7519 "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
7528 onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
7529 this.numPendingFiles--;
7530 this.hasFileLoadError = true;
7534 sourceClass: "Ext.Loader",
7535 classToLoad: className,
7537 loadingType: isSynchronous ? 'synchronous' : 'async',
7546 addOptionalRequires: function(requires) {
7547 var optionalRequires = this.optionalRequires,
7550 requires = Ext.Array.from(requires);
7552 for (i = 0, ln = requires.length; i < ln; i++) {
7553 require = requires[i];
7555 Ext.Array.include(optionalRequires, require);
7564 triggerReady: function(force) {
7565 var readyListeners = this.readyListeners,
7566 optionalRequires, listener;
7568 if (this.isLoading || force) {
7569 this.isLoading = false;
7571 if (this.optionalRequires.length) {
7572 // Clone then empty the array to eliminate potential recursive loop issue
7573 optionalRequires = Ext.Array.clone(this.optionalRequires);
7575 // Empty the original array
7576 this.optionalRequires.length = 0;
7578 this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
7582 while (readyListeners.length) {
7583 listener = readyListeners.shift();
7584 listener.fn.call(listener.scope);
7586 if (this.isLoading) {
7596 * Add a new listener to be executed when all required scripts are fully loaded
7598 * @param {Function} fn The function callback to be executed
7599 * @param {Object} scope The execution scope (<code>this</code>) of the callback function
7600 * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
7602 onReady: function(fn, scope, withDomReady, options) {
7605 if (withDomReady !== false && Ext.onDocumentReady) {
7609 Ext.onDocumentReady(oldFn, scope, options);
7613 if (!this.isLoading) {
7617 this.readyListeners.push({
7626 * @param {String} className
7628 historyPush: function(className) {
7629 if (className && this.isFileLoaded.hasOwnProperty(className)) {
7630 Ext.Array.include(this.history, className);
7638 * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
7639 * {@link Ext.Loader} for examples.
7643 Ext.require = alias(Loader, 'require');
7646 * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
7649 * @method syncRequire
7651 Ext.syncRequire = alias(Loader, 'syncRequire');
7654 * Convenient shortcut to {@link Ext.Loader#exclude}
7658 Ext.exclude = alias(Loader, 'exclude');
7664 Ext.onReady = function(fn, scope, options) {
7665 Loader.onReady(fn, scope, true, options);
7668 Class.registerPreprocessor('loader', function(cls, data, continueFn) {
7671 className = Manager.getName(cls),
7672 i, j, ln, subLn, value, propertyName, propertyValue;
7675 Basically loop through the dependencyProperties, look for string class names and push
7676 them into a stack, regardless of whether the property's value is a string, array or object. For example:
7678 extend: 'Ext.MyClass',
7679 requires: ['Ext.some.OtherClass'],
7681 observable: 'Ext.util.Observable';
7684 which will later be transformed into:
7686 extend: Ext.MyClass,
7687 requires: [Ext.some.OtherClass],
7689 observable: Ext.util.Observable;
7694 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7695 propertyName = dependencyProperties[i];
7697 if (data.hasOwnProperty(propertyName)) {
7698 propertyValue = data[propertyName];
7700 if (typeof propertyValue === 'string') {
7701 dependencies.push(propertyValue);
7703 else if (propertyValue instanceof Array) {
7704 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
7705 value = propertyValue[j];
7707 if (typeof value === 'string') {
7708 dependencies.push(value);
7713 for (j in propertyValue) {
7714 if (propertyValue.hasOwnProperty(j)) {
7715 value = propertyValue[j];
7717 if (typeof value === 'string') {
7718 dependencies.push(value);
7726 if (dependencies.length === 0) {
7727 // Loader.historyPush(className);
7732 var deadlockPath = [],
7733 requiresMap = Loader.requiresMap,
7737 Automatically detect deadlocks before-hand,
7738 will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
7740 - A extends B, then B extends A
7741 - A requires B, B requires C, then C requires A
7743 The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
7744 no matter how deep the path is.
7748 requiresMap[className] = dependencies;
7750 detectDeadlock = function(cls) {
7751 deadlockPath.push(cls);
7753 if (requiresMap[cls]) {
7754 if (Ext.Array.contains(requiresMap[cls], className)) {
7756 sourceClass: "Ext.Loader",
7757 msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
7758 deadlockPath[1] + "' " + "mutually require each other. Path: " +
7759 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
7763 for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
7764 detectDeadlock(requiresMap[cls][i]);
7769 detectDeadlock(className);
7774 Loader.require(dependencies, function() {
7775 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7776 propertyName = dependencyProperties[i];
7778 if (data.hasOwnProperty(propertyName)) {
7779 propertyValue = data[propertyName];
7781 if (typeof propertyValue === 'string') {
7782 data[propertyName] = Manager.get(propertyValue);
7784 else if (propertyValue instanceof Array) {
7785 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
7786 value = propertyValue[j];
7788 if (typeof value === 'string') {
7789 data[propertyName][j] = Manager.get(value);
7794 for (var k in propertyValue) {
7795 if (propertyValue.hasOwnProperty(k)) {
7796 value = propertyValue[k];
7798 if (typeof value === 'string') {
7799 data[propertyName][k] = Manager.get(value);
7807 continueFn.call(me, cls, data);
7813 Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
7815 Manager.registerPostprocessor('uses', function(name, cls, data) {
7816 var uses = Ext.Array.from(data.uses),
7820 for (i = 0, ln = uses.length; i < ln; i++) {
7823 if (typeof item === 'string') {
7828 Loader.addOptionalRequires(items);
7831 Manager.setDefaultPostprocessorPosition('uses', 'last');
7833 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
7840 A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
7841 errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
7842 uses the Ext 4 class system, the Error class can automatically add the source class and method from which
7843 the error was raised. It also includes logic to automatically log the eroor to the console, if available,
7844 with additional metadata about the error. In all cases, the error will always be thrown at the end so that
7845 execution will halt.
7847 Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
7848 handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
7849 although in a real application it's usually a better idea to override the handling function and perform
7850 logging or some other method of reporting the errors in a way that is meaningful to the application.
7852 At its simplest you can simply raise an error as a simple string from within any code:
7856 Ext.Error.raise('Something bad happened!');
7858 If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
7859 displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
7860 additional metadata about the error being raised. The {@link #raise} method can also take a config object.
7861 In this form the `msg` attribute becomes the error description, and any other data added to the config gets
7862 added to the error object and, if the console is available, logged to the console for inspection.
7866 Ext.define('Ext.Foo', {
7867 doSomething: function(option){
7868 if (someCondition === false) {
7870 msg: 'You cannot do that!',
7871 option: option, // whatever was passed into the method
7872 'error code': 100 // other arbitrary info
7878 If a console is available (that supports the `console.dir` function) you'll see console output like:
7880 An error was raised with the following data:
7881 option: Object { foo: "bar"}
7884 msg: "You cannot do that!"
7885 sourceClass: "Ext.Foo"
7886 sourceMethod: "doSomething"
7888 uncaught exception: You cannot do that!
7890 As you can see, the error will report exactly where it was raised and will include as much information as the
7891 raising code can usefully provide.
7893 If you want to handle all application errors globally you can simply override the static {@link handle} method
7894 and provide whatever handling logic you need. If the method returns true then the error is considered handled
7895 and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
7899 Ext.Error.handle = function(err) {
7900 if (err.someProperty == 'NotReallyAnError') {
7901 // maybe log something to the application here if applicable
7904 // any non-true return value (including none) will cause the error to be thrown
7907 * Create a new Error object
7908 * @param {Object} config The config object
7910 * @author Brian Moeskau <brian@sencha.com>
7911 * @docauthor Brian Moeskau <brian@sencha.com>
7913 Ext.Error = Ext.extend(Error, {
7917 Static flag that can be used to globally disable error reporting to the browser if set to true
7918 (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
7919 and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
7920 be preferable to supply a custom error {@link #handle handling} function instead.
7924 Ext.Error.ignore = true;
7933 Static flag that can be used to globally control error notification to the user. Unlike
7934 Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
7935 set to false to disable the alert notification (default is true for IE6 and IE7).
7937 Only the first error will generate an alert. Internally this flag is set to false when the
7938 first error occurs prior to displaying the alert.
7940 This flag is not used in a release build.
7944 Ext.Error.notify = false;
7949 //notify: Ext.isIE6 || Ext.isIE7,
7952 Raise an error that can include additional data and supports automatic console logging if available.
7953 You can pass a string error message or an object with the `msg` attribute which will be used as the
7954 error message. The object can contain any other name-value attributes (or objects) to be logged
7955 along with the error.
7957 Note that after displaying the error message a JavaScript error will ultimately be thrown so that
7958 execution will halt.
7962 Ext.Error.raise('A simple string error message');
7966 Ext.define('Ext.Foo', {
7967 doSomething: function(option){
7968 if (someCondition === false) {
7970 msg: 'You cannot do that!',
7971 option: option, // whatever was passed into the method
7972 'error code': 100 // other arbitrary info
7977 * @param {String/Object} err The error message string, or an object containing the
7978 * attribute "msg" that will be used as the error message. Any other data included in
7979 * the object will also be logged to the browser console, if available.
7983 raise: function(err){
7985 if (Ext.isString(err)) {
7989 var method = this.raise.caller;
7993 err.sourceMethod = method.$name;
7995 if (method.$owner) {
7996 err.sourceClass = method.$owner.$className;
8000 if (Ext.Error.handle(err) !== true) {
8001 var msg = Ext.Error.prototype.toString.call(err);
8010 throw new Ext.Error(err);
8015 Globally handle any Ext errors that may be raised, optionally providing custom logic to
8016 handle different errors individually. Return true from the function to bypass throwing the
8017 error to the browser, otherwise the error will be thrown and execution will halt.
8021 Ext.Error.handle = function(err) {
8022 if (err.someProperty == 'NotReallyAnError') {
8023 // maybe log something to the application here if applicable
8026 // any non-true return value (including none) will cause the error to be thrown
8029 * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes
8030 * that were originally raised with it, plus properties about the method and class from which
8031 * the error originated (if raised from a class that uses the Ext 4 class system).
8036 return Ext.Error.ignore;
8040 // This is the standard property that is the name of the constructor.
8045 * @param {String/Object} config The error message string, or an object containing the
8046 * attribute "msg" that will be used as the error message. Any other data included in
8047 * the object will be applied to the error instance and logged to the browser console, if available.
8049 constructor: function(config){
8050 if (Ext.isString(config)) {
8051 config = { msg: config };
8056 Ext.apply(me, config);
8058 me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
8059 // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
8063 Provides a custom string representation of the error object. This is an override of the base JavaScript
8064 `Object.toString` method, which is useful so that when logged to the browser console, an error object will
8065 be displayed with a useful message instead of `[object Object]`, the default `toString` result.
8067 The default implementation will include the error message along with the raising class and method, if available,
8068 but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
8069 a particular error instance, if you want to provide a custom description that will show up in the console.
8071 * @return {String} The error message. If raised from within the Ext 4 class system, the error message
8072 * will also include the raising class and method names, if available.
8074 toString: function(){
8076 className = me.className ? me.className : '',
8077 methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
8078 msg = me.msg || '(No description provided)';
8080 return className + methodName + msg;
8085 * This mechanism is used to notify the user of the first error encountered on the page. This
8086 * was previously internal to Ext.Error.raise and is a desirable feature since errors often
8087 * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
8088 * where exceptions are handled in a try/catch.
8092 var prevOnError, timer, errors = 0,
8093 extraordinarilyBad = /(out of stack)|(too much recursion)|(stack overflow)|(out of memory)/i,
8096 if (typeof window === 'undefined') {
8097 return; // build system or some such environment...
8100 // This method is called to notify the user of the current error status.
8101 function notify () {
8102 var counters = Ext.log.counters,
8103 supports = Ext.supports,
8104 hasOnError = supports && supports.WindowOnError; // TODO - timing
8106 // Put log counters to the status bar (for most browsers):
8107 if (counters && (counters.error + counters.warn + counters.info + counters.log)) {
8108 var msg = [ 'Logged Errors:',counters.error, 'Warnings:',counters.warn,
8109 'Info:',counters.info, 'Log:',counters.log].join(' ');
8111 msg = '*** Errors: ' + errors + ' - ' + msg;
8112 } else if (counters.error) {
8118 // Display an alert on the first error:
8119 if (!Ext.isDefined(Ext.Error.notify)) {
8120 Ext.Error.notify = Ext.isIE6 || Ext.isIE7; // TODO - timing
8122 if (Ext.Error.notify && (hasOnError ? errors : (counters && counters.error))) {
8123 Ext.Error.notify = false;
8126 win.clearInterval(timer); // ticks can queue up so stop...
8130 alert('Unhandled error on page: See console or log');
8135 // Sets up polling loop. This is the only way to know about errors in some browsers
8136 // (Opera/Safari) and is the only way to update the status bar for warnings and other
8139 timer = win.setInterval(notify, 1000);
8142 // window.onerror is ideal (esp in IE) because you get full context. This is harmless
8143 // otherwise (never called) which is good because you cannot feature detect it.
8144 prevOnError = win.onerror || Ext.emptyFn;
8145 win.onerror = function (message) {
8148 if (!extraordinarilyBad.test(message)) {
8149 // too much recursion + our alert right now = crash IE
8150 // our polling loop will pick it up even if we don't alert now
8154 return prevOnError.apply(this, arguments);