--- /dev/null
+/*
+Ext JS - JavaScript Library
+Copyright (c) 2006-2011, Sencha Inc.
+All rights reserved.
+licensing@sencha.com
+*/
+/**
+ * @class Ext
+ * @singleton
+ */
+(function() {
+ var global = this,
+ objectPrototype = Object.prototype,
+ toString = Object.prototype.toString,
+ enumerables = true,
+ enumerablesTest = { toString: 1 },
+ i;
+
+ if (typeof Ext === 'undefined') {
+ global.Ext = {};
+ }
+
+ Ext.global = global;
+
+ for (i in enumerablesTest) {
+ enumerables = null;
+ }
+
+ if (enumerables) {
+ enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable',
+ 'toLocaleString', 'toString', 'constructor'];
+ }
+
+ /**
+ * An array containing extra enumerables for old browsers
+ * @type Array
+ */
+ Ext.enumerables = enumerables;
+
+ /**
+ * Copies all the properties of config to the specified object.
+ * Note that if recursive merging and cloning without referencing the original objects / arrays is needed, use
+ * {@link Ext.Object#merge} instead.
+ * @param {Object} object The receiver of the properties
+ * @param {Object} config The source of the properties
+ * @param {Object} defaults A different object that will also be applied for default values
+ * @return {Object} returns obj
+ */
+ Ext.apply = function(object, config, defaults) {
+ if (defaults) {
+ Ext.apply(object, defaults);
+ }
+
+ if (object && config && typeof config === 'object') {
+ var i, j, k;
+
+ for (i in config) {
+ object[i] = config[i];
+ }
+
+ if (enumerables) {
+ for (j = enumerables.length; j--;) {
+ k = enumerables[j];
+ if (config.hasOwnProperty(k)) {
+ object[k] = config[k];
+ }
+ }
+ }
+ }
+
+ return object;
+ };
+
+ Ext.buildSettings = Ext.apply({
+ baseCSSPrefix: 'x-',
+ scopeResetCSS: false
+ }, Ext.buildSettings || {});
+
+ Ext.apply(Ext, {
+ /**
+ * A reusable empty function
+ */
+ emptyFn: function() {},
+
+ baseCSSPrefix: Ext.buildSettings.baseCSSPrefix,
+
+ /**
+ * Copies all the properties of config to object if they don't already exist.
+ * @function
+ * @param {Object} object The receiver of the properties
+ * @param {Object} config The source of the properties
+ * @return {Object} returns obj
+ */
+ applyIf: function(object, config) {
+ var property;
+
+ if (object) {
+ for (property in config) {
+ if (object[property] === undefined) {
+ object[property] = config[property];
+ }
+ }
+ }
+
+ return object;
+ },
+
+ /**
+ * Iterates either an array or an object. This method delegates to
+ * {@link Ext.Array#each Ext.Array.each} if the given value is iterable, and {@link Ext.Object#each Ext.Object.each} otherwise.
+ *
+ * @param {Object/Array} object The object or array to be iterated.
+ * @param {Function} fn The function to be called for each iteration. See and {@link Ext.Array#each Ext.Array.each} and
+ * {@link Ext.Object#each Ext.Object.each} for detailed lists of arguments passed to this function depending on the given object
+ * type that is being iterated.
+ * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
+ * Defaults to the object being iterated itself.
+ * @markdown
+ */
+ iterate: function(object, fn, scope) {
+ if (Ext.isEmpty(object)) {
+ return;
+ }
+
+ if (scope === undefined) {
+ scope = object;
+ }
+
+ if (Ext.isIterable(object)) {
+ Ext.Array.each.call(Ext.Array, object, fn, scope);
+ }
+ else {
+ Ext.Object.each.call(Ext.Object, object, fn, scope);
+ }
+ }
+ });
+
+ Ext.apply(Ext, {
+
+ /**
+ * This method deprecated. Use {@link Ext#define Ext.define} instead.
+ * @function
+ * @param {Function} superclass
+ * @param {Object} overrides
+ * @return {Function} The subclass constructor from the <tt>overrides</tt> parameter, or a generated one if not provided.
+ * @deprecated 4.0.0 Use {@link Ext#define Ext.define} instead
+ */
+ extend: function() {
+ // inline overrides
+ var objectConstructor = objectPrototype.constructor,
+ inlineOverrides = function(o) {
+ for (var m in o) {
+ if (!o.hasOwnProperty(m)) {
+ continue;
+ }
+ this[m] = o[m];
+ }
+ };
+
+ return function(subclass, superclass, overrides) {
+ // First we check if the user passed in just the superClass with overrides
+ if (Ext.isObject(superclass)) {
+ overrides = superclass;
+ superclass = subclass;
+ subclass = overrides.constructor !== objectConstructor ? overrides.constructor : function() {
+ superclass.apply(this, arguments);
+ };
+ }
+
+ if (!superclass) {
+ Ext.Error.raise({
+ sourceClass: 'Ext',
+ sourceMethod: 'extend',
+ msg: 'Attempting to extend from a class which has not been loaded on the page.'
+ });
+ }
+
+ // We create a new temporary class
+ var F = function() {},
+ subclassProto, superclassProto = superclass.prototype;
+
+ F.prototype = superclassProto;
+ subclassProto = subclass.prototype = new F();
+ subclassProto.constructor = subclass;
+ subclass.superclass = superclassProto;
+
+ if (superclassProto.constructor === objectConstructor) {
+ superclassProto.constructor = superclass;
+ }
+
+ subclass.override = function(overrides) {
+ Ext.override(subclass, overrides);
+ };
+
+ subclassProto.override = inlineOverrides;
+ subclassProto.proto = subclassProto;
+
+ subclass.override(overrides);
+ subclass.extend = function(o) {
+ return Ext.extend(subclass, o);
+ };
+
+ return subclass;
+ };
+ }(),
+
+ /**
+ * Proxy to {@link Ext.Base#override}. Please refer {@link Ext.Base#override} for further details.
+
+ Ext.define('My.cool.Class', {
+ sayHi: function() {
+ alert('Hi!');
+ }
+ }
+
+ Ext.override(My.cool.Class, {
+ sayHi: function() {
+ alert('About to say...');
+
+ this.callOverridden();
+ }
+ });
+
+ var cool = new My.cool.Class();
+ cool.sayHi(); // alerts 'About to say...'
+ // alerts 'Hi!'
+
+ * Please note that `this.callOverridden()` only works if the class was previously
+ * created with {@link Ext#define)
+ *
+ * @param {Object} cls The class to override
+ * @param {Object} overrides The list of functions to add to origClass. This should be specified as an object literal
+ * containing one or more methods.
+ * @method override
+ * @markdown
+ */
+ override: function(cls, overrides) {
+ if (cls.prototype.$className) {
+ return cls.override(overrides);
+ }
+ else {
+ Ext.apply(cls.prototype, overrides);
+ }
+ }
+ });
+
+ // A full set of static methods to do type checking
+ Ext.apply(Ext, {
+
+ /**
+ * Returns the given value itself if it's not empty, as described in {@link Ext#isEmpty}; returns the default
+ * value (second argument) otherwise.
+ *
+ * @param {Mixed} value The value to test
+ * @param {Mixed} defaultValue The value to return if the original value is empty
+ * @param {Boolean} allowBlank (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
+ * @return {Mixed} value, if non-empty, else defaultValue
+ */
+ valueFrom: function(value, defaultValue, allowBlank){
+ return Ext.isEmpty(value, allowBlank) ? defaultValue : value;
+ },
+
+ /**
+ * Returns the type of the given variable in string format. List of possible values are:
+ *
+ * - `undefined`: If the given value is `undefined`
+ * - `null`: If the given value is `null`
+ * - `string`: If the given value is a string
+ * - `number`: If the given value is a number
+ * - `boolean`: If the given value is a boolean value
+ * - `date`: If the given value is a `Date` object
+ * - `function`: If the given value is a function reference
+ * - `object`: If the given value is an object
+ * - `array`: If the given value is an array
+ * - `regexp`: If the given value is a regular expression
+ * - `element`: If the given value is a DOM Element
+ * - `textnode`: If the given value is a DOM text node and contains something other than whitespace
+ * - `whitespace`: If the given value is a DOM text node and contains only whitespace
+ *
+ * @param {Mixed} value
+ * @return {String}
+ * @markdown
+ */
+ typeOf: function(value) {
+ if (value === null) {
+ return 'null';
+ }
+
+ var type = typeof value;
+
+ if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
+ return type;
+ }
+
+ var typeToString = toString.call(value);
+
+ switch(typeToString) {
+ case '[object Array]':
+ return 'array';
+ case '[object Date]':
+ return 'date';
+ case '[object Boolean]':
+ return 'boolean';
+ case '[object Number]':
+ return 'number';
+ case '[object RegExp]':
+ return 'regexp';
+ }
+
+ if (type === 'function') {
+ return 'function';
+ }
+
+ if (type === 'object') {
+ if (value.nodeType !== undefined) {
+ if (value.nodeType === 3) {
+ return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
+ }
+ else {
+ return 'element';
+ }
+ }
+
+ return 'object';
+ }
+
+ Ext.Error.raise({
+ sourceClass: 'Ext',
+ sourceMethod: 'typeOf',
+ msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
+ });
+ },
+
+ /**
+ * Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
+ *
+ * - `null`
+ * - `undefined`
+ * - a zero-length array
+ * - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
+ *
+ * @param {Mixed} value The value to test
+ * @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
+ * @return {Boolean}
+ * @markdown
+ */
+ isEmpty: function(value, allowEmptyString) {
+ return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript Array, false otherwise.
+ *
+ * @param {Mixed} target The target to test
+ * @return {Boolean}
+ */
+ isArray: ('isArray' in Array) ? Array.isArray : function(value) {
+ return toString.call(value) === '[object Array]';
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript Date object, false otherwise.
+ * @param {Object} object The object to test
+ * @return {Boolean}
+ */
+ isDate: function(value) {
+ return toString.call(value) === '[object Date]';
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript Object, false otherwise.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isObject: (toString.call(null) === '[object Object]') ?
+ function(value) {
+ return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.nodeType === undefined;
+ } :
+ function(value) {
+ return toString.call(value) === '[object Object]';
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isPrimitive: function(value) {
+ var type = typeof value;
+
+ return type === 'string' || type === 'number' || type === 'boolean';
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript Function, false otherwise.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isFunction:
+ // Safari 3.x and 4.x returns 'function' for typeof <NodeList>, hence we need to fall back to using
+ // Object.prorotype.toString (slower)
+ (typeof document !== 'undefined' && typeof document.getElementsByTagName('body') === 'function') ? function(value) {
+ return toString.call(value) === '[object Function]';
+ } : function(value) {
+ return typeof value === 'function';
+ },
+
+ /**
+ * Returns true if the passed value is a number. Returns false for non-finite numbers.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isNumber: function(value) {
+ return typeof value === 'number' && isFinite(value);
+ },
+
+ /**
+ * Validates that a value is numeric.
+ * @param {Mixed} value Examples: 1, '1', '2.34'
+ * @return {Boolean} True if numeric, false otherwise
+ */
+ isNumeric: function(value) {
+ return !isNaN(parseFloat(value)) && isFinite(value);
+ },
+
+ /**
+ * Returns true if the passed value is a string.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isString: function(value) {
+ return typeof value === 'string';
+ },
+
+ /**
+ * Returns true if the passed value is a boolean.
+ *
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isBoolean: function(value) {
+ return typeof value === 'boolean';
+ },
+
+ /**
+ * Returns true if the passed value is an HTMLElement
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isElement: function(value) {
+ return value ? value.nodeType !== undefined : false;
+ },
+
+ /**
+ * Returns true if the passed value is a TextNode
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isTextNode: function(value) {
+ return value ? value.nodeName === "#text" : false;
+ },
+
+ /**
+ * Returns true if the passed value is defined.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isDefined: function(value) {
+ return typeof value !== 'undefined';
+ },
+
+ /**
+ * Returns true if the passed value is iterable, false otherwise
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isIterable: function(value) {
+ return (value && typeof value !== 'string') ? value.length !== undefined : false;
+ }
+ });
+
+ Ext.apply(Ext, {
+
+ /**
+ * Clone almost any type of variable including array, object, DOM nodes and Date without keeping the old reference
+ * @param {Mixed} item The variable to clone
+ * @return {Mixed} clone
+ */
+ clone: function(item) {
+ if (item === null || item === undefined) {
+ return item;
+ }
+
+ // DOM nodes
+ // TODO proxy this to Ext.Element.clone to handle automatic id attribute changing
+ // recursively
+ if (item.nodeType && item.cloneNode) {
+ return item.cloneNode(true);
+ }
+
+ var type = toString.call(item);
+
+ // Date
+ if (type === '[object Date]') {
+ return new Date(item.getTime());
+ }
+
+ var i, j, k, clone, key;
+
+ // Array
+ if (type === '[object Array]') {
+ i = item.length;
+
+ clone = [];
+
+ while (i--) {
+ clone[i] = Ext.clone(item[i]);
+ }
+ }
+ // Object
+ else if (type === '[object Object]' && item.constructor === Object) {
+ clone = {};
+
+ for (key in item) {
+ clone[key] = Ext.clone(item[key]);
+ }
+
+ if (enumerables) {
+ for (j = enumerables.length; j--;) {
+ k = enumerables[j];
+ clone[k] = item[k];
+ }
+ }
+ }
+
+ return clone || item;
+ },
+
+ /**
+ * @private
+ * Generate a unique reference of Ext in the global scope, useful for sandboxing
+ */
+ getUniqueGlobalNamespace: function() {
+ var uniqueGlobalNamespace = this.uniqueGlobalNamespace;
+
+ if (uniqueGlobalNamespace === undefined) {
+ var i = 0;
+
+ do {
+ uniqueGlobalNamespace = 'ExtSandbox' + (++i);
+ } while (Ext.global[uniqueGlobalNamespace] !== undefined);
+
+ Ext.global[uniqueGlobalNamespace] = Ext;
+ this.uniqueGlobalNamespace = uniqueGlobalNamespace;
+ }
+
+ return uniqueGlobalNamespace;
+ },
+
+ /**
+ * @private
+ */
+ functionFactory: function() {
+ var args = Array.prototype.slice.call(arguments);
+
+ if (args.length > 0) {
+ args[args.length - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' +
+ args[args.length - 1];
+ }
+
+ return Function.prototype.constructor.apply(Function.prototype, args);
+ }
+ });
+
+ /**
+ * Old alias to {@link Ext#typeOf}
+ * @deprecated 4.0.0 Use {@link Ext#typeOf} instead
+ */
+ Ext.type = Ext.typeOf;
+
+})();
+
+/**
+ * @author Jacky Nguyen <jacky@sencha.com>
+ * @docauthor Jacky Nguyen <jacky@sencha.com>
+ * @class Ext.Version
+ *
+ * A utility class that wrap around a string version number and provide convenient
+ * method to perform comparison. See also: {@link Ext.Version#compare compare}. Example:
+
+ var version = new Ext.Version('1.0.2beta');
+ console.log("Version is " + version); // Version is 1.0.2beta
+
+ console.log(version.getMajor()); // 1
+ console.log(version.getMinor()); // 0
+ console.log(version.getPatch()); // 2
+ console.log(version.getBuild()); // 0
+ console.log(version.getRelease()); // beta
+
+ console.log(version.isGreaterThan('1.0.1')); // True
+ console.log(version.isGreaterThan('1.0.2alpha')); // True
+ console.log(version.isGreaterThan('1.0.2RC')); // False
+ console.log(version.isGreaterThan('1.0.2')); // False
+ console.log(version.isLessThan('1.0.2')); // True
+
+ console.log(version.match(1.0)); // True
+ console.log(version.match('1.0.2')); // True
+
+ * @markdown
+ */
+(function() {
+
+// Current core version
+var version = '4.0.0', Version;
+ Ext.Version = Version = Ext.extend(Object, {
+
+ /**
+ * @constructor
+ * @param {String/Number} version The version number in the follow standard format: major[.minor[.patch[.build[release]]]]
+ * Examples: 1.0 or 1.2.3beta or 1.2.3.4RC
+ * @return {Ext.Version} this
+ * @param version
+ */
+ constructor: function(version) {
+ var parts, releaseStartIndex;
+
+ if (version instanceof Version) {
+ return version;
+ }
+
+ this.version = this.shortVersion = String(version).toLowerCase().replace(/_/g, '.').replace(/[\-+]/g, '');
+
+ releaseStartIndex = this.version.search(/([^\d\.])/);
+
+ if (releaseStartIndex !== -1) {
+ this.release = this.version.substr(releaseStartIndex, version.length);
+ this.shortVersion = this.version.substr(0, releaseStartIndex);
+ }
+
+ this.shortVersion = this.shortVersion.replace(/[^\d]/g, '');
+
+ parts = this.version.split('.');
+
+ this.major = parseInt(parts.shift() || 0, 10);
+ this.minor = parseInt(parts.shift() || 0, 10);
+ this.patch = parseInt(parts.shift() || 0, 10);
+ this.build = parseInt(parts.shift() || 0, 10);
+
+ return this;
+ },
+
+ /**
+ * Override the native toString method
+ * @private
+ * @return {String} version
+ */
+ toString: function() {
+ return this.version;
+ },
+
+ /**
+ * Override the native valueOf method
+ * @private
+ * @return {String} version
+ */
+ valueOf: function() {
+ return this.version;
+ },
+
+ /**
+ * Returns the major component value
+ * @return {Number} major
+ */
+ getMajor: function() {
+ return this.major || 0;
+ },
+
+ /**
+ * Returns the minor component value
+ * @return {Number} minor
+ */
+ getMinor: function() {
+ return this.minor || 0;
+ },
+
+ /**
+ * Returns the patch component value
+ * @return {Number} patch
+ */
+ getPatch: function() {
+ return this.patch || 0;
+ },
+
+ /**
+ * Returns the build component value
+ * @return {Number} build
+ */
+ getBuild: function() {
+ return this.build || 0;
+ },
+
+ /**
+ * Returns the release component value
+ * @return {Number} release
+ */
+ getRelease: function() {
+ return this.release || '';
+ },
+
+ /**
+ * Returns whether this version if greater than the supplied argument
+ * @param {String/Number} target The version to compare with
+ * @return {Boolean} True if this version if greater than the target, false otherwise
+ */
+ isGreaterThan: function(target) {
+ return Version.compare(this.version, target) === 1;
+ },
+
+ /**
+ * Returns whether this version if smaller than the supplied argument
+ * @param {String/Number} target The version to compare with
+ * @return {Boolean} True if this version if smaller than the target, false otherwise
+ */
+ isLessThan: function(target) {
+ return Version.compare(this.version, target) === -1;
+ },
+
+ /**
+ * Returns whether this version equals to the supplied argument
+ * @param {String/Number} target The version to compare with
+ * @return {Boolean} True if this version equals to the target, false otherwise
+ */
+ equals: function(target) {
+ return Version.compare(this.version, target) === 0;
+ },
+
+ /**
+ * Returns whether this version matches the supplied argument. Example:
+ * <pre><code>
+ * var version = new Ext.Version('1.0.2beta');
+ * console.log(version.match(1)); // True
+ * console.log(version.match(1.0)); // True
+ * console.log(version.match('1.0.2')); // True
+ * console.log(version.match('1.0.2RC')); // False
+ * </code></pre>
+ * @param {String/Number} target The version to compare with
+ * @return {Boolean} True if this version matches the target, false otherwise
+ */
+ match: function(target) {
+ target = String(target);
+ return this.version.substr(0, target.length) === target;
+ },
+
+ /**
+ * Returns this format: [major, minor, patch, build, release]. Useful for comparison
+ * @return {Array}
+ */
+ toArray: function() {
+ return [this.getMajor(), this.getMinor(), this.getPatch(), this.getBuild(), this.getRelease()];
+ },
+
+ /**
+ * Returns shortVersion version without dots and release
+ * @return {String}
+ */
+ getShortVersion: function() {
+ return this.shortVersion;
+ }
+ });
+
+ Ext.apply(Version, {
+ // @private
+ releaseValueMap: {
+ 'dev': -6,
+ 'alpha': -5,
+ 'a': -5,
+ 'beta': -4,
+ 'b': -4,
+ 'rc': -3,
+ '#': -2,
+ 'p': -1,
+ 'pl': -1
+ },
+
+ /**
+ * Converts a version component to a comparable value
+ *
+ * @static
+ * @param {Mixed} value The value to convert
+ * @return {Mixed}
+ */
+ getComponentValue: function(value) {
+ return !value ? 0 : (isNaN(value) ? this.releaseValueMap[value] || value : parseInt(value, 10));
+ },
+
+ /**
+ * Compare 2 specified versions, starting from left to right. If a part contains special version strings,
+ * they are handled in the following order:
+ * 'dev' < 'alpha' = 'a' < 'beta' = 'b' < 'RC' = 'rc' < '#' < 'pl' = 'p' < 'anything else'
+ *
+ * @static
+ * @param {String} current The current version to compare to
+ * @param {String} target The target version to compare to
+ * @return {Number} Returns -1 if the current version is smaller than the target version, 1 if greater, and 0 if they're equivalent
+ */
+ compare: function(current, target) {
+ var currentValue, targetValue, i;
+
+ current = new Version(current).toArray();
+ target = new Version(target).toArray();
+
+ for (i = 0; i < Math.max(current.length, target.length); i++) {
+ currentValue = this.getComponentValue(current[i]);
+ targetValue = this.getComponentValue(target[i]);
+
+ if (currentValue < targetValue) {
+ return -1;
+ } else if (currentValue > targetValue) {
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+ });
+
+ Ext.apply(Ext, {
+ /**
+ * @private
+ */
+ versions: {},
+
+ /**
+ * @private
+ */
+ lastRegisteredVersion: null,
+
+ /**
+ * Set version number for the given package name.
+ *
+ * @param {String} packageName The package name, for example: 'core', 'touch', 'extjs'
+ * @param {String/Ext.Version} version The version, for example: '1.2.3alpha', '2.4.0-dev'
+ * @return {Ext}
+ */
+ setVersion: function(packageName, version) {
+ Ext.versions[packageName] = new Version(version);
+ Ext.lastRegisteredVersion = Ext.versions[packageName];
+
+ return this;
+ },
+
+ /**
+ * Get the version number of the supplied package name; will return the last registered version
+ * (last Ext.setVersion call) if there's no package name given.
+ *
+ * @param {String} packageName (Optional) The package name, for example: 'core', 'touch', 'extjs'
+ * @return {Ext.Version} The version
+ */
+ getVersion: function(packageName) {
+ if (packageName === undefined) {
+ return Ext.lastRegisteredVersion;
+ }
+
+ return Ext.versions[packageName];
+ },
+
+ /**
+ * Create a closure for deprecated code.
+ *
+ // This means Ext.oldMethod is only supported in 4.0.0beta and older.
+ // If Ext.getVersion('extjs') returns a version that is later than '4.0.0beta', for example '4.0.0RC',
+ // the closure will not be invoked
+ Ext.deprecate('extjs', '4.0.0beta', function() {
+ Ext.oldMethod = Ext.newMethod;
+
+ ...
+ });
+
+ * @param {String} packageName The package name
+ * @param {String} since The last version before it's deprecated
+ * @param {Function} closure The callback function to be executed with the specified version is less than the current version
+ * @param {Object} scope The execution scope (<tt>this</tt>) if the closure
+ * @markdown
+ */
+ deprecate: function(packageName, since, closure, scope) {
+ if (Version.compare(Ext.getVersion(packageName), since) < 1) {
+ closure.call(scope);
+ }
+ }
+ }); // End Versioning
+
+ Ext.setVersion('core', version);
+
+})();
+
+/**
+ * @class Ext.String
+ *
+ * A collection of useful static methods to deal with strings
+ * @singleton
+ */
+
+Ext.String = {
+ 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,
+ escapeRe: /('|\\)/g,
+ formatRe: /\{(\d+)\}/g,
+ escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
+
+ /**
+ * Convert certain characters (&, <, >, and ') to their HTML character equivalents for literal display in web pages.
+ * @param {String} value The string to encode
+ * @return {String} The encoded text
+ */
+ htmlEncode: (function() {
+ var entities = {
+ '&': '&',
+ '>': '>',
+ '<': '<',
+ '"': '"'
+ }, keys = [], p, regex;
+
+ for (p in entities) {
+ keys.push(p);
+ }
+
+ regex = new RegExp('(' + keys.join('|') + ')', 'g');
+
+ return function(value) {
+ return (!value) ? value : String(value).replace(regex, function(match, capture) {
+ return entities[capture];
+ });
+ };
+ })(),
+
+ /**
+ * Convert certain characters (&, <, >, and ') from their HTML character equivalents.
+ * @param {String} value The string to decode
+ * @return {String} The decoded text
+ */
+ htmlDecode: (function() {
+ var entities = {
+ '&': '&',
+ '>': '>',
+ '<': '<',
+ '"': '"'
+ }, keys = [], p, regex;
+
+ for (p in entities) {
+ keys.push(p);
+ }
+
+ regex = new RegExp('(' + keys.join('|') + '|&#[0-9]{1,5};' + ')', 'g');
+
+ return function(value) {
+ return (!value) ? value : String(value).replace(regex, function(match, capture) {
+ if (capture in entities) {
+ return entities[capture];
+ } else {
+ return String.fromCharCode(parseInt(capture.substr(2), 10));
+ }
+ });
+ };
+ })(),
+
+ /**
+ * Appends content to the query string of a URL, handling logic for whether to place
+ * a question mark or ampersand.
+ * @param {String} url The URL to append to.
+ * @param {String} string The content to append to the URL.
+ * @return (String) The resulting URL
+ */
+ urlAppend : function(url, string) {
+ if (!Ext.isEmpty(string)) {
+ return url + (url.indexOf('?') === -1 ? '?' : '&') + string;
+ }
+
+ return url;
+ },
+
+ /**
+ * Trims whitespace from either end of a string, leaving spaces within the string intact. Example:
+ * @example
+var s = ' foo bar ';
+alert('-' + s + '-'); //alerts "- foo bar -"
+alert('-' + Ext.String.trim(s) + '-'); //alerts "-foo bar-"
+
+ * @param {String} string The string to escape
+ * @return {String} The trimmed string
+ */
+ trim: function(string) {
+ return string.replace(Ext.String.trimRegex, "");
+ },
+
+ /**
+ * Capitalize the given string
+ * @param {String} string
+ * @return {String}
+ */
+ capitalize: function(string) {
+ return string.charAt(0).toUpperCase() + string.substr(1);
+ },
+
+ /**
+ * Truncate a string and add an ellipsis ('...') to the end if it exceeds the specified length
+ * @param {String} value The string to truncate
+ * @param {Number} length The maximum length to allow before truncating
+ * @param {Boolean} word True to try to find a common word break
+ * @return {String} The converted text
+ */
+ ellipsis: function(value, len, word) {
+ if (value && value.length > len) {
+ if (word) {
+ var vs = value.substr(0, len - 2),
+ index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
+ if (index !== -1 && index >= (len - 15)) {
+ return vs.substr(0, index) + "...";
+ }
+ }
+ return value.substr(0, len - 3) + "...";
+ }
+ return value;
+ },
+
+ /**
+ * Escapes the passed string for use in a regular expression
+ * @param {String} string
+ * @return {String}
+ */
+ escapeRegex: function(string) {
+ return string.replace(Ext.String.escapeRegexRe, "\\$1");
+ },
+
+ /**
+ * Escapes the passed string for ' and \
+ * @param {String} string The string to escape
+ * @return {String} The escaped string
+ */
+ escape: function(string) {
+ return string.replace(Ext.String.escapeRe, "\\$1");
+ },
+
+ /**
+ * Utility function that allows you to easily switch a string between two alternating values. The passed value
+ * is compared to the current string, and if they are equal, the other value that was passed in is returned. If
+ * they are already different, the first value passed in is returned. Note that this method returns the new value
+ * but does not change the current string.
+ * <pre><code>
+ // alternate sort directions
+ sort = Ext.String.toggle(sort, 'ASC', 'DESC');
+
+ // instead of conditional logic:
+ sort = (sort == 'ASC' ? 'DESC' : 'ASC');
+ </code></pre>
+ * @param {String} string The current string
+ * @param {String} value The value to compare to the current string
+ * @param {String} other The new value to use if the string already equals the first value passed in
+ * @return {String} The new value
+ */
+ toggle: function(string, value, other) {
+ return string === value ? other : value;
+ },
+
+ /**
+ * Pads the left side of a string with a specified character. This is especially useful
+ * for normalizing number and date strings. Example usage:
+ *
+ * <pre><code>
+var s = Ext.String.leftPad('123', 5, '0');
+// s now contains the string: '00123'
+ </code></pre>
+ * @param {String} string The original string
+ * @param {Number} size The total length of the output string
+ * @param {String} character (optional) The character with which to pad the original string (defaults to empty string " ")
+ * @return {String} The padded string
+ */
+ leftPad: function(string, size, character) {
+ var result = String(string);
+ character = character || " ";
+ while (result.length < size) {
+ result = character + result;
+ }
+ return result;
+ },
+
+ /**
+ * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
+ * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
+ * <pre><code>
+var cls = 'my-class', text = 'Some text';
+var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
+// s now contains the string: '<div class="my-class">Some text</div>'
+ </code></pre>
+ * @param {String} string The tokenized string to be formatted
+ * @param {String} value1 The value to replace token {0}
+ * @param {String} value2 Etc...
+ * @return {String} The formatted string
+ */
+ format: function(format) {
+ var args = Ext.Array.toArray(arguments, 1);
+ return format.replace(Ext.String.formatRe, function(m, i) {
+ return args[i];
+ });
+ }
+};
+
+/**
+ * @class Ext.Number
+ *
+ * A collection of useful static methods to deal with numbers
+ * @singleton
+ */
+
+(function() {
+
+var isToFixedBroken = (0.9).toFixed() !== '1';
+
+Ext.Number = {
+ /**
+ * Checks whether or not the current number is within a desired range. If the number is already within the
+ * range it is returned, otherwise the min or max value is returned depending on which side of the range is
+ * exceeded. Note that this method returns the constrained value but does not change the current number.
+ * @param {Number} number The number to check
+ * @param {Number} min The minimum number in the range
+ * @param {Number} max The maximum number in the range
+ * @return {Number} The constrained value if outside the range, otherwise the current value
+ */
+ constrain: function(number, min, max) {
+ number = parseFloat(number);
+
+ if (!isNaN(min)) {
+ number = Math.max(number, min);
+ }
+ if (!isNaN(max)) {
+ number = Math.min(number, max);
+ }
+ return number;
+ },
+
+ /**
+ * Formats a number using fixed-point notation
+ * @param {Number} value The number to format
+ * @param {Number} precision The number of digits to show after the decimal point
+ */
+ toFixed: function(value, precision) {
+ if (isToFixedBroken) {
+ precision = precision || 0;
+ var pow = Math.pow(10, precision);
+ return (Math.round(value * pow) / pow).toFixed(precision);
+ }
+
+ return value.toFixed(precision);
+ },
+
+ /**
+ * Validate that a value is numeric and convert it to a number if necessary. Returns the specified default value if
+ * it is not.
+
+Ext.Number.from('1.23', 1); // returns 1.23
+Ext.Number.from('abc', 1); // returns 1
+
+ * @param {Mixed} value
+ * @param {Number} defaultValue The value to return if the original value is non-numeric
+ * @return {Number} value, if numeric, defaultValue otherwise
+ */
+ from: function(value, defaultValue) {
+ if (isFinite(value)) {
+ value = parseFloat(value);
+ }
+
+ return !isNaN(value) ? value : defaultValue;
+ }
+};
+
+})();
+
+/**
+ * This method is deprecated, please use {@link Ext.Number#from Ext.Number.from} instead
+ *
+ * @deprecated 4.0.0 Replaced by Ext.Number.from
+ * @member Ext
+ * @method num
+ */
+Ext.num = function() {
+ return Ext.Number.from.apply(this, arguments);
+};
+/**
+ * @author Jacky Nguyen <jacky@sencha.com>
+ * @docauthor Jacky Nguyen <jacky@sencha.com>
+ * @class Ext.Array
+ *
+ * A set of useful static methods to deal with arrays; provide missing methods for older browsers.
+
+ * @singleton
+ * @markdown
+ */
+(function() {
+
+ var arrayPrototype = Array.prototype,
+ slice = arrayPrototype.slice,
+ supportsForEach = 'forEach' in arrayPrototype,
+ supportsMap = 'map' in arrayPrototype,
+ supportsIndexOf = 'indexOf' in arrayPrototype,
+ supportsEvery = 'every' in arrayPrototype,
+ supportsSome = 'some' in arrayPrototype,
+ supportsFilter = 'filter' in arrayPrototype,
+ supportsSort = function() {
+ var a = [1,2,3,4,5].sort(function(){ return 0; });
+ return a[0] === 1 && a[1] === 2 && a[2] === 3 && a[3] === 4 && a[4] === 5;
+ }(),
+ supportsSliceOnNodeList = true,
+ ExtArray;
+ try {
+ // IE 6 - 8 will throw an error when using Array.prototype.slice on NodeList
+ if (typeof document !== 'undefined') {
+ slice.call(document.getElementsByTagName('body'));
+ }
+ } catch (e) {
+ supportsSliceOnNodeList = false;
+ }
+
+ ExtArray = Ext.Array = {
+ /*
+ * Iterates an array or an iterable value and invoke the given callback function for each item.
+
+ var countries = ['Vietnam', 'Singapore', 'United States', 'Russia'];
+
+ Ext.Array.each(countries, function(name, index, countriesItSelf) {
+ console.log(name);
+ });
+
+ var sum = function() {
+ var sum = 0;
+
+ Ext.Array.each(arguments, function(value) {
+ sum += value;
+ });
+
+ return sum;
+ };
+
+ sum(1, 2, 3); // returns 6
+
+ * The iteration can be stopped by returning false in the function callback.
+
+ Ext.Array.each(countries, function(name, index, countriesItSelf) {
+ if (name === 'Singapore') {
+ return false; // break here
+ }
+ });
+
+ * @param {Array/NodeList/Mixed} iterable The value to be iterated. If this
+ * argument is not iterable, the callback function is called once.
+ * @param {Function} fn The callback function. If it returns false, the iteration stops and this method returns
+ * the current `index`. Arguments passed to this callback function are:
+
+- `item`: {Mixed} The item at the current `index` in the passed `array`
+- `index`: {Number} The current `index` within the `array`
+- `allItems`: {Array/NodeList/Mixed} The `array` passed as the first argument to `Ext.Array.each`
+
+ * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed.
+ * @param {Boolean} reverse (Optional) Reverse the iteration order (loop from the end to the beginning)
+ * Defaults false
+ * @return {Boolean} See description for the `fn` parameter.
+ * @markdown
+ */
+ each: function(array, fn, scope, reverse) {
+ array = ExtArray.from(array);
+
+ var i,
+ ln = array.length;
+
+ if (reverse !== true) {
+ for (i = 0; i < ln; i++) {
+ if (fn.call(scope || array[i], array[i], i, array) === false) {
+ return i;
+ }
+ }
+ }
+ else {
+ for (i = ln - 1; i > -1; i--) {
+ if (fn.call(scope || array[i], array[i], i, array) === false) {
+ return i;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Iterates an array and invoke the given callback function for each item. Note that this will simply
+ * delegate to the native Array.prototype.forEach method if supported.
+ * It doesn't support stopping the iteration by returning false in the callback function like
+ * {@link Ext.Array#each}. However, performance could be much better in modern browsers comparing with
+ * {@link Ext.Array#each}
+ *
+ * @param {Array} array The array to iterate
+ * @param {Function} fn The function callback, to be invoked these arguments:
+ *
+- `item`: {Mixed} The item at the current `index` in the passed `array`
+- `index`: {Number} The current `index` within the `array`
+- `allItems`: {Array} The `array` itself which was passed as the first argument
+
+ * @param {Object} scope (Optional) The execution scope (`this`) in which the specified function is executed.
+ * @markdown
+ */
+ forEach: function(array, fn, scope) {
+ if (supportsForEach) {
+ return array.forEach(fn, scope);
+ }
+
+ var i = 0,
+ ln = array.length;
+
+ for (; i < ln; i++) {
+ fn.call(scope, array[i], i, array);
+ }
+ },
+
+ /**
+ * Get the index of the provided `item` in the given `array`, a supplement for the
+ * missing arrayPrototype.indexOf in Internet Explorer.
+ *
+ * @param {Array} array The array to check
+ * @param {Mixed} item The item to look for
+ * @param {Number} from (Optional) The index at which to begin the search
+ * @return {Number} The index of item in the array (or -1 if it is not found)
+ * @markdown
+ */
+ indexOf: function(array, item, from) {
+ if (supportsIndexOf) {
+ return array.indexOf(item, from);
+ }
+
+ var i, length = array.length;
+
+ for (i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++) {
+ if (array[i] === item) {
+ return i;
+ }
+ }
+
+ return -1;
+ },
+
+ /**
+ * Checks whether or not the given `array` contains the specified `item`
+ *
+ * @param {Array} array The array to check
+ * @param {Mixed} item The item to look for
+ * @return {Boolean} True if the array contains the item, false otherwise
+ * @markdown
+ */
+ contains: function(array, item) {
+ if (supportsIndexOf) {
+ return array.indexOf(item) !== -1;
+ }
+
+ var i, ln;
+
+ for (i = 0, ln = array.length; i < ln; i++) {
+ if (array[i] === item) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Converts any iterable (numeric indices and a length property) into a true array.
+
+function test() {
+ var args = Ext.Array.toArray(arguments),
+ fromSecondToLastArgs = Ext.Array.toArray(arguments, 1);
+
+ alert(args.join(' '));
+ alert(fromSecondToLastArgs.join(' '));
+}
+
+test('just', 'testing', 'here'); // alerts 'just testing here';
+ // alerts 'testing here';
+
+Ext.Array.toArray(document.getElementsByTagName('div')); // will convert the NodeList into an array
+Ext.Array.toArray('splitted'); // returns ['s', 'p', 'l', 'i', 't', 't', 'e', 'd']
+Ext.Array.toArray('splitted', 0, 3); // returns ['s', 'p', 'l', 'i']
+
+ * @param {Mixed} iterable the iterable object to be turned into a true Array.
+ * @param {Number} start (Optional) a zero-based index that specifies the start of extraction. Defaults to 0
+ * @param {Number} end (Optional) a zero-based index that specifies the end of extraction. Defaults to the last
+ * index of the iterable value
+ * @return {Array} array
+ * @markdown
+ */
+ toArray: function(iterable, start, end){
+ if (!iterable || !iterable.length) {
+ return [];
+ }
+
+ if (typeof iterable === 'string') {
+ iterable = iterable.split('');
+ }
+
+ if (supportsSliceOnNodeList) {
+ return slice.call(iterable, start || 0, end || iterable.length);
+ }
+
+ var array = [],
+ i;
+
+ start = start || 0;
+ end = end ? ((end < 0) ? iterable.length + end : end) : iterable.length;
+
+ for (i = start; i < end; i++) {
+ array.push(iterable[i]);
+ }
+
+ return array;
+ },
+
+ /**
+ * Plucks the value of a property from each item in the Array. Example:
+ *
+ Ext.Array.pluck(Ext.query("p"), "className"); // [el1.className, el2.className, ..., elN.className]
+
+ * @param {Array|NodeList} array The Array of items to pluck the value from.
+ * @param {String} propertyName The property name to pluck from each element.
+ * @return {Array} The value from each item in the Array.
+ */
+ pluck: function(array, propertyName) {
+ var ret = [],
+ i, ln, item;
+
+ for (i = 0, ln = array.length; i < ln; i++) {
+ item = array[i];
+
+ ret.push(item[propertyName]);
+ }
+
+ return ret;
+ },
+
+ /**
+ * Creates a new array with the results of calling a provided function on every element in this array.
+ * @param {Array} array
+ * @param {Function} fn Callback function for each item
+ * @param {Object} scope Callback function scope
+ * @return {Array} results
+ */
+ map: function(array, fn, scope) {
+ if (supportsMap) {
+ return array.map(fn, scope);
+ }
+
+ var results = [],
+ i = 0,
+ len = array.length;
+
+ for (; i < len; i++) {
+ results[i] = fn.call(scope, array[i], i, array);
+ }
+
+ return results;
+ },
+
+ /**
+ * Executes the specified function for each array element until the function returns a falsy value.
+ * If such an item is found, the function will return false immediately.
+ * Otherwise, it will return true.
+ *
+ * @param {Array} array
+ * @param {Function} fn Callback function for each item
+ * @param {Object} scope Callback function scope
+ * @return {Boolean} True if no false value is returned by the callback function.
+ */
+ every: function(array, fn, scope) {
+ if (!fn) {
+ Ext.Error.raise('Ext.Array.every must have a callback function passed as second argument.');
+ }
+ if (supportsEvery) {
+ return array.every(fn, scope);
+ }
+
+ var i = 0,
+ ln = array.length;
+
+ for (; i < ln; ++i) {
+ if (!fn.call(scope, array[i], i, array)) {
+ return false;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Executes the specified function for each array element until the function returns a truthy value.
+ * If such an item is found, the function will return true immediately. Otherwise, it will return false.
+ *
+ * @param {Array} array
+ * @param {Function} fn Callback function for each item
+ * @param {Object} scope Callback function scope
+ * @return {Boolean} True if the callback function returns a truthy value.
+ */
+ some: function(array, fn, scope) {
+ if (!fn) {
+ Ext.Error.raise('Ext.Array.some must have a callback function passed as second argument.');
+ }
+ if (supportsSome) {
+ return array.some(fn, scope);
+ }
+
+ var i = 0,
+ ln = array.length;
+
+ for (; i < ln; ++i) {
+ if (fn.call(scope, array[i], i, array)) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Filter through an array and remove empty item as defined in {@link Ext#isEmpty Ext.isEmpty}
+ *
+ * @see Ext.Array.filter
+ * @param {Array} array
+ * @return {Array} results
+ */
+ clean: function(array) {
+ var results = [],
+ i = 0,
+ ln = array.length,
+ item;
+
+ for (; i < ln; i++) {
+ item = array[i];
+
+ if (!Ext.isEmpty(item)) {
+ results.push(item);
+ }
+ }
+
+ return results;
+ },
+
+ /**
+ * Returns a new array with unique items
+ *
+ * @param {Array} array
+ * @return {Array} results
+ */
+ unique: function(array) {
+ var clone = [],
+ i = 0,
+ ln = array.length,
+ item;
+
+ for (; i < ln; i++) {
+ item = array[i];
+
+ if (ExtArray.indexOf(clone, item) === -1) {
+ clone.push(item);
+ }
+ }
+
+ return clone;
+ },
+
+ /**
+ * Creates a new array with all of the elements of this array for which
+ * the provided filtering function returns true.
+ * @param {Array} array
+ * @param {Function} fn Callback function for each item
+ * @param {Object} scope Callback function scope
+ * @return {Array} results
+ */
+ filter: function(array, fn, scope) {
+ if (supportsFilter) {
+ return array.filter(fn, scope);
+ }
+
+ var results = [],
+ i = 0,
+ ln = array.length;
+
+ for (; i < ln; i++) {
+ if (fn.call(scope, array[i], i, array)) {
+ results.push(array[i]);
+ }
+ }
+
+ return results;
+ },
+
+ /**
+ * Converts a value to an array if it's not already an array; returns:
+ *
+ * - An empty array if given value is `undefined` or `null`
+ * - Itself if given value is already an array
+ * - An array copy if given value is {@link Ext#isIterable iterable} (arguments, NodeList and alike)
+ * - An array with one item which is the given value, otherwise
+ *
+ * @param {Array/Mixed} value The value to convert to an array if it's not already is an array
+ * @param {Boolean} (Optional) newReference True to clone the given array and return a new reference if necessary,
+ * defaults to false
+ * @return {Array} array
+ * @markdown
+ */
+ from: function(value, newReference) {
+ if (value === undefined || value === null) {
+ return [];
+ }
+
+ if (Ext.isArray(value)) {
+ return (newReference) ? slice.call(value) : value;
+ }
+
+ if (value && value.length !== undefined && typeof value !== 'string') {
+ return Ext.toArray(value);
+ }
+
+ return [value];
+ },
+
+ /**
+ * Removes the specified item from the array if it exists
+ *
+ * @param {Array} array The array
+ * @param {Mixed} item The item to remove
+ * @return {Array} The passed array itself
+ */
+ remove: function(array, item) {
+ var index = ExtArray.indexOf(array, item);
+
+ if (index !== -1) {
+ array.splice(index, 1);
+ }
+
+ return array;
+ },
+
+ /**
+ * Push an item into the array only if the array doesn't contain it yet
+ *
+ * @param {Array} array The array
+ * @param {Mixed} item The item to include
+ * @return {Array} The passed array itself
+ */
+ include: function(array, item) {
+ if (!ExtArray.contains(array, item)) {
+ array.push(item);
+ }
+ },
+
+ /**
+ * Clone a flat array without referencing the previous one. Note that this is different
+ * from Ext.clone since it doesn't handle recursive cloning. It's simply a convenient, easy-to-remember method
+ * for Array.prototype.slice.call(array)
+ *
+ * @param {Array} array The array
+ * @return {Array} The clone array
+ */
+ clone: function(array) {
+ return slice.call(array);
+ },
+
+ /**
+ * Merge multiple arrays into one with unique items. Alias to {@link Ext.Array#union}.
+ *
+ * @param {Array} array,...
+ * @return {Array} merged
+ */
+ merge: function() {
+ var args = slice.call(arguments),
+ array = [],
+ i, ln;
+
+ for (i = 0, ln = args.length; i < ln; i++) {
+ array = array.concat(args[i]);
+ }
+
+ return ExtArray.unique(array);
+ },
+
+ /**
+ * Merge multiple arrays into one with unique items that exist in all of the arrays.
+ *
+ * @param {Array} array,...
+ * @return {Array} intersect
+ */
+ intersect: function() {
+ var intersect = [],
+ arrays = slice.call(arguments),
+ i, j, k, minArray, array, x, y, ln, arraysLn, arrayLn;
+
+ if (!arrays.length) {
+ return intersect;
+ }
+
+ // Find the smallest array
+ for (i = x = 0,ln = arrays.length; i < ln,array = arrays[i]; i++) {
+ if (!minArray || array.length < minArray.length) {
+ minArray = array;
+ x = i;
+ }
+ }
+
+ minArray = Ext.Array.unique(minArray);
+ arrays.splice(x, 1);
+
+ // Use the smallest unique'd array as the anchor loop. If the other array(s) do contain
+ // an item in the small array, we're likely to find it before reaching the end
+ // of the inner loop and can terminate the search early.
+ for (i = 0,ln = minArray.length; i < ln,x = minArray[i]; i++) {
+ var count = 0;
+
+ for (j = 0,arraysLn = arrays.length; j < arraysLn,array = arrays[j]; j++) {
+ for (k = 0,arrayLn = array.length; k < arrayLn,y = array[k]; k++) {
+ if (x === y) {
+ count++;
+ break;
+ }
+ }
+ }
+
+ if (count === arraysLn) {
+ intersect.push(x);
+ }
+ }
+
+ return intersect;
+ },
+
+ /**
+ * Perform a set difference A-B by subtracting all items in array B from array A.
+ *
+ * @param {Array} array A
+ * @param {Array} array B
+ * @return {Array} difference
+ */
+ difference: function(arrayA, arrayB) {
+ var clone = slice.call(arrayA),
+ ln = clone.length,
+ i, j, lnB;
+
+ for (i = 0,lnB = arrayB.length; i < lnB; i++) {
+ for (j = 0; j < ln; j++) {
+ if (clone[j] === arrayB[i]) {
+ clone.splice(j, 1);
+ j--;
+ ln--;
+ }
+ }
+ }
+
+ return clone;
+ },
+
+ /**
+ * Sorts the elements of an Array.
+ * By default, this method sorts the elements alphabetically and ascending.
+ *
+ * @param {Array} array The array to sort.
+ * @param {Function} sortFn (optional) The comparison function.
+ * @return {Array} The sorted array.
+ */
+ sort: function(array, sortFn) {
+ if (supportsSort) {
+ if (sortFn) {
+ return array.sort(sortFn);
+ } else {
+ return array.sort();
+ }
+ }
+
+ var length = array.length,
+ i = 0,
+ comparison,
+ j, min, tmp;
+
+ for (; i < length; i++) {
+ min = i;
+ for (j = i + 1; j < length; j++) {
+ if (sortFn) {
+ comparison = sortFn(array[j], array[min]);
+ if (comparison < 0) {
+ min = j;
+ }
+ } else if (array[j] < array[min]) {
+ min = j;
+ }
+ }
+ if (min !== i) {
+ tmp = array[i];
+ array[i] = array[min];
+ array[min] = tmp;
+ }
+ }
+
+ return array;
+ },
+
+ /**
+ * Recursively flattens into 1-d Array. Injects Arrays inline.
+ * @param {Array} array The array to flatten
+ * @return {Array} The new, flattened array.
+ */
+ flatten: function(array) {
+ var worker = [];
+
+ function rFlatten(a) {
+ var i, ln, v;
+
+ for (i = 0, ln = a.length; i < ln; i++) {
+ v = a[i];
+
+ if (Ext.isArray(v)) {
+ rFlatten(v);
+ } else {
+ worker.push(v);
+ }
+ }
+
+ return worker;
+ }
+
+ return rFlatten(array);
+ },
+
+ /**
+ * Returns the minimum value in the Array.
+ * @param {Array|NodeList} array The Array from which to select the minimum value.
+ * @param {Function} comparisonFn (optional) a function to perform the comparision which determines minimization.
+ * If omitted the "<" operator will be used. Note: gt = 1; eq = 0; lt = -1
+ * @return {Mixed} minValue The minimum value
+ */
+ min: function(array, comparisonFn) {
+ var min = array[0],
+ i, ln, item;
+
+ for (i = 0, ln = array.length; i < ln; i++) {
+ item = array[i];
+
+ if (comparisonFn) {
+ if (comparisonFn(min, item) === 1) {
+ min = item;
+ }
+ }
+ else {
+ if (item < min) {
+ min = item;
+ }
+ }
+ }
+
+ return min;
+ },
+
+ /**
+ * Returns the maximum value in the Array
+ * @param {Array|NodeList} array The Array from which to select the maximum value.
+ * @param {Function} comparisonFn (optional) a function to perform the comparision which determines maximization.
+ * If omitted the ">" operator will be used. Note: gt = 1; eq = 0; lt = -1
+ * @return {Mixed} maxValue The maximum value
+ */
+ max: function(array, comparisonFn) {
+ var max = array[0],
+ i, ln, item;
+
+ for (i = 0, ln = array.length; i < ln; i++) {
+ item = array[i];
+
+ if (comparisonFn) {
+ if (comparisonFn(max, item) === -1) {
+ max = item;
+ }
+ }
+ else {
+ if (item > max) {
+ max = item;
+ }
+ }
+ }
+
+ return max;
+ },
+
+ /**
+ * Calculates the mean of all items in the array
+ * @param {Array} array The Array to calculate the mean value of.
+ * @return {Number} The mean.
+ */
+ mean: function(array) {
+ return array.length > 0 ? ExtArray.sum(array) / array.length : undefined;
+ },
+
+ /**
+ * Calculates the sum of all items in the given array
+ * @param {Array} array The Array to calculate the sum value of.
+ * @return {Number} The sum.
+ */
+ sum: function(array) {
+ var sum = 0,
+ i, ln, item;
+
+ for (i = 0,ln = array.length; i < ln; i++) {
+ item = array[i];
+
+ sum += item;
+ }
+
+ return sum;
+ }
+
+ };
+
+ /**
+ * Convenient alias to {@link Ext.Array#each}
+ * @member Ext
+ * @method each
+ */
+ Ext.each = Ext.Array.each;
+
+ /**
+ * Alias to {@link Ext.Array#merge}.
+ * @member Ext.Array
+ * @method union
+ */
+ Ext.Array.union = Ext.Array.merge;
+
+ /**
+ * Old alias to {@link Ext.Array#min}
+ * @deprecated 4.0.0 Use {@link Ext.Array#min} instead
+ * @member Ext
+ * @method min
+ */
+ Ext.min = Ext.Array.min;
+
+ /**
+ * Old alias to {@link Ext.Array#max}
+ * @deprecated 4.0.0 Use {@link Ext.Array#max} instead
+ * @member Ext
+ * @method max
+ */
+ Ext.max = Ext.Array.max;
+
+ /**
+ * Old alias to {@link Ext.Array#sum}
+ * @deprecated 4.0.0 Use {@link Ext.Array#sum} instead
+ * @member Ext
+ * @method sum
+ */
+ Ext.sum = Ext.Array.sum;
+
+ /**
+ * Old alias to {@link Ext.Array#mean}
+ * @deprecated 4.0.0 Use {@link Ext.Array#mean} instead
+ * @member Ext
+ * @method mean
+ */
+ Ext.mean = Ext.Array.mean;
+
+ /**
+ * Old alias to {@link Ext.Array#flatten}
+ * @deprecated 4.0.0 Use {@link Ext.Array#flatten} instead
+ * @member Ext
+ * @method flatten
+ */
+ Ext.flatten = Ext.Array.flatten;
+
+ /**
+ * Old alias to {@link Ext.Array#clean Ext.Array.clean}
+ * @deprecated 4.0.0 Use {@link Ext.Array.clean} instead
+ * @member Ext
+ * @method clean
+ */
+ Ext.clean = Ext.Array.clean;
+
+ /**
+ * Old alias to {@link Ext.Array#unique Ext.Array.unique}
+ * @deprecated 4.0.0 Use {@link Ext.Array.unique} instead
+ * @member Ext
+ * @method unique
+ */
+ Ext.unique = Ext.Array.unique;
+
+ /**
+ * Old alias to {@link Ext.Array#pluck Ext.Array.pluck}
+ * @deprecated 4.0.0 Use {@link Ext.Array#pluck Ext.Array.pluck} instead
+ * @member Ext
+ * @method pluck
+ */
+ Ext.pluck = Ext.Array.pluck;
+
+ /**
+ * Convenient alias to {@link Ext.Array#toArray Ext.Array.toArray}
+ * @param {Iterable} the iterable object to be turned into a true Array.
+ * @member Ext
+ * @method toArray
+ * @return {Array} array
+ */
+ Ext.toArray = function() {
+ return ExtArray.toArray.apply(ExtArray, arguments);
+ }
+})();
+
+/**
+ * @class Ext.Function
+ *
+ * A collection of useful static methods to deal with function callbacks
+ * @singleton
+ */
+
+Ext.Function = {
+
+ /**
+ * A very commonly used method throughout the framework. It acts as a wrapper around another method
+ * which originally accepts 2 arguments for <code>name</code> and <code>value</code>.
+ * The wrapped function then allows "flexible" value setting of either:
+ *
+ * <ul>
+ * <li><code>name</code> and <code>value</code> as 2 arguments</li>
+ * <li>one single object argument with multiple key - value pairs</li>
+ * </ul>
+ *
+ * For example:
+ * <pre><code>
+var setValue = Ext.Function.flexSetter(function(name, value) {
+ this[name] = value;
+});
+
+// Afterwards
+// Setting a single name - value
+setValue('name1', 'value1');
+
+// Settings multiple name - value pairs
+setValue({
+ name1: 'value1',
+ name2: 'value2',
+ name3: 'value3'
+});
+ * </code></pre>
+ * @param {Function} setter
+ * @returns {Function} flexSetter
+ */
+ flexSetter: function(fn) {
+ return function(a, b) {
+ var k, i;
+
+ if (a === null) {
+ return this;
+ }
+
+ if (typeof a !== 'string') {
+ for (k in a) {
+ if (a.hasOwnProperty(k)) {
+ fn.call(this, k, a[k]);
+ }
+ }
+
+ if (Ext.enumerables) {
+ for (i = Ext.enumerables.length; i--;) {
+ k = Ext.enumerables[i];
+ if (a.hasOwnProperty(k)) {
+ fn.call(this, k, a[k]);
+ }
+ }
+ }
+ } else {
+ fn.call(this, a, b);
+ }
+
+ return this;
+ };
+ },
+
+ /**
+ * Create a new function from the provided <code>fn</code>, change <code>this</code> to the provided scope, optionally
+ * overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ *
+ * @param {Function} fn The function to delegate.
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * <b>If omitted, defaults to the browser window.</b>
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Function} The new function
+ */
+ bind: function(fn, scope, args, appendArgs) {
+ var method = fn,
+ applyArgs;
+
+ return function() {
+ var callArgs = args || arguments;
+
+ if (appendArgs === true) {
+ callArgs = Array.prototype.slice.call(arguments, 0);
+ callArgs = callArgs.concat(args);
+ }
+ else if (Ext.isNumber(appendArgs)) {
+ callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
+ applyArgs = [appendArgs, 0].concat(args); // create method call params
+ Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
+ }
+
+ return method.apply(scope || window, callArgs);
+ };
+ },
+
+ /**
+ * Create a new function from the provided <code>fn</code>, the arguments of which are pre-set to `args`.
+ * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
+ * This is especially useful when creating callbacks.
+ * For example:
+ *
+ var originalFunction = function(){
+ alert(Ext.Array.from(arguments).join(' '));
+ };
+
+ var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
+
+ callback(); // alerts 'Hello World'
+ callback('by Me'); // alerts 'Hello World by Me'
+
+ * @param {Function} fn The original function
+ * @param {Array} args The arguments to pass to new callback
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * @return {Function} The new callback function
+ */
+ pass: function(fn, args, scope) {
+ if (args) {
+ args = Ext.Array.from(args);
+ }
+
+ return function() {
+ return fn.apply(scope, args.concat(Ext.Array.toArray(arguments)));
+ };
+ },
+
+ /**
+ * Create an alias to the provided method property with name <code>methodName</code> of <code>object</code>.
+ * Note that the execution scope will still be bound to the provided <code>object</code> itself.
+ *
+ * @param {Object/Function} object
+ * @param {String} methodName
+ * @return {Function} aliasFn
+ */
+ alias: function(object, methodName) {
+ return function() {
+ return object[methodName].apply(object, arguments);
+ };
+ },
+
+ /**
+ * Creates an interceptor function. The passed function is called before the original one. If it returns false,
+ * the original one is not called. The resulting function returns the results of the original function.
+ * The passed function is called with the parameters of the original function. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+sayHi('Fred'); // alerts "Hi, Fred"
+
+// create a new function that validates input without
+// directly modifying the original function:
+var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
+ return name == 'Brian';
+});
+
+sayHiToFriend('Fred'); // no alert
+sayHiToFriend('Brian'); // alerts "Hi, Brian"
+ </code></pre>
+ * @param {Function} origFn The original function.
+ * @param {Function} newFn The function to call before the original
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
+ * <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
+ * @param {Mixed} returnValue (optional) The value to return if the passed function return false (defaults to null).
+ * @return {Function} The new function
+ */
+ createInterceptor: function(origFn, newFn, scope, returnValue) {
+ var method = origFn;
+ if (!Ext.isFunction(newFn)) {
+ return origFn;
+ }
+ else {
+ return function() {
+ var me = this,
+ args = arguments;
+ newFn.target = me;
+ newFn.method = origFn;
+ return (newFn.apply(scope || me || window, args) !== false) ? origFn.apply(me || window, args) : returnValue || null;
+ };
+ }
+ },
+
+ /**
+ * Creates a delegate (callback) which, when called, executes after a specific delay.
+ * @param {Function} fn The function which will be called on a delay when the returned function is called.
+ * Optionally, a replacement (or additional) argument list may be specified.
+ * @param {Number} delay The number of milliseconds to defer execution by whenever called.
+ * @param {Object} scope (optional) The scope (<code>this</code> reference) used by the function at execution time.
+ * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position.
+ * @return {Function} A function which, when called, executes the original function after the specified delay.
+ */
+ createDelayed: function(fn, delay, scope, args, appendArgs) {
+ if (scope || args) {
+ fn = Ext.Function.bind(fn, scope, args, appendArgs);
+ }
+ return function() {
+ var me = this;
+ setTimeout(function() {
+ fn.apply(me, arguments);
+ }, delay);
+ };
+ },
+
+ /**
+ * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+// executes immediately:
+sayHi('Fred');
+
+// executes after 2 seconds:
+Ext.Function.defer(sayHi, 2000, this, ['Fred']);
+
+// this syntax is sometimes useful for deferring
+// execution of an anonymous function:
+Ext.Function.defer(function(){
+ alert('Anonymous');
+}, 100);
+ </code></pre>
+ * @param {Function} fn The function to defer.
+ * @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * <b>If omitted, defaults to the browser window.</b>
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Number} The timeout id that can be used with clearTimeout
+ */
+ defer: function(fn, millis, obj, args, appendArgs) {
+ fn = Ext.Function.bind(fn, obj, args, appendArgs);
+ if (millis > 0) {
+ return setTimeout(fn, millis);
+ }
+ fn();
+ return 0;
+ },
+
+ /**
+ * Create a combined function call sequence of the original function + the passed function.
+ * The resulting function returns the results of the original function.
+ * The passed function is called with the parameters of the original function. Example usage:
+ *
+ * <pre><code>
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+sayHi('Fred'); // alerts "Hi, Fred"
+
+var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
+ alert('Bye, ' + name);
+});
+
+sayGoodbye('Fred'); // both alerts show
+ * </code></pre>
+ *
+ * @param {Function} origFn The original function.
+ * @param {Function} newFn The function to sequence
+ * @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
+ * If omitted, defaults to the scope in which the original function is called or the browser window.
+ * @return {Function} The new function
+ */
+ createSequence: function(origFn, newFn, scope) {
+ if (!Ext.isFunction(newFn)) {
+ return origFn;
+ }
+ else {
+ return function() {
+ var retval = origFn.apply(this || window, arguments);
+ newFn.apply(scope || this || window, arguments);
+ return retval;
+ };
+ }
+ },
+
+ /**
+ * <p>Creates a delegate function, optionally with a bound scope which, when called, buffers
+ * the execution of the passed function for the configured number of milliseconds.
+ * If called again within that period, the impending invocation will be canceled, and the
+ * timeout period will begin again.</p>
+ *
+ * @param {Function} fn The function to invoke on a buffered timer.
+ * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
+ * function.
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which
+ * the passed function is executed. If omitted, defaults to the scope specified by the caller.
+ * @param {Array} args (optional) Override arguments for the call. Defaults to the arguments
+ * passed by the caller.
+ * @return {Function} A function which invokes the passed function after buffering for the specified time.
+ */
+ createBuffered: function(fn, buffer, scope, args) {
+ return function(){
+ var timerId;
+ return function() {
+ var me = this;
+ if (timerId) {
+ clearInterval(timerId);
+ timerId = null;
+ }
+ timerId = setTimeout(function(){
+ fn.apply(scope || me, args || arguments);
+ }, buffer);
+ };
+ }();
+ },
+
+ /**
+ * <p>Creates a throttled version of the passed function which, when called repeatedly and
+ * rapidly, invokes the passed function only after a certain interval has elapsed since the
+ * previous invocation.</p>
+ *
+ * <p>This is useful for wrapping functions which may be called repeatedly, such as
+ * a handler of a mouse move event when the processing is expensive.</p>
+ *
+ * @param fn {Function} The function to execute at a regular time interval.
+ * @param interval {Number} The interval <b>in milliseconds</b> on which the passed function is executed.
+ * @param scope (optional) The scope (<code><b>this</b></code> reference) in which
+ * the passed function is executed. If omitted, defaults to the scope specified by the caller.
+ * @returns {Function} A function which invokes the passed function at the specified interval.
+ */
+ createThrottled: function(fn, interval, scope) {
+ var lastCallTime, elapsed, lastArgs, timer, execute = function() {
+ fn.apply(scope || this, lastArgs);
+ lastCallTime = new Date().getTime();
+ };
+
+ return function() {
+ elapsed = new Date().getTime() - lastCallTime;
+ lastArgs = arguments;
+
+ clearTimeout(timer);
+ if (!lastCallTime || (elapsed >= interval)) {
+ execute();
+ } else {
+ timer = setTimeout(execute, interval - elapsed);
+ }
+ };
+ }
+};
+
+/**
+ * Shorthand for {@link Ext.Function#defer}
+ * @member Ext
+ * @method defer
+ */
+Ext.defer = Ext.Function.alias(Ext.Function, 'defer');
+
+/**
+ * Shorthand for {@link Ext.Function#pass}
+ * @member Ext
+ * @method pass
+ */
+Ext.pass = Ext.Function.alias(Ext.Function, 'pass');
+
+/**
+ * Shorthand for {@link Ext.Function#bind}
+ * @member Ext
+ * @method bind
+ */
+Ext.bind = Ext.Function.alias(Ext.Function, 'bind');
+
+/**
+ * @author Jacky Nguyen <jacky@sencha.com>
+ * @docauthor Jacky Nguyen <jacky@sencha.com>
+ * @class Ext.Object
+ *
+ * A collection of useful static methods to deal with objects
+ *
+ * @singleton
+ */
+
+(function() {
+
+var ExtObject = Ext.Object = {
+
+ /**
+ * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct
+ * query strings. For example:
+
+ var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']);
+
+ // objects then equals:
+ [
+ { name: 'hobbies', value: 'reading' },
+ { name: 'hobbies', value: 'cooking' },
+ { name: 'hobbies', value: 'swimming' },
+ ];
+
+ var objects = Ext.Object.toQueryObjects('dateOfBirth', {
+ day: 3,
+ month: 8,
+ year: 1987,
+ extra: {
+ hour: 4
+ minute: 30
+ }
+ }, true); // Recursive
+
+ // objects then equals:
+ [
+ { name: 'dateOfBirth[day]', value: 3 },
+ { name: 'dateOfBirth[month]', value: 8 },
+ { name: 'dateOfBirth[year]', value: 1987 },
+ { name: 'dateOfBirth[extra][hour]', value: 4 },
+ { name: 'dateOfBirth[extra][minute]', value: 30 },
+ ];
+
+ * @param {String} name
+ * @param {Mixed} value
+ * @param {Boolean} recursive
+ * @markdown
+ */
+ toQueryObjects: function(name, value, recursive) {
+ var self = ExtObject.toQueryObjects,
+ objects = [],
+ i, ln;
+
+ if (Ext.isArray(value)) {
+ for (i = 0, ln = value.length; i < ln; i++) {
+ if (recursive) {
+ objects = objects.concat(self(name + '[' + i + ']', value[i], true));
+ }
+ else {
+ objects.push({
+ name: name,
+ value: value[i]
+ });
+ }
+ }
+ }
+ else if (Ext.isObject(value)) {
+ for (i in value) {
+ if (value.hasOwnProperty(i)) {
+ if (recursive) {
+ objects = objects.concat(self(name + '[' + i + ']', value[i], true));
+ }
+ else {
+ objects.push({
+ name: name,
+ value: value[i]
+ });
+ }
+ }
+ }
+ }
+ else {
+ objects.push({
+ name: name,
+ value: value
+ });
+ }
+
+ return objects;
+ },
+
+ /**
+ * Takes an object and converts it to an encoded query string
+
+- Non-recursive:
+
+ Ext.Object.toQueryString({foo: 1, bar: 2}); // returns "foo=1&bar=2"
+ Ext.Object.toQueryString({foo: null, bar: 2}); // returns "foo=&bar=2"
+ Ext.Object.toQueryString({'some price': '$300'}); // returns "some%20price=%24300"
+ Ext.Object.toQueryString({date: new Date(2011, 0, 1)}); // returns "date=%222011-01-01T00%3A00%3A00%22"
+ Ext.Object.toQueryString({colors: ['red', 'green', 'blue']}); // returns "colors=red&colors=green&colors=blue"
+
+- Recursive:
+
+ Ext.Object.toQueryString({
+ username: 'Jacky',
+ dateOfBirth: {
+ day: 1,
+ month: 2,
+ year: 1911
+ },
+ hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
+ }, true); // returns the following string (broken down and url-decoded for ease of reading purpose):
+ // 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
+
+ *
+ * @param {Object} object The object to encode
+ * @param {Boolean} recursive (optional) Whether or not to interpret the object in recursive format.
+ * (PHP / Ruby on Rails servers and similar). Defaults to false
+ * @return {String} queryString
+ * @markdown
+ */
+ toQueryString: function(object, recursive) {
+ var paramObjects = [],
+ params = [],
+ i, j, ln, paramObject, value;
+
+ for (i in object) {
+ if (object.hasOwnProperty(i)) {
+ paramObjects = paramObjects.concat(ExtObject.toQueryObjects(i, object[i], recursive));
+ }
+ }
+
+ for (j = 0, ln = paramObjects.length; j < ln; j++) {
+ paramObject = paramObjects[j];
+ value = paramObject.value;
+
+ if (Ext.isEmpty(value)) {
+ value = '';
+ }
+ else if (Ext.isDate(value)) {
+ value = Ext.Date.toString(value);
+ }
+
+ params.push(encodeURIComponent(paramObject.name) + '=' + encodeURIComponent(String(value)));
+ }
+
+ return params.join('&');
+ },
+
+ /**
+ * Converts a query string back into an object.
+ *
+- Non-recursive:
+
+ Ext.Object.fromQueryString(foo=1&bar=2); // returns {foo: 1, bar: 2}
+ Ext.Object.fromQueryString(foo=&bar=2); // returns {foo: null, bar: 2}
+ Ext.Object.fromQueryString(some%20price=%24300); // returns {'some price': '$300'}
+ Ext.Object.fromQueryString(colors=red&colors=green&colors=blue); // returns {colors: ['red', 'green', 'blue']}
+
+- Recursive:
+
+ 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);
+
+ // returns
+ {
+ username: 'Jacky',
+ dateOfBirth: {
+ day: '1',
+ month: '2',
+ year: '1911'
+ },
+ hobbies: ['coding', 'eating', 'sleeping', ['nested', 'stuff']]
+ }
+
+ * @param {String} queryString The query string to decode
+ * @param {Boolean} recursive (Optional) Whether or not to recursively decode the string. This format is supported by
+ * PHP / Ruby on Rails servers and similar. Defaults to false
+ * @return {Object}
+ */
+ fromQueryString: function(queryString, recursive) {
+ var parts = queryString.replace(/^\?/, '').split('&'),
+ object = {},
+ temp, components, name, value, i, ln,
+ part, j, subLn, matchedKeys, matchedName,
+ keys, key, nextKey;
+
+ for (i = 0, ln = parts.length; i < ln; i++) {
+ part = parts[i];
+
+ if (part.length > 0) {
+ components = part.split('=');
+ name = decodeURIComponent(components[0]);
+ value = (components[1] !== undefined) ? decodeURIComponent(components[1]) : '';
+
+ if (!recursive) {
+ if (object.hasOwnProperty(name)) {
+ if (!Ext.isArray(object[name])) {
+ object[name] = [object[name]];
+ }
+
+ object[name].push(value);
+ }
+ else {
+ object[name] = value;
+ }
+ }
+ else {
+ matchedKeys = name.match(/(\[):?([^\]]*)\]/g);
+ matchedName = name.match(/^([^\[]+)/);
+
+ if (!matchedName) {
+ Ext.Error.raise({
+ sourceClass: "Ext.Object",
+ sourceMethod: "fromQueryString",
+ queryString: queryString,
+ recursive: recursive,
+ msg: 'Malformed query string given, failed parsing name from "' + part + '"'
+ });
+ }
+
+ name = matchedName[0];
+ keys = [];
+
+ if (matchedKeys === null) {
+ object[name] = value;
+ continue;
+ }
+
+ for (j = 0, subLn = matchedKeys.length; j < subLn; j++) {
+ key = matchedKeys[j];
+ key = (key.length === 2) ? '' : key.substring(1, key.length - 1);
+ keys.push(key);
+ }
+
+ keys.unshift(name);
+
+ temp = object;
+
+ for (j = 0, subLn = keys.length; j < subLn; j++) {
+ key = keys[j];
+
+ if (j === subLn - 1) {
+ if (Ext.isArray(temp) && key === '') {
+ temp.push(value);
+ }
+ else {
+ temp[key] = value;
+ }
+ }
+ else {
+ if (temp[key] === undefined || typeof temp[key] === 'string') {
+ nextKey = keys[j+1];
+
+ temp[key] = (Ext.isNumeric(nextKey) || nextKey === '') ? [] : {};
+ }
+
+ temp = temp[key];
+ }
+ }
+ }
+ }
+ }
+
+ return object;
+ },
+
+ /**
+ * Iterate through an object and invoke the given callback function for each iteration. The iteration can be stop
+ * by returning `false` in the callback function. For example:
+
+ var person = {
+ name: 'Jacky'
+ hairColor: 'black'
+ loves: ['food', 'sleeping', 'wife']
+ };
+
+ Ext.Object.each(person, function(key, value, myself) {
+ console.log(key + ":" + value);
+
+ if (key === 'hairColor') {
+ return false; // stop the iteration
+ }
+ });
+
+ * @param {Object} object The object to iterate
+ * @param {Function} fn The callback function. Passed arguments for each iteration are:
+
+- {String} `key`
+- {Mixed} `value`
+- {Object} `object` The object itself
+
+ * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
+ * @markdown
+ */
+ each: function(object, fn, scope) {
+ for (var property in object) {
+ if (object.hasOwnProperty(property)) {
+ if (fn.call(scope || object, property, object[property], object) === false) {
+ return;
+ }
+ }
+ }
+ },
+
+ /**
+ * Merges any number of objects recursively without referencing them or their children.
+
+ var extjs = {
+ companyName: 'Ext JS',
+ products: ['Ext JS', 'Ext GWT', 'Ext Designer'],
+ isSuperCool: true
+ office: {
+ size: 2000,
+ location: 'Palo Alto',
+ isFun: true
+ }
+ };
+
+ var newStuff = {
+ companyName: 'Sencha Inc.',
+ products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
+ office: {
+ size: 40000,
+ location: 'Redwood City'
+ }
+ };
+
+ var sencha = Ext.Object.merge(extjs, newStuff);
+
+ // extjs and sencha then equals to
+ {
+ companyName: 'Sencha Inc.',
+ products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'],
+ isSuperCool: true
+ office: {
+ size: 30000,
+ location: 'Redwood City'
+ isFun: true
+ }
+ }
+
+ * @param {Object} object,...
+ * @return {Object} merged The object that is created as a result of merging all the objects passed in.
+ * @markdown
+ */
+ merge: function(source, key, value) {
+ if (typeof key === 'string') {
+ if (value && value.constructor === Object) {
+ if (source[key] && source[key].constructor === Object) {
+ ExtObject.merge(source[key], value);
+ }
+ else {
+ source[key] = Ext.clone(value);
+ }
+ }
+ else {
+ source[key] = value;
+ }
+
+ return source;
+ }
+
+ var i = 1,
+ ln = arguments.length,
+ object, property;
+
+ for (; i < ln; i++) {
+ object = arguments[i];
+
+ for (property in object) {
+ if (object.hasOwnProperty(property)) {
+ ExtObject.merge(source, property, object[property]);
+ }
+ }
+ }
+
+ return source;
+ },
+
+ /**
+ * Returns the first matching key corresponding to the given value.
+ * If no matching value is found, null is returned.
+
+ var person = {
+ name: 'Jacky',
+ loves: 'food'
+ };
+
+ alert(Ext.Object.getKey(sencha, 'loves')); // alerts 'food'
+
+ * @param {Object} object
+ * @param {Object} value The value to find
+ * @markdown
+ */
+ getKey: function(object, value) {
+ for (var property in object) {
+ if (object.hasOwnProperty(property) && object[property] === value) {
+ return property;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Gets all values of the given object as an array.
+
+ var values = Ext.Object.getValues({
+ name: 'Jacky',
+ loves: 'food'
+ }); // ['Jacky', 'food']
+
+ * @param {Object} object
+ * @return {Array} An array of values from the object
+ * @markdown
+ */
+ getValues: function(object) {
+ var values = [],
+ property;
+
+ for (property in object) {
+ if (object.hasOwnProperty(property)) {
+ values.push(object[property]);
+ }
+ }
+
+ return values;
+ },
+
+ /**
+ * Gets all keys of the given object as an array.
+
+ var values = Ext.Object.getKeys({
+ name: 'Jacky',
+ loves: 'food'
+ }); // ['name', 'loves']
+
+ * @param {Object} object
+ * @return {Array} An array of keys from the object
+ */
+ getKeys: ('keys' in Object.prototype) ? Object.keys : function(object) {
+ var keys = [],
+ property;
+
+ for (property in object) {
+ if (object.hasOwnProperty(property)) {
+ keys.push(property);
+ }
+ }
+
+ return keys;
+ },
+
+ /**
+ * Gets the total number of this object's own properties
+
+ var size = Ext.Object.getSize({
+ name: 'Jacky',
+ loves: 'food'
+ }); // size equals 2
+
+ * @param {Object} object
+ * @return {Number} size
+ * @markdown
+ */
+ getSize: function(object) {
+ var size = 0,
+ property;
+
+ for (property in object) {
+ if (object.hasOwnProperty(property)) {
+ size++;
+ }
+ }
+
+ return size;
+ }
+};
+
+
+/**
+ * A convenient alias method for {@link Ext.Object#merge}
+ *
+ * @member Ext
+ * @method merge
+ */
+Ext.merge = Ext.Object.merge;
+
+/**
+ * A convenient alias method for {@link Ext.Object#toQueryString}
+ *
+ * @member Ext
+ * @method urlEncode
+ * @deprecated 4.0.0 Use {@link Ext.Object#toQueryString Ext.Object.toQueryString} instead
+ */
+Ext.urlEncode = function() {
+ var args = Ext.Array.from(arguments),
+ prefix = '';
+
+ // Support for the old `pre` argument
+ if ((typeof args[1] === 'string')) {
+ prefix = args[1] + '&';
+ args[1] = false;
+ }
+
+ return prefix + Ext.Object.toQueryString.apply(Ext.Object, args);
+};
+
+/**
+ * A convenient alias method for {@link Ext.Object#fromQueryString}
+ *
+ * @member Ext
+ * @method urlDecode
+ * @deprecated 4.0.0 Use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead
+ */
+Ext.urlDecode = function() {
+ return Ext.Object.fromQueryString.apply(Ext.Object, arguments);
+};
+
+})();
+
+/**
+ * @class Ext.Date
+ * A set of useful static methods to deal with date
+ * Note that if Ext.Date is required and loaded, it will copy all methods / properties to
+ * this object for convenience
+ *
+ * The date parsing and formatting syntax contains a subset of
+ * <a href="http://www.php.net/date">PHP's date() function</a>, and the formats that are
+ * supported will provide results equivalent to their PHP versions.
+ *
+ * The following is a list of all currently supported formats:
+ * <pre class="">
+Format Description Example returned values
+------ ----------------------------------------------------------------------- -----------------------
+ d Day of the month, 2 digits with leading zeros 01 to 31
+ D A short textual representation of the day of the week Mon to Sun
+ j Day of the month without leading zeros 1 to 31
+ l A full textual representation of the day of the week Sunday to Saturday
+ N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday)
+ S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j
+ w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday)
+ z The day of the year (starting from 0) 0 to 364 (365 in leap years)
+ W ISO-8601 week number of year, weeks starting on Monday 01 to 53
+ F A full textual representation of a month, such as January or March January to December
+ m Numeric representation of a month, with leading zeros 01 to 12
+ M A short textual representation of a month Jan to Dec
+ n Numeric representation of a month, without leading zeros 1 to 12
+ t Number of days in the given month 28 to 31
+ L Whether it's a leap year 1 if it is a leap year, 0 otherwise.
+ o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004
+ belongs to the previous or next year, that year is used instead)
+ Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003
+ y A two digit representation of a year Examples: 99 or 03
+ a Lowercase Ante meridiem and Post meridiem am or pm
+ A Uppercase Ante meridiem and Post meridiem AM or PM
+ g 12-hour format of an hour without leading zeros 1 to 12
+ G 24-hour format of an hour without leading zeros 0 to 23
+ h 12-hour format of an hour with leading zeros 01 to 12
+ H 24-hour format of an hour with leading zeros 00 to 23
+ i Minutes, with leading zeros 00 to 59
+ s Seconds, with leading zeros 00 to 59
+ u Decimal fraction of a second Examples:
+ (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or
+ 100 (i.e. 0.100s) or
+ 999 (i.e. 0.999s) or
+ 999876543210 (i.e. 0.999876543210s)
+ O Difference to Greenwich time (GMT) in hours and minutes Example: +1030
+ P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00
+ T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ...
+ Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400
+ c ISO 8601 date
+ Notes: Examples:
+ 1) If unspecified, the month / day defaults to the current month / day, 1991 or
+ the time defaults to midnight, while the timezone defaults to the 1992-10 or
+ browser's timezone. If a time is specified, it must include both hours 1993-09-20 or
+ and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or
+ are optional. 1995-07-18T17:21:28-02:00 or
+ 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or
+ least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or
+ of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or
+ Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or
+ date-time granularity which are supported, or see 2000-02-13T21:25:33
+ http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34
+ U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463
+ MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or
+ \/Date(1238606590509+0800)\/
+</pre>
+ *
+ * Example usage (note that you must escape format specifiers with '\\' to render them as character literals):
+ * <pre><code>
+// Sample date:
+// 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
+
+var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
+console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
+console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
+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
+</code></pre>
+ *
+ * Here are some standard date/time patterns that you might find helpful. They
+ * are not part of the source of Ext.Date, but to use them you can simply copy this
+ * block of code into any script that is included after Ext.Date and they will also become
+ * globally available on the Date object. Feel free to add or remove patterns as needed in your code.
+ * <pre><code>
+Ext.Date.patterns = {
+ ISO8601Long:"Y-m-d H:i:s",
+ ISO8601Short:"Y-m-d",
+ ShortDate: "n/j/Y",
+ LongDate: "l, F d, Y",
+ FullDateTime: "l, F d, Y g:i:s A",
+ MonthDay: "F d",
+ ShortTime: "g:i A",
+ LongTime: "g:i:s A",
+ SortableDateTime: "Y-m-d\\TH:i:s",
+ UniversalSortableDateTime: "Y-m-d H:i:sO",
+ YearMonth: "F, Y"
+};
+</code></pre>
+ *
+ * Example usage:
+ * <pre><code>
+var dt = new Date();
+console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
+</code></pre>
+ * <p>Developer-written, custom formats may be used by supplying both a formatting and a parsing function
+ * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.</p>
+ * @singleton
+ */
+
+/*
+ * Most of the date-formatting functions below are the excellent work of Baron Schwartz.
+ * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/)
+ * They generate precompiled functions from format patterns instead of parsing and
+ * processing each pattern every time a date is formatted. These functions are available
+ * on every Date object.
+ */
+
+(function() {
+
+// create private copy of Ext's Ext.util.Format.format() method
+// - to remove unnecessary dependency
+// - to resolve namespace conflict with MS-Ajax's implementation
+function xf(format) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return format.replace(/\{(\d+)\}/g, function(m, i) {
+ return args[i];
+ });
+}
+
+Ext.Date = {
+ /**
+ * Returns the current timestamp
+ * @return {Date} The current timestamp
+ */
+ now: Date.now || function() {
+ return +new Date();
+ },
+
+ /**
+ * @private
+ * Private for now
+ */
+ toString: function(date) {
+ var pad = Ext.String.leftPad;
+
+ return date.getFullYear() + "-"
+ + pad(date.getMonth() + 1, 2, '0') + "-"
+ + pad(date.getDate(), 2, '0') + "T"
+ + pad(date.getHours(), 2, '0') + ":"
+ + pad(date.getMinutes(), 2, '0') + ":"
+ + pad(date.getSeconds(), 2, '0');
+ },
+
+ /**
+ * Returns the number of milliseconds between two dates
+ * @param {Date} dateA The first date
+ * @param {Date} dateB (optional) The second date, defaults to now
+ * @return {Number} The difference in milliseconds
+ */
+ getElapsed: function(dateA, dateB) {
+ return Math.abs(dateA - (dateB || new Date()));
+ },
+
+ /**
+ * Global flag which determines if strict date parsing should be used.
+ * Strict date parsing will not roll-over invalid dates, which is the
+ * default behaviour of javascript Date objects.
+ * (see {@link #parse} for more information)
+ * Defaults to <tt>false</tt>.
+ * @static
+ * @type Boolean
+ */
+ useStrict: false,
+
+ // private
+ formatCodeToRegex: function(character, currentGroup) {
+ // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below)
+ var p = utilDate.parseCodes[character];
+
+ if (p) {
+ p = typeof p == 'function'? p() : p;
+ utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution
+ }
+
+ return p ? Ext.applyIf({
+ c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
+ }, p) : {
+ g: 0,
+ c: null,
+ s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals
+ };
+ },
+
+ /**
+ * <p>An object hash in which each property is a date parsing function. The property name is the
+ * format string which that function parses.</p>
+ * <p>This object is automatically populated with date parsing functions as
+ * date formats are requested for Ext standard formatting strings.</p>
+ * <p>Custom parsing functions may be inserted into this object, keyed by a name which from then on
+ * may be used as a format string to {@link #parse}.<p>
+ * <p>Example:</p><pre><code>
+Ext.Date.parseFunctions['x-date-format'] = myDateParser;
+</code></pre>
+ * <p>A parsing function should return a Date object, and is passed the following parameters:<div class="mdetail-params"><ul>
+ * <li><code>date</code> : String<div class="sub-desc">The date string to parse.</div></li>
+ * <li><code>strict</code> : Boolean<div class="sub-desc">True to validate date strings while parsing
+ * (i.e. prevent javascript Date "rollover") (The default must be false).
+ * Invalid date strings should return null when parsed.</div></li>
+ * </ul></div></p>
+ * <p>To enable Dates to also be <i>formatted</i> according to that format, a corresponding
+ * formatting function must be placed into the {@link #formatFunctions} property.
+ * @property parseFunctions
+ * @static
+ * @type Object
+ */
+ parseFunctions: {
+ "MS": function(input, strict) {
+ // note: the timezone offset is ignored since the MS Ajax server sends
+ // a UTC milliseconds-since-Unix-epoch value (negative values are allowed)
+ var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
+ var r = (input || '').match(re);
+ return r? new Date(((r[1] || '') + r[2]) * 1) : null;
+ }
+ },
+ parseRegexes: [],
+
+ /**
+ * <p>An object hash in which each property is a date formatting function. The property name is the
+ * format string which corresponds to the produced formatted date string.</p>
+ * <p>This object is automatically populated with date formatting functions as
+ * date formats are requested for Ext standard formatting strings.</p>
+ * <p>Custom formatting functions may be inserted into this object, keyed by a name which from then on
+ * may be used as a format string to {@link #format}. Example:</p><pre><code>
+Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
+</code></pre>
+ * <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>
+ * <li><code>date</code> : Date<div class="sub-desc">The Date to format.</div></li>
+ * </ul></div></p>
+ * <p>To enable date strings to also be <i>parsed</i> according to that format, a corresponding
+ * parsing function must be placed into the {@link #parseFunctions} property.
+ * @property formatFunctions
+ * @static
+ * @type Object
+ */
+ formatFunctions: {
+ "MS": function() {
+ // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF))
+ return '\\/Date(' + this.getTime() + ')\\/';
+ }
+ },
+
+ y2kYear : 50,
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ MILLI : "ms",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ SECOND : "s",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ MINUTE : "mi",
+
+ /** Date interval constant
+ * @static
+ * @type String
+ */
+ HOUR : "h",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ DAY : "d",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ MONTH : "mo",
+
+ /**
+ * Date interval constant
+ * @static
+ * @type String
+ */
+ YEAR : "y",
+
+ /**
+ * <p>An object hash containing default date values used during date parsing.</p>
+ * <p>The following properties are available:<div class="mdetail-params"><ul>
+ * <li><code>y</code> : Number<div class="sub-desc">The default year value. (defaults to undefined)</div></li>
+ * <li><code>m</code> : Number<div class="sub-desc">The default 1-based month value. (defaults to undefined)</div></li>
+ * <li><code>d</code> : Number<div class="sub-desc">The default day value. (defaults to undefined)</div></li>
+ * <li><code>h</code> : Number<div class="sub-desc">The default hour value. (defaults to undefined)</div></li>
+ * <li><code>i</code> : Number<div class="sub-desc">The default minute value. (defaults to undefined)</div></li>
+ * <li><code>s</code> : Number<div class="sub-desc">The default second value. (defaults to undefined)</div></li>
+ * <li><code>ms</code> : Number<div class="sub-desc">The default millisecond value. (defaults to undefined)</div></li>
+ * </ul></div></p>
+ * <p>Override these properties to customize the default date values used by the {@link #parse} method.</p>
+ * <p><b>Note: In countries which experience Daylight Saving Time (i.e. DST), the <tt>h</tt>, <tt>i</tt>, <tt>s</tt>
+ * and <tt>ms</tt> properties may coincide with the exact time in which DST takes effect.
+ * It is the responsiblity of the developer to account for this.</b></p>
+ * Example Usage:
+ * <pre><code>
+// set default day value to the first day of the month
+Ext.Date.defaults.d = 1;
+
+// parse a February date string containing only year and month values.
+// setting the default day value to 1 prevents weird date rollover issues
+// when attempting to parse the following date string on, for example, March 31st 2009.
+Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
+</code></pre>
+ * @property defaults
+ * @static
+ * @type Object
+ */
+ defaults: {},
+
+ /**
+ * An array of textual day names.
+ * Override these values for international dates.
+ * Example:
+ * <pre><code>
+Ext.Date.dayNames = [
+ 'SundayInYourLang',
+ 'MondayInYourLang',
+ ...
+];
+</code></pre>
+ * @type Array
+ * @static
+ */
+ dayNames : [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday"
+ ],
+
+ /**
+ * An array of textual month names.
+ * Override these values for international dates.
+ * Example:
+ * <pre><code>
+Ext.Date.monthNames = [
+ 'JanInYourLang',
+ 'FebInYourLang',
+ ...
+];
+</code></pre>
+ * @type Array
+ * @static
+ */
+ monthNames : [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"
+ ],
+
+ /**
+ * An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
+ * Override these values for international dates.
+ * Example:
+ * <pre><code>
+Ext.Date.monthNumbers = {
+ 'ShortJanNameInYourLang':0,
+ 'ShortFebNameInYourLang':1,
+ ...
+};
+</code></pre>
+ * @type Object
+ * @static
+ */
+ monthNumbers : {
+ Jan:0,
+ Feb:1,
+ Mar:2,
+ Apr:3,
+ May:4,
+ Jun:5,
+ Jul:6,
+ Aug:7,
+ Sep:8,
+ Oct:9,
+ Nov:10,
+ Dec:11
+ },
+ /**
+ * <p>The date format string that the {@link #dateRenderer} and {@link #date} functions use.
+ * see {@link #Date} for details.</p>
+ * <p>This defaults to <code>m/d/Y</code>, but may be overridden in a locale file.</p>
+ * @property defaultFormat
+ * @static
+ * @type String
+ */
+ defaultFormat : "m/d/Y",
+ /**
+ * Get the short month name for the given month number.
+ * Override this function for international dates.
+ * @param {Number} month A zero-based javascript month number.
+ * @return {String} The short month name.
+ * @static
+ */
+ getShortMonthName : function(month) {
+ return utilDate.monthNames[month].substring(0, 3);
+ },
+
+ /**
+ * Get the short day name for the given day number.
+ * Override this function for international dates.
+ * @param {Number} day A zero-based javascript day number.
+ * @return {String} The short day name.
+ * @static
+ */
+ getShortDayName : function(day) {
+ return utilDate.dayNames[day].substring(0, 3);
+ },
+
+ /**
+ * Get the zero-based javascript month number for the given short/full month name.
+ * Override this function for international dates.
+ * @param {String} name The short/full month name.
+ * @return {Number} The zero-based javascript month number.
+ * @static
+ */
+ getMonthNumber : function(name) {
+ // handle camel casing for english month names (since the keys for the Ext.Date.monthNumbers hash are case sensitive)
+ return utilDate.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
+ },
+
+ /**
+ * Checks if the specified format contains hour information
+ * @param {String} format The format to check
+ * @return {Boolean} True if the format contains hour information
+ * @static
+ */
+ formatContainsHourInfo : (function(){
+ var stripEscapeRe = /(\\.)/g,
+ hourInfoRe = /([gGhHisucUOPZ]|MS)/;
+ return function(format){
+ return hourInfoRe.test(format.replace(stripEscapeRe, ''));
+ };
+ })(),
+
+ /**
+ * Checks if the specified format contains information about
+ * anything other than the time.
+ * @param {String} format The format to check
+ * @return {Boolean} True if the format contains information about
+ * date/day information.
+ * @static
+ */
+ formatContainsDateInfo : (function(){
+ var stripEscapeRe = /(\\.)/g,
+ dateInfoRe = /([djzmnYycU]|MS)/;
+
+ return function(format){
+ return dateInfoRe.test(format.replace(stripEscapeRe, ''));
+ };
+ })(),
+
+ /**
+ * The base format-code to formatting-function hashmap used by the {@link #format} method.
+ * Formatting functions are strings (or functions which return strings) which
+ * will return the appropriate value when evaluated in the context of the Date object
+ * from which the {@link #format} method is called.
+ * Add to / override these mappings for custom date formatting.
+ * Note: Ext.Date.format() treats characters as literals if an appropriate mapping cannot be found.
+ * Example:
+ * <pre><code>
+Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
+console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
+</code></pre>
+ * @type Object
+ * @static
+ */
+ formatCodes : {
+ d: "Ext.String.leftPad(this.getDate(), 2, '0')",
+ D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
+ j: "this.getDate()",
+ l: "Ext.Date.dayNames[this.getDay()]",
+ N: "(this.getDay() ? this.getDay() : 7)",
+ S: "Ext.Date.getSuffix(this)",
+ w: "this.getDay()",
+ z: "Ext.Date.getDayOfYear(this)",
+ W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
+ F: "Ext.Date.monthNames[this.getMonth()]",
+ m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
+ M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
+ n: "(this.getMonth() + 1)",
+ t: "Ext.Date.getDaysInMonth(this)",
+ L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
+ o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
+ Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
+ y: "('' + this.getFullYear()).substring(2, 4)",
+ a: "(this.getHours() < 12 ? 'am' : 'pm')",
+ A: "(this.getHours() < 12 ? 'AM' : 'PM')",
+ g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
+ G: "this.getHours()",
+ h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
+ H: "Ext.String.leftPad(this.getHours(), 2, '0')",
+ i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
+ s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
+ u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
+ O: "Ext.Date.getGMTOffset(this)",
+ P: "Ext.Date.getGMTOffset(this, true)",
+ T: "Ext.Date.getTimezone(this)",
+ Z: "(this.getTimezoneOffset() * -60)",
+
+ c: function() { // ISO-8601 -- GMT format
+ for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
+ var e = c.charAt(i);
+ code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
+ }
+ return code.join(" + ");
+ },
+ /*
+ c: function() { // ISO-8601 -- UTC format
+ return [
+ "this.getUTCFullYear()", "'-'",
+ "Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
+ "Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
+ "'T'",
+ "Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
+ "Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
+ "Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
+ "'Z'"
+ ].join(" + ");
+ },
+ */
+
+ U: "Math.round(this.getTime() / 1000)"
+ },
+
+ /**
+ * Checks if the passed Date parameters will cause a javascript Date "rollover".
+ * @param {Number} year 4-digit year
+ * @param {Number} month 1-based month-of-year
+ * @param {Number} day Day of month
+ * @param {Number} hour (optional) Hour
+ * @param {Number} minute (optional) Minute
+ * @param {Number} second (optional) Second
+ * @param {Number} millisecond (optional) Millisecond
+ * @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
+ * @static
+ */
+ isValid : function(y, m, d, h, i, s, ms) {
+ // setup defaults
+ h = h || 0;
+ i = i || 0;
+ s = s || 0;
+ ms = ms || 0;
+
+ // Special handling for year < 100
+ var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
+
+ return y == dt.getFullYear() &&
+ m == dt.getMonth() + 1 &&
+ d == dt.getDate() &&
+ h == dt.getHours() &&
+ i == dt.getMinutes() &&
+ s == dt.getSeconds() &&
+ ms == dt.getMilliseconds();
+ },
+
+ /**
+ * Parses the passed string using the specified date format.
+ * Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
+ * The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
+ * which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
+ * the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
+ * Keep in mind that the input date string must precisely match the specified format string
+ * in order for the parse operation to be successful (failed parse operations return a null value).
+ * <p>Example:</p><pre><code>
+//dt = Fri May 25 2007 (current date)
+var dt = new Date();
+
+//dt = Thu May 25 2006 (today's month/day in 2006)
+dt = Ext.Date.parse("2006", "Y");
+
+//dt = Sun Jan 15 2006 (all date parts specified)
+dt = Ext.Date.parse("2006-01-15", "Y-m-d");
+
+//dt = Sun Jan 15 2006 15:20:01
+dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
+
+// attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
+dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
+</code></pre>
+ * @param {String} input The raw date string.
+ * @param {String} format The expected date string format.
+ * @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
+ (defaults to false). Invalid date strings will return null when parsed.
+ * @return {Date} The parsed Date.
+ * @static
+ */
+ parse : function(input, format, strict) {
+ var p = utilDate.parseFunctions;
+ if (p[format] == null) {
+ utilDate.createParser(format);
+ }
+ return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
+ },
+
+ // Backwards compat
+ parseDate: function(input, format, strict){
+ return utilDate.parse(input, format, strict);
+ },
+
+
+ // private
+ getFormatCode : function(character) {
+ var f = utilDate.formatCodes[character];
+
+ if (f) {
+ f = typeof f == 'function'? f() : f;
+ utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
+ }
+
+ // note: unknown characters are treated as literals
+ return f || ("'" + Ext.String.escape(character) + "'");
+ },
+
+ // private
+ createFormat : function(format) {
+ var code = [],
+ special = false,
+ ch = '';
+
+ for (var i = 0; i < format.length; ++i) {
+ ch = format.charAt(i);
+ if (!special && ch == "\\") {
+ special = true;
+ } else if (special) {
+ special = false;
+ code.push("'" + Ext.String.escape(ch) + "'");
+ } else {
+ code.push(utilDate.getFormatCode(ch));
+ }
+ }
+ utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
+ },
+
+ // private
+ createParser : (function() {
+ var code = [
+ "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
+ "def = Ext.Date.defaults,",
+ "results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
+
+ "if(results){",
+ "{1}",
+
+ "if(u != null){", // i.e. unix time is defined
+ "v = new Date(u * 1000);", // give top priority to UNIX time
+ "}else{",
+ // create Date object representing midnight of the current day;
+ // this will provide us with our date defaults
+ // (note: clearTime() handles Daylight Saving Time automatically)
+ "dt = Ext.Date.clearTime(new Date);",
+
+ // date calculations (note: these calculations create a dependency on Ext.Number.from())
+ "y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
+ "m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
+ "d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
+
+ // time calculations (note: these calculations create a dependency on Ext.Number.from())
+ "h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
+ "i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
+ "s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
+ "ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
+
+ "if(z >= 0 && y >= 0){",
+ // both the year and zero-based day of year are defined and >= 0.
+ // these 2 values alone provide sufficient info to create a full date object
+
+ // create Date object representing January 1st for the given year
+ // handle years < 100 appropriately
+ "v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
+
+ // then add day of year, checking for Date "rollover" if necessary
+ "v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
+ "}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
+ "v = null;", // invalid date, so return null
+ "}else{",
+ // plain old Date object
+ // handle years < 100 properly
+ "v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
+ "}",
+ "}",
+ "}",
+
+ "if(v){",
+ // favour UTC offset over GMT offset
+ "if(zz != null){",
+ // reset to UTC, then add offset
+ "v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
+ "}else if(o){",
+ // reset to GMT, then add offset
+ "v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
+ "}",
+ "}",
+
+ "return v;"
+ ].join('\n');
+
+ return function(format) {
+ var regexNum = utilDate.parseRegexes.length,
+ currentGroup = 1,
+ calc = [],
+ regex = [],
+ special = false,
+ ch = "";
+
+ for (var i = 0; i < format.length; ++i) {
+ ch = format.charAt(i);
+ if (!special && ch == "\\") {
+ special = true;
+ } else if (special) {
+ special = false;
+ regex.push(Ext.String.escape(ch));
+ } else {
+ var obj = utilDate.formatCodeToRegex(ch, currentGroup);
+ currentGroup += obj.g;
+ regex.push(obj.s);
+ if (obj.g && obj.c) {
+ calc.push(obj.c);
+ }
+ }
+ }
+
+ utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
+ utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
+ };
+ })(),
+
+ // private
+ parseCodes : {
+ /*
+ * Notes:
+ * g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
+ * c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
+ * s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
+ */
+ d: {
+ g:1,
+ c:"d = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
+ },
+ j: {
+ g:1,
+ c:"d = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
+ },
+ D: function() {
+ for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
+ return {
+ g:0,
+ c:null,
+ s:"(?:" + a.join("|") +")"
+ };
+ },
+ l: function() {
+ return {
+ g:0,
+ c:null,
+ s:"(?:" + utilDate.dayNames.join("|") + ")"
+ };
+ },
+ N: {
+ g:0,
+ c:null,
+ s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
+ },
+ S: {
+ g:0,
+ c:null,
+ s:"(?:st|nd|rd|th)"
+ },
+ w: {
+ g:0,
+ c:null,
+ s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
+ },
+ z: {
+ g:1,
+ c:"z = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
+ },
+ W: {
+ g:0,
+ c:null,
+ s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
+ },
+ F: function() {
+ return {
+ g:1,
+ c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
+ s:"(" + utilDate.monthNames.join("|") + ")"
+ };
+ },
+ M: function() {
+ for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
+ return Ext.applyIf({
+ s:"(" + a.join("|") + ")"
+ }, utilDate.formatCodeToRegex("F"));
+ },
+ m: {
+ g:1,
+ c:"m = parseInt(results[{0}], 10) - 1;\n",
+ s:"(\\d{2})" // month number with leading zeros (01 - 12)
+ },
+ n: {
+ g:1,
+ c:"m = parseInt(results[{0}], 10) - 1;\n",
+ s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
+ },
+ t: {
+ g:0,
+ c:null,
+ s:"(?:\\d{2})" // no. of days in the month (28 - 31)
+ },
+ L: {
+ g:0,
+ c:null,
+ s:"(?:1|0)"
+ },
+ o: function() {
+ return utilDate.formatCodeToRegex("Y");
+ },
+ Y: {
+ g:1,
+ c:"y = parseInt(results[{0}], 10);\n",
+ s:"(\\d{4})" // 4-digit year
+ },
+ y: {
+ g:1,
+ c:"var ty = parseInt(results[{0}], 10);\n"
+ + "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
+ s:"(\\d{1,2})"
+ },
+ /**
+ * In the am/pm parsing routines, we allow both upper and lower case
+ * even though it doesn't exactly match the spec. It gives much more flexibility
+ * in being able to specify case insensitive regexes.
+ */
+ a: {
+ g:1,
+ c:"if (/(am)/i.test(results[{0}])) {\n"
+ + "if (!h || h == 12) { h = 0; }\n"
+ + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
+ s:"(am|pm|AM|PM)"
+ },
+ A: {
+ g:1,
+ c:"if (/(am)/i.test(results[{0}])) {\n"
+ + "if (!h || h == 12) { h = 0; }\n"
+ + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
+ s:"(AM|PM|am|pm)"
+ },
+ g: function() {
+ return utilDate.formatCodeToRegex("G");
+ },
+ G: {
+ g:1,
+ c:"h = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
+ },
+ h: function() {
+ return utilDate.formatCodeToRegex("H");
+ },
+ H: {
+ g:1,
+ c:"h = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
+ },
+ i: {
+ g:1,
+ c:"i = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})" // minutes with leading zeros (00 - 59)
+ },
+ s: {
+ g:1,
+ c:"s = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})" // seconds with leading zeros (00 - 59)
+ },
+ u: {
+ g:1,
+ c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
+ s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
+ },
+ O: {
+ g:1,
+ c:[
+ "o = results[{0}];",
+ "var sn = o.substring(0,1),", // get + / - sign
+ "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
+ "mn = o.substring(3,5) % 60;", // get minutes
+ "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
+ ].join("\n"),
+ s: "([+\-]\\d{4})" // GMT offset in hrs and mins
+ },
+ P: {
+ g:1,
+ c:[
+ "o = results[{0}];",
+ "var sn = o.substring(0,1),", // get + / - sign
+ "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
+ "mn = o.substring(4,6) % 60;", // get minutes
+ "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
+ ].join("\n"),
+ s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
+ },
+ T: {
+ g:0,
+ c:null,
+ s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
+ },
+ Z: {
+ g:1,
+ c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
+ + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
+ s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
+ },
+ c: function() {
+ var calc = [],
+ arr = [
+ utilDate.formatCodeToRegex("Y", 1), // year
+ utilDate.formatCodeToRegex("m", 2), // month
+ utilDate.formatCodeToRegex("d", 3), // day
+ utilDate.formatCodeToRegex("h", 4), // hour
+ utilDate.formatCodeToRegex("i", 5), // minute
+ utilDate.formatCodeToRegex("s", 6), // second
+ {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)
+ {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
+ "if(results[8]) {", // timezone specified
+ "if(results[8] == 'Z'){",
+ "zz = 0;", // UTC
+ "}else if (results[8].indexOf(':') > -1){",
+ utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
+ "}else{",
+ utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
+ "}",
+ "}"
+ ].join('\n')}
+ ];
+
+ for (var i = 0, l = arr.length; i < l; ++i) {
+ calc.push(arr[i].c);
+ }
+
+ return {
+ g:1,
+ c:calc.join(""),
+ s:[
+ arr[0].s, // year (required)
+ "(?:", "-", arr[1].s, // month (optional)
+ "(?:", "-", arr[2].s, // day (optional)
+ "(?:",
+ "(?:T| )?", // time delimiter -- either a "T" or a single blank space
+ 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
+ "(?::", arr[5].s, ")?", // seconds (optional)
+ "(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
+ "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
+ ")?",
+ ")?",
+ ")?"
+ ].join("")
+ };
+ },
+ U: {
+ g:1,
+ c:"u = parseInt(results[{0}], 10);\n",
+ s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
+ }
+ },
+
+ //Old Ext.Date prototype methods.
+ // private
+ dateFormat: function(date, format) {
+ return utilDate.format(date, format);
+ },
+
+ /**
+ * Formats a date given the supplied format string.
+ * @param {Date} date The date to format
+ * @param {String} format The format string
+ * @return {String} The formatted date
+ */
+ format: function(date, format) {
+ if (utilDate.formatFunctions[format] == null) {
+ utilDate.createFormat(format);
+ }
+ var result = utilDate.formatFunctions[format].call(date);
+ return result + '';
+ },
+
+ /**
+ * Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
+ *
+ * Note: The date string returned by the javascript Date object's toString() method varies
+ * between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
+ * For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
+ * getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
+ * (which may or may not be present), failing which it proceeds to get the timezone abbreviation
+ * from the GMT offset portion of the date string.
+ * @param {Date} date The date
+ * @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
+ */
+ getTimezone : function(date) {
+ // the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
+ //
+ // Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
+ // 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)
+ // FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
+ // IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
+ // IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
+ //
+ // this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
+ // step 1: (?:\((.*)\) -- find timezone in parentheses
+ // 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
+ // step 3: remove all non uppercase characters found in step 1 and 2
+ return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
+ },
+
+ /**
+ * Get the offset from GMT of the current date (equivalent to the format specifier 'O').
+ * @param {Date} date The date
+ * @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
+ * @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
+ */
+ getGMTOffset : function(date, colon) {
+ var offset = date.getTimezoneOffset();
+ return (offset > 0 ? "-" : "+")
+ + Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
+ + (colon ? ":" : "")
+ + Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
+ },
+
+ /**
+ * Get the numeric day number of the year, adjusted for leap year.
+ * @param {Date} date The date
+ * @return {Number} 0 to 364 (365 in leap years).
+ */
+ getDayOfYear: function(date) {
+ var num = 0,
+ d = Ext.Date.clone(date),
+ m = date.getMonth(),
+ i;
+
+ for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
+ num += utilDate.getDaysInMonth(d);
+ }
+ return num + date.getDate() - 1;
+ },
+
+ /**
+ * Get the numeric ISO-8601 week number of the year.
+ * (equivalent to the format specifier 'W', but without a leading zero).
+ * @param {Date} date The date
+ * @return {Number} 1 to 53
+ */
+ getWeekOfYear : (function() {
+ // adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
+ var ms1d = 864e5, // milliseconds in a day
+ ms7d = 7 * ms1d; // milliseconds in a week
+
+ return function(date) { // return a closure so constants get calculated only once
+ var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
+ AWN = Math.floor(DC3 / 7), // an Absolute Week Number
+ Wyr = new Date(AWN * ms7d).getUTCFullYear();
+
+ return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
+ };
+ })(),
+
+ /**
+ * Checks if the current date falls within a leap year.
+ * @param {Date} date The date
+ * @return {Boolean} True if the current date falls within a leap year, false otherwise.
+ */
+ isLeapYear : function(date) {
+ var year = date.getFullYear();
+ return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
+ },
+
+ /**
+ * Get the first day of the current month, adjusted for leap year. The returned value
+ * is the numeric day index within the week (0-6) which can be used in conjunction with
+ * the {@link #monthNames} array to retrieve the textual day name.
+ * Example:
+ * <pre><code>
+var dt = new Date('1/10/2007'),
+ firstDay = Ext.Date.getFirstDayOfMonth(dt);
+console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
+ * </code></pre>
+ * @param {Date} date The date
+ * @return {Number} The day number (0-6).
+ */
+ getFirstDayOfMonth : function(date) {
+ var day = (date.getDay() - (date.getDate() - 1)) % 7;
+ return (day < 0) ? (day + 7) : day;
+ },
+
+ /**
+ * Get the last day of the current month, adjusted for leap year. The returned value
+ * is the numeric day index within the week (0-6) which can be used in conjunction with
+ * the {@link #monthNames} array to retrieve the textual day name.
+ * Example:
+ * <pre><code>
+var dt = new Date('1/10/2007'),
+ lastDay = Ext.Date.getLastDayOfMonth(dt);
+console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
+ * </code></pre>
+ * @param {Date} date The date
+ * @return {Number} The day number (0-6).
+ */
+ getLastDayOfMonth : function(date) {
+ return utilDate.getLastDateOfMonth(date).getDay();
+ },
+
+
+ /**
+ * Get the date of the first day of the month in which this date resides.
+ * @param {Date} date The date
+ * @return {Date}
+ */
+ getFirstDateOfMonth : function(date) {
+ return new Date(date.getFullYear(), date.getMonth(), 1);
+ },
+
+ /**
+ * Get the date of the last day of the month in which this date resides.
+ * @param {Date} date The date
+ * @return {Date}
+ */
+ getLastDateOfMonth : function(date) {
+ return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
+ },
+
+ /**
+ * Get the number of days in the current month, adjusted for leap year.
+ * @param {Date} date The date
+ * @return {Number} The number of days in the month.
+ */
+ getDaysInMonth: (function() {
+ var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+ return function(date) { // return a closure for efficiency
+ var m = date.getMonth();
+
+ return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
+ };
+ })(),
+
+ /**
+ * Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
+ * @param {Date} date The date
+ * @return {String} 'st, 'nd', 'rd' or 'th'.
+ */
+ getSuffix : function(date) {
+ switch (date.getDate()) {
+ case 1:
+ case 21:
+ case 31:
+ return "st";
+ case 2:
+ case 22:
+ return "nd";
+ case 3:
+ case 23:
+ return "rd";
+ default:
+ return "th";
+ }
+ },
+
+ /**
+ * Creates and returns a new Date instance with the exact same date value as the called instance.
+ * Dates are copied and passed by reference, so if a copied date variable is modified later, the original
+ * variable will also be changed. When the intention is to create a new variable that will not
+ * modify the original instance, you should create a clone.
+ *
+ * Example of correctly cloning a date:
+ * <pre><code>
+//wrong way:
+var orig = new Date('10/1/2006');
+var copy = orig;
+copy.setDate(5);
+console.log(orig); //returns 'Thu Oct 05 2006'!
+
+//correct way:
+var orig = new Date('10/1/2006'),
+ copy = Ext.Date.clone(orig);
+copy.setDate(5);
+console.log(orig); //returns 'Thu Oct 01 2006'
+ * </code></pre>
+ * @param {Date} date The date
+ * @return {Date} The new Date instance.
+ */
+ clone : function(date) {
+ return new Date(date.getTime());
+ },
+
+ /**
+ * Checks if the current date is affected by Daylight Saving Time (DST).
+ * @param {Date} date The date
+ * @return {Boolean} True if the current date is affected by DST.
+ */
+ isDST : function(date) {
+ // adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
+ // courtesy of @geoffrey.mcgill
+ return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
+ },
+
+ /**
+ * Attempts to clear all time information from this Date by setting the time to midnight of the same day,
+ * automatically adjusting for Daylight Saving Time (DST) where applicable.
+ * (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
+ * @param {Date} date The date
+ * @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
+ * @return {Date} this or the clone.
+ */
+ clearTime : function(date, clone) {
+ if (clone) {
+ return Ext.Date.clearTime(Ext.Date.clone(date));
+ }
+
+ // get current date before clearing time
+ var d = date.getDate();
+
+ // clear time
+ date.setHours(0);
+ date.setMinutes(0);
+ date.setSeconds(0);
+ date.setMilliseconds(0);
+
+ if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
+ // note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
+ // refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
+
+ // increment hour until cloned date == current date
+ for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
+
+ date.setDate(d);
+ date.setHours(c.getHours());
+ }
+
+ return date;
+ },
+
+ /**
+ * Provides a convenient method for performing basic date arithmetic. This method
+ * does not modify the Date instance being called - it creates and returns
+ * a new Date instance containing the resulting date value.
+ *
+ * Examples:
+ * <pre><code>
+// Basic usage:
+var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
+console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
+
+// Negative values will be subtracted:
+var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
+console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
+
+ * </code></pre>
+ *
+ * @param {Date} date The date to modify
+ * @param {String} interval A valid date interval enum value.
+ * @param {Number} value The amount to add to the current date.
+ * @return {Date} The new Date instance.
+ */
+ add : function(date, interval, value) {
+ var d = Ext.Date.clone(date),
+ Date = Ext.Date;
+ if (!interval || value === 0) return d;
+
+ switch(interval.toLowerCase()) {
+ case Ext.Date.MILLI:
+ d.setMilliseconds(d.getMilliseconds() + value);
+ break;
+ case Ext.Date.SECOND:
+ d.setSeconds(d.getSeconds() + value);
+ break;
+ case Ext.Date.MINUTE:
+ d.setMinutes(d.getMinutes() + value);
+ break;
+ case Ext.Date.HOUR:
+ d.setHours(d.getHours() + value);
+ break;
+ case Ext.Date.DAY:
+ d.setDate(d.getDate() + value);
+ break;
+ case Ext.Date.MONTH:
+ var day = date.getDate();
+ if (day > 28) {
+ day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
+ }
+ d.setDate(day);
+ d.setMonth(date.getMonth() + value);
+ break;
+ case Ext.Date.YEAR:
+ d.setFullYear(date.getFullYear() + value);
+ break;
+ }
+ return d;
+ },
+
+ /**
+ * Checks if a date falls on or between the given start and end dates.
+ * @param {Date} date The date to check
+ * @param {Date} start Start date
+ * @param {Date} end End date
+ * @return {Boolean} true if this date falls on or between the given start and end dates.
+ */
+ between : function(date, start, end) {
+ var t = date.getTime();
+ return start.getTime() <= t && t <= end.getTime();
+ },
+
+ //Maintains compatibility with old static and prototype window.Date methods.
+ compat: function() {
+ var nativeDate = window.Date,
+ p, u,
+ 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'],
+ proto = ['dateFormat', 'format', 'getTimezone', 'getGMTOffset', 'getDayOfYear', 'getWeekOfYear', 'isLeapYear', 'getFirstDayOfMonth', 'getLastDayOfMonth', 'getDaysInMonth', 'getSuffix', 'clone', 'isDST', 'clearTime', 'add', 'between'];
+
+ //Append statics
+ Ext.Array.forEach(statics, function(s) {
+ nativeDate[s] = utilDate[s];
+ });
+
+ //Append to prototype
+ Ext.Array.forEach(proto, function(s) {
+ nativeDate.prototype[s] = function() {
+ var args = Array.prototype.slice.call(arguments);
+ args.unshift(this);
+ return utilDate[s].apply(utilDate, args);
+ };
+ });
+ }
+};
+
+var utilDate = Ext.Date;
+
+})();
+
+/**
+ * @author Jacky Nguyen <jacky@sencha.com>
+ * @docauthor Jacky Nguyen <jacky@sencha.com>
+ * @class Ext.Base
+ *
+ * The root of all classes created with {@link Ext#define}
+ * All prototype and static members of this class are inherited by any other class
+ *
+ */
+(function(flexSetter) {
+
+var Base = Ext.Base = function() {};
+ Base.prototype = {
+ $className: 'Ext.Base',
+
+ $class: Base,
+
+ /**
+ * Get the reference to the current class from which this object was instantiated. Unlike {@link Ext.Base#statics},
+ * `this.self` is scope-dependent and it's meant to be used for dynamic inheritance. See {@link Ext.Base#statics}
+ * for a detailed comparison
+
+ Ext.define('My.Cat', {
+ statics: {
+ speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
+ },
+
+ constructor: function() {
+ alert(this.self.speciesName); / dependent on 'this'
+
+ return this;
+ },
+
+ clone: function() {
+ return new this.self();
+ }
+ });
+
+
+ Ext.define('My.SnowLeopard', {
+ extend: 'My.Cat',
+ statics: {
+ speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
+ }
+ });
+
+ var cat = new My.Cat(); // alerts 'Cat'
+ var snowLeopard = new My.SnowLeopard(); // alerts 'Snow Leopard'
+
+ var clone = snowLeopard.clone();
+ alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
+
+ * @type Class
+ * @protected
+ * @markdown
+ */
+ self: Base,
+
+ /**
+ * Default constructor, simply returns `this`
+ *
+ * @constructor
+ * @protected
+ * @return {Object} this
+ */
+ constructor: function() {
+ return this;
+ },
+
+ /**
+ * Initialize configuration for this class. a typical example:
+
+ Ext.define('My.awesome.Class', {
+ // The default config
+ config: {
+ name: 'Awesome',
+ isAwesome: true
+ },
+
+ constructor: function(config) {
+ this.initConfig(config);
+
+ return this;
+ }
+ });
+
+ var awesome = new My.awesome.Class({
+ name: 'Super Awesome'
+ });
+
+ alert(awesome.getName()); // 'Super Awesome'
+
+ * @protected
+ * @param {Object} config
+ * @return {Object} mixins The mixin prototypes as key - value pairs
+ * @markdown
+ */
+ initConfig: function(config) {
+ if (!this.$configInited) {
+ this.config = Ext.Object.merge({}, this.config || {}, config || {});
+
+ this.applyConfig(this.config);
+
+ this.$configInited = true;
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ */
+ setConfig: function(config) {
+ this.applyConfig(config || {});
+
+ return this;
+ },
+
+ /**
+ * @private
+ */
+ applyConfig: flexSetter(function(name, value) {
+ var setter = 'set' + Ext.String.capitalize(name);
+
+ if (typeof this[setter] === 'function') {
+ this[setter].call(this, value);
+ }
+
+ return this;
+ }),
+
+ /**
+ * Call the parent's overridden method. For example:
+
+ Ext.define('My.own.A', {
+ constructor: function(test) {
+ alert(test);
+ }
+ });
+
+ Ext.define('My.own.B', {
+ extend: 'My.own.A',
+
+ constructor: function(test) {
+ alert(test);
+
+ this.callParent([test + 1]);
+ }
+ });
+
+ Ext.define('My.own.C', {
+ extend: 'My.own.B',
+
+ constructor: function() {
+ alert("Going to call parent's overriden constructor...");
+
+ this.callParent(arguments);
+ }
+ });
+
+ var a = new My.own.A(1); // alerts '1'
+ var b = new My.own.B(1); // alerts '1', then alerts '2'
+ var c = new My.own.C(2); // alerts "Going to call parent's overriden constructor..."
+ // alerts '2', then alerts '3'
+
+ * @protected
+ * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
+ * from the current method, for example: `this.callParent(arguments)`
+ * @return {Mixed} Returns the result from the superclass' method
+ * @markdown
+ */
+ callParent: function(args) {
+ var method = this.callParent.caller,
+ parentClass, methodName;
+
+ if (!method.$owner) {
+ if (!method.caller) {
+ Ext.Error.raise({
+ sourceClass: Ext.getClassName(this),
+ sourceMethod: "callParent",
+ msg: "Attempting to call a protected method from the public scope, which is not allowed"
+ });
+ }
+
+ method = method.caller;
+ }
+
+ parentClass = method.$owner.superclass;
+ methodName = method.$name;
+
+ if (!(methodName in parentClass)) {
+ Ext.Error.raise({
+ sourceClass: Ext.getClassName(this),
+ sourceMethod: methodName,
+ msg: "this.callParent() was called but there's no such method (" + methodName +
+ ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"
+ });
+ }
+
+ return parentClass[methodName].apply(this, args || []);
+ },
+
+
+ /**
+ * Get the reference to the class from which this object was instantiated. Note that unlike {@link Ext.Base#self},
+ * `this.statics()` is scope-independent and it always returns the class from which it was called, regardless of what
+ * `this` points to during run-time
+
+ Ext.define('My.Cat', {
+ statics: {
+ totalCreated: 0,
+ speciesName: 'Cat' // My.Cat.speciesName = 'Cat'
+ },
+
+ constructor: function() {
+ var statics = this.statics();
+
+ alert(statics.speciesName); // always equals to 'Cat' no matter what 'this' refers to
+ // equivalent to: My.Cat.speciesName
+
+ alert(this.self.speciesName); // dependent on 'this'
+
+ statics.totalCreated++;
+
+ return this;
+ },
+
+ clone: function() {
+ var cloned = new this.self; // dependent on 'this'
+
+ cloned.groupName = this.statics().speciesName; // equivalent to: My.Cat.speciesName
+
+ return cloned;
+ }
+ });
+
+
+ Ext.define('My.SnowLeopard', {
+ extend: 'My.Cat',
+
+ statics: {
+ speciesName: 'Snow Leopard' // My.SnowLeopard.speciesName = 'Snow Leopard'
+ },
+
+ constructor: function() {
+ this.callParent();
+ }
+ });
+
+ var cat = new My.Cat(); // alerts 'Cat', then alerts 'Cat'
+
+ var snowLeopard = new My.SnowLeopard(); // alerts 'Cat', then alerts 'Snow Leopard'
+
+ var clone = snowLeopard.clone();
+ alert(Ext.getClassName(clone)); // alerts 'My.SnowLeopard'
+ alert(clone.groupName); // alerts 'Cat'
+
+ alert(My.Cat.totalCreated); // alerts 3
+
+ * @protected
+ * @return {Class}
+ * @markdown
+ */
+ statics: function() {
+ var method = this.statics.caller,
+ self = this.self;
+
+ if (!method) {
+ return self;
+ }
+
+ return method.$owner;
+ },
+
+ /**
+ * Call the original method that was previously overridden with {@link Ext.Base#override}
+
+ Ext.define('My.Cat', {
+ constructor: function() {
+ alert("I'm a cat!");
+
+ return this;
+ }
+ });
+
+ My.Cat.override({
+ constructor: function() {
+ alert("I'm going to be a cat!");
+
+ var instance = this.callOverridden();
+
+ alert("Meeeeoooowwww");
+
+ return instance;
+ }
+ });
+
+ var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
+ // alerts "I'm a cat!"
+ // alerts "Meeeeoooowwww"
+
+ * @param {Array/Arguments} args The arguments, either an array or the `arguments` object
+ * @return {Mixed} Returns the result after calling the overridden method
+ * @markdown
+ */
+ callOverridden: function(args) {
+ var method = this.callOverridden.caller;
+
+ if (!method.$owner) {
+ Ext.Error.raise({
+ sourceClass: Ext.getClassName(this),
+ sourceMethod: "callOverridden",
+ msg: "Attempting to call a protected method from the public scope, which is not allowed"
+ });
+ }
+
+ if (!method.$previous) {
+ Ext.Error.raise({
+ sourceClass: Ext.getClassName(this),
+ sourceMethod: "callOverridden",
+ msg: "this.callOverridden was called in '" + method.$name +
+ "' but this method has never been overridden"
+ });
+ }
+
+ return method.$previous.apply(this, args || []);
+ },
+
+ destroy: function() {}
+ };
+
+ // These static properties will be copied to every newly created class with {@link Ext#define}
+ Ext.apply(Ext.Base, {
+ /**
+ * Create a new instance of this Class.
+Ext.define('My.cool.Class', {
+ ...
+});
+
+My.cool.Class.create({
+ someConfig: true
+});
+ * @property create
+ * @static
+ * @type Function
+ * @markdown
+ */
+ create: function() {
+ return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0)));
+ },
+
+ /**
+ * @private
+ */
+ own: flexSetter(function(name, value) {
+ if (typeof value === 'function') {
+ this.ownMethod(name, value);
+ }
+ else {
+ this.prototype[name] = value;
+ }
+ }),
+
+ /**
+ * @private
+ */
+ ownMethod: function(name, fn) {
+ var originalFn;
+
+ if (fn.$owner !== undefined && fn !== Ext.emptyFn) {
+ originalFn = fn;
+
+ fn = function() {
+ return originalFn.apply(this, arguments);
+ };
+ }
+
+ var className;
+ className = Ext.getClassName(this);
+ if (className) {
+ fn.displayName = className + '#' + name;
+ }
+ fn.$owner = this;
+ fn.$name = name;
+
+ this.prototype[name] = fn;
+ },
+
+ /**
+ * Add / override static properties of this class.
+
+ Ext.define('My.cool.Class', {
+ ...
+ });
+
+ My.cool.Class.addStatics({
+ someProperty: 'someValue', // My.cool.Class.someProperty = 'someValue'
+ method1: function() { ... }, // My.cool.Class.method1 = function() { ... };
+ method2: function() { ... } // My.cool.Class.method2 = function() { ... };
+ });
+
+ * @property addStatics
+ * @static
+ * @type Function
+ * @param {Object} members
+ * @markdown
+ */
+ addStatics: function(members) {
+ for (var name in members) {
+ if (members.hasOwnProperty(name)) {
+ this[name] = members[name];
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * Add methods / properties to the prototype of this class.
+
+ Ext.define('My.awesome.Cat', {
+ constructor: function() {
+ ...
+ }
+ });
+
+ My.awesome.Cat.implement({
+ meow: function() {
+ alert('Meowww...');
+ }
+ });
+
+ var kitty = new My.awesome.Cat;
+ kitty.meow();
+
+ * @property implement
+ * @static
+ * @type Function
+ * @param {Object} members
+ * @markdown
+ */
+ implement: function(members) {
+ var prototype = this.prototype,
+ name, i, member, previous;
+ var className = Ext.getClassName(this);
+ for (name in members) {
+ if (members.hasOwnProperty(name)) {
+ member = members[name];
+
+ if (typeof member === 'function') {
+ member.$owner = this;
+ member.$name = name;
+ if (className) {
+ member.displayName = className + '#' + name;
+ }
+ }
+
+ prototype[name] = member;
+ }
+ }
+
+ if (Ext.enumerables) {
+ var enumerables = Ext.enumerables;
+
+ for (i = enumerables.length; i--;) {
+ name = enumerables[i];
+
+ if (members.hasOwnProperty(name)) {
+ member = members[name];
+ member.$owner = this;
+ member.$name = name;
+ prototype[name] = member;
+ }
+ }
+ }
+ },
+
+ /**
+ * Borrow another class' members to the prototype of this class.
+
+Ext.define('Bank', {
+ money: '$$$',
+ printMoney: function() {
+ alert('$$$$$$$');
+ }
+});
+
+Ext.define('Thief', {
+ ...
+});
+
+Thief.borrow(Bank, ['money', 'printMoney']);
+
+var steve = new Thief();
+
+alert(steve.money); // alerts '$$$'
+steve.printMoney(); // alerts '$$$$$$$'
+
+ * @property borrow
+ * @static
+ * @type Function
+ * @param {Ext.Base} fromClass The class to borrow members from
+ * @param {Array/String} members The names of the members to borrow
+ * @return {Ext.Base} this
+ * @markdown
+ */
+ borrow: function(fromClass, members) {
+ var fromPrototype = fromClass.prototype,
+ i, ln, member;
+
+ members = Ext.Array.from(members);
+
+ for (i = 0, ln = members.length; i < ln; i++) {
+ member = members[i];
+
+ this.own(member, fromPrototype[member]);
+ }
+
+ return this;
+ },
+
+ /**
+ * Override prototype members of this class. Overridden methods can be invoked via
+ * {@link Ext.Base#callOverridden}
+
+ Ext.define('My.Cat', {
+ constructor: function() {
+ alert("I'm a cat!");
+
+ return this;
+ }
+ });
+
+ My.Cat.override({
+ constructor: function() {
+ alert("I'm going to be a cat!");
+
+ var instance = this.callOverridden();
+
+ alert("Meeeeoooowwww");
+
+ return instance;
+ }
+ });
+
+ var kitty = new My.Cat(); // alerts "I'm going to be a cat!"
+ // alerts "I'm a cat!"
+ // alerts "Meeeeoooowwww"
+
+ * @property override
+ * @static
+ * @type Function
+ * @param {Object} members
+ * @return {Ext.Base} this
+ * @markdown
+ */
+ override: function(members) {
+ var prototype = this.prototype,
+ name, i, member, previous;
+
+ for (name in members) {
+ if (members.hasOwnProperty(name)) {
+ member = members[name];
+
+ if (typeof member === 'function') {
+ if (typeof prototype[name] === 'function') {
+ previous = prototype[name];
+ member.$previous = previous;
+ }
+
+ this.ownMethod(name, member);
+ }
+ else {
+ prototype[name] = member;
+ }
+ }
+ }
+
+ if (Ext.enumerables) {
+ var enumerables = Ext.enumerables;
+
+ for (i = enumerables.length; i--;) {
+ name = enumerables[i];
+
+ if (members.hasOwnProperty(name)) {
+ if (prototype[name] !== undefined) {
+ previous = prototype[name];
+ members[name].$previous = previous;
+ }
+
+ this.ownMethod(name, members[name]);
+ }
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * Used internally by the mixins pre-processor
+ * @private
+ */
+ mixin: flexSetter(function(name, cls) {
+ var mixin = cls.prototype,
+ my = this.prototype,
+ i, fn;
+
+ for (i in mixin) {
+ if (mixin.hasOwnProperty(i)) {
+ if (my[i] === undefined) {
+ if (typeof mixin[i] === 'function') {
+ fn = mixin[i];
+
+ if (fn.$owner === undefined) {
+ this.ownMethod(i, fn);
+ }
+ else {
+ my[i] = fn;
+ }
+ }
+ else {
+ my[i] = mixin[i];
+ }
+ }
+ else if (i === 'config' && my.config && mixin.config) {
+ Ext.Object.merge(my.config, mixin.config);
+ }
+ }
+ }
+
+ if (my.mixins === undefined) {
+ my.mixins = {};
+ }
+
+ my.mixins[name] = mixin;
+ }),
+
+ /**
+ * Get the current class' name in string format.
+
+ Ext.define('My.cool.Class', {
+ constructor: function() {
+ alert(this.self.getName()); // alerts 'My.cool.Class'
+ }
+ });
+
+ My.cool.Class.getName(); // 'My.cool.Class'
+
+ * @return {String} className
+ * @markdown
+ */
+ getName: function() {
+ return Ext.getClassName(this);
+ },
+
+ /**
+ * Create aliases for existing prototype methods. Example:
+
+ Ext.define('My.cool.Class', {
+ method1: function() { ... },
+ method2: function() { ... }
+ });
+
+ var test = new My.cool.Class();
+
+ My.cool.Class.createAlias({
+ method3: 'method1',
+ method4: 'method2'
+ });
+
+ test.method3(); // test.method1()
+
+ My.cool.Class.createAlias('method5', 'method3');
+
+ test.method5(); // test.method3() -> test.method1()
+
+ * @property createAlias
+ * @static
+ * @type Function
+ * @param {String/Object} alias The new method name, or an object to set multiple aliases. See
+ * {@link Ext.Function#flexSetter flexSetter}
+ * @param {String/Object} origin The original method name
+ * @markdown
+ */
+ createAlias: flexSetter(function(alias, origin) {
+ this.prototype[alias] = this.prototype[origin];
+ })
+ });
+
+})(Ext.Function.flexSetter);
+
+/**
+ * @author Jacky Nguyen <jacky@sencha.com>
+ * @docauthor Jacky Nguyen <jacky@sencha.com>
+ * @class Ext.Class
+ *
+ * Handles class creation throughout the whole framework. Note that most of the time {@link Ext#define Ext.define} should
+ * be used instead, since it's a higher level wrapper that aliases to {@link Ext.ClassManager#create}
+ * to enable namespacing and dynamic dependency resolution.
+ *
+ * # Basic syntax: #
+ *
+ * Ext.define(className, properties);
+ *
+ * in which `properties` is an object represent a collection of properties that apply to the class. See
+ * {@link Ext.ClassManager#create} for more detailed instructions.
+ *
+ * Ext.define('Person', {
+ * name: 'Unknown',
+ *
+ * constructor: function(name) {
+ * if (name) {
+ * this.name = name;
+ * }
+ *
+ * return this;
+ * },
+ *
+ * eat: function(foodType) {
+ * alert("I'm eating: " + foodType);
+ *
+ * return this;
+ * }
+ * });
+ *
+ * var aaron = new Person("Aaron");
+ * aaron.eat("Sandwich"); // alert("I'm eating: Sandwich");
+ *
+ * Ext.Class has a powerful set of extensible {@link Ext.Class#registerPreprocessor pre-processors} which takes care of
+ * everything related to class creation, including but not limited to inheritance, mixins, configuration, statics, etc.
+ *
+ * # Inheritance: #
+ *
+ * Ext.define('Developer', {
+ * extend: 'Person',
+ *
+ * constructor: function(name, isGeek) {
+ * this.isGeek = isGeek;
+ *
+ * // Apply a method from the parent class' prototype
+ * this.callParent([name]);
+ *
+ * return this;
+ *
+ * },
+ *
+ * code: function(language) {
+ * alert("I'm coding in: " + language);
+ *
+ * this.eat("Bugs");
+ *
+ * return this;
+ * }
+ * });
+ *
+ * var jacky = new Developer("Jacky", true);
+ * jacky.code("JavaScript"); // alert("I'm coding in: JavaScript");
+ * // alert("I'm eating: Bugs");
+ *
+ * See {@link Ext.Base#callParent} for more details on calling superclass' methods
+ *
+ * # Mixins: #
+ *
+ * Ext.define('CanPlayGuitar', {
+ * playGuitar: function() {
+ * alert("F#...G...D...A");
+ * }
+ * });
+ *
+ * Ext.define('CanComposeSongs', {
+ * composeSongs: function() { ... }
+ * });
+ *
+ * Ext.define('CanSing', {
+ * sing: function() {
+ * alert("I'm on the highway to hell...")
+ * }
+ * });
+ *
+ * Ext.define('Musician', {
+ * extend: 'Person',
+ *
+ * mixins: {
+ * canPlayGuitar: 'CanPlayGuitar',
+ * canComposeSongs: 'CanComposeSongs',
+ * canSing: 'CanSing'
+ * }
+ * })
+ *
+ * Ext.define('CoolPerson', {
+ * extend: 'Person',
+ *
+ * mixins: {
+ * canPlayGuitar: 'CanPlayGuitar',
+ * canSing: 'CanSing'
+ * },
+ *
+ * sing: function() {
+ * alert("Ahem....");
+ *
+ * this.mixins.canSing.sing.call(this);
+ *
+ * alert("[Playing guitar at the same time...]");
+ *
+ * this.playGuitar();
+ * }
+ * });
+ *
+ * var me = new CoolPerson("Jacky");
+ *
+ * me.sing(); // alert("Ahem...");
+ * // alert("I'm on the highway to hell...");
+ * // alert("[Playing guitar at the same time...]");
+ * // alert("F#...G...D...A");
+ *
+ * # Config: #
+ *
+ * Ext.define('SmartPhone', {
+ * config: {
+ * hasTouchScreen: false,
+ * operatingSystem: 'Other',
+ * price: 500
+ * },
+ *
+ * isExpensive: false,
+ *
+ * constructor: function(config) {
+ * this.initConfig(config);
+ *
+ * return this;
+ * },
+ *
+ * applyPrice: function(price) {
+ * this.isExpensive = (price > 500);
+ *
+ * return price;
+ * },
+ *
+ * applyOperatingSystem: function(operatingSystem) {
+ * if (!(/^(iOS|Android|BlackBerry)$/i).test(operatingSystem)) {
+ * return 'Other';
+ * }
+ *
+ * return operatingSystem;
+ * }
+ * });
+ *
+ * var iPhone = new SmartPhone({
+ * hasTouchScreen: true,
+ * operatingSystem: 'iOS'
+ * });
+ *
+ * iPhone.getPrice(); // 500;
+ * iPhone.getOperatingSystem(); // 'iOS'
+ * iPhone.getHasTouchScreen(); // true;
+ * iPhone.hasTouchScreen(); // true
+ *
+ * iPhone.isExpensive; // false;
+ * iPhone.setPrice(600);
+ * iPhone.getPrice(); // 600
+ * iPhone.isExpensive; // true;
+ *
+ * iPhone.setOperatingSystem('AlienOS');
+ * iPhone.getOperatingSystem(); // 'Other'
+ *
+ * # Statics: #
+ *
+ * Ext.define('Computer', {
+ * statics: {
+ * factory: function(brand) {
+ * // 'this' in static methods refer to the class itself
+ * return new this(brand);
+ * }
+ * },
+ *
+ * constructor: function() { ... }
+ * });
+ *
+ * var dellComputer = Computer.factory('Dell');
+ *
+ * Also see {@link Ext.Base#statics} and {@link Ext.Base#self} for more details on accessing
+ * static properties within class methods
+ *
+ */
+(function() {
+
+ var Class,
+ Base = Ext.Base,
+ baseStaticProperties = [],
+ baseStaticProperty;
+
+ for (baseStaticProperty in Base) {
+ if (Base.hasOwnProperty(baseStaticProperty)) {
+ baseStaticProperties.push(baseStaticProperty);
+ }
+ }
+
+ /**
+ * @constructor
+ * @param {Object} classData An object represent the properties of this class
+ * @param {Function} createdFn Optional, the callback function to be executed when this class is fully created.
+ * Note that the creation process can be asynchronous depending on the pre-processors used.
+ * @return {Ext.Base} The newly created class
+ */
+ Ext.Class = Class = function(newClass, classData, onClassCreated) {
+ if (typeof newClass !== 'function') {
+ onClassCreated = classData;
+ classData = newClass;
+ newClass = function() {
+ return this.constructor.apply(this, arguments);
+ };
+ }
+
+ if (!classData) {
+ classData = {};
+ }
+
+ var preprocessorStack = classData.preprocessors || Class.getDefaultPreprocessors(),
+ registeredPreprocessors = Class.getPreprocessors(),
+ index = 0,
+ preprocessors = [],
+ preprocessor, preprocessors, staticPropertyName, process, i, j, ln;
+
+ for (i = 0, ln = baseStaticProperties.length; i < ln; i++) {
+ staticPropertyName = baseStaticProperties[i];
+ newClass[staticPropertyName] = Base[staticPropertyName];
+ }
+
+ delete classData.preprocessors;
+
+ for (j = 0, ln = preprocessorStack.length; j < ln; j++) {
+ preprocessor = preprocessorStack[j];
+
+ if (typeof preprocessor === 'string') {
+ preprocessor = registeredPreprocessors[preprocessor];
+
+ if (!preprocessor.always) {
+ if (classData.hasOwnProperty(preprocessor.name)) {
+ preprocessors.push(preprocessor.fn);
+ }
+ }
+ else {
+ preprocessors.push(preprocessor.fn);
+ }
+ }
+ else {
+ preprocessors.push(preprocessor);
+ }
+ }
+
+ classData.onClassCreated = onClassCreated;
+
+ classData.onBeforeClassCreated = function(cls, data) {
+ onClassCreated = data.onClassCreated;
+
+ delete data.onBeforeClassCreated;
+ delete data.onClassCreated;
+
+ cls.implement(data);
+
+ if (onClassCreated) {
+ onClassCreated.call(cls, cls);
+ }
+ };
+
+ process = function(cls, data) {
+ preprocessor = preprocessors[index++];
+
+ if (!preprocessor) {
+ data.onBeforeClassCreated.apply(this, arguments);
+ return;
+ }
+
+ if (preprocessor.call(this, cls, data, process) !== false) {
+ process.apply(this, arguments);
+ }
+ };
+
+ process.call(Class, newClass, classData);
+
+ return newClass;
+ };
+
+ Ext.apply(Class, {
+
+ /** @private */
+ preprocessors: {},
+
+ /**
+ * Register a new pre-processor to be used during the class creation process
+ *
+ * @member Ext.Class registerPreprocessor
+ * @param {String} name The pre-processor's name
+ * @param {Function} fn The callback function to be executed. Typical format:
+
+ function(cls, data, fn) {
+ // Your code here
+
+ // Execute this when the processing is finished.
+ // Asynchronous processing is perfectly ok
+ if (fn) {
+ fn.call(this, cls, data);
+ }
+ });
+
+ * Passed arguments for this function are:
+ *
+ * - `{Function} cls`: The created class
+ * - `{Object} data`: The set of properties passed in {@link Ext.Class} constructor
+ * - `{Function} fn`: The callback function that <b>must</b> to be executed when this pre-processor finishes,
+ * regardless of whether the processing is synchronous or aynchronous
+ *
+ * @return {Ext.Class} this
+ * @markdown
+ */
+ registerPreprocessor: function(name, fn, always) {
+ this.preprocessors[name] = {
+ name: name,
+ always: always || false,
+ fn: fn
+ };
+
+ return this;
+ },
+
+ /**
+ * Retrieve a pre-processor callback function by its name, which has been registered before
+ *
+ * @param {String} name
+ * @return {Function} preprocessor
+ */
+ getPreprocessor: function(name) {
+ return this.preprocessors[name];
+ },
+
+ getPreprocessors: function() {
+ return this.preprocessors;
+ },
+
+ /**
+ * Retrieve the array stack of default pre-processors
+ *
+ * @return {Function} defaultPreprocessors
+ */
+ getDefaultPreprocessors: function() {
+ return this.defaultPreprocessors || [];
+ },
+
+ /**
+ * Set the default array stack of default pre-processors
+ *
+ * @param {Array} preprocessors
+ * @return {Ext.Class} this
+ */
+ setDefaultPreprocessors: function(preprocessors) {
+ this.defaultPreprocessors = Ext.Array.from(preprocessors);
+
+ return this;
+ },
+
+ /**
+ * Insert this pre-processor at a specific position in the stack, optionally relative to
+ * any existing pre-processor. For example:
+
+ Ext.Class.registerPreprocessor('debug', function(cls, data, fn) {
+ // Your code here
+
+ if (fn) {
+ fn.call(this, cls, data);
+ }
+ }).insertDefaultPreprocessor('debug', 'last');
+
+ * @param {String} name The pre-processor name. Note that it needs to be registered with
+ * {@link Ext#registerPreprocessor registerPreprocessor} before this
+ * @param {String} offset The insertion position. Four possible values are:
+ * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
+ * @param {String} relativeName
+ * @return {Ext.Class} this
+ * @markdown
+ */
+ setDefaultPreprocessorPosition: function(name, offset, relativeName) {
+ var defaultPreprocessors = this.defaultPreprocessors,
+ index;
+
+ if (typeof offset === 'string') {
+ if (offset === 'first') {
+ defaultPreprocessors.unshift(name);
+
+ return this;
+ }
+ else if (offset === 'last') {
+ defaultPreprocessors.push(name);
+
+ return this;
+ }
+
+ offset = (offset === 'after') ? 1 : -1;
+ }
+
+ index = Ext.Array.indexOf(defaultPreprocessors, relativeName);
+
+ if (index !== -1) {
+ defaultPreprocessors.splice(Math.max(0, index + offset), 0, name);
+ }
+
+ return this;
+ }
+ });
+
+ Class.registerPreprocessor('extend', function(cls, data) {
+ var extend = data.extend,
+ base = Ext.Base,
+ basePrototype = base.prototype,
+ prototype = function() {},
+ parent, i, k, ln, staticName, parentStatics,
+ parentPrototype, clsPrototype;
+
+ if (extend && extend !== Object) {
+ parent = extend;
+ }
+ else {
+ parent = base;
+ }
+
+ parentPrototype = parent.prototype;
+
+ prototype.prototype = parentPrototype;
+ clsPrototype = cls.prototype = new prototype();
+
+ if (!('$class' in parent)) {
+ for (i in basePrototype) {
+ if (!parentPrototype[i]) {
+ parentPrototype[i] = basePrototype[i];
+ }
+ }
+ }
+
+ clsPrototype.self = cls;
+
+ cls.superclass = clsPrototype.superclass = parentPrototype;
+
+ delete data.extend;
+
+ // Statics inheritance
+ parentStatics = parentPrototype.$inheritableStatics;
+
+ if (parentStatics) {
+ for (k = 0, ln = parentStatics.length; k < ln; k++) {
+ staticName = parentStatics[k];
+
+ if (!cls.hasOwnProperty(staticName)) {
+ cls[staticName] = parent[staticName];
+ }
+ }
+ }
+
+ // Merge the parent class' config object without referencing it
+ if (parentPrototype.config) {
+ clsPrototype.config = Ext.Object.merge({}, parentPrototype.config);
+ }
+ else {
+ clsPrototype.config = {};
+ }
+
+ if (clsPrototype.$onExtended) {
+ clsPrototype.$onExtended.call(cls, cls, data);
+ }
+
+ if (data.onClassExtended) {
+ clsPrototype.$onExtended = data.onClassExtended;
+ delete data.onClassExtended;
+ }
+
+ }, true);
+
+ Class.registerPreprocessor('statics', function(cls, data) {
+ var statics = data.statics,
+ name;
+
+ for (name in statics) {
+ if (statics.hasOwnProperty(name)) {
+ cls[name] = statics[name];
+ }
+ }
+
+ delete data.statics;
+ });
+
+ Class.registerPreprocessor('inheritableStatics', function(cls, data) {
+ var statics = data.inheritableStatics,
+ inheritableStatics,
+ prototype = cls.prototype,
+ name;
+
+ inheritableStatics = prototype.$inheritableStatics;
+
+ if (!inheritableStatics) {
+ inheritableStatics = prototype.$inheritableStatics = [];
+ }
+
+ for (name in statics) {
+ if (statics.hasOwnProperty(name)) {
+ cls[name] = statics[name];
+ inheritableStatics.push(name);
+ }
+ }
+
+ delete data.inheritableStatics;
+ });
+
+ Class.registerPreprocessor('mixins', function(cls, data) {
+ cls.mixin(data.mixins);
+
+ delete data.mixins;
+ });
+
+ Class.registerPreprocessor('config', function(cls, data) {
+ var prototype = cls.prototype;
+
+ Ext.Object.each(data.config, function(name) {
+ var cName = name.charAt(0).toUpperCase() + name.substr(1),
+ pName = name,
+ apply = 'apply' + cName,
+ setter = 'set' + cName,
+ getter = 'get' + cName;
+
+ if (!(apply in prototype) && !data.hasOwnProperty(apply)) {
+ data[apply] = function(val) {
+ return val;
+ };
+ }
+
+ if (!(setter in prototype) && !data.hasOwnProperty(setter)) {
+ data[setter] = function(val) {
+ var ret = this[apply].call(this, val, this[pName]);
+
+ if (ret !== undefined) {
+ this[pName] = ret;
+ }
+
+ return this;
+ };
+ }
+
+ if (!(getter in prototype) && !data.hasOwnProperty(getter)) {
+ data[getter] = function() {
+ return this[pName];
+ };
+ }
+ });
+
+ Ext.Object.merge(prototype.config, data.config);
+ delete data.config;
+ });
+
+ Class.setDefaultPreprocessors(['extend', 'statics', 'inheritableStatics', 'mixins', 'config']);
+
+ // Backwards compatible
+ Ext.extend = function(subclass, superclass, members) {
+ if (arguments.length === 2 && Ext.isObject(superclass)) {
+ members = superclass;
+ superclass = subclass;
+ subclass = null;
+ }
+
+ var cls;
+
+ if (!superclass) {
+ Ext.Error.raise("Attempting to extend from a class which has not been loaded on the page.");
+ }
+
+ members.extend = superclass;
+ members.preprocessors = ['extend', 'mixins', 'config', 'statics'];
+
+ if (subclass) {
+ cls = new Class(subclass, members);
+ }
+ else {
+ cls = new Class(members);
+ }
+
+ cls.prototype.override = function(o) {
+ for (var m in o) {
+ if (o.hasOwnProperty(m)) {
+ this[m] = o[m];
+ }
+ }
+ };
+
+ return cls;
+ };
+
+})();
+
+/**
+ * @author Jacky Nguyen <jacky@sencha.com>
+ * @docauthor Jacky Nguyen <jacky@sencha.com>
+ * @class Ext.ClassManager
+
+Ext.ClassManager manages all classes and handles mapping from string class name to
+actual class objects throughout the whole framework. It is not generally accessed directly, rather through
+these convenient shorthands:
+
+- {@link Ext#define Ext.define}
+- {@link Ext#create Ext.create}
+- {@link Ext#widget Ext.widget}
+- {@link Ext#getClass Ext.getClass}
+- {@link Ext#getClassName Ext.getClassName}
+
+ * @singleton
+ * @markdown
+ */
+(function(Class, alias) {
+
+ var slice = Array.prototype.slice;
+
+ var Manager = Ext.ClassManager = {
+
+ /**
+ * @property classes
+ * @type Object
+ * All classes which were defined through the ClassManager. Keys are the
+ * name of the classes and the values are references to the classes.
+ * @private
+ */
+ classes: {},
+
+ /**
+ * @private
+ */
+ existCache: {},
+
+ /**
+ * @private
+ */
+ namespaceRewrites: [{
+ from: 'Ext.',
+ to: Ext
+ }],
+
+ /**
+ * @private
+ */
+ maps: {
+ alternateToName: {},
+ aliasToName: {},
+ nameToAliases: {}
+ },
+
+ /** @private */
+ enableNamespaceParseCache: true,
+
+ /** @private */
+ namespaceParseCache: {},
+
+ /** @private */
+ instantiators: [],
+
+ /** @private */
+ instantiationCounts: {},
+
+ /**
+ * Checks if a class has already been created.
+ *
+ * @param {String} className
+ * @return {Boolean} exist
+ */
+ isCreated: function(className) {
+ var i, ln, part, root, parts;
+
+ if (typeof className !== 'string' || className.length < 1) {
+ Ext.Error.raise({
+ sourceClass: "Ext.ClassManager",
+ sourceMethod: "exist",
+ msg: "Invalid classname, must be a string and must not be empty"
+ });
+ }
+
+ if (this.classes.hasOwnProperty(className) || this.existCache.hasOwnProperty(className)) {
+ return true;
+ }
+
+ root = Ext.global;
+ parts = this.parseNamespace(className);
+
+ for (i = 0, ln = parts.length; i < ln; i++) {
+ part = parts[i];
+
+ if (typeof part !== 'string') {
+ root = part;
+ } else {
+ if (!root || !root[part]) {
+ return false;
+ }
+
+ root = root[part];
+ }
+ }
+
+ Ext.Loader.historyPush(className);
+
+ this.existCache[className] = true;
+
+ return true;
+ },
+
+ /**
+ * Supports namespace rewriting
+ * @private
+ */
+ parseNamespace: function(namespace) {
+ if (typeof namespace !== 'string') {
+ Ext.Error.raise({
+ sourceClass: "Ext.ClassManager",
+ sourceMethod: "parseNamespace",
+ msg: "Invalid namespace, must be a string"
+ });
+ }
+
+ var cache = this.namespaceParseCache;
+
+ if (this.enableNamespaceParseCache) {
+ if (cache.hasOwnProperty(namespace)) {
+ return cache[namespace];
+ }
+ }
+
+ var parts = [],
+ rewrites = this.namespaceRewrites,
+ rewrite, from, to, i, ln, root = Ext.global;
+
+ for (i = 0, ln = rewrites.length; i < ln; i++) {
+ rewrite = rewrites[i];
+ from = rewrite.from;
+ to = rewrite.to;
+
+ if (namespace === from || namespace.substring(0, from.length) === from) {
+ namespace = namespace.substring(from.length);
+
+ if (typeof to !== 'string') {
+ root = to;
+ } else {
+ parts = parts.concat(to.split('.'));
+ }
+
+ break;
+ }
+ }
+
+ parts.push(root);
+
+ parts = parts.concat(namespace.split('.'));
+
+ if (this.enableNamespaceParseCache) {
+ cache[namespace] = parts;
+ }
+
+ return parts;
+ },
+
+ /**
+ * Creates a namespace and assign the `value` to the created object
+
+ Ext.ClassManager.setNamespace('MyCompany.pkg.Example', someObject);
+
+ alert(MyCompany.pkg.Example === someObject); // alerts true
+
+ * @param {String} name
+ * @param {Mixed} value
+ * @markdown
+ */
+ setNamespace: function(name, value) {
+ var root = Ext.global,
+ parts = this.parseNamespace(name),
+ leaf = parts.pop(),
+ i, ln, part;
+
+ for (i = 0, ln = parts.length; i < ln; i++) {
+ part = parts[i];
+
+ if (typeof part !== 'string') {
+ root = part;
+ } else {
+ if (!root[part]) {
+ root[part] = {};
+ }
+
+ root = root[part];
+ }
+ }
+
+ root[leaf] = value;
+
+ return root[leaf];
+ },
+
+ /**
+ * The new Ext.ns, supports namespace rewriting
+ * @private
+ */
+ createNamespaces: function() {
+ var root = Ext.global,
+ parts, part, i, j, ln, subLn;
+
+ for (i = 0, ln = arguments.length; i < ln; i++) {
+ parts = this.parseNamespace(arguments[i]);
+
+ for (j = 0, subLn = parts.length; j < subLn; j++) {
+ part = parts[j];
+
+ if (typeof part !== 'string') {
+ root = part;
+ } else {
+ if (!root[part]) {
+ root[part] = {};
+ }
+
+ root = root[part];
+ }
+ }
+ }
+
+ return root;
+ },
+
+ /**
+ * Sets a name reference to a class.
+ *
+ * @param {String} name
+ * @param {Object} value
+ * @return {Ext.ClassManager} this
+ */
+ set: function(name, value) {
+ var targetName = this.getName(value);
+
+ this.classes[name] = this.setNamespace(name, value);
+
+ if (targetName && targetName !== name) {
+ this.maps.alternateToName[name] = targetName;
+ }
+
+ return this;
+ },
+
+ /**
+ * Retrieve a class by its name.
+ *
+ * @param {String} name
+ * @return {Class} class
+ */
+ get: function(name) {
+ if (this.classes.hasOwnProperty(name)) {
+ return this.classes[name];
+ }
+
+ var root = Ext.global,
+ parts = this.parseNamespace(name),
+ part, i, ln;
+
+ for (i = 0, ln = parts.length; i < ln; i++) {
+ part = parts[i];
+
+ if (typeof part !== 'string') {
+ root = part;
+ } else {
+ if (!root || !root[part]) {
+ return null;
+ }
+
+ root = root[part];
+ }
+ }
+
+ return root;
+ },
+
+ /**
+ * Register the alias for a class.
+ *
+ * @param {Class/String} cls a reference to a class or a className
+ * @param {String} alias Alias to use when referring to this class
+ */
+ setAlias: function(cls, alias) {
+ var aliasToNameMap = this.maps.aliasToName,
+ nameToAliasesMap = this.maps.nameToAliases,
+ className;
+
+ if (typeof cls === 'string') {
+ className = cls;
+ } else {
+ className = this.getName(cls);
+ }
+
+ if (alias && aliasToNameMap[alias] !== className) {
+ if (aliasToNameMap.hasOwnProperty(alias) && Ext.isDefined(Ext.global.console)) {
+ Ext.global.console.log("[Ext.ClassManager] Overriding existing alias: '" + alias + "' " +
+ "of: '" + aliasToNameMap[alias] + "' with: '" + className + "'. Be sure it's intentional.");
+ }
+
+ aliasToNameMap[alias] = className;
+ }
+
+ if (!nameToAliasesMap[className]) {
+ nameToAliasesMap[className] = [];
+ }
+
+ if (alias) {
+ Ext.Array.include(nameToAliasesMap[className], alias);
+ }
+
+ return this;
+ },
+
+ /**
+ * Get a reference to the class by its alias.
+ *
+ * @param {String} alias
+ * @return {Class} class
+ */
+ getByAlias: function(alias) {
+ return this.get(this.getNameByAlias(alias));
+ },
+
+ /**
+ * Get the name of a class by its alias.
+ *
+ * @param {String} alias
+ * @return {String} className
+ */
+ getNameByAlias: function(alias) {
+ return this.maps.aliasToName[alias] || '';
+ },
+
+ /**
+ * Get the name of a class by its alternate name.
+ *
+ * @param {String} alternate
+ * @return {String} className
+ */
+ getNameByAlternate: function(alternate) {
+ return this.maps.alternateToName[alternate] || '';
+ },
+
+ /**
+ * Get the aliases of a class by the class name
+ *
+ * @param {String} name
+ * @return {Array} aliases
+ */
+ getAliasesByName: function(name) {
+ return this.maps.nameToAliases[name] || [];
+ },
+
+ /**
+ * Get the name of the class by its reference or its instance;
+ * usually invoked by the shorthand {@link Ext#getClassName Ext.getClassName}
+
+ Ext.ClassManager.getName(Ext.Action); // returns "Ext.Action"
+
+ * @param {Class/Object} object
+ * @return {String} className
+ * @markdown
+ */
+ getName: function(object) {
+ return object && object.$className || '';
+ },
+
+ /**
+ * Get the class of the provided object; returns null if it's not an instance
+ * of any class created with Ext.define. This is usually invoked by the shorthand {@link Ext#getClass Ext.getClass}
+ *
+ var component = new Ext.Component();
+
+ Ext.ClassManager.getClass(component); // returns Ext.Component
+ *
+ * @param {Object} object
+ * @return {Class} class
+ * @markdown
+ */
+ getClass: function(object) {
+ return object && object.self || null;
+ },
+
+ /**
+ * Defines a class. This is usually invoked via the alias {@link Ext#define Ext.define}
+
+ Ext.ClassManager.create('My.awesome.Class', {
+ someProperty: 'something',
+ someMethod: function() { ... }
+ ...
+
+ }, function() {
+ alert('Created!');
+ alert(this === My.awesome.Class); // alerts true
+
+ var myInstance = new this();
+ });
+
+ * @param {String} className The class name to create in string dot-namespaced format, for example:
+ * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
+ * It is highly recommended to follow this simple convention:
+
+- The root and the class name are 'CamelCased'
+- Everything else is lower-cased
+
+ * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
+ * strings, except those in the reserved listed below:
+
+- `mixins`
+- `statics`
+- `config`
+- `alias`
+- `self`
+- `singleton`
+- `alternateClassName`
+ *
+ * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
+ * (`this`) will be the newly created class itself.
+ * @return {Ext.Base}
+ * @markdown
+ */
+ create: function(className, data, createdFn) {
+ var manager = this;
+
+ if (typeof className !== 'string') {
+ Ext.Error.raise({
+ sourceClass: "Ext",
+ sourceMethod: "define",
+ msg: "Invalid class name '" + className + "' specified, must be a non-empty string"
+ });
+ }
+
+ data.$className = className;
+
+ return new Class(data, function() {
+ var postprocessorStack = data.postprocessors || manager.defaultPostprocessors,
+ registeredPostprocessors = manager.postprocessors,
+ index = 0,
+ postprocessors = [],
+ postprocessor, postprocessors, process, i, ln;
+
+ delete data.postprocessors;
+
+ for (i = 0, ln = postprocessorStack.length; i < ln; i++) {
+ postprocessor = postprocessorStack[i];
+
+ if (typeof postprocessor === 'string') {
+ postprocessor = registeredPostprocessors[postprocessor];
+
+ if (!postprocessor.always) {
+ if (data[postprocessor.name] !== undefined) {
+ postprocessors.push(postprocessor.fn);
+ }
+ }
+ else {
+ postprocessors.push(postprocessor.fn);
+ }
+ }
+ else {
+ postprocessors.push(postprocessor);
+ }
+ }
+
+ process = function(clsName, cls, clsData) {
+ postprocessor = postprocessors[index++];
+
+ if (!postprocessor) {
+ manager.set(className, cls);
+
+ Ext.Loader.historyPush(className);
+
+ if (createdFn) {
+ createdFn.call(cls, cls);
+ }
+
+ return;
+ }
+
+ if (postprocessor.call(this, clsName, cls, clsData, process) !== false) {
+ process.apply(this, arguments);
+ }
+ };
+
+ process.call(manager, className, this, data);
+ });
+ },
+
+ /**
+ * Instantiate a class by its alias; usually invoked by the convenient shorthand {@link Ext#createByAlias Ext.createByAlias}
+ * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
+ * attempt to load the class via synchronous loading.
+
+ var window = Ext.ClassManager.instantiateByAlias('widget.window', { width: 600, height: 800, ... });
+
+ * @param {String} alias
+ * @param {Mixed} args,... Additional arguments after the alias will be passed to the
+ * class constructor.
+ * @return {Object} instance
+ * @markdown
+ */
+ instantiateByAlias: function() {
+ var alias = arguments[0],
+ args = slice.call(arguments),
+ className = this.getNameByAlias(alias);
+
+ if (!className) {
+ className = this.maps.aliasToName[alias];
+
+ if (!className) {
+ Ext.Error.raise({
+ sourceClass: "Ext",
+ sourceMethod: "createByAlias",
+ msg: "Cannot create an instance of unrecognized alias: " + alias
+ });
+ }
+
+ if (Ext.global.console) {
+ Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + className + "'; consider adding " +
+ "Ext.require('" + alias + "') above Ext.onReady");
+ }
+
+ Ext.syncRequire(className);
+ }
+
+ args[0] = className;
+
+ return this.instantiate.apply(this, args);
+ },
+
+ /**
+ * Instantiate a class by either full name, alias or alternate name; usually invoked by the convenient
+ * shorthand {@link Ext#create Ext.create}
+ *
+ * If {@link Ext.Loader} is {@link Ext.Loader#setConfig enabled} and the class has not been defined yet, it will
+ * attempt to load the class via synchronous loading.
+ *
+ * For example, all these three lines return the same result:
+
+ // alias
+ var window = Ext.ClassManager.instantiate('widget.window', { width: 600, height: 800, ... });
+
+ // alternate name
+ var window = Ext.ClassManager.instantiate('Ext.Window', { width: 600, height: 800, ... });
+
+ // full class name
+ var window = Ext.ClassManager.instantiate('Ext.window.Window', { width: 600, height: 800, ... });
+
+ * @param {String} name
+ * @param {Mixed} args,... Additional arguments after the name will be passed to the class' constructor.
+ * @return {Object} instance
+ * @markdown
+ */
+ instantiate: function() {
+ var name = arguments[0],
+ args = slice.call(arguments, 1),
+ alias = name,
+ possibleName, cls;
+
+ if (typeof name !== 'function') {
+ if ((typeof name !== 'string' || name.length < 1)) {
+ Ext.Error.raise({
+ sourceClass: "Ext",
+ sourceMethod: "create",
+ msg: "Invalid class name or alias '" + name + "' specified, must be a non-empty string"
+ });
+ }
+
+ cls = this.get(name);
+ }
+ else {
+ cls = name;
+ }
+
+ // No record of this class name, it's possibly an alias, so look it up
+ if (!cls) {
+ possibleName = this.getNameByAlias(name);
+
+ if (possibleName) {
+ name = possibleName;
+
+ cls = this.get(name);
+ }
+ }
+
+ // Still no record of this class name, it's possibly an alternate name, so look it up
+ if (!cls) {
+ possibleName = this.getNameByAlternate(name);
+
+ if (possibleName) {
+ name = possibleName;
+
+ cls = this.get(name);
+ }
+ }
+
+ // Still not existing at this point, try to load it via synchronous mode as the last resort
+ if (!cls) {
+ if (Ext.global.console) {
+ Ext.global.console.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding " +
+ "Ext.require('" + ((possibleName) ? alias : name) + "') above Ext.onReady");
+ }
+
+ Ext.syncRequire(name);
+
+ cls = this.get(name);
+ }
+
+ if (!cls) {
+ Ext.Error.raise({
+ sourceClass: "Ext",
+ sourceMethod: "create",
+ msg: "Cannot create an instance of unrecognized class name / alias: " + alias
+ });
+ }
+
+ if (typeof cls !== 'function') {
+ Ext.Error.raise({
+ sourceClass: "Ext",
+ sourceMethod: "create",
+ msg: "'" + name + "' is a singleton and cannot be instantiated"
+ });
+ }
+
+ if (!this.instantiationCounts[name]) {
+ this.instantiationCounts[name] = 0;
+ }
+
+ this.instantiationCounts[name]++;
+
+ return this.getInstantiator(args.length)(cls, args);
+ },
+
+ /**
+ * @private
+ * @param name
+ * @param args
+ */
+ dynInstantiate: function(name, args) {
+ args = Ext.Array.from(args, true);
+ args.unshift(name);
+
+ return this.instantiate.apply(this, args);
+ },
+
+ /**
+ * @private
+ * @param length
+ */
+ getInstantiator: function(length) {
+ if (!this.instantiators[length]) {
+ var i = length,
+ args = [];
+
+ for (i = 0; i < length; i++) {
+ args.push('a['+i+']');
+ }
+
+ this.instantiators[length] = new Function('c', 'a', 'return new c('+args.join(',')+')');
+ }
+
+ return this.instantiators[length];
+ },
+
+ /**
+ * @private
+ */
+ postprocessors: {},
+
+ /**
+ * @private
+ */
+ defaultPostprocessors: [],
+
+ /**
+ * Register a post-processor function.
+ *
+ * @param {String} name
+ * @param {Function} postprocessor
+ */
+ registerPostprocessor: function(name, fn, always) {
+ this.postprocessors[name] = {
+ name: name,
+ always: always || false,
+ fn: fn
+ };
+
+ return this;
+ },
+
+ /**
+ * Set the default post processors array stack which are applied to every class.
+ *
+ * @param {String/Array} The name of a registered post processor or an array of registered names.
+ * @return {Ext.ClassManager} this
+ */
+ setDefaultPostprocessors: function(postprocessors) {
+ this.defaultPostprocessors = Ext.Array.from(postprocessors);
+
+ return this;
+ },
+
+ /**
+ * Insert this post-processor at a specific position in the stack, optionally relative to
+ * any existing post-processor
+ *
+ * @param {String} name The post-processor name. Note that it needs to be registered with
+ * {@link Ext.ClassManager#registerPostprocessor} before this
+ * @param {String} offset The insertion position. Four possible values are:
+ * 'first', 'last', or: 'before', 'after' (relative to the name provided in the third argument)
+ * @param {String} relativeName
+ * @return {Ext.ClassManager} this
+ */
+ setDefaultPostprocessorPosition: function(name, offset, relativeName) {
+ var defaultPostprocessors = this.defaultPostprocessors,
+ index;
+
+ if (typeof offset === 'string') {
+ if (offset === 'first') {
+ defaultPostprocessors.unshift(name);
+
+ return this;
+ }
+ else if (offset === 'last') {
+ defaultPostprocessors.push(name);
+
+ return this;
+ }
+
+ offset = (offset === 'after') ? 1 : -1;
+ }
+
+ index = Ext.Array.indexOf(defaultPostprocessors, relativeName);
+
+ if (index !== -1) {
+ defaultPostprocessors.splice(Math.max(0, index + offset), 0, name);
+ }
+
+ return this;
+ },
+
+ /**
+ * Converts a string expression to an array of matching class names. An expression can either refers to class aliases
+ * or class names. Expressions support wildcards:
+
+ // returns ['Ext.window.Window']
+ var window = Ext.ClassManager.getNamesByExpression('widget.window');
+
+ // returns ['widget.panel', 'widget.window', ...]
+ var allWidgets = Ext.ClassManager.getNamesByExpression('widget.*');
+
+ // returns ['Ext.data.Store', 'Ext.data.ArrayProxy', ...]
+ var allData = Ext.ClassManager.getNamesByExpression('Ext.data.*');
+
+ * @param {String} expression
+ * @return {Array} classNames
+ * @markdown
+ */
+ getNamesByExpression: function(expression) {
+ var nameToAliasesMap = this.maps.nameToAliases,
+ names = [],
+ name, alias, aliases, possibleName, regex, i, ln;
+
+ if (typeof expression !== 'string' || expression.length < 1) {
+ Ext.Error.raise({
+ sourceClass: "Ext.ClassManager",
+ sourceMethod: "getNamesByExpression",
+ msg: "Expression " + expression + " is invalid, must be a non-empty string"
+ });
+ }
+
+ if (expression.indexOf('*') !== -1) {
+ expression = expression.replace(/\*/g, '(.*?)');
+ regex = new RegExp('^' + expression + '$');
+
+ for (name in nameToAliasesMap) {
+ if (nameToAliasesMap.hasOwnProperty(name)) {
+ aliases = nameToAliasesMap[name];
+
+ if (name.search(regex) !== -1) {
+ names.push(name);
+ }
+ else {
+ for (i = 0, ln = aliases.length; i < ln; i++) {
+ alias = aliases[i];
+
+ if (alias.search(regex) !== -1) {
+ names.push(name);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ } else {
+ possibleName = this.getNameByAlias(expression);
+
+ if (possibleName) {
+ names.push(possibleName);
+ } else {
+ possibleName = this.getNameByAlternate(expression);
+
+ if (possibleName) {
+ names.push(possibleName);
+ } else {
+ names.push(expression);
+ }
+ }
+ }
+
+ return names;
+ }
+ };
+
+ Manager.registerPostprocessor('alias', function(name, cls, data) {
+ var aliases = data.alias,
+ widgetPrefix = 'widget.',
+ i, ln, alias;
+
+ if (!(aliases instanceof Array)) {
+ aliases = [aliases];
+ }
+
+ for (i = 0, ln = aliases.length; i < ln; i++) {
+ alias = aliases[i];
+
+ if (typeof alias !== 'string') {
+ Ext.Error.raise({
+ sourceClass: "Ext",
+ sourceMethod: "define",
+ msg: "Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"
+ });
+ }
+
+ this.setAlias(cls, alias);
+ }
+
+ // This is ugly, will change to make use of parseNamespace for alias later on
+ for (i = 0, ln = aliases.length; i < ln; i++) {
+ alias = aliases[i];
+
+ if (alias.substring(0, widgetPrefix.length) === widgetPrefix) {
+ // Only the first alias with 'widget.' prefix will be used for xtype
+ cls.xtype = cls.$xtype = alias.substring(widgetPrefix.length);
+ break;
+ }
+ }
+ });
+
+ Manager.registerPostprocessor('singleton', function(name, cls, data, fn) {
+ fn.call(this, name, new cls(), data);
+ return false;
+ });
+
+ Manager.registerPostprocessor('alternateClassName', function(name, cls, data) {
+ var alternates = data.alternateClassName,
+ i, ln, alternate;
+
+ if (!(alternates instanceof Array)) {
+ alternates = [alternates];
+ }
+
+ for (i = 0, ln = alternates.length; i < ln; i++) {
+ alternate = alternates[i];
+
+ if (typeof alternate !== 'string') {
+ Ext.Error.raise({
+ sourceClass: "Ext",
+ sourceMethod: "define",
+ msg: "Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"
+ });
+ }
+
+ this.set(alternate, cls);
+ }
+ });
+
+ Manager.setDefaultPostprocessors(['alias', 'singleton', 'alternateClassName']);
+
+ Ext.apply(Ext, {
+ /**
+ * Convenient shorthand, see {@link Ext.ClassManager#instantiate}
+ * @member Ext
+ * @method create
+ */
+ create: alias(Manager, 'instantiate'),
+
+ /**
+ * @private
+ * API to be stablized
+ *
+ * @param {Mixed} item
+ * @param {String} namespace
+ */
+ factory: function(item, namespace) {
+ if (item instanceof Array) {
+ var i, ln;
+
+ for (i = 0, ln = item.length; i < ln; i++) {
+ item[i] = Ext.factory(item[i], namespace);
+ }
+
+ return item;
+ }
+
+ var isString = (typeof item === 'string');
+
+ if (isString || (item instanceof Object && item.constructor === Object)) {
+ var name, config = {};
+
+ if (isString) {
+ name = item;
+ }
+ else {
+ name = item.className;
+ config = item;
+ delete config.className;
+ }
+
+ if (namespace !== undefined && name.indexOf(namespace) === -1) {
+ name = namespace + '.' + Ext.String.capitalize(name);
+ }
+
+ return Ext.create(name, config);
+ }
+
+ if (typeof item === 'function') {
+ return Ext.create(item);
+ }
+
+ return item;
+ },
+
+ /**
+ * Convenient shorthand to create a widget by its xtype, also see {@link Ext.ClassManager#instantiateByAlias}
+
+ var button = Ext.widget('button'); // Equivalent to Ext.create('widget.button')
+ var panel = Ext.widget('panel'); // Equivalent to Ext.create('widget.panel')
+
+ * @member Ext
+ * @method widget
+ * @markdown
+ */
+ widget: function(name) {
+ var args = slice.call(arguments);
+ args[0] = 'widget.' + name;
+
+ return Manager.instantiateByAlias.apply(Manager, args);
+ },
+
+ /**
+ * Convenient shorthand, see {@link Ext.ClassManager#instantiateByAlias}
+ * @member Ext
+ * @method createByAlias
+ */
+ createByAlias: alias(Manager, 'instantiateByAlias'),
+
+ /**
+ * Convenient shorthand for {@link Ext.ClassManager#create}, see detailed {@link Ext.Class explanation}
+ * @member Ext
+ * @method define
+ */
+ define: alias(Manager, 'create'),
+
+ /**
+ * Convenient shorthand, see {@link Ext.ClassManager#getName}
+ * @member Ext
+ * @method getClassName
+ */
+ getClassName: alias(Manager, 'getName'),
+
+ /**
+ *
+ * @param {Mixed} object
+ */
+ getDisplayName: function(object) {
+ if (object.displayName) {
+ return object.displayName;
+ }
+
+ if (object.$name && object.$class) {
+ return Ext.getClassName(object.$class) + '#' + object.$name;
+ }
+
+ if (object.$className) {
+ return object.$className;
+ }
+
+ return 'Anonymous';
+ },
+
+ /**
+ * Convenient shorthand, see {@link Ext.ClassManager#getClass}
+ * @member Ext
+ * @method getClassName
+ */
+ getClass: alias(Manager, 'getClass'),
+
+ /**
+ * Creates namespaces to be used for scoping variables and classes so that they are not global.
+ * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
+
+ Ext.namespace('Company', 'Company.data');
+
+ // equivalent and preferable to the above syntax
+ Ext.namespace('Company.data');
+
+ Company.Widget = function() { ... };
+
+ Company.data.CustomStore = function(config) { ... };
+
+ * @param {String} namespace1
+ * @param {String} namespace2
+ * @param {String} etc
+ * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
+ * @function
+ * @member Ext
+ * @method namespace
+ * @markdown
+ */
+ namespace: alias(Manager, 'createNamespaces')
+ });
+
+ Ext.createWidget = Ext.widget;
+
+ /**
+ * Convenient alias for {@link Ext#namespace Ext.namespace}
+ * @member Ext
+ * @method ns
+ */
+ Ext.ns = Ext.namespace;
+
+ Class.registerPreprocessor('className', function(cls, data) {
+ if (data.$className) {
+ cls.$className = data.$className;
+ cls.displayName = cls.$className;
+ }
+ }, true);
+
+ Class.setDefaultPreprocessorPosition('className', 'first');
+
+})(Ext.Class, Ext.Function.alias);
+
+/**
+ * @author Jacky Nguyen <jacky@sencha.com>
+ * @docauthor Jacky Nguyen <jacky@sencha.com>
+ * @class Ext.Loader
+ *
+
+Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
+via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
+approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons of each approach:
+
+# Asynchronous Loading #
+
+- Advantages:
+ + Cross-domain
+ + No web server needed: you can run the application via the file system protocol (i.e: `file://path/to/your/index
+ .html`)
+ + Best possible debugging experience: error messages come with the exact file name and line number
+
+- Disadvantages:
+ + Dependencies need to be specified before-hand
+
+### Method 1: Explicitly include what you need: ###
+
+ // Syntax
+ Ext.require({String/Array} expressions);
+
+ // Example: Single alias
+ Ext.require('widget.window');
+
+ // Example: Single class name
+ Ext.require('Ext.window.Window');
+
+ // Example: Multiple aliases / class names mix
+ Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
+
+ // Wildcards
+ Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
+
+### Method 2: Explicitly exclude what you don't need: ###
+
+ // Syntax: Note that it must be in this chaining format.
+ Ext.exclude({String/Array} expressions)
+ .require({String/Array} expressions);
+
+ // Include everything except Ext.data.*
+ Ext.exclude('Ext.data.*').require('*');Â
+
+ // Include all widgets except widget.checkbox*,
+ // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
+ Ext.exclude('widget.checkbox*').require('widget.*');
+
+# Synchronous Loading on Demand #
+
+- *Advantages:*
+ + There's no need to specify dependencies before-hand, which is always the convenience of including ext-all.js
+ before
+
+- *Disadvantages:*
+ + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
+ + Must be from the same domain due to XHR restriction
+ + Need a web server, same reason as above
+
+There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
+
+ Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
+
+ Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
+
+ Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
+
+Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
+ existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load the given
+ class and all its dependencies.
+
+# Hybrid Loading - The Best of Both Worlds #
+
+It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
+
+### Step 1: Start writing your application using synchronous approach. Ext.Loader will automatically fetch all
+ dependencies on demand as they're needed during run-time. For example: ###
+
+ Ext.onReady(function(){
+ var window = Ext.createWidget('window', {
+ width: 500,
+ height: 300,
+ layout: {
+ type: 'border',
+ padding: 5
+ },
+ title: 'Hello Dialog',
+ items: [{
+ title: 'Navigation',
+ collapsible: true,
+ region: 'west',
+ width: 200,
+ html: 'Hello',
+ split: true
+ }, {
+ title: 'TabPanel',
+ region: 'center'
+ }]
+ });
+
+ window.show();
+ })
+
+### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these: ###
+
+ [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code
+ ClassManager.js:432
+ [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
+
+Simply copy and paste the suggested code above `Ext.onReady`, i.e:
+
+ Ext.require('Ext.window.Window');
+ Ext.require('Ext.layout.container.Border');
+
+ Ext.onReady(...);
+
+Everything should now load via asynchronous mode.
+
+# Deployment #
+
+It's important to note that dynamic loading should only be used during development on your local machines.
+During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
+the whole process of transitioning from / to between development / maintenance and production as easy as
+possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies your application
+needs in the exact loading sequence. It's as simple as concatenating all files in this array into one,
+then include it on top of your application.
+
+This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
+
+ * @singleton
+ * @markdown
+ */
+
+(function(Manager, Class, flexSetter, alias) {
+
+ var
+ dependencyProperties = ['extend', 'mixins', 'requires'],
+ Loader;
+
+ Loader = Ext.Loader = {
+ /**
+ * @private
+ */
+ documentHead: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),
+
+ /**
+ * Flag indicating whether there are still files being loaded
+ * @private
+ */
+ isLoading: false,
+
+ /**
+ * Maintain the queue for all dependencies. Each item in the array is an object of the format:
+ * {
+ * requires: [...], // The required classes for this queue item
+ * callback: function() { ... } // The function to execute when all classes specified in requires exist
+ * }
+ * @private
+ */
+ queue: [],
+
+ /**
+ * Maintain the list of files that have already been handled so that they never get double-loaded
+ * @private
+ */
+ isFileLoaded: {},
+
+ /**
+ * Maintain the list of listeners to execute when all required scripts are fully loaded
+ * @private
+ */
+ readyListeners: [],
+
+ /**
+ * Contains optional dependencies to be loaded last
+ * @private
+ */
+ optionalRequires: [],
+
+ /**
+ * Map of fully qualified class names to an array of dependent classes.
+ * @private
+ */
+ requiresMap: {},
+
+ /**
+ * @private
+ */
+ numPendingFiles: 0,
+
+ /**
+ * @private
+ */
+ numLoadedFiles: 0,
+
+ /** @private */
+ hasFileLoadError: false,
+
+ /**
+ * @private
+ */
+ classNameToFilePathMap: {},
+
+ /**
+ * An array of class names to keep track of the dependency loading order.
+ * This is not guaranteed to be the same everytime due to the asynchronous
+ * nature of the Loader.
+ *
+ * @property history
+ * @type Array
+ */
+ history: [],
+
+ /**
+ * Configuration
+ * @private
+ */
+ config: {
+ /**
+ * Whether or not to enable the dynamic dependency loading feature
+ * Defaults to false
+ * @cfg {Boolean} enabled
+ */
+ enabled: false,
+
+ /**
+ * @cfg {Boolean} disableCaching
+ * Appends current timestamp to script files to prevent caching
+ * Defaults to true
+ */
+ disableCaching: true,
+
+ /**
+ * @cfg {String} disableCachingParam
+ * The get parameter name for the cache buster's timestamp.
+ * Defaults to '_dc'
+ */
+ disableCachingParam: '_dc',
+
+ /**
+ * @cfg {Object} paths
+ * The mapping from namespaces to file paths
+ {
+ 'Ext': '.', // This is set by default, Ext.layout.container.Container will be
+ // loaded from ./layout/Container.js
+
+ 'My': './src/my_own_folder' // My.layout.Container will be loaded from
+ // ./src/my_own_folder/layout/Container.js
+ }
+ * Note that all relative paths are relative to the current HTML document.
+ * If not being specified, for example, <code>Other.awesome.Class</code>
+ * will simply be loaded from <code>./Other/awesome/Class.js</code>
+ */
+ paths: {
+ 'Ext': '.'
+ }
+ },
+
+ /**
+ * Set the configuration for the loader. This should be called right after ext-core.js
+ * (or ext-core-debug.js) is included in the page, i.e:
+
+ <script type="text/javascript" src="ext-core-debug.js"></script>
+ <script type="text/javascript">
+ Ext.Loader.setConfig({
+ enabled: true,
+ paths: {
+ 'My': 'my_own_path'
+ }
+ });
+ <script>
+ <script type="text/javascript">
+ Ext.require(...);
+
+ Ext.onReady(function() {
+ // application code here
+ });
+ </script>
+
+ * Refer to {@link Ext.Loader#configs} for the list of possible properties
+ *
+ * @param {Object} config The config object to override the default values in {@link Ext.Loader#config}
+ * @return {Ext.Loader} this
+ * @markdown
+ */
+ setConfig: function(name, value) {
+ if (Ext.isObject(name) && arguments.length === 1) {
+ Ext.Object.merge(this.config, name);
+ }
+ else {
+ this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
+ }
+
+ return this;
+ },
+
+ /**
+ * Get the config value corresponding to the specified name. If no name is given, will return the config object
+ * @param {String} name The config property name
+ * @return {Object/Mixed}
+ */
+ getConfig: function(name) {
+ if (name) {
+ return this.config[name];
+ }
+
+ return this.config;
+ },
+
+ /**
+ * Sets the path of a namespace.
+ * For Example:
+
+ Ext.Loader.setPath('Ext', '.');
+
+ * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
+ * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
+ * @return {Ext.Loader} this
+ * @markdown
+ */
+ setPath: flexSetter(function(name, path) {
+ this.config.paths[name] = path;
+
+ return this;
+ }),
+
+ /**
+ * Translates a className to a file path by adding the
+ * the proper prefix and converting the .'s to /'s. For example:
+
+ Ext.Loader.setPath('My', '/path/to/My');
+
+ alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
+
+ * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
+
+ Ext.Loader.setPath({
+ 'My': '/path/to/lib',
+ 'My.awesome': '/other/path/for/awesome/stuff',
+ 'My.awesome.more': '/more/awesome/path'
+ });
+
+ alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
+
+ alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
+
+ alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
+
+ alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
+
+ * @param {String} className
+ * @return {String} path
+ * @markdown
+ */
+ getPath: function(className) {
+ var path = '',
+ paths = this.config.paths,
+ prefix = this.getPrefix(className);
+
+ if (prefix.length > 0) {
+ if (prefix === className) {
+ return paths[prefix];
+ }
+
+ path = paths[prefix];
+ className = className.substring(prefix.length + 1);
+ }
+
+ if (path.length > 0) {
+ path += '/';
+ }
+
+ return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
+ },
+
+ /**
+ * @private
+ * @param {String} className
+ */
+ getPrefix: function(className) {
+ var paths = this.config.paths,
+ prefix, deepestPrefix = '';
+
+ if (paths.hasOwnProperty(className)) {
+ return className;
+ }
+
+ for (prefix in paths) {
+ if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
+ if (prefix.length > deepestPrefix.length) {
+ deepestPrefix = prefix;
+ }
+ }
+ }
+
+ return deepestPrefix;
+ },
+
+ /**
+ * Refresh all items in the queue. If all dependencies for an item exist during looping,
+ * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
+ * empty
+ * @private
+ */
+ refreshQueue: function() {
+ var ln = this.queue.length,
+ i, item, j, requires;
+
+ if (ln === 0) {
+ this.triggerReady();
+ return;
+ }
+
+ for (i = 0; i < ln; i++) {
+ item = this.queue[i];
+
+ if (item) {
+ requires = item.requires;
+
+ // Don't bother checking when the number of files loaded
+ // is still less than the array length
+ if (requires.length > this.numLoadedFiles) {
+ continue;
+ }
+
+ j = 0;
+
+ do {
+ if (Manager.isCreated(requires[j])) {
+ // Take out from the queue
+ requires.splice(j, 1);
+ }
+ else {
+ j++;
+ }
+ } while (j < requires.length);
+
+ if (item.requires.length === 0) {
+ this.queue.splice(i, 1);
+ item.callback.call(item.scope);
+ this.refreshQueue();
+ break;
+ }
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * Inject a script element to document's head, call onLoad and onError accordingly
+ * @private
+ */
+ injectScriptElement: function(url, onLoad, onError, scope) {
+ var script = document.createElement('script'),
+ me = this,
+ onLoadFn = function() {
+ me.cleanupScriptElement(script);
+ onLoad.call(scope);
+ },
+ onErrorFn = function() {
+ me.cleanupScriptElement(script);
+ onError.call(scope);
+ };
+
+ script.type = 'text/javascript';
+ script.src = url;
+ script.onload = onLoadFn;
+ script.onerror = onErrorFn;
+ script.onreadystatechange = function() {
+ if (this.readyState === 'loaded' || this.readyState === 'complete') {
+ onLoadFn();
+ }
+ };
+
+ this.documentHead.appendChild(script);
+
+ return script;
+ },
+
+ /**
+ * @private
+ */
+ cleanupScriptElement: function(script) {
+ script.onload = null;
+ script.onreadystatechange = null;
+ script.onerror = null;
+
+ return this;
+ },
+
+ /**
+ * Load a script file, supports both asynchronous and synchronous approaches
+ *
+ * @param {String} url
+ * @param {Function} onLoad
+ * @param {Scope} scope
+ * @param {Boolean} synchronous
+ * @private
+ */
+ loadScriptFile: function(url, onLoad, onError, scope, synchronous) {
+ var me = this,
+ noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
+ fileName = url.split('/').pop(),
+ isCrossOriginRestricted = false,
+ xhr, status, onScriptError;
+
+ scope = scope || this;
+
+ this.isLoading = true;
+
+ if (!synchronous) {
+ onScriptError = function() {
+ onError.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
+ };
+
+ if (!Ext.isReady && Ext.onDocumentReady) {
+ Ext.onDocumentReady(function() {
+ me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
+ });
+ }
+ else {
+ this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
+ }
+ }
+ else {
+ if (typeof XMLHttpRequest !== 'undefined') {
+ xhr = new XMLHttpRequest();
+ } else {
+ xhr = new ActiveXObject('Microsoft.XMLHTTP');
+ }
+
+ try {
+ xhr.open('GET', noCacheUrl, false);
+ xhr.send(null);
+ } catch (e) {
+ isCrossOriginRestricted = true;
+ }
+
+ status = (xhr.status === 1223) ? 204 : xhr.status;
+
+ if (!isCrossOriginRestricted) {
+ isCrossOriginRestricted = (status === 0);
+ }
+
+ if (isCrossOriginRestricted
+ ) {
+ onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
+ "being loaded from a different domain or from the local file system whereby cross origin " +
+ "requests are not allowed due to security reasons. Use asynchronous loading with " +
+ "Ext.require instead.", synchronous);
+ }
+ else if (status >= 200 && status < 300
+ ) {
+ // Firebug friendly, file names are still shown even though they're eval'ed code
+ new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();
+
+ onLoad.call(scope);
+ }
+ else {
+ onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
+ "verify that the file exists. " +
+ "XHR status code: " + status, synchronous);
+ }
+
+ // Prevent potential IE memory leak
+ xhr = null;
+ }
+ },
+
+ /**
+ * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
+ * Can be chained with more `require` and `exclude` methods, eg:
+
+ Ext.exclude('Ext.data.*').require('*');
+
+ Ext.exclude('widget.button*').require('widget.*');
+
+ * @param {Array} excludes
+ * @return {Object} object contains `require` method for chaining
+ * @markdown
+ */
+ exclude: function(excludes) {
+ var me = this;
+
+ return {
+ require: function(expressions, fn, scope) {
+ return me.require(expressions, fn, scope, excludes);
+ },
+
+ syncRequire: function(expressions, fn, scope) {
+ return me.syncRequire(expressions, fn, scope, excludes);
+ }
+ };
+ },
+
+ /**
+ * 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
+ * @param {String/Array} expressions Can either be a string or an array of string
+ * @param {Function} fn (Optional) The callback function
+ * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
+ * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
+ * @markdown
+ */
+ syncRequire: function() {
+ this.syncModeEnabled = true;
+ this.require.apply(this, arguments);
+ this.refreshQueue();
+ this.syncModeEnabled = false;
+ },
+
+ /**
+ * 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#require Ext.require} for convenience
+ * @param {String/Array} expressions Can either be a string or an array of string
+ * @param {Function} fn (Optional) The callback function
+ * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
+ * @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
+ * @markdown
+ */
+ require: function(expressions, fn, scope, excludes) {
+ var filePath, expression, exclude, className, excluded = {},
+ excludedClassNames = [],
+ possibleClassNames = [],
+ possibleClassName, classNames = [],
+ i, j, ln, subLn;
+
+ expressions = Ext.Array.from(expressions);
+ excludes = Ext.Array.from(excludes);
+
+ fn = fn || Ext.emptyFn;
+
+ scope = scope || Ext.global;
+
+ for (i = 0, ln = excludes.length; i < ln; i++) {
+ exclude = excludes[i];
+
+ if (typeof exclude === 'string' && exclude.length > 0) {
+ excludedClassNames = Manager.getNamesByExpression(exclude);
+
+ for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
+ excluded[excludedClassNames[j]] = true;
+ }
+ }
+ }
+
+ for (i = 0, ln = expressions.length; i < ln; i++) {
+ expression = expressions[i];
+
+ if (typeof expression === 'string' && expression.length > 0) {
+ possibleClassNames = Manager.getNamesByExpression(expression);
+
+ for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
+ possibleClassName = possibleClassNames[j];
+
+ if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
+ Ext.Array.include(classNames, possibleClassName);
+ }
+ }
+ }
+ }
+
+ // If the dynamic dependency feature is not being used, throw an error
+ // if the dependencies are not defined
+ if (!this.config.enabled) {
+ if (classNames.length > 0) {
+ Ext.Error.raise({
+ sourceClass: "Ext.Loader",
+ sourceMethod: "require",
+ msg: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
+ "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
+ });
+ }
+ }
+
+ if (classNames.length === 0) {
+ fn.call(scope);
+ return this;
+ }
+
+ this.queue.push({
+ requires: classNames,
+ callback: fn,
+ scope: scope
+ });
+
+ classNames = classNames.slice();
+
+ for (i = 0, ln = classNames.length; i < ln; i++) {
+ className = classNames[i];
+
+ if (!this.isFileLoaded.hasOwnProperty(className)) {
+ this.isFileLoaded[className] = false;
+
+ filePath = this.getPath(className);
+
+ this.classNameToFilePathMap[className] = filePath;
+
+ this.numPendingFiles++;
+
+ this.loadScriptFile(
+ filePath,
+ Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
+ Ext.Function.pass(this.onFileLoadError, [className, filePath]),
+ this,
+ this.syncModeEnabled
+ );
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ * @param {String} className
+ * @param {String} filePath
+ */
+ onFileLoaded: function(className, filePath) {
+ this.numLoadedFiles++;
+
+ this.isFileLoaded[className] = true;
+
+ this.numPendingFiles--;
+
+ if (this.numPendingFiles === 0) {
+ this.refreshQueue();
+ }
+
+ if (this.numPendingFiles <= 1) {
+ window.status = "Finished loading all dependencies, onReady fired!";
+ }
+ else {
+ window.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
+ }
+
+ if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
+ var queue = this.queue,
+ requires,
+ i, ln, j, subLn, missingClasses = [], missingPaths = [];
+
+ for (i = 0, ln = queue.length; i < ln; i++) {
+ requires = queue[i].requires;
+
+ for (j = 0, subLn = requires.length; j < ln; j++) {
+ if (this.isFileLoaded[requires[j]]) {
+ missingClasses.push(requires[j]);
+ }
+ }
+ }
+
+ if (missingClasses.length < 1) {
+ return;
+ }
+
+ missingClasses = Ext.Array.filter(missingClasses, function(item) {
+ return !this.requiresMap.hasOwnProperty(item);
+ }, this);
+
+ for (i = 0,ln = missingClasses.length; i < ln; i++) {
+ missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]);
+ }
+
+ Ext.Error.raise({
+ sourceClass: "Ext.Loader",
+ sourceMethod: "onFileLoaded",
+ msg: "The following classes are not declared even if their files have been " +
+ "loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
+ "corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
+ });
+ }
+ },
+
+ /**
+ * @private
+ */
+ onFileLoadError: function(className, filePath, errorMessage, isSynchronous) {
+ this.numPendingFiles--;
+ this.hasFileLoadError = true;
+
+ Ext.Error.raise({
+ sourceClass: "Ext.Loader",
+ classToLoad: className,
+ loadPath: filePath,
+ loadingType: isSynchronous ? 'synchronous' : 'async',
+ msg: errorMessage
+ });
+ },
+
+ /**
+ * @private
+ */
+ addOptionalRequires: function(requires) {
+ var optionalRequires = this.optionalRequires,
+ i, ln, require;
+
+ requires = Ext.Array.from(requires);
+
+ for (i = 0, ln = requires.length; i < ln; i++) {
+ require = requires[i];
+
+ Ext.Array.include(optionalRequires, require);
+ }
+
+ return this;
+ },
+
+ /**
+ * @private
+ */
+ triggerReady: function(force) {
+ var readyListeners = this.readyListeners,
+ optionalRequires, listener;
+
+ if (this.isLoading || force) {
+ this.isLoading = false;
+
+ if (this.optionalRequires.length) {
+ // Clone then empty the array to eliminate potential recursive loop issue
+ optionalRequires = Ext.Array.clone(this.optionalRequires);
+
+ // Empty the original array
+ this.optionalRequires.length = 0;
+
+ this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
+ return this;
+ }
+
+ while (readyListeners.length) {
+ listener = readyListeners.shift();
+ listener.fn.call(listener.scope);
+
+ if (this.isLoading) {
+ return this;
+ }
+ }
+ }
+
+ return this;
+ },
+
+ /**
+ * Add a new listener to be executed when all required scripts are fully loaded
+ *
+ * @param {Function} fn The function callback to be executed
+ * @param {Object} scope The execution scope (<code>this</code>) of the callback function
+ * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
+ */
+ onReady: function(fn, scope, withDomReady, options) {
+ var oldFn;
+
+ if (withDomReady !== false && Ext.onDocumentReady) {
+ oldFn = fn;
+
+ fn = function() {
+ Ext.onDocumentReady(oldFn, scope, options);
+ };
+ }
+
+ if (!this.isLoading) {
+ fn.call(scope);
+ }
+ else {
+ this.readyListeners.push({
+ fn: fn,
+ scope: scope
+ });
+ }
+ },
+
+ /**
+ * @private
+ * @param {String} className
+ */
+ historyPush: function(className) {
+ if (className && this.isFileLoaded.hasOwnProperty(className)) {
+ Ext.Array.include(this.history, className);
+ }
+
+ return this;
+ }
+ };
+
+ /**
+ * Convenient alias of {@link Ext.Loader#require}. Please see the introduction documentation of
+ * {@link Ext.Loader} for examples.
+ * @member Ext
+ * @method require
+ */
+ Ext.require = alias(Loader, 'require');
+
+ /**
+ * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
+ *
+ * @member Ext
+ * @method syncRequire
+ */
+ Ext.syncRequire = alias(Loader, 'syncRequire');
+
+ /**
+ * Convenient shortcut to {@link Ext.Loader#exclude}
+ * @member Ext
+ * @method exclude
+ */
+ Ext.exclude = alias(Loader, 'exclude');
+
+ /**
+ * @member Ext
+ * @method onReady
+ */
+ Ext.onReady = function(fn, scope, options) {
+ Loader.onReady(fn, scope, true, options);
+ };
+
+ Class.registerPreprocessor('loader', function(cls, data, continueFn) {
+ var me = this,
+ dependencies = [],
+ className = Manager.getName(cls),
+ i, j, ln, subLn, value, propertyName, propertyValue;
+
+ /*
+ Basically loop through the dependencyProperties, look for string class names and push
+ them into a stack, regardless of whether the property's value is a string, array or object. For example:
+ {
+ extend: 'Ext.MyClass',
+ requires: ['Ext.some.OtherClass'],
+ mixins: {
+ observable: 'Ext.util.Observable';
+ }
+ }
+ which will later be transformed into:
+ {
+ extend: Ext.MyClass,
+ requires: [Ext.some.OtherClass],
+ mixins: {
+ observable: Ext.util.Observable;
+ }
+ }
+ */
+
+ for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
+ propertyName = dependencyProperties[i];
+
+ if (data.hasOwnProperty(propertyName)) {
+ propertyValue = data[propertyName];
+
+ if (typeof propertyValue === 'string') {
+ dependencies.push(propertyValue);
+ }
+ else if (propertyValue instanceof Array) {
+ for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
+ value = propertyValue[j];
+
+ if (typeof value === 'string') {
+ dependencies.push(value);
+ }
+ }
+ }
+ else {
+ for (j in propertyValue) {
+ if (propertyValue.hasOwnProperty(j)) {
+ value = propertyValue[j];
+
+ if (typeof value === 'string') {
+ dependencies.push(value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (dependencies.length === 0) {
+// Loader.historyPush(className);
+ return;
+ }
+
+ var deadlockPath = [],
+ requiresMap = Loader.requiresMap,
+ detectDeadlock;
+
+ /*
+ Automatically detect deadlocks before-hand,
+ will throw an error with detailed path for ease of debugging. Examples of deadlock cases:
+
+ - A extends B, then B extends A
+ - A requires B, B requires C, then C requires A
+
+ The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
+ no matter how deep the path is.
+ */
+
+ if (className) {
+ requiresMap[className] = dependencies;
+
+ detectDeadlock = function(cls) {
+ deadlockPath.push(cls);
+
+ if (requiresMap[cls]) {
+ if (Ext.Array.contains(requiresMap[cls], className)) {
+ Ext.Error.raise({
+ sourceClass: "Ext.Loader",
+ msg: "Deadlock detected while loading dependencies! '" + className + "' and '" +
+ deadlockPath[1] + "' " + "mutually require each other. Path: " +
+ deadlockPath.join(' -> ') + " -> " + deadlockPath[0]
+ });
+ }
+
+ for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
+ detectDeadlock(requiresMap[cls][i]);
+ }
+ }
+ };
+
+ detectDeadlock(className);
+ }
+
+
+ Loader.require(dependencies, function() {
+ for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
+ propertyName = dependencyProperties[i];
+
+ if (data.hasOwnProperty(propertyName)) {
+ propertyValue = data[propertyName];
+
+ if (typeof propertyValue === 'string') {
+ data[propertyName] = Manager.get(propertyValue);
+ }
+ else if (propertyValue instanceof Array) {
+ for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
+ value = propertyValue[j];
+
+ if (typeof value === 'string') {
+ data[propertyName][j] = Manager.get(value);
+ }
+ }
+ }
+ else {
+ for (var k in propertyValue) {
+ if (propertyValue.hasOwnProperty(k)) {
+ value = propertyValue[k];
+
+ if (typeof value === 'string') {
+ data[propertyName][k] = Manager.get(value);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ continueFn.call(me, cls, data);
+ });
+
+ return false;
+ }, true);
+
+ Class.setDefaultPreprocessorPosition('loader', 'after', 'className');
+
+ Manager.registerPostprocessor('uses', function(name, cls, data) {
+ var uses = Ext.Array.from(data.uses),
+ items = [],
+ i, ln, item;
+
+ for (i = 0, ln = uses.length; i < ln; i++) {
+ item = uses[i];
+
+ if (typeof item === 'string') {
+ items.push(item);
+ }
+ }
+
+ Loader.addOptionalRequires(items);
+ });
+
+ Manager.setDefaultPostprocessorPosition('uses', 'last');
+
+})(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);
+
+/**
+ * @class Ext.Error
+ * @private
+ * @extends Error
+
+A wrapper class for the native JavaScript Error object that adds a few useful capabilities for handling
+errors in an Ext application. When you use Ext.Error to {@link #raise} an error from within any class that
+uses the Ext 4 class system, the Error class can automatically add the source class and method from which
+the error was raised. It also includes logic to automatically log the eroor to the console, if available,
+with additional metadata about the error. In all cases, the error will always be thrown at the end so that
+execution will halt.
+
+Ext.Error also offers a global error {@link #handle handling} method that can be overridden in order to
+handle application-wide errors in a single spot. You can optionally {@link #ignore} errors altogether,
+although in a real application it's usually a better idea to override the handling function and perform
+logging or some other method of reporting the errors in a way that is meaningful to the application.
+
+At its simplest you can simply raise an error as a simple string from within any code:
+
+#Example usage:#
+
+ Ext.Error.raise('Something bad happened!');
+
+If raised from plain JavaScript code, the error will be logged to the console (if available) and the message
+displayed. In most cases however you'll be raising errors from within a class, and it may often be useful to add
+additional metadata about the error being raised. The {@link #raise} method can also take a config object.
+In this form the `msg` attribute becomes the error description, and any other data added to the config gets
+added to the error object and, if the console is available, logged to the console for inspection.
+
+#Example usage:#
+
+ Ext.define('Ext.Foo', {
+ doSomething: function(option){
+ if (someCondition === false) {
+ Ext.Error.raise({
+ msg: 'You cannot do that!',
+ option: option, // whatever was passed into the method
+ 'error code': 100 // other arbitrary info
+ });
+ }
+ }
+ });
+
+If a console is available (that supports the `console.dir` function) you'll see console output like:
+
+ An error was raised with the following data:
+ option: Object { foo: "bar"}
+ foo: "bar"
+ error code: 100
+ msg: "You cannot do that!"
+ sourceClass: "Ext.Foo"
+ sourceMethod: "doSomething"
+
+ uncaught exception: You cannot do that!
+
+As you can see, the error will report exactly where it was raised and will include as much information as the
+raising code can usefully provide.
+
+If you want to handle all application errors globally you can simply override the static {@link handle} method
+and provide whatever handling logic you need. If the method returns true then the error is considered handled
+and will not be thrown to the browser. If anything but true is returned then the error will be thrown normally.
+
+#Example usage:#
+
+ Ext.Error.handle = function(err) {
+ if (err.someProperty == 'NotReallyAnError') {
+ // maybe log something to the application here if applicable
+ return true;
+ }
+ // any non-true return value (including none) will cause the error to be thrown
+ }
+
+ * Create a new Error object
+ * @param {Object} config The config object
+ * @markdown
+ * @author Brian Moeskau <brian@sencha.com>
+ * @docauthor Brian Moeskau <brian@sencha.com>
+ */
+Ext.Error = Ext.extend(Error, {
+ statics: {
+ /**
+ * @property ignore
+Static flag that can be used to globally disable error reporting to the browser if set to true
+(defaults to false). Note that if you ignore Ext errors it's likely that some other code may fail
+and throw a native JavaScript error thereafter, so use with caution. In most cases it will probably
+be preferable to supply a custom error {@link #handle handling} function instead.
+
+#Example usage:#
+
+ Ext.Error.ignore = true;
+
+ * @markdown
+ * @static
+ */
+ ignore: false,
+
+ /**
+Raise an error that can include additional data and supports automatic console logging if available.
+You can pass a string error message or an object with the `msg` attribute which will be used as the
+error message. The object can contain any other name-value attributes (or objects) to be logged
+along with the error.
+
+Note that after displaying the error message a JavaScript error will ultimately be thrown so that
+execution will halt.
+
+#Example usage:#
+
+ Ext.Error.raise('A simple string error message');
+
+ // or...
+
+ Ext.define('Ext.Foo', {
+ doSomething: function(option){
+ if (someCondition === false) {
+ Ext.Error.raise({
+ msg: 'You cannot do that!',
+ option: option, // whatever was passed into the method
+ 'error code': 100 // other arbitrary info
+ });
+ }
+ }
+ });
+ * @param {String/Object} err The error message string, or an object containing the
+ * attribute "msg" that will be used as the error message. Any other data included in
+ * the object will also be logged to the browser console, if available.
+ * @static
+ * @markdown
+ */
+ raise: function(err){
+ err = err || {};
+ if (Ext.isString(err)) {
+ err = { msg: err };
+ }
+
+ var method = this.raise.caller;
+
+ if (method) {
+ if (method.$name) {
+ err.sourceMethod = method.$name;
+ }
+ if (method.$owner) {
+ err.sourceClass = method.$owner.$className;
+ }
+ }
+
+ if (Ext.Error.handle(err) !== true) {
+ var global = Ext.global,
+ con = global.console,
+ msg = Ext.Error.prototype.toString.call(err),
+ noConsoleMsg = 'An uncaught error was raised: "' + msg +
+ '". Use Firebug or Webkit console for additional details.';
+
+ if (con) {
+ if (con.dir) {
+ con.warn('An uncaught error was raised with the following data:');
+ con.dir(err);
+ }
+ else {
+ con.warn(noConsoleMsg);
+ }
+ if (con.error) {
+ con.error(msg);
+ }
+ }
+ else if (global.alert){
+ global.alert(noConsoleMsg);
+ }
+
+ throw new Ext.Error(err);
+ }
+ },
+
+ /**
+Globally handle any Ext errors that may be raised, optionally providing custom logic to
+handle different errors individually. Return true from the function to bypass throwing the
+error to the browser, otherwise the error will be thrown and execution will halt.
+
+#Example usage:#
+
+ Ext.Error.handle = function(err) {
+ if (err.someProperty == 'NotReallyAnError') {
+ // maybe log something to the application here if applicable
+ return true;
+ }
+ // any non-true return value (including none) will cause the error to be thrown
+ }
+
+ * @param {Ext.Error} err The Ext.Error object being raised. It will contain any attributes
+ * that were originally raised with it, plus properties about the method and class from which
+ * the error originated (if raised from a class that uses the Ext 4 class system).
+ * @static
+ * @markdown
+ */
+ handle: function(){
+ return Ext.Error.ignore;
+ }
+ },
+
+ /**
+ * @constructor
+ * @param {String/Object} config The error message string, or an object containing the
+ * attribute "msg" that will be used as the error message. Any other data included in
+ * the object will be applied to the error instance and logged to the browser console, if available.
+ */
+ constructor: function(config){
+ if (Ext.isString(config)) {
+ config = { msg: config };
+ }
+ Ext.apply(this, config);
+ },
+
+ /**
+Provides a custom string representation of the error object. This is an override of the base JavaScript
+`Object.toString` method, which is useful so that when logged to the browser console, an error object will
+be displayed with a useful message instead of `[object Object]`, the default `toString` result.
+
+The default implementation will include the error message along with the raising class and method, if available,
+but this can be overridden with a custom implementation either at the prototype level (for all errors) or on
+a particular error instance, if you want to provide a custom description that will show up in the console.
+ * @markdown
+ * @return {String} The error message. If raised from within the Ext 4 class system, the error message
+ * will also include the raising class and method names, if available.
+ */
+ toString: function(){
+ var me = this,
+ className = me.className ? me.className : '',
+ methodName = me.methodName ? '.' + me.methodName + '(): ' : '',
+ msg = me.msg || '(No description provided)';
+
+ return className + methodName + msg;
+ }
+});
+
+
+