3 This file is part of Ext JS 4
5 Copyright (c) 2011 Sencha Inc
7 Contact: http://www.sencha.com/contact
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file. Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
21 objectPrototype = Object.prototype,
22 toString = objectPrototype.toString,
24 enumerablesTest = { toString: 1 },
27 if (typeof Ext === 'undefined') {
33 for (i in enumerablesTest) {
38 enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
39 'toLocaleString', 'toString', 'constructor'];
43 * An array containing extra enumerables for old browsers
46 Ext.enumerables = enumerables;
49 * Copies all the properties of config to the specified object.
50 * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
51 * {@link Ext.Object#merge} instead.
52 * @param {Object} object The receiver of the properties
53 * @param {Object} config The source of the properties
54 * @param {Object} defaults A different object that will also be applied for default values
55 * @return {Object} returns obj
57 Ext.apply = function(object, config, defaults) {
59 Ext.apply(object, defaults);
62 if (object && config && typeof config === 'object') {
66 object[i] = config[i];
70 for (j = enumerables.length; j--;) {
72 if (config.hasOwnProperty(k)) {
73 object[k] = config[k];
82 Ext.buildSettings = Ext.apply({
85 }, Ext.buildSettings || {});
89 * A reusable empty function
91 emptyFn: function() {},
93 baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
96 * Copies all the properties of config to object if they don't already exist.
97 * @param {Object} object The receiver of the properties
98 * @param {Object} config The source of the properties
99 * @return {Object} returns obj
101 applyIf: function(object, config) {
105 for (property in config) {
106 if (object[property] === undefined) {
107 object[property] = config[property];
116 * Iterates either an array or an object. This method delegates to
117 * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
119 * @param {Object/Array} object The object or array to be iterated.
120 * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
121 * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
122 * type that is being iterated.
123 * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
124 * Defaults to the object being iterated itself.
127 iterate: function(object, fn, scope) {
128 if (Ext.isEmpty(object)) {
132 if (scope === undefined) {
136 if (Ext.isIterable(object)) {
137 Ext.Array.each.call(Ext.Array, object, fn, scope);
140 Ext.Object.each.call(Ext.Object, object, fn, scope);
148 * This method deprecated. Use {@link Ext#define Ext.define} instead.
150 * @param {Function} superclass
151 * @param {Object} overrides
152 * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
153 * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
157 var objectConstructor = objectPrototype.constructor,
158 inlineOverrides = function(o) {
160 if (!o.hasOwnProperty(m)) {
167 return function(subclass, superclass, overrides) {
168 // First we check if the user passed in just the superClass with overrides
169 if (Ext.isObject(superclass)) {
170 overrides = superclass;
171 superclass = subclass;
172 subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
173 superclass.apply(this, arguments);
180 sourceMethod: 'extend',
181 msg: 'Attempting to extend from a class which has not been loaded on the page.'
185 // We create a new temporary class
186 var F = function() {},
187 subclassProto, superclassProto = superclass.prototype;
189 F.prototype = superclassProto;
190 subclassProto = subclass.prototype = new F();
191 subclassProto.constructor = subclass;
192 subclass.superclass = superclassProto;
194 if (superclassProto.constructor === objectConstructor) {
195 superclassProto.constructor = superclass;
198 subclass.override = function(overrides) {
199 Ext.override(subclass, overrides);
202 subclassProto.override = inlineOverrides;
203 subclassProto.proto = subclassProto;
205 subclass.override(overrides);
206 subclass.extend = function(o) {
207 return Ext.extend(subclass, o);
215 * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
217 Ext.define('My.cool.Class', {
223 Ext.override(My.cool.Class, {
225 alert('About to say...');
227 this.callOverridden();
231 var cool = new My.cool.Class();
232 cool.sayHi(); // alerts 'About to say...'
235 * Please note that `this.callOverridden()` only works if the class was previously
236 * created with {@link Ext#define)
238 * @param {Object} cls The class to override
239 * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
240 * containing one or more methods.
244 override: function(cls, overrides) {
245 if (cls.prototype.$className) {
246 return cls.override(overrides);
249 Ext.apply(cls.prototype, overrides);
254 // A full set of static methods to do type checking
258 * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
259 * value (second argument) otherwise.
261 * @param {Mixed} value The value to test
262 * @param {Mixed} defaultValue The value to return if the original value is empty
263 * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
264 * @return {Mixed} value, if non-empty, else defaultValue
266 valueFrom: function(value, defaultValue, allowBlank){
267 return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
271 * Returns the type of the given variable in string format. List of possible values are:
273 * - `undefined`: If the given value is `undefined`
274 * - `null`: If the given value is `null`
275 * - `string`: If the given value is a string
276 * - `number`: If the given value is a number
277 * - `boolean`: If the given value is a boolean value
278 * - `date`: If the given value is a `Date` object
279 * - `function`: If the given value is a function reference
280 * - `object`: If the given value is an object
281 * - `array`: If the given value is an array
282 * - `regexp`: If the given value is a regular expression
283 * - `element`: If the given value is a DOM Element
284 * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
285 * - `whitespace`: If the given value is a DOM text node and contains only whitespace
287 * @param {Mixed} value
291 typeOf: function(value) {
292 if (value === null) {
296 var type = typeof value;
298 if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
302 var typeToString = toString.call(value);
304 switch(typeToString) {
305 case '[object Array]':
307 case '[object Date]':
309 case '[object Boolean]':
311 case '[object Number]':
313 case '[object RegExp]':
317 if (type === 'function') {
321 if (type === 'object') {
322 if (value.nodeType !== undefined) {
323 if (value.nodeType === 3) {
324 return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
336 sourceMethod: 'typeOf',
337 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
342 * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
346 * - a zero-length array
347 * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
349 * @param {Mixed} value The value to test
350 * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
354 isEmpty: function(value, allowEmptyString) {
355 return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
359 * Returns true if the passed value is a JavaScript Array, false otherwise.
361 * @param {Mixed} target The target to test
365 isArray: ('isArray' in Array) ? Array.isArray : function(value) {
366 return toString.call(value) === '[object Array]';
370 * Returns true if the passed value is a JavaScript Date object, false otherwise.
371 * @param {Object} object The object to test
374 isDate: function(value) {
375 return toString.call(value) === '[object Date]';
379 * Returns true if the passed value is a JavaScript Object, false otherwise.
380 * @param {Mixed} value The value to test
384 isObject: (toString.call(null) === '[object Object]') ?
386 // check ownerDocument here as well to exclude DOM nodes
387 return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
390 return toString.call(value) === '[object Object]';
394 * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
395 * @param {Mixed} value The value to test
398 isPrimitive: function(value) {
399 var type = typeof value;
401 return type === 'string' || type === 'number' || type === 'boolean';
405 * Returns true if the passed value is a JavaScript Function, false otherwise.
406 * @param {Mixed} value The value to test
411 // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
412 // Object.prorotype.toString (slower)
413 (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
414 return toString.call(value) === '[object Function]';
415 } : function(value) {
416 return typeof value === 'function';
420 * Returns true if the passed value is a number. Returns false for non-finite numbers.
421 * @param {Mixed} value The value to test
424 isNumber: function(value) {
425 return typeof value === 'number' && isFinite(value);
429 * Validates that a value is numeric.
430 * @param {Mixed} value Examples: 1, '1', '2.34'
431 * @return {Boolean} True if numeric, false otherwise
433 isNumeric: function(value) {
434 return !isNaN(parseFloat(value)) && isFinite(value);
438 * Returns true if the passed value is a string.
439 * @param {Mixed} value The value to test
442 isString: function(value) {
443 return typeof value === 'string';
447 * Returns true if the passed value is a boolean.
449 * @param {Mixed} value The value to test
452 isBoolean: function(value) {
453 return typeof value === 'boolean';
457 * Returns true if the passed value is an HTMLElement
458 * @param {Mixed} value The value to test
461 isElement: function(value) {
462 return value ? value.nodeType === 1 : false;
466 * Returns true if the passed value is a TextNode
467 * @param {Mixed} value The value to test
470 isTextNode: function(value) {
471 return value ? value.nodeName === "#text" : false;
475 * Returns true if the passed value is defined.
476 * @param {Mixed} value The value to test
479 isDefined: function(value) {
480 return typeof value !== 'undefined';
484 * Returns true if the passed value is iterable, false otherwise
485 * @param {Mixed} value The value to test
488 isIterable: function(value) {
489 return (value && typeof value !== 'string') ? value.length !== undefined : false;
496 * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
497 * @param {Mixed} item The variable to clone
498 * @return {Mixed} clone
500 clone: function(item) {
501 if (item === null || item === undefined) {
506 // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
508 if (item.nodeType && item.cloneNode) {
509 return item.cloneNode(true);
512 var type = toString.call(item);
515 if (type === '[object Date]') {
516 return new Date(item.getTime());
519 var i, j, k, clone, key;
522 if (type === '[object Array]') {
528 clone[i] = Ext.clone(item[i]);
532 else if (type === '[object Object]' && item.constructor === Object) {
536 clone[key] = Ext.clone(item[key]);
540 for (j = enumerables.length; j--;) {
547 return clone || item;
552 * Generate a unique reference of Ext in the global scope, useful for sandboxing
554 getUniqueGlobalNamespace: function() {
555 var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
557 if (uniqueGlobalNamespace === undefined) {
561 uniqueGlobalNamespace = 'ExtBox' + (++i);
562 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
564 Ext.global[uniqueGlobalNamespace] = Ext;
565 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
568 return uniqueGlobalNamespace;
574 functionFactory: function() {
575 var args = Array.prototype.slice.call(arguments);
577 if (args.length > 0) {
578 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
579 args[args.length - 1];
582 return Function.prototype.constructor.apply(Function.prototype, args);
587 * Old alias to {@link Ext#typeOf}
588 * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
592 Ext.type = Ext.typeOf;
597 * @author Jacky Nguyen <jacky@sencha.com>
598 * @docauthor Jacky Nguyen <jacky@sencha.com>
601 * A utility class that wrap around a string version number and provide convenient
602 * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
604 var version = new Ext.Version('1.0.2beta');
605 console.log("Version is " + version); // Version is 1.0.2beta
607 console.log(version.getMajor()); // 1
608 console.log(version.getMinor()); // 0
609 console.log(version.getPatch()); // 2
610 console.log(version.getBuild()); // 0
611 console.log(version.getRelease()); // beta
613 console.log(version.isGreaterThan('1.0.1')); // True
614 console.log(version.isGreaterThan('1.0.2alpha')); // True
615 console.log(version.isGreaterThan('1.0.2RC')); // False
616 console.log(version.isGreaterThan('1.0.2')); // False
617 console.log(version.isLessThan('1.0.2')); // True
619 console.log(version.match(1.0)); // True
620 console.log(version.match('1.0.2')); // True
626 // Current core version
627 var version = '4.0.2', Version;
628 Ext.Version = Version = Ext.extend(Object, {
631 * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
632 * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
633 * @return {Ext.Version} this
635 constructor: function(version) {
636 var parts, releaseStartIndex;
638 if (version instanceof Version) {
642 this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
644 releaseStartIndex = this.version.search(/([^\d\.])/);
646 if (releaseStartIndex !== -1) {
647 this.release = this.version.substr(releaseStartIndex, version.length);
648 this.shortVersion = this.version.substr(0, releaseStartIndex);
651 this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
653 parts = this.version.split('.');
655 this.major = parseInt(parts.shift() || 0, 10);
656 this.minor = parseInt(parts.shift() || 0, 10);
657 this.patch = parseInt(parts.shift() || 0, 10);
658 this.build = parseInt(parts.shift() || 0, 10);
664 * Override the native toString method
666 * @return {String} version
668 toString: function() {
673 * Override the native valueOf method
675 * @return {String} version
677 valueOf: function() {
682 * Returns the major component value
683 * @return {Number} major
685 getMajor: function() {
686 return this.major || 0;
690 * Returns the minor component value
691 * @return {Number} minor
693 getMinor: function() {
694 return this.minor || 0;
698 * Returns the patch component value
699 * @return {Number} patch
701 getPatch: function() {
702 return this.patch || 0;
706 * Returns the build component value
707 * @return {Number} build
709 getBuild: function() {
710 return this.build || 0;
714 * Returns the release component value
715 * @return {Number} release
717 getRelease: function() {
718 return this.release || '';
722 * Returns whether this version if greater than the supplied argument
723 * @param {String/Number} target The version to compare with
724 * @return {Boolean} True if this version if greater than the target, false otherwise
726 isGreaterThan: function(target) {
727 return Version.compare(this.version, target) === 1;
731 * Returns whether this version if smaller than the supplied argument
732 * @param {String/Number} target The version to compare with
733 * @return {Boolean} True if this version if smaller than the target, false otherwise
735 isLessThan: function(target) {
736 return Version.compare(this.version, target) === -1;
740 * Returns whether this version equals to the supplied argument
741 * @param {String/Number} target The version to compare with
742 * @return {Boolean} True if this version equals to the target, false otherwise
744 equals: function(target) {
745 return Version.compare(this.version, target) === 0;
749 * Returns whether this version matches the supplied argument. Example:
751 * var version = new Ext.Version('1.0.2beta');
752 * console.log(version.match(1)); // True
753 * console.log(version.match(1.0)); // True
754 * console.log(version.match('1.0.2')); // True
755 * console.log(version.match('1.0.2RC')); // False
757 * @param {String/Number} target The version to compare with
758 * @return {Boolean} True if this version matches the target, false otherwise
760 match: function(target) {
761 target = String(target);
762 return this.version.substr(0, target.length) === target;
766 * Returns this format: [major, minor, patch, build, release]. Useful for comparison
769 toArray: function() {
770 return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
774 * Returns shortVersion version without dots and release
777 getShortVersion: function() {
778 return this.shortVersion;
797 * Converts a version component to a comparable value
800 * @param {Mixed} value The value to convert
803 getComponentValue: function(value) {
804 return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
808 * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
809 * they are handled in the following order:
810 * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
813 * @param {String} current The current version to compare to
814 * @param {String} target The target version to compare to
815 * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
817 compare: function(current, target) {
818 var currentValue, targetValue, i;
820 current = new Version(current).toArray();
821 target = new Version(target).toArray();
823 for (i = 0; i < Math.max(current.length, target.length); i++) {
824 currentValue = this.getComponentValue(current[i]);
825 targetValue = this.getComponentValue(target[i]);
827 if (currentValue < targetValue) {
829 } else if (currentValue > targetValue) {
847 lastRegisteredVersion: null,
850 * Set version number for the given package name.
852 * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
853 * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
856 setVersion: function(packageName, version) {
857 Ext.versions[packageName] = new Version(version);
858 Ext.lastRegisteredVersion = Ext.versions[packageName];
864 * Get the version number of the supplied package name; will return the last registered version
865 * (last Ext.setVersion call) if there's no package name given.
867 * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
868 * @return {Ext.Version} The version
870 getVersion: function(packageName) {
871 if (packageName === undefined) {
872 return Ext.lastRegisteredVersion;
875 return Ext.versions[packageName];
879 * Create a closure for deprecated code.
881 // This means Ext.oldMethod is only supported in 4.0.0beta and older.
882 // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
883 // the closure will not be invoked
884 Ext.deprecate('extjs', '4.0.0beta', function() {
885 Ext.oldMethod = Ext.newMethod;
890 * @param {String} packageName The package name
891 * @param {String} since The last version before it's deprecated
892 * @param {Function} closure The callback function to be executed with the specified version is less than the current version
893 * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
896 deprecate: function(packageName, since, closure, scope) {
897 if (Version.compare(Ext.getVersion(packageName), since) < 1) {
901 }); // End Versioning
903 Ext.setVersion('core', version);
910 * A collection of useful static methods to deal with strings
915 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,
917 formatRe: /\{(\d+)\}/g,
918 escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
921 * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
922 * @param {String} value The string to encode
923 * @return {String} The encoded text
926 htmlEncode: (function() {
932 }, keys = [], p, regex;
934 for (p in entities) {
938 regex = new RegExp('(' + keys.join('|') + ')', 'g');
940 return function(value) {
941 return (!value) ? value : String(value).replace(regex, function(match, capture) {
942 return entities[capture];
948 * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
949 * @param {String} value The string to decode
950 * @return {String} The decoded text
953 htmlDecode: (function() {
959 }, keys = [], p, regex;
961 for (p in entities) {
965 regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
967 return function(value) {
968 return (!value) ? value : String(value).replace(regex, function(match, capture) {
969 if (capture in entities) {
970 return entities[capture];
972 return String.fromCharCode(parseInt(capture.substr(2), 10));
979 * Appends content to the query string of a URL, handling logic for whether to place
980 * a question mark or ampersand.
981 * @param {String} url The URL to append to.
982 * @param {String} string The content to append to the URL.
983 * @return (String) The resulting URL
985 urlAppend : function(url, string) {
986 if (!Ext.isEmpty(string)) {
987 return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
994 * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
997 alert('-' + s + '-'); //alerts "- foo bar -"
998 alert('-' + Ext.String.trim(s) + '-'); //alerts "-foo bar-"
1000 * @param {String} string The string to escape
1001 * @return {String} The trimmed string
1003 trim: function(string) {
1004 return string.replace(Ext.String.trimRegex, "");
1008 * Capitalize the given string
1009 * @param {String} string
1012 capitalize: function(string) {
1013 return string.charAt(0).toUpperCase() + string.substr(1);
1017 * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1018 * @param {String} value The string to truncate
1019 * @param {Number} length The maximum length to allow before truncating
1020 * @param {Boolean} word True to try to find a common word break
1021 * @return {String} The converted text
1023 ellipsis: function(value, len, word) {
1024 if (value && value.length > len) {
1026 var vs = value.substr(0, len - 2),
1027 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1028 if (index !== -1 && index >= (len - 15)) {
1029 return vs.substr(0, index) + "...";
1032 return value.substr(0, len - 3) + "...";
1038 * Escapes the passed string for use in a regular expression
1039 * @param {String} string
1042 escapeRegex: function(string) {
1043 return string.replace(Ext.String.escapeRegexRe, "\\$1");
1047 * Escapes the passed string for ' and \
1048 * @param {String} string The string to escape
1049 * @return {String} The escaped string
1051 escape: function(string) {
1052 return string.replace(Ext.String.escapeRe, "\\$1");
1056 * Utility function that allows you to easily switch a string between two alternating values. The passed value
1057 * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
1058 * they are already different, the first value passed in is returned. Note that this method returns the new value
1059 * but does not change the current string.
1061 // alternate sort directions
1062 sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1064 // instead of conditional logic:
1065 sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1067 * @param {String} string The current string
1068 * @param {String} value The value to compare to the current string
1069 * @param {String} other The new value to use if the string already equals the first value passed in
1070 * @return {String} The new value
1072 toggle: function(string, value, other) {
1073 return string === value ? other : value;
1077 * Pads the left side of a string with a specified character. This is especially useful
1078 * for normalizing number and date strings. Example usage:
1081 var s = Ext.String.leftPad('123', 5, '0');
1082 // s now contains the string: '00123'
1084 * @param {String} string The original string
1085 * @param {Number} size The total length of the output string
1086 * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1087 * @return {String} The padded string
1089 leftPad: function(string, size, character) {
1090 var result = String(string);
1091 character = character || " ";
1092 while (result.length < size) {
1093 result = character + result;
1099 * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
1100 * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
1102 var cls = 'my-class', text = 'Some text';
1103 var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
1104 // s now contains the string: '<div class="my-class">Some text</div>'
1106 * @param {String} string The tokenized string to be formatted
1107 * @param {String} value1 The value to replace token {0}
1108 * @param {String} value2 Etc...
1109 * @return {String} The formatted string
1111 format: function(format) {
1112 var args = Ext.Array.toArray(arguments, 1);
1113 return format.replace(Ext.String.formatRe, function(m, i) {
1122 * A collection of useful static methods to deal with numbers
1128 var isToFixedBroken = (0.9).toFixed() !== '1';
1132 * Checks whether or not the passed number is within a desired range. If the number is already within the
1133 * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1134 * exceeded. Note that this method returns the constrained value but does not change the current number.
1135 * @param {Number} number The number to check
1136 * @param {Number} min The minimum number in the range
1137 * @param {Number} max The maximum number in the range
1138 * @return {Number} The constrained value if outside the range, otherwise the current value
1140 constrain: function(number, min, max) {
1141 number = parseFloat(number);
1144 number = Math.max(number, min);
1147 number = Math.min(number, max);
1153 * Snaps the passed number between stopping points based upon a passed increment value.
1154 * @param {Number} value The unsnapped value.
1155 * @param {Number} increment The increment by which the value must move.
1156 * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
1157 * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
1158 * @return {Number} The value of the nearest snap target.
1160 snap : function(value, increment, minValue, maxValue) {
1161 var newValue = value,
1164 if (!(increment && value)) {
1167 m = value % increment;
1170 if (m * 2 >= increment) {
1171 newValue += increment;
1172 } else if (m * 2 < -increment) {
1173 newValue -= increment;
1176 return Ext.Number.constrain(newValue, minValue, maxValue);
1180 * Formats a number using fixed-point notation
1181 * @param {Number} value The number to format
1182 * @param {Number} precision The number of digits to show after the decimal point
1184 toFixed: function(value, precision) {
1185 if (isToFixedBroken) {
1186 precision = precision || 0;
1187 var pow = Math.pow(10, precision);
1188 return (Math.round(value * pow) / pow).toFixed(precision);
1191 return value.toFixed(precision);
1195 * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1198 Ext.Number.from('1.23', 1); // returns 1.23
1199 Ext.Number.from('abc', 1); // returns 1
1201 * @param {Mixed} value
1202 * @param {Number} defaultValue The value to return if the original value is non-numeric
1203 * @return {Number} value, if numeric, defaultValue otherwise
1205 from: function(value, defaultValue) {
1206 if (isFinite(value)) {
1207 value = parseFloat(value);
1210 return !isNaN(value) ? value : defaultValue;
1217 * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
1219 * @deprecated 4.0.0 Replaced by Ext.Number.from
1223 Ext.num = function() {
1224 return Ext.Number.from.apply(this, arguments);
1227 * @author Jacky Nguyen <jacky@sencha.com>
1228 * @docauthor Jacky Nguyen <jacky@sencha.com>
1231 * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1238 var arrayPrototype = Array.prototype,
1239 slice = arrayPrototype.slice,
1240 supportsSplice = function () {
1245 if (!array.splice) {
1249 // This detects a bug in IE8 splice method:
1250 // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
1256 array.splice(15, 0, "F", "F", "F", "F", "F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F");
1258 lengthBefore = array.length; //41
1259 array.splice(13, 0, "XXX"); // add one element
1261 if (lengthBefore+1 != array.length) {
1268 supportsForEach = 'forEach' in arrayPrototype,
1269 supportsMap = 'map' in arrayPrototype,
1270 supportsIndexOf = 'indexOf' in arrayPrototype,
1271 supportsEvery = 'every' in arrayPrototype,
1272 supportsSome = 'some' in arrayPrototype,
1273 supportsFilter = 'filter' in arrayPrototype,
1274 supportsSort = function() {
1275 var a = [1,2,3,4,5].sort(function(){ return 0; });
1276 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1278 supportsSliceOnNodeList = true,
1282 // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1283 if (typeof document !== 'undefined') {
1284 slice.call(document.getElementsByTagName('body'));
1287 supportsSliceOnNodeList = false;
1290 function fixArrayIndex (array, index) {
1291 return (index < 0) ? Math.max(0, array.length + index)
1292 : Math.min(array.length, index);
1296 Does the same work as splice, but with a slightly more convenient signature. The splice
1297 method has bugs in IE8, so this is the implementation we use on that platform.
1299 The rippling of items in the array can be tricky. Consider two use cases:
1304 +---+---+---+---+---+---+---+---+
1305 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1306 +---+---+---+---+---+---+---+---+
1309 / / \/ \/ \ +--------------------------+
1310 / / /\ /\ +--------------------------+ \
1311 / / / \/ +--------------------------+ \ \
1312 / / / /+--------------------------+ \ \ \
1315 +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
1316 | 0 | 1 | 4 | 5 | 6 | 7 | | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
1317 +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
1321 In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
1322 that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
1323 must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
1325 function replaceSim (array, index, removeCount, insert) {
1326 var add = insert ? insert.length : 0,
1327 length = array.length,
1328 pos = fixArrayIndex(array, index);
1330 // we try to use Array.push when we can for efficiency...
1331 if (pos === length) {
1333 array.push.apply(array, insert);
1336 var remove = Math.min(removeCount, length - pos),
1337 tailOldPos = pos + remove,
1338 tailNewPos = tailOldPos + add - remove,
1339 tailCount = length - tailOldPos,
1340 lengthAfterRemove = length - remove,
1343 if (tailNewPos < tailOldPos) { // case A
1344 for (i = 0; i < tailCount; ++i) {
1345 array[tailNewPos+i] = array[tailOldPos+i];
1347 } else if (tailNewPos > tailOldPos) { // case B
1348 for (i = tailCount; i--; ) {
1349 array[tailNewPos+i] = array[tailOldPos+i];
1351 } // else, add == remove (nothing to do)
1353 if (add && pos === lengthAfterRemove) {
1354 array.length = lengthAfterRemove; // truncate array
1355 array.push.apply(array, insert);
1357 array.length = lengthAfterRemove + add; // reserves space
1358 for (i = 0; i < add; ++i) {
1359 array[pos+i] = insert[i];
1367 function replaceNative (array, index, removeCount, insert) {
1368 if (insert && insert.length) {
1369 if (index < array.length) {
1370 array.splice.apply(array, [index, removeCount].concat(insert));
1372 array.push.apply(array, insert);
1375 array.splice(index, removeCount);
1380 function eraseSim (array, index, removeCount) {
1381 return replaceSim(array, index, removeCount);
1384 function eraseNative (array, index, removeCount) {
1385 array.splice(index, removeCount);
1389 function spliceSim (array, index, removeCount) {
1390 var pos = fixArrayIndex(array, index),
1391 removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
1393 if (arguments.length < 4) {
1394 replaceSim(array, pos, removeCount);
1396 replaceSim(array, pos, removeCount, slice.call(arguments, 3));
1402 function spliceNative (array) {
1403 return array.splice.apply(array, slice.call(arguments, 1));
1406 var erase = supportsSplice ? eraseNative : eraseSim,
1407 replace = supportsSplice ? replaceNative : replaceSim,
1408 splice = supportsSplice ? spliceNative : spliceSim;
1410 // NOTE: from here on, use erase, replace or splice (not native methods)...
1412 ExtArray = Ext.Array = {
1414 * Iterates an array or an iterable value and invoke the given callback function for each item.
1416 * var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1418 * Ext.Array.each(countries, function(name, index, countriesItSelf) {
1419 * console.log(name);
1422 * var sum = function() {
1425 * Ext.Array.each(arguments, function(value) {
1432 * sum(1, 2, 3); // returns 6
1434 * The iteration can be stopped by returning false in the function callback.
1436 * Ext.Array.each(countries, function(name, index, countriesItSelf) {
1437 * if (name === 'Singapore') {
1438 * return false; // break here
1442 * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
1444 * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
1445 * argument is not iterable, the callback function is called once.
1446 * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1447 * the current `index`. Arguments passed to this callback function are:
1449 * - `item` : Mixed - The item at the current `index` in the passed `array`
1450 * - `index` : Number - The current `index` within the `array`
1451 * - `allItems` : Array/NodeList/Mixed - The `array` passed as the first argument to `Ext.Array.each`
1453 * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1454 * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1456 * @return {Boolean} See description for the `fn` parameter.
1458 each: function(array, fn, scope, reverse) {
1459 array = ExtArray.from(array);
1464 if (reverse !== true) {
1465 for (i = 0; i < ln; i++) {
1466 if (fn.call(scope || array[i], array[i], i, array) === false) {
1472 for (i = ln - 1; i > -1; i--) {
1473 if (fn.call(scope || array[i], array[i], i, array) === false) {
1483 * Iterates an array and invoke the given callback function for each item. Note that this will simply
1484 * delegate to the native Array.prototype.forEach method if supported.
1485 * It doesn't support stopping the iteration by returning false in the callback function like
1486 * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
1487 * {@link Ext.Array#each}
1489 * @param {Array} array The array to iterate
1490 * @param {Function} fn The function callback, to be invoked these arguments:
1492 * - `item` : Mixed - The item at the current `index` in the passed `array`
1493 * - `index` : Number - The current `index` within the `array`
1494 * - `allItems` : Array - The `array` itself which was passed as the first argument
1496 * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1498 forEach: function(array, fn, scope) {
1499 if (supportsForEach) {
1500 return array.forEach(fn, scope);
1506 for (; i < ln; i++) {
1507 fn.call(scope, array[i], i, array);
1512 * Get the index of the provided `item` in the given `array`, a supplement for the
1513 * missing arrayPrototype.indexOf in Internet Explorer.
1515 * @param {Array} array The array to check
1516 * @param {Mixed} item The item to look for
1517 * @param {Number} from (Optional) The index at which to begin the search
1518 * @return {Number} The index of item in the array (or -1 if it is not found)
1520 indexOf: function(array, item, from) {
1521 if (supportsIndexOf) {
1522 return array.indexOf(item, from);
1525 var i, length = array.length;
1527 for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1528 if (array[i] === item) {
1537 * Checks whether or not the given `array` contains the specified `item`
1539 * @param {Array} array The array to check
1540 * @param {Mixed} item The item to look for
1541 * @return {Boolean} True if the array contains the item, false otherwise
1543 contains: function(array, item) {
1544 if (supportsIndexOf) {
1545 return array.indexOf(item) !== -1;
1550 for (i = 0, ln = array.length; i < ln; i++) {
1551 if (array[i] === item) {
1560 * Converts any iterable (numeric indices and a length property) into a true array.
1563 * var args = Ext.Array.toArray(arguments),
1564 * fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1566 * alert(args.join(' '));
1567 * alert(fromSecondToLastArgs.join(' '));
1570 * test('just', 'testing', 'here'); // alerts 'just testing here';
1571 * // alerts 'testing here';
1573 * Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1574 * Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1575 * Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1577 * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
1579 * @param {Mixed} iterable the iterable object to be turned into a true Array.
1580 * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1581 * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1582 * index of the iterable value
1583 * @return {Array} array
1585 toArray: function(iterable, start, end){
1586 if (!iterable || !iterable.length) {
1590 if (typeof iterable === 'string') {
1591 iterable = iterable.split('');
1594 if (supportsSliceOnNodeList) {
1595 return slice.call(iterable, start || 0, end || iterable.length);
1602 end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1604 for (i = start; i < end; i++) {
1605 array.push(iterable[i]);
1612 * Plucks the value of a property from each item in the Array. Example:
1614 * Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1616 * @param {Array|NodeList} array The Array of items to pluck the value from.
1617 * @param {String} propertyName The property name to pluck from each element.
1618 * @return {Array} The value from each item in the Array.
1620 pluck: function(array, propertyName) {
1624 for (i = 0, ln = array.length; i < ln; i++) {
1627 ret.push(item[propertyName]);
1634 * Creates a new array with the results of calling a provided function on every element in this array.
1636 * @param {Array} array
1637 * @param {Function} fn Callback function for each item
1638 * @param {Object} scope Callback function scope
1639 * @return {Array} results
1641 map: function(array, fn, scope) {
1643 return array.map(fn, scope);
1650 for (; i < len; i++) {
1651 results[i] = fn.call(scope, array[i], i, array);
1658 * Executes the specified function for each array element until the function returns a falsy value.
1659 * If such an item is found, the function will return false immediately.
1660 * Otherwise, it will return true.
1662 * @param {Array} array
1663 * @param {Function} fn Callback function for each item
1664 * @param {Object} scope Callback function scope
1665 * @return {Boolean} True if no false value is returned by the callback function.
1667 every: function(array, fn, scope) {
1669 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
1671 if (supportsEvery) {
1672 return array.every(fn, scope);
1678 for (; i < ln; ++i) {
1679 if (!fn.call(scope, array[i], i, array)) {
1688 * Executes the specified function for each array element until the function returns a truthy value.
1689 * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1691 * @param {Array} array
1692 * @param {Function} fn Callback function for each item
1693 * @param {Object} scope Callback function scope
1694 * @return {Boolean} True if the callback function returns a truthy value.
1696 some: function(array, fn, scope) {
1698 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
1701 return array.some(fn, scope);
1707 for (; i < ln; ++i) {
1708 if (fn.call(scope, array[i], i, array)) {
1717 * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1719 * See {@link Ext.Array#filter}
1721 * @param {Array} array
1722 * @return {Array} results
1724 clean: function(array) {
1730 for (; i < ln; i++) {
1733 if (!Ext.isEmpty(item)) {
1742 * Returns a new array with unique items
1744 * @param {Array} array
1745 * @return {Array} results
1747 unique: function(array) {
1753 for (; i < ln; i++) {
1756 if (ExtArray.indexOf(clone, item) === -1) {
1765 * Creates a new array with all of the elements of this array for which
1766 * the provided filtering function returns true.
1768 * @param {Array} array
1769 * @param {Function} fn Callback function for each item
1770 * @param {Object} scope Callback function scope
1771 * @return {Array} results
1773 filter: function(array, fn, scope) {
1774 if (supportsFilter) {
1775 return array.filter(fn, scope);
1782 for (; i < ln; i++) {
1783 if (fn.call(scope, array[i], i, array)) {
1784 results.push(array[i]);
1792 * Converts a value to an array if it's not already an array; returns:
1794 * - An empty array if given value is `undefined` or `null`
1795 * - Itself if given value is already an array
1796 * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1797 * - An array with one item which is the given value, otherwise
1799 * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
1800 * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
1802 * @return {Array} array
1804 from: function(value, newReference) {
1805 if (value === undefined || value === null) {
1809 if (Ext.isArray(value)) {
1810 return (newReference) ? slice.call(value) : value;
1813 if (value && value.length !== undefined && typeof value !== 'string') {
1814 return Ext.toArray(value);
1821 * Removes the specified item from the array if it exists
1823 * @param {Array} array The array
1824 * @param {Mixed} item The item to remove
1825 * @return {Array} The passed array itself
1827 remove: function(array, item) {
1828 var index = ExtArray.indexOf(array, item);
1831 erase(array, index, 1);
1838 * Push an item into the array only if the array doesn't contain it yet
1840 * @param {Array} array The array
1841 * @param {Mixed} item The item to include
1843 include: function(array, item) {
1844 if (!ExtArray.contains(array, item)) {
1850 * Clone a flat array without referencing the previous one. Note that this is different
1851 * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1852 * for Array.prototype.slice.call(array)
1854 * @param {Array} array The array
1855 * @return {Array} The clone array
1857 clone: function(array) {
1858 return slice.call(array);
1862 * Merge multiple arrays into one with unique items.
1864 * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
1866 * @param {Array} array1
1867 * @param {Array} array2
1868 * @param {Array} etc
1869 * @return {Array} merged
1872 var args = slice.call(arguments),
1876 for (i = 0, ln = args.length; i < ln; i++) {
1877 array = array.concat(args[i]);
1880 return ExtArray.unique(array);
1884 * Merge multiple arrays into one with unique items that exist in all of the arrays.
1886 * @param {Array} array1
1887 * @param {Array} array2
1888 * @param {Array} etc
1889 * @return {Array} intersect
1891 intersect: function() {
1893 arrays = slice.call(arguments),
1894 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1896 if (!arrays.length) {
1900 // Find the smallest array
1901 for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1902 if (!minArray || array.length < minArray.length) {
1908 minArray = ExtArray.unique(minArray);
1909 erase(arrays, x, 1);
1911 // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1912 // an item in the small array, we're likely to find it before reaching the end
1913 // of the inner loop and can terminate the search early.
1914 for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1917 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1918 for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1926 if (count === arraysLn) {
1935 * Perform a set difference A-B by subtracting all items in array B from array A.
1937 * @param {Array} arrayA
1938 * @param {Array} arrayB
1939 * @return {Array} difference
1941 difference: function(arrayA, arrayB) {
1942 var clone = slice.call(arrayA),
1946 for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1947 for (j = 0; j < ln; j++) {
1948 if (clone[j] === arrayB[i]) {
1960 * Returns a shallow copy of a part of an array. This is equivalent to the native
1961 * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
1962 * is "arguments" since the arguments object does not supply a slice method but can
1963 * be the context object to Array.prototype.slice.
1965 * @param {Array} array The array (or arguments object).
1966 * @param {Number} begin The index at which to begin. Negative values are offsets from
1967 * the end of the array.
1968 * @param {Number} end The index at which to end. The copied items do not include
1969 * end. Negative values are offsets from the end of the array. If end is omitted,
1970 * all items up to the end of the array are copied.
1971 * @return {Array} The copied piece of the array.
1973 slice: function(array, begin, end) {
1974 return slice.call(array, begin, end);
1978 * Sorts the elements of an Array.
1979 * By default, this method sorts the elements alphabetically and ascending.
1981 * @param {Array} array The array to sort.
1982 * @param {Function} sortFn (optional) The comparison function.
1983 * @return {Array} The sorted array.
1985 sort: function(array, sortFn) {
1988 return array.sort(sortFn);
1990 return array.sort();
1994 var length = array.length,
1999 for (; i < length; i++) {
2001 for (j = i + 1; j < length; j++) {
2003 comparison = sortFn(array[j], array[min]);
2004 if (comparison < 0) {
2007 } else if (array[j] < array[min]) {
2013 array[i] = array[min];
2022 * Recursively flattens into 1-d Array. Injects Arrays inline.
2025 flatten: function(array) {
2028 function rFlatten(a) {
2031 for (i = 0, ln = a.length; i < ln; i++) {
2034 if (Ext.isArray(v)) {
2044 return rFlatten(array);
2048 * Returns the minimum value in the Array.
2050 * @param {Array|NodeList} array The Array from which to select the minimum value.
2051 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
2052 * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
2053 * @return {Mixed} minValue The minimum value
2055 min: function(array, comparisonFn) {
2059 for (i = 0, ln = array.length; i < ln; i++) {
2063 if (comparisonFn(min, item) === 1) {
2078 * Returns the maximum value in the Array.
2080 * @param {Array|NodeList} array The Array from which to select the maximum value.
2081 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
2082 * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
2083 * @return {Mixed} maxValue The maximum value
2085 max: function(array, comparisonFn) {
2089 for (i = 0, ln = array.length; i < ln; i++) {
2093 if (comparisonFn(max, item) === -1) {
2108 * Calculates the mean of all items in the array.
2110 * @param {Array} array The Array to calculate the mean value of.
2111 * @return {Number} The mean.
2113 mean: function(array) {
2114 return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
2118 * Calculates the sum of all items in the given array.
2120 * @param {Array} array The Array to calculate the sum value of.
2121 * @return {Number} The sum.
2123 sum: function(array) {
2127 for (i = 0,ln = array.length; i < ln; i++) {
2136 _replaceSim: replaceSim, // for unit testing
2137 _spliceSim: spliceSim,
2140 * Removes items from an array. This is functionally equivalent to the splice method
2141 * of Array, but works around bugs in IE8's splice method and does not copy the
2142 * removed elements in order to return them (because very often they are ignored).
2144 * @param {Array} array The Array on which to replace.
2145 * @param {Number} index The index in the array at which to operate.
2146 * @param {Number} removeCount The number of items to remove at index.
2147 * @return {Array} The array passed.
2153 * Inserts items in to an array.
2155 * @param {Array} array The Array on which to replace.
2156 * @param {Number} index The index in the array at which to operate.
2157 * @param {Array} items The array of items to insert at index.
2158 * @return {Array} The array passed.
2160 insert: function (array, index, items) {
2161 return replace(array, index, 0, items);
2165 * Replaces items in an array. This is functionally equivalent to the splice method
2166 * of Array, but works around bugs in IE8's splice method and is often more convenient
2167 * to call because it accepts an array of items to insert rather than use a variadic
2170 * @param {Array} array The Array on which to replace.
2171 * @param {Number} index The index in the array at which to operate.
2172 * @param {Number} removeCount The number of items to remove at index (can be 0).
2173 * @param {Array} insert An optional array of items to insert at index.
2174 * @return {Array} The array passed.
2180 * Replaces items in an array. This is equivalent to the splice method of Array, but
2181 * works around bugs in IE8's splice method. The signature is exactly the same as the
2182 * splice method except that the array is the first argument. All arguments following
2183 * removeCount are inserted in the array at index.
2185 * @param {Array} array The Array on which to replace.
2186 * @param {Number} index The index in the array at which to operate.
2187 * @param {Number} removeCount The number of items to remove at index (can be 0).
2188 * @return {Array} An array containing the removed items.
2197 * @alias Ext.Array#each
2199 Ext.each = ExtArray.each;
2204 * @alias Ext.Array#merge
2206 ExtArray.union = ExtArray.merge;
2209 * Old alias to {@link Ext.Array#min}
2210 * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
2213 * @alias Ext.Array#min
2215 Ext.min = ExtArray.min;
2218 * Old alias to {@link Ext.Array#max}
2219 * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
2222 * @alias Ext.Array#max
2224 Ext.max = ExtArray.max;
2227 * Old alias to {@link Ext.Array#sum}
2228 * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
2231 * @alias Ext.Array#sum
2233 Ext.sum = ExtArray.sum;
2236 * Old alias to {@link Ext.Array#mean}
2237 * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
2240 * @alias Ext.Array#mean
2242 Ext.mean = ExtArray.mean;
2245 * Old alias to {@link Ext.Array#flatten}
2246 * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
2249 * @alias Ext.Array#flatten
2251 Ext.flatten = ExtArray.flatten;
2254 * Old alias to {@link Ext.Array#clean}
2255 * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
2258 * @alias Ext.Array#clean
2260 Ext.clean = ExtArray.clean;
2263 * Old alias to {@link Ext.Array#unique}
2264 * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
2267 * @alias Ext.Array#unique
2269 Ext.unique = ExtArray.unique;
2272 * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
2273 * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
2276 * @alias Ext.Array#pluck
2278 Ext.pluck = ExtArray.pluck;
2283 * @alias Ext.Array#toArray
2285 Ext.toArray = function() {
2286 return ExtArray.toArray.apply(ExtArray, arguments);
2291 * @class Ext.Function
2293 * A collection of useful static methods to deal with function callbacks
2299 * A very commonly used method throughout the framework. It acts as a wrapper around another method
2300 * which originally accepts 2 arguments for `name` and `value`.
2301 * The wrapped function then allows "flexible" value setting of either:
2303 * - `name` and `value` as 2 arguments
2304 * - one single object argument with multiple key - value pairs
2308 * var setValue = Ext.Function.flexSetter(function(name, value) {
2309 * this[name] = value;
2313 * // Setting a single name - value
2314 * setValue('name1', 'value1');
2316 * // Settings multiple name - value pairs
2323 * @param {Function} setter
2324 * @returns {Function} flexSetter
2326 flexSetter: function(fn) {
2327 return function(a, b) {
2334 if (typeof a !== 'string') {
2336 if (a.hasOwnProperty(k)) {
2337 fn.call(this, k, a[k]);
2341 if (Ext.enumerables) {
2342 for (i = Ext.enumerables.length; i--;) {
2343 k = Ext.enumerables[i];
2344 if (a.hasOwnProperty(k)) {
2345 fn.call(this, k, a[k]);
2350 fn.call(this, a, b);
2358 * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
2359 * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2361 * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
2363 * @param {Function} fn The function to delegate.
2364 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2365 * **If omitted, defaults to the browser window.**
2366 * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2367 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2368 * if a number the args are inserted at the specified position
2369 * @return {Function} The new function
2371 bind: function(fn, scope, args, appendArgs) {
2373 slice = Array.prototype.slice;
2376 var callArgs = args || arguments;
2378 if (appendArgs === true) {
2379 callArgs = slice.call(arguments, 0);
2380 callArgs = callArgs.concat(args);
2382 else if (Ext.isNumber(appendArgs)) {
2383 callArgs = slice.call(arguments, 0); // copy arguments first
2384 Ext.Array.insert(callArgs, appendArgs, args);
2387 return method.apply(scope || window, callArgs);
2392 * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
2393 * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2394 * This is especially useful when creating callbacks.
2398 * var originalFunction = function(){
2399 * alert(Ext.Array.from(arguments).join(' '));
2402 * var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2404 * callback(); // alerts 'Hello World'
2405 * callback('by Me'); // alerts 'Hello World by Me'
2407 * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
2409 * @param {Function} fn The original function
2410 * @param {Array} args The arguments to pass to new callback
2411 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2412 * @return {Function} The new callback function
2414 pass: function(fn, args, scope) {
2416 args = Ext.Array.from(args);
2420 return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2425 * Create an alias to the provided method property with name `methodName` of `object`.
2426 * Note that the execution scope will still be bound to the provided `object` itself.
2428 * @param {Object/Function} object
2429 * @param {String} methodName
2430 * @return {Function} aliasFn
2432 alias: function(object, methodName) {
2434 return object[methodName].apply(object, arguments);
2439 * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2440 * the original one is not called. The resulting function returns the results of the original function.
2441 * The passed function is called with the parameters of the original function. Example usage:
2443 * var sayHi = function(name){
2444 * alert('Hi, ' + name);
2447 * sayHi('Fred'); // alerts "Hi, Fred"
2449 * // create a new function that validates input without
2450 * // directly modifying the original function:
2451 * var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2452 * return name == 'Brian';
2455 * sayHiToFriend('Fred'); // no alert
2456 * sayHiToFriend('Brian'); // alerts "Hi, Brian"
2458 * @param {Function} origFn The original function.
2459 * @param {Function} newFn The function to call before the original
2460 * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2461 * **If omitted, defaults to the scope in which the original function is called or the browser window.**
2462 * @param {Mixed} returnValue (optional) The value to return if the passed function return false (defaults to null).
2463 * @return {Function} The new function
2465 createInterceptor: function(origFn, newFn, scope, returnValue) {
2466 var method = origFn;
2467 if (!Ext.isFunction(newFn)) {
2475 newFn.method = origFn;
2476 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2482 * Creates a delegate (callback) which, when called, executes after a specific delay.
2484 * @param {Function} fn The function which will be called on a delay when the returned function is called.
2485 * Optionally, a replacement (or additional) argument list may be specified.
2486 * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2487 * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
2488 * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2489 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2490 * if a number the args are inserted at the specified position.
2491 * @return {Function} A function which, when called, executes the original function after the specified delay.
2493 createDelayed: function(fn, delay, scope, args, appendArgs) {
2494 if (scope || args) {
2495 fn = Ext.Function.bind(fn, scope, args, appendArgs);
2499 setTimeout(function() {
2500 fn.apply(me, arguments);
2506 * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2508 * var sayHi = function(name){
2509 * alert('Hi, ' + name);
2512 * // executes immediately:
2515 * // executes after 2 seconds:
2516 * Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2518 * // this syntax is sometimes useful for deferring
2519 * // execution of an anonymous function:
2520 * Ext.Function.defer(function(){
2521 * alert('Anonymous');
2524 * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
2526 * @param {Function} fn The function to defer.
2527 * @param {Number} millis The number of milliseconds for the setTimeout call
2528 * (if less than or equal to 0 the function is executed immediately)
2529 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2530 * **If omitted, defaults to the browser window.**
2531 * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2532 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2533 * if a number the args are inserted at the specified position
2534 * @return {Number} The timeout id that can be used with clearTimeout
2536 defer: function(fn, millis, obj, args, appendArgs) {
2537 fn = Ext.Function.bind(fn, obj, args, appendArgs);
2539 return setTimeout(fn, millis);
2546 * Create a combined function call sequence of the original function + the passed function.
2547 * The resulting function returns the results of the original function.
2548 * The passed function is called with the parameters of the original function. Example usage:
2550 * var sayHi = function(name){
2551 * alert('Hi, ' + name);
2554 * sayHi('Fred'); // alerts "Hi, Fred"
2556 * var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2557 * alert('Bye, ' + name);
2560 * sayGoodbye('Fred'); // both alerts show
2562 * @param {Function} origFn The original function.
2563 * @param {Function} newFn The function to sequence
2564 * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2565 * If omitted, defaults to the scope in which the original function is called or the browser window.
2566 * @return {Function} The new function
2568 createSequence: function(origFn, newFn, scope) {
2569 if (!Ext.isFunction(newFn)) {
2574 var retval = origFn.apply(this || window, arguments);
2575 newFn.apply(scope || this || window, arguments);
2582 * Creates a delegate function, optionally with a bound scope which, when called, buffers
2583 * the execution of the passed function for the configured number of milliseconds.
2584 * If called again within that period, the impending invocation will be canceled, and the
2585 * timeout period will begin again.
2587 * @param {Function} fn The function to invoke on a buffered timer.
2588 * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2590 * @param {Object} scope (optional) The scope (`this` reference) in which
2591 * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2592 * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2593 * passed by the caller.
2594 * @return {Function} A function which invokes the passed function after buffering for the specified time.
2596 createBuffered: function(fn, buffer, scope, args) {
2602 clearInterval(timerId);
2605 timerId = setTimeout(function(){
2606 fn.apply(scope || me, args || arguments);
2613 * Creates a throttled version of the passed function which, when called repeatedly and
2614 * rapidly, invokes the passed function only after a certain interval has elapsed since the
2615 * previous invocation.
2617 * This is useful for wrapping functions which may be called repeatedly, such as
2618 * a handler of a mouse move event when the processing is expensive.
2620 * @param {Function} fn The function to execute at a regular time interval.
2621 * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
2622 * @param {Object} scope (optional) The scope (`this` reference) in which
2623 * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2624 * @returns {Function} A function which invokes the passed function at the specified interval.
2626 createThrottled: function(fn, interval, scope) {
2627 var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2628 fn.apply(scope || this, lastArgs);
2629 lastCallTime = new Date().getTime();
2633 elapsed = new Date().getTime() - lastCallTime;
2634 lastArgs = arguments;
2636 clearTimeout(timer);
2637 if (!lastCallTime || (elapsed >= interval)) {
2640 timer = setTimeout(execute, interval - elapsed);
2649 * @alias Ext.Function#defer
2651 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2656 * @alias Ext.Function#pass
2658 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2663 * @alias Ext.Function#bind
2665 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2668 * @author Jacky Nguyen <jacky@sencha.com>
2669 * @docauthor Jacky Nguyen <jacky@sencha.com>
2672 * A collection of useful static methods to deal with objects
2679 var ExtObject = Ext.Object = {
2682 * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
2683 * query strings. For example:
2685 var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2687 // objects then equals:
2689 { name: 'hobbies', value: 'reading' },
2690 { name: 'hobbies', value: 'cooking' },
2691 { name: 'hobbies', value: 'swimming' },
2694 var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2702 }, true); // Recursive
2704 // objects then equals:
2706 { name: 'dateOfBirth[day]', value: 3 },
2707 { name: 'dateOfBirth[month]', value: 8 },
2708 { name: 'dateOfBirth[year]', value: 1987 },
2709 { name: 'dateOfBirth[extra][hour]', value: 4 },
2710 { name: 'dateOfBirth[extra][minute]', value: 30 },
2713 * @param {String} name
2714 * @param {Mixed} value
2715 * @param {Boolean} recursive
2718 toQueryObjects: function(name, value, recursive) {
2719 var self = ExtObject.toQueryObjects,
2723 if (Ext.isArray(value)) {
2724 for (i = 0, ln = value.length; i < ln; i++) {
2726 objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2736 else if (Ext.isObject(value)) {
2738 if (value.hasOwnProperty(i)) {
2740 objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2762 * Takes an object and converts it to an encoded query string
2766 Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2767 Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2768 Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2769 Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2770 Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2774 Ext.Object.toQueryString({
2781 hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2782 }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2784 // &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2785 // &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2788 * @param {Object} object The object to encode
2789 * @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
2790 * (PHP / Ruby on Rails servers and similar). Defaults to false
2791 * @return {String} queryString
2794 toQueryString: function(object, recursive) {
2795 var paramObjects = [],
2797 i, j, ln, paramObject, value;
2800 if (object.hasOwnProperty(i)) {
2801 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2805 for (j = 0, ln = paramObjects.length; j < ln; j++) {
2806 paramObject = paramObjects[j];
2807 value = paramObject.value;
2809 if (Ext.isEmpty(value)) {
2812 else if (Ext.isDate(value)) {
2813 value = Ext.Date.toString(value);
2816 params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2819 return params.join('&');
2823 * Converts a query string back into an object.
2827 Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2828 Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2829 Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2830 Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2834 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);
2844 hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2847 * @param {String} queryString The query string to decode
2848 * @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
2849 * PHP / Ruby on Rails servers and similar. Defaults to false
2852 fromQueryString: function(queryString, recursive) {
2853 var parts = queryString.replace(/^\?/, '').split('&'),
2855 temp, components, name, value, i, ln,
2856 part, j, subLn, matchedKeys, matchedName,
2859 for (i = 0, ln = parts.length; i < ln; i++) {
2862 if (part.length > 0) {
2863 components = part.split('=');
2864 name = decodeURIComponent(components[0]);
2865 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2868 if (object.hasOwnProperty(name)) {
2869 if (!Ext.isArray(object[name])) {
2870 object[name] = [object[name]];
2873 object[name].push(value);
2876 object[name] = value;
2880 matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2881 matchedName = name.match(/^([^\[]+)/);
2885 sourceClass: "Ext.Object",
2886 sourceMethod: "fromQueryString",
2887 queryString: queryString,
2888 recursive: recursive,
2889 msg: 'Malformed query string given, failed parsing name from "' + part + '"'
2893 name = matchedName[0];
2896 if (matchedKeys === null) {
2897 object[name] = value;
2901 for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
2902 key = matchedKeys[j];
2903 key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
2911 for (j = 0, subLn = keys.length; j < subLn; j++) {
2914 if (j === subLn - 1) {
2915 if (Ext.isArray(temp) && key === '') {
2923 if (temp[key] === undefined || typeof temp[key] === 'string') {
2924 nextKey = keys[j+1];
2926 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
2940 * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
2941 * by returning `false` in the callback function. For example:
2946 loves: ['food', 'sleeping', 'wife']
2949 Ext.Object.each(person, function(key, value, myself) {
2950 console.log(key + ":" + value);
2952 if (key === 'hairColor') {
2953 return false; // stop the iteration
2957 * @param {Object} object The object to iterate
2958 * @param {Function} fn The callback function. Passed arguments for each iteration are:
2962 - {Object} `object` The object itself
2964 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
2967 each: function(object, fn, scope) {
2968 for (var property in object) {
2969 if (object.hasOwnProperty(property)) {
2970 if (fn.call(scope || object, property, object[property], object) === false) {
2978 * Merges any number of objects recursively without referencing them or their children.
2981 companyName: 'Ext JS',
2982 products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
2986 location: 'Palo Alto',
2992 companyName: 'Sencha Inc.',
2993 products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2996 location: 'Redwood City'
3000 var sencha = Ext.Object.merge(extjs, newStuff);
3002 // extjs and sencha then equals to
3004 companyName: 'Sencha Inc.',
3005 products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3009 location: 'Redwood City'
3014 * @param {Object} object,...
3015 * @return {Object} merged The object that is created as a result of merging all the objects passed in.
3018 merge: function(source, key, value) {
3019 if (typeof key === 'string') {
3020 if (value && value.constructor === Object) {
3021 if (source[key] && source[key].constructor === Object) {
3022 ExtObject.merge(source[key], value);
3025 source[key] = Ext.clone(value);
3029 source[key] = value;
3036 ln = arguments.length,
3039 for (; i < ln; i++) {
3040 object = arguments[i];
3042 for (property in object) {
3043 if (object.hasOwnProperty(property)) {
3044 ExtObject.merge(source, property, object[property]);
3053 * Returns the first matching key corresponding to the given value.
3054 * If no matching value is found, null is returned.
3061 alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'
3063 * @param {Object} object
3064 * @param {Object} value The value to find
3067 getKey: function(object, value) {
3068 for (var property in object) {
3069 if (object.hasOwnProperty(property) && object[property] === value) {
3078 * Gets all values of the given object as an array.
3080 var values = Ext.Object.getValues({
3083 }); // ['Jacky', 'food']
3085 * @param {Object} object
3086 * @return {Array} An array of values from the object
3089 getValues: function(object) {
3093 for (property in object) {
3094 if (object.hasOwnProperty(property)) {
3095 values.push(object[property]);
3103 * Gets all keys of the given object as an array.
3105 var values = Ext.Object.getKeys({
3108 }); // ['name', 'loves']
3110 * @param {Object} object
3111 * @return {Array} An array of keys from the object
3114 getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
3118 for (property in object) {
3119 if (object.hasOwnProperty(property)) {
3120 keys.push(property);
3128 * Gets the total number of this object's own properties
3130 var size = Ext.Object.getSize({
3133 }); // size equals 2
3135 * @param {Object} object
3136 * @return {Number} size
3139 getSize: function(object) {
3143 for (property in object) {
3144 if (object.hasOwnProperty(property)) {
3155 * A convenient alias method for {@link Ext.Object#merge}
3160 Ext.merge = Ext.Object.merge;
3163 * A convenient alias method for {@link Ext.Object#toQueryString}
3167 * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
3169 Ext.urlEncode = function() {
3170 var args = Ext.Array.from(arguments),
3173 // Support for the old `pre` argument
3174 if ((typeof args[1] === 'string')) {
3175 prefix = args[1] + '&';
3179 return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
3183 * A convenient alias method for {@link Ext.Object#fromQueryString}
3187 * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
3189 Ext.urlDecode = function() {
3190 return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
3197 * A set of useful static methods to deal with date
3198 * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
3199 * this object for convenience
3201 * The date parsing and formatting syntax contains a subset of
3202 * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
3203 * supported will provide results equivalent to their PHP versions.
3205 * The following is a list of all currently supported formats:
3207 Format Description Example returned values
3208 ------ ----------------------------------------------------------------------- -----------------------
3209 d Day of the month, 2 digits with leading zeros 01 to 31
3210 D A short textual representation of the day of the week Mon to Sun
3211 j Day of the month without leading zeros 1 to 31
3212 l A full textual representation of the day of the week Sunday to Saturday
3213 N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
3214 S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
3215 w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
3216 z The day of the year (starting from 0) 0 to 364 (365 in leap years)
3217 W ISO-8601 week number of year, weeks starting on Monday 01 to 53
3218 F A full textual representation of a month, such as January or March January to December
3219 m Numeric representation of a month, with leading zeros 01 to 12
3220 M A short textual representation of a month Jan to Dec
3221 n Numeric representation of a month, without leading zeros 1 to 12
3222 t Number of days in the given month 28 to 31
3223 L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
3224 o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
3225 belongs to the previous or next year, that year is used instead)
3226 Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
3227 y A two digit representation of a year Examples: 99 or 03
3228 a Lowercase Ante meridiem and Post meridiem am or pm
3229 A Uppercase Ante meridiem and Post meridiem AM or PM
3230 g 12-hour format of an hour without leading zeros 1 to 12
3231 G 24-hour format of an hour without leading zeros 0 to 23
3232 h 12-hour format of an hour with leading zeros 01 to 12
3233 H 24-hour format of an hour with leading zeros 00 to 23
3234 i Minutes, with leading zeros 00 to 59
3235 s Seconds, with leading zeros 00 to 59
3236 u Decimal fraction of a second Examples:
3237 (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
3238 100 (i.e. 0.100s) or
3239 999 (i.e. 0.999s) or
3240 999876543210 (i.e. 0.999876543210s)
3241 O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
3242 P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
3243 T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
3244 Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
3247 1) If unspecified, the month / day defaults to the current month / day, 1991 or
3248 the time defaults to midnight, while the timezone defaults to the 1992-10 or
3249 browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
3250 and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
3251 are optional. 1995-07-18T17:21:28-02:00 or
3252 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
3253 least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
3254 of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
3255 Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
3256 date-time granularity which are supported, or see 2000-02-13T21:25:33
3257 http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
3258 U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
3259 MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
3260 \/Date(1238606590509+0800)\/
3263 * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
3266 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
3268 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
3269 console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
3270 console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
3271 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
3274 * Here are some standard date/time patterns that you might find helpful. They
3275 * are not part of the source of Ext.Date, but to use them you can simply copy this
3276 * block of code into any script that is included after Ext.Date and they will also become
3277 * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
3279 Ext.Date.patterns = {
3280 ISO8601Long:"Y-m-d H:i:s",
3281 ISO8601Short:"Y-m-d",
3283 LongDate: "l, F d, Y",
3284 FullDateTime: "l, F d, Y g:i:s A",
3287 LongTime: "g:i:s A",
3288 SortableDateTime: "Y-m-d\\TH:i:s",
3289 UniversalSortableDateTime: "Y-m-d H:i:sO",
3296 var dt = new Date();
3297 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3299 * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3300 * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3305 * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3306 * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3307 * They generate precompiled functions from format patterns instead of parsing and
3308 * processing each pattern every time a date is formatted. These functions are available
3309 * on every Date object.
3314 // create private copy of Ext's Ext.util.Format.format() method
3315 // - to remove unnecessary dependency
3316 // - to resolve namespace conflict with MS-Ajax's implementation
3317 function xf(format) {
3318 var args = Array.prototype.slice.call(arguments, 1);
3319 return format.replace(/\{(\d+)\}/g, function(m, i) {
3326 * Returns the current timestamp
3327 * @return {Date} The current timestamp
3330 now: Date.now || function() {
3338 toString: function(date) {
3339 var pad = Ext.String.leftPad;
3341 return date.getFullYear() + "-"
3342 + pad(date.getMonth() + 1, 2, '0') + "-"
3343 + pad(date.getDate(), 2, '0') + "T"
3344 + pad(date.getHours(), 2, '0') + ":"
3345 + pad(date.getMinutes(), 2, '0') + ":"
3346 + pad(date.getSeconds(), 2, '0');
3350 * Returns the number of milliseconds between two dates
3351 * @param {Date} dateA The first date
3352 * @param {Date} dateB (optional) The second date, defaults to now
3353 * @return {Number} The difference in milliseconds
3355 getElapsed: function(dateA, dateB) {
3356 return Math.abs(dateA - (dateB || new Date()));
3360 * Global flag which determines if strict date parsing should be used.
3361 * Strict date parsing will not roll-over invalid dates, which is the
3362 * default behaviour of javascript Date objects.
3363 * (see {@link #parse} for more information)
3364 * Defaults to <tt>false</tt>.
3371 formatCodeToRegex: function(character, currentGroup) {
3372 // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3373 var p = utilDate.parseCodes[character];
3376 p = typeof p == 'function'? p() : p;
3377 utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3380 return p ? Ext.applyIf({
3381 c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3385 s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3390 * <p>An object hash in which each property is a date parsing function. The property name is the
3391 * format string which that function parses.</p>
3392 * <p>This object is automatically populated with date parsing functions as
3393 * date formats are requested for Ext standard formatting strings.</p>
3394 * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3395 * may be used as a format string to {@link #parse}.<p>
3396 * <p>Example:</p><pre><code>
3397 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3399 * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3400 * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3401 * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3402 * (i.e. prevent javascript Date "rollover") (The default must be false).
3403 * Invalid date strings should return null when parsed.</div></li>
3405 * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3406 * formatting function must be placed into the {@link #formatFunctions} property.
3407 * @property parseFunctions
3412 "MS": function(input, strict) {
3413 // note: the timezone offset is ignored since the MS Ajax server sends
3414 // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3415 var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3416 var r = (input || '').match(re);
3417 return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3423 * <p>An object hash in which each property is a date formatting function. The property name is the
3424 * format string which corresponds to the produced formatted date string.</p>
3425 * <p>This object is automatically populated with date formatting functions as
3426 * date formats are requested for Ext standard formatting strings.</p>
3427 * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3428 * may be used as a format string to {@link #format}. Example:</p><pre><code>
3429 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3431 * <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>
3432 * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3434 * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3435 * parsing function must be placed into the {@link #parseFunctions} property.
3436 * @property formatFunctions
3442 // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3443 return '\\/Date(' + this.getTime() + ')\\/';
3450 * Date interval constant
3457 * Date interval constant
3464 * Date interval constant
3470 /** Date interval constant
3477 * Date interval constant
3484 * Date interval constant
3491 * Date interval constant
3498 * <p>An object hash containing default date values used during date parsing.</p>
3499 * <p>The following properties are available:<div class="mdetail-params"><ul>
3500 * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3501 * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3502 * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3503 * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3504 * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3505 * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3506 * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3508 * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3509 * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3510 * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3511 * It is the responsiblity of the developer to account for this.</b></p>
3514 // set default day value to the first day of the month
3515 Ext.Date.defaults.d = 1;
3517 // parse a February date string containing only year and month values.
3518 // setting the default day value to 1 prevents weird date rollover issues
3519 // when attempting to parse the following date string on, for example, March 31st 2009.
3520 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3522 * @property defaults
3529 * An array of textual day names.
3530 * Override these values for international dates.
3533 Ext.Date.dayNames = [
3553 * An array of textual month names.
3554 * Override these values for international dates.
3557 Ext.Date.monthNames = [
3582 * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3583 * Override these values for international dates.
3586 Ext.Date.monthNumbers = {
3587 'ShortJanNameInYourLang':0,
3588 'ShortFebNameInYourLang':1,
3610 * <p>The date format string that the {@link Ext.util.Format#dateRenderer}
3611 * and {@link Ext.util.Format#date} functions use. See {@link Ext.Date} for details.</p>
3612 * <p>This defaults to <code>m/d/Y</code>, but may be overridden in a locale file.</p>
3613 * @property defaultFormat
3617 defaultFormat : "m/d/Y",
3619 * Get the short month name for the given month number.
3620 * Override this function for international dates.
3621 * @param {Number} month A zero-based javascript month number.
3622 * @return {String} The short month name.
3625 getShortMonthName : function(month) {
3626 return utilDate.monthNames[month].substring(0, 3);
3630 * Get the short day name for the given day number.
3631 * Override this function for international dates.
3632 * @param {Number} day A zero-based javascript day number.
3633 * @return {String} The short day name.
3636 getShortDayName : function(day) {
3637 return utilDate.dayNames[day].substring(0, 3);
3641 * Get the zero-based javascript month number for the given short/full month name.
3642 * Override this function for international dates.
3643 * @param {String} name The short/full month name.
3644 * @return {Number} The zero-based javascript month number.
3647 getMonthNumber : function(name) {
3648 // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3649 return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3653 * Checks if the specified format contains hour information
3654 * @param {String} format The format to check
3655 * @return {Boolean} True if the format contains hour information
3659 formatContainsHourInfo : (function(){
3660 var stripEscapeRe = /(\\.)/g,
3661 hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3662 return function(format){
3663 return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3668 * Checks if the specified format contains information about
3669 * anything other than the time.
3670 * @param {String} format The format to check
3671 * @return {Boolean} True if the format contains information about
3672 * date/day information.
3676 formatContainsDateInfo : (function(){
3677 var stripEscapeRe = /(\\.)/g,
3678 dateInfoRe = /([djzmnYycU]|MS)/;
3680 return function(format){
3681 return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3686 * The base format-code to formatting-function hashmap used by the {@link #format} method.
3687 * Formatting functions are strings (or functions which return strings) which
3688 * will return the appropriate value when evaluated in the context of the Date object
3689 * from which the {@link #format} method is called.
3690 * Add to / override these mappings for custom date formatting.
3691 * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3694 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3695 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3701 d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3702 D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3703 j: "this.getDate()",
3704 l: "Ext.Date.dayNames[this.getDay()]",
3705 N: "(this.getDay() ? this.getDay() : 7)",
3706 S: "Ext.Date.getSuffix(this)",
3708 z: "Ext.Date.getDayOfYear(this)",
3709 W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3710 F: "Ext.Date.monthNames[this.getMonth()]",
3711 m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3712 M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3713 n: "(this.getMonth() + 1)",
3714 t: "Ext.Date.getDaysInMonth(this)",
3715 L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3716 o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3717 Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3718 y: "('' + this.getFullYear()).substring(2, 4)",
3719 a: "(this.getHours() < 12 ? 'am' : 'pm')",
3720 A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3721 g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3722 G: "this.getHours()",
3723 h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3724 H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3725 i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3726 s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3727 u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3728 O: "Ext.Date.getGMTOffset(this)",
3729 P: "Ext.Date.getGMTOffset(this, true)",
3730 T: "Ext.Date.getTimezone(this)",
3731 Z: "(this.getTimezoneOffset() * -60)",
3733 c: function() { // ISO-8601 -- GMT format
3734 for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3735 var e = c.charAt(i);
3736 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3738 return code.join(" + ");
3741 c: function() { // ISO-8601 -- UTC format
3743 "this.getUTCFullYear()", "'-'",
3744 "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3745 "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3747 "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3748 "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3749 "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3755 U: "Math.round(this.getTime() / 1000)"
3759 * Checks if the passed Date parameters will cause a javascript Date "rollover".
3760 * @param {Number} year 4-digit year
3761 * @param {Number} month 1-based month-of-year
3762 * @param {Number} day Day of month
3763 * @param {Number} hour (optional) Hour
3764 * @param {Number} minute (optional) Minute
3765 * @param {Number} second (optional) Second
3766 * @param {Number} millisecond (optional) Millisecond
3767 * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3770 isValid : function(y, m, d, h, i, s, ms) {
3777 // Special handling for year < 100
3778 var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3780 return y == dt.getFullYear() &&
3781 m == dt.getMonth() + 1 &&
3782 d == dt.getDate() &&
3783 h == dt.getHours() &&
3784 i == dt.getMinutes() &&
3785 s == dt.getSeconds() &&
3786 ms == dt.getMilliseconds();
3790 * Parses the passed string using the specified date format.
3791 * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3792 * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3793 * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3794 * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3795 * Keep in mind that the input date string must precisely match the specified format string
3796 * in order for the parse operation to be successful (failed parse operations return a null value).
3797 * <p>Example:</p><pre><code>
3798 //dt = Fri May 25 2007 (current date)
3799 var dt = new Date();
3801 //dt = Thu May 25 2006 (today's month/day in 2006)
3802 dt = Ext.Date.parse("2006", "Y");
3804 //dt = Sun Jan 15 2006 (all date parts specified)
3805 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3807 //dt = Sun Jan 15 2006 15:20:01
3808 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3810 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3811 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3813 * @param {String} input The raw date string.
3814 * @param {String} format The expected date string format.
3815 * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3816 (defaults to false). Invalid date strings will return null when parsed.
3817 * @return {Date} The parsed Date.
3820 parse : function(input, format, strict) {
3821 var p = utilDate.parseFunctions;
3822 if (p[format] == null) {
3823 utilDate.createParser(format);
3825 return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3829 parseDate: function(input, format, strict){
3830 return utilDate.parse(input, format, strict);
3835 getFormatCode : function(character) {
3836 var f = utilDate.formatCodes[character];
3839 f = typeof f == 'function'? f() : f;
3840 utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3843 // note: unknown characters are treated as literals
3844 return f || ("'" + Ext.String.escape(character) + "'");
3848 createFormat : function(format) {
3853 for (var i = 0; i < format.length; ++i) {
3854 ch = format.charAt(i);
3855 if (!special && ch == "\\") {
3857 } else if (special) {
3859 code.push("'" + Ext.String.escape(ch) + "'");
3861 code.push(utilDate.getFormatCode(ch));
3864 utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3868 createParser : (function() {
3870 "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3871 "def = Ext.Date.defaults,",
3872 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3877 "if(u != null){", // i.e. unix time is defined
3878 "v = new Date(u * 1000);", // give top priority to UNIX time
3880 // create Date object representing midnight of the current day;
3881 // this will provide us with our date defaults
3882 // (note: clearTime() handles Daylight Saving Time automatically)
3883 "dt = Ext.Date.clearTime(new Date);",
3885 // date calculations (note: these calculations create a dependency on Ext.Number.from())
3886 "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3887 "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3888 "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3890 // time calculations (note: these calculations create a dependency on Ext.Number.from())
3891 "h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3892 "i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3893 "s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3894 "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3896 "if(z >= 0 && y >= 0){",
3897 // both the year and zero-based day of year are defined and >= 0.
3898 // these 2 values alone provide sufficient info to create a full date object
3900 // create Date object representing January 1st for the given year
3901 // handle years < 100 appropriately
3902 "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3904 // then add day of year, checking for Date "rollover" if necessary
3905 "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3906 "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3907 "v = null;", // invalid date, so return null
3909 // plain old Date object
3910 // handle years < 100 properly
3911 "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3917 // favour UTC offset over GMT offset
3919 // reset to UTC, then add offset
3920 "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3922 // reset to GMT, then add offset
3923 "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
3930 return function(format) {
3931 var regexNum = utilDate.parseRegexes.length,
3938 for (var i = 0; i < format.length; ++i) {
3939 ch = format.charAt(i);
3940 if (!special && ch == "\\") {
3942 } else if (special) {
3944 regex.push(Ext.String.escape(ch));
3946 var obj = utilDate.formatCodeToRegex(ch, currentGroup);
3947 currentGroup += obj.g;
3949 if (obj.g && obj.c) {
3955 utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
3956 utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
3964 * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
3965 * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
3966 * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
3970 c:"d = parseInt(results[{0}], 10);\n",
3971 s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
3975 c:"d = parseInt(results[{0}], 10);\n",
3976 s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
3979 for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
3983 s:"(?:" + a.join("|") +")"
3990 s:"(?:" + utilDate.dayNames.join("|") + ")"
3996 s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
4006 s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
4010 c:"z = parseInt(results[{0}], 10);\n",
4011 s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
4016 s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
4021 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
4022 s:"(" + utilDate.monthNames.join("|") + ")"
4026 for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
4027 return Ext.applyIf({
4028 s:"(" + a.join("|") + ")"
4029 }, utilDate.formatCodeToRegex("F"));
4033 c:"m = parseInt(results[{0}], 10) - 1;\n",
4034 s:"(\\d{2})" // month number with leading zeros (01 - 12)
4038 c:"m = parseInt(results[{0}], 10) - 1;\n",
4039 s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
4044 s:"(?:\\d{2})" // no. of days in the month (28 - 31)
4052 return utilDate.formatCodeToRegex("Y");
4056 c:"y = parseInt(results[{0}], 10);\n",
4057 s:"(\\d{4})" // 4-digit year
4061 c:"var ty = parseInt(results[{0}], 10);\n"
4062 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
4066 * In the am/pm parsing routines, we allow both upper and lower case
4067 * even though it doesn't exactly match the spec. It gives much more flexibility
4068 * in being able to specify case insensitive regexes.
4072 c:"if (/(am)/i.test(results[{0}])) {\n"
4073 + "if (!h || h == 12) { h = 0; }\n"
4074 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4079 c:"if (/(am)/i.test(results[{0}])) {\n"
4080 + "if (!h || h == 12) { h = 0; }\n"
4081 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4085 return utilDate.formatCodeToRegex("G");
4089 c:"h = parseInt(results[{0}], 10);\n",
4090 s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
4093 return utilDate.formatCodeToRegex("H");
4097 c:"h = parseInt(results[{0}], 10);\n",
4098 s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
4102 c:"i = parseInt(results[{0}], 10);\n",
4103 s:"(\\d{2})" // minutes with leading zeros (00 - 59)
4107 c:"s = parseInt(results[{0}], 10);\n",
4108 s:"(\\d{2})" // seconds with leading zeros (00 - 59)
4112 c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
4113 s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
4118 "o = results[{0}];",
4119 "var sn = o.substring(0,1),", // get + / - sign
4120 "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4121 "mn = o.substring(3,5) % 60;", // get minutes
4122 "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
4124 s: "([+\-]\\d{4})" // GMT offset in hrs and mins
4129 "o = results[{0}];",
4130 "var sn = o.substring(0,1),", // get + / - sign
4131 "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4132 "mn = o.substring(4,6) % 60;", // get minutes
4133 "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
4135 s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
4140 s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
4144 c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
4145 + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
4146 s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
4151 utilDate.formatCodeToRegex("Y", 1), // year
4152 utilDate.formatCodeToRegex("m", 2), // month
4153 utilDate.formatCodeToRegex("d", 3), // day
4154 utilDate.formatCodeToRegex("h", 4), // hour
4155 utilDate.formatCodeToRegex("i", 5), // minute
4156 utilDate.formatCodeToRegex("s", 6), // second
4157 {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)
4158 {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
4159 "if(results[8]) {", // timezone specified
4160 "if(results[8] == 'Z'){",
4162 "}else if (results[8].indexOf(':') > -1){",
4163 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
4165 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
4171 for (var i = 0, l = arr.length; i < l; ++i) {
4172 calc.push(arr[i].c);
4179 arr[0].s, // year (required)
4180 "(?:", "-", arr[1].s, // month (optional)
4181 "(?:", "-", arr[2].s, // day (optional)
4183 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
4184 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
4185 "(?::", arr[5].s, ")?", // seconds (optional)
4186 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
4187 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
4196 c:"u = parseInt(results[{0}], 10);\n",
4197 s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
4201 //Old Ext.Date prototype methods.
4203 dateFormat: function(date, format) {
4204 return utilDate.format(date, format);
4208 * Formats a date given the supplied format string.
4209 * @param {Date} date The date to format
4210 * @param {String} format The format string
4211 * @return {String} The formatted date
4213 format: function(date, format) {
4214 if (utilDate.formatFunctions[format] == null) {
4215 utilDate.createFormat(format);
4217 var result = utilDate.formatFunctions[format].call(date);
4222 * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
4224 * Note: The date string returned by the javascript Date object's toString() method varies
4225 * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
4226 * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
4227 * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
4228 * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
4229 * from the GMT offset portion of the date string.
4230 * @param {Date} date The date
4231 * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
4233 getTimezone : function(date) {
4234 // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
4236 // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
4237 // 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)
4238 // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
4239 // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
4240 // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
4242 // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
4243 // step 1: (?:\((.*)\) -- find timezone in parentheses
4244 // 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
4245 // step 3: remove all non uppercase characters found in step 1 and 2
4246 return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
4250 * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
4251 * @param {Date} date The date
4252 * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
4253 * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
4255 getGMTOffset : function(date, colon) {
4256 var offset = date.getTimezoneOffset();
4257 return (offset > 0 ? "-" : "+")
4258 + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
4259 + (colon ? ":" : "")
4260 + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
4264 * Get the numeric day number of the year, adjusted for leap year.
4265 * @param {Date} date The date
4266 * @return {Number} 0 to 364 (365 in leap years).
4268 getDayOfYear: function(date) {
4270 d = Ext.Date.clone(date),
4271 m = date.getMonth(),
4274 for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
4275 num += utilDate.getDaysInMonth(d);
4277 return num + date.getDate() - 1;
4281 * Get the numeric ISO-8601 week number of the year.
4282 * (equivalent to the format specifier 'W', but without a leading zero).
4283 * @param {Date} date The date
4284 * @return {Number} 1 to 53
4287 getWeekOfYear : (function() {
4288 // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4289 var ms1d = 864e5, // milliseconds in a day
4290 ms7d = 7 * ms1d; // milliseconds in a week
4292 return function(date) { // return a closure so constants get calculated only once
4293 var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4294 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4295 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4297 return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4302 * Checks if the current date falls within a leap year.
4303 * @param {Date} date The date
4304 * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4306 isLeapYear : function(date) {
4307 var year = date.getFullYear();
4308 return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4312 * Get the first day of the current month, adjusted for leap year. The returned value
4313 * is the numeric day index within the week (0-6) which can be used in conjunction with
4314 * the {@link #monthNames} array to retrieve the textual day name.
4317 var dt = new Date('1/10/2007'),
4318 firstDay = Ext.Date.getFirstDayOfMonth(dt);
4319 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4321 * @param {Date} date The date
4322 * @return {Number} The day number (0-6).
4324 getFirstDayOfMonth : function(date) {
4325 var day = (date.getDay() - (date.getDate() - 1)) % 7;
4326 return (day < 0) ? (day + 7) : day;
4330 * Get the last day of the current month, adjusted for leap year. The returned value
4331 * is the numeric day index within the week (0-6) which can be used in conjunction with
4332 * the {@link #monthNames} array to retrieve the textual day name.
4335 var dt = new Date('1/10/2007'),
4336 lastDay = Ext.Date.getLastDayOfMonth(dt);
4337 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4339 * @param {Date} date The date
4340 * @return {Number} The day number (0-6).
4342 getLastDayOfMonth : function(date) {
4343 return utilDate.getLastDateOfMonth(date).getDay();
4348 * Get the date of the first day of the month in which this date resides.
4349 * @param {Date} date The date
4352 getFirstDateOfMonth : function(date) {
4353 return new Date(date.getFullYear(), date.getMonth(), 1);
4357 * Get the date of the last day of the month in which this date resides.
4358 * @param {Date} date The date
4361 getLastDateOfMonth : function(date) {
4362 return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4366 * Get the number of days in the current month, adjusted for leap year.
4367 * @param {Date} date The date
4368 * @return {Number} The number of days in the month.
4371 getDaysInMonth: (function() {
4372 var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4374 return function(date) { // return a closure for efficiency
4375 var m = date.getMonth();
4377 return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4382 * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4383 * @param {Date} date The date
4384 * @return {String} 'st, 'nd', 'rd' or 'th'.
4386 getSuffix : function(date) {
4387 switch (date.getDate()) {
4404 * Creates and returns a new Date instance with the exact same date value as the called instance.
4405 * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4406 * variable will also be changed. When the intention is to create a new variable that will not
4407 * modify the original instance, you should create a clone.
4409 * Example of correctly cloning a date:
4412 var orig = new Date('10/1/2006');
4415 console.log(orig); //returns 'Thu Oct 05 2006'!
4418 var orig = new Date('10/1/2006'),
4419 copy = Ext.Date.clone(orig);
4421 console.log(orig); //returns 'Thu Oct 01 2006'
4423 * @param {Date} date The date
4424 * @return {Date} The new Date instance.
4426 clone : function(date) {
4427 return new Date(date.getTime());
4431 * Checks if the current date is affected by Daylight Saving Time (DST).
4432 * @param {Date} date The date
4433 * @return {Boolean} True if the current date is affected by DST.
4435 isDST : function(date) {
4436 // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4437 // courtesy of @geoffrey.mcgill
4438 return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4442 * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4443 * automatically adjusting for Daylight Saving Time (DST) where applicable.
4444 * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4445 * @param {Date} date The date
4446 * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4447 * @return {Date} this or the clone.
4449 clearTime : function(date, clone) {
4451 return Ext.Date.clearTime(Ext.Date.clone(date));
4454 // get current date before clearing time
4455 var d = date.getDate();
4461 date.setMilliseconds(0);
4463 if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4464 // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4465 // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4467 // increment hour until cloned date == current date
4468 for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4471 date.setHours(c.getHours());
4478 * Provides a convenient method for performing basic date arithmetic. This method
4479 * does not modify the Date instance being called - it creates and returns
4480 * a new Date instance containing the resulting date value.
4485 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4486 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4488 // Negative values will be subtracted:
4489 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4490 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4494 * @param {Date} date The date to modify
4495 * @param {String} interval A valid date interval enum value.
4496 * @param {Number} value The amount to add to the current date.
4497 * @return {Date} The new Date instance.
4499 add : function(date, interval, value) {
4500 var d = Ext.Date.clone(date),
4502 if (!interval || value === 0) return d;
4504 switch(interval.toLowerCase()) {
4505 case Ext.Date.MILLI:
4506 d.setMilliseconds(d.getMilliseconds() + value);
4508 case Ext.Date.SECOND:
4509 d.setSeconds(d.getSeconds() + value);
4511 case Ext.Date.MINUTE:
4512 d.setMinutes(d.getMinutes() + value);
4515 d.setHours(d.getHours() + value);
4518 d.setDate(d.getDate() + value);
4520 case Ext.Date.MONTH:
4521 var day = date.getDate();
4523 day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4526 d.setMonth(date.getMonth() + value);
4529 d.setFullYear(date.getFullYear() + value);
4536 * Checks if a date falls on or between the given start and end dates.
4537 * @param {Date} date The date to check
4538 * @param {Date} start Start date
4539 * @param {Date} end End date
4540 * @return {Boolean} true if this date falls on or between the given start and end dates.
4542 between : function(date, start, end) {
4543 var t = date.getTime();
4544 return start.getTime() <= t && t <= end.getTime();
4547 //Maintains compatibility with old static and prototype window.Date methods.
4548 compat: function() {
4549 var nativeDate = window.Date,
4551 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'],
4552 proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4555 Ext.Array.forEach(statics, function(s) {
4556 nativeDate[s] = utilDate[s];
4559 //Append to prototype
4560 Ext.Array.forEach(proto, function(s) {
4561 nativeDate.prototype[s] = function() {
4562 var args = Array.prototype.slice.call(arguments);
4564 return utilDate[s].apply(utilDate, args);
4570 var utilDate = Ext.Date;
4575 * @author Jacky Nguyen <jacky@sencha.com>
4576 * @docauthor Jacky Nguyen <jacky@sencha.com>
4579 * The root of all classes created with {@link Ext#define}
4580 * All prototype and static members of this class are inherited by any other class
4583 (function(flexSetter) {
4585 var Base = Ext.Base = function() {};
4587 $className: 'Ext.Base',
4592 * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4593 * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4594 * for a detailed comparison
4596 * Ext.define('My.Cat', {
4598 * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4601 * constructor: function() {
4602 * alert(this.self.speciesName); / dependent on 'this'
4607 * clone: function() {
4608 * return new this.self();
4613 * Ext.define('My.SnowLeopard', {
4616 * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
4620 * var cat = new My.Cat(); // alerts 'Cat'
4621 * var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
4623 * var clone = snowLeopard.clone();
4624 * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
4631 // Default constructor, simply returns `this`
4632 constructor: function() {
4637 * Initialize configuration for this class. a typical example:
4639 * Ext.define('My.awesome.Class', {
4640 * // The default config
4646 * constructor: function(config) {
4647 * this.initConfig(config);
4653 * var awesome = new My.awesome.Class({
4654 * name: 'Super Awesome'
4657 * alert(awesome.getName()); // 'Super Awesome'
4660 * @param {Object} config
4661 * @return {Object} mixins The mixin prototypes as key - value pairs
4663 initConfig: function(config) {
4664 if (!this.$configInited) {
4665 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4667 this.applyConfig(this.config);
4669 this.$configInited = true;
4678 setConfig: function(config) {
4679 this.applyConfig(config || {});
4687 applyConfig: flexSetter(function(name, value) {
4688 var setter = 'set' + Ext.String.capitalize(name);
4690 if (typeof this[setter] === 'function') {
4691 this[setter].call(this, value);
4698 * Call the parent's overridden method. For example:
4700 * Ext.define('My.own.A', {
4701 * constructor: function(test) {
4706 * Ext.define('My.own.B', {
4707 * extend: 'My.own.A',
4709 * constructor: function(test) {
4712 * this.callParent([test + 1]);
4716 * Ext.define('My.own.C', {
4717 * extend: 'My.own.B',
4719 * constructor: function() {
4720 * alert("Going to call parent's overriden constructor...");
4722 * this.callParent(arguments);
4726 * var a = new My.own.A(1); // alerts '1'
4727 * var b = new My.own.B(1); // alerts '1', then alerts '2'
4728 * var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4729 * // alerts '2', then alerts '3'
4732 * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4733 * from the current method, for example: `this.callParent(arguments)`
4734 * @return {Mixed} Returns the result from the superclass' method
4736 callParent: function(args) {
4737 var method = this.callParent.caller,
4738 parentClass, methodName;
4740 if (!method.$owner) {
4741 if (!method.caller) {
4743 sourceClass: Ext.getClassName(this),
4744 sourceMethod: "callParent",
4745 msg: "Attempting to call a protected method from the public scope, which is not allowed"
4749 method = method.caller;
4752 parentClass = method.$owner.superclass;
4753 methodName = method.$name;
4755 if (!(methodName in parentClass)) {
4757 sourceClass: Ext.getClassName(this),
4758 sourceMethod: methodName,
4759 msg: "this.callParent() was called but there's no such method (" + methodName +
4760 ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
4764 return parentClass[methodName].apply(this, args || []);
4769 * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4770 * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4771 * `this` points to during run-time
4773 * Ext.define('My.Cat', {
4776 * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4779 * constructor: function() {
4780 * var statics = this.statics();
4782 * alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
4783 * // equivalent to: My.Cat.speciesName
4785 * alert(this.self.speciesName); // dependent on 'this'
4787 * statics.totalCreated++;
4792 * clone: function() {
4793 * var cloned = new this.self; // dependent on 'this'
4795 * cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
4802 * Ext.define('My.SnowLeopard', {
4806 * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
4809 * constructor: function() {
4810 * this.callParent();
4814 * var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
4816 * var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4818 * var clone = snowLeopard.clone();
4819 * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
4820 * alert(clone.groupName); // alerts 'Cat'
4822 * alert(My.Cat.totalCreated); // alerts 3
4827 statics: function() {
4828 var method = this.statics.caller,
4835 return method.$owner;
4839 * Call the original method that was previously overridden with {@link Ext.Base#override}
4841 * Ext.define('My.Cat', {
4842 * constructor: function() {
4843 * alert("I'm a cat!");
4850 * constructor: function() {
4851 * alert("I'm going to be a cat!");
4853 * var instance = this.callOverridden();
4855 * alert("Meeeeoooowwww");
4861 * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4862 * // alerts "I'm a cat!"
4863 * // alerts "Meeeeoooowwww"
4865 * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4866 * @return {Mixed} Returns the result after calling the overridden method
4868 callOverridden: function(args) {
4869 var method = this.callOverridden.caller;
4871 if (!method.$owner) {
4873 sourceClass: Ext.getClassName(this),
4874 sourceMethod: "callOverridden",
4875 msg: "Attempting to call a protected method from the public scope, which is not allowed"
4879 if (!method.$previous) {
4881 sourceClass: Ext.getClassName(this),
4882 sourceMethod: "callOverridden",
4883 msg: "this.callOverridden was called in '" + method.$name +
4884 "' but this method has never been overridden"
4888 return method.$previous.apply(this, args || []);
4891 destroy: function() {}
4894 // These static properties will be copied to every newly created class with {@link Ext#define}
4895 Ext.apply(Ext.Base, {
4897 * Create a new instance of this Class.
4899 * Ext.define('My.cool.Class', {
4903 * My.cool.Class.create({
4907 * All parameters are passed to the constructor of the class.
4909 * @return {Object} the created instance.
4912 create: function() {
4913 return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4919 own: flexSetter(function(name, value) {
4920 if (typeof value === 'function') {
4921 this.ownMethod(name, value);
4924 this.prototype[name] = value;
4931 ownMethod: function(name, fn) {
4934 if (fn.$owner !== undefined && fn !== Ext.emptyFn) {
4938 return originalFn.apply(this, arguments);
4943 className = Ext.getClassName(this);
4945 fn.displayName = className + '#' + name;
4950 this.prototype[name] = fn;
4954 * Add / override static properties of this class.
4956 * Ext.define('My.cool.Class', {
4960 * My.cool.Class.addStatics({
4961 * someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
4962 * method1: function() { ... }, // My.cool.Class.method1 = function() { ... };
4963 * method2: function() { ... } // My.cool.Class.method2 = function() { ... };
4966 * @param {Object} members
4967 * @return {Ext.Base} this
4970 addStatics: function(members) {
4971 for (var name in members) {
4972 if (members.hasOwnProperty(name)) {
4973 this[name] = members[name];
4981 * Add methods / properties to the prototype of this class.
4983 * Ext.define('My.awesome.Cat', {
4984 * constructor: function() {
4989 * My.awesome.Cat.implement({
4990 * meow: function() {
4991 * alert('Meowww...');
4995 * var kitty = new My.awesome.Cat;
4998 * @param {Object} members
5001 implement: function(members) {
5002 var prototype = this.prototype,
5003 name, i, member, previous;
5004 var className = Ext.getClassName(this);
5005 for (name in members) {
5006 if (members.hasOwnProperty(name)) {
5007 member = members[name];
5009 if (typeof member === 'function') {
5010 member.$owner = this;
5011 member.$name = name;
5013 member.displayName = className + '#' + name;
5017 prototype[name] = member;
5021 if (Ext.enumerables) {
5022 var enumerables = Ext.enumerables;
5024 for (i = enumerables.length; i--;) {
5025 name = enumerables[i];
5027 if (members.hasOwnProperty(name)) {
5028 member = members[name];
5029 member.$owner = this;
5030 member.$name = name;
5031 prototype[name] = member;
5038 * Borrow another class' members to the prototype of this class.
5040 * Ext.define('Bank', {
5042 * printMoney: function() {
5047 * Ext.define('Thief', {
5051 * Thief.borrow(Bank, ['money', 'printMoney']);
5053 * var steve = new Thief();
5055 * alert(steve.money); // alerts '$$$'
5056 * steve.printMoney(); // alerts '$$$$$$$'
5058 * @param {Ext.Base} fromClass The class to borrow members from
5059 * @param {Array/String} members The names of the members to borrow
5060 * @return {Ext.Base} this
5064 borrow: function(fromClass, members) {
5065 var fromPrototype = fromClass.prototype,
5068 members = Ext.Array.from(members);
5070 for (i = 0, ln = members.length; i < ln; i++) {
5071 member = members[i];
5073 this.own(member, fromPrototype[member]);
5080 * Override prototype members of this class. Overridden methods can be invoked via
5081 * {@link Ext.Base#callOverridden}
5083 * Ext.define('My.Cat', {
5084 * constructor: function() {
5085 * alert("I'm a cat!");
5092 * constructor: function() {
5093 * alert("I'm going to be a cat!");
5095 * var instance = this.callOverridden();
5097 * alert("Meeeeoooowwww");
5103 * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
5104 * // alerts "I'm a cat!"
5105 * // alerts "Meeeeoooowwww"
5107 * @param {Object} members
5108 * @return {Ext.Base} this
5111 override: function(members) {
5112 var prototype = this.prototype,
5113 name, i, member, previous;
5115 for (name in members) {
5116 if (members.hasOwnProperty(name)) {
5117 member = members[name];
5119 if (typeof member === 'function') {
5120 if (typeof prototype[name] === 'function') {
5121 previous = prototype[name];
5122 member.$previous = previous;
5125 this.ownMethod(name, member);
5128 prototype[name] = member;
5133 if (Ext.enumerables) {
5134 var enumerables = Ext.enumerables;
5136 for (i = enumerables.length; i--;) {
5137 name = enumerables[i];
5139 if (members.hasOwnProperty(name)) {
5140 if (prototype[name] !== undefined) {
5141 previous = prototype[name];
5142 members[name].$previous = previous;
5145 this.ownMethod(name, members[name]);
5154 * Used internally by the mixins pre-processor
5157 mixin: flexSetter(function(name, cls) {
5158 var mixin = cls.prototype,
5159 my = this.prototype,
5163 if (mixin.hasOwnProperty(i)) {
5164 if (my[i] === undefined) {
5165 if (typeof mixin[i] === 'function') {
5168 if (fn.$owner === undefined) {
5169 this.ownMethod(i, fn);
5179 else if (i === 'config' && my.config && mixin.config) {
5180 Ext.Object.merge(my.config, mixin.config);
5185 if (my.mixins === undefined) {
5189 my.mixins[name] = mixin;
5193 * Get the current class' name in string format.
5195 * Ext.define('My.cool.Class', {
5196 * constructor: function() {
5197 * alert(this.self.getName()); // alerts 'My.cool.Class'
5201 * My.cool.Class.getName(); // 'My.cool.Class'
5203 * @return {String} className
5205 getName: function() {
5206 return Ext.getClassName(this);
5210 * Create aliases for existing prototype methods. Example:
5212 * Ext.define('My.cool.Class', {
5213 * method1: function() { ... },
5214 * method2: function() { ... }
5217 * var test = new My.cool.Class();
5219 * My.cool.Class.createAlias({
5220 * method3: 'method1',
5221 * method4: 'method2'
5224 * test.method3(); // test.method1()
5226 * My.cool.Class.createAlias('method5', 'method3');
5228 * test.method5(); // test.method3() -> test.method1()
5230 * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
5231 * {@link Ext.Function#flexSetter flexSetter}
5232 * @param {String/Object} origin The original method name
5236 createAlias: flexSetter(function(alias, origin) {
5237 this.prototype[alias] = this.prototype[origin];
5241 })(Ext.Function.flexSetter);
5244 * @author Jacky Nguyen <jacky@sencha.com>
5245 * @docauthor Jacky Nguyen <jacky@sencha.com>
5248 * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
5249 * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
5250 * to enable namespacing and dynamic dependency resolution.
5254 * Ext.define(className, properties);
5256 * in which `properties` is an object represent a collection of properties that apply to the class. See
5257 * {@link Ext.ClassManager#create} for more detailed instructions.
5259 * Ext.define('Person', {
5262 * constructor: function(name) {
5270 * eat: function(foodType) {
5271 * alert("I'm eating: " + foodType);
5277 * var aaron = new Person("Aaron");
5278 * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5280 * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5281 * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5285 * Ext.define('Developer', {
5288 * constructor: function(name, isGeek) {
5289 * this.isGeek = isGeek;
5291 * // Apply a method from the parent class' prototype
5292 * this.callParent([name]);
5298 * code: function(language) {
5299 * alert("I'm coding in: " + language);
5307 * var jacky = new Developer("Jacky", true);
5308 * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
5309 * // alert("I'm eating: Bugs");
5311 * See {@link Ext.Base#callParent} for more details on calling superclass' methods
5315 * Ext.define('CanPlayGuitar', {
5316 * playGuitar: function() {
5317 * alert("F#...G...D...A");
5321 * Ext.define('CanComposeSongs', {
5322 * composeSongs: function() { ... }
5325 * Ext.define('CanSing', {
5326 * sing: function() {
5327 * alert("I'm on the highway to hell...")
5331 * Ext.define('Musician', {
5335 * canPlayGuitar: 'CanPlayGuitar',
5336 * canComposeSongs: 'CanComposeSongs',
5337 * canSing: 'CanSing'
5341 * Ext.define('CoolPerson', {
5345 * canPlayGuitar: 'CanPlayGuitar',
5346 * canSing: 'CanSing'
5349 * sing: function() {
5350 * alert("Ahem....");
5352 * this.mixins.canSing.sing.call(this);
5354 * alert("[Playing guitar at the same time...]");
5356 * this.playGuitar();
5360 * var me = new CoolPerson("Jacky");
5362 * me.sing(); // alert("Ahem...");
5363 * // alert("I'm on the highway to hell...");
5364 * // alert("[Playing guitar at the same time...]");
5365 * // alert("F#...G...D...A");
5369 * Ext.define('SmartPhone', {
5371 * hasTouchScreen: false,
5372 * operatingSystem: 'Other',
5376 * isExpensive: false,
5378 * constructor: function(config) {
5379 * this.initConfig(config);
5384 * applyPrice: function(price) {
5385 * this.isExpensive = (price > 500);
5390 * applyOperatingSystem: function(operatingSystem) {
5391 * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
5395 * return operatingSystem;
5399 * var iPhone = new SmartPhone({
5400 * hasTouchScreen: true,
5401 * operatingSystem: 'iOS'
5404 * iPhone.getPrice(); // 500;
5405 * iPhone.getOperatingSystem(); // 'iOS'
5406 * iPhone.getHasTouchScreen(); // true;
5407 * iPhone.hasTouchScreen(); // true
5409 * iPhone.isExpensive; // false;
5410 * iPhone.setPrice(600);
5411 * iPhone.getPrice(); // 600
5412 * iPhone.isExpensive; // true;
5414 * iPhone.setOperatingSystem('AlienOS');
5415 * iPhone.getOperatingSystem(); // 'Other'
5419 * Ext.define('Computer', {
5421 * factory: function(brand) {
5422 * // 'this' in static methods refer to the class itself
5423 * return new this(brand);
5427 * constructor: function() { ... }
5430 * var dellComputer = Computer.factory('Dell');
5432 * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
5433 * static properties within class methods
5440 baseStaticProperties = [],
5443 for (baseStaticProperty in Base) {
5444 if (Base.hasOwnProperty(baseStaticProperty)) {
5445 baseStaticProperties.push(baseStaticProperty);
5450 * @method constructor
5451 * Creates new class.
5452 * @param {Object} classData An object represent the properties of this class
5453 * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
5454 * Note that the creation process can be asynchronous depending on the pre-processors used.
5455 * @return {Ext.Base} The newly created class
5457 Ext.Class = Class = function(newClass, classData, onClassCreated) {
5458 if (typeof newClass !== 'function') {
5459 onClassCreated = classData;
5460 classData = newClass;
5461 newClass = function() {
5462 return this.constructor.apply(this, arguments);
5470 var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5471 registeredPreprocessors = Class.getPreprocessors(),
5474 preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
5476 for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5477 staticPropertyName = baseStaticProperties[i];
5478 newClass[staticPropertyName] = Base[staticPropertyName];
5481 delete classData.preprocessors;
5483 for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5484 preprocessor = preprocessorStack[j];
5486 if (typeof preprocessor === 'string') {
5487 preprocessor = registeredPreprocessors[preprocessor];
5489 if (!preprocessor.always) {
5490 if (classData.hasOwnProperty(preprocessor.name)) {
5491 preprocessors.push(preprocessor.fn);
5495 preprocessors.push(preprocessor.fn);
5499 preprocessors.push(preprocessor);
5503 classData.onClassCreated = onClassCreated;
5505 classData.onBeforeClassCreated = function(cls, data) {
5506 onClassCreated = data.onClassCreated;
5508 delete data.onBeforeClassCreated;
5509 delete data.onClassCreated;
5511 cls.implement(data);
5513 if (onClassCreated) {
5514 onClassCreated.call(cls, cls);
5518 process = function(cls, data) {
5519 preprocessor = preprocessors[index++];
5521 if (!preprocessor) {
5522 data.onBeforeClassCreated.apply(this, arguments);
5526 if (preprocessor.call(this, cls, data, process) !== false) {
5527 process.apply(this, arguments);
5531 process.call(Class, newClass, classData);
5542 * Register a new pre-processor to be used during the class creation process
5544 * @member Ext.Class registerPreprocessor
5545 * @param {String} name The pre-processor's name
5546 * @param {Function} fn The callback function to be executed. Typical format:
5548 function(cls, data, fn) {
5551 // Execute this when the processing is finished.
5552 // Asynchronous processing is perfectly ok
5554 fn.call(this, cls, data);
5558 * Passed arguments for this function are:
5560 * - `{Function} cls`: The created class
5561 * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
5562 * - `{Function} fn`: The callback function that <b>must</b> to be executed when this pre-processor finishes,
5563 * regardless of whether the processing is synchronous or aynchronous
5565 * @return {Ext.Class} this
5568 registerPreprocessor: function(name, fn, always) {
5569 this.preprocessors[name] = {
5571 always: always || false,
5579 * Retrieve a pre-processor callback function by its name, which has been registered before
5581 * @param {String} name
5582 * @return {Function} preprocessor
5584 getPreprocessor: function(name) {
5585 return this.preprocessors[name];
5588 getPreprocessors: function() {
5589 return this.preprocessors;
5593 * Retrieve the array stack of default pre-processors
5595 * @return {Function} defaultPreprocessors
5597 getDefaultPreprocessors: function() {
5598 return this.defaultPreprocessors || [];
5602 * Set the default array stack of default pre-processors
5604 * @param {Array} preprocessors
5605 * @return {Ext.Class} this
5607 setDefaultPreprocessors: function(preprocessors) {
5608 this.defaultPreprocessors = Ext.Array.from(preprocessors);
5614 * Insert this pre-processor at a specific position in the stack, optionally relative to
5615 * any existing pre-processor. For example:
5617 Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5621 fn.call(this, cls, data);
5623 }).insertDefaultPreprocessor('debug', 'last');
5625 * @param {String} name The pre-processor name. Note that it needs to be registered with
5626 * {@link Ext#registerPreprocessor registerPreprocessor} before this
5627 * @param {String} offset The insertion position. Four possible values are:
5628 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5629 * @param {String} relativeName
5630 * @return {Ext.Class} this
5633 setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5634 var defaultPreprocessors = this.defaultPreprocessors,
5637 if (typeof offset === 'string') {
5638 if (offset === 'first') {
5639 defaultPreprocessors.unshift(name);
5643 else if (offset === 'last') {
5644 defaultPreprocessors.push(name);
5649 offset = (offset === 'after') ? 1 : -1;
5652 index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5655 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
5663 * @cfg {String} extend
5664 * The parent class that this class extends. For example:
5666 * Ext.define('Person', {
5667 * say: function(text) { alert(text); }
5670 * Ext.define('Developer', {
5672 * say: function(text) { this.callParent(["print "+text]); }
5675 Class.registerPreprocessor('extend', function(cls, data) {
5676 var extend = data.extend,
5678 basePrototype = base.prototype,
5679 prototype = function() {},
5680 parent, i, k, ln, staticName, parentStatics,
5681 parentPrototype, clsPrototype;
5683 if (extend && extend !== Object) {
5690 parentPrototype = parent.prototype;
5692 prototype.prototype = parentPrototype;
5693 clsPrototype = cls.prototype = new prototype();
5695 if (!('$class' in parent)) {
5696 for (i in basePrototype) {
5697 if (!parentPrototype[i]) {
5698 parentPrototype[i] = basePrototype[i];
5703 clsPrototype.self = cls;
5705 cls.superclass = clsPrototype.superclass = parentPrototype;
5709 // Statics inheritance
5710 parentStatics = parentPrototype.$inheritableStatics;
5712 if (parentStatics) {
5713 for (k = 0, ln = parentStatics.length; k < ln; k++) {
5714 staticName = parentStatics[k];
5716 if (!cls.hasOwnProperty(staticName)) {
5717 cls[staticName] = parent[staticName];
5722 // Merge the parent class' config object without referencing it
5723 if (parentPrototype.config) {
5724 clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5727 clsPrototype.config = {};
5730 if (clsPrototype.$onExtended) {
5731 clsPrototype.$onExtended.call(cls, cls, data);
5734 if (data.onClassExtended) {
5735 clsPrototype.$onExtended = data.onClassExtended;
5736 delete data.onClassExtended;
5742 * @cfg {Object} statics
5743 * List of static methods for this class. For example:
5745 * Ext.define('Computer', {
5747 * factory: function(brand) {
5748 * // 'this' in static methods refer to the class itself
5749 * return new this(brand);
5753 * constructor: function() { ... }
5756 * var dellComputer = Computer.factory('Dell');
5758 Class.registerPreprocessor('statics', function(cls, data) {
5759 var statics = data.statics,
5762 for (name in statics) {
5763 if (statics.hasOwnProperty(name)) {
5764 cls[name] = statics[name];
5768 delete data.statics;
5772 * @cfg {Object} inheritableStatics
5773 * List of inheritable static methods for this class.
5774 * Otherwise just like {@link #statics} but subclasses inherit these methods.
5776 Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5777 var statics = data.inheritableStatics,
5779 prototype = cls.prototype,
5782 inheritableStatics = prototype.$inheritableStatics;
5784 if (!inheritableStatics) {
5785 inheritableStatics = prototype.$inheritableStatics = [];
5788 for (name in statics) {
5789 if (statics.hasOwnProperty(name)) {
5790 cls[name] = statics[name];
5791 inheritableStatics.push(name);
5795 delete data.inheritableStatics;
5799 * @cfg {Object} mixins
5800 * List of classes to mix into this class. For example:
5802 * Ext.define('CanSing', {
5803 * sing: function() {
5804 * alert("I'm on the highway to hell...")
5808 * Ext.define('Musician', {
5812 * canSing: 'CanSing'
5816 Class.registerPreprocessor('mixins', function(cls, data) {
5817 cls.mixin(data.mixins);
5823 * @cfg {Object} config
5824 * List of configuration options with their default values, for which automatically
5825 * accessor methods are generated. For example:
5827 * Ext.define('SmartPhone', {
5829 * hasTouchScreen: false,
5830 * operatingSystem: 'Other',
5833 * constructor: function(cfg) {
5834 * this.initConfig(cfg);
5838 * var iPhone = new SmartPhone({
5839 * hasTouchScreen: true,
5840 * operatingSystem: 'iOS'
5843 * iPhone.getPrice(); // 500;
5844 * iPhone.getOperatingSystem(); // 'iOS'
5845 * iPhone.getHasTouchScreen(); // true;
5846 * iPhone.hasTouchScreen(); // true
5848 Class.registerPreprocessor('config', function(cls, data) {
5849 var prototype = cls.prototype;
5851 Ext.Object.each(data.config, function(name) {
5852 var cName = name.charAt(0).toUpperCase() + name.substr(1),
5854 apply = 'apply' + cName,
5855 setter = 'set' + cName,
5856 getter = 'get' + cName;
5858 if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5859 data[apply] = function(val) {
5864 if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5865 data[setter] = function(val) {
5866 var ret = this[apply].call(this, val, this[pName]);
5868 if (ret !== undefined) {
5876 if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5877 data[getter] = function() {
5883 Ext.Object.merge(prototype.config, data.config);
5887 Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
5889 // Backwards compatible
5890 Ext.extend = function(subclass, superclass, members) {
5891 if (arguments.length === 2 && Ext.isObject(superclass)) {
5892 members = superclass;
5893 superclass = subclass;
5900 Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5903 members.extend = superclass;
5904 members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
5907 cls = new Class(subclass, members);
5910 cls = new Class(members);
5913 cls.prototype.override = function(o) {
5915 if (o.hasOwnProperty(m)) {
5927 * @author Jacky Nguyen <jacky@sencha.com>
5928 * @docauthor Jacky Nguyen <jacky@sencha.com>
5929 * @class Ext.ClassManager
5931 * Ext.ClassManager manages all classes and handles mapping from string class name to
5932 * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5933 * these convenient shorthands:
5935 * - {@link Ext#define Ext.define}
5936 * - {@link Ext#create Ext.create}
5937 * - {@link Ext#widget Ext.widget}
5938 * - {@link Ext#getClass Ext.getClass}
5939 * - {@link Ext#getClassName Ext.getClassName}
5943 (function(Class, alias) {
5945 var slice = Array.prototype.slice;
5947 var Manager = Ext.ClassManager = {
5950 * @property {Object} classes
5951 * All classes which were defined through the ClassManager. Keys are the
5952 * name of the classes and the values are references to the classes.
5965 namespaceRewrites: [{
5974 alternateToName: {},
5980 enableNamespaceParseCache: true,
5983 namespaceParseCache: {},
5989 instantiationCounts: {},
5992 * Checks if a class has already been created.
5994 * @param {String} className
5995 * @return {Boolean} exist
5997 isCreated: function(className) {
5998 var i, ln, part, root, parts;
6000 if (typeof className !== 'string' || className.length < 1) {
6002 sourceClass: "Ext.ClassManager",
6003 sourceMethod: "exist",
6004 msg: "Invalid classname, must be a string and must not be empty"
6008 if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
6013 parts = this.parseNamespace(className);
6015 for (i = 0, ln = parts.length; i < ln; i++) {
6018 if (typeof part !== 'string') {
6021 if (!root || !root[part]) {
6029 Ext.Loader.historyPush(className);
6031 this.existCache[className] = true;
6037 * Supports namespace rewriting
6040 parseNamespace: function(namespace) {
6041 if (typeof namespace !== 'string') {
6043 sourceClass: "Ext.ClassManager",
6044 sourceMethod: "parseNamespace",
6045 msg: "Invalid namespace, must be a string"
6049 var cache = this.namespaceParseCache;
6051 if (this.enableNamespaceParseCache) {
6052 if (cache.hasOwnProperty(namespace)) {
6053 return cache[namespace];
6058 rewrites = this.namespaceRewrites,
6059 rewrite, from, to, i, ln, root = Ext.global;
6061 for (i = 0, ln = rewrites.length; i < ln; i++) {
6062 rewrite = rewrites[i];
6063 from = rewrite.from;
6066 if (namespace === from || namespace.substring(0, from.length) === from) {
6067 namespace = namespace.substring(from.length);
6069 if (typeof to !== 'string') {
6072 parts = parts.concat(to.split('.'));
6081 parts = parts.concat(namespace.split('.'));
6083 if (this.enableNamespaceParseCache) {
6084 cache[namespace] = parts;
6091 * Creates a namespace and assign the `value` to the created object
6093 * Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
6095 * alert(MyCompany.pkg.Example === someObject); // alerts true
6097 * @param {String} name
6098 * @param {Mixed} value
6100 setNamespace: function(name, value) {
6101 var root = Ext.global,
6102 parts = this.parseNamespace(name),
6106 for (i = 0, ln = parts.length; i < ln; i++) {
6109 if (typeof part !== 'string') {
6126 * The new Ext.ns, supports namespace rewriting
6129 createNamespaces: function() {
6130 var root = Ext.global,
6131 parts, part, i, j, ln, subLn;
6133 for (i = 0, ln = arguments.length; i < ln; i++) {
6134 parts = this.parseNamespace(arguments[i]);
6136 for (j = 0, subLn = parts.length; j < subLn; j++) {
6139 if (typeof part !== 'string') {
6155 * Sets a name reference to a class.
6157 * @param {String} name
6158 * @param {Object} value
6159 * @return {Ext.ClassManager} this
6161 set: function(name, value) {
6162 var targetName = this.getName(value);
6164 this.classes[name] = this.setNamespace(name, value);
6166 if (targetName && targetName !== name) {
6167 this.maps.alternateToName[name] = targetName;
6174 * Retrieve a class by its name.
6176 * @param {String} name
6177 * @return {Class} class
6179 get: function(name) {
6180 if (this.classes.hasOwnProperty(name)) {
6181 return this.classes[name];
6184 var root = Ext.global,
6185 parts = this.parseNamespace(name),
6188 for (i = 0, ln = parts.length; i < ln; i++) {
6191 if (typeof part !== 'string') {
6194 if (!root || !root[part]) {
6206 * Register the alias for a class.
6208 * @param {Class/String} cls a reference to a class or a className
6209 * @param {String} alias Alias to use when referring to this class
6211 setAlias: function(cls, alias) {
6212 var aliasToNameMap = this.maps.aliasToName,
6213 nameToAliasesMap = this.maps.nameToAliases,
6216 if (typeof cls === 'string') {
6219 className = this.getName(cls);
6222 if (alias && aliasToNameMap[alias] !== className) {
6223 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
6224 Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
6225 "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
6228 aliasToNameMap[alias] = className;
6231 if (!nameToAliasesMap[className]) {
6232 nameToAliasesMap[className] = [];
6236 Ext.Array.include(nameToAliasesMap[className], alias);
6243 * Get a reference to the class by its alias.
6245 * @param {String} alias
6246 * @return {Class} class
6248 getByAlias: function(alias) {
6249 return this.get(this.getNameByAlias(alias));
6253 * Get the name of a class by its alias.
6255 * @param {String} alias
6256 * @return {String} className
6258 getNameByAlias: function(alias) {
6259 return this.maps.aliasToName[alias] || '';
6263 * Get the name of a class by its alternate name.
6265 * @param {String} alternate
6266 * @return {String} className
6268 getNameByAlternate: function(alternate) {
6269 return this.maps.alternateToName[alternate] || '';
6273 * Get the aliases of a class by the class name
6275 * @param {String} name
6276 * @return {Array} aliases
6278 getAliasesByName: function(name) {
6279 return this.maps.nameToAliases[name] || [];
6283 * Get the name of the class by its reference or its instance.
6285 * Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
6287 * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
6289 * @param {Class/Object} object
6290 * @return {String} className
6292 getName: function(object) {
6293 return object && object.$className || '';
6297 * Get the class of the provided object; returns null if it's not an instance
6298 * of any class created with Ext.define.
6300 * var component = new Ext.Component();
6302 * Ext.ClassManager.getClass(component); // returns Ext.Component
6304 * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
6306 * @param {Object} object
6307 * @return {Class} class
6309 getClass: function(object) {
6310 return object && object.self || null;
6316 * Ext.ClassManager.create('My.awesome.Class', {
6317 * someProperty: 'something',
6318 * someMethod: function() { ... }
6322 * alert('Created!');
6323 * alert(this === My.awesome.Class); // alerts true
6325 * var myInstance = new this();
6328 * {@link Ext#define Ext.define} is alias for {@link Ext.ClassManager#create Ext.ClassManager.create}.
6330 * @param {String} className The class name to create in string dot-namespaced format, for example:
6331 * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
6332 * It is highly recommended to follow this simple convention:
6334 * - The root and the class name are 'CamelCased'
6335 * - Everything else is lower-cased
6337 * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
6338 * strings, except those in the reserved list below:
6340 * - {@link Ext.Base#self self}
6341 * - {@link Ext.Class#alias alias}
6342 * - {@link Ext.Class#alternateClassName alternateClassName}
6343 * - {@link Ext.Class#config config}
6344 * - {@link Ext.Class#extend extend}
6345 * - {@link Ext.Class#inheritableStatics inheritableStatics}
6346 * - {@link Ext.Class#mixins mixins}
6347 * - {@link Ext.Class#requires requires}
6348 * - {@link Ext.Class#singleton singleton}
6349 * - {@link Ext.Class#statics statics}
6350 * - {@link Ext.Class#uses uses}
6352 * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
6353 * (`this`) will be the newly created class itself.
6354 * @return {Ext.Base}
6356 create: function(className, data, createdFn) {
6359 if (typeof className !== 'string') {
6362 sourceMethod: "define",
6363 msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
6367 data.$className = className;
6369 return new Class(data, function() {
6370 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6371 registeredPostprocessors = manager.postprocessors,
6373 postprocessors = [],
6374 postprocessor, postprocessors, process, i, ln;
6376 delete data.postprocessors;
6378 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6379 postprocessor = postprocessorStack[i];
6381 if (typeof postprocessor === 'string') {
6382 postprocessor = registeredPostprocessors[postprocessor];
6384 if (!postprocessor.always) {
6385 if (data[postprocessor.name] !== undefined) {
6386 postprocessors.push(postprocessor.fn);
6390 postprocessors.push(postprocessor.fn);
6394 postprocessors.push(postprocessor);
6398 process = function(clsName, cls, clsData) {
6399 postprocessor = postprocessors[index++];
6401 if (!postprocessor) {
6402 manager.set(className, cls);
6404 Ext.Loader.historyPush(className);
6407 createdFn.call(cls, cls);
6413 if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6414 process.apply(this, arguments);
6418 process.call(manager, className, this, data);
6423 * Instantiate a class by its alias.
6425 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6426 * attempt to load the class via synchronous loading.
6428 * var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6430 * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
6432 * @param {String} alias
6433 * @param {Mixed} args,... Additional arguments after the alias will be passed to the
6434 * class constructor.
6435 * @return {Object} instance
6437 instantiateByAlias: function() {
6438 var alias = arguments[0],
6439 args = slice.call(arguments),
6440 className = this.getNameByAlias(alias);
6443 className = this.maps.aliasToName[alias];
6448 sourceMethod: "createByAlias",
6449 msg: "Cannot create an instance of unrecognized alias: " + alias
6453 if (Ext.global.console) {
6454 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
6455 "Ext.require('" + alias + "') above Ext.onReady");
6458 Ext.syncRequire(className);
6461 args[0] = className;
6463 return this.instantiate.apply(this, args);
6467 * Instantiate a class by either full name, alias or alternate name.
6469 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6470 * attempt to load the class via synchronous loading.
6472 * For example, all these three lines return the same result:
6475 * var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6478 * var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6480 * // full class name
6481 * var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6483 * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
6485 * @param {String} name
6486 * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
6487 * @return {Object} instance
6489 instantiate: function() {
6490 var name = arguments[0],
6491 args = slice.call(arguments, 1),
6495 if (typeof name !== 'function') {
6496 if ((typeof name !== 'string' || name.length < 1)) {
6499 sourceMethod: "create",
6500 msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
6504 cls = this.get(name);
6510 // No record of this class name, it's possibly an alias, so look it up
6512 possibleName = this.getNameByAlias(name);
6515 name = possibleName;
6517 cls = this.get(name);
6521 // Still no record of this class name, it's possibly an alternate name, so look it up
6523 possibleName = this.getNameByAlternate(name);
6526 name = possibleName;
6528 cls = this.get(name);
6532 // Still not existing at this point, try to load it via synchronous mode as the last resort
6534 if (Ext.global.console) {
6535 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
6536 "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
6539 Ext.syncRequire(name);
6541 cls = this.get(name);
6547 sourceMethod: "create",
6548 msg: "Cannot create an instance of unrecognized class name / alias: " + alias
6552 if (typeof cls !== 'function') {
6555 sourceMethod: "create",
6556 msg: "'" + name + "' is a singleton and cannot be instantiated"
6560 if (!this.instantiationCounts[name]) {
6561 this.instantiationCounts[name] = 0;
6564 this.instantiationCounts[name]++;
6566 return this.getInstantiator(args.length)(cls, args);
6574 dynInstantiate: function(name, args) {
6575 args = Ext.Array.from(args, true);
6578 return this.instantiate.apply(this, args);
6585 getInstantiator: function(length) {
6586 if (!this.instantiators[length]) {
6590 for (i = 0; i < length; i++) {
6591 args.push('a['+i+']');
6594 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6597 return this.instantiators[length];
6608 defaultPostprocessors: [],
6611 * Register a post-processor function.
6613 * @param {String} name
6614 * @param {Function} postprocessor
6616 registerPostprocessor: function(name, fn, always) {
6617 this.postprocessors[name] = {
6619 always: always || false,
6627 * Set the default post processors array stack which are applied to every class.
6629 * @param {String/Array} The name of a registered post processor or an array of registered names.
6630 * @return {Ext.ClassManager} this
6632 setDefaultPostprocessors: function(postprocessors) {
6633 this.defaultPostprocessors = Ext.Array.from(postprocessors);
6639 * Insert this post-processor at a specific position in the stack, optionally relative to
6640 * any existing post-processor
6642 * @param {String} name The post-processor name. Note that it needs to be registered with
6643 * {@link Ext.ClassManager#registerPostprocessor} before this
6644 * @param {String} offset The insertion position. Four possible values are:
6645 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6646 * @param {String} relativeName
6647 * @return {Ext.ClassManager} this
6649 setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6650 var defaultPostprocessors = this.defaultPostprocessors,
6653 if (typeof offset === 'string') {
6654 if (offset === 'first') {
6655 defaultPostprocessors.unshift(name);
6659 else if (offset === 'last') {
6660 defaultPostprocessors.push(name);
6665 offset = (offset === 'after') ? 1 : -1;
6668 index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6671 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
6678 * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6679 * or class names. Expressions support wildcards:
6681 * // returns ['Ext.window.Window']
6682 * var window = Ext.ClassManager.getNamesByExpression('widget.window');
6684 * // returns ['widget.panel', 'widget.window', ...]
6685 * var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6687 * // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6688 * var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6690 * @param {String} expression
6691 * @return {Array} classNames
6694 getNamesByExpression: function(expression) {
6695 var nameToAliasesMap = this.maps.nameToAliases,
6697 name, alias, aliases, possibleName, regex, i, ln;
6699 if (typeof expression !== 'string' || expression.length < 1) {
6701 sourceClass: "Ext.ClassManager",
6702 sourceMethod: "getNamesByExpression",
6703 msg: "Expression " + expression + " is invalid, must be a non-empty string"
6707 if (expression.indexOf('*') !== -1) {
6708 expression = expression.replace(/\*/g, '(.*?)');
6709 regex = new RegExp('^' + expression + '$');
6711 for (name in nameToAliasesMap) {
6712 if (nameToAliasesMap.hasOwnProperty(name)) {
6713 aliases = nameToAliasesMap[name];
6715 if (name.search(regex) !== -1) {
6719 for (i = 0, ln = aliases.length; i < ln; i++) {
6722 if (alias.search(regex) !== -1) {
6732 possibleName = this.getNameByAlias(expression);
6735 names.push(possibleName);
6737 possibleName = this.getNameByAlternate(expression);
6740 names.push(possibleName);
6742 names.push(expression);
6752 * @cfg {[String]} alias
6754 * List of short aliases for class names. Most useful for defining xtypes for widgets:
6756 * Ext.define('MyApp.CoolPanel', {
6757 * extend: 'Ext.panel.Panel',
6758 * alias: ['widget.coolpanel'],
6762 * // Using Ext.create
6763 * Ext.widget('widget.coolpanel');
6764 * // Using the shorthand for widgets and in xtypes
6765 * Ext.widget('panel', {
6767 * {xtype: 'coolpanel', html: 'Foo'},
6768 * {xtype: 'coolpanel', html: 'Bar'}
6772 Manager.registerPostprocessor('alias', function(name, cls, data) {
6773 var aliases = data.alias,
6774 widgetPrefix = 'widget.',
6777 if (!(aliases instanceof Array)) {
6778 aliases = [aliases];
6781 for (i = 0, ln = aliases.length; i < ln; i++) {
6784 if (typeof alias !== 'string') {
6787 sourceMethod: "define",
6788 msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
6792 this.setAlias(cls, alias);
6795 // This is ugly, will change to make use of parseNamespace for alias later on
6796 for (i = 0, ln = aliases.length; i < ln; i++) {
6799 if (alias.substring(0, widgetPrefix.length) === widgetPrefix) {
6800 // Only the first alias with 'widget.' prefix will be used for xtype
6801 cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length);
6808 * @cfg {Boolean} singleton
6810 * When set to true, the class will be instanciated as singleton. For example:
6812 * Ext.define('Logger', {
6814 * log: function(msg) {
6819 * Logger.log('Hello');
6821 Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
6822 fn.call(this, name, new cls(), data);
6827 * @cfg {String/[String]} alternateClassName
6829 * Defines alternate names for this class. For example:
6831 * Ext.define('Developer', {
6832 * alternateClassName: ['Coder', 'Hacker'],
6833 * code: function(msg) {
6834 * alert('Typing... ' + msg);
6838 * var joe = Ext.create('Developer');
6839 * joe.code('stackoverflow');
6841 * var rms = Ext.create('Hacker');
6842 * rms.code('hack hack');
6844 Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
6845 var alternates = data.alternateClassName,
6848 if (!(alternates instanceof Array)) {
6849 alternates = [alternates];
6852 for (i = 0, ln = alternates.length; i < ln; i++) {
6853 alternate = alternates[i];
6855 if (typeof alternate !== 'string') {
6858 sourceMethod: "define",
6859 msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
6863 this.set(alternate, cls);
6867 Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
6873 * @alias Ext.ClassManager#instantiate
6875 create: alias(Manager, 'instantiate'),
6879 * API to be stablized
6881 * @param {Mixed} item
6882 * @param {String} namespace
6884 factory: function(item, namespace) {
6885 if (item instanceof Array) {
6888 for (i = 0, ln = item.length; i < ln; i++) {
6889 item[i] = Ext.factory(item[i], namespace);
6895 var isString = (typeof item === 'string');
6897 if (isString || (item instanceof Object && item.constructor === Object)) {
6898 var name, config = {};
6904 name = item.className;
6906 delete config.className;
6909 if (namespace !== undefined && name.indexOf(namespace) === -1) {
6910 name = namespace + '.' + Ext.String.capitalize(name);
6913 return Ext.create(name, config);
6916 if (typeof item === 'function') {
6917 return Ext.create(item);
6924 * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
6926 * var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
6927 * var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
6931 * @param {String} name xtype of the widget to create.
6932 * @return {Object} widget instance
6934 widget: function(name) {
6935 var args = slice.call(arguments);
6936 args[0] = 'widget.' + name;
6938 return Manager.instantiateByAlias.apply(Manager, args);
6944 * @alias Ext.ClassManager#instantiateByAlias
6946 createByAlias: alias(Manager, 'instantiateByAlias'),
6951 * @alias Ext.ClassManager#create
6953 define: alias(Manager, 'create'),
6958 * @alias Ext.ClassManager#getName
6960 getClassName: alias(Manager, 'getName'),
6964 * @param {Mixed} object
6966 getDisplayName: function(object) {
6967 if (object.displayName) {
6968 return object.displayName;
6971 if (object.$name && object.$class) {
6972 return Ext.getClassName(object.$class) + '#' + object.$name;
6975 if (object.$className) {
6976 return object.$className;
6985 * @alias Ext.ClassManager#getClass
6987 getClass: alias(Manager, 'getClass'),
6990 * Creates namespaces to be used for scoping variables and classes so that they are not global.
6991 * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
6993 * Ext.namespace('Company', 'Company.data');
6995 * // equivalent and preferable to the above syntax
6996 * Ext.namespace('Company.data');
6998 * Company.Widget = function() { ... };
7000 * Company.data.CustomStore = function(config) { ... };
7004 * @param {String} namespace1
7005 * @param {String} namespace2
7006 * @param {String} etc
7007 * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
7009 namespace: alias(Manager, 'createNamespaces')
7013 * Old name for {@link Ext#widget}.
7014 * @deprecated 4.0.0 Use {@link Ext#widget} instead.
7019 Ext.createWidget = Ext.widget;
7022 * Convenient alias for {@link Ext#namespace Ext.namespace}
7025 * @alias Ext#namespace
7027 Ext.ns = Ext.namespace;
7029 Class.registerPreprocessor('className', function(cls, data) {
7030 if (data.$className) {
7031 cls.$className = data.$className;
7032 cls.displayName = cls.$className;
7036 Class.setDefaultPreprocessorPosition('className', 'first');
7038 })(Ext.Class, Ext.Function.alias);
7043 * @author Jacky Nguyen <jacky@sencha.com>
7044 * @docauthor Jacky Nguyen <jacky@sencha.com>
7046 * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
7047 * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
7048 * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons
7051 * # Asynchronous Loading
7055 * + No web server needed: you can run the application via the file system protocol
7056 * (i.e: `file://path/to/your/index.html`)
7057 * + Best possible debugging experience: error messages come with the exact file name and line number
7059 * - *Disadvantages:*
7060 * + Dependencies need to be specified before-hand
7062 * ### Method 1: Explicitly include what you need:
7065 * Ext.require({String/Array} expressions);
7067 * // Example: Single alias
7068 * Ext.require('widget.window');
7070 * // Example: Single class name
7071 * Ext.require('Ext.window.Window');
7073 * // Example: Multiple aliases / class names mix
7074 * Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
7077 * Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
7079 * ### Method 2: Explicitly exclude what you don't need:
7081 * // Syntax: Note that it must be in this chaining format.
7082 * Ext.exclude({String/Array} expressions)
7083 * .require({String/Array} expressions);
7085 * // Include everything except Ext.data.*
7086 * Ext.exclude('Ext.data.*').require('*');Â
7088 * // Include all widgets except widget.checkbox*,
7089 * // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
7090 * Ext.exclude('widget.checkbox*').require('widget.*');
7092 * # Synchronous Loading on Demand
7095 * + There's no need to specify dependencies before-hand, which is always the convenience of including
7098 * - *Disadvantages:*
7099 * + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
7100 * + Must be from the same domain due to XHR restriction
7101 * + Need a web server, same reason as above
7103 * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
7105 * Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
7107 * Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
7109 * Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
7111 * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
7112 * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load
7113 * the given class and all its dependencies.
7115 * # Hybrid Loading - The Best of Both Worlds
7117 * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
7119 * ### Step 1: Start writing your application using synchronous approach.
7121 * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
7123 * Ext.onReady(function(){
7124 * var window = Ext.createWidget('window', {
7131 * title: 'Hello Dialog',
7133 * title: 'Navigation',
7134 * collapsible: true,
7140 * title: 'TabPanel',
7148 * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these:
7150 * [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code ClassManager.js:432
7151 * [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
7153 * Simply copy and paste the suggested code above `Ext.onReady`, e.g.:
7155 * Ext.require('Ext.window.Window');
7156 * Ext.require('Ext.layout.container.Border');
7160 * Everything should now load via asynchronous mode.
7164 * It's important to note that dynamic loading should only be used during development on your local machines.
7165 * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
7166 * the whole process of transitioning from / to between development / maintenance and production as easy as
7167 * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies
7168 * your application needs in the exact loading sequence. It's as simple as concatenating all files in this
7169 * array into one, then include it on top of your application.
7171 * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
7173 (function(Manager, Class, flexSetter, alias) {
7176 dependencyProperties = ['extend', 'mixins', 'requires'],
7179 Loader = Ext.Loader = {
7183 documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
7186 * Flag indicating whether there are still files being loaded
7192 * Maintain the queue for all dependencies. Each item in the array is an object of the format:
7194 * requires: [...], // The required classes for this queue item
7195 * callback: function() { ... } // The function to execute when all classes specified in requires exist
7202 * Maintain the list of files that have already been handled so that they never get double-loaded
7208 * Maintain the list of listeners to execute when all required scripts are fully loaded
7214 * Contains optional dependencies to be loaded last
7217 optionalRequires: [],
7220 * Map of fully qualified class names to an array of dependent classes.
7236 hasFileLoadError: false,
7241 classNameToFilePathMap: {},
7244 * @property {[String]} history
7245 * An array of class names to keep track of the dependency loading order.
7246 * This is not guaranteed to be the same everytime due to the asynchronous nature of the Loader.
7256 * @cfg {Boolean} enabled
7257 * Whether or not to enable the dynamic dependency loading feature Defaults to false
7262 * @cfg {Boolean} disableCaching
7263 * Appends current timestamp to script files to prevent caching Defaults to true
7265 disableCaching: true,
7268 * @cfg {String} disableCachingParam
7269 * The get parameter name for the cache buster's timestamp. Defaults to '_dc'
7271 disableCachingParam: '_dc',
7274 * @cfg {Object} paths
7275 * The mapping from namespaces to file paths
7278 * 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
7279 * // loaded from ./layout/Container.js
7281 * 'My': './src/my_own_folder' // My.layout.Container will be loaded from
7282 * // ./src/my_own_folder/layout/Container.js
7285 * Note that all relative paths are relative to the current HTML document.
7286 * If not being specified, for example, `Other.awesome.Class`
7287 * will simply be loaded from `./Other/awesome/Class.js`
7295 * Set the configuration for the loader. This should be called right after ext-core.js
7296 * (or ext-core-debug.js) is included in the page, e.g.:
7298 * <script type="text/javascript" src="ext-core-debug.js"></script>
7299 * <script type="text/javascript">
7300 * Ext.Loader.setConfig({
7303 * 'My': 'my_own_path'
7307 * <script type="text/javascript">
7310 * Ext.onReady(function() {
7311 * // application code here
7315 * Refer to config options of {@link Ext.Loader} for the list of possible properties.
7317 * @param {String/Object} name Name of the value to override, or a config object to override multiple values.
7318 * @param {Object} value (optional) The new value to set, needed if first parameter is String.
7319 * @return {Ext.Loader} this
7321 setConfig: function(name, value) {
7322 if (Ext.isObject(name) && arguments.length === 1) {
7323 Ext.Object.merge(this.config, name);
7326 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
7333 * Get the config value corresponding to the specified name.
7334 * If no name is given, will return the config object.
7335 * @param {String} name The config property name
7336 * @return {Object/Mixed}
7338 getConfig: function(name) {
7340 return this.config[name];
7347 * Sets the path of a namespace. For Example:
7349 * Ext.Loader.setPath('Ext', '.');
7351 * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
7352 * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
7353 * @return {Ext.Loader} this
7356 setPath: flexSetter(function(name, path) {
7357 this.config.paths[name] = path;
7363 * Translates a className to a file path by adding the the proper prefix and converting the .'s to /'s.
7366 * Ext.Loader.setPath('My', '/path/to/My');
7368 * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
7370 * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
7372 * Ext.Loader.setPath({
7373 * 'My': '/path/to/lib',
7374 * 'My.awesome': '/other/path/for/awesome/stuff',
7375 * 'My.awesome.more': '/more/awesome/path'
7378 * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
7380 * alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
7382 * alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
7384 * alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
7386 * @param {String} className
7387 * @return {String} path
7389 getPath: function(className) {
7391 paths = this.config.paths,
7392 prefix = this.getPrefix(className);
7394 if (prefix.length > 0) {
7395 if (prefix === className) {
7396 return paths[prefix];
7399 path = paths[prefix];
7400 className = className.substring(prefix.length + 1);
7403 if (path.length > 0) {
7407 return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7412 * @param {String} className
7414 getPrefix: function(className) {
7415 var paths = this.config.paths,
7416 prefix, deepestPrefix = '';
7418 if (paths.hasOwnProperty(className)) {
7422 for (prefix in paths) {
7423 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7424 if (prefix.length > deepestPrefix.length) {
7425 deepestPrefix = prefix;
7430 return deepestPrefix;
7434 * Refresh all items in the queue. If all dependencies for an item exist during looping,
7435 * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7439 refreshQueue: function() {
7440 var ln = this.queue.length,
7441 i, item, j, requires;
7444 this.triggerReady();
7448 for (i = 0; i < ln; i++) {
7449 item = this.queue[i];
7452 requires = item.requires;
7454 // Don't bother checking when the number of files loaded
7455 // is still less than the array length
7456 if (requires.length > this.numLoadedFiles) {
7463 if (Manager.isCreated(requires[j])) {
7464 // Take out from the queue
7465 Ext.Array.erase(requires, j, 1);
7470 } while (j < requires.length);
7472 if (item.requires.length === 0) {
7473 Ext.Array.erase(this.queue, i, 1);
7474 item.callback.call(item.scope);
7475 this.refreshQueue();
7485 * Inject a script element to document's head, call onLoad and onError accordingly
7488 injectScriptElement: function(url, onLoad, onError, scope) {
7489 var script = document.createElement('script'),
7491 onLoadFn = function() {
7492 me.cleanupScriptElement(script);
7495 onErrorFn = function() {
7496 me.cleanupScriptElement(script);
7497 onError.call(scope);
7500 script.type = 'text/javascript';
7502 script.onload = onLoadFn;
7503 script.onerror = onErrorFn;
7504 script.onreadystatechange = function() {
7505 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7510 this.documentHead.appendChild(script);
7518 cleanupScriptElement: function(script) {
7519 script.onload = null;
7520 script.onreadystatechange = null;
7521 script.onerror = null;
7527 * Load a script file, supports both asynchronous and synchronous approaches
7529 * @param {String} url
7530 * @param {Function} onLoad
7531 * @param {Scope} scope
7532 * @param {Boolean} synchronous
7535 loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7537 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7538 fileName = url.split('/').pop(),
7539 isCrossOriginRestricted = false,
7540 xhr, status, onScriptError;
7542 scope = scope || this;
7544 this.isLoading = true;
7547 onScriptError = function() {
7548 onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7551 if (!Ext.isReady && Ext.onDocumentReady) {
7552 Ext.onDocumentReady(function() {
7553 me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7557 this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7561 if (typeof XMLHttpRequest !== 'undefined') {
7562 xhr = new XMLHttpRequest();
7564 xhr = new ActiveXObject('Microsoft.XMLHTTP');
7568 xhr.open('GET', noCacheUrl, false);
7571 isCrossOriginRestricted = true;
7574 status = (xhr.status === 1223) ? 204 : xhr.status;
7576 if (!isCrossOriginRestricted) {
7577 isCrossOriginRestricted = (status === 0);
7580 if (isCrossOriginRestricted
7582 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7583 "being loaded from a different domain or from the local file system whereby cross origin " +
7584 "requests are not allowed due to security reasons. Use asynchronous loading with " +
7585 "Ext.require instead.", synchronous);
7587 else if (status >= 200 && status < 300
7589 // Firebug friendly, file names are still shown even though they're eval'ed code
7590 new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7595 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7596 "verify that the file exists. " +
7597 "XHR status code: " + status, synchronous);
7600 // Prevent potential IE memory leak
7606 * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
7607 * Can be chained with more `require` and `exclude` methods, e.g.:
7609 * Ext.exclude('Ext.data.*').require('*');
7611 * Ext.exclude('widget.button*').require('widget.*');
7613 * {@link Ext#exclude Ext.exclude} is alias for {@link Ext.Loader#exclude Ext.Loader.exclude} for convenience.
7615 * @param {String/[String]} excludes
7616 * @return {Object} object contains `require` method for chaining
7618 exclude: function(excludes) {
7622 require: function(expressions, fn, scope) {
7623 return me.require(expressions, fn, scope, excludes);
7626 syncRequire: function(expressions, fn, scope) {
7627 return me.syncRequire(expressions, fn, scope, excludes);
7633 * Synchronously loads all classes by the given names and all their direct dependencies;
7634 * optionally executes the given callback function when finishes, within the optional scope.
7636 * {@link Ext#syncRequire Ext.syncRequire} is alias for {@link Ext.Loader#syncRequire Ext.Loader.syncRequire} for convenience.
7638 * @param {String/[String]} expressions Can either be a string or an array of string
7639 * @param {Function} fn (Optional) The callback function
7640 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7641 * @param {String/[String]} excludes (Optional) Classes to be excluded, useful when being used with expressions
7643 syncRequire: function() {
7644 this.syncModeEnabled = true;
7645 this.require.apply(this, arguments);
7646 this.refreshQueue();
7647 this.syncModeEnabled = false;
7651 * Loads all classes by the given names and all their direct dependencies;
7652 * optionally executes the given callback function when finishes, within the optional scope.
7654 * {@link Ext#require Ext.require} is alias for {@link Ext.Loader#require Ext.Loader.require} for convenience.
7656 * @param {String/[String]} expressions Can either be a string or an array of string
7657 * @param {Function} fn (Optional) The callback function
7658 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7659 * @param {String/[String]} excludes (Optional) Classes to be excluded, useful when being used with expressions
7661 require: function(expressions, fn, scope, excludes) {
7662 var filePath, expression, exclude, className, excluded = {},
7663 excludedClassNames = [],
7664 possibleClassNames = [],
7665 possibleClassName, classNames = [],
7668 expressions = Ext.Array.from(expressions);
7669 excludes = Ext.Array.from(excludes);
7671 fn = fn || Ext.emptyFn;
7673 scope = scope || Ext.global;
7675 for (i = 0, ln = excludes.length; i < ln; i++) {
7676 exclude = excludes[i];
7678 if (typeof exclude === 'string' && exclude.length > 0) {
7679 excludedClassNames = Manager.getNamesByExpression(exclude);
7681 for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
7682 excluded[excludedClassNames[j]] = true;
7687 for (i = 0, ln = expressions.length; i < ln; i++) {
7688 expression = expressions[i];
7690 if (typeof expression === 'string' && expression.length > 0) {
7691 possibleClassNames = Manager.getNamesByExpression(expression);
7693 for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
7694 possibleClassName = possibleClassNames[j];
7696 if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
7697 Ext.Array.include(classNames, possibleClassName);
7703 // If the dynamic dependency feature is not being used, throw an error
7704 // if the dependencies are not defined
7705 if (!this.config.enabled) {
7706 if (classNames.length > 0) {
7708 sourceClass: "Ext.Loader",
7709 sourceMethod: "require",
7710 msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
7711 "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
7716 if (classNames.length === 0) {
7722 requires: classNames,
7727 classNames = classNames.slice();
7729 for (i = 0, ln = classNames.length; i < ln; i++) {
7730 className = classNames[i];
7732 if (!this.isFileLoaded.hasOwnProperty(className)) {
7733 this.isFileLoaded[className] = false;
7735 filePath = this.getPath(className);
7737 this.classNameToFilePathMap[className] = filePath;
7739 this.numPendingFiles++;
7741 this.loadScriptFile(
7743 Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
7744 Ext.Function.pass(this.onFileLoadError, [className, filePath]),
7746 this.syncModeEnabled
7756 * @param {String} className
7757 * @param {String} filePath
7759 onFileLoaded: function(className, filePath) {
7760 this.numLoadedFiles++;
7762 this.isFileLoaded[className] = true;
7764 this.numPendingFiles--;
7766 if (this.numPendingFiles === 0) {
7767 this.refreshQueue();
7770 if (this.numPendingFiles <= 1) {
7771 window.status = "Finished loading all dependencies, onReady fired!";
7774 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
7777 if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
7778 var queue = this.queue,
7780 i, ln, j, subLn, missingClasses = [], missingPaths = [];
7782 for (i = 0, ln = queue.length; i < ln; i++) {
7783 requires = queue[i].requires;
7785 for (j = 0, subLn = requires.length; j < ln; j++) {
7786 if (this.isFileLoaded[requires[j]]) {
7787 missingClasses.push(requires[j]);
7792 if (missingClasses.length < 1) {
7796 missingClasses = Ext.Array.filter(missingClasses, function(item) {
7797 return !this.requiresMap.hasOwnProperty(item);
7800 for (i = 0,ln = missingClasses.length; i < ln; i++) {
7801 missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
7805 sourceClass: "Ext.Loader",
7806 sourceMethod: "onFileLoaded",
7807 msg: "The following classes are not declared even if their files have been " +
7808 "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
7809 "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
7817 onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
7818 this.numPendingFiles--;
7819 this.hasFileLoadError = true;
7822 sourceClass: "Ext.Loader",
7823 classToLoad: className,
7825 loadingType: isSynchronous ? 'synchronous' : 'async',
7833 addOptionalRequires: function(requires) {
7834 var optionalRequires = this.optionalRequires,
7837 requires = Ext.Array.from(requires);
7839 for (i = 0, ln = requires.length; i < ln; i++) {
7840 require = requires[i];
7842 Ext.Array.include(optionalRequires, require);
7851 triggerReady: function(force) {
7852 var readyListeners = this.readyListeners,
7853 optionalRequires, listener;
7855 if (this.isLoading || force) {
7856 this.isLoading = false;
7858 if (this.optionalRequires.length) {
7859 // Clone then empty the array to eliminate potential recursive loop issue
7860 optionalRequires = Ext.Array.clone(this.optionalRequires);
7862 // Empty the original array
7863 this.optionalRequires.length = 0;
7865 this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
7869 while (readyListeners.length) {
7870 listener = readyListeners.shift();
7871 listener.fn.call(listener.scope);
7873 if (this.isLoading) {
7883 * Adds new listener to be executed when all required scripts are fully loaded.
7885 * @param {Function} fn The function callback to be executed
7886 * @param {Object} scope The execution scope (`this`) of the callback function
7887 * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
7889 onReady: function(fn, scope, withDomReady, options) {
7892 if (withDomReady !== false && Ext.onDocumentReady) {
7896 Ext.onDocumentReady(oldFn, scope, options);
7900 if (!this.isLoading) {
7904 this.readyListeners.push({
7913 * @param {String} className
7915 historyPush: function(className) {
7916 if (className && this.isFileLoaded.hasOwnProperty(className)) {
7917 Ext.Array.include(this.history, className);
7927 * @alias Ext.Loader#require
7929 Ext.require = alias(Loader, 'require');
7933 * @method syncRequire
7934 * @alias Ext.Loader#syncRequire
7936 Ext.syncRequire = alias(Loader, 'syncRequire');
7941 * @alias Ext.Loader#exclude
7943 Ext.exclude = alias(Loader, 'exclude');
7948 * @alias Ext.Loader#onReady
7950 Ext.onReady = function(fn, scope, options) {
7951 Loader.onReady(fn, scope, true, options);
7955 * @cfg {[String]} requires
7957 * List of classes that have to be loaded before instanciating this class.
7960 * Ext.define('Mother', {
7961 * requires: ['Child'],
7962 * giveBirth: function() {
7963 * // we can be sure that child class is available.
7964 * return new Child();
7968 Class.registerPreprocessor('loader', function(cls, data, continueFn) {
7971 className = Manager.getName(cls),
7972 i, j, ln, subLn, value, propertyName, propertyValue;
7975 Basically loop through the dependencyProperties, look for string class names and push
7976 them into a stack, regardless of whether the property's value is a string, array or object. For example:
7978 extend: 'Ext.MyClass',
7979 requires: ['Ext.some.OtherClass'],
7981 observable: 'Ext.util.Observable';
7984 which will later be transformed into:
7986 extend: Ext.MyClass,
7987 requires: [Ext.some.OtherClass],
7989 observable: Ext.util.Observable;
7994 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7995 propertyName = dependencyProperties[i];
7997 if (data.hasOwnProperty(propertyName)) {
7998 propertyValue = data[propertyName];
8000 if (typeof propertyValue === 'string') {
8001 dependencies.push(propertyValue);
8003 else if (propertyValue instanceof Array) {
8004 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8005 value = propertyValue[j];
8007 if (typeof value === 'string') {
8008 dependencies.push(value);
8013 for (j in propertyValue) {
8014 if (propertyValue.hasOwnProperty(j)) {
8015 value = propertyValue[j];
8017 if (typeof value === 'string') {
8018 dependencies.push(value);
8026 if (dependencies.length === 0) {
8027 // Loader.historyPush(className);
8031 var deadlockPath = [],
8032 requiresMap = Loader.requiresMap,
8036 Automatically detect deadlocks before-hand,
8037 will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
8039 - A extends B, then B extends A
8040 - A requires B, B requires C, then C requires A
8042 The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
8043 no matter how deep the path is.
8047 requiresMap[className] = dependencies;
8049 detectDeadlock = function(cls) {
8050 deadlockPath.push(cls);
8052 if (requiresMap[cls]) {
8053 if (Ext.Array.contains(requiresMap[cls], className)) {
8055 sourceClass: "Ext.Loader",
8056 msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
8057 deadlockPath[1] + "' " + "mutually require each other. Path: " +
8058 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
8062 for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
8063 detectDeadlock(requiresMap[cls][i]);
8068 detectDeadlock(className);
8072 Loader.require(dependencies, function() {
8073 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8074 propertyName = dependencyProperties[i];
8076 if (data.hasOwnProperty(propertyName)) {
8077 propertyValue = data[propertyName];
8079 if (typeof propertyValue === 'string') {
8080 data[propertyName] = Manager.get(propertyValue);
8082 else if (propertyValue instanceof Array) {
8083 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8084 value = propertyValue[j];
8086 if (typeof value === 'string') {
8087 data[propertyName][j] = Manager.get(value);
8092 for (var k in propertyValue) {
8093 if (propertyValue.hasOwnProperty(k)) {
8094 value = propertyValue[k];
8096 if (typeof value === 'string') {
8097 data[propertyName][k] = Manager.get(value);
8105 continueFn.call(me, cls, data);
8111 Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
8114 * @cfg {[String]} uses
8116 * List of classes to load together with this class. These aren't neccessarily loaded before
8117 * this class is instanciated. For example:
8119 * Ext.define('Mother', {
8121 * giveBirth: function() {
8122 * // This code might, or might not work:
8123 * // return new Child();
8125 * // Instead use Ext.create() to load the class at the spot if not loaded already:
8126 * return Ext.create('Child');
8130 Manager.registerPostprocessor('uses', function(name, cls, data) {
8131 var uses = Ext.Array.from(data.uses),
8135 for (i = 0, ln = uses.length; i < ln; i++) {
8138 if (typeof item === 'string') {
8143 Loader.addOptionalRequires(items);
8146 Manager.setDefaultPostprocessorPosition('uses', 'last');
8148 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
8155 A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
8156 errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
8157 uses the Ext 4 class system, the Error class can automatically add the source class and method from which
8158 the error was raised. It also includes logic to automatically log the eroor to the console, if available,
8159 with additional metadata about the error. In all cases, the error will always be thrown at the end so that
8160 execution will halt.
8162 Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
8163 handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
8164 although in a real application it's usually a better idea to override the handling function and perform
8165 logging or some other method of reporting the errors in a way that is meaningful to the application.
8167 At its simplest you can simply raise an error as a simple string from within any code:
8171 Ext.Error.raise('Something bad happened!');
8173 If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
8174 displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
8175 additional metadata about the error being raised. The {@link #raise} method can also take a config object.
8176 In this form the `msg` attribute becomes the error description, and any other data added to the config gets
8177 added to the error object and, if the console is available, logged to the console for inspection.
8181 Ext.define('Ext.Foo', {
8182 doSomething: function(option){
8183 if (someCondition === false) {
8185 msg: 'You cannot do that!',
8186 option: option, // whatever was passed into the method
8187 'error code': 100 // other arbitrary info
8193 If a console is available (that supports the `console.dir` function) you'll see console output like:
8195 An error was raised with the following data:
8196 option: Object { foo: "bar"}
8199 msg: "You cannot do that!"
8200 sourceClass: "Ext.Foo"
8201 sourceMethod: "doSomething"
8203 uncaught exception: You cannot do that!
8205 As you can see, the error will report exactly where it was raised and will include as much information as the
8206 raising code can usefully provide.
8208 If you want to handle all application errors globally you can simply override the static {@link #handle} method
8209 and provide whatever handling logic you need. If the method returns true then the error is considered handled
8210 and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
8214 Ext.Error.handle = function(err) {
8215 if (err.someProperty == 'NotReallyAnError') {
8216 // maybe log something to the application here if applicable
8219 // any non-true return value (including none) will cause the error to be thrown
8222 * Create a new Error object
8223 * @param {Object} config The config object
8225 * @author Brian Moeskau <brian@sencha.com>
8226 * @docauthor Brian Moeskau <brian@sencha.com>
8228 Ext.Error = Ext.extend(Error, {
8232 Static flag that can be used to globally disable error reporting to the browser if set to true
8233 (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
8234 and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
8235 be preferable to supply a custom error {@link #handle handling} function instead.
8239 Ext.Error.ignore = true;
8248 Static flag that can be used to globally control error notification to the user. Unlike
8249 Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
8250 set to false to disable the alert notification (default is true for IE6 and IE7).
8252 Only the first error will generate an alert. Internally this flag is set to false when the
8253 first error occurs prior to displaying the alert.
8255 This flag is not used in a release build.
8259 Ext.Error.notify = false;
8264 //notify: Ext.isIE6 || Ext.isIE7,
8267 Raise an error that can include additional data and supports automatic console logging if available.
8268 You can pass a string error message or an object with the `msg` attribute which will be used as the
8269 error message. The object can contain any other name-value attributes (or objects) to be logged
8270 along with the error.
8272 Note that after displaying the error message a JavaScript error will ultimately be thrown so that
8273 execution will halt.
8277 Ext.Error.raise('A simple string error message');
8281 Ext.define('Ext.Foo', {
8282 doSomething: function(option){
8283 if (someCondition === false) {
8285 msg: 'You cannot do that!',
8286 option: option, // whatever was passed into the method
8287 'error code': 100 // other arbitrary info
8292 * @param {String/Object} err The error message string, or an object containing the
8293 * attribute "msg" that will be used as the error message. Any other data included in
8294 * the object will also be logged to the browser console, if available.
8298 raise: function(err){
8300 if (Ext.isString(err)) {
8304 var method = this.raise.caller;
8308 err.sourceMethod = method.$name;
8310 if (method.$owner) {
8311 err.sourceClass = method.$owner.$className;
8315 if (Ext.Error.handle(err) !== true) {
8316 var msg = Ext.Error.prototype.toString.call(err);
8325 throw new Ext.Error(err);
8330 Globally handle any Ext errors that may be raised, optionally providing custom logic to
8331 handle different errors individually. Return true from the function to bypass throwing the
8332 error to the browser, otherwise the error will be thrown and execution will halt.
8336 Ext.Error.handle = function(err) {
8337 if (err.someProperty == 'NotReallyAnError') {
8338 // maybe log something to the application here if applicable
8341 // any non-true return value (including none) will cause the error to be thrown
8344 * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes
8345 * that were originally raised with it, plus properties about the method and class from which
8346 * the error originated (if raised from a class that uses the Ext 4 class system).
8351 return Ext.Error.ignore;
8355 // This is the standard property that is the name of the constructor.
8359 * @param {String/Object} config The error message string, or an object containing the
8360 * attribute "msg" that will be used as the error message. Any other data included in
8361 * the object will be applied to the error instance and logged to the browser console, if available.
8363 constructor: function(config){
8364 if (Ext.isString(config)) {
8365 config = { msg: config };
8370 Ext.apply(me, config);
8372 me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
8373 // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
8377 Provides a custom string representation of the error object. This is an override of the base JavaScript
8378 `Object.toString` method, which is useful so that when logged to the browser console, an error object will
8379 be displayed with a useful message instead of `[object Object]`, the default `toString` result.
8381 The default implementation will include the error message along with the raising class and method, if available,
8382 but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
8383 a particular error instance, if you want to provide a custom description that will show up in the console.
8385 * @return {String} The error message. If raised from within the Ext 4 class system, the error message
8386 * will also include the raising class and method names, if available.
8388 toString: function(){
8390 className = me.className ? me.className : '',
8391 methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
8392 msg = me.msg || '(No description provided)';
8394 return className + methodName + msg;
8399 * This mechanism is used to notify the user of the first error encountered on the page. This
8400 * was previously internal to Ext.Error.raise and is a desirable feature since errors often
8401 * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
8402 * where exceptions are handled in a try/catch.
8405 var prevOnError, timer, errors = 0,
8406 extraordinarilyBad = /(out of stack)|(too much recursion)|(stack overflow)|(out of memory)/i,
8409 if (typeof window === 'undefined') {
8410 return; // build system or some such environment...
8413 // This method is called to notify the user of the current error status.
8414 function notify () {
8415 var counters = Ext.log.counters,
8416 supports = Ext.supports,
8417 hasOnError = supports && supports.WindowOnError; // TODO - timing
8419 // Put log counters to the status bar (for most browsers):
8420 if (counters && (counters.error + counters.warn + counters.info + counters.log)) {
8421 var msg = [ 'Logged Errors:',counters.error, 'Warnings:',counters.warn,
8422 'Info:',counters.info, 'Log:',counters.log].join(' ');
8424 msg = '*** Errors: ' + errors + ' - ' + msg;
8425 } else if (counters.error) {
8431 // Display an alert on the first error:
8432 if (!Ext.isDefined(Ext.Error.notify)) {
8433 Ext.Error.notify = Ext.isIE6 || Ext.isIE7; // TODO - timing
8435 if (Ext.Error.notify && (hasOnError ? errors : (counters && counters.error))) {
8436 Ext.Error.notify = false;
8439 win.clearInterval(timer); // ticks can queue up so stop...
8443 alert('Unhandled error on page: See console or log');
8448 // Sets up polling loop. This is the only way to know about errors in some browsers
8449 // (Opera/Safari) and is the only way to update the status bar for warnings and other
8452 timer = win.setInterval(notify, 1000);
8455 // window.onerror is ideal (esp in IE) because you get full context. This is harmless
8456 // otherwise (never called) which is good because you cannot feature detect it.
8457 prevOnError = win.onerror || Ext.emptyFn;
8458 win.onerror = function (message) {
8461 if (!extraordinarilyBad.test(message)) {
8462 // too much recursion + our alert right now = crash IE
8463 // our polling loop will pick it up even if we don't alert now
8467 return prevOnError.apply(this, arguments);