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);
181 sourceMethod: 'extend',
182 msg: 'Attempting to extend from a class which has not been loaded on the page.'
187 // We create a new temporary class
188 var F = function() {},
189 subclassProto, superclassProto = superclass.prototype;
191 F.prototype = superclassProto;
192 subclassProto = subclass.prototype = new F();
193 subclassProto.constructor = subclass;
194 subclass.superclass = superclassProto;
196 if (superclassProto.constructor === objectConstructor) {
197 superclassProto.constructor = superclass;
200 subclass.override = function(overrides) {
201 Ext.override(subclass, overrides);
204 subclassProto.override = inlineOverrides;
205 subclassProto.proto = subclassProto;
207 subclass.override(overrides);
208 subclass.extend = function(o) {
209 return Ext.extend(subclass, o);
217 * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
219 Ext.define('My.cool.Class', {
225 Ext.override(My.cool.Class, {
227 alert('About to say...');
229 this.callOverridden();
233 var cool = new My.cool.Class();
234 cool.sayHi(); // alerts 'About to say...'
237 * Please note that `this.callOverridden()` only works if the class was previously
238 * created with {@link Ext#define)
240 * @param {Object} cls The class to override
241 * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
242 * containing one or more methods.
246 override: function(cls, overrides) {
247 if (cls.prototype.$className) {
248 return cls.override(overrides);
251 Ext.apply(cls.prototype, overrides);
256 // A full set of static methods to do type checking
260 * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
261 * value (second argument) otherwise.
263 * @param {Mixed} value The value to test
264 * @param {Mixed} defaultValue The value to return if the original value is empty
265 * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
266 * @return {Mixed} value, if non-empty, else defaultValue
268 valueFrom: function(value, defaultValue, allowBlank){
269 return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
273 * Returns the type of the given variable in string format. List of possible values are:
275 * - `undefined`: If the given value is `undefined`
276 * - `null`: If the given value is `null`
277 * - `string`: If the given value is a string
278 * - `number`: If the given value is a number
279 * - `boolean`: If the given value is a boolean value
280 * - `date`: If the given value is a `Date` object
281 * - `function`: If the given value is a function reference
282 * - `object`: If the given value is an object
283 * - `array`: If the given value is an array
284 * - `regexp`: If the given value is a regular expression
285 * - `element`: If the given value is a DOM Element
286 * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
287 * - `whitespace`: If the given value is a DOM text node and contains only whitespace
289 * @param {Mixed} value
293 typeOf: function(value) {
294 if (value === null) {
298 var type = typeof value;
300 if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
304 var typeToString = toString.call(value);
306 switch(typeToString) {
307 case '[object Array]':
309 case '[object Date]':
311 case '[object Boolean]':
313 case '[object Number]':
315 case '[object RegExp]':
319 if (type === 'function') {
323 if (type === 'object') {
324 if (value.nodeType !== undefined) {
325 if (value.nodeType === 3) {
326 return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
339 sourceMethod: 'typeOf',
340 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
346 * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
350 * - a zero-length array
351 * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
353 * @param {Mixed} value The value to test
354 * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
358 isEmpty: function(value, allowEmptyString) {
359 return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
363 * Returns true if the passed value is a JavaScript Array, false otherwise.
365 * @param {Mixed} target The target to test
369 isArray: ('isArray' in Array) ? Array.isArray : function(value) {
370 return toString.call(value) === '[object Array]';
374 * Returns true if the passed value is a JavaScript Date object, false otherwise.
375 * @param {Object} object The object to test
378 isDate: function(value) {
379 return toString.call(value) === '[object Date]';
383 * Returns true if the passed value is a JavaScript Object, false otherwise.
384 * @param {Mixed} value The value to test
388 isObject: (toString.call(null) === '[object Object]') ?
390 // check ownerDocument here as well to exclude DOM nodes
391 return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined;
394 return toString.call(value) === '[object Object]';
398 * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
399 * @param {Mixed} value The value to test
402 isPrimitive: function(value) {
403 var type = typeof value;
405 return type === 'string' || type === 'number' || type === 'boolean';
409 * Returns true if the passed value is a JavaScript Function, false otherwise.
410 * @param {Mixed} value The value to test
415 // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
416 // Object.prorotype.toString (slower)
417 (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
418 return toString.call(value) === '[object Function]';
419 } : function(value) {
420 return typeof value === 'function';
424 * Returns true if the passed value is a number. Returns false for non-finite numbers.
425 * @param {Mixed} value The value to test
428 isNumber: function(value) {
429 return typeof value === 'number' && isFinite(value);
433 * Validates that a value is numeric.
434 * @param {Mixed} value Examples: 1, '1', '2.34'
435 * @return {Boolean} True if numeric, false otherwise
437 isNumeric: function(value) {
438 return !isNaN(parseFloat(value)) && isFinite(value);
442 * Returns true if the passed value is a string.
443 * @param {Mixed} value The value to test
446 isString: function(value) {
447 return typeof value === 'string';
451 * Returns true if the passed value is a boolean.
453 * @param {Mixed} value The value to test
456 isBoolean: function(value) {
457 return typeof value === 'boolean';
461 * Returns true if the passed value is an HTMLElement
462 * @param {Mixed} value The value to test
465 isElement: function(value) {
466 return value ? value.nodeType === 1 : false;
470 * Returns true if the passed value is a TextNode
471 * @param {Mixed} value The value to test
474 isTextNode: function(value) {
475 return value ? value.nodeName === "#text" : false;
479 * Returns true if the passed value is defined.
480 * @param {Mixed} value The value to test
483 isDefined: function(value) {
484 return typeof value !== 'undefined';
488 * Returns true if the passed value is iterable, false otherwise
489 * @param {Mixed} value The value to test
492 isIterable: function(value) {
493 return (value && typeof value !== 'string') ? value.length !== undefined : false;
500 * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
501 * @param {Mixed} item The variable to clone
502 * @return {Mixed} clone
504 clone: function(item) {
505 if (item === null || item === undefined) {
510 // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
512 if (item.nodeType && item.cloneNode) {
513 return item.cloneNode(true);
516 var type = toString.call(item);
519 if (type === '[object Date]') {
520 return new Date(item.getTime());
523 var i, j, k, clone, key;
526 if (type === '[object Array]') {
532 clone[i] = Ext.clone(item[i]);
536 else if (type === '[object Object]' && item.constructor === Object) {
540 clone[key] = Ext.clone(item[key]);
544 for (j = enumerables.length; j--;) {
551 return clone || item;
556 * Generate a unique reference of Ext in the global scope, useful for sandboxing
558 getUniqueGlobalNamespace: function() {
559 var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
561 if (uniqueGlobalNamespace === undefined) {
565 uniqueGlobalNamespace = 'ExtBox' + (++i);
566 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
568 Ext.global[uniqueGlobalNamespace] = Ext;
569 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
572 return uniqueGlobalNamespace;
578 functionFactory: function() {
579 var args = Array.prototype.slice.call(arguments);
581 if (args.length > 0) {
582 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
583 args[args.length - 1];
586 return Function.prototype.constructor.apply(Function.prototype, args);
591 * Old alias to {@link Ext#typeOf}
592 * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
596 Ext.type = Ext.typeOf;
601 * @author Jacky Nguyen <jacky@sencha.com>
602 * @docauthor Jacky Nguyen <jacky@sencha.com>
605 * A utility class that wrap around a string version number and provide convenient
606 * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
608 var version = new Ext.Version('1.0.2beta');
609 console.log("Version is " + version); // Version is 1.0.2beta
611 console.log(version.getMajor()); // 1
612 console.log(version.getMinor()); // 0
613 console.log(version.getPatch()); // 2
614 console.log(version.getBuild()); // 0
615 console.log(version.getRelease()); // beta
617 console.log(version.isGreaterThan('1.0.1')); // True
618 console.log(version.isGreaterThan('1.0.2alpha')); // True
619 console.log(version.isGreaterThan('1.0.2RC')); // False
620 console.log(version.isGreaterThan('1.0.2')); // False
621 console.log(version.isLessThan('1.0.2')); // True
623 console.log(version.match(1.0)); // True
624 console.log(version.match('1.0.2')); // True
630 // Current core version
631 var version = '4.0.2', Version;
632 Ext.Version = Version = Ext.extend(Object, {
635 * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
636 * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
637 * @return {Ext.Version} this
639 constructor: function(version) {
640 var parts, releaseStartIndex;
642 if (version instanceof Version) {
646 this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
648 releaseStartIndex = this.version.search(/([^\d\.])/);
650 if (releaseStartIndex !== -1) {
651 this.release = this.version.substr(releaseStartIndex, version.length);
652 this.shortVersion = this.version.substr(0, releaseStartIndex);
655 this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
657 parts = this.version.split('.');
659 this.major = parseInt(parts.shift() || 0, 10);
660 this.minor = parseInt(parts.shift() || 0, 10);
661 this.patch = parseInt(parts.shift() || 0, 10);
662 this.build = parseInt(parts.shift() || 0, 10);
668 * Override the native toString method
670 * @return {String} version
672 toString: function() {
677 * Override the native valueOf method
679 * @return {String} version
681 valueOf: function() {
686 * Returns the major component value
687 * @return {Number} major
689 getMajor: function() {
690 return this.major || 0;
694 * Returns the minor component value
695 * @return {Number} minor
697 getMinor: function() {
698 return this.minor || 0;
702 * Returns the patch component value
703 * @return {Number} patch
705 getPatch: function() {
706 return this.patch || 0;
710 * Returns the build component value
711 * @return {Number} build
713 getBuild: function() {
714 return this.build || 0;
718 * Returns the release component value
719 * @return {Number} release
721 getRelease: function() {
722 return this.release || '';
726 * Returns whether this version if greater than the supplied argument
727 * @param {String/Number} target The version to compare with
728 * @return {Boolean} True if this version if greater than the target, false otherwise
730 isGreaterThan: function(target) {
731 return Version.compare(this.version, target) === 1;
735 * Returns whether this version if smaller than the supplied argument
736 * @param {String/Number} target The version to compare with
737 * @return {Boolean} True if this version if smaller than the target, false otherwise
739 isLessThan: function(target) {
740 return Version.compare(this.version, target) === -1;
744 * Returns whether this version equals to the supplied argument
745 * @param {String/Number} target The version to compare with
746 * @return {Boolean} True if this version equals to the target, false otherwise
748 equals: function(target) {
749 return Version.compare(this.version, target) === 0;
753 * Returns whether this version matches the supplied argument. Example:
755 * var version = new Ext.Version('1.0.2beta');
756 * console.log(version.match(1)); // True
757 * console.log(version.match(1.0)); // True
758 * console.log(version.match('1.0.2')); // True
759 * console.log(version.match('1.0.2RC')); // False
761 * @param {String/Number} target The version to compare with
762 * @return {Boolean} True if this version matches the target, false otherwise
764 match: function(target) {
765 target = String(target);
766 return this.version.substr(0, target.length) === target;
770 * Returns this format: [major, minor, patch, build, release]. Useful for comparison
773 toArray: function() {
774 return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
778 * Returns shortVersion version without dots and release
781 getShortVersion: function() {
782 return this.shortVersion;
801 * Converts a version component to a comparable value
804 * @param {Mixed} value The value to convert
807 getComponentValue: function(value) {
808 return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
812 * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
813 * they are handled in the following order:
814 * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
817 * @param {String} current The current version to compare to
818 * @param {String} target The target version to compare to
819 * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
821 compare: function(current, target) {
822 var currentValue, targetValue, i;
824 current = new Version(current).toArray();
825 target = new Version(target).toArray();
827 for (i = 0; i < Math.max(current.length, target.length); i++) {
828 currentValue = this.getComponentValue(current[i]);
829 targetValue = this.getComponentValue(target[i]);
831 if (currentValue < targetValue) {
833 } else if (currentValue > targetValue) {
851 lastRegisteredVersion: null,
854 * Set version number for the given package name.
856 * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
857 * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
860 setVersion: function(packageName, version) {
861 Ext.versions[packageName] = new Version(version);
862 Ext.lastRegisteredVersion = Ext.versions[packageName];
868 * Get the version number of the supplied package name; will return the last registered version
869 * (last Ext.setVersion call) if there's no package name given.
871 * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
872 * @return {Ext.Version} The version
874 getVersion: function(packageName) {
875 if (packageName === undefined) {
876 return Ext.lastRegisteredVersion;
879 return Ext.versions[packageName];
883 * Create a closure for deprecated code.
885 // This means Ext.oldMethod is only supported in 4.0.0beta and older.
886 // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
887 // the closure will not be invoked
888 Ext.deprecate('extjs', '4.0.0beta', function() {
889 Ext.oldMethod = Ext.newMethod;
894 * @param {String} packageName The package name
895 * @param {String} since The last version before it's deprecated
896 * @param {Function} closure The callback function to be executed with the specified version is less than the current version
897 * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
900 deprecate: function(packageName, since, closure, scope) {
901 if (Version.compare(Ext.getVersion(packageName), since) < 1) {
905 }); // End Versioning
907 Ext.setVersion('core', version);
914 * A collection of useful static methods to deal with strings
919 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,
921 formatRe: /\{(\d+)\}/g,
922 escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
925 * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
926 * @param {String} value The string to encode
927 * @return {String} The encoded text
930 htmlEncode: (function() {
936 }, keys = [], p, regex;
938 for (p in entities) {
942 regex = new RegExp('(' + keys.join('|') + ')', 'g');
944 return function(value) {
945 return (!value) ? value : String(value).replace(regex, function(match, capture) {
946 return entities[capture];
952 * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
953 * @param {String} value The string to decode
954 * @return {String} The decoded text
957 htmlDecode: (function() {
963 }, keys = [], p, regex;
965 for (p in entities) {
969 regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
971 return function(value) {
972 return (!value) ? value : String(value).replace(regex, function(match, capture) {
973 if (capture in entities) {
974 return entities[capture];
976 return String.fromCharCode(parseInt(capture.substr(2), 10));
983 * Appends content to the query string of a URL, handling logic for whether to place
984 * a question mark or ampersand.
985 * @param {String} url The URL to append to.
986 * @param {String} string The content to append to the URL.
987 * @return (String) The resulting URL
989 urlAppend : function(url, string) {
990 if (!Ext.isEmpty(string)) {
991 return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
998 * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
1000 var s = ' foo bar ';
1001 alert('-' + s + '-'); //alerts "- foo bar -"
1002 alert('-' + Ext.String.trim(s) + '-'); //alerts "-foo bar-"
1004 * @param {String} string The string to escape
1005 * @return {String} The trimmed string
1007 trim: function(string) {
1008 return string.replace(Ext.String.trimRegex, "");
1012 * Capitalize the given string
1013 * @param {String} string
1016 capitalize: function(string) {
1017 return string.charAt(0).toUpperCase() + string.substr(1);
1021 * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1022 * @param {String} value The string to truncate
1023 * @param {Number} length The maximum length to allow before truncating
1024 * @param {Boolean} word True to try to find a common word break
1025 * @return {String} The converted text
1027 ellipsis: function(value, len, word) {
1028 if (value && value.length > len) {
1030 var vs = value.substr(0, len - 2),
1031 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1032 if (index !== -1 && index >= (len - 15)) {
1033 return vs.substr(0, index) + "...";
1036 return value.substr(0, len - 3) + "...";
1042 * Escapes the passed string for use in a regular expression
1043 * @param {String} string
1046 escapeRegex: function(string) {
1047 return string.replace(Ext.String.escapeRegexRe, "\\$1");
1051 * Escapes the passed string for ' and \
1052 * @param {String} string The string to escape
1053 * @return {String} The escaped string
1055 escape: function(string) {
1056 return string.replace(Ext.String.escapeRe, "\\$1");
1060 * Utility function that allows you to easily switch a string between two alternating values. The passed value
1061 * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
1062 * they are already different, the first value passed in is returned. Note that this method returns the new value
1063 * but does not change the current string.
1065 // alternate sort directions
1066 sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1068 // instead of conditional logic:
1069 sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1071 * @param {String} string The current string
1072 * @param {String} value The value to compare to the current string
1073 * @param {String} other The new value to use if the string already equals the first value passed in
1074 * @return {String} The new value
1076 toggle: function(string, value, other) {
1077 return string === value ? other : value;
1081 * Pads the left side of a string with a specified character. This is especially useful
1082 * for normalizing number and date strings. Example usage:
1085 var s = Ext.String.leftPad('123', 5, '0');
1086 // s now contains the string: '00123'
1088 * @param {String} string The original string
1089 * @param {Number} size The total length of the output string
1090 * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1091 * @return {String} The padded string
1093 leftPad: function(string, size, character) {
1094 var result = String(string);
1095 character = character || " ";
1096 while (result.length < size) {
1097 result = character + result;
1103 * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
1104 * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
1106 var cls = 'my-class', text = 'Some text';
1107 var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
1108 // s now contains the string: '<div class="my-class">Some text</div>'
1110 * @param {String} string The tokenized string to be formatted
1111 * @param {String} value1 The value to replace token {0}
1112 * @param {String} value2 Etc...
1113 * @return {String} The formatted string
1115 format: function(format) {
1116 var args = Ext.Array.toArray(arguments, 1);
1117 return format.replace(Ext.String.formatRe, function(m, i) {
1126 * A collection of useful static methods to deal with numbers
1132 var isToFixedBroken = (0.9).toFixed() !== '1';
1136 * Checks whether or not the passed number is within a desired range. If the number is already within the
1137 * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1138 * exceeded. Note that this method returns the constrained value but does not change the current number.
1139 * @param {Number} number The number to check
1140 * @param {Number} min The minimum number in the range
1141 * @param {Number} max The maximum number in the range
1142 * @return {Number} The constrained value if outside the range, otherwise the current value
1144 constrain: function(number, min, max) {
1145 number = parseFloat(number);
1148 number = Math.max(number, min);
1151 number = Math.min(number, max);
1157 * Snaps the passed number between stopping points based upon a passed increment value.
1158 * @param {Number} value The unsnapped value.
1159 * @param {Number} increment The increment by which the value must move.
1160 * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment..
1161 * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment..
1162 * @return {Number} The value of the nearest snap target.
1164 snap : function(value, increment, minValue, maxValue) {
1165 var newValue = value,
1168 if (!(increment && value)) {
1171 m = value % increment;
1174 if (m * 2 >= increment) {
1175 newValue += increment;
1176 } else if (m * 2 < -increment) {
1177 newValue -= increment;
1180 return Ext.Number.constrain(newValue, minValue, maxValue);
1184 * Formats a number using fixed-point notation
1185 * @param {Number} value The number to format
1186 * @param {Number} precision The number of digits to show after the decimal point
1188 toFixed: function(value, precision) {
1189 if (isToFixedBroken) {
1190 precision = precision || 0;
1191 var pow = Math.pow(10, precision);
1192 return (Math.round(value * pow) / pow).toFixed(precision);
1195 return value.toFixed(precision);
1199 * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1202 Ext.Number.from('1.23', 1); // returns 1.23
1203 Ext.Number.from('abc', 1); // returns 1
1205 * @param {Mixed} value
1206 * @param {Number} defaultValue The value to return if the original value is non-numeric
1207 * @return {Number} value, if numeric, defaultValue otherwise
1209 from: function(value, defaultValue) {
1210 if (isFinite(value)) {
1211 value = parseFloat(value);
1214 return !isNaN(value) ? value : defaultValue;
1221 * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
1223 * @deprecated 4.0.0 Replaced by Ext.Number.from
1227 Ext.num = function() {
1228 return Ext.Number.from.apply(this, arguments);
1231 * @author Jacky Nguyen <jacky@sencha.com>
1232 * @docauthor Jacky Nguyen <jacky@sencha.com>
1235 * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1242 var arrayPrototype = Array.prototype,
1243 slice = arrayPrototype.slice,
1244 supportsSplice = function () {
1249 if (!array.splice) {
1253 // This detects a bug in IE8 splice method:
1254 // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/
1260 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");
1262 lengthBefore = array.length; //41
1263 array.splice(13, 0, "XXX"); // add one element
1265 if (lengthBefore+1 != array.length) {
1272 supportsForEach = 'forEach' in arrayPrototype,
1273 supportsMap = 'map' in arrayPrototype,
1274 supportsIndexOf = 'indexOf' in arrayPrototype,
1275 supportsEvery = 'every' in arrayPrototype,
1276 supportsSome = 'some' in arrayPrototype,
1277 supportsFilter = 'filter' in arrayPrototype,
1278 supportsSort = function() {
1279 var a = [1,2,3,4,5].sort(function(){ return 0; });
1280 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1282 supportsSliceOnNodeList = true,
1286 // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1287 if (typeof document !== 'undefined') {
1288 slice.call(document.getElementsByTagName('body'));
1291 supportsSliceOnNodeList = false;
1294 function fixArrayIndex (array, index) {
1295 return (index < 0) ? Math.max(0, array.length + index)
1296 : Math.min(array.length, index);
1300 Does the same work as splice, but with a slightly more convenient signature. The splice
1301 method has bugs in IE8, so this is the implementation we use on that platform.
1303 The rippling of items in the array can be tricky. Consider two use cases:
1308 +---+---+---+---+---+---+---+---+
1309 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
1310 +---+---+---+---+---+---+---+---+
1313 / / \/ \/ \ +--------------------------+
1314 / / /\ /\ +--------------------------+ \
1315 / / / \/ +--------------------------+ \ \
1316 / / / /+--------------------------+ \ \ \
1319 +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
1320 | 0 | 1 | 4 | 5 | 6 | 7 | | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 |
1321 +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+
1325 In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so
1326 that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we
1327 must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4].
1329 function replaceSim (array, index, removeCount, insert) {
1330 var add = insert ? insert.length : 0,
1331 length = array.length,
1332 pos = fixArrayIndex(array, index);
1334 // we try to use Array.push when we can for efficiency...
1335 if (pos === length) {
1337 array.push.apply(array, insert);
1340 var remove = Math.min(removeCount, length - pos),
1341 tailOldPos = pos + remove,
1342 tailNewPos = tailOldPos + add - remove,
1343 tailCount = length - tailOldPos,
1344 lengthAfterRemove = length - remove,
1347 if (tailNewPos < tailOldPos) { // case A
1348 for (i = 0; i < tailCount; ++i) {
1349 array[tailNewPos+i] = array[tailOldPos+i];
1351 } else if (tailNewPos > tailOldPos) { // case B
1352 for (i = tailCount; i--; ) {
1353 array[tailNewPos+i] = array[tailOldPos+i];
1355 } // else, add == remove (nothing to do)
1357 if (add && pos === lengthAfterRemove) {
1358 array.length = lengthAfterRemove; // truncate array
1359 array.push.apply(array, insert);
1361 array.length = lengthAfterRemove + add; // reserves space
1362 for (i = 0; i < add; ++i) {
1363 array[pos+i] = insert[i];
1371 function replaceNative (array, index, removeCount, insert) {
1372 if (insert && insert.length) {
1373 if (index < array.length) {
1374 array.splice.apply(array, [index, removeCount].concat(insert));
1376 array.push.apply(array, insert);
1379 array.splice(index, removeCount);
1384 function eraseSim (array, index, removeCount) {
1385 return replaceSim(array, index, removeCount);
1388 function eraseNative (array, index, removeCount) {
1389 array.splice(index, removeCount);
1393 function spliceSim (array, index, removeCount) {
1394 var pos = fixArrayIndex(array, index),
1395 removed = array.slice(index, fixArrayIndex(array, pos+removeCount));
1397 if (arguments.length < 4) {
1398 replaceSim(array, pos, removeCount);
1400 replaceSim(array, pos, removeCount, slice.call(arguments, 3));
1406 function spliceNative (array) {
1407 return array.splice.apply(array, slice.call(arguments, 1));
1410 var erase = supportsSplice ? eraseNative : eraseSim,
1411 replace = supportsSplice ? replaceNative : replaceSim,
1412 splice = supportsSplice ? spliceNative : spliceSim;
1414 // NOTE: from here on, use erase, replace or splice (not native methods)...
1416 ExtArray = Ext.Array = {
1418 * Iterates an array or an iterable value and invoke the given callback function for each item.
1420 * var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1422 * Ext.Array.each(countries, function(name, index, countriesItSelf) {
1423 * console.log(name);
1426 * var sum = function() {
1429 * Ext.Array.each(arguments, function(value) {
1436 * sum(1, 2, 3); // returns 6
1438 * The iteration can be stopped by returning false in the function callback.
1440 * Ext.Array.each(countries, function(name, index, countriesItSelf) {
1441 * if (name === 'Singapore') {
1442 * return false; // break here
1446 * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each}
1448 * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
1449 * argument is not iterable, the callback function is called once.
1450 * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1451 * the current `index`. Arguments passed to this callback function are:
1453 * - `item` : Mixed - The item at the current `index` in the passed `array`
1454 * - `index` : Number - The current `index` within the `array`
1455 * - `allItems` : Array/NodeList/Mixed - The `array` passed as the first argument to `Ext.Array.each`
1457 * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1458 * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1460 * @return {Boolean} See description for the `fn` parameter.
1462 each: function(array, fn, scope, reverse) {
1463 array = ExtArray.from(array);
1468 if (reverse !== true) {
1469 for (i = 0; i < ln; i++) {
1470 if (fn.call(scope || array[i], array[i], i, array) === false) {
1476 for (i = ln - 1; i > -1; i--) {
1477 if (fn.call(scope || array[i], array[i], i, array) === false) {
1487 * Iterates an array and invoke the given callback function for each item. Note that this will simply
1488 * delegate to the native Array.prototype.forEach method if supported.
1489 * It doesn't support stopping the iteration by returning false in the callback function like
1490 * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
1491 * {@link Ext.Array#each}
1493 * @param {Array} array The array to iterate
1494 * @param {Function} fn The function callback, to be invoked these arguments:
1496 * - `item` : Mixed - The item at the current `index` in the passed `array`
1497 * - `index` : Number - The current `index` within the `array`
1498 * - `allItems` : Array - The `array` itself which was passed as the first argument
1500 * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1502 forEach: function(array, fn, scope) {
1503 if (supportsForEach) {
1504 return array.forEach(fn, scope);
1510 for (; i < ln; i++) {
1511 fn.call(scope, array[i], i, array);
1516 * Get the index of the provided `item` in the given `array`, a supplement for the
1517 * missing arrayPrototype.indexOf in Internet Explorer.
1519 * @param {Array} array The array to check
1520 * @param {Mixed} item The item to look for
1521 * @param {Number} from (Optional) The index at which to begin the search
1522 * @return {Number} The index of item in the array (or -1 if it is not found)
1524 indexOf: function(array, item, from) {
1525 if (supportsIndexOf) {
1526 return array.indexOf(item, from);
1529 var i, length = array.length;
1531 for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1532 if (array[i] === item) {
1541 * Checks whether or not the given `array` contains the specified `item`
1543 * @param {Array} array The array to check
1544 * @param {Mixed} item The item to look for
1545 * @return {Boolean} True if the array contains the item, false otherwise
1547 contains: function(array, item) {
1548 if (supportsIndexOf) {
1549 return array.indexOf(item) !== -1;
1554 for (i = 0, ln = array.length; i < ln; i++) {
1555 if (array[i] === item) {
1564 * Converts any iterable (numeric indices and a length property) into a true array.
1567 * var args = Ext.Array.toArray(arguments),
1568 * fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1570 * alert(args.join(' '));
1571 * alert(fromSecondToLastArgs.join(' '));
1574 * test('just', 'testing', 'here'); // alerts 'just testing here';
1575 * // alerts 'testing here';
1577 * Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1578 * Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1579 * Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1581 * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray}
1583 * @param {Mixed} iterable the iterable object to be turned into a true Array.
1584 * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1585 * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1586 * index of the iterable value
1587 * @return {Array} array
1589 toArray: function(iterable, start, end){
1590 if (!iterable || !iterable.length) {
1594 if (typeof iterable === 'string') {
1595 iterable = iterable.split('');
1598 if (supportsSliceOnNodeList) {
1599 return slice.call(iterable, start || 0, end || iterable.length);
1606 end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1608 for (i = start; i < end; i++) {
1609 array.push(iterable[i]);
1616 * Plucks the value of a property from each item in the Array. Example:
1618 * Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1620 * @param {Array|NodeList} array The Array of items to pluck the value from.
1621 * @param {String} propertyName The property name to pluck from each element.
1622 * @return {Array} The value from each item in the Array.
1624 pluck: function(array, propertyName) {
1628 for (i = 0, ln = array.length; i < ln; i++) {
1631 ret.push(item[propertyName]);
1638 * Creates a new array with the results of calling a provided function on every element in this array.
1640 * @param {Array} array
1641 * @param {Function} fn Callback function for each item
1642 * @param {Object} scope Callback function scope
1643 * @return {Array} results
1645 map: function(array, fn, scope) {
1647 return array.map(fn, scope);
1654 for (; i < len; i++) {
1655 results[i] = fn.call(scope, array[i], i, array);
1662 * Executes the specified function for each array element until the function returns a falsy value.
1663 * If such an item is found, the function will return false immediately.
1664 * Otherwise, it will return true.
1666 * @param {Array} array
1667 * @param {Function} fn Callback function for each item
1668 * @param {Object} scope Callback function scope
1669 * @return {Boolean} True if no false value is returned by the callback function.
1671 every: function(array, fn, scope) {
1674 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
1677 if (supportsEvery) {
1678 return array.every(fn, scope);
1684 for (; i < ln; ++i) {
1685 if (!fn.call(scope, array[i], i, array)) {
1694 * Executes the specified function for each array element until the function returns a truthy value.
1695 * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1697 * @param {Array} array
1698 * @param {Function} fn Callback function for each item
1699 * @param {Object} scope Callback function scope
1700 * @return {Boolean} True if the callback function returns a truthy value.
1702 some: function(array, fn, scope) {
1705 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
1709 return array.some(fn, scope);
1715 for (; i < ln; ++i) {
1716 if (fn.call(scope, array[i], i, array)) {
1725 * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1727 * See {@link Ext.Array#filter}
1729 * @param {Array} array
1730 * @return {Array} results
1732 clean: function(array) {
1738 for (; i < ln; i++) {
1741 if (!Ext.isEmpty(item)) {
1750 * Returns a new array with unique items
1752 * @param {Array} array
1753 * @return {Array} results
1755 unique: function(array) {
1761 for (; i < ln; i++) {
1764 if (ExtArray.indexOf(clone, item) === -1) {
1773 * Creates a new array with all of the elements of this array for which
1774 * the provided filtering function returns true.
1776 * @param {Array} array
1777 * @param {Function} fn Callback function for each item
1778 * @param {Object} scope Callback function scope
1779 * @return {Array} results
1781 filter: function(array, fn, scope) {
1782 if (supportsFilter) {
1783 return array.filter(fn, scope);
1790 for (; i < ln; i++) {
1791 if (fn.call(scope, array[i], i, array)) {
1792 results.push(array[i]);
1800 * Converts a value to an array if it's not already an array; returns:
1802 * - An empty array if given value is `undefined` or `null`
1803 * - Itself if given value is already an array
1804 * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1805 * - An array with one item which is the given value, otherwise
1807 * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
1808 * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
1810 * @return {Array} array
1812 from: function(value, newReference) {
1813 if (value === undefined || value === null) {
1817 if (Ext.isArray(value)) {
1818 return (newReference) ? slice.call(value) : value;
1821 if (value && value.length !== undefined && typeof value !== 'string') {
1822 return Ext.toArray(value);
1829 * Removes the specified item from the array if it exists
1831 * @param {Array} array The array
1832 * @param {Mixed} item The item to remove
1833 * @return {Array} The passed array itself
1835 remove: function(array, item) {
1836 var index = ExtArray.indexOf(array, item);
1839 erase(array, index, 1);
1846 * Push an item into the array only if the array doesn't contain it yet
1848 * @param {Array} array The array
1849 * @param {Mixed} item The item to include
1851 include: function(array, item) {
1852 if (!ExtArray.contains(array, item)) {
1858 * Clone a flat array without referencing the previous one. Note that this is different
1859 * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1860 * for Array.prototype.slice.call(array)
1862 * @param {Array} array The array
1863 * @return {Array} The clone array
1865 clone: function(array) {
1866 return slice.call(array);
1870 * Merge multiple arrays into one with unique items.
1872 * {@link Ext.Array#union} is alias for {@link Ext.Array#merge}
1874 * @param {Array} array1
1875 * @param {Array} array2
1876 * @param {Array} etc
1877 * @return {Array} merged
1880 var args = slice.call(arguments),
1884 for (i = 0, ln = args.length; i < ln; i++) {
1885 array = array.concat(args[i]);
1888 return ExtArray.unique(array);
1892 * Merge multiple arrays into one with unique items that exist in all of the arrays.
1894 * @param {Array} array1
1895 * @param {Array} array2
1896 * @param {Array} etc
1897 * @return {Array} intersect
1899 intersect: function() {
1901 arrays = slice.call(arguments),
1902 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1904 if (!arrays.length) {
1908 // Find the smallest array
1909 for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1910 if (!minArray || array.length < minArray.length) {
1916 minArray = ExtArray.unique(minArray);
1917 erase(arrays, x, 1);
1919 // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1920 // an item in the small array, we're likely to find it before reaching the end
1921 // of the inner loop and can terminate the search early.
1922 for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1925 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1926 for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1934 if (count === arraysLn) {
1943 * Perform a set difference A-B by subtracting all items in array B from array A.
1945 * @param {Array} arrayA
1946 * @param {Array} arrayB
1947 * @return {Array} difference
1949 difference: function(arrayA, arrayB) {
1950 var clone = slice.call(arrayA),
1954 for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1955 for (j = 0; j < ln; j++) {
1956 if (clone[j] === arrayB[i]) {
1968 * Returns a shallow copy of a part of an array. This is equivalent to the native
1969 * call "Array.prototype.slice.call(array, begin, end)". This is often used when "array"
1970 * is "arguments" since the arguments object does not supply a slice method but can
1971 * be the context object to Array.prototype.slice.
1973 * @param {Array} array The array (or arguments object).
1974 * @param {Number} begin The index at which to begin. Negative values are offsets from
1975 * the end of the array.
1976 * @param {Number} end The index at which to end. The copied items do not include
1977 * end. Negative values are offsets from the end of the array. If end is omitted,
1978 * all items up to the end of the array are copied.
1979 * @return {Array} The copied piece of the array.
1981 slice: function(array, begin, end) {
1982 return slice.call(array, begin, end);
1986 * Sorts the elements of an Array.
1987 * By default, this method sorts the elements alphabetically and ascending.
1989 * @param {Array} array The array to sort.
1990 * @param {Function} sortFn (optional) The comparison function.
1991 * @return {Array} The sorted array.
1993 sort: function(array, sortFn) {
1996 return array.sort(sortFn);
1998 return array.sort();
2002 var length = array.length,
2007 for (; i < length; i++) {
2009 for (j = i + 1; j < length; j++) {
2011 comparison = sortFn(array[j], array[min]);
2012 if (comparison < 0) {
2015 } else if (array[j] < array[min]) {
2021 array[i] = array[min];
2030 * Recursively flattens into 1-d Array. Injects Arrays inline.
2033 flatten: function(array) {
2036 function rFlatten(a) {
2039 for (i = 0, ln = a.length; i < ln; i++) {
2042 if (Ext.isArray(v)) {
2052 return rFlatten(array);
2056 * Returns the minimum value in the Array.
2058 * @param {Array|NodeList} array The Array from which to select the minimum value.
2059 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
2060 * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
2061 * @return {Mixed} minValue The minimum value
2063 min: function(array, comparisonFn) {
2067 for (i = 0, ln = array.length; i < ln; i++) {
2071 if (comparisonFn(min, item) === 1) {
2086 * Returns the maximum value in the Array.
2088 * @param {Array|NodeList} array The Array from which to select the maximum value.
2089 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
2090 * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
2091 * @return {Mixed} maxValue The maximum value
2093 max: function(array, comparisonFn) {
2097 for (i = 0, ln = array.length; i < ln; i++) {
2101 if (comparisonFn(max, item) === -1) {
2116 * Calculates the mean of all items in the array.
2118 * @param {Array} array The Array to calculate the mean value of.
2119 * @return {Number} The mean.
2121 mean: function(array) {
2122 return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
2126 * Calculates the sum of all items in the given array.
2128 * @param {Array} array The Array to calculate the sum value of.
2129 * @return {Number} The sum.
2131 sum: function(array) {
2135 for (i = 0,ln = array.length; i < ln; i++) {
2145 _replaceSim: replaceSim, // for unit testing
2146 _spliceSim: spliceSim,
2150 * Removes items from an array. This is functionally equivalent to the splice method
2151 * of Array, but works around bugs in IE8's splice method and does not copy the
2152 * removed elements in order to return them (because very often they are ignored).
2154 * @param {Array} array The Array on which to replace.
2155 * @param {Number} index The index in the array at which to operate.
2156 * @param {Number} removeCount The number of items to remove at index.
2157 * @return {Array} The array passed.
2163 * Inserts items in to an array.
2165 * @param {Array} array The Array on which to replace.
2166 * @param {Number} index The index in the array at which to operate.
2167 * @param {Array} items The array of items to insert at index.
2168 * @return {Array} The array passed.
2170 insert: function (array, index, items) {
2171 return replace(array, index, 0, items);
2175 * Replaces items in an array. This is functionally equivalent to the splice method
2176 * of Array, but works around bugs in IE8's splice method and is often more convenient
2177 * to call because it accepts an array of items to insert rather than use a variadic
2180 * @param {Array} array The Array on which to replace.
2181 * @param {Number} index The index in the array at which to operate.
2182 * @param {Number} removeCount The number of items to remove at index (can be 0).
2183 * @param {Array} insert An optional array of items to insert at index.
2184 * @return {Array} The array passed.
2190 * Replaces items in an array. This is equivalent to the splice method of Array, but
2191 * works around bugs in IE8's splice method. The signature is exactly the same as the
2192 * splice method except that the array is the first argument. All arguments following
2193 * removeCount are inserted in the array at index.
2195 * @param {Array} array The Array on which to replace.
2196 * @param {Number} index The index in the array at which to operate.
2197 * @param {Number} removeCount The number of items to remove at index (can be 0).
2198 * @return {Array} An array containing the removed items.
2207 * @alias Ext.Array#each
2209 Ext.each = ExtArray.each;
2214 * @alias Ext.Array#merge
2216 ExtArray.union = ExtArray.merge;
2219 * Old alias to {@link Ext.Array#min}
2220 * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
2223 * @alias Ext.Array#min
2225 Ext.min = ExtArray.min;
2228 * Old alias to {@link Ext.Array#max}
2229 * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
2232 * @alias Ext.Array#max
2234 Ext.max = ExtArray.max;
2237 * Old alias to {@link Ext.Array#sum}
2238 * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
2241 * @alias Ext.Array#sum
2243 Ext.sum = ExtArray.sum;
2246 * Old alias to {@link Ext.Array#mean}
2247 * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
2250 * @alias Ext.Array#mean
2252 Ext.mean = ExtArray.mean;
2255 * Old alias to {@link Ext.Array#flatten}
2256 * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
2259 * @alias Ext.Array#flatten
2261 Ext.flatten = ExtArray.flatten;
2264 * Old alias to {@link Ext.Array#clean}
2265 * @deprecated 4.0.0 Use {@link Ext.Array#clean} instead
2268 * @alias Ext.Array#clean
2270 Ext.clean = ExtArray.clean;
2273 * Old alias to {@link Ext.Array#unique}
2274 * @deprecated 4.0.0 Use {@link Ext.Array#unique} instead
2277 * @alias Ext.Array#unique
2279 Ext.unique = ExtArray.unique;
2282 * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
2283 * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
2286 * @alias Ext.Array#pluck
2288 Ext.pluck = ExtArray.pluck;
2293 * @alias Ext.Array#toArray
2295 Ext.toArray = function() {
2296 return ExtArray.toArray.apply(ExtArray, arguments);
2301 * @class Ext.Function
2303 * A collection of useful static methods to deal with function callbacks
2309 * A very commonly used method throughout the framework. It acts as a wrapper around another method
2310 * which originally accepts 2 arguments for `name` and `value`.
2311 * The wrapped function then allows "flexible" value setting of either:
2313 * - `name` and `value` as 2 arguments
2314 * - one single object argument with multiple key - value pairs
2318 * var setValue = Ext.Function.flexSetter(function(name, value) {
2319 * this[name] = value;
2323 * // Setting a single name - value
2324 * setValue('name1', 'value1');
2326 * // Settings multiple name - value pairs
2333 * @param {Function} setter
2334 * @returns {Function} flexSetter
2336 flexSetter: function(fn) {
2337 return function(a, b) {
2344 if (typeof a !== 'string') {
2346 if (a.hasOwnProperty(k)) {
2347 fn.call(this, k, a[k]);
2351 if (Ext.enumerables) {
2352 for (i = Ext.enumerables.length; i--;) {
2353 k = Ext.enumerables[i];
2354 if (a.hasOwnProperty(k)) {
2355 fn.call(this, k, a[k]);
2360 fn.call(this, a, b);
2368 * Create a new function from the provided `fn`, change `this` to the provided scope, optionally
2369 * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2371 * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
2373 * @param {Function} fn The function to delegate.
2374 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2375 * **If omitted, defaults to the browser window.**
2376 * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2377 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2378 * if a number the args are inserted at the specified position
2379 * @return {Function} The new function
2381 bind: function(fn, scope, args, appendArgs) {
2383 slice = Array.prototype.slice;
2386 var callArgs = args || arguments;
2388 if (appendArgs === true) {
2389 callArgs = slice.call(arguments, 0);
2390 callArgs = callArgs.concat(args);
2392 else if (Ext.isNumber(appendArgs)) {
2393 callArgs = slice.call(arguments, 0); // copy arguments first
2394 Ext.Array.insert(callArgs, appendArgs, args);
2397 return method.apply(scope || window, callArgs);
2402 * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
2403 * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2404 * This is especially useful when creating callbacks.
2408 * var originalFunction = function(){
2409 * alert(Ext.Array.from(arguments).join(' '));
2412 * var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2414 * callback(); // alerts 'Hello World'
2415 * callback('by Me'); // alerts 'Hello World by Me'
2417 * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
2419 * @param {Function} fn The original function
2420 * @param {Array} args The arguments to pass to new callback
2421 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2422 * @return {Function} The new callback function
2424 pass: function(fn, args, scope) {
2426 args = Ext.Array.from(args);
2430 return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2435 * Create an alias to the provided method property with name `methodName` of `object`.
2436 * Note that the execution scope will still be bound to the provided `object` itself.
2438 * @param {Object/Function} object
2439 * @param {String} methodName
2440 * @return {Function} aliasFn
2442 alias: function(object, methodName) {
2444 return object[methodName].apply(object, arguments);
2449 * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2450 * the original one is not called. The resulting function returns the results of the original function.
2451 * The passed function is called with the parameters of the original function. Example usage:
2453 * var sayHi = function(name){
2454 * alert('Hi, ' + name);
2457 * sayHi('Fred'); // alerts "Hi, Fred"
2459 * // create a new function that validates input without
2460 * // directly modifying the original function:
2461 * var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2462 * return name == 'Brian';
2465 * sayHiToFriend('Fred'); // no alert
2466 * sayHiToFriend('Brian'); // alerts "Hi, Brian"
2468 * @param {Function} origFn The original function.
2469 * @param {Function} newFn The function to call before the original
2470 * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2471 * **If omitted, defaults to the scope in which the original function is called or the browser window.**
2472 * @param {Mixed} returnValue (optional) The value to return if the passed function return false (defaults to null).
2473 * @return {Function} The new function
2475 createInterceptor: function(origFn, newFn, scope, returnValue) {
2476 var method = origFn;
2477 if (!Ext.isFunction(newFn)) {
2485 newFn.method = origFn;
2486 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2492 * Creates a delegate (callback) which, when called, executes after a specific delay.
2494 * @param {Function} fn The function which will be called on a delay when the returned function is called.
2495 * Optionally, a replacement (or additional) argument list may be specified.
2496 * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2497 * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
2498 * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2499 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2500 * if a number the args are inserted at the specified position.
2501 * @return {Function} A function which, when called, executes the original function after the specified delay.
2503 createDelayed: function(fn, delay, scope, args, appendArgs) {
2504 if (scope || args) {
2505 fn = Ext.Function.bind(fn, scope, args, appendArgs);
2509 setTimeout(function() {
2510 fn.apply(me, arguments);
2516 * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2518 * var sayHi = function(name){
2519 * alert('Hi, ' + name);
2522 * // executes immediately:
2525 * // executes after 2 seconds:
2526 * Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2528 * // this syntax is sometimes useful for deferring
2529 * // execution of an anonymous function:
2530 * Ext.Function.defer(function(){
2531 * alert('Anonymous');
2534 * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer}
2536 * @param {Function} fn The function to defer.
2537 * @param {Number} millis The number of milliseconds for the setTimeout call
2538 * (if less than or equal to 0 the function is executed immediately)
2539 * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
2540 * **If omitted, defaults to the browser window.**
2541 * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2542 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2543 * if a number the args are inserted at the specified position
2544 * @return {Number} The timeout id that can be used with clearTimeout
2546 defer: function(fn, millis, obj, args, appendArgs) {
2547 fn = Ext.Function.bind(fn, obj, args, appendArgs);
2549 return setTimeout(fn, millis);
2556 * Create a combined function call sequence of the original function + the passed function.
2557 * The resulting function returns the results of the original function.
2558 * The passed function is called with the parameters of the original function. Example usage:
2560 * var sayHi = function(name){
2561 * alert('Hi, ' + name);
2564 * sayHi('Fred'); // alerts "Hi, Fred"
2566 * var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2567 * alert('Bye, ' + name);
2570 * sayGoodbye('Fred'); // both alerts show
2572 * @param {Function} origFn The original function.
2573 * @param {Function} newFn The function to sequence
2574 * @param {Object} scope (optional) The scope (`this` reference) in which the passed function is executed.
2575 * If omitted, defaults to the scope in which the original function is called or the browser window.
2576 * @return {Function} The new function
2578 createSequence: function(origFn, newFn, scope) {
2579 if (!Ext.isFunction(newFn)) {
2584 var retval = origFn.apply(this || window, arguments);
2585 newFn.apply(scope || this || window, arguments);
2592 * Creates a delegate function, optionally with a bound scope which, when called, buffers
2593 * the execution of the passed function for the configured number of milliseconds.
2594 * If called again within that period, the impending invocation will be canceled, and the
2595 * timeout period will begin again.
2597 * @param {Function} fn The function to invoke on a buffered timer.
2598 * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2600 * @param {Object} scope (optional) The scope (`this` reference) in which
2601 * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2602 * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2603 * passed by the caller.
2604 * @return {Function} A function which invokes the passed function after buffering for the specified time.
2606 createBuffered: function(fn, buffer, scope, args) {
2612 clearInterval(timerId);
2615 timerId = setTimeout(function(){
2616 fn.apply(scope || me, args || arguments);
2623 * Creates a throttled version of the passed function which, when called repeatedly and
2624 * rapidly, invokes the passed function only after a certain interval has elapsed since the
2625 * previous invocation.
2627 * This is useful for wrapping functions which may be called repeatedly, such as
2628 * a handler of a mouse move event when the processing is expensive.
2630 * @param {Function} fn The function to execute at a regular time interval.
2631 * @param {Number} interval The interval **in milliseconds** on which the passed function is executed.
2632 * @param {Object} scope (optional) The scope (`this` reference) in which
2633 * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2634 * @returns {Function} A function which invokes the passed function at the specified interval.
2636 createThrottled: function(fn, interval, scope) {
2637 var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2638 fn.apply(scope || this, lastArgs);
2639 lastCallTime = new Date().getTime();
2643 elapsed = new Date().getTime() - lastCallTime;
2644 lastArgs = arguments;
2646 clearTimeout(timer);
2647 if (!lastCallTime || (elapsed >= interval)) {
2650 timer = setTimeout(execute, interval - elapsed);
2659 * @alias Ext.Function#defer
2661 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2666 * @alias Ext.Function#pass
2668 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2673 * @alias Ext.Function#bind
2675 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2678 * @author Jacky Nguyen <jacky@sencha.com>
2679 * @docauthor Jacky Nguyen <jacky@sencha.com>
2682 * A collection of useful static methods to deal with objects
2689 var ExtObject = Ext.Object = {
2692 * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
2693 * query strings. For example:
2695 var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2697 // objects then equals:
2699 { name: 'hobbies', value: 'reading' },
2700 { name: 'hobbies', value: 'cooking' },
2701 { name: 'hobbies', value: 'swimming' },
2704 var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2712 }, true); // Recursive
2714 // objects then equals:
2716 { name: 'dateOfBirth[day]', value: 3 },
2717 { name: 'dateOfBirth[month]', value: 8 },
2718 { name: 'dateOfBirth[year]', value: 1987 },
2719 { name: 'dateOfBirth[extra][hour]', value: 4 },
2720 { name: 'dateOfBirth[extra][minute]', value: 30 },
2723 * @param {String} name
2724 * @param {Mixed} value
2725 * @param {Boolean} recursive
2728 toQueryObjects: function(name, value, recursive) {
2729 var self = ExtObject.toQueryObjects,
2733 if (Ext.isArray(value)) {
2734 for (i = 0, ln = value.length; i < ln; i++) {
2736 objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2746 else if (Ext.isObject(value)) {
2748 if (value.hasOwnProperty(i)) {
2750 objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2772 * Takes an object and converts it to an encoded query string
2776 Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2777 Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2778 Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2779 Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2780 Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2784 Ext.Object.toQueryString({
2791 hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2792 }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2794 // &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2795 // &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2798 * @param {Object} object The object to encode
2799 * @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
2800 * (PHP / Ruby on Rails servers and similar). Defaults to false
2801 * @return {String} queryString
2804 toQueryString: function(object, recursive) {
2805 var paramObjects = [],
2807 i, j, ln, paramObject, value;
2810 if (object.hasOwnProperty(i)) {
2811 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2815 for (j = 0, ln = paramObjects.length; j < ln; j++) {
2816 paramObject = paramObjects[j];
2817 value = paramObject.value;
2819 if (Ext.isEmpty(value)) {
2822 else if (Ext.isDate(value)) {
2823 value = Ext.Date.toString(value);
2826 params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2829 return params.join('&');
2833 * Converts a query string back into an object.
2837 Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2838 Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2839 Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2840 Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2844 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);
2854 hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2857 * @param {String} queryString The query string to decode
2858 * @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
2859 * PHP / Ruby on Rails servers and similar. Defaults to false
2862 fromQueryString: function(queryString, recursive) {
2863 var parts = queryString.replace(/^\?/, '').split('&'),
2865 temp, components, name, value, i, ln,
2866 part, j, subLn, matchedKeys, matchedName,
2869 for (i = 0, ln = parts.length; i < ln; i++) {
2872 if (part.length > 0) {
2873 components = part.split('=');
2874 name = decodeURIComponent(components[0]);
2875 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2878 if (object.hasOwnProperty(name)) {
2879 if (!Ext.isArray(object[name])) {
2880 object[name] = [object[name]];
2883 object[name].push(value);
2886 object[name] = value;
2890 matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2891 matchedName = name.match(/^([^\[]+)/);
2896 sourceClass: "Ext.Object",
2897 sourceMethod: "fromQueryString",
2898 queryString: queryString,
2899 recursive: recursive,
2900 msg: 'Malformed query string given, failed parsing name from "' + part + '"'
2905 name = matchedName[0];
2908 if (matchedKeys === null) {
2909 object[name] = value;
2913 for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
2914 key = matchedKeys[j];
2915 key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
2923 for (j = 0, subLn = keys.length; j < subLn; j++) {
2926 if (j === subLn - 1) {
2927 if (Ext.isArray(temp) && key === '') {
2935 if (temp[key] === undefined || typeof temp[key] === 'string') {
2936 nextKey = keys[j+1];
2938 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
2952 * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
2953 * by returning `false` in the callback function. For example:
2958 loves: ['food', 'sleeping', 'wife']
2961 Ext.Object.each(person, function(key, value, myself) {
2962 console.log(key + ":" + value);
2964 if (key === 'hairColor') {
2965 return false; // stop the iteration
2969 * @param {Object} object The object to iterate
2970 * @param {Function} fn The callback function. Passed arguments for each iteration are:
2974 - {Object} `object` The object itself
2976 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
2979 each: function(object, fn, scope) {
2980 for (var property in object) {
2981 if (object.hasOwnProperty(property)) {
2982 if (fn.call(scope || object, property, object[property], object) === false) {
2990 * Merges any number of objects recursively without referencing them or their children.
2993 companyName: 'Ext JS',
2994 products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
2998 location: 'Palo Alto',
3004 companyName: 'Sencha Inc.',
3005 products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3008 location: 'Redwood City'
3012 var sencha = Ext.Object.merge(extjs, newStuff);
3014 // extjs and sencha then equals to
3016 companyName: 'Sencha Inc.',
3017 products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
3021 location: 'Redwood City'
3026 * @param {Object} object,...
3027 * @return {Object} merged The object that is created as a result of merging all the objects passed in.
3030 merge: function(source, key, value) {
3031 if (typeof key === 'string') {
3032 if (value && value.constructor === Object) {
3033 if (source[key] && source[key].constructor === Object) {
3034 ExtObject.merge(source[key], value);
3037 source[key] = Ext.clone(value);
3041 source[key] = value;
3048 ln = arguments.length,
3051 for (; i < ln; i++) {
3052 object = arguments[i];
3054 for (property in object) {
3055 if (object.hasOwnProperty(property)) {
3056 ExtObject.merge(source, property, object[property]);
3065 * Returns the first matching key corresponding to the given value.
3066 * If no matching value is found, null is returned.
3073 alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'
3075 * @param {Object} object
3076 * @param {Object} value The value to find
3079 getKey: function(object, value) {
3080 for (var property in object) {
3081 if (object.hasOwnProperty(property) && object[property] === value) {
3090 * Gets all values of the given object as an array.
3092 var values = Ext.Object.getValues({
3095 }); // ['Jacky', 'food']
3097 * @param {Object} object
3098 * @return {Array} An array of values from the object
3101 getValues: function(object) {
3105 for (property in object) {
3106 if (object.hasOwnProperty(property)) {
3107 values.push(object[property]);
3115 * Gets all keys of the given object as an array.
3117 var values = Ext.Object.getKeys({
3120 }); // ['name', 'loves']
3122 * @param {Object} object
3123 * @return {Array} An array of keys from the object
3126 getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
3130 for (property in object) {
3131 if (object.hasOwnProperty(property)) {
3132 keys.push(property);
3140 * Gets the total number of this object's own properties
3142 var size = Ext.Object.getSize({
3145 }); // size equals 2
3147 * @param {Object} object
3148 * @return {Number} size
3151 getSize: function(object) {
3155 for (property in object) {
3156 if (object.hasOwnProperty(property)) {
3167 * A convenient alias method for {@link Ext.Object#merge}
3172 Ext.merge = Ext.Object.merge;
3175 * A convenient alias method for {@link Ext.Object#toQueryString}
3179 * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
3181 Ext.urlEncode = function() {
3182 var args = Ext.Array.from(arguments),
3185 // Support for the old `pre` argument
3186 if ((typeof args[1] === 'string')) {
3187 prefix = args[1] + '&';
3191 return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
3195 * A convenient alias method for {@link Ext.Object#fromQueryString}
3199 * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
3201 Ext.urlDecode = function() {
3202 return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
3209 * A set of useful static methods to deal with date
3210 * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
3211 * this object for convenience
3213 * The date parsing and formatting syntax contains a subset of
3214 * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
3215 * supported will provide results equivalent to their PHP versions.
3217 * The following is a list of all currently supported formats:
3219 Format Description Example returned values
3220 ------ ----------------------------------------------------------------------- -----------------------
3221 d Day of the month, 2 digits with leading zeros 01 to 31
3222 D A short textual representation of the day of the week Mon to Sun
3223 j Day of the month without leading zeros 1 to 31
3224 l A full textual representation of the day of the week Sunday to Saturday
3225 N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
3226 S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
3227 w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
3228 z The day of the year (starting from 0) 0 to 364 (365 in leap years)
3229 W ISO-8601 week number of year, weeks starting on Monday 01 to 53
3230 F A full textual representation of a month, such as January or March January to December
3231 m Numeric representation of a month, with leading zeros 01 to 12
3232 M A short textual representation of a month Jan to Dec
3233 n Numeric representation of a month, without leading zeros 1 to 12
3234 t Number of days in the given month 28 to 31
3235 L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
3236 o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
3237 belongs to the previous or next year, that year is used instead)
3238 Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
3239 y A two digit representation of a year Examples: 99 or 03
3240 a Lowercase Ante meridiem and Post meridiem am or pm
3241 A Uppercase Ante meridiem and Post meridiem AM or PM
3242 g 12-hour format of an hour without leading zeros 1 to 12
3243 G 24-hour format of an hour without leading zeros 0 to 23
3244 h 12-hour format of an hour with leading zeros 01 to 12
3245 H 24-hour format of an hour with leading zeros 00 to 23
3246 i Minutes, with leading zeros 00 to 59
3247 s Seconds, with leading zeros 00 to 59
3248 u Decimal fraction of a second Examples:
3249 (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
3250 100 (i.e. 0.100s) or
3251 999 (i.e. 0.999s) or
3252 999876543210 (i.e. 0.999876543210s)
3253 O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
3254 P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
3255 T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
3256 Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
3259 1) If unspecified, the month / day defaults to the current month / day, 1991 or
3260 the time defaults to midnight, while the timezone defaults to the 1992-10 or
3261 browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
3262 and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
3263 are optional. 1995-07-18T17:21:28-02:00 or
3264 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
3265 least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
3266 of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
3267 Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
3268 date-time granularity which are supported, or see 2000-02-13T21:25:33
3269 http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
3270 U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
3271 MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
3272 \/Date(1238606590509+0800)\/
3275 * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
3278 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
3280 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
3281 console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
3282 console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
3283 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
3286 * Here are some standard date/time patterns that you might find helpful. They
3287 * are not part of the source of Ext.Date, but to use them you can simply copy this
3288 * block of code into any script that is included after Ext.Date and they will also become
3289 * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
3291 Ext.Date.patterns = {
3292 ISO8601Long:"Y-m-d H:i:s",
3293 ISO8601Short:"Y-m-d",
3295 LongDate: "l, F d, Y",
3296 FullDateTime: "l, F d, Y g:i:s A",
3299 LongTime: "g:i:s A",
3300 SortableDateTime: "Y-m-d\\TH:i:s",
3301 UniversalSortableDateTime: "Y-m-d H:i:sO",
3308 var dt = new Date();
3309 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3311 * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3312 * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3317 * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3318 * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3319 * They generate precompiled functions from format patterns instead of parsing and
3320 * processing each pattern every time a date is formatted. These functions are available
3321 * on every Date object.
3326 // create private copy of Ext's Ext.util.Format.format() method
3327 // - to remove unnecessary dependency
3328 // - to resolve namespace conflict with MS-Ajax's implementation
3329 function xf(format) {
3330 var args = Array.prototype.slice.call(arguments, 1);
3331 return format.replace(/\{(\d+)\}/g, function(m, i) {
3338 * Returns the current timestamp
3339 * @return {Date} The current timestamp
3342 now: Date.now || function() {
3350 toString: function(date) {
3351 var pad = Ext.String.leftPad;
3353 return date.getFullYear() + "-"
3354 + pad(date.getMonth() + 1, 2, '0') + "-"
3355 + pad(date.getDate(), 2, '0') + "T"
3356 + pad(date.getHours(), 2, '0') + ":"
3357 + pad(date.getMinutes(), 2, '0') + ":"
3358 + pad(date.getSeconds(), 2, '0');
3362 * Returns the number of milliseconds between two dates
3363 * @param {Date} dateA The first date
3364 * @param {Date} dateB (optional) The second date, defaults to now
3365 * @return {Number} The difference in milliseconds
3367 getElapsed: function(dateA, dateB) {
3368 return Math.abs(dateA - (dateB || new Date()));
3372 * Global flag which determines if strict date parsing should be used.
3373 * Strict date parsing will not roll-over invalid dates, which is the
3374 * default behaviour of javascript Date objects.
3375 * (see {@link #parse} for more information)
3376 * Defaults to <tt>false</tt>.
3383 formatCodeToRegex: function(character, currentGroup) {
3384 // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3385 var p = utilDate.parseCodes[character];
3388 p = typeof p == 'function'? p() : p;
3389 utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3392 return p ? Ext.applyIf({
3393 c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3397 s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3402 * <p>An object hash in which each property is a date parsing function. The property name is the
3403 * format string which that function parses.</p>
3404 * <p>This object is automatically populated with date parsing functions as
3405 * date formats are requested for Ext standard formatting strings.</p>
3406 * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3407 * may be used as a format string to {@link #parse}.<p>
3408 * <p>Example:</p><pre><code>
3409 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3411 * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3412 * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3413 * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3414 * (i.e. prevent javascript Date "rollover") (The default must be false).
3415 * Invalid date strings should return null when parsed.</div></li>
3417 * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3418 * formatting function must be placed into the {@link #formatFunctions} property.
3419 * @property parseFunctions
3424 "MS": function(input, strict) {
3425 // note: the timezone offset is ignored since the MS Ajax server sends
3426 // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3427 var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3428 var r = (input || '').match(re);
3429 return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3435 * <p>An object hash in which each property is a date formatting function. The property name is the
3436 * format string which corresponds to the produced formatted date string.</p>
3437 * <p>This object is automatically populated with date formatting functions as
3438 * date formats are requested for Ext standard formatting strings.</p>
3439 * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3440 * may be used as a format string to {@link #format}. Example:</p><pre><code>
3441 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3443 * <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>
3444 * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3446 * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3447 * parsing function must be placed into the {@link #parseFunctions} property.
3448 * @property formatFunctions
3454 // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3455 return '\\/Date(' + this.getTime() + ')\\/';
3462 * Date interval constant
3469 * Date interval constant
3476 * Date interval constant
3482 /** Date interval constant
3489 * Date interval constant
3496 * Date interval constant
3503 * Date interval constant
3510 * <p>An object hash containing default date values used during date parsing.</p>
3511 * <p>The following properties are available:<div class="mdetail-params"><ul>
3512 * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3513 * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3514 * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3515 * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3516 * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3517 * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3518 * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3520 * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3521 * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3522 * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3523 * It is the responsiblity of the developer to account for this.</b></p>
3526 // set default day value to the first day of the month
3527 Ext.Date.defaults.d = 1;
3529 // parse a February date string containing only year and month values.
3530 // setting the default day value to 1 prevents weird date rollover issues
3531 // when attempting to parse the following date string on, for example, March 31st 2009.
3532 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3534 * @property defaults
3541 * An array of textual day names.
3542 * Override these values for international dates.
3545 Ext.Date.dayNames = [
3565 * An array of textual month names.
3566 * Override these values for international dates.
3569 Ext.Date.monthNames = [
3594 * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3595 * Override these values for international dates.
3598 Ext.Date.monthNumbers = {
3599 'ShortJanNameInYourLang':0,
3600 'ShortFebNameInYourLang':1,
3622 * <p>The date format string that the {@link Ext.util.Format#dateRenderer}
3623 * and {@link Ext.util.Format#date} functions use. See {@link Ext.Date} for details.</p>
3624 * <p>This defaults to <code>m/d/Y</code>, but may be overridden in a locale file.</p>
3625 * @property defaultFormat
3629 defaultFormat : "m/d/Y",
3631 * Get the short month name for the given month number.
3632 * Override this function for international dates.
3633 * @param {Number} month A zero-based javascript month number.
3634 * @return {String} The short month name.
3637 getShortMonthName : function(month) {
3638 return utilDate.monthNames[month].substring(0, 3);
3642 * Get the short day name for the given day number.
3643 * Override this function for international dates.
3644 * @param {Number} day A zero-based javascript day number.
3645 * @return {String} The short day name.
3648 getShortDayName : function(day) {
3649 return utilDate.dayNames[day].substring(0, 3);
3653 * Get the zero-based javascript month number for the given short/full month name.
3654 * Override this function for international dates.
3655 * @param {String} name The short/full month name.
3656 * @return {Number} The zero-based javascript month number.
3659 getMonthNumber : function(name) {
3660 // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3661 return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3665 * Checks if the specified format contains hour information
3666 * @param {String} format The format to check
3667 * @return {Boolean} True if the format contains hour information
3671 formatContainsHourInfo : (function(){
3672 var stripEscapeRe = /(\\.)/g,
3673 hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3674 return function(format){
3675 return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3680 * Checks if the specified format contains information about
3681 * anything other than the time.
3682 * @param {String} format The format to check
3683 * @return {Boolean} True if the format contains information about
3684 * date/day information.
3688 formatContainsDateInfo : (function(){
3689 var stripEscapeRe = /(\\.)/g,
3690 dateInfoRe = /([djzmnYycU]|MS)/;
3692 return function(format){
3693 return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3698 * The base format-code to formatting-function hashmap used by the {@link #format} method.
3699 * Formatting functions are strings (or functions which return strings) which
3700 * will return the appropriate value when evaluated in the context of the Date object
3701 * from which the {@link #format} method is called.
3702 * Add to / override these mappings for custom date formatting.
3703 * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3706 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3707 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3713 d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3714 D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3715 j: "this.getDate()",
3716 l: "Ext.Date.dayNames[this.getDay()]",
3717 N: "(this.getDay() ? this.getDay() : 7)",
3718 S: "Ext.Date.getSuffix(this)",
3720 z: "Ext.Date.getDayOfYear(this)",
3721 W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3722 F: "Ext.Date.monthNames[this.getMonth()]",
3723 m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3724 M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3725 n: "(this.getMonth() + 1)",
3726 t: "Ext.Date.getDaysInMonth(this)",
3727 L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3728 o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3729 Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3730 y: "('' + this.getFullYear()).substring(2, 4)",
3731 a: "(this.getHours() < 12 ? 'am' : 'pm')",
3732 A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3733 g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3734 G: "this.getHours()",
3735 h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3736 H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3737 i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3738 s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3739 u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3740 O: "Ext.Date.getGMTOffset(this)",
3741 P: "Ext.Date.getGMTOffset(this, true)",
3742 T: "Ext.Date.getTimezone(this)",
3743 Z: "(this.getTimezoneOffset() * -60)",
3745 c: function() { // ISO-8601 -- GMT format
3746 for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3747 var e = c.charAt(i);
3748 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3750 return code.join(" + ");
3753 c: function() { // ISO-8601 -- UTC format
3755 "this.getUTCFullYear()", "'-'",
3756 "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3757 "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3759 "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3760 "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3761 "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3767 U: "Math.round(this.getTime() / 1000)"
3771 * Checks if the passed Date parameters will cause a javascript Date "rollover".
3772 * @param {Number} year 4-digit year
3773 * @param {Number} month 1-based month-of-year
3774 * @param {Number} day Day of month
3775 * @param {Number} hour (optional) Hour
3776 * @param {Number} minute (optional) Minute
3777 * @param {Number} second (optional) Second
3778 * @param {Number} millisecond (optional) Millisecond
3779 * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3782 isValid : function(y, m, d, h, i, s, ms) {
3789 // Special handling for year < 100
3790 var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3792 return y == dt.getFullYear() &&
3793 m == dt.getMonth() + 1 &&
3794 d == dt.getDate() &&
3795 h == dt.getHours() &&
3796 i == dt.getMinutes() &&
3797 s == dt.getSeconds() &&
3798 ms == dt.getMilliseconds();
3802 * Parses the passed string using the specified date format.
3803 * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3804 * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3805 * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3806 * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3807 * Keep in mind that the input date string must precisely match the specified format string
3808 * in order for the parse operation to be successful (failed parse operations return a null value).
3809 * <p>Example:</p><pre><code>
3810 //dt = Fri May 25 2007 (current date)
3811 var dt = new Date();
3813 //dt = Thu May 25 2006 (today's month/day in 2006)
3814 dt = Ext.Date.parse("2006", "Y");
3816 //dt = Sun Jan 15 2006 (all date parts specified)
3817 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3819 //dt = Sun Jan 15 2006 15:20:01
3820 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3822 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3823 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3825 * @param {String} input The raw date string.
3826 * @param {String} format The expected date string format.
3827 * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3828 (defaults to false). Invalid date strings will return null when parsed.
3829 * @return {Date} The parsed Date.
3832 parse : function(input, format, strict) {
3833 var p = utilDate.parseFunctions;
3834 if (p[format] == null) {
3835 utilDate.createParser(format);
3837 return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3841 parseDate: function(input, format, strict){
3842 return utilDate.parse(input, format, strict);
3847 getFormatCode : function(character) {
3848 var f = utilDate.formatCodes[character];
3851 f = typeof f == 'function'? f() : f;
3852 utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3855 // note: unknown characters are treated as literals
3856 return f || ("'" + Ext.String.escape(character) + "'");
3860 createFormat : function(format) {
3865 for (var i = 0; i < format.length; ++i) {
3866 ch = format.charAt(i);
3867 if (!special && ch == "\\") {
3869 } else if (special) {
3871 code.push("'" + Ext.String.escape(ch) + "'");
3873 code.push(utilDate.getFormatCode(ch));
3876 utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3880 createParser : (function() {
3882 "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3883 "def = Ext.Date.defaults,",
3884 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3889 "if(u != null){", // i.e. unix time is defined
3890 "v = new Date(u * 1000);", // give top priority to UNIX time
3892 // create Date object representing midnight of the current day;
3893 // this will provide us with our date defaults
3894 // (note: clearTime() handles Daylight Saving Time automatically)
3895 "dt = Ext.Date.clearTime(new Date);",
3897 // date calculations (note: these calculations create a dependency on Ext.Number.from())
3898 "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3899 "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3900 "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3902 // time calculations (note: these calculations create a dependency on Ext.Number.from())
3903 "h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3904 "i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3905 "s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3906 "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3908 "if(z >= 0 && y >= 0){",
3909 // both the year and zero-based day of year are defined and >= 0.
3910 // these 2 values alone provide sufficient info to create a full date object
3912 // create Date object representing January 1st for the given year
3913 // handle years < 100 appropriately
3914 "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3916 // then add day of year, checking for Date "rollover" if necessary
3917 "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3918 "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3919 "v = null;", // invalid date, so return null
3921 // plain old Date object
3922 // handle years < 100 properly
3923 "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3929 // favour UTC offset over GMT offset
3931 // reset to UTC, then add offset
3932 "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3934 // reset to GMT, then add offset
3935 "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
3942 return function(format) {
3943 var regexNum = utilDate.parseRegexes.length,
3950 for (var i = 0; i < format.length; ++i) {
3951 ch = format.charAt(i);
3952 if (!special && ch == "\\") {
3954 } else if (special) {
3956 regex.push(Ext.String.escape(ch));
3958 var obj = utilDate.formatCodeToRegex(ch, currentGroup);
3959 currentGroup += obj.g;
3961 if (obj.g && obj.c) {
3967 utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
3968 utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
3976 * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
3977 * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
3978 * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
3982 c:"d = parseInt(results[{0}], 10);\n",
3983 s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
3987 c:"d = parseInt(results[{0}], 10);\n",
3988 s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
3991 for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
3995 s:"(?:" + a.join("|") +")"
4002 s:"(?:" + utilDate.dayNames.join("|") + ")"
4008 s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
4018 s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
4022 c:"z = parseInt(results[{0}], 10);\n",
4023 s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
4028 s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
4033 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
4034 s:"(" + utilDate.monthNames.join("|") + ")"
4038 for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
4039 return Ext.applyIf({
4040 s:"(" + a.join("|") + ")"
4041 }, utilDate.formatCodeToRegex("F"));
4045 c:"m = parseInt(results[{0}], 10) - 1;\n",
4046 s:"(\\d{2})" // month number with leading zeros (01 - 12)
4050 c:"m = parseInt(results[{0}], 10) - 1;\n",
4051 s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
4056 s:"(?:\\d{2})" // no. of days in the month (28 - 31)
4064 return utilDate.formatCodeToRegex("Y");
4068 c:"y = parseInt(results[{0}], 10);\n",
4069 s:"(\\d{4})" // 4-digit year
4073 c:"var ty = parseInt(results[{0}], 10);\n"
4074 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
4078 * In the am/pm parsing routines, we allow both upper and lower case
4079 * even though it doesn't exactly match the spec. It gives much more flexibility
4080 * in being able to specify case insensitive regexes.
4084 c:"if (/(am)/i.test(results[{0}])) {\n"
4085 + "if (!h || h == 12) { h = 0; }\n"
4086 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4091 c:"if (/(am)/i.test(results[{0}])) {\n"
4092 + "if (!h || h == 12) { h = 0; }\n"
4093 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
4097 return utilDate.formatCodeToRegex("G");
4101 c:"h = parseInt(results[{0}], 10);\n",
4102 s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
4105 return utilDate.formatCodeToRegex("H");
4109 c:"h = parseInt(results[{0}], 10);\n",
4110 s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
4114 c:"i = parseInt(results[{0}], 10);\n",
4115 s:"(\\d{2})" // minutes with leading zeros (00 - 59)
4119 c:"s = parseInt(results[{0}], 10);\n",
4120 s:"(\\d{2})" // seconds with leading zeros (00 - 59)
4124 c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
4125 s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
4130 "o = results[{0}];",
4131 "var sn = o.substring(0,1),", // get + / - sign
4132 "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4133 "mn = o.substring(3,5) % 60;", // get minutes
4134 "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
4136 s: "([+\-]\\d{4})" // GMT offset in hrs and mins
4141 "o = results[{0}];",
4142 "var sn = o.substring(0,1),", // get + / - sign
4143 "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
4144 "mn = o.substring(4,6) % 60;", // get minutes
4145 "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
4147 s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
4152 s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
4156 c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
4157 + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
4158 s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
4163 utilDate.formatCodeToRegex("Y", 1), // year
4164 utilDate.formatCodeToRegex("m", 2), // month
4165 utilDate.formatCodeToRegex("d", 3), // day
4166 utilDate.formatCodeToRegex("h", 4), // hour
4167 utilDate.formatCodeToRegex("i", 5), // minute
4168 utilDate.formatCodeToRegex("s", 6), // second
4169 {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)
4170 {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
4171 "if(results[8]) {", // timezone specified
4172 "if(results[8] == 'Z'){",
4174 "}else if (results[8].indexOf(':') > -1){",
4175 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
4177 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
4183 for (var i = 0, l = arr.length; i < l; ++i) {
4184 calc.push(arr[i].c);
4191 arr[0].s, // year (required)
4192 "(?:", "-", arr[1].s, // month (optional)
4193 "(?:", "-", arr[2].s, // day (optional)
4195 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
4196 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
4197 "(?::", arr[5].s, ")?", // seconds (optional)
4198 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
4199 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
4208 c:"u = parseInt(results[{0}], 10);\n",
4209 s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
4213 //Old Ext.Date prototype methods.
4215 dateFormat: function(date, format) {
4216 return utilDate.format(date, format);
4220 * Formats a date given the supplied format string.
4221 * @param {Date} date The date to format
4222 * @param {String} format The format string
4223 * @return {String} The formatted date
4225 format: function(date, format) {
4226 if (utilDate.formatFunctions[format] == null) {
4227 utilDate.createFormat(format);
4229 var result = utilDate.formatFunctions[format].call(date);
4234 * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
4236 * Note: The date string returned by the javascript Date object's toString() method varies
4237 * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
4238 * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
4239 * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
4240 * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
4241 * from the GMT offset portion of the date string.
4242 * @param {Date} date The date
4243 * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
4245 getTimezone : function(date) {
4246 // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
4248 // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
4249 // 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)
4250 // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
4251 // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
4252 // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
4254 // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
4255 // step 1: (?:\((.*)\) -- find timezone in parentheses
4256 // 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
4257 // step 3: remove all non uppercase characters found in step 1 and 2
4258 return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
4262 * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
4263 * @param {Date} date The date
4264 * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
4265 * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
4267 getGMTOffset : function(date, colon) {
4268 var offset = date.getTimezoneOffset();
4269 return (offset > 0 ? "-" : "+")
4270 + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
4271 + (colon ? ":" : "")
4272 + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
4276 * Get the numeric day number of the year, adjusted for leap year.
4277 * @param {Date} date The date
4278 * @return {Number} 0 to 364 (365 in leap years).
4280 getDayOfYear: function(date) {
4282 d = Ext.Date.clone(date),
4283 m = date.getMonth(),
4286 for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
4287 num += utilDate.getDaysInMonth(d);
4289 return num + date.getDate() - 1;
4293 * Get the numeric ISO-8601 week number of the year.
4294 * (equivalent to the format specifier 'W', but without a leading zero).
4295 * @param {Date} date The date
4296 * @return {Number} 1 to 53
4299 getWeekOfYear : (function() {
4300 // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4301 var ms1d = 864e5, // milliseconds in a day
4302 ms7d = 7 * ms1d; // milliseconds in a week
4304 return function(date) { // return a closure so constants get calculated only once
4305 var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4306 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4307 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4309 return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4314 * Checks if the current date falls within a leap year.
4315 * @param {Date} date The date
4316 * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4318 isLeapYear : function(date) {
4319 var year = date.getFullYear();
4320 return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4324 * Get the first day of the current month, adjusted for leap year. The returned value
4325 * is the numeric day index within the week (0-6) which can be used in conjunction with
4326 * the {@link #monthNames} array to retrieve the textual day name.
4329 var dt = new Date('1/10/2007'),
4330 firstDay = Ext.Date.getFirstDayOfMonth(dt);
4331 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4333 * @param {Date} date The date
4334 * @return {Number} The day number (0-6).
4336 getFirstDayOfMonth : function(date) {
4337 var day = (date.getDay() - (date.getDate() - 1)) % 7;
4338 return (day < 0) ? (day + 7) : day;
4342 * Get the last day of the current month, adjusted for leap year. The returned value
4343 * is the numeric day index within the week (0-6) which can be used in conjunction with
4344 * the {@link #monthNames} array to retrieve the textual day name.
4347 var dt = new Date('1/10/2007'),
4348 lastDay = Ext.Date.getLastDayOfMonth(dt);
4349 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4351 * @param {Date} date The date
4352 * @return {Number} The day number (0-6).
4354 getLastDayOfMonth : function(date) {
4355 return utilDate.getLastDateOfMonth(date).getDay();
4360 * Get the date of the first day of the month in which this date resides.
4361 * @param {Date} date The date
4364 getFirstDateOfMonth : function(date) {
4365 return new Date(date.getFullYear(), date.getMonth(), 1);
4369 * Get the date of the last day of the month in which this date resides.
4370 * @param {Date} date The date
4373 getLastDateOfMonth : function(date) {
4374 return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4378 * Get the number of days in the current month, adjusted for leap year.
4379 * @param {Date} date The date
4380 * @return {Number} The number of days in the month.
4383 getDaysInMonth: (function() {
4384 var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4386 return function(date) { // return a closure for efficiency
4387 var m = date.getMonth();
4389 return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4394 * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4395 * @param {Date} date The date
4396 * @return {String} 'st, 'nd', 'rd' or 'th'.
4398 getSuffix : function(date) {
4399 switch (date.getDate()) {
4416 * Creates and returns a new Date instance with the exact same date value as the called instance.
4417 * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4418 * variable will also be changed. When the intention is to create a new variable that will not
4419 * modify the original instance, you should create a clone.
4421 * Example of correctly cloning a date:
4424 var orig = new Date('10/1/2006');
4427 console.log(orig); //returns 'Thu Oct 05 2006'!
4430 var orig = new Date('10/1/2006'),
4431 copy = Ext.Date.clone(orig);
4433 console.log(orig); //returns 'Thu Oct 01 2006'
4435 * @param {Date} date The date
4436 * @return {Date} The new Date instance.
4438 clone : function(date) {
4439 return new Date(date.getTime());
4443 * Checks if the current date is affected by Daylight Saving Time (DST).
4444 * @param {Date} date The date
4445 * @return {Boolean} True if the current date is affected by DST.
4447 isDST : function(date) {
4448 // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4449 // courtesy of @geoffrey.mcgill
4450 return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4454 * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4455 * automatically adjusting for Daylight Saving Time (DST) where applicable.
4456 * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4457 * @param {Date} date The date
4458 * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4459 * @return {Date} this or the clone.
4461 clearTime : function(date, clone) {
4463 return Ext.Date.clearTime(Ext.Date.clone(date));
4466 // get current date before clearing time
4467 var d = date.getDate();
4473 date.setMilliseconds(0);
4475 if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4476 // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4477 // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4479 // increment hour until cloned date == current date
4480 for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4483 date.setHours(c.getHours());
4490 * Provides a convenient method for performing basic date arithmetic. This method
4491 * does not modify the Date instance being called - it creates and returns
4492 * a new Date instance containing the resulting date value.
4497 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4498 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4500 // Negative values will be subtracted:
4501 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4502 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4506 * @param {Date} date The date to modify
4507 * @param {String} interval A valid date interval enum value.
4508 * @param {Number} value The amount to add to the current date.
4509 * @return {Date} The new Date instance.
4511 add : function(date, interval, value) {
4512 var d = Ext.Date.clone(date),
4514 if (!interval || value === 0) return d;
4516 switch(interval.toLowerCase()) {
4517 case Ext.Date.MILLI:
4518 d.setMilliseconds(d.getMilliseconds() + value);
4520 case Ext.Date.SECOND:
4521 d.setSeconds(d.getSeconds() + value);
4523 case Ext.Date.MINUTE:
4524 d.setMinutes(d.getMinutes() + value);
4527 d.setHours(d.getHours() + value);
4530 d.setDate(d.getDate() + value);
4532 case Ext.Date.MONTH:
4533 var day = date.getDate();
4535 day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4538 d.setMonth(date.getMonth() + value);
4541 d.setFullYear(date.getFullYear() + value);
4548 * Checks if a date falls on or between the given start and end dates.
4549 * @param {Date} date The date to check
4550 * @param {Date} start Start date
4551 * @param {Date} end End date
4552 * @return {Boolean} true if this date falls on or between the given start and end dates.
4554 between : function(date, start, end) {
4555 var t = date.getTime();
4556 return start.getTime() <= t && t <= end.getTime();
4559 //Maintains compatibility with old static and prototype window.Date methods.
4560 compat: function() {
4561 var nativeDate = window.Date,
4563 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'],
4564 proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4567 Ext.Array.forEach(statics, function(s) {
4568 nativeDate[s] = utilDate[s];
4571 //Append to prototype
4572 Ext.Array.forEach(proto, function(s) {
4573 nativeDate.prototype[s] = function() {
4574 var args = Array.prototype.slice.call(arguments);
4576 return utilDate[s].apply(utilDate, args);
4582 var utilDate = Ext.Date;
4587 * @author Jacky Nguyen <jacky@sencha.com>
4588 * @docauthor Jacky Nguyen <jacky@sencha.com>
4591 * The root of all classes created with {@link Ext#define}
4592 * All prototype and static members of this class are inherited by any other class
4595 (function(flexSetter) {
4597 var Base = Ext.Base = function() {};
4599 $className: 'Ext.Base',
4604 * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4605 * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4606 * for a detailed comparison
4608 * Ext.define('My.Cat', {
4610 * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4613 * constructor: function() {
4614 * alert(this.self.speciesName); / dependent on 'this'
4619 * clone: function() {
4620 * return new this.self();
4625 * Ext.define('My.SnowLeopard', {
4628 * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
4632 * var cat = new My.Cat(); // alerts 'Cat'
4633 * var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
4635 * var clone = snowLeopard.clone();
4636 * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
4643 // Default constructor, simply returns `this`
4644 constructor: function() {
4649 * Initialize configuration for this class. a typical example:
4651 * Ext.define('My.awesome.Class', {
4652 * // The default config
4658 * constructor: function(config) {
4659 * this.initConfig(config);
4665 * var awesome = new My.awesome.Class({
4666 * name: 'Super Awesome'
4669 * alert(awesome.getName()); // 'Super Awesome'
4672 * @param {Object} config
4673 * @return {Object} mixins The mixin prototypes as key - value pairs
4675 initConfig: function(config) {
4676 if (!this.$configInited) {
4677 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4679 this.applyConfig(this.config);
4681 this.$configInited = true;
4690 setConfig: function(config) {
4691 this.applyConfig(config || {});
4699 applyConfig: flexSetter(function(name, value) {
4700 var setter = 'set' + Ext.String.capitalize(name);
4702 if (typeof this[setter] === 'function') {
4703 this[setter].call(this, value);
4710 * Call the parent's overridden method. For example:
4712 * Ext.define('My.own.A', {
4713 * constructor: function(test) {
4718 * Ext.define('My.own.B', {
4719 * extend: 'My.own.A',
4721 * constructor: function(test) {
4724 * this.callParent([test + 1]);
4728 * Ext.define('My.own.C', {
4729 * extend: 'My.own.B',
4731 * constructor: function() {
4732 * alert("Going to call parent's overriden constructor...");
4734 * this.callParent(arguments);
4738 * var a = new My.own.A(1); // alerts '1'
4739 * var b = new My.own.B(1); // alerts '1', then alerts '2'
4740 * var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4741 * // alerts '2', then alerts '3'
4744 * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4745 * from the current method, for example: `this.callParent(arguments)`
4746 * @return {Mixed} Returns the result from the superclass' method
4748 callParent: function(args) {
4749 var method = this.callParent.caller,
4750 parentClass, methodName;
4752 if (!method.$owner) {
4754 if (!method.caller) {
4756 sourceClass: Ext.getClassName(this),
4757 sourceMethod: "callParent",
4758 msg: "Attempting to call a protected method from the public scope, which is not allowed"
4763 method = method.caller;
4766 parentClass = method.$owner.superclass;
4767 methodName = method.$name;
4770 if (!(methodName in parentClass)) {
4772 sourceClass: Ext.getClassName(this),
4773 sourceMethod: methodName,
4774 msg: "this.callParent() was called but there's no such method (" + methodName +
4775 ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
4780 return parentClass[methodName].apply(this, args || []);
4785 * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4786 * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4787 * `this` points to during run-time
4789 * Ext.define('My.Cat', {
4792 * speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4795 * constructor: function() {
4796 * var statics = this.statics();
4798 * alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
4799 * // equivalent to: My.Cat.speciesName
4801 * alert(this.self.speciesName); // dependent on 'this'
4803 * statics.totalCreated++;
4808 * clone: function() {
4809 * var cloned = new this.self; // dependent on 'this'
4811 * cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
4818 * Ext.define('My.SnowLeopard', {
4822 * speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
4825 * constructor: function() {
4826 * this.callParent();
4830 * var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
4832 * var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4834 * var clone = snowLeopard.clone();
4835 * alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
4836 * alert(clone.groupName); // alerts 'Cat'
4838 * alert(My.Cat.totalCreated); // alerts 3
4843 statics: function() {
4844 var method = this.statics.caller,
4851 return method.$owner;
4855 * Call the original method that was previously overridden with {@link Ext.Base#override}
4857 * Ext.define('My.Cat', {
4858 * constructor: function() {
4859 * alert("I'm a cat!");
4866 * constructor: function() {
4867 * alert("I'm going to be a cat!");
4869 * var instance = this.callOverridden();
4871 * alert("Meeeeoooowwww");
4877 * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4878 * // alerts "I'm a cat!"
4879 * // alerts "Meeeeoooowwww"
4881 * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4882 * @return {Mixed} Returns the result after calling the overridden method
4884 callOverridden: function(args) {
4885 var method = this.callOverridden.caller;
4888 if (!method.$owner) {
4890 sourceClass: Ext.getClassName(this),
4891 sourceMethod: "callOverridden",
4892 msg: "Attempting to call a protected method from the public scope, which is not allowed"
4896 if (!method.$previous) {
4898 sourceClass: Ext.getClassName(this),
4899 sourceMethod: "callOverridden",
4900 msg: "this.callOverridden was called in '" + method.$name +
4901 "' but this method has never been overridden"
4906 return method.$previous.apply(this, args || []);
4909 destroy: function() {}
4912 // These static properties will be copied to every newly created class with {@link Ext#define}
4913 Ext.apply(Ext.Base, {
4915 * Create a new instance of this Class.
4917 * Ext.define('My.cool.Class', {
4921 * My.cool.Class.create({
4925 * All parameters are passed to the constructor of the class.
4927 * @return {Object} the created instance.
4930 create: function() {
4931 return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4937 own: flexSetter(function(name, value) {
4938 if (typeof value === 'function') {
4939 this.ownMethod(name, value);
4942 this.prototype[name] = value;
4949 ownMethod: function(name, fn) {
4952 if (fn.$owner !== undefined && fn !== Ext.emptyFn) {
4956 return originalFn.apply(this, arguments);
4962 className = Ext.getClassName(this);
4964 fn.displayName = className + '#' + name;
4970 this.prototype[name] = fn;
4974 * Add / override static properties of this class.
4976 * Ext.define('My.cool.Class', {
4980 * My.cool.Class.addStatics({
4981 * someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
4982 * method1: function() { ... }, // My.cool.Class.method1 = function() { ... };
4983 * method2: function() { ... } // My.cool.Class.method2 = function() { ... };
4986 * @param {Object} members
4987 * @return {Ext.Base} this
4990 addStatics: function(members) {
4991 for (var name in members) {
4992 if (members.hasOwnProperty(name)) {
4993 this[name] = members[name];
5001 * Add methods / properties to the prototype of this class.
5003 * Ext.define('My.awesome.Cat', {
5004 * constructor: function() {
5009 * My.awesome.Cat.implement({
5010 * meow: function() {
5011 * alert('Meowww...');
5015 * var kitty = new My.awesome.Cat;
5018 * @param {Object} members
5021 implement: function(members) {
5022 var prototype = this.prototype,
5023 name, i, member, previous;
5025 var className = Ext.getClassName(this);
5027 for (name in members) {
5028 if (members.hasOwnProperty(name)) {
5029 member = members[name];
5031 if (typeof member === 'function') {
5032 member.$owner = this;
5033 member.$name = name;
5036 member.displayName = className + '#' + name;
5041 prototype[name] = member;
5045 if (Ext.enumerables) {
5046 var enumerables = Ext.enumerables;
5048 for (i = enumerables.length; i--;) {
5049 name = enumerables[i];
5051 if (members.hasOwnProperty(name)) {
5052 member = members[name];
5053 member.$owner = this;
5054 member.$name = name;
5055 prototype[name] = member;
5062 * Borrow another class' members to the prototype of this class.
5064 * Ext.define('Bank', {
5066 * printMoney: function() {
5071 * Ext.define('Thief', {
5075 * Thief.borrow(Bank, ['money', 'printMoney']);
5077 * var steve = new Thief();
5079 * alert(steve.money); // alerts '$$$'
5080 * steve.printMoney(); // alerts '$$$$$$$'
5082 * @param {Ext.Base} fromClass The class to borrow members from
5083 * @param {Array/String} members The names of the members to borrow
5084 * @return {Ext.Base} this
5088 borrow: function(fromClass, members) {
5089 var fromPrototype = fromClass.prototype,
5092 members = Ext.Array.from(members);
5094 for (i = 0, ln = members.length; i < ln; i++) {
5095 member = members[i];
5097 this.own(member, fromPrototype[member]);
5104 * Override prototype members of this class. Overridden methods can be invoked via
5105 * {@link Ext.Base#callOverridden}
5107 * Ext.define('My.Cat', {
5108 * constructor: function() {
5109 * alert("I'm a cat!");
5116 * constructor: function() {
5117 * alert("I'm going to be a cat!");
5119 * var instance = this.callOverridden();
5121 * alert("Meeeeoooowwww");
5127 * var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
5128 * // alerts "I'm a cat!"
5129 * // alerts "Meeeeoooowwww"
5131 * @param {Object} members
5132 * @return {Ext.Base} this
5135 override: function(members) {
5136 var prototype = this.prototype,
5137 name, i, member, previous;
5139 for (name in members) {
5140 if (members.hasOwnProperty(name)) {
5141 member = members[name];
5143 if (typeof member === 'function') {
5144 if (typeof prototype[name] === 'function') {
5145 previous = prototype[name];
5146 member.$previous = previous;
5149 this.ownMethod(name, member);
5152 prototype[name] = member;
5157 if (Ext.enumerables) {
5158 var enumerables = Ext.enumerables;
5160 for (i = enumerables.length; i--;) {
5161 name = enumerables[i];
5163 if (members.hasOwnProperty(name)) {
5164 if (prototype[name] !== undefined) {
5165 previous = prototype[name];
5166 members[name].$previous = previous;
5169 this.ownMethod(name, members[name]);
5178 * Used internally by the mixins pre-processor
5181 mixin: flexSetter(function(name, cls) {
5182 var mixin = cls.prototype,
5183 my = this.prototype,
5187 if (mixin.hasOwnProperty(i)) {
5188 if (my[i] === undefined) {
5189 if (typeof mixin[i] === 'function') {
5192 if (fn.$owner === undefined) {
5193 this.ownMethod(i, fn);
5203 else if (i === 'config' && my.config && mixin.config) {
5204 Ext.Object.merge(my.config, mixin.config);
5209 if (my.mixins === undefined) {
5213 my.mixins[name] = mixin;
5217 * Get the current class' name in string format.
5219 * Ext.define('My.cool.Class', {
5220 * constructor: function() {
5221 * alert(this.self.getName()); // alerts 'My.cool.Class'
5225 * My.cool.Class.getName(); // 'My.cool.Class'
5227 * @return {String} className
5229 getName: function() {
5230 return Ext.getClassName(this);
5234 * Create aliases for existing prototype methods. Example:
5236 * Ext.define('My.cool.Class', {
5237 * method1: function() { ... },
5238 * method2: function() { ... }
5241 * var test = new My.cool.Class();
5243 * My.cool.Class.createAlias({
5244 * method3: 'method1',
5245 * method4: 'method2'
5248 * test.method3(); // test.method1()
5250 * My.cool.Class.createAlias('method5', 'method3');
5252 * test.method5(); // test.method3() -> test.method1()
5254 * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
5255 * {@link Ext.Function#flexSetter flexSetter}
5256 * @param {String/Object} origin The original method name
5260 createAlias: flexSetter(function(alias, origin) {
5261 this.prototype[alias] = this.prototype[origin];
5265 })(Ext.Function.flexSetter);
5268 * @author Jacky Nguyen <jacky@sencha.com>
5269 * @docauthor Jacky Nguyen <jacky@sencha.com>
5272 * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
5273 * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
5274 * to enable namespacing and dynamic dependency resolution.
5278 * Ext.define(className, properties);
5280 * in which `properties` is an object represent a collection of properties that apply to the class. See
5281 * {@link Ext.ClassManager#create} for more detailed instructions.
5283 * Ext.define('Person', {
5286 * constructor: function(name) {
5294 * eat: function(foodType) {
5295 * alert("I'm eating: " + foodType);
5301 * var aaron = new Person("Aaron");
5302 * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5304 * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5305 * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5309 * Ext.define('Developer', {
5312 * constructor: function(name, isGeek) {
5313 * this.isGeek = isGeek;
5315 * // Apply a method from the parent class' prototype
5316 * this.callParent([name]);
5322 * code: function(language) {
5323 * alert("I'm coding in: " + language);
5331 * var jacky = new Developer("Jacky", true);
5332 * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
5333 * // alert("I'm eating: Bugs");
5335 * See {@link Ext.Base#callParent} for more details on calling superclass' methods
5339 * Ext.define('CanPlayGuitar', {
5340 * playGuitar: function() {
5341 * alert("F#...G...D...A");
5345 * Ext.define('CanComposeSongs', {
5346 * composeSongs: function() { ... }
5349 * Ext.define('CanSing', {
5350 * sing: function() {
5351 * alert("I'm on the highway to hell...")
5355 * Ext.define('Musician', {
5359 * canPlayGuitar: 'CanPlayGuitar',
5360 * canComposeSongs: 'CanComposeSongs',
5361 * canSing: 'CanSing'
5365 * Ext.define('CoolPerson', {
5369 * canPlayGuitar: 'CanPlayGuitar',
5370 * canSing: 'CanSing'
5373 * sing: function() {
5374 * alert("Ahem....");
5376 * this.mixins.canSing.sing.call(this);
5378 * alert("[Playing guitar at the same time...]");
5380 * this.playGuitar();
5384 * var me = new CoolPerson("Jacky");
5386 * me.sing(); // alert("Ahem...");
5387 * // alert("I'm on the highway to hell...");
5388 * // alert("[Playing guitar at the same time...]");
5389 * // alert("F#...G...D...A");
5393 * Ext.define('SmartPhone', {
5395 * hasTouchScreen: false,
5396 * operatingSystem: 'Other',
5400 * isExpensive: false,
5402 * constructor: function(config) {
5403 * this.initConfig(config);
5408 * applyPrice: function(price) {
5409 * this.isExpensive = (price > 500);
5414 * applyOperatingSystem: function(operatingSystem) {
5415 * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
5419 * return operatingSystem;
5423 * var iPhone = new SmartPhone({
5424 * hasTouchScreen: true,
5425 * operatingSystem: 'iOS'
5428 * iPhone.getPrice(); // 500;
5429 * iPhone.getOperatingSystem(); // 'iOS'
5430 * iPhone.getHasTouchScreen(); // true;
5431 * iPhone.hasTouchScreen(); // true
5433 * iPhone.isExpensive; // false;
5434 * iPhone.setPrice(600);
5435 * iPhone.getPrice(); // 600
5436 * iPhone.isExpensive; // true;
5438 * iPhone.setOperatingSystem('AlienOS');
5439 * iPhone.getOperatingSystem(); // 'Other'
5443 * Ext.define('Computer', {
5445 * factory: function(brand) {
5446 * // 'this' in static methods refer to the class itself
5447 * return new this(brand);
5451 * constructor: function() { ... }
5454 * var dellComputer = Computer.factory('Dell');
5456 * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
5457 * static properties within class methods
5464 baseStaticProperties = [],
5467 for (baseStaticProperty in Base) {
5468 if (Base.hasOwnProperty(baseStaticProperty)) {
5469 baseStaticProperties.push(baseStaticProperty);
5474 * @method constructor
5475 * Creates new class.
5476 * @param {Object} classData An object represent the properties of this class
5477 * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
5478 * Note that the creation process can be asynchronous depending on the pre-processors used.
5479 * @return {Ext.Base} The newly created class
5481 Ext.Class = Class = function(newClass, classData, onClassCreated) {
5482 if (typeof newClass !== 'function') {
5483 onClassCreated = classData;
5484 classData = newClass;
5485 newClass = function() {
5486 return this.constructor.apply(this, arguments);
5494 var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5495 registeredPreprocessors = Class.getPreprocessors(),
5498 preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
5500 for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5501 staticPropertyName = baseStaticProperties[i];
5502 newClass[staticPropertyName] = Base[staticPropertyName];
5505 delete classData.preprocessors;
5507 for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5508 preprocessor = preprocessorStack[j];
5510 if (typeof preprocessor === 'string') {
5511 preprocessor = registeredPreprocessors[preprocessor];
5513 if (!preprocessor.always) {
5514 if (classData.hasOwnProperty(preprocessor.name)) {
5515 preprocessors.push(preprocessor.fn);
5519 preprocessors.push(preprocessor.fn);
5523 preprocessors.push(preprocessor);
5527 classData.onClassCreated = onClassCreated;
5529 classData.onBeforeClassCreated = function(cls, data) {
5530 onClassCreated = data.onClassCreated;
5532 delete data.onBeforeClassCreated;
5533 delete data.onClassCreated;
5535 cls.implement(data);
5537 if (onClassCreated) {
5538 onClassCreated.call(cls, cls);
5542 process = function(cls, data) {
5543 preprocessor = preprocessors[index++];
5545 if (!preprocessor) {
5546 data.onBeforeClassCreated.apply(this, arguments);
5550 if (preprocessor.call(this, cls, data, process) !== false) {
5551 process.apply(this, arguments);
5555 process.call(Class, newClass, classData);
5566 * Register a new pre-processor to be used during the class creation process
5568 * @member Ext.Class registerPreprocessor
5569 * @param {String} name The pre-processor's name
5570 * @param {Function} fn The callback function to be executed. Typical format:
5572 function(cls, data, fn) {
5575 // Execute this when the processing is finished.
5576 // Asynchronous processing is perfectly ok
5578 fn.call(this, cls, data);
5582 * Passed arguments for this function are:
5584 * - `{Function} cls`: The created class
5585 * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
5586 * - `{Function} fn`: The callback function that <b>must</b> to be executed when this pre-processor finishes,
5587 * regardless of whether the processing is synchronous or aynchronous
5589 * @return {Ext.Class} this
5592 registerPreprocessor: function(name, fn, always) {
5593 this.preprocessors[name] = {
5595 always: always || false,
5603 * Retrieve a pre-processor callback function by its name, which has been registered before
5605 * @param {String} name
5606 * @return {Function} preprocessor
5608 getPreprocessor: function(name) {
5609 return this.preprocessors[name];
5612 getPreprocessors: function() {
5613 return this.preprocessors;
5617 * Retrieve the array stack of default pre-processors
5619 * @return {Function} defaultPreprocessors
5621 getDefaultPreprocessors: function() {
5622 return this.defaultPreprocessors || [];
5626 * Set the default array stack of default pre-processors
5628 * @param {Array} preprocessors
5629 * @return {Ext.Class} this
5631 setDefaultPreprocessors: function(preprocessors) {
5632 this.defaultPreprocessors = Ext.Array.from(preprocessors);
5638 * Insert this pre-processor at a specific position in the stack, optionally relative to
5639 * any existing pre-processor. For example:
5641 Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5645 fn.call(this, cls, data);
5647 }).insertDefaultPreprocessor('debug', 'last');
5649 * @param {String} name The pre-processor name. Note that it needs to be registered with
5650 * {@link Ext#registerPreprocessor registerPreprocessor} before this
5651 * @param {String} offset The insertion position. Four possible values are:
5652 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5653 * @param {String} relativeName
5654 * @return {Ext.Class} this
5657 setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5658 var defaultPreprocessors = this.defaultPreprocessors,
5661 if (typeof offset === 'string') {
5662 if (offset === 'first') {
5663 defaultPreprocessors.unshift(name);
5667 else if (offset === 'last') {
5668 defaultPreprocessors.push(name);
5673 offset = (offset === 'after') ? 1 : -1;
5676 index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5679 Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name);
5687 * @cfg {String} extend
5688 * The parent class that this class extends. For example:
5690 * Ext.define('Person', {
5691 * say: function(text) { alert(text); }
5694 * Ext.define('Developer', {
5696 * say: function(text) { this.callParent(["print "+text]); }
5699 Class.registerPreprocessor('extend', function(cls, data) {
5700 var extend = data.extend,
5702 basePrototype = base.prototype,
5703 prototype = function() {},
5704 parent, i, k, ln, staticName, parentStatics,
5705 parentPrototype, clsPrototype;
5707 if (extend && extend !== Object) {
5714 parentPrototype = parent.prototype;
5716 prototype.prototype = parentPrototype;
5717 clsPrototype = cls.prototype = new prototype();
5719 if (!('$class' in parent)) {
5720 for (i in basePrototype) {
5721 if (!parentPrototype[i]) {
5722 parentPrototype[i] = basePrototype[i];
5727 clsPrototype.self = cls;
5729 cls.superclass = clsPrototype.superclass = parentPrototype;
5733 // Statics inheritance
5734 parentStatics = parentPrototype.$inheritableStatics;
5736 if (parentStatics) {
5737 for (k = 0, ln = parentStatics.length; k < ln; k++) {
5738 staticName = parentStatics[k];
5740 if (!cls.hasOwnProperty(staticName)) {
5741 cls[staticName] = parent[staticName];
5746 // Merge the parent class' config object without referencing it
5747 if (parentPrototype.config) {
5748 clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5751 clsPrototype.config = {};
5754 if (clsPrototype.$onExtended) {
5755 clsPrototype.$onExtended.call(cls, cls, data);
5758 if (data.onClassExtended) {
5759 clsPrototype.$onExtended = data.onClassExtended;
5760 delete data.onClassExtended;
5766 * @cfg {Object} statics
5767 * List of static methods for this class. For example:
5769 * Ext.define('Computer', {
5771 * factory: function(brand) {
5772 * // 'this' in static methods refer to the class itself
5773 * return new this(brand);
5777 * constructor: function() { ... }
5780 * var dellComputer = Computer.factory('Dell');
5782 Class.registerPreprocessor('statics', function(cls, data) {
5783 var statics = data.statics,
5786 for (name in statics) {
5787 if (statics.hasOwnProperty(name)) {
5788 cls[name] = statics[name];
5792 delete data.statics;
5796 * @cfg {Object} inheritableStatics
5797 * List of inheritable static methods for this class.
5798 * Otherwise just like {@link #statics} but subclasses inherit these methods.
5800 Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5801 var statics = data.inheritableStatics,
5803 prototype = cls.prototype,
5806 inheritableStatics = prototype.$inheritableStatics;
5808 if (!inheritableStatics) {
5809 inheritableStatics = prototype.$inheritableStatics = [];
5812 for (name in statics) {
5813 if (statics.hasOwnProperty(name)) {
5814 cls[name] = statics[name];
5815 inheritableStatics.push(name);
5819 delete data.inheritableStatics;
5823 * @cfg {Object} mixins
5824 * List of classes to mix into this class. For example:
5826 * Ext.define('CanSing', {
5827 * sing: function() {
5828 * alert("I'm on the highway to hell...")
5832 * Ext.define('Musician', {
5836 * canSing: 'CanSing'
5840 Class.registerPreprocessor('mixins', function(cls, data) {
5841 cls.mixin(data.mixins);
5847 * @cfg {Object} config
5848 * List of configuration options with their default values, for which automatically
5849 * accessor methods are generated. For example:
5851 * Ext.define('SmartPhone', {
5853 * hasTouchScreen: false,
5854 * operatingSystem: 'Other',
5857 * constructor: function(cfg) {
5858 * this.initConfig(cfg);
5862 * var iPhone = new SmartPhone({
5863 * hasTouchScreen: true,
5864 * operatingSystem: 'iOS'
5867 * iPhone.getPrice(); // 500;
5868 * iPhone.getOperatingSystem(); // 'iOS'
5869 * iPhone.getHasTouchScreen(); // true;
5870 * iPhone.hasTouchScreen(); // true
5872 Class.registerPreprocessor('config', function(cls, data) {
5873 var prototype = cls.prototype;
5875 Ext.Object.each(data.config, function(name) {
5876 var cName = name.charAt(0).toUpperCase() + name.substr(1),
5878 apply = 'apply' + cName,
5879 setter = 'set' + cName,
5880 getter = 'get' + cName;
5882 if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5883 data[apply] = function(val) {
5888 if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5889 data[setter] = function(val) {
5890 var ret = this[apply].call(this, val, this[pName]);
5892 if (ret !== undefined) {
5900 if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5901 data[getter] = function() {
5907 Ext.Object.merge(prototype.config, data.config);
5911 Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
5913 // Backwards compatible
5914 Ext.extend = function(subclass, superclass, members) {
5915 if (arguments.length === 2 && Ext.isObject(superclass)) {
5916 members = superclass;
5917 superclass = subclass;
5924 Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5927 members.extend = superclass;
5928 members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
5931 cls = new Class(subclass, members);
5934 cls = new Class(members);
5937 cls.prototype.override = function(o) {
5939 if (o.hasOwnProperty(m)) {
5951 * @author Jacky Nguyen <jacky@sencha.com>
5952 * @docauthor Jacky Nguyen <jacky@sencha.com>
5953 * @class Ext.ClassManager
5955 * Ext.ClassManager manages all classes and handles mapping from string class name to
5956 * actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5957 * these convenient shorthands:
5959 * - {@link Ext#define Ext.define}
5960 * - {@link Ext#create Ext.create}
5961 * - {@link Ext#widget Ext.widget}
5962 * - {@link Ext#getClass Ext.getClass}
5963 * - {@link Ext#getClassName Ext.getClassName}
5967 (function(Class, alias) {
5969 var slice = Array.prototype.slice;
5971 var Manager = Ext.ClassManager = {
5974 * @property {Object} classes
5975 * All classes which were defined through the ClassManager. Keys are the
5976 * name of the classes and the values are references to the classes.
5989 namespaceRewrites: [{
5998 alternateToName: {},
6004 enableNamespaceParseCache: true,
6007 namespaceParseCache: {},
6014 instantiationCounts: {},
6018 * Checks if a class has already been created.
6020 * @param {String} className
6021 * @return {Boolean} exist
6023 isCreated: function(className) {
6024 var i, ln, part, root, parts;
6027 if (typeof className !== 'string' || className.length < 1) {
6029 sourceClass: "Ext.ClassManager",
6030 sourceMethod: "exist",
6031 msg: "Invalid classname, must be a string and must not be empty"
6036 if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
6041 parts = this.parseNamespace(className);
6043 for (i = 0, ln = parts.length; i < ln; i++) {
6046 if (typeof part !== 'string') {
6049 if (!root || !root[part]) {
6057 Ext.Loader.historyPush(className);
6059 this.existCache[className] = true;
6065 * Supports namespace rewriting
6068 parseNamespace: function(namespace) {
6070 if (typeof namespace !== 'string') {
6072 sourceClass: "Ext.ClassManager",
6073 sourceMethod: "parseNamespace",
6074 msg: "Invalid namespace, must be a string"
6079 var cache = this.namespaceParseCache;
6081 if (this.enableNamespaceParseCache) {
6082 if (cache.hasOwnProperty(namespace)) {
6083 return cache[namespace];
6088 rewrites = this.namespaceRewrites,
6089 rewrite, from, to, i, ln, root = Ext.global;
6091 for (i = 0, ln = rewrites.length; i < ln; i++) {
6092 rewrite = rewrites[i];
6093 from = rewrite.from;
6096 if (namespace === from || namespace.substring(0, from.length) === from) {
6097 namespace = namespace.substring(from.length);
6099 if (typeof to !== 'string') {
6102 parts = parts.concat(to.split('.'));
6111 parts = parts.concat(namespace.split('.'));
6113 if (this.enableNamespaceParseCache) {
6114 cache[namespace] = parts;
6121 * Creates a namespace and assign the `value` to the created object
6123 * Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
6125 * alert(MyCompany.pkg.Example === someObject); // alerts true
6127 * @param {String} name
6128 * @param {Mixed} value
6130 setNamespace: function(name, value) {
6131 var root = Ext.global,
6132 parts = this.parseNamespace(name),
6136 for (i = 0, ln = parts.length; i < ln; i++) {
6139 if (typeof part !== 'string') {
6156 * The new Ext.ns, supports namespace rewriting
6159 createNamespaces: function() {
6160 var root = Ext.global,
6161 parts, part, i, j, ln, subLn;
6163 for (i = 0, ln = arguments.length; i < ln; i++) {
6164 parts = this.parseNamespace(arguments[i]);
6166 for (j = 0, subLn = parts.length; j < subLn; j++) {
6169 if (typeof part !== 'string') {
6185 * Sets a name reference to a class.
6187 * @param {String} name
6188 * @param {Object} value
6189 * @return {Ext.ClassManager} this
6191 set: function(name, value) {
6192 var targetName = this.getName(value);
6194 this.classes[name] = this.setNamespace(name, value);
6196 if (targetName && targetName !== name) {
6197 this.maps.alternateToName[name] = targetName;
6204 * Retrieve a class by its name.
6206 * @param {String} name
6207 * @return {Class} class
6209 get: function(name) {
6210 if (this.classes.hasOwnProperty(name)) {
6211 return this.classes[name];
6214 var root = Ext.global,
6215 parts = this.parseNamespace(name),
6218 for (i = 0, ln = parts.length; i < ln; i++) {
6221 if (typeof part !== 'string') {
6224 if (!root || !root[part]) {
6236 * Register the alias for a class.
6238 * @param {Class/String} cls a reference to a class or a className
6239 * @param {String} alias Alias to use when referring to this class
6241 setAlias: function(cls, alias) {
6242 var aliasToNameMap = this.maps.aliasToName,
6243 nameToAliasesMap = this.maps.nameToAliases,
6246 if (typeof cls === 'string') {
6249 className = this.getName(cls);
6252 if (alias && aliasToNameMap[alias] !== className) {
6254 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
6255 Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
6256 "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
6260 aliasToNameMap[alias] = className;
6263 if (!nameToAliasesMap[className]) {
6264 nameToAliasesMap[className] = [];
6268 Ext.Array.include(nameToAliasesMap[className], alias);
6275 * Get a reference to the class by its alias.
6277 * @param {String} alias
6278 * @return {Class} class
6280 getByAlias: function(alias) {
6281 return this.get(this.getNameByAlias(alias));
6285 * Get the name of a class by its alias.
6287 * @param {String} alias
6288 * @return {String} className
6290 getNameByAlias: function(alias) {
6291 return this.maps.aliasToName[alias] || '';
6295 * Get the name of a class by its alternate name.
6297 * @param {String} alternate
6298 * @return {String} className
6300 getNameByAlternate: function(alternate) {
6301 return this.maps.alternateToName[alternate] || '';
6305 * Get the aliases of a class by the class name
6307 * @param {String} name
6308 * @return {Array} aliases
6310 getAliasesByName: function(name) {
6311 return this.maps.nameToAliases[name] || [];
6315 * Get the name of the class by its reference or its instance.
6317 * Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
6319 * {@link Ext#getClassName Ext.getClassName} is alias for {@link Ext.ClassManager#getName Ext.ClassManager.getName}.
6321 * @param {Class/Object} object
6322 * @return {String} className
6324 getName: function(object) {
6325 return object && object.$className || '';
6329 * Get the class of the provided object; returns null if it's not an instance
6330 * of any class created with Ext.define.
6332 * var component = new Ext.Component();
6334 * Ext.ClassManager.getClass(component); // returns Ext.Component
6336 * {@link Ext#getClass Ext.getClass} is alias for {@link Ext.ClassManager#getClass Ext.ClassManager.getClass}.
6338 * @param {Object} object
6339 * @return {Class} class
6341 getClass: function(object) {
6342 return object && object.self || null;
6348 * Ext.ClassManager.create('My.awesome.Class', {
6349 * someProperty: 'something',
6350 * someMethod: function() { ... }
6354 * alert('Created!');
6355 * alert(this === My.awesome.Class); // alerts true
6357 * var myInstance = new this();
6360 * {@link Ext#define Ext.define} is alias for {@link Ext.ClassManager#create Ext.ClassManager.create}.
6362 * @param {String} className The class name to create in string dot-namespaced format, for example:
6363 * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
6364 * It is highly recommended to follow this simple convention:
6366 * - The root and the class name are 'CamelCased'
6367 * - Everything else is lower-cased
6369 * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
6370 * strings, except those in the reserved list below:
6372 * - {@link Ext.Base#self self}
6373 * - {@link Ext.Class#alias alias}
6374 * - {@link Ext.Class#alternateClassName alternateClassName}
6375 * - {@link Ext.Class#config config}
6376 * - {@link Ext.Class#extend extend}
6377 * - {@link Ext.Class#inheritableStatics inheritableStatics}
6378 * - {@link Ext.Class#mixins mixins}
6379 * - {@link Ext.Class#requires requires}
6380 * - {@link Ext.Class#singleton singleton}
6381 * - {@link Ext.Class#statics statics}
6382 * - {@link Ext.Class#uses uses}
6384 * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
6385 * (`this`) will be the newly created class itself.
6386 * @return {Ext.Base}
6388 create: function(className, data, createdFn) {
6392 if (typeof className !== 'string') {
6395 sourceMethod: "define",
6396 msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
6401 data.$className = className;
6403 return new Class(data, function() {
6404 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6405 registeredPostprocessors = manager.postprocessors,
6407 postprocessors = [],
6408 postprocessor, postprocessors, process, i, ln;
6410 delete data.postprocessors;
6412 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6413 postprocessor = postprocessorStack[i];
6415 if (typeof postprocessor === 'string') {
6416 postprocessor = registeredPostprocessors[postprocessor];
6418 if (!postprocessor.always) {
6419 if (data[postprocessor.name] !== undefined) {
6420 postprocessors.push(postprocessor.fn);
6424 postprocessors.push(postprocessor.fn);
6428 postprocessors.push(postprocessor);
6432 process = function(clsName, cls, clsData) {
6433 postprocessor = postprocessors[index++];
6435 if (!postprocessor) {
6436 manager.set(className, cls);
6438 Ext.Loader.historyPush(className);
6441 createdFn.call(cls, cls);
6447 if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6448 process.apply(this, arguments);
6452 process.call(manager, className, this, data);
6457 * Instantiate a class by its alias.
6459 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6460 * attempt to load the class via synchronous loading.
6462 * var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6464 * {@link Ext#createByAlias Ext.createByAlias} is alias for {@link Ext.ClassManager#instantiateByAlias Ext.ClassManager.instantiateByAlias}.
6466 * @param {String} alias
6467 * @param {Mixed} args,... Additional arguments after the alias will be passed to the
6468 * class constructor.
6469 * @return {Object} instance
6471 instantiateByAlias: function() {
6472 var alias = arguments[0],
6473 args = slice.call(arguments),
6474 className = this.getNameByAlias(alias);
6477 className = this.maps.aliasToName[alias];
6483 sourceMethod: "createByAlias",
6484 msg: "Cannot create an instance of unrecognized alias: " + alias
6490 if (Ext.global.console) {
6491 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
6492 "Ext.require('" + alias + "') above Ext.onReady");
6496 Ext.syncRequire(className);
6499 args[0] = className;
6501 return this.instantiate.apply(this, args);
6505 * Instantiate a class by either full name, alias or alternate name.
6507 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6508 * attempt to load the class via synchronous loading.
6510 * For example, all these three lines return the same result:
6513 * var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6516 * var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6518 * // full class name
6519 * var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6521 * {@link Ext#create Ext.create} is alias for {@link Ext.ClassManager#instantiate Ext.ClassManager.instantiate}.
6523 * @param {String} name
6524 * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
6525 * @return {Object} instance
6527 instantiate: function() {
6528 var name = arguments[0],
6529 args = slice.call(arguments, 1),
6533 if (typeof name !== 'function') {
6535 if ((typeof name !== 'string' || name.length < 1)) {
6538 sourceMethod: "create",
6539 msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
6544 cls = this.get(name);
6550 // No record of this class name, it's possibly an alias, so look it up
6552 possibleName = this.getNameByAlias(name);
6555 name = possibleName;
6557 cls = this.get(name);
6561 // Still no record of this class name, it's possibly an alternate name, so look it up
6563 possibleName = this.getNameByAlternate(name);
6566 name = possibleName;
6568 cls = this.get(name);
6572 // Still not existing at this point, try to load it via synchronous mode as the last resort
6575 if (Ext.global.console) {
6576 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
6577 "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
6581 Ext.syncRequire(name);
6583 cls = this.get(name);
6590 sourceMethod: "create",
6591 msg: "Cannot create an instance of unrecognized class name / alias: " + alias
6595 if (typeof cls !== 'function') {
6598 sourceMethod: "create",
6599 msg: "'" + name + "' is a singleton and cannot be instantiated"
6605 if (!this.instantiationCounts[name]) {
6606 this.instantiationCounts[name] = 0;
6609 this.instantiationCounts[name]++;
6612 return this.getInstantiator(args.length)(cls, args);
6620 dynInstantiate: function(name, args) {
6621 args = Ext.Array.from(args, true);
6624 return this.instantiate.apply(this, args);
6631 getInstantiator: function(length) {
6632 if (!this.instantiators[length]) {
6636 for (i = 0; i < length; i++) {
6637 args.push('a['+i+']');
6640 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6643 return this.instantiators[length];
6654 defaultPostprocessors: [],
6657 * Register a post-processor function.
6659 * @param {String} name
6660 * @param {Function} postprocessor
6662 registerPostprocessor: function(name, fn, always) {
6663 this.postprocessors[name] = {
6665 always: always || false,
6673 * Set the default post processors array stack which are applied to every class.
6675 * @param {String/Array} The name of a registered post processor or an array of registered names.
6676 * @return {Ext.ClassManager} this
6678 setDefaultPostprocessors: function(postprocessors) {
6679 this.defaultPostprocessors = Ext.Array.from(postprocessors);
6685 * Insert this post-processor at a specific position in the stack, optionally relative to
6686 * any existing post-processor
6688 * @param {String} name The post-processor name. Note that it needs to be registered with
6689 * {@link Ext.ClassManager#registerPostprocessor} before this
6690 * @param {String} offset The insertion position. Four possible values are:
6691 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6692 * @param {String} relativeName
6693 * @return {Ext.ClassManager} this
6695 setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6696 var defaultPostprocessors = this.defaultPostprocessors,
6699 if (typeof offset === 'string') {
6700 if (offset === 'first') {
6701 defaultPostprocessors.unshift(name);
6705 else if (offset === 'last') {
6706 defaultPostprocessors.push(name);
6711 offset = (offset === 'after') ? 1 : -1;
6714 index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6717 Ext.Array.splice(defaultPostprocessors, Math.max(0, index + offset), 0, name);
6724 * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6725 * or class names. Expressions support wildcards:
6727 * // returns ['Ext.window.Window']
6728 * var window = Ext.ClassManager.getNamesByExpression('widget.window');
6730 * // returns ['widget.panel', 'widget.window', ...]
6731 * var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6733 * // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6734 * var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6736 * @param {String} expression
6737 * @return {Array} classNames
6740 getNamesByExpression: function(expression) {
6741 var nameToAliasesMap = this.maps.nameToAliases,
6743 name, alias, aliases, possibleName, regex, i, ln;
6746 if (typeof expression !== 'string' || expression.length < 1) {
6748 sourceClass: "Ext.ClassManager",
6749 sourceMethod: "getNamesByExpression",
6750 msg: "Expression " + expression + " is invalid, must be a non-empty string"
6755 if (expression.indexOf('*') !== -1) {
6756 expression = expression.replace(/\*/g, '(.*?)');
6757 regex = new RegExp('^' + expression + '$');
6759 for (name in nameToAliasesMap) {
6760 if (nameToAliasesMap.hasOwnProperty(name)) {
6761 aliases = nameToAliasesMap[name];
6763 if (name.search(regex) !== -1) {
6767 for (i = 0, ln = aliases.length; i < ln; i++) {
6770 if (alias.search(regex) !== -1) {
6780 possibleName = this.getNameByAlias(expression);
6783 names.push(possibleName);
6785 possibleName = this.getNameByAlternate(expression);
6788 names.push(possibleName);
6790 names.push(expression);
6800 * @cfg {[String]} alias
6802 * List of short aliases for class names. Most useful for defining xtypes for widgets:
6804 * Ext.define('MyApp.CoolPanel', {
6805 * extend: 'Ext.panel.Panel',
6806 * alias: ['widget.coolpanel'],
6810 * // Using Ext.create
6811 * Ext.widget('widget.coolpanel');
6812 * // Using the shorthand for widgets and in xtypes
6813 * Ext.widget('panel', {
6815 * {xtype: 'coolpanel', html: 'Foo'},
6816 * {xtype: 'coolpanel', html: 'Bar'}
6820 Manager.registerPostprocessor('alias', function(name, cls, data) {
6821 var aliases = data.alias,
6822 widgetPrefix = 'widget.',
6825 if (!(aliases instanceof Array)) {
6826 aliases = [aliases];
6829 for (i = 0, ln = aliases.length; i < ln; i++) {
6833 if (typeof alias !== 'string') {
6836 sourceMethod: "define",
6837 msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
6842 this.setAlias(cls, alias);
6845 // This is ugly, will change to make use of parseNamespace for alias later on
6846 for (i = 0, ln = aliases.length; i < ln; i++) {
6849 if (alias.substring(0, widgetPrefix.length) === widgetPrefix) {
6850 // Only the first alias with 'widget.' prefix will be used for xtype
6851 cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length);
6858 * @cfg {Boolean} singleton
6860 * When set to true, the class will be instanciated as singleton. For example:
6862 * Ext.define('Logger', {
6864 * log: function(msg) {
6869 * Logger.log('Hello');
6871 Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
6872 fn.call(this, name, new cls(), data);
6877 * @cfg {String/[String]} alternateClassName
6879 * Defines alternate names for this class. For example:
6881 * Ext.define('Developer', {
6882 * alternateClassName: ['Coder', 'Hacker'],
6883 * code: function(msg) {
6884 * alert('Typing... ' + msg);
6888 * var joe = Ext.create('Developer');
6889 * joe.code('stackoverflow');
6891 * var rms = Ext.create('Hacker');
6892 * rms.code('hack hack');
6894 Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
6895 var alternates = data.alternateClassName,
6898 if (!(alternates instanceof Array)) {
6899 alternates = [alternates];
6902 for (i = 0, ln = alternates.length; i < ln; i++) {
6903 alternate = alternates[i];
6906 if (typeof alternate !== 'string') {
6909 sourceMethod: "define",
6910 msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
6915 this.set(alternate, cls);
6919 Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
6925 * @alias Ext.ClassManager#instantiate
6927 create: alias(Manager, 'instantiate'),
6931 * API to be stablized
6933 * @param {Mixed} item
6934 * @param {String} namespace
6936 factory: function(item, namespace) {
6937 if (item instanceof Array) {
6940 for (i = 0, ln = item.length; i < ln; i++) {
6941 item[i] = Ext.factory(item[i], namespace);
6947 var isString = (typeof item === 'string');
6949 if (isString || (item instanceof Object && item.constructor === Object)) {
6950 var name, config = {};
6956 name = item.className;
6958 delete config.className;
6961 if (namespace !== undefined && name.indexOf(namespace) === -1) {
6962 name = namespace + '.' + Ext.String.capitalize(name);
6965 return Ext.create(name, config);
6968 if (typeof item === 'function') {
6969 return Ext.create(item);
6976 * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
6978 * var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
6979 * var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
6983 * @param {String} name xtype of the widget to create.
6984 * @return {Object} widget instance
6986 widget: function(name) {
6987 var args = slice.call(arguments);
6988 args[0] = 'widget.' + name;
6990 return Manager.instantiateByAlias.apply(Manager, args);
6996 * @alias Ext.ClassManager#instantiateByAlias
6998 createByAlias: alias(Manager, 'instantiateByAlias'),
7003 * @alias Ext.ClassManager#create
7005 define: alias(Manager, 'create'),
7010 * @alias Ext.ClassManager#getName
7012 getClassName: alias(Manager, 'getName'),
7016 * @param {Mixed} object
7018 getDisplayName: function(object) {
7019 if (object.displayName) {
7020 return object.displayName;
7023 if (object.$name && object.$class) {
7024 return Ext.getClassName(object.$class) + '#' + object.$name;
7027 if (object.$className) {
7028 return object.$className;
7037 * @alias Ext.ClassManager#getClass
7039 getClass: alias(Manager, 'getClass'),
7042 * Creates namespaces to be used for scoping variables and classes so that they are not global.
7043 * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
7045 * Ext.namespace('Company', 'Company.data');
7047 * // equivalent and preferable to the above syntax
7048 * Ext.namespace('Company.data');
7050 * Company.Widget = function() { ... };
7052 * Company.data.CustomStore = function(config) { ... };
7056 * @param {String} namespace1
7057 * @param {String} namespace2
7058 * @param {String} etc
7059 * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
7061 namespace: alias(Manager, 'createNamespaces')
7065 * Old name for {@link Ext#widget}.
7066 * @deprecated 4.0.0 Use {@link Ext#widget} instead.
7071 Ext.createWidget = Ext.widget;
7074 * Convenient alias for {@link Ext#namespace Ext.namespace}
7077 * @alias Ext#namespace
7079 Ext.ns = Ext.namespace;
7081 Class.registerPreprocessor('className', function(cls, data) {
7082 if (data.$className) {
7083 cls.$className = data.$className;
7085 cls.displayName = cls.$className;
7090 Class.setDefaultPreprocessorPosition('className', 'first');
7092 })(Ext.Class, Ext.Function.alias);
7097 * @author Jacky Nguyen <jacky@sencha.com>
7098 * @docauthor Jacky Nguyen <jacky@sencha.com>
7100 * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
7101 * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
7102 * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons
7105 * # Asynchronous Loading
7109 * + No web server needed: you can run the application via the file system protocol
7110 * (i.e: `file://path/to/your/index.html`)
7111 * + Best possible debugging experience: error messages come with the exact file name and line number
7113 * - *Disadvantages:*
7114 * + Dependencies need to be specified before-hand
7116 * ### Method 1: Explicitly include what you need:
7119 * Ext.require({String/Array} expressions);
7121 * // Example: Single alias
7122 * Ext.require('widget.window');
7124 * // Example: Single class name
7125 * Ext.require('Ext.window.Window');
7127 * // Example: Multiple aliases / class names mix
7128 * Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
7131 * Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
7133 * ### Method 2: Explicitly exclude what you don't need:
7135 * // Syntax: Note that it must be in this chaining format.
7136 * Ext.exclude({String/Array} expressions)
7137 * .require({String/Array} expressions);
7139 * // Include everything except Ext.data.*
7140 * Ext.exclude('Ext.data.*').require('*');Â
7142 * // Include all widgets except widget.checkbox*,
7143 * // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
7144 * Ext.exclude('widget.checkbox*').require('widget.*');
7146 * # Synchronous Loading on Demand
7149 * + There's no need to specify dependencies before-hand, which is always the convenience of including
7152 * - *Disadvantages:*
7153 * + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
7154 * + Must be from the same domain due to XHR restriction
7155 * + Need a web server, same reason as above
7157 * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
7159 * Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
7161 * Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
7163 * Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
7165 * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
7166 * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load
7167 * the given class and all its dependencies.
7169 * # Hybrid Loading - The Best of Both Worlds
7171 * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
7173 * ### Step 1: Start writing your application using synchronous approach.
7175 * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
7177 * Ext.onReady(function(){
7178 * var window = Ext.createWidget('window', {
7185 * title: 'Hello Dialog',
7187 * title: 'Navigation',
7188 * collapsible: true,
7194 * title: 'TabPanel',
7202 * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these:
7204 * [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code ClassManager.js:432
7205 * [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
7207 * Simply copy and paste the suggested code above `Ext.onReady`, e.g.:
7209 * Ext.require('Ext.window.Window');
7210 * Ext.require('Ext.layout.container.Border');
7214 * Everything should now load via asynchronous mode.
7218 * It's important to note that dynamic loading should only be used during development on your local machines.
7219 * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
7220 * the whole process of transitioning from / to between development / maintenance and production as easy as
7221 * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies
7222 * your application needs in the exact loading sequence. It's as simple as concatenating all files in this
7223 * array into one, then include it on top of your application.
7225 * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
7227 (function(Manager, Class, flexSetter, alias) {
7231 isNonBrowser = typeof window === 'undefined',
7232 isNodeJS = isNonBrowser && (typeof require === 'function'),
7233 isPhantomJS = (typeof phantom !== 'undefined' && phantom.fs),
7235 dependencyProperties = ['extend', 'mixins', 'requires'],
7238 Loader = Ext.Loader = {
7242 documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
7245 * Flag indicating whether there are still files being loaded
7251 * Maintain the queue for all dependencies. Each item in the array is an object of the format:
7253 * requires: [...], // The required classes for this queue item
7254 * callback: function() { ... } // The function to execute when all classes specified in requires exist
7261 * Maintain the list of files that have already been handled so that they never get double-loaded
7267 * Maintain the list of listeners to execute when all required scripts are fully loaded
7273 * Contains optional dependencies to be loaded last
7276 optionalRequires: [],
7279 * Map of fully qualified class names to an array of dependent classes.
7295 hasFileLoadError: false,
7300 classNameToFilePathMap: {},
7303 * @property {[String]} history
7304 * An array of class names to keep track of the dependency loading order.
7305 * This is not guaranteed to be the same everytime due to the asynchronous nature of the Loader.
7315 * @cfg {Boolean} enabled
7316 * Whether or not to enable the dynamic dependency loading feature Defaults to false
7321 * @cfg {Boolean} disableCaching
7322 * Appends current timestamp to script files to prevent caching Defaults to true
7324 disableCaching: true,
7327 * @cfg {String} disableCachingParam
7328 * The get parameter name for the cache buster's timestamp. Defaults to '_dc'
7330 disableCachingParam: '_dc',
7333 * @cfg {Object} paths
7334 * The mapping from namespaces to file paths
7337 * 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
7338 * // loaded from ./layout/Container.js
7340 * 'My': './src/my_own_folder' // My.layout.Container will be loaded from
7341 * // ./src/my_own_folder/layout/Container.js
7344 * Note that all relative paths are relative to the current HTML document.
7345 * If not being specified, for example, `Other.awesome.Class`
7346 * will simply be loaded from `./Other/awesome/Class.js`
7354 * Set the configuration for the loader. This should be called right after ext-core.js
7355 * (or ext-core-debug.js) is included in the page, e.g.:
7357 * <script type="text/javascript" src="ext-core-debug.js"></script>
7358 * <script type="text/javascript">
7359 * Ext.Loader.setConfig({
7362 * 'My': 'my_own_path'
7366 * <script type="text/javascript">
7369 * Ext.onReady(function() {
7370 * // application code here
7374 * Refer to config options of {@link Ext.Loader} for the list of possible properties.
7376 * @param {String/Object} name Name of the value to override, or a config object to override multiple values.
7377 * @param {Object} value (optional) The new value to set, needed if first parameter is String.
7378 * @return {Ext.Loader} this
7380 setConfig: function(name, value) {
7381 if (Ext.isObject(name) && arguments.length === 1) {
7382 Ext.Object.merge(this.config, name);
7385 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
7392 * Get the config value corresponding to the specified name.
7393 * If no name is given, will return the config object.
7394 * @param {String} name The config property name
7395 * @return {Object/Mixed}
7397 getConfig: function(name) {
7399 return this.config[name];
7406 * Sets the path of a namespace. For Example:
7408 * Ext.Loader.setPath('Ext', '.');
7410 * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
7411 * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
7412 * @return {Ext.Loader} this
7415 setPath: flexSetter(function(name, path) {
7419 path = require('fs').realpathSync(path);
7423 this.config.paths[name] = path;
7429 * Translates a className to a file path by adding the the proper prefix and converting the .'s to /'s.
7432 * Ext.Loader.setPath('My', '/path/to/My');
7434 * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
7436 * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
7438 * Ext.Loader.setPath({
7439 * 'My': '/path/to/lib',
7440 * 'My.awesome': '/other/path/for/awesome/stuff',
7441 * 'My.awesome.more': '/more/awesome/path'
7444 * alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
7446 * alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
7448 * alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
7450 * alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
7452 * @param {String} className
7453 * @return {String} path
7455 getPath: function(className) {
7457 paths = this.config.paths,
7458 prefix = this.getPrefix(className);
7460 if (prefix.length > 0) {
7461 if (prefix === className) {
7462 return paths[prefix];
7465 path = paths[prefix];
7466 className = className.substring(prefix.length + 1);
7469 if (path.length > 0) {
7473 return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7478 * @param {String} className
7480 getPrefix: function(className) {
7481 var paths = this.config.paths,
7482 prefix, deepestPrefix = '';
7484 if (paths.hasOwnProperty(className)) {
7488 for (prefix in paths) {
7489 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7490 if (prefix.length > deepestPrefix.length) {
7491 deepestPrefix = prefix;
7496 return deepestPrefix;
7500 * Refresh all items in the queue. If all dependencies for an item exist during looping,
7501 * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7505 refreshQueue: function() {
7506 var ln = this.queue.length,
7507 i, item, j, requires;
7510 this.triggerReady();
7514 for (i = 0; i < ln; i++) {
7515 item = this.queue[i];
7518 requires = item.requires;
7520 // Don't bother checking when the number of files loaded
7521 // is still less than the array length
7522 if (requires.length > this.numLoadedFiles) {
7529 if (Manager.isCreated(requires[j])) {
7530 // Take out from the queue
7531 Ext.Array.erase(requires, j, 1);
7536 } while (j < requires.length);
7538 if (item.requires.length === 0) {
7539 Ext.Array.erase(this.queue, i, 1);
7540 item.callback.call(item.scope);
7541 this.refreshQueue();
7551 * Inject a script element to document's head, call onLoad and onError accordingly
7554 injectScriptElement: function(url, onLoad, onError, scope) {
7555 var script = document.createElement('script'),
7557 onLoadFn = function() {
7558 me.cleanupScriptElement(script);
7561 onErrorFn = function() {
7562 me.cleanupScriptElement(script);
7563 onError.call(scope);
7566 script.type = 'text/javascript';
7568 script.onload = onLoadFn;
7569 script.onerror = onErrorFn;
7570 script.onreadystatechange = function() {
7571 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7576 this.documentHead.appendChild(script);
7584 cleanupScriptElement: function(script) {
7585 script.onload = null;
7586 script.onreadystatechange = null;
7587 script.onerror = null;
7593 * Load a script file, supports both asynchronous and synchronous approaches
7595 * @param {String} url
7596 * @param {Function} onLoad
7597 * @param {Scope} scope
7598 * @param {Boolean} synchronous
7601 loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7603 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7604 fileName = url.split('/').pop(),
7605 isCrossOriginRestricted = false,
7606 xhr, status, onScriptError;
7608 scope = scope || this;
7610 this.isLoading = true;
7613 onScriptError = function() {
7614 onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7617 if (!Ext.isReady && Ext.onDocumentReady) {
7618 Ext.onDocumentReady(function() {
7619 me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7623 this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7627 if (typeof XMLHttpRequest !== 'undefined') {
7628 xhr = new XMLHttpRequest();
7630 xhr = new ActiveXObject('Microsoft.XMLHTTP');
7634 xhr.open('GET', noCacheUrl, false);
7637 isCrossOriginRestricted = true;
7640 status = (xhr.status === 1223) ? 204 : xhr.status;
7642 if (!isCrossOriginRestricted) {
7643 isCrossOriginRestricted = (status === 0);
7646 if (isCrossOriginRestricted
7651 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7652 "being loaded from a different domain or from the local file system whereby cross origin " +
7653 "requests are not allowed due to security reasons. Use asynchronous loading with " +
7654 "Ext.require instead.", synchronous);
7656 else if (status >= 200 && status < 300
7661 // Firebug friendly, file names are still shown even though they're eval'ed code
7662 new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7667 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7668 "verify that the file exists. " +
7669 "XHR status code: " + status, synchronous);
7672 // Prevent potential IE memory leak
7678 * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
7679 * Can be chained with more `require` and `exclude` methods, e.g.:
7681 * Ext.exclude('Ext.data.*').require('*');
7683 * Ext.exclude('widget.button*').require('widget.*');
7685 * {@link Ext#exclude Ext.exclude} is alias for {@link Ext.Loader#exclude Ext.Loader.exclude} for convenience.
7687 * @param {String/[String]} excludes
7688 * @return {Object} object contains `require` method for chaining
7690 exclude: function(excludes) {
7694 require: function(expressions, fn, scope) {
7695 return me.require(expressions, fn, scope, excludes);
7698 syncRequire: function(expressions, fn, scope) {
7699 return me.syncRequire(expressions, fn, scope, excludes);
7705 * Synchronously loads all classes by the given names and all their direct dependencies;
7706 * optionally executes the given callback function when finishes, within the optional scope.
7708 * {@link Ext#syncRequire Ext.syncRequire} is alias for {@link Ext.Loader#syncRequire Ext.Loader.syncRequire} for convenience.
7710 * @param {String/[String]} expressions Can either be a string or an array of string
7711 * @param {Function} fn (Optional) The callback function
7712 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7713 * @param {String/[String]} excludes (Optional) Classes to be excluded, useful when being used with expressions
7715 syncRequire: function() {
7716 this.syncModeEnabled = true;
7717 this.require.apply(this, arguments);
7718 this.refreshQueue();
7719 this.syncModeEnabled = false;
7723 * Loads all classes by the given names and all their direct dependencies;
7724 * optionally executes the given callback function when finishes, within the optional scope.
7726 * {@link Ext#require Ext.require} is alias for {@link Ext.Loader#require Ext.Loader.require} for convenience.
7728 * @param {String/[String]} expressions Can either be a string or an array of string
7729 * @param {Function} fn (Optional) The callback function
7730 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7731 * @param {String/[String]} excludes (Optional) Classes to be excluded, useful when being used with expressions
7733 require: function(expressions, fn, scope, excludes) {
7734 var filePath, expression, exclude, className, excluded = {},
7735 excludedClassNames = [],
7736 possibleClassNames = [],
7737 possibleClassName, classNames = [],
7740 expressions = Ext.Array.from(expressions);
7741 excludes = Ext.Array.from(excludes);
7743 fn = fn || Ext.emptyFn;
7745 scope = scope || Ext.global;
7747 for (i = 0, ln = excludes.length; i < ln; i++) {
7748 exclude = excludes[i];
7750 if (typeof exclude === 'string' && exclude.length > 0) {
7751 excludedClassNames = Manager.getNamesByExpression(exclude);
7753 for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
7754 excluded[excludedClassNames[j]] = true;
7759 for (i = 0, ln = expressions.length; i < ln; i++) {
7760 expression = expressions[i];
7762 if (typeof expression === 'string' && expression.length > 0) {
7763 possibleClassNames = Manager.getNamesByExpression(expression);
7765 for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
7766 possibleClassName = possibleClassNames[j];
7768 if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
7769 Ext.Array.include(classNames, possibleClassName);
7775 // If the dynamic dependency feature is not being used, throw an error
7776 // if the dependencies are not defined
7777 if (!this.config.enabled) {
7778 if (classNames.length > 0) {
7780 sourceClass: "Ext.Loader",
7781 sourceMethod: "require",
7782 msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
7783 "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
7788 if (classNames.length === 0) {
7794 requires: classNames,
7799 classNames = classNames.slice();
7801 for (i = 0, ln = classNames.length; i < ln; i++) {
7802 className = classNames[i];
7804 if (!this.isFileLoaded.hasOwnProperty(className)) {
7805 this.isFileLoaded[className] = false;
7807 filePath = this.getPath(className);
7809 this.classNameToFilePathMap[className] = filePath;
7811 this.numPendingFiles++;
7818 // Temporary support for hammerjs
7820 var f = fs.open(filePath),
7825 line = f.readLine();
7826 if (line.length === 0) {
7836 this.onFileLoaded(className, filePath);
7839 return Manager.get(className);
7845 this.loadScriptFile(
7847 Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
7848 Ext.Function.pass(this.onFileLoadError, [className, filePath]),
7850 this.syncModeEnabled
7860 * @param {String} className
7861 * @param {String} filePath
7863 onFileLoaded: function(className, filePath) {
7864 this.numLoadedFiles++;
7866 this.isFileLoaded[className] = true;
7868 this.numPendingFiles--;
7870 if (this.numPendingFiles === 0) {
7871 this.refreshQueue();
7875 if (this.numPendingFiles <= 1) {
7876 window.status = "Finished loading all dependencies, onReady fired!";
7879 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
7884 if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
7885 var queue = this.queue,
7887 i, ln, j, subLn, missingClasses = [], missingPaths = [];
7889 for (i = 0, ln = queue.length; i < ln; i++) {
7890 requires = queue[i].requires;
7892 for (j = 0, subLn = requires.length; j < ln; j++) {
7893 if (this.isFileLoaded[requires[j]]) {
7894 missingClasses.push(requires[j]);
7899 if (missingClasses.length < 1) {
7903 missingClasses = Ext.Array.filter(missingClasses, function(item) {
7904 return !this.requiresMap.hasOwnProperty(item);
7907 for (i = 0,ln = missingClasses.length; i < ln; i++) {
7908 missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
7912 sourceClass: "Ext.Loader",
7913 sourceMethod: "onFileLoaded",
7914 msg: "The following classes are not declared even if their files have been " +
7915 "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
7916 "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
7925 onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
7926 this.numPendingFiles--;
7927 this.hasFileLoadError = true;
7931 sourceClass: "Ext.Loader",
7932 classToLoad: className,
7934 loadingType: isSynchronous ? 'synchronous' : 'async',
7943 addOptionalRequires: function(requires) {
7944 var optionalRequires = this.optionalRequires,
7947 requires = Ext.Array.from(requires);
7949 for (i = 0, ln = requires.length; i < ln; i++) {
7950 require = requires[i];
7952 Ext.Array.include(optionalRequires, require);
7961 triggerReady: function(force) {
7962 var readyListeners = this.readyListeners,
7963 optionalRequires, listener;
7965 if (this.isLoading || force) {
7966 this.isLoading = false;
7968 if (this.optionalRequires.length) {
7969 // Clone then empty the array to eliminate potential recursive loop issue
7970 optionalRequires = Ext.Array.clone(this.optionalRequires);
7972 // Empty the original array
7973 this.optionalRequires.length = 0;
7975 this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
7979 while (readyListeners.length) {
7980 listener = readyListeners.shift();
7981 listener.fn.call(listener.scope);
7983 if (this.isLoading) {
7993 * Adds new listener to be executed when all required scripts are fully loaded.
7995 * @param {Function} fn The function callback to be executed
7996 * @param {Object} scope The execution scope (`this`) of the callback function
7997 * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
7999 onReady: function(fn, scope, withDomReady, options) {
8002 if (withDomReady !== false && Ext.onDocumentReady) {
8006 Ext.onDocumentReady(oldFn, scope, options);
8010 if (!this.isLoading) {
8014 this.readyListeners.push({
8023 * @param {String} className
8025 historyPush: function(className) {
8026 if (className && this.isFileLoaded.hasOwnProperty(className)) {
8027 Ext.Array.include(this.history, className);
8037 * @alias Ext.Loader#require
8039 Ext.require = alias(Loader, 'require');
8043 * @method syncRequire
8044 * @alias Ext.Loader#syncRequire
8046 Ext.syncRequire = alias(Loader, 'syncRequire');
8051 * @alias Ext.Loader#exclude
8053 Ext.exclude = alias(Loader, 'exclude');
8058 * @alias Ext.Loader#onReady
8060 Ext.onReady = function(fn, scope, options) {
8061 Loader.onReady(fn, scope, true, options);
8065 * @cfg {[String]} requires
8067 * List of classes that have to be loaded before instanciating this class.
8070 * Ext.define('Mother', {
8071 * requires: ['Child'],
8072 * giveBirth: function() {
8073 * // we can be sure that child class is available.
8074 * return new Child();
8078 Class.registerPreprocessor('loader', function(cls, data, continueFn) {
8081 className = Manager.getName(cls),
8082 i, j, ln, subLn, value, propertyName, propertyValue;
8085 Basically loop through the dependencyProperties, look for string class names and push
8086 them into a stack, regardless of whether the property's value is a string, array or object. For example:
8088 extend: 'Ext.MyClass',
8089 requires: ['Ext.some.OtherClass'],
8091 observable: 'Ext.util.Observable';
8094 which will later be transformed into:
8096 extend: Ext.MyClass,
8097 requires: [Ext.some.OtherClass],
8099 observable: Ext.util.Observable;
8104 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8105 propertyName = dependencyProperties[i];
8107 if (data.hasOwnProperty(propertyName)) {
8108 propertyValue = data[propertyName];
8110 if (typeof propertyValue === 'string') {
8111 dependencies.push(propertyValue);
8113 else if (propertyValue instanceof Array) {
8114 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8115 value = propertyValue[j];
8117 if (typeof value === 'string') {
8118 dependencies.push(value);
8123 for (j in propertyValue) {
8124 if (propertyValue.hasOwnProperty(j)) {
8125 value = propertyValue[j];
8127 if (typeof value === 'string') {
8128 dependencies.push(value);
8136 if (dependencies.length === 0) {
8137 // Loader.historyPush(className);
8142 var deadlockPath = [],
8143 requiresMap = Loader.requiresMap,
8147 Automatically detect deadlocks before-hand,
8148 will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
8150 - A extends B, then B extends A
8151 - A requires B, B requires C, then C requires A
8153 The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
8154 no matter how deep the path is.
8158 requiresMap[className] = dependencies;
8160 detectDeadlock = function(cls) {
8161 deadlockPath.push(cls);
8163 if (requiresMap[cls]) {
8164 if (Ext.Array.contains(requiresMap[cls], className)) {
8166 sourceClass: "Ext.Loader",
8167 msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
8168 deadlockPath[1] + "' " + "mutually require each other. Path: " +
8169 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
8173 for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
8174 detectDeadlock(requiresMap[cls][i]);
8179 detectDeadlock(className);
8184 Loader.require(dependencies, function() {
8185 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
8186 propertyName = dependencyProperties[i];
8188 if (data.hasOwnProperty(propertyName)) {
8189 propertyValue = data[propertyName];
8191 if (typeof propertyValue === 'string') {
8192 data[propertyName] = Manager.get(propertyValue);
8194 else if (propertyValue instanceof Array) {
8195 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
8196 value = propertyValue[j];
8198 if (typeof value === 'string') {
8199 data[propertyName][j] = Manager.get(value);
8204 for (var k in propertyValue) {
8205 if (propertyValue.hasOwnProperty(k)) {
8206 value = propertyValue[k];
8208 if (typeof value === 'string') {
8209 data[propertyName][k] = Manager.get(value);
8217 continueFn.call(me, cls, data);
8223 Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
8226 * @cfg {[String]} uses
8228 * List of classes to load together with this class. These aren't neccessarily loaded before
8229 * this class is instanciated. For example:
8231 * Ext.define('Mother', {
8233 * giveBirth: function() {
8234 * // This code might, or might not work:
8235 * // return new Child();
8237 * // Instead use Ext.create() to load the class at the spot if not loaded already:
8238 * return Ext.create('Child');
8242 Manager.registerPostprocessor('uses', function(name, cls, data) {
8243 var uses = Ext.Array.from(data.uses),
8247 for (i = 0, ln = uses.length; i < ln; i++) {
8250 if (typeof item === 'string') {
8255 Loader.addOptionalRequires(items);
8258 Manager.setDefaultPostprocessorPosition('uses', 'last');
8260 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
8267 A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
8268 errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
8269 uses the Ext 4 class system, the Error class can automatically add the source class and method from which
8270 the error was raised. It also includes logic to automatically log the eroor to the console, if available,
8271 with additional metadata about the error. In all cases, the error will always be thrown at the end so that
8272 execution will halt.
8274 Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
8275 handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
8276 although in a real application it's usually a better idea to override the handling function and perform
8277 logging or some other method of reporting the errors in a way that is meaningful to the application.
8279 At its simplest you can simply raise an error as a simple string from within any code:
8283 Ext.Error.raise('Something bad happened!');
8285 If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
8286 displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
8287 additional metadata about the error being raised. The {@link #raise} method can also take a config object.
8288 In this form the `msg` attribute becomes the error description, and any other data added to the config gets
8289 added to the error object and, if the console is available, logged to the console for inspection.
8293 Ext.define('Ext.Foo', {
8294 doSomething: function(option){
8295 if (someCondition === false) {
8297 msg: 'You cannot do that!',
8298 option: option, // whatever was passed into the method
8299 'error code': 100 // other arbitrary info
8305 If a console is available (that supports the `console.dir` function) you'll see console output like:
8307 An error was raised with the following data:
8308 option: Object { foo: "bar"}
8311 msg: "You cannot do that!"
8312 sourceClass: "Ext.Foo"
8313 sourceMethod: "doSomething"
8315 uncaught exception: You cannot do that!
8317 As you can see, the error will report exactly where it was raised and will include as much information as the
8318 raising code can usefully provide.
8320 If you want to handle all application errors globally you can simply override the static {@link #handle} method
8321 and provide whatever handling logic you need. If the method returns true then the error is considered handled
8322 and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
8326 Ext.Error.handle = function(err) {
8327 if (err.someProperty == 'NotReallyAnError') {
8328 // maybe log something to the application here if applicable
8331 // any non-true return value (including none) will cause the error to be thrown
8334 * Create a new Error object
8335 * @param {Object} config The config object
8337 * @author Brian Moeskau <brian@sencha.com>
8338 * @docauthor Brian Moeskau <brian@sencha.com>
8340 Ext.Error = Ext.extend(Error, {
8344 Static flag that can be used to globally disable error reporting to the browser if set to true
8345 (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
8346 and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
8347 be preferable to supply a custom error {@link #handle handling} function instead.
8351 Ext.Error.ignore = true;
8360 Static flag that can be used to globally control error notification to the user. Unlike
8361 Ex.Error.ignore, this does not effect exceptions. They are still thrown. This value can be
8362 set to false to disable the alert notification (default is true for IE6 and IE7).
8364 Only the first error will generate an alert. Internally this flag is set to false when the
8365 first error occurs prior to displaying the alert.
8367 This flag is not used in a release build.
8371 Ext.Error.notify = false;
8376 //notify: Ext.isIE6 || Ext.isIE7,
8379 Raise an error that can include additional data and supports automatic console logging if available.
8380 You can pass a string error message or an object with the `msg` attribute which will be used as the
8381 error message. The object can contain any other name-value attributes (or objects) to be logged
8382 along with the error.
8384 Note that after displaying the error message a JavaScript error will ultimately be thrown so that
8385 execution will halt.
8389 Ext.Error.raise('A simple string error message');
8393 Ext.define('Ext.Foo', {
8394 doSomething: function(option){
8395 if (someCondition === false) {
8397 msg: 'You cannot do that!',
8398 option: option, // whatever was passed into the method
8399 'error code': 100 // other arbitrary info
8404 * @param {String/Object} err The error message string, or an object containing the
8405 * attribute "msg" that will be used as the error message. Any other data included in
8406 * the object will also be logged to the browser console, if available.
8410 raise: function(err){
8412 if (Ext.isString(err)) {
8416 var method = this.raise.caller;
8420 err.sourceMethod = method.$name;
8422 if (method.$owner) {
8423 err.sourceClass = method.$owner.$className;
8427 if (Ext.Error.handle(err) !== true) {
8428 var msg = Ext.Error.prototype.toString.call(err);
8437 throw new Ext.Error(err);
8442 Globally handle any Ext errors that may be raised, optionally providing custom logic to
8443 handle different errors individually. Return true from the function to bypass throwing the
8444 error to the browser, otherwise the error will be thrown and execution will halt.
8448 Ext.Error.handle = function(err) {
8449 if (err.someProperty == 'NotReallyAnError') {
8450 // maybe log something to the application here if applicable
8453 // any non-true return value (including none) will cause the error to be thrown
8456 * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes
8457 * that were originally raised with it, plus properties about the method and class from which
8458 * the error originated (if raised from a class that uses the Ext 4 class system).
8463 return Ext.Error.ignore;
8467 // This is the standard property that is the name of the constructor.
8471 * @param {String/Object} config The error message string, or an object containing the
8472 * attribute "msg" that will be used as the error message. Any other data included in
8473 * the object will be applied to the error instance and logged to the browser console, if available.
8475 constructor: function(config){
8476 if (Ext.isString(config)) {
8477 config = { msg: config };
8482 Ext.apply(me, config);
8484 me.message = me.message || me.msg; // 'message' is standard ('msg' is non-standard)
8485 // note: the above does not work in old WebKit (me.message is readonly) (Safari 4)
8489 Provides a custom string representation of the error object. This is an override of the base JavaScript
8490 `Object.toString` method, which is useful so that when logged to the browser console, an error object will
8491 be displayed with a useful message instead of `[object Object]`, the default `toString` result.
8493 The default implementation will include the error message along with the raising class and method, if available,
8494 but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
8495 a particular error instance, if you want to provide a custom description that will show up in the console.
8497 * @return {String} The error message. If raised from within the Ext 4 class system, the error message
8498 * will also include the raising class and method names, if available.
8500 toString: function(){
8502 className = me.className ? me.className : '',
8503 methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
8504 msg = me.msg || '(No description provided)';
8506 return className + methodName + msg;
8511 * This mechanism is used to notify the user of the first error encountered on the page. This
8512 * was previously internal to Ext.Error.raise and is a desirable feature since errors often
8513 * slip silently under the radar. It cannot live in Ext.Error.raise since there are times
8514 * where exceptions are handled in a try/catch.
8518 var prevOnError, timer, errors = 0,
8519 extraordinarilyBad = /(out of stack)|(too much recursion)|(stack overflow)|(out of memory)/i,
8522 if (typeof window === 'undefined') {
8523 return; // build system or some such environment...
8526 // This method is called to notify the user of the current error status.
8527 function notify () {
8528 var counters = Ext.log.counters,
8529 supports = Ext.supports,
8530 hasOnError = supports && supports.WindowOnError; // TODO - timing
8532 // Put log counters to the status bar (for most browsers):
8533 if (counters && (counters.error + counters.warn + counters.info + counters.log)) {
8534 var msg = [ 'Logged Errors:',counters.error, 'Warnings:',counters.warn,
8535 'Info:',counters.info, 'Log:',counters.log].join(' ');
8537 msg = '*** Errors: ' + errors + ' - ' + msg;
8538 } else if (counters.error) {
8544 // Display an alert on the first error:
8545 if (!Ext.isDefined(Ext.Error.notify)) {
8546 Ext.Error.notify = Ext.isIE6 || Ext.isIE7; // TODO - timing
8548 if (Ext.Error.notify && (hasOnError ? errors : (counters && counters.error))) {
8549 Ext.Error.notify = false;
8552 win.clearInterval(timer); // ticks can queue up so stop...
8556 alert('Unhandled error on page: See console or log');
8561 // Sets up polling loop. This is the only way to know about errors in some browsers
8562 // (Opera/Safari) and is the only way to update the status bar for warnings and other
8565 timer = win.setInterval(notify, 1000);
8568 // window.onerror is ideal (esp in IE) because you get full context. This is harmless
8569 // otherwise (never called) which is good because you cannot feature detect it.
8570 prevOnError = win.onerror || Ext.emptyFn;
8571 win.onerror = function (message) {
8574 if (!extraordinarilyBad.test(message)) {
8575 // too much recursion + our alert right now = crash IE
8576 // our polling loop will pick it up even if we don't alert now
8580 return prevOnError.apply(this, arguments);