2 Ext JS - JavaScript Library
3 Copyright (c) 2006-2011, Sencha Inc.
13 objectPrototype = Object.prototype,
14 toString = Object.prototype.toString,
16 enumerablesTest = { toString: 1 },
19 if (typeof Ext === 'undefined') {
25 for (i in enumerablesTest) {
30 enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
31 'toLocaleString', 'toString', 'constructor'];
35 * An array containing extra enumerables for old browsers
38 Ext.enumerables = enumerables;
41 * Copies all the properties of config to the specified object.
42 * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
43 * {@link Ext.Object#merge} instead.
44 * @param {Object} object The receiver of the properties
45 * @param {Object} config The source of the properties
46 * @param {Object} defaults A different object that will also be applied for default values
47 * @return {Object} returns obj
49 Ext.apply = function(object, config, defaults) {
51 Ext.apply(object, defaults);
54 if (object && config && typeof config === 'object') {
58 object[i] = config[i];
62 for (j = enumerables.length; j--;) {
64 if (config.hasOwnProperty(k)) {
65 object[k] = config[k];
74 Ext.buildSettings = Ext.apply({
77 }, Ext.buildSettings || {});
81 * A reusable empty function
83 emptyFn: function() {},
85 baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
88 * Copies all the properties of config to object if they don't already exist.
90 * @param {Object} object The receiver of the properties
91 * @param {Object} config The source of the properties
92 * @return {Object} returns obj
94 applyIf: function(object, config) {
98 for (property in config) {
99 if (object[property] === undefined) {
100 object[property] = config[property];
109 * Iterates either an array or an object. This method delegates to
110 * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
112 * @param {Object/Array} object The object or array to be iterated.
113 * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
114 * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
115 * type that is being iterated.
116 * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
117 * Defaults to the object being iterated itself.
120 iterate: function(object, fn, scope) {
121 if (Ext.isEmpty(object)) {
125 if (scope === undefined) {
129 if (Ext.isIterable(object)) {
130 Ext.Array.each.call(Ext.Array, object, fn, scope);
133 Ext.Object.each.call(Ext.Object, object, fn, scope);
141 * This method deprecated. Use {@link Ext#define Ext.define} instead.
143 * @param {Function} superclass
144 * @param {Object} overrides
145 * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
146 * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
150 var objectConstructor = objectPrototype.constructor,
151 inlineOverrides = function(o) {
153 if (!o.hasOwnProperty(m)) {
160 return function(subclass, superclass, overrides) {
161 // First we check if the user passed in just the superClass with overrides
162 if (Ext.isObject(superclass)) {
163 overrides = superclass;
164 superclass = subclass;
165 subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
166 superclass.apply(this, arguments);
173 sourceMethod: 'extend',
174 msg: 'Attempting to extend from a class which has not been loaded on the page.'
178 // We create a new temporary class
179 var F = function() {},
180 subclassProto, superclassProto = superclass.prototype;
182 F.prototype = superclassProto;
183 subclassProto = subclass.prototype = new F();
184 subclassProto.constructor = subclass;
185 subclass.superclass = superclassProto;
187 if (superclassProto.constructor === objectConstructor) {
188 superclassProto.constructor = superclass;
191 subclass.override = function(overrides) {
192 Ext.override(subclass, overrides);
195 subclassProto.override = inlineOverrides;
196 subclassProto.proto = subclassProto;
198 subclass.override(overrides);
199 subclass.extend = function(o) {
200 return Ext.extend(subclass, o);
208 * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
210 Ext.define('My.cool.Class', {
216 Ext.override(My.cool.Class, {
218 alert('About to say...');
220 this.callOverridden();
224 var cool = new My.cool.Class();
225 cool.sayHi(); // alerts 'About to say...'
228 * Please note that `this.callOverridden()` only works if the class was previously
229 * created with {@link Ext#define)
231 * @param {Object} cls The class to override
232 * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
233 * containing one or more methods.
237 override: function(cls, overrides) {
238 if (cls.prototype.$className) {
239 return cls.override(overrides);
242 Ext.apply(cls.prototype, overrides);
247 // A full set of static methods to do type checking
251 * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
252 * value (second argument) otherwise.
254 * @param {Mixed} value The value to test
255 * @param {Mixed} defaultValue The value to return if the original value is empty
256 * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
257 * @return {Mixed} value, if non-empty, else defaultValue
259 valueFrom: function(value, defaultValue, allowBlank){
260 return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
264 * Returns the type of the given variable in string format. List of possible values are:
266 * - `undefined`: If the given value is `undefined`
267 * - `null`: If the given value is `null`
268 * - `string`: If the given value is a string
269 * - `number`: If the given value is a number
270 * - `boolean`: If the given value is a boolean value
271 * - `date`: If the given value is a `Date` object
272 * - `function`: If the given value is a function reference
273 * - `object`: If the given value is an object
274 * - `array`: If the given value is an array
275 * - `regexp`: If the given value is a regular expression
276 * - `element`: If the given value is a DOM Element
277 * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
278 * - `whitespace`: If the given value is a DOM text node and contains only whitespace
280 * @param {Mixed} value
284 typeOf: function(value) {
285 if (value === null) {
289 var type = typeof value;
291 if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
295 var typeToString = toString.call(value);
297 switch(typeToString) {
298 case '[object Array]':
300 case '[object Date]':
302 case '[object Boolean]':
304 case '[object Number]':
306 case '[object RegExp]':
310 if (type === 'function') {
314 if (type === 'object') {
315 if (value.nodeType !== undefined) {
316 if (value.nodeType === 3) {
317 return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
329 sourceMethod: 'typeOf',
330 msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
335 * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
339 * - a zero-length array
340 * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
342 * @param {Mixed} value The value to test
343 * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
347 isEmpty: function(value, allowEmptyString) {
348 return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
352 * Returns true if the passed value is a JavaScript Array, false otherwise.
354 * @param {Mixed} target The target to test
357 isArray: ('isArray' in Array) ? Array.isArray : function(value) {
358 return toString.call(value) === '[object Array]';
362 * Returns true if the passed value is a JavaScript Date object, false otherwise.
363 * @param {Object} object The object to test
366 isDate: function(value) {
367 return toString.call(value) === '[object Date]';
371 * Returns true if the passed value is a JavaScript Object, false otherwise.
372 * @param {Mixed} value The value to test
375 isObject: (toString.call(null) === '[object Object]') ?
377 return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.nodeType === undefined;
380 return toString.call(value) === '[object Object]';
384 * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
385 * @param {Mixed} value The value to test
388 isPrimitive: function(value) {
389 var type = typeof value;
391 return type === 'string' || type === 'number' || type === 'boolean';
395 * Returns true if the passed value is a JavaScript Function, false otherwise.
396 * @param {Mixed} value The value to test
400 // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
401 // Object.prorotype.toString (slower)
402 (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
403 return toString.call(value) === '[object Function]';
404 } : function(value) {
405 return typeof value === 'function';
409 * Returns true if the passed value is a number. Returns false for non-finite numbers.
410 * @param {Mixed} value The value to test
413 isNumber: function(value) {
414 return typeof value === 'number' && isFinite(value);
418 * Validates that a value is numeric.
419 * @param {Mixed} value Examples: 1, '1', '2.34'
420 * @return {Boolean} True if numeric, false otherwise
422 isNumeric: function(value) {
423 return !isNaN(parseFloat(value)) && isFinite(value);
427 * Returns true if the passed value is a string.
428 * @param {Mixed} value The value to test
431 isString: function(value) {
432 return typeof value === 'string';
436 * Returns true if the passed value is a boolean.
438 * @param {Mixed} value The value to test
441 isBoolean: function(value) {
442 return typeof value === 'boolean';
446 * Returns true if the passed value is an HTMLElement
447 * @param {Mixed} value The value to test
450 isElement: function(value) {
451 return value ? value.nodeType !== undefined : false;
455 * Returns true if the passed value is a TextNode
456 * @param {Mixed} value The value to test
459 isTextNode: function(value) {
460 return value ? value.nodeName === "#text" : false;
464 * Returns true if the passed value is defined.
465 * @param {Mixed} value The value to test
468 isDefined: function(value) {
469 return typeof value !== 'undefined';
473 * Returns true if the passed value is iterable, false otherwise
474 * @param {Mixed} value The value to test
477 isIterable: function(value) {
478 return (value && typeof value !== 'string') ? value.length !== undefined : false;
485 * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
486 * @param {Mixed} item The variable to clone
487 * @return {Mixed} clone
489 clone: function(item) {
490 if (item === null || item === undefined) {
495 // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
497 if (item.nodeType && item.cloneNode) {
498 return item.cloneNode(true);
501 var type = toString.call(item);
504 if (type === '[object Date]') {
505 return new Date(item.getTime());
508 var i, j, k, clone, key;
511 if (type === '[object Array]') {
517 clone[i] = Ext.clone(item[i]);
521 else if (type === '[object Object]' && item.constructor === Object) {
525 clone[key] = Ext.clone(item[key]);
529 for (j = enumerables.length; j--;) {
536 return clone || item;
541 * Generate a unique reference of Ext in the global scope, useful for sandboxing
543 getUniqueGlobalNamespace: function() {
544 var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
546 if (uniqueGlobalNamespace === undefined) {
550 uniqueGlobalNamespace = 'ExtSandbox' + (++i);
551 } while (Ext.global[uniqueGlobalNamespace] !== undefined);
553 Ext.global[uniqueGlobalNamespace] = Ext;
554 this.uniqueGlobalNamespace = uniqueGlobalNamespace;
557 return uniqueGlobalNamespace;
563 functionFactory: function() {
564 var args = Array.prototype.slice.call(arguments);
566 if (args.length > 0) {
567 args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
568 args[args.length - 1];
571 return Function.prototype.constructor.apply(Function.prototype, args);
576 * Old alias to {@link Ext#typeOf}
577 * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
579 Ext.type = Ext.typeOf;
584 * @author Jacky Nguyen <jacky@sencha.com>
585 * @docauthor Jacky Nguyen <jacky@sencha.com>
588 * A utility class that wrap around a string version number and provide convenient
589 * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
591 var version = new Ext.Version('1.0.2beta');
592 console.log("Version is " + version); // Version is 1.0.2beta
594 console.log(version.getMajor()); // 1
595 console.log(version.getMinor()); // 0
596 console.log(version.getPatch()); // 2
597 console.log(version.getBuild()); // 0
598 console.log(version.getRelease()); // beta
600 console.log(version.isGreaterThan('1.0.1')); // True
601 console.log(version.isGreaterThan('1.0.2alpha')); // True
602 console.log(version.isGreaterThan('1.0.2RC')); // False
603 console.log(version.isGreaterThan('1.0.2')); // False
604 console.log(version.isLessThan('1.0.2')); // True
606 console.log(version.match(1.0)); // True
607 console.log(version.match('1.0.2')); // True
613 // Current core version
614 var version = '4.0.0', Version;
615 Ext.Version = Version = Ext.extend(Object, {
619 * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
620 * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
621 * @return {Ext.Version} this
624 constructor: function(version) {
625 var parts, releaseStartIndex;
627 if (version instanceof Version) {
631 this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
633 releaseStartIndex = this.version.search(/([^\d\.])/);
635 if (releaseStartIndex !== -1) {
636 this.release = this.version.substr(releaseStartIndex, version.length);
637 this.shortVersion = this.version.substr(0, releaseStartIndex);
640 this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
642 parts = this.version.split('.');
644 this.major = parseInt(parts.shift() || 0, 10);
645 this.minor = parseInt(parts.shift() || 0, 10);
646 this.patch = parseInt(parts.shift() || 0, 10);
647 this.build = parseInt(parts.shift() || 0, 10);
653 * Override the native toString method
655 * @return {String} version
657 toString: function() {
662 * Override the native valueOf method
664 * @return {String} version
666 valueOf: function() {
671 * Returns the major component value
672 * @return {Number} major
674 getMajor: function() {
675 return this.major || 0;
679 * Returns the minor component value
680 * @return {Number} minor
682 getMinor: function() {
683 return this.minor || 0;
687 * Returns the patch component value
688 * @return {Number} patch
690 getPatch: function() {
691 return this.patch || 0;
695 * Returns the build component value
696 * @return {Number} build
698 getBuild: function() {
699 return this.build || 0;
703 * Returns the release component value
704 * @return {Number} release
706 getRelease: function() {
707 return this.release || '';
711 * Returns whether this version if greater than the supplied argument
712 * @param {String/Number} target The version to compare with
713 * @return {Boolean} True if this version if greater than the target, false otherwise
715 isGreaterThan: function(target) {
716 return Version.compare(this.version, target) === 1;
720 * Returns whether this version if smaller than the supplied argument
721 * @param {String/Number} target The version to compare with
722 * @return {Boolean} True if this version if smaller than the target, false otherwise
724 isLessThan: function(target) {
725 return Version.compare(this.version, target) === -1;
729 * Returns whether this version equals to the supplied argument
730 * @param {String/Number} target The version to compare with
731 * @return {Boolean} True if this version equals to the target, false otherwise
733 equals: function(target) {
734 return Version.compare(this.version, target) === 0;
738 * Returns whether this version matches the supplied argument. Example:
740 * var version = new Ext.Version('1.0.2beta');
741 * console.log(version.match(1)); // True
742 * console.log(version.match(1.0)); // True
743 * console.log(version.match('1.0.2')); // True
744 * console.log(version.match('1.0.2RC')); // False
746 * @param {String/Number} target The version to compare with
747 * @return {Boolean} True if this version matches the target, false otherwise
749 match: function(target) {
750 target = String(target);
751 return this.version.substr(0, target.length) === target;
755 * Returns this format: [major, minor, patch, build, release]. Useful for comparison
758 toArray: function() {
759 return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
763 * Returns shortVersion version without dots and release
766 getShortVersion: function() {
767 return this.shortVersion;
786 * Converts a version component to a comparable value
789 * @param {Mixed} value The value to convert
792 getComponentValue: function(value) {
793 return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
797 * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
798 * they are handled in the following order:
799 * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
802 * @param {String} current The current version to compare to
803 * @param {String} target The target version to compare to
804 * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
806 compare: function(current, target) {
807 var currentValue, targetValue, i;
809 current = new Version(current).toArray();
810 target = new Version(target).toArray();
812 for (i = 0; i < Math.max(current.length, target.length); i++) {
813 currentValue = this.getComponentValue(current[i]);
814 targetValue = this.getComponentValue(target[i]);
816 if (currentValue < targetValue) {
818 } else if (currentValue > targetValue) {
836 lastRegisteredVersion: null,
839 * Set version number for the given package name.
841 * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
842 * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
845 setVersion: function(packageName, version) {
846 Ext.versions[packageName] = new Version(version);
847 Ext.lastRegisteredVersion = Ext.versions[packageName];
853 * Get the version number of the supplied package name; will return the last registered version
854 * (last Ext.setVersion call) if there's no package name given.
856 * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
857 * @return {Ext.Version} The version
859 getVersion: function(packageName) {
860 if (packageName === undefined) {
861 return Ext.lastRegisteredVersion;
864 return Ext.versions[packageName];
868 * Create a closure for deprecated code.
870 // This means Ext.oldMethod is only supported in 4.0.0beta and older.
871 // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
872 // the closure will not be invoked
873 Ext.deprecate('extjs', '4.0.0beta', function() {
874 Ext.oldMethod = Ext.newMethod;
879 * @param {String} packageName The package name
880 * @param {String} since The last version before it's deprecated
881 * @param {Function} closure The callback function to be executed with the specified version is less than the current version
882 * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
885 deprecate: function(packageName, since, closure, scope) {
886 if (Version.compare(Ext.getVersion(packageName), since) < 1) {
890 }); // End Versioning
892 Ext.setVersion('core', version);
899 * A collection of useful static methods to deal with strings
904 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,
906 formatRe: /\{(\d+)\}/g,
907 escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
910 * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
911 * @param {String} value The string to encode
912 * @return {String} The encoded text
914 htmlEncode: (function() {
920 }, keys = [], p, regex;
922 for (p in entities) {
926 regex = new RegExp('(' + keys.join('|') + ')', 'g');
928 return function(value) {
929 return (!value) ? value : String(value).replace(regex, function(match, capture) {
930 return entities[capture];
936 * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
937 * @param {String} value The string to decode
938 * @return {String} The decoded text
940 htmlDecode: (function() {
946 }, keys = [], p, regex;
948 for (p in entities) {
952 regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
954 return function(value) {
955 return (!value) ? value : String(value).replace(regex, function(match, capture) {
956 if (capture in entities) {
957 return entities[capture];
959 return String.fromCharCode(parseInt(capture.substr(2), 10));
966 * Appends content to the query string of a URL, handling logic for whether to place
967 * a question mark or ampersand.
968 * @param {String} url The URL to append to.
969 * @param {String} string The content to append to the URL.
970 * @return (String) The resulting URL
972 urlAppend : function(url, string) {
973 if (!Ext.isEmpty(string)) {
974 return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
981 * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
984 alert('-' + s + '-'); //alerts "- foo bar -"
985 alert('-' + Ext.String.trim(s) + '-'); //alerts "-foo bar-"
987 * @param {String} string The string to escape
988 * @return {String} The trimmed string
990 trim: function(string) {
991 return string.replace(Ext.String.trimRegex, "");
995 * Capitalize the given string
996 * @param {String} string
999 capitalize: function(string) {
1000 return string.charAt(0).toUpperCase() + string.substr(1);
1004 * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
1005 * @param {String} value The string to truncate
1006 * @param {Number} length The maximum length to allow before truncating
1007 * @param {Boolean} word True to try to find a common word break
1008 * @return {String} The converted text
1010 ellipsis: function(value, len, word) {
1011 if (value && value.length > len) {
1013 var vs = value.substr(0, len - 2),
1014 index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
1015 if (index !== -1 && index >= (len - 15)) {
1016 return vs.substr(0, index) + "...";
1019 return value.substr(0, len - 3) + "...";
1025 * Escapes the passed string for use in a regular expression
1026 * @param {String} string
1029 escapeRegex: function(string) {
1030 return string.replace(Ext.String.escapeRegexRe, "\\$1");
1034 * Escapes the passed string for ' and \
1035 * @param {String} string The string to escape
1036 * @return {String} The escaped string
1038 escape: function(string) {
1039 return string.replace(Ext.String.escapeRe, "\\$1");
1043 * Utility function that allows you to easily switch a string between two alternating values. The passed value
1044 * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
1045 * they are already different, the first value passed in is returned. Note that this method returns the new value
1046 * but does not change the current string.
1048 // alternate sort directions
1049 sort = Ext.String.toggle(sort, 'ASC', 'DESC');
1051 // instead of conditional logic:
1052 sort = (sort == 'ASC' ? 'DESC' : 'ASC');
1054 * @param {String} string The current string
1055 * @param {String} value The value to compare to the current string
1056 * @param {String} other The new value to use if the string already equals the first value passed in
1057 * @return {String} The new value
1059 toggle: function(string, value, other) {
1060 return string === value ? other : value;
1064 * Pads the left side of a string with a specified character. This is especially useful
1065 * for normalizing number and date strings. Example usage:
1068 var s = Ext.String.leftPad('123', 5, '0');
1069 // s now contains the string: '00123'
1071 * @param {String} string The original string
1072 * @param {Number} size The total length of the output string
1073 * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
1074 * @return {String} The padded string
1076 leftPad: function(string, size, character) {
1077 var result = String(string);
1078 character = character || " ";
1079 while (result.length < size) {
1080 result = character + result;
1086 * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
1087 * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
1089 var cls = 'my-class', text = 'Some text';
1090 var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
1091 // s now contains the string: '<div class="my-class">Some text</div>'
1093 * @param {String} string The tokenized string to be formatted
1094 * @param {String} value1 The value to replace token {0}
1095 * @param {String} value2 Etc...
1096 * @return {String} The formatted string
1098 format: function(format) {
1099 var args = Ext.Array.toArray(arguments, 1);
1100 return format.replace(Ext.String.formatRe, function(m, i) {
1109 * A collection of useful static methods to deal with numbers
1115 var isToFixedBroken = (0.9).toFixed() !== '1';
1119 * Checks whether or not the current number is within a desired range. If the number is already within the
1120 * range it is returned, otherwise the min or max value is returned depending on which side of the range is
1121 * exceeded. Note that this method returns the constrained value but does not change the current number.
1122 * @param {Number} number The number to check
1123 * @param {Number} min The minimum number in the range
1124 * @param {Number} max The maximum number in the range
1125 * @return {Number} The constrained value if outside the range, otherwise the current value
1127 constrain: function(number, min, max) {
1128 number = parseFloat(number);
1131 number = Math.max(number, min);
1134 number = Math.min(number, max);
1140 * Formats a number using fixed-point notation
1141 * @param {Number} value The number to format
1142 * @param {Number} precision The number of digits to show after the decimal point
1144 toFixed: function(value, precision) {
1145 if (isToFixedBroken) {
1146 precision = precision || 0;
1147 var pow = Math.pow(10, precision);
1148 return (Math.round(value * pow) / pow).toFixed(precision);
1151 return value.toFixed(precision);
1155 * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
1158 Ext.Number.from('1.23', 1); // returns 1.23
1159 Ext.Number.from('abc', 1); // returns 1
1161 * @param {Mixed} value
1162 * @param {Number} defaultValue The value to return if the original value is non-numeric
1163 * @return {Number} value, if numeric, defaultValue otherwise
1165 from: function(value, defaultValue) {
1166 if (isFinite(value)) {
1167 value = parseFloat(value);
1170 return !isNaN(value) ? value : defaultValue;
1177 * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
1179 * @deprecated 4.0.0 Replaced by Ext.Number.from
1183 Ext.num = function() {
1184 return Ext.Number.from.apply(this, arguments);
1187 * @author Jacky Nguyen <jacky@sencha.com>
1188 * @docauthor Jacky Nguyen <jacky@sencha.com>
1191 * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
1198 var arrayPrototype = Array.prototype,
1199 slice = arrayPrototype.slice,
1200 supportsForEach = 'forEach' in arrayPrototype,
1201 supportsMap = 'map' in arrayPrototype,
1202 supportsIndexOf = 'indexOf' in arrayPrototype,
1203 supportsEvery = 'every' in arrayPrototype,
1204 supportsSome = 'some' in arrayPrototype,
1205 supportsFilter = 'filter' in arrayPrototype,
1206 supportsSort = function() {
1207 var a = [1,2,3,4,5].sort(function(){ return 0; });
1208 return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
1210 supportsSliceOnNodeList = true,
1213 // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
1214 if (typeof document !== 'undefined') {
1215 slice.call(document.getElementsByTagName('body'));
1218 supportsSliceOnNodeList = false;
1221 ExtArray = Ext.Array = {
1223 * Iterates an array or an iterable value and invoke the given callback function for each item.
1225 var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
1227 Ext.Array.each(countries, function(name, index, countriesItSelf) {
1231 var sum = function() {
1234 Ext.Array.each(arguments, function(value) {
1241 sum(1, 2, 3); // returns 6
1243 * The iteration can be stopped by returning false in the function callback.
1245 Ext.Array.each(countries, function(name, index, countriesItSelf) {
1246 if (name === 'Singapore') {
1247 return false; // break here
1251 * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
1252 * argument is not iterable, the callback function is called once.
1253 * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
1254 * the current `index`. Arguments passed to this callback function are:
1256 - `item`: {Mixed} The item at the current `index` in the passed `array`
1257 - `index`: {Number} The current `index` within the `array`
1258 - `allItems`: {Array/NodeList/Mixed} The `array` passed as the first argument to `Ext.Array.each`
1260 * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
1261 * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
1263 * @return {Boolean} See description for the `fn` parameter.
1266 each: function(array, fn, scope, reverse) {
1267 array = ExtArray.from(array);
1272 if (reverse !== true) {
1273 for (i = 0; i < ln; i++) {
1274 if (fn.call(scope || array[i], array[i], i, array) === false) {
1280 for (i = ln - 1; i > -1; i--) {
1281 if (fn.call(scope || array[i], array[i], i, array) === false) {
1291 * Iterates an array and invoke the given callback function for each item. Note that this will simply
1292 * delegate to the native Array.prototype.forEach method if supported.
1293 * It doesn't support stopping the iteration by returning false in the callback function like
1294 * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
1295 * {@link Ext.Array#each}
1297 * @param {Array} array The array to iterate
1298 * @param {Function} fn The function callback, to be invoked these arguments:
1300 - `item`: {Mixed} The item at the current `index` in the passed `array`
1301 - `index`: {Number} The current `index` within the `array`
1302 - `allItems`: {Array} The `array` itself which was passed as the first argument
1304 * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
1307 forEach: function(array, fn, scope) {
1308 if (supportsForEach) {
1309 return array.forEach(fn, scope);
1315 for (; i < ln; i++) {
1316 fn.call(scope, array[i], i, array);
1321 * Get the index of the provided `item` in the given `array`, a supplement for the
1322 * missing arrayPrototype.indexOf in Internet Explorer.
1324 * @param {Array} array The array to check
1325 * @param {Mixed} item The item to look for
1326 * @param {Number} from (Optional) The index at which to begin the search
1327 * @return {Number} The index of item in the array (or -1 if it is not found)
1330 indexOf: function(array, item, from) {
1331 if (supportsIndexOf) {
1332 return array.indexOf(item, from);
1335 var i, length = array.length;
1337 for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
1338 if (array[i] === item) {
1347 * Checks whether or not the given `array` contains the specified `item`
1349 * @param {Array} array The array to check
1350 * @param {Mixed} item The item to look for
1351 * @return {Boolean} True if the array contains the item, false otherwise
1354 contains: function(array, item) {
1355 if (supportsIndexOf) {
1356 return array.indexOf(item) !== -1;
1361 for (i = 0, ln = array.length; i < ln; i++) {
1362 if (array[i] === item) {
1371 * Converts any iterable (numeric indices and a length property) into a true array.
1374 var args = Ext.Array.toArray(arguments),
1375 fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
1377 alert(args.join(' '));
1378 alert(fromSecondToLastArgs.join(' '));
1381 test('just', 'testing', 'here'); // alerts 'just testing here';
1382 // alerts 'testing here';
1384 Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
1385 Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
1386 Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
1388 * @param {Mixed} iterable the iterable object to be turned into a true Array.
1389 * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
1390 * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
1391 * index of the iterable value
1392 * @return {Array} array
1395 toArray: function(iterable, start, end){
1396 if (!iterable || !iterable.length) {
1400 if (typeof iterable === 'string') {
1401 iterable = iterable.split('');
1404 if (supportsSliceOnNodeList) {
1405 return slice.call(iterable, start || 0, end || iterable.length);
1412 end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
1414 for (i = start; i < end; i++) {
1415 array.push(iterable[i]);
1422 * Plucks the value of a property from each item in the Array. Example:
1424 Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
1426 * @param {Array|NodeList} array The Array of items to pluck the value from.
1427 * @param {String} propertyName The property name to pluck from each element.
1428 * @return {Array} The value from each item in the Array.
1430 pluck: function(array, propertyName) {
1434 for (i = 0, ln = array.length; i < ln; i++) {
1437 ret.push(item[propertyName]);
1444 * Creates a new array with the results of calling a provided function on every element in this array.
1445 * @param {Array} array
1446 * @param {Function} fn Callback function for each item
1447 * @param {Object} scope Callback function scope
1448 * @return {Array} results
1450 map: function(array, fn, scope) {
1452 return array.map(fn, scope);
1459 for (; i < len; i++) {
1460 results[i] = fn.call(scope, array[i], i, array);
1467 * Executes the specified function for each array element until the function returns a falsy value.
1468 * If such an item is found, the function will return false immediately.
1469 * Otherwise, it will return true.
1471 * @param {Array} array
1472 * @param {Function} fn Callback function for each item
1473 * @param {Object} scope Callback function scope
1474 * @return {Boolean} True if no false value is returned by the callback function.
1476 every: function(array, fn, scope) {
1478 Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
1480 if (supportsEvery) {
1481 return array.every(fn, scope);
1487 for (; i < ln; ++i) {
1488 if (!fn.call(scope, array[i], i, array)) {
1497 * Executes the specified function for each array element until the function returns a truthy value.
1498 * If such an item is found, the function will return true immediately. Otherwise, it will return false.
1500 * @param {Array} array
1501 * @param {Function} fn Callback function for each item
1502 * @param {Object} scope Callback function scope
1503 * @return {Boolean} True if the callback function returns a truthy value.
1505 some: function(array, fn, scope) {
1507 Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
1510 return array.some(fn, scope);
1516 for (; i < ln; ++i) {
1517 if (fn.call(scope, array[i], i, array)) {
1526 * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
1528 * @see Ext.Array.filter
1529 * @param {Array} array
1530 * @return {Array} results
1532 clean: function(array) {
1538 for (; i < ln; i++) {
1541 if (!Ext.isEmpty(item)) {
1550 * Returns a new array with unique items
1552 * @param {Array} array
1553 * @return {Array} results
1555 unique: function(array) {
1561 for (; i < ln; i++) {
1564 if (ExtArray.indexOf(clone, item) === -1) {
1573 * Creates a new array with all of the elements of this array for which
1574 * the provided filtering function returns true.
1575 * @param {Array} array
1576 * @param {Function} fn Callback function for each item
1577 * @param {Object} scope Callback function scope
1578 * @return {Array} results
1580 filter: function(array, fn, scope) {
1581 if (supportsFilter) {
1582 return array.filter(fn, scope);
1589 for (; i < ln; i++) {
1590 if (fn.call(scope, array[i], i, array)) {
1591 results.push(array[i]);
1599 * Converts a value to an array if it's not already an array; returns:
1601 * - An empty array if given value is `undefined` or `null`
1602 * - Itself if given value is already an array
1603 * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
1604 * - An array with one item which is the given value, otherwise
1606 * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
1607 * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
1609 * @return {Array} array
1612 from: function(value, newReference) {
1613 if (value === undefined || value === null) {
1617 if (Ext.isArray(value)) {
1618 return (newReference) ? slice.call(value) : value;
1621 if (value && value.length !== undefined && typeof value !== 'string') {
1622 return Ext.toArray(value);
1629 * Removes the specified item from the array if it exists
1631 * @param {Array} array The array
1632 * @param {Mixed} item The item to remove
1633 * @return {Array} The passed array itself
1635 remove: function(array, item) {
1636 var index = ExtArray.indexOf(array, item);
1639 array.splice(index, 1);
1646 * Push an item into the array only if the array doesn't contain it yet
1648 * @param {Array} array The array
1649 * @param {Mixed} item The item to include
1650 * @return {Array} The passed array itself
1652 include: function(array, item) {
1653 if (!ExtArray.contains(array, item)) {
1659 * Clone a flat array without referencing the previous one. Note that this is different
1660 * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
1661 * for Array.prototype.slice.call(array)
1663 * @param {Array} array The array
1664 * @return {Array} The clone array
1666 clone: function(array) {
1667 return slice.call(array);
1671 * Merge multiple arrays into one with unique items. Alias to {@link Ext.Array#union}.
1673 * @param {Array} array,...
1674 * @return {Array} merged
1677 var args = slice.call(arguments),
1681 for (i = 0, ln = args.length; i < ln; i++) {
1682 array = array.concat(args[i]);
1685 return ExtArray.unique(array);
1689 * Merge multiple arrays into one with unique items that exist in all of the arrays.
1691 * @param {Array} array,...
1692 * @return {Array} intersect
1694 intersect: function() {
1696 arrays = slice.call(arguments),
1697 i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
1699 if (!arrays.length) {
1703 // Find the smallest array
1704 for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
1705 if (!minArray || array.length < minArray.length) {
1711 minArray = Ext.Array.unique(minArray);
1712 arrays.splice(x, 1);
1714 // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
1715 // an item in the small array, we're likely to find it before reaching the end
1716 // of the inner loop and can terminate the search early.
1717 for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
1720 for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
1721 for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
1729 if (count === arraysLn) {
1738 * Perform a set difference A-B by subtracting all items in array B from array A.
1740 * @param {Array} array A
1741 * @param {Array} array B
1742 * @return {Array} difference
1744 difference: function(arrayA, arrayB) {
1745 var clone = slice.call(arrayA),
1749 for (i = 0,lnB = arrayB.length; i < lnB; i++) {
1750 for (j = 0; j < ln; j++) {
1751 if (clone[j] === arrayB[i]) {
1763 * Sorts the elements of an Array.
1764 * By default, this method sorts the elements alphabetically and ascending.
1766 * @param {Array} array The array to sort.
1767 * @param {Function} sortFn (optional) The comparison function.
1768 * @return {Array} The sorted array.
1770 sort: function(array, sortFn) {
1773 return array.sort(sortFn);
1775 return array.sort();
1779 var length = array.length,
1784 for (; i < length; i++) {
1786 for (j = i + 1; j < length; j++) {
1788 comparison = sortFn(array[j], array[min]);
1789 if (comparison < 0) {
1792 } else if (array[j] < array[min]) {
1798 array[i] = array[min];
1807 * Recursively flattens into 1-d Array. Injects Arrays inline.
1808 * @param {Array} array The array to flatten
1809 * @return {Array} The new, flattened array.
1811 flatten: function(array) {
1814 function rFlatten(a) {
1817 for (i = 0, ln = a.length; i < ln; i++) {
1820 if (Ext.isArray(v)) {
1830 return rFlatten(array);
1834 * Returns the minimum value in the Array.
1835 * @param {Array|NodeList} array The Array from which to select the minimum value.
1836 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
1837 * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
1838 * @return {Mixed} minValue The minimum value
1840 min: function(array, comparisonFn) {
1844 for (i = 0, ln = array.length; i < ln; i++) {
1848 if (comparisonFn(min, item) === 1) {
1863 * Returns the maximum value in the Array
1864 * @param {Array|NodeList} array The Array from which to select the maximum value.
1865 * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
1866 * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
1867 * @return {Mixed} maxValue The maximum value
1869 max: function(array, comparisonFn) {
1873 for (i = 0, ln = array.length; i < ln; i++) {
1877 if (comparisonFn(max, item) === -1) {
1892 * Calculates the mean of all items in the array
1893 * @param {Array} array The Array to calculate the mean value of.
1894 * @return {Number} The mean.
1896 mean: function(array) {
1897 return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
1901 * Calculates the sum of all items in the given array
1902 * @param {Array} array The Array to calculate the sum value of.
1903 * @return {Number} The sum.
1905 sum: function(array) {
1909 for (i = 0,ln = array.length; i < ln; i++) {
1921 * Convenient alias to {@link Ext.Array#each}
1925 Ext.each = Ext.Array.each;
1928 * Alias to {@link Ext.Array#merge}.
1932 Ext.Array.union = Ext.Array.merge;
1935 * Old alias to {@link Ext.Array#min}
1936 * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
1940 Ext.min = Ext.Array.min;
1943 * Old alias to {@link Ext.Array#max}
1944 * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
1948 Ext.max = Ext.Array.max;
1951 * Old alias to {@link Ext.Array#sum}
1952 * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
1956 Ext.sum = Ext.Array.sum;
1959 * Old alias to {@link Ext.Array#mean}
1960 * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
1964 Ext.mean = Ext.Array.mean;
1967 * Old alias to {@link Ext.Array#flatten}
1968 * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
1972 Ext.flatten = Ext.Array.flatten;
1975 * Old alias to {@link Ext.Array#clean Ext.Array.clean}
1976 * @deprecated 4.0.0 Use {@link Ext.Array.clean} instead
1980 Ext.clean = Ext.Array.clean;
1983 * Old alias to {@link Ext.Array#unique Ext.Array.unique}
1984 * @deprecated 4.0.0 Use {@link Ext.Array.unique} instead
1988 Ext.unique = Ext.Array.unique;
1991 * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
1992 * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
1996 Ext.pluck = Ext.Array.pluck;
1999 * Convenient alias to {@link Ext.Array#toArray Ext.Array.toArray}
2000 * @param {Iterable} the iterable object to be turned into a true Array.
2003 * @return {Array} array
2005 Ext.toArray = function() {
2006 return ExtArray.toArray.apply(ExtArray, arguments);
2011 * @class Ext.Function
2013 * A collection of useful static methods to deal with function callbacks
2020 * A very commonly used method throughout the framework. It acts as a wrapper around another method
2021 * which originally accepts 2 arguments for <code>name</code> and <code>value</code>.
2022 * The wrapped function then allows "flexible" value setting of either:
2025 * <li><code>name</code> and <code>value</code> as 2 arguments</li>
2026 * <li>one single object argument with multiple key - value pairs</li>
2031 var setValue = Ext.Function.flexSetter(function(name, value) {
2036 // Setting a single name - value
2037 setValue('name1', 'value1');
2039 // Settings multiple name - value pairs
2046 * @param {Function} setter
2047 * @returns {Function} flexSetter
2049 flexSetter: function(fn) {
2050 return function(a, b) {
2057 if (typeof a !== 'string') {
2059 if (a.hasOwnProperty(k)) {
2060 fn.call(this, k, a[k]);
2064 if (Ext.enumerables) {
2065 for (i = Ext.enumerables.length; i--;) {
2066 k = Ext.enumerables[i];
2067 if (a.hasOwnProperty(k)) {
2068 fn.call(this, k, a[k]);
2073 fn.call(this, a, b);
2081 * Create a new function from the provided <code>fn</code>, change <code>this</code> to the provided scope, optionally
2082 * overrides arguments for the call. (Defaults to the arguments passed by the caller)
2084 * @param {Function} fn The function to delegate.
2085 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2086 * <b>If omitted, defaults to the browser window.</b>
2087 * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2088 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2089 * if a number the args are inserted at the specified position
2090 * @return {Function} The new function
2092 bind: function(fn, scope, args, appendArgs) {
2097 var callArgs = args || arguments;
2099 if (appendArgs === true) {
2100 callArgs = Array.prototype.slice.call(arguments, 0);
2101 callArgs = callArgs.concat(args);
2103 else if (Ext.isNumber(appendArgs)) {
2104 callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
2105 applyArgs = [appendArgs, 0].concat(args); // create method call params
2106 Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
2109 return method.apply(scope || window, callArgs);
2114 * Create a new function from the provided <code>fn</code>, the arguments of which are pre-set to `args`.
2115 * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
2116 * This is especially useful when creating callbacks.
2119 var originalFunction = function(){
2120 alert(Ext.Array.from(arguments).join(' '));
2123 var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
2125 callback(); // alerts 'Hello World'
2126 callback('by Me'); // alerts 'Hello World by Me'
2128 * @param {Function} fn The original function
2129 * @param {Array} args The arguments to pass to new callback
2130 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2131 * @return {Function} The new callback function
2133 pass: function(fn, args, scope) {
2135 args = Ext.Array.from(args);
2139 return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
2144 * Create an alias to the provided method property with name <code>methodName</code> of <code>object</code>.
2145 * Note that the execution scope will still be bound to the provided <code>object</code> itself.
2147 * @param {Object/Function} object
2148 * @param {String} methodName
2149 * @return {Function} aliasFn
2151 alias: function(object, methodName) {
2153 return object[methodName].apply(object, arguments);
2158 * Creates an interceptor function. The passed function is called before the original one. If it returns false,
2159 * the original one is not called. The resulting function returns the results of the original function.
2160 * The passed function is called with the parameters of the original function. Example usage:
2162 var sayHi = function(name){
2163 alert('Hi, ' + name);
2166 sayHi('Fred'); // alerts "Hi, Fred"
2168 // create a new function that validates input without
2169 // directly modifying the original function:
2170 var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
2171 return name == 'Brian';
2174 sayHiToFriend('Fred'); // no alert
2175 sayHiToFriend('Brian'); // alerts "Hi, Brian"
2177 * @param {Function} origFn The original function.
2178 * @param {Function} newFn The function to call before the original
2179 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
2180 * <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
2181 * @param {Mixed} returnValue (optional) The value to return if the passed function return false (defaults to null).
2182 * @return {Function} The new function
2184 createInterceptor: function(origFn, newFn, scope, returnValue) {
2185 var method = origFn;
2186 if (!Ext.isFunction(newFn)) {
2194 newFn.method = origFn;
2195 return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
2201 * Creates a delegate (callback) which, when called, executes after a specific delay.
2202 * @param {Function} fn The function which will be called on a delay when the returned function is called.
2203 * Optionally, a replacement (or additional) argument list may be specified.
2204 * @param {Number} delay The number of milliseconds to defer execution by whenever called.
2205 * @param {Object} scope (optional) The scope (<code>this</code> reference) used by the function at execution time.
2206 * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
2207 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2208 * if a number the args are inserted at the specified position.
2209 * @return {Function} A function which, when called, executes the original function after the specified delay.
2211 createDelayed: function(fn, delay, scope, args, appendArgs) {
2212 if (scope || args) {
2213 fn = Ext.Function.bind(fn, scope, args, appendArgs);
2217 setTimeout(function() {
2218 fn.apply(me, arguments);
2224 * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
2226 var sayHi = function(name){
2227 alert('Hi, ' + name);
2230 // executes immediately:
2233 // executes after 2 seconds:
2234 Ext.Function.defer(sayHi, 2000, this, ['Fred']);
2236 // this syntax is sometimes useful for deferring
2237 // execution of an anonymous function:
2238 Ext.Function.defer(function(){
2242 * @param {Function} fn The function to defer.
2243 * @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
2244 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
2245 * <b>If omitted, defaults to the browser window.</b>
2246 * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
2247 * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
2248 * if a number the args are inserted at the specified position
2249 * @return {Number} The timeout id that can be used with clearTimeout
2251 defer: function(fn, millis, obj, args, appendArgs) {
2252 fn = Ext.Function.bind(fn, obj, args, appendArgs);
2254 return setTimeout(fn, millis);
2261 * Create a combined function call sequence of the original function + the passed function.
2262 * The resulting function returns the results of the original function.
2263 * The passed function is called with the parameters of the original function. Example usage:
2266 var sayHi = function(name){
2267 alert('Hi, ' + name);
2270 sayHi('Fred'); // alerts "Hi, Fred"
2272 var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
2273 alert('Bye, ' + name);
2276 sayGoodbye('Fred'); // both alerts show
2279 * @param {Function} origFn The original function.
2280 * @param {Function} newFn The function to sequence
2281 * @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
2282 * If omitted, defaults to the scope in which the original function is called or the browser window.
2283 * @return {Function} The new function
2285 createSequence: function(origFn, newFn, scope) {
2286 if (!Ext.isFunction(newFn)) {
2291 var retval = origFn.apply(this || window, arguments);
2292 newFn.apply(scope || this || window, arguments);
2299 * <p>Creates a delegate function, optionally with a bound scope which, when called, buffers
2300 * the execution of the passed function for the configured number of milliseconds.
2301 * If called again within that period, the impending invocation will be canceled, and the
2302 * timeout period will begin again.</p>
2304 * @param {Function} fn The function to invoke on a buffered timer.
2305 * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
2307 * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which
2308 * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2309 * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
2310 * passed by the caller.
2311 * @return {Function} A function which invokes the passed function after buffering for the specified time.
2313 createBuffered: function(fn, buffer, scope, args) {
2319 clearInterval(timerId);
2322 timerId = setTimeout(function(){
2323 fn.apply(scope || me, args || arguments);
2330 * <p>Creates a throttled version of the passed function which, when called repeatedly and
2331 * rapidly, invokes the passed function only after a certain interval has elapsed since the
2332 * previous invocation.</p>
2334 * <p>This is useful for wrapping functions which may be called repeatedly, such as
2335 * a handler of a mouse move event when the processing is expensive.</p>
2337 * @param fn {Function} The function to execute at a regular time interval.
2338 * @param interval {Number} The interval <b>in milliseconds</b> on which the passed function is executed.
2339 * @param scope (optional) The scope (<code><b>this</b></code> reference) in which
2340 * the passed function is executed. If omitted, defaults to the scope specified by the caller.
2341 * @returns {Function} A function which invokes the passed function at the specified interval.
2343 createThrottled: function(fn, interval, scope) {
2344 var lastCallTime, elapsed, lastArgs, timer, execute = function() {
2345 fn.apply(scope || this, lastArgs);
2346 lastCallTime = new Date().getTime();
2350 elapsed = new Date().getTime() - lastCallTime;
2351 lastArgs = arguments;
2353 clearTimeout(timer);
2354 if (!lastCallTime || (elapsed >= interval)) {
2357 timer = setTimeout(execute, interval - elapsed);
2364 * Shorthand for {@link Ext.Function#defer}
2368 Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
2371 * Shorthand for {@link Ext.Function#pass}
2375 Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
2378 * Shorthand for {@link Ext.Function#bind}
2382 Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
2385 * @author Jacky Nguyen <jacky@sencha.com>
2386 * @docauthor Jacky Nguyen <jacky@sencha.com>
2389 * A collection of useful static methods to deal with objects
2396 var ExtObject = Ext.Object = {
2399 * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
2400 * query strings. For example:
2402 var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
2404 // objects then equals:
2406 { name: 'hobbies', value: 'reading' },
2407 { name: 'hobbies', value: 'cooking' },
2408 { name: 'hobbies', value: 'swimming' },
2411 var objects = Ext.Object.toQueryObjects('dateOfBirth', {
2419 }, true); // Recursive
2421 // objects then equals:
2423 { name: 'dateOfBirth[day]', value: 3 },
2424 { name: 'dateOfBirth[month]', value: 8 },
2425 { name: 'dateOfBirth[year]', value: 1987 },
2426 { name: 'dateOfBirth[extra][hour]', value: 4 },
2427 { name: 'dateOfBirth[extra][minute]', value: 30 },
2430 * @param {String} name
2431 * @param {Mixed} value
2432 * @param {Boolean} recursive
2435 toQueryObjects: function(name, value, recursive) {
2436 var self = ExtObject.toQueryObjects,
2440 if (Ext.isArray(value)) {
2441 for (i = 0, ln = value.length; i < ln; i++) {
2443 objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2453 else if (Ext.isObject(value)) {
2455 if (value.hasOwnProperty(i)) {
2457 objects = objects.concat(self(name + '[' + i + ']', value[i], true));
2479 * Takes an object and converts it to an encoded query string
2483 Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
2484 Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
2485 Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
2486 Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
2487 Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
2491 Ext.Object.toQueryString({
2498 hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2499 }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
2501 // &dateOfBirth[day]=1&dateOfBirth[month]=2&dateOfBirth[year]=1911
2502 // &hobbies[0]=coding&hobbies[1]=eating&hobbies[2]=sleeping&hobbies[3][0]=nested&hobbies[3][1]=stuff
2505 * @param {Object} object The object to encode
2506 * @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
2507 * (PHP / Ruby on Rails servers and similar). Defaults to false
2508 * @return {String} queryString
2511 toQueryString: function(object, recursive) {
2512 var paramObjects = [],
2514 i, j, ln, paramObject, value;
2517 if (object.hasOwnProperty(i)) {
2518 paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
2522 for (j = 0, ln = paramObjects.length; j < ln; j++) {
2523 paramObject = paramObjects[j];
2524 value = paramObject.value;
2526 if (Ext.isEmpty(value)) {
2529 else if (Ext.isDate(value)) {
2530 value = Ext.Date.toString(value);
2533 params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
2536 return params.join('&');
2540 * Converts a query string back into an object.
2544 Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
2545 Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
2546 Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
2547 Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
2551 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);
2561 hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
2564 * @param {String} queryString The query string to decode
2565 * @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
2566 * PHP / Ruby on Rails servers and similar. Defaults to false
2569 fromQueryString: function(queryString, recursive) {
2570 var parts = queryString.replace(/^\?/, '').split('&'),
2572 temp, components, name, value, i, ln,
2573 part, j, subLn, matchedKeys, matchedName,
2576 for (i = 0, ln = parts.length; i < ln; i++) {
2579 if (part.length > 0) {
2580 components = part.split('=');
2581 name = decodeURIComponent(components[0]);
2582 value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
2585 if (object.hasOwnProperty(name)) {
2586 if (!Ext.isArray(object[name])) {
2587 object[name] = [object[name]];
2590 object[name].push(value);
2593 object[name] = value;
2597 matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
2598 matchedName = name.match(/^([^\[]+)/);
2602 sourceClass: "Ext.Object",
2603 sourceMethod: "fromQueryString",
2604 queryString: queryString,
2605 recursive: recursive,
2606 msg: 'Malformed query string given, failed parsing name from "' + part + '"'
2610 name = matchedName[0];
2613 if (matchedKeys === null) {
2614 object[name] = value;
2618 for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
2619 key = matchedKeys[j];
2620 key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
2628 for (j = 0, subLn = keys.length; j < subLn; j++) {
2631 if (j === subLn - 1) {
2632 if (Ext.isArray(temp) && key === '') {
2640 if (temp[key] === undefined || typeof temp[key] === 'string') {
2641 nextKey = keys[j+1];
2643 temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
2657 * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
2658 * by returning `false` in the callback function. For example:
2663 loves: ['food', 'sleeping', 'wife']
2666 Ext.Object.each(person, function(key, value, myself) {
2667 console.log(key + ":" + value);
2669 if (key === 'hairColor') {
2670 return false; // stop the iteration
2674 * @param {Object} object The object to iterate
2675 * @param {Function} fn The callback function. Passed arguments for each iteration are:
2679 - {Object} `object` The object itself
2681 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
2684 each: function(object, fn, scope) {
2685 for (var property in object) {
2686 if (object.hasOwnProperty(property)) {
2687 if (fn.call(scope || object, property, object[property], object) === false) {
2695 * Merges any number of objects recursively without referencing them or their children.
2698 companyName: 'Ext JS',
2699 products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
2703 location: 'Palo Alto',
2709 companyName: 'Sencha Inc.',
2710 products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2713 location: 'Redwood City'
2717 var sencha = Ext.Object.merge(extjs, newStuff);
2719 // extjs and sencha then equals to
2721 companyName: 'Sencha Inc.',
2722 products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
2726 location: 'Redwood City'
2731 * @param {Object} object,...
2732 * @return {Object} merged The object that is created as a result of merging all the objects passed in.
2735 merge: function(source, key, value) {
2736 if (typeof key === 'string') {
2737 if (value && value.constructor === Object) {
2738 if (source[key] && source[key].constructor === Object) {
2739 ExtObject.merge(source[key], value);
2742 source[key] = Ext.clone(value);
2746 source[key] = value;
2753 ln = arguments.length,
2756 for (; i < ln; i++) {
2757 object = arguments[i];
2759 for (property in object) {
2760 if (object.hasOwnProperty(property)) {
2761 ExtObject.merge(source, property, object[property]);
2770 * Returns the first matching key corresponding to the given value.
2771 * If no matching value is found, null is returned.
2778 alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'
2780 * @param {Object} object
2781 * @param {Object} value The value to find
2784 getKey: function(object, value) {
2785 for (var property in object) {
2786 if (object.hasOwnProperty(property) && object[property] === value) {
2795 * Gets all values of the given object as an array.
2797 var values = Ext.Object.getValues({
2800 }); // ['Jacky', 'food']
2802 * @param {Object} object
2803 * @return {Array} An array of values from the object
2806 getValues: function(object) {
2810 for (property in object) {
2811 if (object.hasOwnProperty(property)) {
2812 values.push(object[property]);
2820 * Gets all keys of the given object as an array.
2822 var values = Ext.Object.getKeys({
2825 }); // ['name', 'loves']
2827 * @param {Object} object
2828 * @return {Array} An array of keys from the object
2830 getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
2834 for (property in object) {
2835 if (object.hasOwnProperty(property)) {
2836 keys.push(property);
2844 * Gets the total number of this object's own properties
2846 var size = Ext.Object.getSize({
2849 }); // size equals 2
2851 * @param {Object} object
2852 * @return {Number} size
2855 getSize: function(object) {
2859 for (property in object) {
2860 if (object.hasOwnProperty(property)) {
2871 * A convenient alias method for {@link Ext.Object#merge}
2876 Ext.merge = Ext.Object.merge;
2879 * A convenient alias method for {@link Ext.Object#toQueryString}
2883 * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
2885 Ext.urlEncode = function() {
2886 var args = Ext.Array.from(arguments),
2889 // Support for the old `pre` argument
2890 if ((typeof args[1] === 'string')) {
2891 prefix = args[1] + '&';
2895 return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
2899 * A convenient alias method for {@link Ext.Object#fromQueryString}
2903 * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
2905 Ext.urlDecode = function() {
2906 return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
2913 * A set of useful static methods to deal with date
2914 * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
2915 * this object for convenience
2917 * The date parsing and formatting syntax contains a subset of
2918 * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
2919 * supported will provide results equivalent to their PHP versions.
2921 * The following is a list of all currently supported formats:
2923 Format Description Example returned values
2924 ------ ----------------------------------------------------------------------- -----------------------
2925 d Day of the month, 2 digits with leading zeros 01 to 31
2926 D A short textual representation of the day of the week Mon to Sun
2927 j Day of the month without leading zeros 1 to 31
2928 l A full textual representation of the day of the week Sunday to Saturday
2929 N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
2930 S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
2931 w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
2932 z The day of the year (starting from 0) 0 to 364 (365 in leap years)
2933 W ISO-8601 week number of year, weeks starting on Monday 01 to 53
2934 F A full textual representation of a month, such as January or March January to December
2935 m Numeric representation of a month, with leading zeros 01 to 12
2936 M A short textual representation of a month Jan to Dec
2937 n Numeric representation of a month, without leading zeros 1 to 12
2938 t Number of days in the given month 28 to 31
2939 L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
2940 o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
2941 belongs to the previous or next year, that year is used instead)
2942 Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
2943 y A two digit representation of a year Examples: 99 or 03
2944 a Lowercase Ante meridiem and Post meridiem am or pm
2945 A Uppercase Ante meridiem and Post meridiem AM or PM
2946 g 12-hour format of an hour without leading zeros 1 to 12
2947 G 24-hour format of an hour without leading zeros 0 to 23
2948 h 12-hour format of an hour with leading zeros 01 to 12
2949 H 24-hour format of an hour with leading zeros 00 to 23
2950 i Minutes, with leading zeros 00 to 59
2951 s Seconds, with leading zeros 00 to 59
2952 u Decimal fraction of a second Examples:
2953 (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
2954 100 (i.e. 0.100s) or
2955 999 (i.e. 0.999s) or
2956 999876543210 (i.e. 0.999876543210s)
2957 O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
2958 P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
2959 T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
2960 Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
2963 1) If unspecified, the month / day defaults to the current month / day, 1991 or
2964 the time defaults to midnight, while the timezone defaults to the 1992-10 or
2965 browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
2966 and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
2967 are optional. 1995-07-18T17:21:28-02:00 or
2968 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
2969 least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
2970 of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
2971 Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
2972 date-time granularity which are supported, or see 2000-02-13T21:25:33
2973 http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
2974 U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
2975 MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
2976 \/Date(1238606590509+0800)\/
2979 * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
2982 // 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
2984 var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
2985 console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
2986 console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
2987 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
2990 * Here are some standard date/time patterns that you might find helpful. They
2991 * are not part of the source of Ext.Date, but to use them you can simply copy this
2992 * block of code into any script that is included after Ext.Date and they will also become
2993 * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
2995 Ext.Date.patterns = {
2996 ISO8601Long:"Y-m-d H:i:s",
2997 ISO8601Short:"Y-m-d",
2999 LongDate: "l, F d, Y",
3000 FullDateTime: "l, F d, Y g:i:s A",
3003 LongTime: "g:i:s A",
3004 SortableDateTime: "Y-m-d\\TH:i:s",
3005 UniversalSortableDateTime: "Y-m-d H:i:sO",
3012 var dt = new Date();
3013 console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
3015 * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
3016 * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
3021 * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
3022 * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
3023 * They generate precompiled functions from format patterns instead of parsing and
3024 * processing each pattern every time a date is formatted. These functions are available
3025 * on every Date object.
3030 // create private copy of Ext's Ext.util.Format.format() method
3031 // - to remove unnecessary dependency
3032 // - to resolve namespace conflict with MS-Ajax's implementation
3033 function xf(format) {
3034 var args = Array.prototype.slice.call(arguments, 1);
3035 return format.replace(/\{(\d+)\}/g, function(m, i) {
3042 * Returns the current timestamp
3043 * @return {Date} The current timestamp
3045 now: Date.now || function() {
3053 toString: function(date) {
3054 var pad = Ext.String.leftPad;
3056 return date.getFullYear() + "-"
3057 + pad(date.getMonth() + 1, 2, '0') + "-"
3058 + pad(date.getDate(), 2, '0') + "T"
3059 + pad(date.getHours(), 2, '0') + ":"
3060 + pad(date.getMinutes(), 2, '0') + ":"
3061 + pad(date.getSeconds(), 2, '0');
3065 * Returns the number of milliseconds between two dates
3066 * @param {Date} dateA The first date
3067 * @param {Date} dateB (optional) The second date, defaults to now
3068 * @return {Number} The difference in milliseconds
3070 getElapsed: function(dateA, dateB) {
3071 return Math.abs(dateA - (dateB || new Date()));
3075 * Global flag which determines if strict date parsing should be used.
3076 * Strict date parsing will not roll-over invalid dates, which is the
3077 * default behaviour of javascript Date objects.
3078 * (see {@link #parse} for more information)
3079 * Defaults to <tt>false</tt>.
3086 formatCodeToRegex: function(character, currentGroup) {
3087 // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
3088 var p = utilDate.parseCodes[character];
3091 p = typeof p == 'function'? p() : p;
3092 utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
3095 return p ? Ext.applyIf({
3096 c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
3100 s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
3105 * <p>An object hash in which each property is a date parsing function. The property name is the
3106 * format string which that function parses.</p>
3107 * <p>This object is automatically populated with date parsing functions as
3108 * date formats are requested for Ext standard formatting strings.</p>
3109 * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
3110 * may be used as a format string to {@link #parse}.<p>
3111 * <p>Example:</p><pre><code>
3112 Ext.Date.parseFunctions['x-date-format'] = myDateParser;
3114 * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
3115 * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
3116 * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
3117 * (i.e. prevent javascript Date "rollover") (The default must be false).
3118 * Invalid date strings should return null when parsed.</div></li>
3120 * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
3121 * formatting function must be placed into the {@link #formatFunctions} property.
3122 * @property parseFunctions
3127 "MS": function(input, strict) {
3128 // note: the timezone offset is ignored since the MS Ajax server sends
3129 // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
3130 var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
3131 var r = (input || '').match(re);
3132 return r? new Date(((r[1] || '') + r[2]) * 1) : null;
3138 * <p>An object hash in which each property is a date formatting function. The property name is the
3139 * format string which corresponds to the produced formatted date string.</p>
3140 * <p>This object is automatically populated with date formatting functions as
3141 * date formats are requested for Ext standard formatting strings.</p>
3142 * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
3143 * may be used as a format string to {@link #format}. Example:</p><pre><code>
3144 Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
3146 * <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>
3147 * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
3149 * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
3150 * parsing function must be placed into the {@link #parseFunctions} property.
3151 * @property formatFunctions
3157 // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
3158 return '\\/Date(' + this.getTime() + ')\\/';
3165 * Date interval constant
3172 * Date interval constant
3179 * Date interval constant
3185 /** Date interval constant
3192 * Date interval constant
3199 * Date interval constant
3206 * Date interval constant
3213 * <p>An object hash containing default date values used during date parsing.</p>
3214 * <p>The following properties are available:<div class="mdetail-params"><ul>
3215 * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
3216 * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
3217 * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
3218 * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
3219 * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
3220 * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
3221 * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
3223 * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
3224 * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
3225 * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
3226 * It is the responsiblity of the developer to account for this.</b></p>
3229 // set default day value to the first day of the month
3230 Ext.Date.defaults.d = 1;
3232 // parse a February date string containing only year and month values.
3233 // setting the default day value to 1 prevents weird date rollover issues
3234 // when attempting to parse the following date string on, for example, March 31st 2009.
3235 Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
3237 * @property defaults
3244 * An array of textual day names.
3245 * Override these values for international dates.
3248 Ext.Date.dayNames = [
3268 * An array of textual month names.
3269 * Override these values for international dates.
3272 Ext.Date.monthNames = [
3297 * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
3298 * Override these values for international dates.
3301 Ext.Date.monthNumbers = {
3302 'ShortJanNameInYourLang':0,
3303 'ShortFebNameInYourLang':1,
3325 * <p>The date format string that the {@link #dateRenderer} and {@link #date} functions use.
3326 * see {@link #Date} for details.</p>
3327 * <p>This defaults to <code>m/d/Y</code>, but may be overridden in a locale file.</p>
3328 * @property defaultFormat
3332 defaultFormat : "m/d/Y",
3334 * Get the short month name for the given month number.
3335 * Override this function for international dates.
3336 * @param {Number} month A zero-based javascript month number.
3337 * @return {String} The short month name.
3340 getShortMonthName : function(month) {
3341 return utilDate.monthNames[month].substring(0, 3);
3345 * Get the short day name for the given day number.
3346 * Override this function for international dates.
3347 * @param {Number} day A zero-based javascript day number.
3348 * @return {String} The short day name.
3351 getShortDayName : function(day) {
3352 return utilDate.dayNames[day].substring(0, 3);
3356 * Get the zero-based javascript month number for the given short/full month name.
3357 * Override this function for international dates.
3358 * @param {String} name The short/full month name.
3359 * @return {Number} The zero-based javascript month number.
3362 getMonthNumber : function(name) {
3363 // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
3364 return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
3368 * Checks if the specified format contains hour information
3369 * @param {String} format The format to check
3370 * @return {Boolean} True if the format contains hour information
3373 formatContainsHourInfo : (function(){
3374 var stripEscapeRe = /(\\.)/g,
3375 hourInfoRe = /([gGhHisucUOPZ]|MS)/;
3376 return function(format){
3377 return hourInfoRe.test(format.replace(stripEscapeRe, ''));
3382 * Checks if the specified format contains information about
3383 * anything other than the time.
3384 * @param {String} format The format to check
3385 * @return {Boolean} True if the format contains information about
3386 * date/day information.
3389 formatContainsDateInfo : (function(){
3390 var stripEscapeRe = /(\\.)/g,
3391 dateInfoRe = /([djzmnYycU]|MS)/;
3393 return function(format){
3394 return dateInfoRe.test(format.replace(stripEscapeRe, ''));
3399 * The base format-code to formatting-function hashmap used by the {@link #format} method.
3400 * Formatting functions are strings (or functions which return strings) which
3401 * will return the appropriate value when evaluated in the context of the Date object
3402 * from which the {@link #format} method is called.
3403 * Add to / override these mappings for custom date formatting.
3404 * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
3407 Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
3408 console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
3414 d: "Ext.String.leftPad(this.getDate(), 2, '0')",
3415 D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
3416 j: "this.getDate()",
3417 l: "Ext.Date.dayNames[this.getDay()]",
3418 N: "(this.getDay() ? this.getDay() : 7)",
3419 S: "Ext.Date.getSuffix(this)",
3421 z: "Ext.Date.getDayOfYear(this)",
3422 W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
3423 F: "Ext.Date.monthNames[this.getMonth()]",
3424 m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
3425 M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
3426 n: "(this.getMonth() + 1)",
3427 t: "Ext.Date.getDaysInMonth(this)",
3428 L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
3429 o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
3430 Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
3431 y: "('' + this.getFullYear()).substring(2, 4)",
3432 a: "(this.getHours() < 12 ? 'am' : 'pm')",
3433 A: "(this.getHours() < 12 ? 'AM' : 'PM')",
3434 g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
3435 G: "this.getHours()",
3436 h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
3437 H: "Ext.String.leftPad(this.getHours(), 2, '0')",
3438 i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
3439 s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
3440 u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
3441 O: "Ext.Date.getGMTOffset(this)",
3442 P: "Ext.Date.getGMTOffset(this, true)",
3443 T: "Ext.Date.getTimezone(this)",
3444 Z: "(this.getTimezoneOffset() * -60)",
3446 c: function() { // ISO-8601 -- GMT format
3447 for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
3448 var e = c.charAt(i);
3449 code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
3451 return code.join(" + ");
3454 c: function() { // ISO-8601 -- UTC format
3456 "this.getUTCFullYear()", "'-'",
3457 "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
3458 "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
3460 "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
3461 "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
3462 "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
3468 U: "Math.round(this.getTime() / 1000)"
3472 * Checks if the passed Date parameters will cause a javascript Date "rollover".
3473 * @param {Number} year 4-digit year
3474 * @param {Number} month 1-based month-of-year
3475 * @param {Number} day Day of month
3476 * @param {Number} hour (optional) Hour
3477 * @param {Number} minute (optional) Minute
3478 * @param {Number} second (optional) Second
3479 * @param {Number} millisecond (optional) Millisecond
3480 * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
3483 isValid : function(y, m, d, h, i, s, ms) {
3490 // Special handling for year < 100
3491 var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
3493 return y == dt.getFullYear() &&
3494 m == dt.getMonth() + 1 &&
3495 d == dt.getDate() &&
3496 h == dt.getHours() &&
3497 i == dt.getMinutes() &&
3498 s == dt.getSeconds() &&
3499 ms == dt.getMilliseconds();
3503 * Parses the passed string using the specified date format.
3504 * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
3505 * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
3506 * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
3507 * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
3508 * Keep in mind that the input date string must precisely match the specified format string
3509 * in order for the parse operation to be successful (failed parse operations return a null value).
3510 * <p>Example:</p><pre><code>
3511 //dt = Fri May 25 2007 (current date)
3512 var dt = new Date();
3514 //dt = Thu May 25 2006 (today's month/day in 2006)
3515 dt = Ext.Date.parse("2006", "Y");
3517 //dt = Sun Jan 15 2006 (all date parts specified)
3518 dt = Ext.Date.parse("2006-01-15", "Y-m-d");
3520 //dt = Sun Jan 15 2006 15:20:01
3521 dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
3523 // attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
3524 dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
3526 * @param {String} input The raw date string.
3527 * @param {String} format The expected date string format.
3528 * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
3529 (defaults to false). Invalid date strings will return null when parsed.
3530 * @return {Date} The parsed Date.
3533 parse : function(input, format, strict) {
3534 var p = utilDate.parseFunctions;
3535 if (p[format] == null) {
3536 utilDate.createParser(format);
3538 return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
3542 parseDate: function(input, format, strict){
3543 return utilDate.parse(input, format, strict);
3548 getFormatCode : function(character) {
3549 var f = utilDate.formatCodes[character];
3552 f = typeof f == 'function'? f() : f;
3553 utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
3556 // note: unknown characters are treated as literals
3557 return f || ("'" + Ext.String.escape(character) + "'");
3561 createFormat : function(format) {
3566 for (var i = 0; i < format.length; ++i) {
3567 ch = format.charAt(i);
3568 if (!special && ch == "\\") {
3570 } else if (special) {
3572 code.push("'" + Ext.String.escape(ch) + "'");
3574 code.push(utilDate.getFormatCode(ch));
3577 utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
3581 createParser : (function() {
3583 "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
3584 "def = Ext.Date.defaults,",
3585 "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
3590 "if(u != null){", // i.e. unix time is defined
3591 "v = new Date(u * 1000);", // give top priority to UNIX time
3593 // create Date object representing midnight of the current day;
3594 // this will provide us with our date defaults
3595 // (note: clearTime() handles Daylight Saving Time automatically)
3596 "dt = Ext.Date.clearTime(new Date);",
3598 // date calculations (note: these calculations create a dependency on Ext.Number.from())
3599 "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
3600 "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
3601 "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
3603 // time calculations (note: these calculations create a dependency on Ext.Number.from())
3604 "h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
3605 "i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
3606 "s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
3607 "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
3609 "if(z >= 0 && y >= 0){",
3610 // both the year and zero-based day of year are defined and >= 0.
3611 // these 2 values alone provide sufficient info to create a full date object
3613 // create Date object representing January 1st for the given year
3614 // handle years < 100 appropriately
3615 "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3617 // then add day of year, checking for Date "rollover" if necessary
3618 "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
3619 "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
3620 "v = null;", // invalid date, so return null
3622 // plain old Date object
3623 // handle years < 100 properly
3624 "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
3630 // favour UTC offset over GMT offset
3632 // reset to UTC, then add offset
3633 "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
3635 // reset to GMT, then add offset
3636 "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
3643 return function(format) {
3644 var regexNum = utilDate.parseRegexes.length,
3651 for (var i = 0; i < format.length; ++i) {
3652 ch = format.charAt(i);
3653 if (!special && ch == "\\") {
3655 } else if (special) {
3657 regex.push(Ext.String.escape(ch));
3659 var obj = utilDate.formatCodeToRegex(ch, currentGroup);
3660 currentGroup += obj.g;
3662 if (obj.g && obj.c) {
3668 utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
3669 utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
3677 * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
3678 * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
3679 * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
3683 c:"d = parseInt(results[{0}], 10);\n",
3684 s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
3688 c:"d = parseInt(results[{0}], 10);\n",
3689 s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
3692 for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
3696 s:"(?:" + a.join("|") +")"
3703 s:"(?:" + utilDate.dayNames.join("|") + ")"
3709 s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
3719 s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
3723 c:"z = parseInt(results[{0}], 10);\n",
3724 s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
3729 s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
3734 c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
3735 s:"(" + utilDate.monthNames.join("|") + ")"
3739 for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
3740 return Ext.applyIf({
3741 s:"(" + a.join("|") + ")"
3742 }, utilDate.formatCodeToRegex("F"));
3746 c:"m = parseInt(results[{0}], 10) - 1;\n",
3747 s:"(\\d{2})" // month number with leading zeros (01 - 12)
3751 c:"m = parseInt(results[{0}], 10) - 1;\n",
3752 s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
3757 s:"(?:\\d{2})" // no. of days in the month (28 - 31)
3765 return utilDate.formatCodeToRegex("Y");
3769 c:"y = parseInt(results[{0}], 10);\n",
3770 s:"(\\d{4})" // 4-digit year
3774 c:"var ty = parseInt(results[{0}], 10);\n"
3775 + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
3779 * In the am/pm parsing routines, we allow both upper and lower case
3780 * even though it doesn't exactly match the spec. It gives much more flexibility
3781 * in being able to specify case insensitive regexes.
3785 c:"if (/(am)/i.test(results[{0}])) {\n"
3786 + "if (!h || h == 12) { h = 0; }\n"
3787 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
3792 c:"if (/(am)/i.test(results[{0}])) {\n"
3793 + "if (!h || h == 12) { h = 0; }\n"
3794 + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
3798 return utilDate.formatCodeToRegex("G");
3802 c:"h = parseInt(results[{0}], 10);\n",
3803 s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
3806 return utilDate.formatCodeToRegex("H");
3810 c:"h = parseInt(results[{0}], 10);\n",
3811 s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
3815 c:"i = parseInt(results[{0}], 10);\n",
3816 s:"(\\d{2})" // minutes with leading zeros (00 - 59)
3820 c:"s = parseInt(results[{0}], 10);\n",
3821 s:"(\\d{2})" // seconds with leading zeros (00 - 59)
3825 c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
3826 s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
3831 "o = results[{0}];",
3832 "var sn = o.substring(0,1),", // get + / - sign
3833 "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
3834 "mn = o.substring(3,5) % 60;", // get minutes
3835 "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
3837 s: "([+\-]\\d{4})" // GMT offset in hrs and mins
3842 "o = results[{0}];",
3843 "var sn = o.substring(0,1),", // get + / - sign
3844 "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
3845 "mn = o.substring(4,6) % 60;", // get minutes
3846 "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
3848 s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
3853 s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
3857 c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
3858 + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
3859 s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
3864 utilDate.formatCodeToRegex("Y", 1), // year
3865 utilDate.formatCodeToRegex("m", 2), // month
3866 utilDate.formatCodeToRegex("d", 3), // day
3867 utilDate.formatCodeToRegex("h", 4), // hour
3868 utilDate.formatCodeToRegex("i", 5), // minute
3869 utilDate.formatCodeToRegex("s", 6), // second
3870 {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)
3871 {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
3872 "if(results[8]) {", // timezone specified
3873 "if(results[8] == 'Z'){",
3875 "}else if (results[8].indexOf(':') > -1){",
3876 utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
3878 utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
3884 for (var i = 0, l = arr.length; i < l; ++i) {
3885 calc.push(arr[i].c);
3892 arr[0].s, // year (required)
3893 "(?:", "-", arr[1].s, // month (optional)
3894 "(?:", "-", arr[2].s, // day (optional)
3896 "(?:T| )?", // time delimiter -- either a "T" or a single blank space
3897 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
3898 "(?::", arr[5].s, ")?", // seconds (optional)
3899 "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
3900 "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
3909 c:"u = parseInt(results[{0}], 10);\n",
3910 s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
3914 //Old Ext.Date prototype methods.
3916 dateFormat: function(date, format) {
3917 return utilDate.format(date, format);
3921 * Formats a date given the supplied format string.
3922 * @param {Date} date The date to format
3923 * @param {String} format The format string
3924 * @return {String} The formatted date
3926 format: function(date, format) {
3927 if (utilDate.formatFunctions[format] == null) {
3928 utilDate.createFormat(format);
3930 var result = utilDate.formatFunctions[format].call(date);
3935 * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
3937 * Note: The date string returned by the javascript Date object's toString() method varies
3938 * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
3939 * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
3940 * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
3941 * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
3942 * from the GMT offset portion of the date string.
3943 * @param {Date} date The date
3944 * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
3946 getTimezone : function(date) {
3947 // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
3949 // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
3950 // 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)
3951 // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
3952 // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
3953 // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
3955 // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
3956 // step 1: (?:\((.*)\) -- find timezone in parentheses
3957 // 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
3958 // step 3: remove all non uppercase characters found in step 1 and 2
3959 return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
3963 * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
3964 * @param {Date} date The date
3965 * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
3966 * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
3968 getGMTOffset : function(date, colon) {
3969 var offset = date.getTimezoneOffset();
3970 return (offset > 0 ? "-" : "+")
3971 + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
3972 + (colon ? ":" : "")
3973 + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
3977 * Get the numeric day number of the year, adjusted for leap year.
3978 * @param {Date} date The date
3979 * @return {Number} 0 to 364 (365 in leap years).
3981 getDayOfYear: function(date) {
3983 d = Ext.Date.clone(date),
3984 m = date.getMonth(),
3987 for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
3988 num += utilDate.getDaysInMonth(d);
3990 return num + date.getDate() - 1;
3994 * Get the numeric ISO-8601 week number of the year.
3995 * (equivalent to the format specifier 'W', but without a leading zero).
3996 * @param {Date} date The date
3997 * @return {Number} 1 to 53
3999 getWeekOfYear : (function() {
4000 // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
4001 var ms1d = 864e5, // milliseconds in a day
4002 ms7d = 7 * ms1d; // milliseconds in a week
4004 return function(date) { // return a closure so constants get calculated only once
4005 var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
4006 AWN = Math.floor(DC3 / 7), // an Absolute Week Number
4007 Wyr = new Date(AWN * ms7d).getUTCFullYear();
4009 return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
4014 * Checks if the current date falls within a leap year.
4015 * @param {Date} date The date
4016 * @return {Boolean} True if the current date falls within a leap year, false otherwise.
4018 isLeapYear : function(date) {
4019 var year = date.getFullYear();
4020 return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
4024 * Get the first day of the current month, adjusted for leap year. The returned value
4025 * is the numeric day index within the week (0-6) which can be used in conjunction with
4026 * the {@link #monthNames} array to retrieve the textual day name.
4029 var dt = new Date('1/10/2007'),
4030 firstDay = Ext.Date.getFirstDayOfMonth(dt);
4031 console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
4033 * @param {Date} date The date
4034 * @return {Number} The day number (0-6).
4036 getFirstDayOfMonth : function(date) {
4037 var day = (date.getDay() - (date.getDate() - 1)) % 7;
4038 return (day < 0) ? (day + 7) : day;
4042 * Get the last day of the current month, adjusted for leap year. The returned value
4043 * is the numeric day index within the week (0-6) which can be used in conjunction with
4044 * the {@link #monthNames} array to retrieve the textual day name.
4047 var dt = new Date('1/10/2007'),
4048 lastDay = Ext.Date.getLastDayOfMonth(dt);
4049 console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
4051 * @param {Date} date The date
4052 * @return {Number} The day number (0-6).
4054 getLastDayOfMonth : function(date) {
4055 return utilDate.getLastDateOfMonth(date).getDay();
4060 * Get the date of the first day of the month in which this date resides.
4061 * @param {Date} date The date
4064 getFirstDateOfMonth : function(date) {
4065 return new Date(date.getFullYear(), date.getMonth(), 1);
4069 * Get the date of the last day of the month in which this date resides.
4070 * @param {Date} date The date
4073 getLastDateOfMonth : function(date) {
4074 return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
4078 * Get the number of days in the current month, adjusted for leap year.
4079 * @param {Date} date The date
4080 * @return {Number} The number of days in the month.
4082 getDaysInMonth: (function() {
4083 var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
4085 return function(date) { // return a closure for efficiency
4086 var m = date.getMonth();
4088 return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
4093 * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
4094 * @param {Date} date The date
4095 * @return {String} 'st, 'nd', 'rd' or 'th'.
4097 getSuffix : function(date) {
4098 switch (date.getDate()) {
4115 * Creates and returns a new Date instance with the exact same date value as the called instance.
4116 * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
4117 * variable will also be changed. When the intention is to create a new variable that will not
4118 * modify the original instance, you should create a clone.
4120 * Example of correctly cloning a date:
4123 var orig = new Date('10/1/2006');
4126 console.log(orig); //returns 'Thu Oct 05 2006'!
4129 var orig = new Date('10/1/2006'),
4130 copy = Ext.Date.clone(orig);
4132 console.log(orig); //returns 'Thu Oct 01 2006'
4134 * @param {Date} date The date
4135 * @return {Date} The new Date instance.
4137 clone : function(date) {
4138 return new Date(date.getTime());
4142 * Checks if the current date is affected by Daylight Saving Time (DST).
4143 * @param {Date} date The date
4144 * @return {Boolean} True if the current date is affected by DST.
4146 isDST : function(date) {
4147 // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
4148 // courtesy of @geoffrey.mcgill
4149 return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
4153 * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
4154 * automatically adjusting for Daylight Saving Time (DST) where applicable.
4155 * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
4156 * @param {Date} date The date
4157 * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
4158 * @return {Date} this or the clone.
4160 clearTime : function(date, clone) {
4162 return Ext.Date.clearTime(Ext.Date.clone(date));
4165 // get current date before clearing time
4166 var d = date.getDate();
4172 date.setMilliseconds(0);
4174 if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
4175 // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
4176 // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
4178 // increment hour until cloned date == current date
4179 for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
4182 date.setHours(c.getHours());
4189 * Provides a convenient method for performing basic date arithmetic. This method
4190 * does not modify the Date instance being called - it creates and returns
4191 * a new Date instance containing the resulting date value.
4196 var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
4197 console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
4199 // Negative values will be subtracted:
4200 var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
4201 console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
4205 * @param {Date} date The date to modify
4206 * @param {String} interval A valid date interval enum value.
4207 * @param {Number} value The amount to add to the current date.
4208 * @return {Date} The new Date instance.
4210 add : function(date, interval, value) {
4211 var d = Ext.Date.clone(date),
4213 if (!interval || value === 0) return d;
4215 switch(interval.toLowerCase()) {
4216 case Ext.Date.MILLI:
4217 d.setMilliseconds(d.getMilliseconds() + value);
4219 case Ext.Date.SECOND:
4220 d.setSeconds(d.getSeconds() + value);
4222 case Ext.Date.MINUTE:
4223 d.setMinutes(d.getMinutes() + value);
4226 d.setHours(d.getHours() + value);
4229 d.setDate(d.getDate() + value);
4231 case Ext.Date.MONTH:
4232 var day = date.getDate();
4234 day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
4237 d.setMonth(date.getMonth() + value);
4240 d.setFullYear(date.getFullYear() + value);
4247 * Checks if a date falls on or between the given start and end dates.
4248 * @param {Date} date The date to check
4249 * @param {Date} start Start date
4250 * @param {Date} end End date
4251 * @return {Boolean} true if this date falls on or between the given start and end dates.
4253 between : function(date, start, end) {
4254 var t = date.getTime();
4255 return start.getTime() <= t && t <= end.getTime();
4258 //Maintains compatibility with old static and prototype window.Date methods.
4259 compat: function() {
4260 var nativeDate = window.Date,
4262 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'],
4263 proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
4266 Ext.Array.forEach(statics, function(s) {
4267 nativeDate[s] = utilDate[s];
4270 //Append to prototype
4271 Ext.Array.forEach(proto, function(s) {
4272 nativeDate.prototype[s] = function() {
4273 var args = Array.prototype.slice.call(arguments);
4275 return utilDate[s].apply(utilDate, args);
4281 var utilDate = Ext.Date;
4286 * @author Jacky Nguyen <jacky@sencha.com>
4287 * @docauthor Jacky Nguyen <jacky@sencha.com>
4290 * The root of all classes created with {@link Ext#define}
4291 * All prototype and static members of this class are inherited by any other class
4294 (function(flexSetter) {
4296 var Base = Ext.Base = function() {};
4298 $className: 'Ext.Base',
4303 * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
4304 * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
4305 * for a detailed comparison
4307 Ext.define('My.Cat', {
4309 speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4312 constructor: function() {
4313 alert(this.self.speciesName); / dependent on 'this'
4319 return new this.self();
4324 Ext.define('My.SnowLeopard', {
4327 speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
4331 var cat = new My.Cat(); // alerts 'Cat'
4332 var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
4334 var clone = snowLeopard.clone();
4335 alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
4344 * Default constructor, simply returns `this`
4348 * @return {Object} this
4350 constructor: function() {
4355 * Initialize configuration for this class. a typical example:
4357 Ext.define('My.awesome.Class', {
4358 // The default config
4364 constructor: function(config) {
4365 this.initConfig(config);
4371 var awesome = new My.awesome.Class({
4372 name: 'Super Awesome'
4375 alert(awesome.getName()); // 'Super Awesome'
4378 * @param {Object} config
4379 * @return {Object} mixins The mixin prototypes as key - value pairs
4382 initConfig: function(config) {
4383 if (!this.$configInited) {
4384 this.config = Ext.Object.merge({}, this.config || {}, config || {});
4386 this.applyConfig(this.config);
4388 this.$configInited = true;
4397 setConfig: function(config) {
4398 this.applyConfig(config || {});
4406 applyConfig: flexSetter(function(name, value) {
4407 var setter = 'set' + Ext.String.capitalize(name);
4409 if (typeof this[setter] === 'function') {
4410 this[setter].call(this, value);
4417 * Call the parent's overridden method. For example:
4419 Ext.define('My.own.A', {
4420 constructor: function(test) {
4425 Ext.define('My.own.B', {
4428 constructor: function(test) {
4431 this.callParent([test + 1]);
4435 Ext.define('My.own.C', {
4438 constructor: function() {
4439 alert("Going to call parent's overriden constructor...");
4441 this.callParent(arguments);
4445 var a = new My.own.A(1); // alerts '1'
4446 var b = new My.own.B(1); // alerts '1', then alerts '2'
4447 var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
4448 // alerts '2', then alerts '3'
4451 * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4452 * from the current method, for example: `this.callParent(arguments)`
4453 * @return {Mixed} Returns the result from the superclass' method
4456 callParent: function(args) {
4457 var method = this.callParent.caller,
4458 parentClass, methodName;
4460 if (!method.$owner) {
4461 if (!method.caller) {
4463 sourceClass: Ext.getClassName(this),
4464 sourceMethod: "callParent",
4465 msg: "Attempting to call a protected method from the public scope, which is not allowed"
4469 method = method.caller;
4472 parentClass = method.$owner.superclass;
4473 methodName = method.$name;
4475 if (!(methodName in parentClass)) {
4477 sourceClass: Ext.getClassName(this),
4478 sourceMethod: methodName,
4479 msg: "this.callParent() was called but there's no such method (" + methodName +
4480 ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
4484 return parentClass[methodName].apply(this, args || []);
4489 * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
4490 * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
4491 * `this` points to during run-time
4493 Ext.define('My.Cat', {
4496 speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
4499 constructor: function() {
4500 var statics = this.statics();
4502 alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
4503 // equivalent to: My.Cat.speciesName
4505 alert(this.self.speciesName); // dependent on 'this'
4507 statics.totalCreated++;
4513 var cloned = new this.self; // dependent on 'this'
4515 cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
4522 Ext.define('My.SnowLeopard', {
4526 speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
4529 constructor: function() {
4534 var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
4536 var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
4538 var clone = snowLeopard.clone();
4539 alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
4540 alert(clone.groupName); // alerts 'Cat'
4542 alert(My.Cat.totalCreated); // alerts 3
4548 statics: function() {
4549 var method = this.statics.caller,
4556 return method.$owner;
4560 * Call the original method that was previously overridden with {@link Ext.Base#override}
4562 Ext.define('My.Cat', {
4563 constructor: function() {
4564 alert("I'm a cat!");
4571 constructor: function() {
4572 alert("I'm going to be a cat!");
4574 var instance = this.callOverridden();
4576 alert("Meeeeoooowwww");
4582 var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4583 // alerts "I'm a cat!"
4584 // alerts "Meeeeoooowwww"
4586 * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
4587 * @return {Mixed} Returns the result after calling the overridden method
4590 callOverridden: function(args) {
4591 var method = this.callOverridden.caller;
4593 if (!method.$owner) {
4595 sourceClass: Ext.getClassName(this),
4596 sourceMethod: "callOverridden",
4597 msg: "Attempting to call a protected method from the public scope, which is not allowed"
4601 if (!method.$previous) {
4603 sourceClass: Ext.getClassName(this),
4604 sourceMethod: "callOverridden",
4605 msg: "this.callOverridden was called in '" + method.$name +
4606 "' but this method has never been overridden"
4610 return method.$previous.apply(this, args || []);
4613 destroy: function() {}
4616 // These static properties will be copied to every newly created class with {@link Ext#define}
4617 Ext.apply(Ext.Base, {
4619 * Create a new instance of this Class.
4620 Ext.define('My.cool.Class', {
4624 My.cool.Class.create({
4632 create: function() {
4633 return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
4639 own: flexSetter(function(name, value) {
4640 if (typeof value === 'function') {
4641 this.ownMethod(name, value);
4644 this.prototype[name] = value;
4651 ownMethod: function(name, fn) {
4654 if (fn.$owner !== undefined && fn !== Ext.emptyFn) {
4658 return originalFn.apply(this, arguments);
4663 className = Ext.getClassName(this);
4665 fn.displayName = className + '#' + name;
4670 this.prototype[name] = fn;
4674 * Add / override static properties of this class.
4676 Ext.define('My.cool.Class', {
4680 My.cool.Class.addStatics({
4681 someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
4682 method1: function() { ... }, // My.cool.Class.method1 = function() { ... };
4683 method2: function() { ... } // My.cool.Class.method2 = function() { ... };
4686 * @property addStatics
4689 * @param {Object} members
4692 addStatics: function(members) {
4693 for (var name in members) {
4694 if (members.hasOwnProperty(name)) {
4695 this[name] = members[name];
4703 * Add methods / properties to the prototype of this class.
4705 Ext.define('My.awesome.Cat', {
4706 constructor: function() {
4711 My.awesome.Cat.implement({
4717 var kitty = new My.awesome.Cat;
4720 * @property implement
4723 * @param {Object} members
4726 implement: function(members) {
4727 var prototype = this.prototype,
4728 name, i, member, previous;
4729 var className = Ext.getClassName(this);
4730 for (name in members) {
4731 if (members.hasOwnProperty(name)) {
4732 member = members[name];
4734 if (typeof member === 'function') {
4735 member.$owner = this;
4736 member.$name = name;
4738 member.displayName = className + '#' + name;
4742 prototype[name] = member;
4746 if (Ext.enumerables) {
4747 var enumerables = Ext.enumerables;
4749 for (i = enumerables.length; i--;) {
4750 name = enumerables[i];
4752 if (members.hasOwnProperty(name)) {
4753 member = members[name];
4754 member.$owner = this;
4755 member.$name = name;
4756 prototype[name] = member;
4763 * Borrow another class' members to the prototype of this class.
4765 Ext.define('Bank', {
4767 printMoney: function() {
4772 Ext.define('Thief', {
4776 Thief.borrow(Bank, ['money', 'printMoney']);
4778 var steve = new Thief();
4780 alert(steve.money); // alerts '$$$'
4781 steve.printMoney(); // alerts '$$$$$$$'
4786 * @param {Ext.Base} fromClass The class to borrow members from
4787 * @param {Array/String} members The names of the members to borrow
4788 * @return {Ext.Base} this
4791 borrow: function(fromClass, members) {
4792 var fromPrototype = fromClass.prototype,
4795 members = Ext.Array.from(members);
4797 for (i = 0, ln = members.length; i < ln; i++) {
4798 member = members[i];
4800 this.own(member, fromPrototype[member]);
4807 * Override prototype members of this class. Overridden methods can be invoked via
4808 * {@link Ext.Base#callOverridden}
4810 Ext.define('My.Cat', {
4811 constructor: function() {
4812 alert("I'm a cat!");
4819 constructor: function() {
4820 alert("I'm going to be a cat!");
4822 var instance = this.callOverridden();
4824 alert("Meeeeoooowwww");
4830 var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
4831 // alerts "I'm a cat!"
4832 // alerts "Meeeeoooowwww"
4834 * @property override
4837 * @param {Object} members
4838 * @return {Ext.Base} this
4841 override: function(members) {
4842 var prototype = this.prototype,
4843 name, i, member, previous;
4845 for (name in members) {
4846 if (members.hasOwnProperty(name)) {
4847 member = members[name];
4849 if (typeof member === 'function') {
4850 if (typeof prototype[name] === 'function') {
4851 previous = prototype[name];
4852 member.$previous = previous;
4855 this.ownMethod(name, member);
4858 prototype[name] = member;
4863 if (Ext.enumerables) {
4864 var enumerables = Ext.enumerables;
4866 for (i = enumerables.length; i--;) {
4867 name = enumerables[i];
4869 if (members.hasOwnProperty(name)) {
4870 if (prototype[name] !== undefined) {
4871 previous = prototype[name];
4872 members[name].$previous = previous;
4875 this.ownMethod(name, members[name]);
4884 * Used internally by the mixins pre-processor
4887 mixin: flexSetter(function(name, cls) {
4888 var mixin = cls.prototype,
4889 my = this.prototype,
4893 if (mixin.hasOwnProperty(i)) {
4894 if (my[i] === undefined) {
4895 if (typeof mixin[i] === 'function') {
4898 if (fn.$owner === undefined) {
4899 this.ownMethod(i, fn);
4909 else if (i === 'config' && my.config && mixin.config) {
4910 Ext.Object.merge(my.config, mixin.config);
4915 if (my.mixins === undefined) {
4919 my.mixins[name] = mixin;
4923 * Get the current class' name in string format.
4925 Ext.define('My.cool.Class', {
4926 constructor: function() {
4927 alert(this.self.getName()); // alerts 'My.cool.Class'
4931 My.cool.Class.getName(); // 'My.cool.Class'
4933 * @return {String} className
4936 getName: function() {
4937 return Ext.getClassName(this);
4941 * Create aliases for existing prototype methods. Example:
4943 Ext.define('My.cool.Class', {
4944 method1: function() { ... },
4945 method2: function() { ... }
4948 var test = new My.cool.Class();
4950 My.cool.Class.createAlias({
4955 test.method3(); // test.method1()
4957 My.cool.Class.createAlias('method5', 'method3');
4959 test.method5(); // test.method3() -> test.method1()
4961 * @property createAlias
4964 * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
4965 * {@link Ext.Function#flexSetter flexSetter}
4966 * @param {String/Object} origin The original method name
4969 createAlias: flexSetter(function(alias, origin) {
4970 this.prototype[alias] = this.prototype[origin];
4974 })(Ext.Function.flexSetter);
4977 * @author Jacky Nguyen <jacky@sencha.com>
4978 * @docauthor Jacky Nguyen <jacky@sencha.com>
4981 * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
4982 * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
4983 * to enable namespacing and dynamic dependency resolution.
4987 * Ext.define(className, properties);
4989 * in which `properties` is an object represent a collection of properties that apply to the class. See
4990 * {@link Ext.ClassManager#create} for more detailed instructions.
4992 * Ext.define('Person', {
4995 * constructor: function(name) {
5003 * eat: function(foodType) {
5004 * alert("I'm eating: " + foodType);
5010 * var aaron = new Person("Aaron");
5011 * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
5013 * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
5014 * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
5018 * Ext.define('Developer', {
5021 * constructor: function(name, isGeek) {
5022 * this.isGeek = isGeek;
5024 * // Apply a method from the parent class' prototype
5025 * this.callParent([name]);
5031 * code: function(language) {
5032 * alert("I'm coding in: " + language);
5040 * var jacky = new Developer("Jacky", true);
5041 * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
5042 * // alert("I'm eating: Bugs");
5044 * See {@link Ext.Base#callParent} for more details on calling superclass' methods
5048 * Ext.define('CanPlayGuitar', {
5049 * playGuitar: function() {
5050 * alert("F#...G...D...A");
5054 * Ext.define('CanComposeSongs', {
5055 * composeSongs: function() { ... }
5058 * Ext.define('CanSing', {
5059 * sing: function() {
5060 * alert("I'm on the highway to hell...")
5064 * Ext.define('Musician', {
5068 * canPlayGuitar: 'CanPlayGuitar',
5069 * canComposeSongs: 'CanComposeSongs',
5070 * canSing: 'CanSing'
5074 * Ext.define('CoolPerson', {
5078 * canPlayGuitar: 'CanPlayGuitar',
5079 * canSing: 'CanSing'
5082 * sing: function() {
5083 * alert("Ahem....");
5085 * this.mixins.canSing.sing.call(this);
5087 * alert("[Playing guitar at the same time...]");
5089 * this.playGuitar();
5093 * var me = new CoolPerson("Jacky");
5095 * me.sing(); // alert("Ahem...");
5096 * // alert("I'm on the highway to hell...");
5097 * // alert("[Playing guitar at the same time...]");
5098 * // alert("F#...G...D...A");
5102 * Ext.define('SmartPhone', {
5104 * hasTouchScreen: false,
5105 * operatingSystem: 'Other',
5109 * isExpensive: false,
5111 * constructor: function(config) {
5112 * this.initConfig(config);
5117 * applyPrice: function(price) {
5118 * this.isExpensive = (price > 500);
5123 * applyOperatingSystem: function(operatingSystem) {
5124 * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
5128 * return operatingSystem;
5132 * var iPhone = new SmartPhone({
5133 * hasTouchScreen: true,
5134 * operatingSystem: 'iOS'
5137 * iPhone.getPrice(); // 500;
5138 * iPhone.getOperatingSystem(); // 'iOS'
5139 * iPhone.getHasTouchScreen(); // true;
5140 * iPhone.hasTouchScreen(); // true
5142 * iPhone.isExpensive; // false;
5143 * iPhone.setPrice(600);
5144 * iPhone.getPrice(); // 600
5145 * iPhone.isExpensive; // true;
5147 * iPhone.setOperatingSystem('AlienOS');
5148 * iPhone.getOperatingSystem(); // 'Other'
5152 * Ext.define('Computer', {
5154 * factory: function(brand) {
5155 * // 'this' in static methods refer to the class itself
5156 * return new this(brand);
5160 * constructor: function() { ... }
5163 * var dellComputer = Computer.factory('Dell');
5165 * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
5166 * static properties within class methods
5173 baseStaticProperties = [],
5176 for (baseStaticProperty in Base) {
5177 if (Base.hasOwnProperty(baseStaticProperty)) {
5178 baseStaticProperties.push(baseStaticProperty);
5184 * @param {Object} classData An object represent the properties of this class
5185 * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
5186 * Note that the creation process can be asynchronous depending on the pre-processors used.
5187 * @return {Ext.Base} The newly created class
5189 Ext.Class = Class = function(newClass, classData, onClassCreated) {
5190 if (typeof newClass !== 'function') {
5191 onClassCreated = classData;
5192 classData = newClass;
5193 newClass = function() {
5194 return this.constructor.apply(this, arguments);
5202 var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
5203 registeredPreprocessors = Class.getPreprocessors(),
5206 preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
5208 for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
5209 staticPropertyName = baseStaticProperties[i];
5210 newClass[staticPropertyName] = Base[staticPropertyName];
5213 delete classData.preprocessors;
5215 for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
5216 preprocessor = preprocessorStack[j];
5218 if (typeof preprocessor === 'string') {
5219 preprocessor = registeredPreprocessors[preprocessor];
5221 if (!preprocessor.always) {
5222 if (classData.hasOwnProperty(preprocessor.name)) {
5223 preprocessors.push(preprocessor.fn);
5227 preprocessors.push(preprocessor.fn);
5231 preprocessors.push(preprocessor);
5235 classData.onClassCreated = onClassCreated;
5237 classData.onBeforeClassCreated = function(cls, data) {
5238 onClassCreated = data.onClassCreated;
5240 delete data.onBeforeClassCreated;
5241 delete data.onClassCreated;
5243 cls.implement(data);
5245 if (onClassCreated) {
5246 onClassCreated.call(cls, cls);
5250 process = function(cls, data) {
5251 preprocessor = preprocessors[index++];
5253 if (!preprocessor) {
5254 data.onBeforeClassCreated.apply(this, arguments);
5258 if (preprocessor.call(this, cls, data, process) !== false) {
5259 process.apply(this, arguments);
5263 process.call(Class, newClass, classData);
5274 * Register a new pre-processor to be used during the class creation process
5276 * @member Ext.Class registerPreprocessor
5277 * @param {String} name The pre-processor's name
5278 * @param {Function} fn The callback function to be executed. Typical format:
5280 function(cls, data, fn) {
5283 // Execute this when the processing is finished.
5284 // Asynchronous processing is perfectly ok
5286 fn.call(this, cls, data);
5290 * Passed arguments for this function are:
5292 * - `{Function} cls`: The created class
5293 * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
5294 * - `{Function} fn`: The callback function that <b>must</b> to be executed when this pre-processor finishes,
5295 * regardless of whether the processing is synchronous or aynchronous
5297 * @return {Ext.Class} this
5300 registerPreprocessor: function(name, fn, always) {
5301 this.preprocessors[name] = {
5303 always: always || false,
5311 * Retrieve a pre-processor callback function by its name, which has been registered before
5313 * @param {String} name
5314 * @return {Function} preprocessor
5316 getPreprocessor: function(name) {
5317 return this.preprocessors[name];
5320 getPreprocessors: function() {
5321 return this.preprocessors;
5325 * Retrieve the array stack of default pre-processors
5327 * @return {Function} defaultPreprocessors
5329 getDefaultPreprocessors: function() {
5330 return this.defaultPreprocessors || [];
5334 * Set the default array stack of default pre-processors
5336 * @param {Array} preprocessors
5337 * @return {Ext.Class} this
5339 setDefaultPreprocessors: function(preprocessors) {
5340 this.defaultPreprocessors = Ext.Array.from(preprocessors);
5346 * Insert this pre-processor at a specific position in the stack, optionally relative to
5347 * any existing pre-processor. For example:
5349 Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
5353 fn.call(this, cls, data);
5355 }).insertDefaultPreprocessor('debug', 'last');
5357 * @param {String} name The pre-processor name. Note that it needs to be registered with
5358 * {@link Ext#registerPreprocessor registerPreprocessor} before this
5359 * @param {String} offset The insertion position. Four possible values are:
5360 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
5361 * @param {String} relativeName
5362 * @return {Ext.Class} this
5365 setDefaultPreprocessorPosition: function(name, offset, relativeName) {
5366 var defaultPreprocessors = this.defaultPreprocessors,
5369 if (typeof offset === 'string') {
5370 if (offset === 'first') {
5371 defaultPreprocessors.unshift(name);
5375 else if (offset === 'last') {
5376 defaultPreprocessors.push(name);
5381 offset = (offset === 'after') ? 1 : -1;
5384 index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
5387 defaultPreprocessors.splice(Math.max(0, index + offset), 0, name);
5394 Class.registerPreprocessor('extend', function(cls, data) {
5395 var extend = data.extend,
5397 basePrototype = base.prototype,
5398 prototype = function() {},
5399 parent, i, k, ln, staticName, parentStatics,
5400 parentPrototype, clsPrototype;
5402 if (extend && extend !== Object) {
5409 parentPrototype = parent.prototype;
5411 prototype.prototype = parentPrototype;
5412 clsPrototype = cls.prototype = new prototype();
5414 if (!('$class' in parent)) {
5415 for (i in basePrototype) {
5416 if (!parentPrototype[i]) {
5417 parentPrototype[i] = basePrototype[i];
5422 clsPrototype.self = cls;
5424 cls.superclass = clsPrototype.superclass = parentPrototype;
5428 // Statics inheritance
5429 parentStatics = parentPrototype.$inheritableStatics;
5431 if (parentStatics) {
5432 for (k = 0, ln = parentStatics.length; k < ln; k++) {
5433 staticName = parentStatics[k];
5435 if (!cls.hasOwnProperty(staticName)) {
5436 cls[staticName] = parent[staticName];
5441 // Merge the parent class' config object without referencing it
5442 if (parentPrototype.config) {
5443 clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
5446 clsPrototype.config = {};
5449 if (clsPrototype.$onExtended) {
5450 clsPrototype.$onExtended.call(cls, cls, data);
5453 if (data.onClassExtended) {
5454 clsPrototype.$onExtended = data.onClassExtended;
5455 delete data.onClassExtended;
5460 Class.registerPreprocessor('statics', function(cls, data) {
5461 var statics = data.statics,
5464 for (name in statics) {
5465 if (statics.hasOwnProperty(name)) {
5466 cls[name] = statics[name];
5470 delete data.statics;
5473 Class.registerPreprocessor('inheritableStatics', function(cls, data) {
5474 var statics = data.inheritableStatics,
5476 prototype = cls.prototype,
5479 inheritableStatics = prototype.$inheritableStatics;
5481 if (!inheritableStatics) {
5482 inheritableStatics = prototype.$inheritableStatics = [];
5485 for (name in statics) {
5486 if (statics.hasOwnProperty(name)) {
5487 cls[name] = statics[name];
5488 inheritableStatics.push(name);
5492 delete data.inheritableStatics;
5495 Class.registerPreprocessor('mixins', function(cls, data) {
5496 cls.mixin(data.mixins);
5501 Class.registerPreprocessor('config', function(cls, data) {
5502 var prototype = cls.prototype;
5504 Ext.Object.each(data.config, function(name) {
5505 var cName = name.charAt(0).toUpperCase() + name.substr(1),
5507 apply = 'apply' + cName,
5508 setter = 'set' + cName,
5509 getter = 'get' + cName;
5511 if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
5512 data[apply] = function(val) {
5517 if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
5518 data[setter] = function(val) {
5519 var ret = this[apply].call(this, val, this[pName]);
5521 if (ret !== undefined) {
5529 if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
5530 data[getter] = function() {
5536 Ext.Object.merge(prototype.config, data.config);
5540 Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
5542 // Backwards compatible
5543 Ext.extend = function(subclass, superclass, members) {
5544 if (arguments.length === 2 && Ext.isObject(superclass)) {
5545 members = superclass;
5546 superclass = subclass;
5553 Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
5556 members.extend = superclass;
5557 members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
5560 cls = new Class(subclass, members);
5563 cls = new Class(members);
5566 cls.prototype.override = function(o) {
5568 if (o.hasOwnProperty(m)) {
5580 * @author Jacky Nguyen <jacky@sencha.com>
5581 * @docauthor Jacky Nguyen <jacky@sencha.com>
5582 * @class Ext.ClassManager
5584 Ext.ClassManager manages all classes and handles mapping from string class name to
5585 actual class objects throughout the whole framework. It is not generally accessed directly, rather through
5586 these convenient shorthands:
5588 - {@link Ext#define Ext.define}
5589 - {@link Ext#create Ext.create}
5590 - {@link Ext#widget Ext.widget}
5591 - {@link Ext#getClass Ext.getClass}
5592 - {@link Ext#getClassName Ext.getClassName}
5597 (function(Class, alias) {
5599 var slice = Array.prototype.slice;
5601 var Manager = Ext.ClassManager = {
5606 * All classes which were defined through the ClassManager. Keys are the
5607 * name of the classes and the values are references to the classes.
5620 namespaceRewrites: [{
5629 alternateToName: {},
5635 enableNamespaceParseCache: true,
5638 namespaceParseCache: {},
5644 instantiationCounts: {},
5647 * Checks if a class has already been created.
5649 * @param {String} className
5650 * @return {Boolean} exist
5652 isCreated: function(className) {
5653 var i, ln, part, root, parts;
5655 if (typeof className !== 'string' || className.length < 1) {
5657 sourceClass: "Ext.ClassManager",
5658 sourceMethod: "exist",
5659 msg: "Invalid classname, must be a string and must not be empty"
5663 if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
5668 parts = this.parseNamespace(className);
5670 for (i = 0, ln = parts.length; i < ln; i++) {
5673 if (typeof part !== 'string') {
5676 if (!root || !root[part]) {
5684 Ext.Loader.historyPush(className);
5686 this.existCache[className] = true;
5692 * Supports namespace rewriting
5695 parseNamespace: function(namespace) {
5696 if (typeof namespace !== 'string') {
5698 sourceClass: "Ext.ClassManager",
5699 sourceMethod: "parseNamespace",
5700 msg: "Invalid namespace, must be a string"
5704 var cache = this.namespaceParseCache;
5706 if (this.enableNamespaceParseCache) {
5707 if (cache.hasOwnProperty(namespace)) {
5708 return cache[namespace];
5713 rewrites = this.namespaceRewrites,
5714 rewrite, from, to, i, ln, root = Ext.global;
5716 for (i = 0, ln = rewrites.length; i < ln; i++) {
5717 rewrite = rewrites[i];
5718 from = rewrite.from;
5721 if (namespace === from || namespace.substring(0, from.length) === from) {
5722 namespace = namespace.substring(from.length);
5724 if (typeof to !== 'string') {
5727 parts = parts.concat(to.split('.'));
5736 parts = parts.concat(namespace.split('.'));
5738 if (this.enableNamespaceParseCache) {
5739 cache[namespace] = parts;
5746 * Creates a namespace and assign the `value` to the created object
5748 Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
5750 alert(MyCompany.pkg.Example === someObject); // alerts true
5752 * @param {String} name
5753 * @param {Mixed} value
5756 setNamespace: function(name, value) {
5757 var root = Ext.global,
5758 parts = this.parseNamespace(name),
5762 for (i = 0, ln = parts.length; i < ln; i++) {
5765 if (typeof part !== 'string') {
5782 * The new Ext.ns, supports namespace rewriting
5785 createNamespaces: function() {
5786 var root = Ext.global,
5787 parts, part, i, j, ln, subLn;
5789 for (i = 0, ln = arguments.length; i < ln; i++) {
5790 parts = this.parseNamespace(arguments[i]);
5792 for (j = 0, subLn = parts.length; j < subLn; j++) {
5795 if (typeof part !== 'string') {
5811 * Sets a name reference to a class.
5813 * @param {String} name
5814 * @param {Object} value
5815 * @return {Ext.ClassManager} this
5817 set: function(name, value) {
5818 var targetName = this.getName(value);
5820 this.classes[name] = this.setNamespace(name, value);
5822 if (targetName && targetName !== name) {
5823 this.maps.alternateToName[name] = targetName;
5830 * Retrieve a class by its name.
5832 * @param {String} name
5833 * @return {Class} class
5835 get: function(name) {
5836 if (this.classes.hasOwnProperty(name)) {
5837 return this.classes[name];
5840 var root = Ext.global,
5841 parts = this.parseNamespace(name),
5844 for (i = 0, ln = parts.length; i < ln; i++) {
5847 if (typeof part !== 'string') {
5850 if (!root || !root[part]) {
5862 * Register the alias for a class.
5864 * @param {Class/String} cls a reference to a class or a className
5865 * @param {String} alias Alias to use when referring to this class
5867 setAlias: function(cls, alias) {
5868 var aliasToNameMap = this.maps.aliasToName,
5869 nameToAliasesMap = this.maps.nameToAliases,
5872 if (typeof cls === 'string') {
5875 className = this.getName(cls);
5878 if (alias && aliasToNameMap[alias] !== className) {
5879 if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
5880 Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
5881 "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
5884 aliasToNameMap[alias] = className;
5887 if (!nameToAliasesMap[className]) {
5888 nameToAliasesMap[className] = [];
5892 Ext.Array.include(nameToAliasesMap[className], alias);
5899 * Get a reference to the class by its alias.
5901 * @param {String} alias
5902 * @return {Class} class
5904 getByAlias: function(alias) {
5905 return this.get(this.getNameByAlias(alias));
5909 * Get the name of a class by its alias.
5911 * @param {String} alias
5912 * @return {String} className
5914 getNameByAlias: function(alias) {
5915 return this.maps.aliasToName[alias] || '';
5919 * Get the name of a class by its alternate name.
5921 * @param {String} alternate
5922 * @return {String} className
5924 getNameByAlternate: function(alternate) {
5925 return this.maps.alternateToName[alternate] || '';
5929 * Get the aliases of a class by the class name
5931 * @param {String} name
5932 * @return {Array} aliases
5934 getAliasesByName: function(name) {
5935 return this.maps.nameToAliases[name] || [];
5939 * Get the name of the class by its reference or its instance;
5940 * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
5942 Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
5944 * @param {Class/Object} object
5945 * @return {String} className
5948 getName: function(object) {
5949 return object && object.$className || '';
5953 * Get the class of the provided object; returns null if it's not an instance
5954 * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}
5956 var component = new Ext.Component();
5958 Ext.ClassManager.getClass(component); // returns Ext.Component
5960 * @param {Object} object
5961 * @return {Class} class
5964 getClass: function(object) {
5965 return object && object.self || null;
5969 * Defines a class. This is usually invoked via the alias {@link Ext#define Ext.define}
5971 Ext.ClassManager.create('My.awesome.Class', {
5972 someProperty: 'something',
5973 someMethod: function() { ... }
5978 alert(this === My.awesome.Class); // alerts true
5980 var myInstance = new this();
5983 * @param {String} className The class name to create in string dot-namespaced format, for example:
5984 * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
5985 * It is highly recommended to follow this simple convention:
5987 - The root and the class name are 'CamelCased'
5988 - Everything else is lower-cased
5990 * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
5991 * strings, except those in the reserved listed below:
5999 - `alternateClassName`
6001 * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
6002 * (`this`) will be the newly created class itself.
6003 * @return {Ext.Base}
6006 create: function(className, data, createdFn) {
6009 if (typeof className !== 'string') {
6012 sourceMethod: "define",
6013 msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
6017 data.$className = className;
6019 return new Class(data, function() {
6020 var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
6021 registeredPostprocessors = manager.postprocessors,
6023 postprocessors = [],
6024 postprocessor, postprocessors, process, i, ln;
6026 delete data.postprocessors;
6028 for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
6029 postprocessor = postprocessorStack[i];
6031 if (typeof postprocessor === 'string') {
6032 postprocessor = registeredPostprocessors[postprocessor];
6034 if (!postprocessor.always) {
6035 if (data[postprocessor.name] !== undefined) {
6036 postprocessors.push(postprocessor.fn);
6040 postprocessors.push(postprocessor.fn);
6044 postprocessors.push(postprocessor);
6048 process = function(clsName, cls, clsData) {
6049 postprocessor = postprocessors[index++];
6051 if (!postprocessor) {
6052 manager.set(className, cls);
6054 Ext.Loader.historyPush(className);
6057 createdFn.call(cls, cls);
6063 if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
6064 process.apply(this, arguments);
6068 process.call(manager, className, this, data);
6073 * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
6074 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6075 * attempt to load the class via synchronous loading.
6077 var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
6079 * @param {String} alias
6080 * @param {Mixed} args,... Additional arguments after the alias will be passed to the
6081 * class constructor.
6082 * @return {Object} instance
6085 instantiateByAlias: function() {
6086 var alias = arguments[0],
6087 args = slice.call(arguments),
6088 className = this.getNameByAlias(alias);
6091 className = this.maps.aliasToName[alias];
6096 sourceMethod: "createByAlias",
6097 msg: "Cannot create an instance of unrecognized alias: " + alias
6101 if (Ext.global.console) {
6102 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
6103 "Ext.require('" + alias + "') above Ext.onReady");
6106 Ext.syncRequire(className);
6109 args[0] = className;
6111 return this.instantiate.apply(this, args);
6115 * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
6116 * shorthand {@link Ext#create Ext.create}
6118 * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
6119 * attempt to load the class via synchronous loading.
6121 * For example, all these three lines return the same result:
6124 var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
6127 var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
6130 var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
6132 * @param {String} name
6133 * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
6134 * @return {Object} instance
6137 instantiate: function() {
6138 var name = arguments[0],
6139 args = slice.call(arguments, 1),
6143 if (typeof name !== 'function') {
6144 if ((typeof name !== 'string' || name.length < 1)) {
6147 sourceMethod: "create",
6148 msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
6152 cls = this.get(name);
6158 // No record of this class name, it's possibly an alias, so look it up
6160 possibleName = this.getNameByAlias(name);
6163 name = possibleName;
6165 cls = this.get(name);
6169 // Still no record of this class name, it's possibly an alternate name, so look it up
6171 possibleName = this.getNameByAlternate(name);
6174 name = possibleName;
6176 cls = this.get(name);
6180 // Still not existing at this point, try to load it via synchronous mode as the last resort
6182 if (Ext.global.console) {
6183 Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
6184 "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
6187 Ext.syncRequire(name);
6189 cls = this.get(name);
6195 sourceMethod: "create",
6196 msg: "Cannot create an instance of unrecognized class name / alias: " + alias
6200 if (typeof cls !== 'function') {
6203 sourceMethod: "create",
6204 msg: "'" + name + "' is a singleton and cannot be instantiated"
6208 if (!this.instantiationCounts[name]) {
6209 this.instantiationCounts[name] = 0;
6212 this.instantiationCounts[name]++;
6214 return this.getInstantiator(args.length)(cls, args);
6222 dynInstantiate: function(name, args) {
6223 args = Ext.Array.from(args, true);
6226 return this.instantiate.apply(this, args);
6233 getInstantiator: function(length) {
6234 if (!this.instantiators[length]) {
6238 for (i = 0; i < length; i++) {
6239 args.push('a['+i+']');
6242 this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
6245 return this.instantiators[length];
6256 defaultPostprocessors: [],
6259 * Register a post-processor function.
6261 * @param {String} name
6262 * @param {Function} postprocessor
6264 registerPostprocessor: function(name, fn, always) {
6265 this.postprocessors[name] = {
6267 always: always || false,
6275 * Set the default post processors array stack which are applied to every class.
6277 * @param {String/Array} The name of a registered post processor or an array of registered names.
6278 * @return {Ext.ClassManager} this
6280 setDefaultPostprocessors: function(postprocessors) {
6281 this.defaultPostprocessors = Ext.Array.from(postprocessors);
6287 * Insert this post-processor at a specific position in the stack, optionally relative to
6288 * any existing post-processor
6290 * @param {String} name The post-processor name. Note that it needs to be registered with
6291 * {@link Ext.ClassManager#registerPostprocessor} before this
6292 * @param {String} offset The insertion position. Four possible values are:
6293 * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
6294 * @param {String} relativeName
6295 * @return {Ext.ClassManager} this
6297 setDefaultPostprocessorPosition: function(name, offset, relativeName) {
6298 var defaultPostprocessors = this.defaultPostprocessors,
6301 if (typeof offset === 'string') {
6302 if (offset === 'first') {
6303 defaultPostprocessors.unshift(name);
6307 else if (offset === 'last') {
6308 defaultPostprocessors.push(name);
6313 offset = (offset === 'after') ? 1 : -1;
6316 index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
6319 defaultPostprocessors.splice(Math.max(0, index + offset), 0, name);
6326 * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
6327 * or class names. Expressions support wildcards:
6329 // returns ['Ext.window.Window']
6330 var window = Ext.ClassManager.getNamesByExpression('widget.window');
6332 // returns ['widget.panel', 'widget.window', ...]
6333 var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
6335 // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
6336 var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
6338 * @param {String} expression
6339 * @return {Array} classNames
6342 getNamesByExpression: function(expression) {
6343 var nameToAliasesMap = this.maps.nameToAliases,
6345 name, alias, aliases, possibleName, regex, i, ln;
6347 if (typeof expression !== 'string' || expression.length < 1) {
6349 sourceClass: "Ext.ClassManager",
6350 sourceMethod: "getNamesByExpression",
6351 msg: "Expression " + expression + " is invalid, must be a non-empty string"
6355 if (expression.indexOf('*') !== -1) {
6356 expression = expression.replace(/\*/g, '(.*?)');
6357 regex = new RegExp('^' + expression + '$');
6359 for (name in nameToAliasesMap) {
6360 if (nameToAliasesMap.hasOwnProperty(name)) {
6361 aliases = nameToAliasesMap[name];
6363 if (name.search(regex) !== -1) {
6367 for (i = 0, ln = aliases.length; i < ln; i++) {
6370 if (alias.search(regex) !== -1) {
6380 possibleName = this.getNameByAlias(expression);
6383 names.push(possibleName);
6385 possibleName = this.getNameByAlternate(expression);
6388 names.push(possibleName);
6390 names.push(expression);
6399 Manager.registerPostprocessor('alias', function(name, cls, data) {
6400 var aliases = data.alias,
6401 widgetPrefix = 'widget.',
6404 if (!(aliases instanceof Array)) {
6405 aliases = [aliases];
6408 for (i = 0, ln = aliases.length; i < ln; i++) {
6411 if (typeof alias !== 'string') {
6414 sourceMethod: "define",
6415 msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
6419 this.setAlias(cls, alias);
6422 // This is ugly, will change to make use of parseNamespace for alias later on
6423 for (i = 0, ln = aliases.length; i < ln; i++) {
6426 if (alias.substring(0, widgetPrefix.length) === widgetPrefix) {
6427 // Only the first alias with 'widget.' prefix will be used for xtype
6428 cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length);
6434 Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
6435 fn.call(this, name, new cls(), data);
6439 Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
6440 var alternates = data.alternateClassName,
6443 if (!(alternates instanceof Array)) {
6444 alternates = [alternates];
6447 for (i = 0, ln = alternates.length; i < ln; i++) {
6448 alternate = alternates[i];
6450 if (typeof alternate !== 'string') {
6453 sourceMethod: "define",
6454 msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
6458 this.set(alternate, cls);
6462 Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
6466 * Convenient shorthand, see {@link Ext.ClassManager#instantiate}
6470 create: alias(Manager, 'instantiate'),
6474 * API to be stablized
6476 * @param {Mixed} item
6477 * @param {String} namespace
6479 factory: function(item, namespace) {
6480 if (item instanceof Array) {
6483 for (i = 0, ln = item.length; i < ln; i++) {
6484 item[i] = Ext.factory(item[i], namespace);
6490 var isString = (typeof item === 'string');
6492 if (isString || (item instanceof Object && item.constructor === Object)) {
6493 var name, config = {};
6499 name = item.className;
6501 delete config.className;
6504 if (namespace !== undefined && name.indexOf(namespace) === -1) {
6505 name = namespace + '.' + Ext.String.capitalize(name);
6508 return Ext.create(name, config);
6511 if (typeof item === 'function') {
6512 return Ext.create(item);
6519 * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
6521 var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
6522 var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
6528 widget: function(name) {
6529 var args = slice.call(arguments);
6530 args[0] = 'widget.' + name;
6532 return Manager.instantiateByAlias.apply(Manager, args);
6536 * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}
6538 * @method createByAlias
6540 createByAlias: alias(Manager, 'instantiateByAlias'),
6543 * Convenient shorthand for {@link Ext.ClassManager#create}, see detailed {@link Ext.Class explanation}
6547 define: alias(Manager, 'create'),
6550 * Convenient shorthand, see {@link Ext.ClassManager#getName}
6552 * @method getClassName
6554 getClassName: alias(Manager, 'getName'),
6558 * @param {Mixed} object
6560 getDisplayName: function(object) {
6561 if (object.displayName) {
6562 return object.displayName;
6565 if (object.$name && object.$class) {
6566 return Ext.getClassName(object.$class) + '#' + object.$name;
6569 if (object.$className) {
6570 return object.$className;
6577 * Convenient shorthand, see {@link Ext.ClassManager#getClass}
6579 * @method getClassName
6581 getClass: alias(Manager, 'getClass'),
6584 * Creates namespaces to be used for scoping variables and classes so that they are not global.
6585 * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
6587 Ext.namespace('Company', 'Company.data');
6589 // equivalent and preferable to the above syntax
6590 Ext.namespace('Company.data');
6592 Company.Widget = function() { ... };
6594 Company.data.CustomStore = function(config) { ... };
6596 * @param {String} namespace1
6597 * @param {String} namespace2
6598 * @param {String} etc
6599 * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
6605 namespace: alias(Manager, 'createNamespaces')
6608 Ext.createWidget = Ext.widget;
6611 * Convenient alias for {@link Ext#namespace Ext.namespace}
6615 Ext.ns = Ext.namespace;
6617 Class.registerPreprocessor('className', function(cls, data) {
6618 if (data.$className) {
6619 cls.$className = data.$className;
6620 cls.displayName = cls.$className;
6624 Class.setDefaultPreprocessorPosition('className', 'first');
6626 })(Ext.Class, Ext.Function.alias);
6629 * @author Jacky Nguyen <jacky@sencha.com>
6630 * @docauthor Jacky Nguyen <jacky@sencha.com>
6634 Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
6635 via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
6636 approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
6638 # Asynchronous Loading #
6642 + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
6644 + Best possible debugging experience: error messages come with the exact file name and line number
6647 + Dependencies need to be specified before-hand
6649 ### Method 1: Explicitly include what you need: ###
6652 Ext.require({String/Array} expressions);
6654 // Example: Single alias
6655 Ext.require('widget.window');
6657 // Example: Single class name
6658 Ext.require('Ext.window.Window');
6660 // Example: Multiple aliases / class names mix
6661 Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
6664 Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
6666 ### Method 2: Explicitly exclude what you don't need: ###
6668 // Syntax: Note that it must be in this chaining format.
6669 Ext.exclude({String/Array} expressions)
6670 .require({String/Array} expressions);
6672 // Include everything except Ext.data.*
6673 Ext.exclude('Ext.data.*').require('*');Â
6675 // Include all widgets except widget.checkbox*,
6676 // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
6677 Ext.exclude('widget.checkbox*').require('widget.*');
6679 # Synchronous Loading on Demand #
6682 + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
6686 + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
6687 + Must be from the same domain due to XHR restriction
6688 + Need a web server, same reason as above
6690 There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
6692 Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
6694 Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
6696 Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
6698 Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
6699 existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
6700 class and all its dependencies.
6702 # Hybrid Loading - The Best of Both Worlds #
6704 It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
6706 ### Step 1: Start writing your application using synchronous approach. Ext.Loader will automatically fetch all
6707 dependencies on demand as they're needed during run-time. For example: ###
6709 Ext.onReady(function(){
6710 var window = Ext.createWidget('window', {
6717 title: 'Hello Dialog',
6719 title: 'Navigation',
6734 ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
6736 [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
6738 [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
6740 Simply copy and paste the suggested code above `Ext.onReady`, i.e:
6742 Ext.require('Ext.window.Window');
6743 Ext.require('Ext.layout.container.Border');
6747 Everything should now load via asynchronous mode.
6751 It's important to note that dynamic loading should only be used during development on your local machines.
6752 During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
6753 the whole process of transitioning from / to between development / maintenance and production as easy as
6754 possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
6755 needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
6756 then include it on top of your application.
6758 This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
6764 (function(Manager, Class, flexSetter, alias) {
6767 dependencyProperties = ['extend', 'mixins', 'requires'],
6770 Loader = Ext.Loader = {
6774 documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
6777 * Flag indicating whether there are still files being loaded
6783 * Maintain the queue for all dependencies. Each item in the array is an object of the format:
6785 * requires: [...], // The required classes for this queue item
6786 * callback: function() { ... } // The function to execute when all classes specified in requires exist
6793 * Maintain the list of files that have already been handled so that they never get double-loaded
6799 * Maintain the list of listeners to execute when all required scripts are fully loaded
6805 * Contains optional dependencies to be loaded last
6808 optionalRequires: [],
6811 * Map of fully qualified class names to an array of dependent classes.
6827 hasFileLoadError: false,
6832 classNameToFilePathMap: {},
6835 * An array of class names to keep track of the dependency loading order.
6836 * This is not guaranteed to be the same everytime due to the asynchronous
6837 * nature of the Loader.
6850 * Whether or not to enable the dynamic dependency loading feature
6852 * @cfg {Boolean} enabled
6857 * @cfg {Boolean} disableCaching
6858 * Appends current timestamp to script files to prevent caching
6861 disableCaching: true,
6864 * @cfg {String} disableCachingParam
6865 * The get parameter name for the cache buster's timestamp.
6868 disableCachingParam: '_dc',
6871 * @cfg {Object} paths
6872 * The mapping from namespaces to file paths
6874 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
6875 // loaded from ./layout/Container.js
6877 'My': './src/my_own_folder' // My.layout.Container will be loaded from
6878 // ./src/my_own_folder/layout/Container.js
6880 * Note that all relative paths are relative to the current HTML document.
6881 * If not being specified, for example, <code>Other.awesome.Class</code>
6882 * will simply be loaded from <code>./Other/awesome/Class.js</code>
6890 * Set the configuration for the loader. This should be called right after ext-core.js
6891 * (or ext-core-debug.js) is included in the page, i.e:
6893 <script type="text/javascript" src="ext-core-debug.js"></script>
6894 <script type="text/javascript">
6895 Ext.Loader.setConfig({
6902 <script type="text/javascript">
6905 Ext.onReady(function() {
6906 // application code here
6910 * Refer to {@link Ext.Loader#configs} for the list of possible properties
6912 * @param {Object} config The config object to override the default values in {@link Ext.Loader#config}
6913 * @return {Ext.Loader} this
6916 setConfig: function(name, value) {
6917 if (Ext.isObject(name) && arguments.length === 1) {
6918 Ext.Object.merge(this.config, name);
6921 this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
6928 * Get the config value corresponding to the specified name. If no name is given, will return the config object
6929 * @param {String} name The config property name
6930 * @return {Object/Mixed}
6932 getConfig: function(name) {
6934 return this.config[name];
6941 * Sets the path of a namespace.
6944 Ext.Loader.setPath('Ext', '.');
6946 * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
6947 * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
6948 * @return {Ext.Loader} this
6951 setPath: flexSetter(function(name, path) {
6952 this.config.paths[name] = path;
6958 * Translates a className to a file path by adding the
6959 * the proper prefix and converting the .'s to /'s. For example:
6961 Ext.Loader.setPath('My', '/path/to/My');
6963 alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
6965 * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
6967 Ext.Loader.setPath({
6968 'My': '/path/to/lib',
6969 'My.awesome': '/other/path/for/awesome/stuff',
6970 'My.awesome.more': '/more/awesome/path'
6973 alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
6975 alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
6977 alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
6979 alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
6981 * @param {String} className
6982 * @return {String} path
6985 getPath: function(className) {
6987 paths = this.config.paths,
6988 prefix = this.getPrefix(className);
6990 if (prefix.length > 0) {
6991 if (prefix === className) {
6992 return paths[prefix];
6995 path = paths[prefix];
6996 className = className.substring(prefix.length + 1);
6999 if (path.length > 0) {
7003 return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
7008 * @param {String} className
7010 getPrefix: function(className) {
7011 var paths = this.config.paths,
7012 prefix, deepestPrefix = '';
7014 if (paths.hasOwnProperty(className)) {
7018 for (prefix in paths) {
7019 if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
7020 if (prefix.length > deepestPrefix.length) {
7021 deepestPrefix = prefix;
7026 return deepestPrefix;
7030 * Refresh all items in the queue. If all dependencies for an item exist during looping,
7031 * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
7035 refreshQueue: function() {
7036 var ln = this.queue.length,
7037 i, item, j, requires;
7040 this.triggerReady();
7044 for (i = 0; i < ln; i++) {
7045 item = this.queue[i];
7048 requires = item.requires;
7050 // Don't bother checking when the number of files loaded
7051 // is still less than the array length
7052 if (requires.length > this.numLoadedFiles) {
7059 if (Manager.isCreated(requires[j])) {
7060 // Take out from the queue
7061 requires.splice(j, 1);
7066 } while (j < requires.length);
7068 if (item.requires.length === 0) {
7069 this.queue.splice(i, 1);
7070 item.callback.call(item.scope);
7071 this.refreshQueue();
7081 * Inject a script element to document's head, call onLoad and onError accordingly
7084 injectScriptElement: function(url, onLoad, onError, scope) {
7085 var script = document.createElement('script'),
7087 onLoadFn = function() {
7088 me.cleanupScriptElement(script);
7091 onErrorFn = function() {
7092 me.cleanupScriptElement(script);
7093 onError.call(scope);
7096 script.type = 'text/javascript';
7098 script.onload = onLoadFn;
7099 script.onerror = onErrorFn;
7100 script.onreadystatechange = function() {
7101 if (this.readyState === 'loaded' || this.readyState === 'complete') {
7106 this.documentHead.appendChild(script);
7114 cleanupScriptElement: function(script) {
7115 script.onload = null;
7116 script.onreadystatechange = null;
7117 script.onerror = null;
7123 * Load a script file, supports both asynchronous and synchronous approaches
7125 * @param {String} url
7126 * @param {Function} onLoad
7127 * @param {Scope} scope
7128 * @param {Boolean} synchronous
7131 loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
7133 noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
7134 fileName = url.split('/').pop(),
7135 isCrossOriginRestricted = false,
7136 xhr, status, onScriptError;
7138 scope = scope || this;
7140 this.isLoading = true;
7143 onScriptError = function() {
7144 onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
7147 if (!Ext.isReady && Ext.onDocumentReady) {
7148 Ext.onDocumentReady(function() {
7149 me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7153 this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
7157 if (typeof XMLHttpRequest !== 'undefined') {
7158 xhr = new XMLHttpRequest();
7160 xhr = new ActiveXObject('Microsoft.XMLHTTP');
7164 xhr.open('GET', noCacheUrl, false);
7167 isCrossOriginRestricted = true;
7170 status = (xhr.status === 1223) ? 204 : xhr.status;
7172 if (!isCrossOriginRestricted) {
7173 isCrossOriginRestricted = (status === 0);
7176 if (isCrossOriginRestricted
7178 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
7179 "being loaded from a different domain or from the local file system whereby cross origin " +
7180 "requests are not allowed due to security reasons. Use asynchronous loading with " +
7181 "Ext.require instead.", synchronous);
7183 else if (status >= 200 && status < 300
7185 // Firebug friendly, file names are still shown even though they're eval'ed code
7186 new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
7191 onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
7192 "verify that the file exists. " +
7193 "XHR status code: " + status, synchronous);
7196 // Prevent potential IE memory leak
7202 * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
7203 * Can be chained with more `require` and `exclude` methods, eg:
7205 Ext.exclude('Ext.data.*').require('*');
7207 Ext.exclude('widget.button*').require('widget.*');
7209 * @param {Array} excludes
7210 * @return {Object} object contains `require` method for chaining
7213 exclude: function(excludes) {
7217 require: function(expressions, fn, scope) {
7218 return me.require(expressions, fn, scope, excludes);
7221 syncRequire: function(expressions, fn, scope) {
7222 return me.syncRequire(expressions, fn, scope, excludes);
7228 * Synchronously loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when finishes, within the optional scope. This method is aliased by {@link Ext#syncRequire} for convenience
7229 * @param {String/Array} expressions Can either be a string or an array of string
7230 * @param {Function} fn (Optional) The callback function
7231 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7232 * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
7235 syncRequire: function() {
7236 this.syncModeEnabled = true;
7237 this.require.apply(this, arguments);
7238 this.refreshQueue();
7239 this.syncModeEnabled = false;
7243 * Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
7244 * finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience
7245 * @param {String/Array} expressions Can either be a string or an array of string
7246 * @param {Function} fn (Optional) The callback function
7247 * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
7248 * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
7251 require: function(expressions, fn, scope, excludes) {
7252 var filePath, expression, exclude, className, excluded = {},
7253 excludedClassNames = [],
7254 possibleClassNames = [],
7255 possibleClassName, classNames = [],
7258 expressions = Ext.Array.from(expressions);
7259 excludes = Ext.Array.from(excludes);
7261 fn = fn || Ext.emptyFn;
7263 scope = scope || Ext.global;
7265 for (i = 0, ln = excludes.length; i < ln; i++) {
7266 exclude = excludes[i];
7268 if (typeof exclude === 'string' && exclude.length > 0) {
7269 excludedClassNames = Manager.getNamesByExpression(exclude);
7271 for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
7272 excluded[excludedClassNames[j]] = true;
7277 for (i = 0, ln = expressions.length; i < ln; i++) {
7278 expression = expressions[i];
7280 if (typeof expression === 'string' && expression.length > 0) {
7281 possibleClassNames = Manager.getNamesByExpression(expression);
7283 for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
7284 possibleClassName = possibleClassNames[j];
7286 if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
7287 Ext.Array.include(classNames, possibleClassName);
7293 // If the dynamic dependency feature is not being used, throw an error
7294 // if the dependencies are not defined
7295 if (!this.config.enabled) {
7296 if (classNames.length > 0) {
7298 sourceClass: "Ext.Loader",
7299 sourceMethod: "require",
7300 msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
7301 "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
7306 if (classNames.length === 0) {
7312 requires: classNames,
7317 classNames = classNames.slice();
7319 for (i = 0, ln = classNames.length; i < ln; i++) {
7320 className = classNames[i];
7322 if (!this.isFileLoaded.hasOwnProperty(className)) {
7323 this.isFileLoaded[className] = false;
7325 filePath = this.getPath(className);
7327 this.classNameToFilePathMap[className] = filePath;
7329 this.numPendingFiles++;
7331 this.loadScriptFile(
7333 Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
7334 Ext.Function.pass(this.onFileLoadError, [className, filePath]),
7336 this.syncModeEnabled
7346 * @param {String} className
7347 * @param {String} filePath
7349 onFileLoaded: function(className, filePath) {
7350 this.numLoadedFiles++;
7352 this.isFileLoaded[className] = true;
7354 this.numPendingFiles--;
7356 if (this.numPendingFiles === 0) {
7357 this.refreshQueue();
7360 if (this.numPendingFiles <= 1) {
7361 window.status = "Finished loading all dependencies, onReady fired!";
7364 window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
7367 if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
7368 var queue = this.queue,
7370 i, ln, j, subLn, missingClasses = [], missingPaths = [];
7372 for (i = 0, ln = queue.length; i < ln; i++) {
7373 requires = queue[i].requires;
7375 for (j = 0, subLn = requires.length; j < ln; j++) {
7376 if (this.isFileLoaded[requires[j]]) {
7377 missingClasses.push(requires[j]);
7382 if (missingClasses.length < 1) {
7386 missingClasses = Ext.Array.filter(missingClasses, function(item) {
7387 return !this.requiresMap.hasOwnProperty(item);
7390 for (i = 0,ln = missingClasses.length; i < ln; i++) {
7391 missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
7395 sourceClass: "Ext.Loader",
7396 sourceMethod: "onFileLoaded",
7397 msg: "The following classes are not declared even if their files have been " +
7398 "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
7399 "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
7407 onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
7408 this.numPendingFiles--;
7409 this.hasFileLoadError = true;
7412 sourceClass: "Ext.Loader",
7413 classToLoad: className,
7415 loadingType: isSynchronous ? 'synchronous' : 'async',
7423 addOptionalRequires: function(requires) {
7424 var optionalRequires = this.optionalRequires,
7427 requires = Ext.Array.from(requires);
7429 for (i = 0, ln = requires.length; i < ln; i++) {
7430 require = requires[i];
7432 Ext.Array.include(optionalRequires, require);
7441 triggerReady: function(force) {
7442 var readyListeners = this.readyListeners,
7443 optionalRequires, listener;
7445 if (this.isLoading || force) {
7446 this.isLoading = false;
7448 if (this.optionalRequires.length) {
7449 // Clone then empty the array to eliminate potential recursive loop issue
7450 optionalRequires = Ext.Array.clone(this.optionalRequires);
7452 // Empty the original array
7453 this.optionalRequires.length = 0;
7455 this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
7459 while (readyListeners.length) {
7460 listener = readyListeners.shift();
7461 listener.fn.call(listener.scope);
7463 if (this.isLoading) {
7473 * Add a new listener to be executed when all required scripts are fully loaded
7475 * @param {Function} fn The function callback to be executed
7476 * @param {Object} scope The execution scope (<code>this</code>) of the callback function
7477 * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
7479 onReady: function(fn, scope, withDomReady, options) {
7482 if (withDomReady !== false && Ext.onDocumentReady) {
7486 Ext.onDocumentReady(oldFn, scope, options);
7490 if (!this.isLoading) {
7494 this.readyListeners.push({
7503 * @param {String} className
7505 historyPush: function(className) {
7506 if (className && this.isFileLoaded.hasOwnProperty(className)) {
7507 Ext.Array.include(this.history, className);
7515 * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
7516 * {@link Ext.Loader} for examples.
7520 Ext.require = alias(Loader, 'require');
7523 * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
7526 * @method syncRequire
7528 Ext.syncRequire = alias(Loader, 'syncRequire');
7531 * Convenient shortcut to {@link Ext.Loader#exclude}
7535 Ext.exclude = alias(Loader, 'exclude');
7541 Ext.onReady = function(fn, scope, options) {
7542 Loader.onReady(fn, scope, true, options);
7545 Class.registerPreprocessor('loader', function(cls, data, continueFn) {
7548 className = Manager.getName(cls),
7549 i, j, ln, subLn, value, propertyName, propertyValue;
7552 Basically loop through the dependencyProperties, look for string class names and push
7553 them into a stack, regardless of whether the property's value is a string, array or object. For example:
7555 extend: 'Ext.MyClass',
7556 requires: ['Ext.some.OtherClass'],
7558 observable: 'Ext.util.Observable';
7561 which will later be transformed into:
7563 extend: Ext.MyClass,
7564 requires: [Ext.some.OtherClass],
7566 observable: Ext.util.Observable;
7571 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7572 propertyName = dependencyProperties[i];
7574 if (data.hasOwnProperty(propertyName)) {
7575 propertyValue = data[propertyName];
7577 if (typeof propertyValue === 'string') {
7578 dependencies.push(propertyValue);
7580 else if (propertyValue instanceof Array) {
7581 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
7582 value = propertyValue[j];
7584 if (typeof value === 'string') {
7585 dependencies.push(value);
7590 for (j in propertyValue) {
7591 if (propertyValue.hasOwnProperty(j)) {
7592 value = propertyValue[j];
7594 if (typeof value === 'string') {
7595 dependencies.push(value);
7603 if (dependencies.length === 0) {
7604 // Loader.historyPush(className);
7608 var deadlockPath = [],
7609 requiresMap = Loader.requiresMap,
7613 Automatically detect deadlocks before-hand,
7614 will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
7616 - A extends B, then B extends A
7617 - A requires B, B requires C, then C requires A
7619 The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
7620 no matter how deep the path is.
7624 requiresMap[className] = dependencies;
7626 detectDeadlock = function(cls) {
7627 deadlockPath.push(cls);
7629 if (requiresMap[cls]) {
7630 if (Ext.Array.contains(requiresMap[cls], className)) {
7632 sourceClass: "Ext.Loader",
7633 msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
7634 deadlockPath[1] + "' " + "mutually require each other. Path: " +
7635 deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
7639 for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
7640 detectDeadlock(requiresMap[cls][i]);
7645 detectDeadlock(className);
7649 Loader.require(dependencies, function() {
7650 for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
7651 propertyName = dependencyProperties[i];
7653 if (data.hasOwnProperty(propertyName)) {
7654 propertyValue = data[propertyName];
7656 if (typeof propertyValue === 'string') {
7657 data[propertyName] = Manager.get(propertyValue);
7659 else if (propertyValue instanceof Array) {
7660 for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
7661 value = propertyValue[j];
7663 if (typeof value === 'string') {
7664 data[propertyName][j] = Manager.get(value);
7669 for (var k in propertyValue) {
7670 if (propertyValue.hasOwnProperty(k)) {
7671 value = propertyValue[k];
7673 if (typeof value === 'string') {
7674 data[propertyName][k] = Manager.get(value);
7682 continueFn.call(me, cls, data);
7688 Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
7690 Manager.registerPostprocessor('uses', function(name, cls, data) {
7691 var uses = Ext.Array.from(data.uses),
7695 for (i = 0, ln = uses.length; i < ln; i++) {
7698 if (typeof item === 'string') {
7703 Loader.addOptionalRequires(items);
7706 Manager.setDefaultPostprocessorPosition('uses', 'last');
7708 })(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
7715 A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
7716 errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
7717 uses the Ext 4 class system, the Error class can automatically add the source class and method from which
7718 the error was raised. It also includes logic to automatically log the eroor to the console, if available,
7719 with additional metadata about the error. In all cases, the error will always be thrown at the end so that
7720 execution will halt.
7722 Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
7723 handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
7724 although in a real application it's usually a better idea to override the handling function and perform
7725 logging or some other method of reporting the errors in a way that is meaningful to the application.
7727 At its simplest you can simply raise an error as a simple string from within any code:
7731 Ext.Error.raise('Something bad happened!');
7733 If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
7734 displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
7735 additional metadata about the error being raised. The {@link #raise} method can also take a config object.
7736 In this form the `msg` attribute becomes the error description, and any other data added to the config gets
7737 added to the error object and, if the console is available, logged to the console for inspection.
7741 Ext.define('Ext.Foo', {
7742 doSomething: function(option){
7743 if (someCondition === false) {
7745 msg: 'You cannot do that!',
7746 option: option, // whatever was passed into the method
7747 'error code': 100 // other arbitrary info
7753 If a console is available (that supports the `console.dir` function) you'll see console output like:
7755 An error was raised with the following data:
7756 option: Object { foo: "bar"}
7759 msg: "You cannot do that!"
7760 sourceClass: "Ext.Foo"
7761 sourceMethod: "doSomething"
7763 uncaught exception: You cannot do that!
7765 As you can see, the error will report exactly where it was raised and will include as much information as the
7766 raising code can usefully provide.
7768 If you want to handle all application errors globally you can simply override the static {@link handle} method
7769 and provide whatever handling logic you need. If the method returns true then the error is considered handled
7770 and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
7774 Ext.Error.handle = function(err) {
7775 if (err.someProperty == 'NotReallyAnError') {
7776 // maybe log something to the application here if applicable
7779 // any non-true return value (including none) will cause the error to be thrown
7782 * Create a new Error object
7783 * @param {Object} config The config object
7785 * @author Brian Moeskau <brian@sencha.com>
7786 * @docauthor Brian Moeskau <brian@sencha.com>
7788 Ext.Error = Ext.extend(Error, {
7792 Static flag that can be used to globally disable error reporting to the browser if set to true
7793 (defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
7794 and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
7795 be preferable to supply a custom error {@link #handle handling} function instead.
7799 Ext.Error.ignore = true;
7807 Raise an error that can include additional data and supports automatic console logging if available.
7808 You can pass a string error message or an object with the `msg` attribute which will be used as the
7809 error message. The object can contain any other name-value attributes (or objects) to be logged
7810 along with the error.
7812 Note that after displaying the error message a JavaScript error will ultimately be thrown so that
7813 execution will halt.
7817 Ext.Error.raise('A simple string error message');
7821 Ext.define('Ext.Foo', {
7822 doSomething: function(option){
7823 if (someCondition === false) {
7825 msg: 'You cannot do that!',
7826 option: option, // whatever was passed into the method
7827 'error code': 100 // other arbitrary info
7832 * @param {String/Object} err The error message string, or an object containing the
7833 * attribute "msg" that will be used as the error message. Any other data included in
7834 * the object will also be logged to the browser console, if available.
7838 raise: function(err){
7840 if (Ext.isString(err)) {
7844 var method = this.raise.caller;
7848 err.sourceMethod = method.$name;
7850 if (method.$owner) {
7851 err.sourceClass = method.$owner.$className;
7855 if (Ext.Error.handle(err) !== true) {
7856 var global = Ext.global,
7857 con = global.console,
7858 msg = Ext.Error.prototype.toString.call(err),
7859 noConsoleMsg = 'An uncaught error was raised: "' + msg +
7860 '". Use Firebug or Webkit console for additional details.';
7864 con.warn('An uncaught error was raised with the following data:');
7868 con.warn(noConsoleMsg);
7874 else if (global.alert){
7875 global.alert(noConsoleMsg);
7878 throw new Ext.Error(err);
7883 Globally handle any Ext errors that may be raised, optionally providing custom logic to
7884 handle different errors individually. Return true from the function to bypass throwing the
7885 error to the browser, otherwise the error will be thrown and execution will halt.
7889 Ext.Error.handle = function(err) {
7890 if (err.someProperty == 'NotReallyAnError') {
7891 // maybe log something to the application here if applicable
7894 // any non-true return value (including none) will cause the error to be thrown
7897 * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes
7898 * that were originally raised with it, plus properties about the method and class from which
7899 * the error originated (if raised from a class that uses the Ext 4 class system).
7904 return Ext.Error.ignore;
7910 * @param {String/Object} config The error message string, or an object containing the
7911 * attribute "msg" that will be used as the error message. Any other data included in
7912 * the object will be applied to the error instance and logged to the browser console, if available.
7914 constructor: function(config){
7915 if (Ext.isString(config)) {
7916 config = { msg: config };
7918 Ext.apply(this, config);
7922 Provides a custom string representation of the error object. This is an override of the base JavaScript
7923 `Object.toString` method, which is useful so that when logged to the browser console, an error object will
7924 be displayed with a useful message instead of `[object Object]`, the default `toString` result.
7926 The default implementation will include the error message along with the raising class and method, if available,
7927 but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
7928 a particular error instance, if you want to provide a custom description that will show up in the console.
7930 * @return {String} The error message. If raised from within the Ext 4 class system, the error message
7931 * will also include the raising class and method names, if available.
7933 toString: function(){
7935 className = me.className ? me.className : '',
7936 methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
7937 msg = me.msg || '(No description provided)';
7939 return className + methodName + msg;