From 25b8292a30cd29487f37e9081bc88aaec2a51ebc Mon Sep 17 00:00:00 2001 From: Geoff H Date: Mon, 8 Jul 2013 18:02:17 -0700 Subject: [PATCH] port to sencha --- www/js/e.js | 4 + www/{lib => lib-old}/ext-all.css | 0 www/{lib => lib-old}/ext-all.js | 0 www/{lib => lib-old}/ext-base.js | 0 www/lib/sencha-touch-all-debug.js | 109175 +++++++++++++++++++++++++++ www/lib/sencha-touch-all.js | 21 + www/lib/sencha-touch-debug.css | 11139 +++ www/starte.yaws | 19 +- 8 files changed, 120347 insertions(+), 11 deletions(-) rename www/{lib => lib-old}/ext-all.css (100%) rename www/{lib => lib-old}/ext-all.js (100%) rename www/{lib => lib-old}/ext-base.js (100%) create mode 100644 www/lib/sencha-touch-all-debug.js create mode 100644 www/lib/sencha-touch-all.js create mode 100644 www/lib/sencha-touch-debug.css diff --git a/www/js/e.js b/www/js/e.js index 8febd8d..8329fa5 100644 --- a/www/js/e.js +++ b/www/js/e.js @@ -166,6 +166,8 @@ var windowAddImage = function(tuple) { var ImageHeight = tuple[4]; var Data = tuple[5].value; + console.log("Window add image: " + WindowID ); + var img = "data:image/png;base64," + window.btoa(Data); var Image = new Ext.Component({ @@ -267,6 +269,8 @@ function launchProcessing(tuple) var Height = tuple[2]; var FuncName = tuple[3]; + //console.log( "launchProcessing" , FuncName ); + canvasWindow = new Ext.Window({ title: FuncName ,height:Height + 32 //132 diff --git a/www/lib/ext-all.css b/www/lib-old/ext-all.css similarity index 100% rename from www/lib/ext-all.css rename to www/lib-old/ext-all.css diff --git a/www/lib/ext-all.js b/www/lib-old/ext-all.js similarity index 100% rename from www/lib/ext-all.js rename to www/lib-old/ext-all.js diff --git a/www/lib/ext-base.js b/www/lib-old/ext-base.js similarity index 100% rename from www/lib/ext-base.js rename to www/lib-old/ext-base.js diff --git a/www/lib/sencha-touch-all-debug.js b/www/lib/sencha-touch-all-debug.js new file mode 100644 index 0000000..dd976a4 --- /dev/null +++ b/www/lib/sencha-touch-all-debug.js @@ -0,0 +1,109175 @@ +/* +This file is part of Sencha Touch 2.1 + +Copyright (c) 2011-2012 Sencha Inc + +Contact: http://www.sencha.com/contact + +GNU General Public License Usage +This file may be used under the terms of the GNU General Public License version 3.0 as +published by the Free Software Foundation and appearing in the file LICENSE included in the +packaging of this file. + +Please review the following information to ensure the GNU General Public License version 3.0 +requirements will be met: http://www.gnu.org/copyleft/gpl.html. + +If you are unsure which license is appropriate for your use, please contact the sales department +at http://www.sencha.com/contact. + +Build date: 2012-11-05 22:31:29 (08c91901ae8449841ff23e5d3fb404d6128d3b0b) +*/ +//@tag foundation,core +//@define Ext + +/** + * @class Ext + * @singleton + */ +(function() { + var global = this, + objectPrototype = Object.prototype, + toString = objectPrototype.toString, + enumerables = true, + enumerablesTest = { toString: 1 }, + emptyFn = function(){}, + 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. + * @property {String[]} + */ + 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, { + /** + * @property {Function} + * A reusable empty function + */ + emptyFn: emptyFn, + + baseCSSPrefix: Ext.buildSettings.baseCSSPrefix, + + /** + * Copies all the properties of config to object if they don't already exist. + * @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. + */ + 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. + * @method + * @param {Function} superclass + * @param {Object} overrides + * @return {Function} The subclass constructor from the `overrides` parameter, or a generated one if not provided. + * @deprecated 4.0.0 Please 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. + * + * @param {Object} cls The class to override + * @param {Object} overrides The properties to add to `origClass`. This should be specified as an object literal + * containing one or more properties. + * @method override + * @deprecated 4.1.0 Please use {@link Ext#define Ext.define} instead. + */ + override: function(cls, overrides) { + if (cls.$isClass) { + 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 {Object} value The value to test. + * @param {Object} defaultValue The value to return if the original value is empty. + * @param {Boolean} [allowBlank=false] (optional) `true` to allow zero length strings to qualify as non-empty. + * @return {Object} `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 {Object} value + * @return {String} + */ + 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 {Object} value The value to test. + * @param {Boolean} [allowEmptyString=false] (optional) `true` to allow empty strings. + * @return {Boolean} + */ + 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 {Object} target The target to test. + * @return {Boolean} + * @method + */ + 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 {Object} value The value to test. + * @return {Boolean} + * @method + */ + isObject: (toString.call(null) === '[object Object]') ? + function(value) { + // check ownerDocument here as well to exclude DOM nodes + return value !== null && value !== undefined && toString.call(value) === '[object Object]' && value.ownerDocument === undefined; + } : + function(value) { + return toString.call(value) === '[object Object]'; + }, + + /** + * @private + */ + isSimpleObject: function(value) { + return value instanceof Object && value.constructor === Object; + }, + /** + * Returns `true` if the passed value is a JavaScript 'primitive', a string, number or Boolean. + * @param {Object} 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 {Object} value The value to test. + * @return {Boolean} + * @method + */ + isFunction: + // Safari 3.x and 4.x returns 'function' for typeof , 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 {Object} value The value to test. + * @return {Boolean} + */ + isNumber: function(value) { + return typeof value === 'number' && isFinite(value); + }, + + /** + * Validates that a value is numeric. + * @param {Object} 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 {Object} value The value to test. + * @return {Boolean} + */ + isString: function(value) { + return typeof value === 'string'; + }, + + /** + * Returns `true` if the passed value is a Boolean. + * + * @param {Object} value The value to test. + * @return {Boolean} + */ + isBoolean: function(value) { + return typeof value === 'boolean'; + }, + + /** + * Returns `true` if the passed value is an HTMLElement. + * @param {Object} value The value to test. + * @return {Boolean} + */ + isElement: function(value) { + return value ? value.nodeType === 1 : false; + }, + + /** + * Returns `true` if the passed value is a TextNode. + * @param {Object} 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 {Object} 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 {Object} 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 {Object} item The variable to clone. + * @return {Object} clone + */ + clone: function(item) { + if (item === null || item === undefined) { + return item; + } + + // DOM nodes + if (item.nodeType && item.cloneNode) { + return item.cloneNode(true); + } + + // Strings + var type = toString.call(item); + + // Dates + if (type === '[object Date]') { + return new Date(item.getTime()); + } + + var i, j, k, clone, key; + + // Arrays + if (type === '[object Array]') { + i = item.length; + + clone = []; + + while (i--) { + clone[i] = Ext.clone(item[i]); + } + } + // Objects + 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 = 'ExtBox' + (++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), + ln = args.length; + + if (ln > 0) { + args[ln - 1] = 'var Ext=window.' + this.getUniqueGlobalNamespace() + ';' + args[ln - 1]; + } + + return Function.prototype.constructor.apply(Function.prototype, args); + }, + + /** + * @private + */ + globalEval: ('execScript' in global) ? function(code) { + global.execScript(code) + } : function(code) { + (function(){ + eval(code); + })(); + } + + // + /** + * @private + * @property + */ + ,Logger: { + log: function(message, priority) { + if ('console' in global) { + if (!priority || !(priority in global.console)) { + priority = 'log'; + } + message = '[' + priority.toUpperCase() + '] ' + message; + global.console[priority](message); + } + }, + verbose: function(message) { + this.log(message, 'verbose'); + }, + info: function(message) { + this.log(message, 'info'); + }, + warn: function(message) { + this.log(message, 'warn'); + }, + error: function(message) { + throw new Error(message); + }, + deprecate: function(message) { + this.log(message, 'warn'); + } + } + // + }); + + /** + * Old alias to {@link Ext#typeOf}. + * @deprecated 4.0.0 Please use {@link Ext#typeOf} instead. + * @method + * @alias Ext#typeOf + */ + Ext.type = Ext.typeOf; + +})(); + +//@tag foundation,core +//@define Ext.Version +//@require Ext + +/** + * @author Jacky Nguyen + * @docauthor Jacky Nguyen + * @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 + */ +(function() { + +// Current core version +var version = '4.1.0', Version; + Ext.Version = Version = Ext.extend(Object, { + + /** + * Creates new Version object. + * @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 + */ + constructor: function(version) { + var toNumber = this.toNumber, + 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 = toNumber(parts.shift()); + this.minor = toNumber(parts.shift()); + this.patch = toNumber(parts.shift()); + this.build = toNumber(parts.shift()); + + return this; + }, + + /** + * @param value + * @return {Number} + */ + toNumber: function(value) { + value = parseInt(value || 0, 10); + + if (isNaN(value)) { + value = 0; + } + + return value; + }, + + /** + * 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 greater than or equal to the supplied argument. + * @param {String/Number} target The version to compare with. + * @return {Boolean} `true` if this version if greater than or equal to the target, `false` otherwise. + */ + isGreaterThanOrEqual: function(target) { + return Version.compare(this.version, target) >= 0; + }, + + /** + * 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 if less than or equal to the supplied argument. + * @param {String/Number} target The version to compare with. + * @return {Boolean} `true` if this version if less than or equal to the target, `false` otherwise. + */ + isLessThanOrEqual: function(target) { + return Version.compare(this.version, target) <= 0; + }, + + /** + * 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: + * + * 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 + * + * @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 {Number[]} + */ + 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; + }, + + /** + * Convenient alias to {@link Ext.Version#isGreaterThan isGreaterThan} + * @param {String/Number} target + * @return {Boolean} + */ + gt: function() { + return this.isGreaterThan.apply(this, arguments); + }, + + /** + * Convenient alias to {@link Ext.Version#isLessThan isLessThan} + * @param {String/Number} target + * @return {Boolean} + */ + lt: function() { + return this.isLessThan.apply(this, arguments); + }, + + /** + * Convenient alias to {@link Ext.Version#isGreaterThanOrEqual isGreaterThanOrEqual} + * @param {String/Number} target + * @return {Boolean} + */ + gtEq: function() { + return this.isGreaterThanOrEqual.apply(this, arguments); + }, + + /** + * Convenient alias to {@link Ext.Version#isLessThanOrEqual isLessThanOrEqual} + * @param {String/Number} target + * @return {Boolean} + */ + ltEq: function() { + return this.isLessThanOrEqual.apply(this, arguments); + } + }); + + 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 {Object} value The value to convert + * @return {Object} + */ + 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 (`this`) if the closure + */ + deprecate: function(packageName, since, closure, scope) { + if (Version.compare(Ext.getVersion(packageName), since) < 1) { + closure.call(scope); + } + } + }); // End Versioning + + Ext.setVersion('core', version); + +})(); + +//@tag foundation,core +//@define Ext.String +//@require Ext.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. + * @method + */ + 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. + * @method + */ + 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. + * + * // alternate sort directions + * sort = Ext.String.toggle(sort, 'ASC', 'DESC'); + * + * // instead of conditional logic: + * sort = (sort == 'ASC' ? 'DESC' : 'ASC'); + * + * @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: + * + * var s = Ext.String.leftPad('123', 5, '0'); + * alert(s); // '00123' + * + * @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: + * + * var cls = 'my-class', + * text = 'Some text'; + * var s = Ext.String.format('
{1}
', cls, text); + * alert(s); // '
Some text
' + * + * @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]; + }); + }, + + /** + * Returns a string with a specified number of repetitions a given string pattern. + * The pattern be separated by a different string. + * + * var s = Ext.String.repeat('---', 4); // '------------' + * var t = Ext.String.repeat('--', 3, '/'); // '--/--/--' + * + * @param {String} pattern The pattern to repeat. + * @param {Number} count The number of times to repeat the pattern (may be 0). + * @param {String} sep An option string to separate each pattern. + */ + repeat: function(pattern, count, sep) { + for (var buf = [], i = count; i--; ) { + buf.push(pattern); + } + return buf.join(sep || ''); + } +}; + +/** + * Old alias to {@link Ext.String#htmlEncode}. + * @deprecated Use {@link Ext.String#htmlEncode} instead. + * @method + * @member Ext + * @alias Ext.String#htmlEncode + */ +Ext.htmlEncode = Ext.String.htmlEncode; + + +/** + * Old alias to {@link Ext.String#htmlDecode}. + * @deprecated Use {@link Ext.String#htmlDecode} instead. + * @method + * @member Ext + * @alias Ext.String#htmlDecode + */ +Ext.htmlDecode = Ext.String.htmlDecode; + +/** + * Old alias to {@link Ext.String#urlAppend}. + * @deprecated Use {@link Ext.String#urlAppend} instead. + * @method + * @member Ext + * @alias Ext.String#urlAppend + */ +Ext.urlAppend = Ext.String.urlAppend; + +//@tag foundation,core +//@define Ext.Array +//@require Ext.String + +/** + * @class Ext.Array + * @singleton + * @author Jacky Nguyen + * @docauthor Jacky Nguyen + * + * A set of useful static methods to deal with arrays; provide missing methods for older browsers. + */ +(function() { + + var arrayPrototype = Array.prototype, + slice = arrayPrototype.slice, + supportsSplice = function () { + var array = [], + lengthBefore, + j = 20; + + if (!array.splice) { + return false; + } + + // This detects a bug in IE8 splice method: + // see http://social.msdn.microsoft.com/Forums/en-US/iewebdevelopment/thread/6e946d03-e09f-4b22-a4dd-cd5e276bf05a/ + + while (j--) { + array.push("A"); + } + + array.splice(15, 0, "F", "F", "F", "F", "F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F","F"); + + lengthBefore = array.length; //41 + array.splice(13, 0, "XXX"); // add one element + + if (lengthBefore+1 != array.length) { + return false; + } + // end IE8 bug + + return true; + }(), + 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; + } + + function fixArrayIndex (array, index) { + return (index < 0) ? Math.max(0, array.length + index) + : Math.min(array.length, index); + } + + /* + Does the same work as splice, but with a slightly more convenient signature. The splice + method has bugs in IE8, so this is the implementation we use on that platform. + + The rippling of items in the array can be tricky. Consider two use cases: + + index=2 + removeCount=2 + /=====\ + +---+---+---+---+---+---+---+---+ + | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | + +---+---+---+---+---+---+---+---+ + / \/ \/ \/ \ + / /\ /\ /\ \ + / / \/ \/ \ +--------------------------+ + / / /\ /\ +--------------------------+ \ + / / / \/ +--------------------------+ \ \ + / / / /+--------------------------+ \ \ \ + / / / / \ \ \ \ + v v v v v v v v + +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+ + | 0 | 1 | 4 | 5 | 6 | 7 | | 0 | 1 | a | b | c | 4 | 5 | 6 | 7 | + +---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+---+ + A B \=========/ + insert=[a,b,c] + + In case A, it is obvious that copying of [4,5,6,7] must be left-to-right so + that we don't end up with [0,1,6,7,6,7]. In case B, we have the opposite; we + must go right-to-left or else we would end up with [0,1,a,b,c,4,4,4,4]. + */ + function replaceSim (array, index, removeCount, insert) { + var add = insert ? insert.length : 0, + length = array.length, + pos = fixArrayIndex(array, index); + + // we try to use Array.push when we can for efficiency... + if (pos === length) { + if (add) { + array.push.apply(array, insert); + } + } else { + var remove = Math.min(removeCount, length - pos), + tailOldPos = pos + remove, + tailNewPos = tailOldPos + add - remove, + tailCount = length - tailOldPos, + lengthAfterRemove = length - remove, + i; + + if (tailNewPos < tailOldPos) { // case A + for (i = 0; i < tailCount; ++i) { + array[tailNewPos+i] = array[tailOldPos+i]; + } + } else if (tailNewPos > tailOldPos) { // case B + for (i = tailCount; i--; ) { + array[tailNewPos+i] = array[tailOldPos+i]; + } + } // else, add == remove (nothing to do) + + if (add && pos === lengthAfterRemove) { + array.length = lengthAfterRemove; // truncate array + array.push.apply(array, insert); + } else { + array.length = lengthAfterRemove + add; // reserves space + for (i = 0; i < add; ++i) { + array[pos+i] = insert[i]; + } + } + } + + return array; + } + + function replaceNative (array, index, removeCount, insert) { + if (insert && insert.length) { + if (index < array.length) { + array.splice.apply(array, [index, removeCount].concat(insert)); + } else { + array.push.apply(array, insert); + } + } else { + array.splice(index, removeCount); + } + return array; + } + + function eraseSim (array, index, removeCount) { + return replaceSim(array, index, removeCount); + } + + function eraseNative (array, index, removeCount) { + array.splice(index, removeCount); + return array; + } + + function spliceSim (array, index, removeCount) { + var pos = fixArrayIndex(array, index), + removed = array.slice(index, fixArrayIndex(array, pos+removeCount)); + + if (arguments.length < 4) { + replaceSim(array, pos, removeCount); + } else { + replaceSim(array, pos, removeCount, slice.call(arguments, 3)); + } + + return removed; + } + + function spliceNative (array) { + return array.splice.apply(array, slice.call(arguments, 1)); + } + + var erase = supportsSplice ? eraseNative : eraseSim, + replace = supportsSplice ? replaceNative : replaceSim, + splice = supportsSplice ? spliceNative : spliceSim; + + // NOTE: from here on, use erase, replace or splice (not native methods)... + 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 + * } + * }); + * + * {@link Ext#each Ext.each} is alias for {@link Ext.Array#each Ext.Array.each} + * + * @param {Array/NodeList/Object} 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`. + * @param {Object} fn.item The item at the current `index` in the passed `array` + * @param {Number} fn.index The current `index` within the `array` + * @param {Array} fn.allItems The `array` itself which was passed as the first argument + * @param {Boolean} fn.return Return false to stop iteration. + * @param {Object} scope (Optional) The scope (`this` reference) in which the specified function is executed. + * @param {Boolean} [reverse=false] (Optional) Reverse the iteration order (loop from the end to the beginning). + * @return {Boolean} See description for the `fn` parameter. + */ + 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 callback function. + * @param {Object} fn.item The item at the current `index` in the passed `array`. + * @param {Number} fn.index The current `index` within the `array`. + * @param {Array} fn.allItems 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. + */ + forEach: supportsForEach ? function(array, fn, scope) { + return array.forEach(fn, scope); + } : function(array, 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 {Object} 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). + */ + indexOf: (supportsIndexOf) ? function(array, item, from) { + return array.indexOf(item, from); + } : function(array, 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 {Object} item The item to look for. + * @return {Boolean} `true` if the array contains the item, `false` otherwise. + */ + contains: supportsIndexOf ? function(array, item) { + return array.indexOf(item) !== -1; + } : function(array, item) { + 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'] + * + * {@link Ext#toArray Ext.toArray} is alias for {@link Ext.Array#toArray Ext.Array.toArray} + * + * @param {Object} iterable the iterable object to be turned into a true Array. + * @param {Number} [start=0] (Optional) a zero-based index that specifies the start of extraction. + * @param {Number} [end=-1] (Optional) a zero-based index that specifies the end of extraction. + * @return {Array} + */ + 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: supportsMap ? function(array, fn, scope) { + return array.map(fn, scope); + } : function(array, 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 {@link 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 {Object} value The value to convert to an array if it's not already is an array. + * @param {Boolean} [newReference=false] (Optional) `true` to clone the given array and return a new reference if necessary. + * @return {Array} array + */ + 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 ExtArray.toArray(value); + } + + return [value]; + }, + + /** + * Removes the specified item from the array if it exists. + * + * @param {Array} array The array. + * @param {Object} item The item to remove. + * @return {Array} The passed array itself. + */ + remove: function(array, item) { + var index = ExtArray.indexOf(array, item); + + if (index !== -1) { + erase(array, 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 {Object} item The item to include. + */ + 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. + * + * {@link Ext.Array#union} is alias for {@link Ext.Array#merge} + * + * @param {Array} array1 + * @param {Array} array2 + * @param {Array} etc + * @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} array1 + * @param {Array} array2 + * @param {Array} etc + * @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 = ExtArray.unique(minArray); + erase(arrays, 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} arrayA + * @param {Array} arrayB + * @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]) { + erase(clone, j, 1); + j--; + ln--; + } + } + } + + return clone; + }, + + /** + * Returns a shallow copy of a part of an array. This is equivalent to the native + * call `Array.prototype.slice.call(array, begin, end)`. This is often used when "array" + * is "arguments" since the arguments object does not supply a slice method but can + * be the context object to `Array.prototype.slice()`. + * + * @param {Array} array The array (or arguments object). + * @param {Number} begin The index at which to begin. Negative values are offsets from + * the end of the array. + * @param {Number} end The index at which to end. The copied items do not include + * end. Negative values are offsets from the end of the array. If end is omitted, + * all items up to the end of the array are copied. + * @return {Array} The copied piece of the array. + */ + slice: function(array, begin, end) { + return slice.call(array, begin, end); + }, + + /** + * 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 1-d 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 comparison which determines minimization. + * If omitted the "<" operator will be used. + * __Note:__ gt = 1; eq = 0; lt = -1 + * @return {Object} 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 comparison which determines maximization. + * If omitted the ">" operator will be used. + * __Note:__ gt = 1; eq = 0; lt = -1 + * @return {Object} 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; + }, + + // + _replaceSim: replaceSim, // for unit testing + _spliceSim: spliceSim, + // + + /** + * Removes items from an array. This is functionally equivalent to the splice method + * of Array, but works around bugs in IE8's splice method and does not copy the + * removed elements in order to return them (because very often they are ignored). + * + * @param {Array} array The Array on which to replace. + * @param {Number} index The index in the array at which to operate. + * @param {Number} removeCount The number of items to remove at index. + * @return {Array} The array passed. + * @method + */ + erase: erase, + + /** + * Inserts items in to an array. + * + * @param {Array} array The Array on which to replace. + * @param {Number} index The index in the array at which to operate. + * @param {Array} items The array of items to insert at index. + * @return {Array} The array passed. + */ + insert: function (array, index, items) { + return replace(array, index, 0, items); + }, + + /** + * Replaces items in an array. This is functionally equivalent to the splice method + * of Array, but works around bugs in IE8's splice method and is often more convenient + * to call because it accepts an array of items to insert rather than use a variadic + * argument list. + * + * @param {Array} array The Array on which to replace. + * @param {Number} index The index in the array at which to operate. + * @param {Number} removeCount The number of items to remove at index (can be 0). + * @param {Array} insert (optional) An array of items to insert at index. + * @return {Array} The array passed. + * @method + */ + replace: replace, + + /** + * Replaces items in an array. This is equivalent to the splice method of Array, but + * works around bugs in IE8's splice method. The signature is exactly the same as the + * splice method except that the array is the first argument. All arguments following + * removeCount are inserted in the array at index. + * + * @param {Array} array The Array on which to replace. + * @param {Number} index The index in the array at which to operate. + * @param {Number} removeCount The number of items to remove at index (can be 0). + * @return {Array} An array containing the removed items. + * @method + */ + splice: splice + }; + + /** + * @method + * @member Ext + * @alias Ext.Array#each + */ + Ext.each = ExtArray.each; + + /** + * @method + * @member Ext.Array + * @alias Ext.Array#merge + */ + ExtArray.union = ExtArray.merge; + + /** + * Old alias to {@link Ext.Array#min} + * @deprecated 4.0.0 Please use {@link Ext.Array#min} instead + * @method + * @member Ext + * @alias Ext.Array#min + */ + Ext.min = ExtArray.min; + + /** + * Old alias to {@link Ext.Array#max} + * @deprecated 4.0.0 Please use {@link Ext.Array#max} instead + * @method + * @member Ext + * @alias Ext.Array#max + */ + Ext.max = ExtArray.max; + + /** + * Old alias to {@link Ext.Array#sum} + * @deprecated 4.0.0 Please use {@link Ext.Array#sum} instead + * @method + * @member Ext + * @alias Ext.Array#sum + */ + Ext.sum = ExtArray.sum; + + /** + * Old alias to {@link Ext.Array#mean} + * @deprecated 4.0.0 Please use {@link Ext.Array#mean} instead + * @method + * @member Ext + * @alias Ext.Array#mean + */ + Ext.mean = ExtArray.mean; + + /** + * Old alias to {@link Ext.Array#flatten} + * @deprecated 4.0.0 Please use {@link Ext.Array#flatten} instead + * @method + * @member Ext + * @alias Ext.Array#flatten + */ + Ext.flatten = ExtArray.flatten; + + /** + * Old alias to {@link Ext.Array#clean} + * @deprecated 4.0.0 Please use {@link Ext.Array#clean} instead + * @method + * @member Ext + * @alias Ext.Array#clean + */ + Ext.clean = ExtArray.clean; + + /** + * Old alias to {@link Ext.Array#unique} + * @deprecated 4.0.0 Please use {@link Ext.Array#unique} instead + * @method + * @member Ext + * @alias Ext.Array#unique + */ + Ext.unique = ExtArray.unique; + + /** + * Old alias to {@link Ext.Array#pluck Ext.Array.pluck} + * @deprecated 4.0.0 Please use {@link Ext.Array#pluck Ext.Array.pluck} instead + * @method + * @member Ext + * @alias Ext.Array#pluck + */ + Ext.pluck = ExtArray.pluck; + + /** + * @method + * @member Ext + * @alias Ext.Array#toArray + */ + Ext.toArray = function() { + return ExtArray.toArray.apply(ExtArray, arguments); + }; +})(); + +//@tag foundation,core +//@define Ext.Number +//@require Ext.Array + +/** + * @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 passed 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; + }, + + /** + * Snaps the passed number between stopping points based upon a passed increment value. + * @param {Number} value The unsnapped value. + * @param {Number} increment The increment by which the value must move. + * @param {Number} minValue The minimum value to which the returned value must be constrained. Overrides the increment.. + * @param {Number} maxValue The maximum value to which the returned value must be constrained. Overrides the increment.. + * @return {Number} The value of the nearest snap target. + */ + snap : function(value, increment, minValue, maxValue) { + var newValue = value, + m; + + if (!(increment && value)) { + return value; + } + m = value % increment; + if (m !== 0) { + newValue -= m; + if (m * 2 >= increment) { + newValue += increment; + } else if (m * 2 < -increment) { + newValue -= increment; + } + } + return Ext.Number.constrain(newValue, minValue, maxValue); + }, + + /** + * 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 {Object} 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); +}; + +//@tag foundation,core +//@define Ext.Object +//@require Ext.Number + +/** + * @author Jacky Nguyen + * @docauthor Jacky Nguyen + * @class Ext.Object + * + * A collection of useful static methods to deal with objects. + * + * @singleton + */ + +(function() { + +// The "constructor" for chain: +var TemplateClass = function(){}; + +var ExtObject = Ext.Object = { + + /** + * Returns a new object with the given object as the prototype chain. + * @param {Object} object The prototype chain for the new object. + */ + chain: ('create' in Object) ? function(object){ + return Object.create(object); + } : function (object) { + TemplateClass.prototype = object; + var result = new TemplateClass(); + TemplateClass.prototype = null; + return result; + }, + + /** + * Convert a `name` - `value` pair to an array of objects with support for nested structures; useful to construct + * query strings. For example: + * + * Non-recursive: + * + * var objects = Ext.Object.toQueryObjects('hobbies', ['reading', 'cooking', 'swimming']); + * + * // objects then equals: + * [ + * { name: 'hobbies', value: 'reading' }, + * { name: 'hobbies', value: 'cooking' }, + * { name: 'hobbies', value: 'swimming' } + * ] + * + * Recursive: + * + * var objects = Ext.Object.toQueryObjects('dateOfBirth', { + * day: 3, + * month: 8, + * year: 1987, + * extra: { + * hour: 4, + * minute: 30 + * } + * }, true); + * + * // 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 {Object} value + * @param {Boolean} [recursive=false] `true` to recursively encode any sub-objects. + * @return {Object[]} Array of objects with `name` and `value` fields. + */ + 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=false] Whether or not to interpret the object in recursive format. + * (PHP / Ruby on Rails servers and similar). + * @return {String} queryString + */ + 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=false] Whether or not to recursively decode the string. This format is supported by + * PHP / Ruby on Rails servers and similar. + * @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) { + throw new Error('[Ext.Object.fromQueryString] 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. + * @param {String} fn.key + * @param {Mixed} fn.value + * @param {Object} fn.object The object itself + * @param {Object} [scope] The execution scope (`this`) of the callback function + */ + 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); + * + * // sencha then equals to + * { + * companyName: 'Sencha Inc.', + * products: ['Ext JS', 'Ext GWT', 'Ext Designer', 'Sencha Touch', 'Sencha Animator'], + * isSuperCool: true + * office: { + * size: 40000, + * location: 'Redwood City' + * isFun: true + * } + * } + * + * @param {Object} source The first object into which to merge the others. + * @param {Object...} objs One or more objects to be merged into the first. + * @return {Object} The object that is created as a result of merging all the objects passed in. + */ + merge: function(source) { + var i = 1, + ln = arguments.length, + mergeFn = ExtObject.merge, + cloneFn = Ext.clone, + object, key, value, sourceKey; + + for (; i < ln; i++) { + object = arguments[i]; + + for (key in object) { + value = object[key]; + if (value && value.constructor === Object) { + sourceKey = source[key]; + if (sourceKey && sourceKey.constructor === Object) { + mergeFn(sourceKey, value); + } + else { + source[key] = cloneFn(value); + } + } + else { + source[key] = value; + } + } + } + + return source; + }, + + /** + * @private + * @param source + */ + mergeIf: function(source) { + var i = 1, + ln = arguments.length, + cloneFn = Ext.clone, + object, key, value; + + for (; i < ln; i++) { + object = arguments[i]; + + for (key in object) { + if (!(key in source)) { + value = object[key]; + + if (value && value.constructor === Object) { + source[key] = cloneFn(value); + } + else { + source[key] = value; + } + } + } + } + + 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, 'food')); // alerts 'loves' + * + * @param {Object} object + * @param {Object} value The value to find + */ + 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. + */ + 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 {String[]} An array of keys from the object. + * @method + */ + getKeys: ('keys' in Object) ? 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 + */ + getSize: function(object) { + var size = 0, + property; + + for (property in object) { + if (object.hasOwnProperty(property)) { + size++; + } + } + + return size; + }, + + /** + * @private + */ + classify: function(object) { + var objectProperties = [], + arrayProperties = [], + propertyClassesMap = {}, + objectClass = function() { + var i = 0, + ln = objectProperties.length, + property; + + for (; i < ln; i++) { + property = objectProperties[i]; + this[property] = new propertyClassesMap[property]; + } + + ln = arrayProperties.length; + + for (i = 0; i < ln; i++) { + property = arrayProperties[i]; + this[property] = object[property].slice(); + } + }, + key, value, constructor; + + for (key in object) { + if (object.hasOwnProperty(key)) { + value = object[key]; + + if (value) { + constructor = value.constructor; + + if (constructor === Object) { + objectProperties.push(key); + propertyClassesMap[key] = ExtObject.classify(value); + } + else if (constructor === Array) { + arrayProperties.push(key); + } + } + } + } + + objectClass.prototype = object; + + return objectClass; + }, + + defineProperty: ('defineProperty' in Object) ? Object.defineProperty : function(object, name, descriptor) { + if (descriptor.get) { + object.__defineGetter__(name, descriptor.get); + } + + if (descriptor.set) { + object.__defineSetter__(name, descriptor.set); + } + } +}; + +/** + * A convenient alias method for {@link Ext.Object#merge}. + * + * @member Ext + * @method merge + */ +Ext.merge = Ext.Object.merge; + +/** + * @private + */ +Ext.mergeIf = Ext.Object.mergeIf; + +/** + * A convenient alias method for {@link Ext.Object#toQueryString}. + * + * @member Ext + * @method urlEncode + * @deprecated 4.0.0 Please 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 + ExtObject.toQueryString.apply(ExtObject, args); +}; + +/** + * A convenient alias method for {@link Ext.Object#fromQueryString}. + * + * @member Ext + * @method urlDecode + * @deprecated 4.0.0 Please use {@link Ext.Object#fromQueryString Ext.Object.fromQueryString} instead + */ +Ext.urlDecode = function() { + return ExtObject.fromQueryString.apply(ExtObject, arguments); +}; + +})(); + +//@tag foundation,core +//@define Ext.Function +//@require Ext.Object + +/** + * @class Ext.Function + * + * A collection of useful static methods to deal with function callbacks. + * @singleton + * @alternateClassName Ext.util.Functions + */ +Ext.Function = { + + /** + * A very commonly used method throughout the framework. It acts as a wrapper around another method + * which originally accepts 2 arguments for `name` and `value`. + * The wrapped function then allows "flexible" value setting of either: + * + * - `name` and `value` as 2 arguments + * - one single object argument with multiple key - value pairs + * + * For example: + * + * 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' + * }); + * + * @param {Function} setter + * @return {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 `fn`, change `this` to the provided scope, optionally + * overrides arguments for the call. Defaults to the arguments passed by the caller. + * + * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind} + * + * @param {Function} fn The function to delegate. + * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. + * **If omitted, defaults to the browser window.** + * @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) { + if (arguments.length === 2) { + return function() { + return fn.apply(scope, arguments); + } + } + + var method = fn, + slice = Array.prototype.slice; + + return function() { + var callArgs = args || arguments; + + if (appendArgs === true) { + callArgs = slice.call(arguments, 0); + callArgs = callArgs.concat(args); + } + else if (typeof appendArgs == 'number') { + callArgs = slice.call(arguments, 0); // copy arguments first + Ext.Array.insert(callArgs, appendArgs, args); + } + + return method.apply(scope || window, callArgs); + }; + }, + + /** + * Create a new function from the provided `fn`, 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' + * + * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass} + * + * @param {Function} fn The original function. + * @param {Array} args The arguments to pass to new callback. + * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed. + * @return {Function} The new callback function. + */ + pass: function(fn, args, scope) { + if (!Ext.isArray(args)) { + args = Ext.Array.clone(args); + } + + return function() { + args.push.apply(args, arguments); + return fn.apply(scope || this, args); + }; + }, + + /** + * Create an alias to the provided method property with name `methodName` of `object`. + * Note that the execution scope will still be bound to the provided `object` itself. + * + * @param {Object/Function} object + * @param {String} methodName + * @return {Function} aliasFn + */ + alias: function(object, methodName) { + return function() { + return object[methodName].apply(object, arguments); + }; + }, + + /** + * Create a "clone" of the provided method. The returned method will call the given + * method passing along all arguments and the "this" pointer and return its result. + * + * @param {Function} method + * @return {Function} cloneFn + */ + clone: function(method) { + return function() { + return method.apply(this, 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: + * + * 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" + * + * @param {Function} origFn The original function. + * @param {Function} newFn The function to call before the original. + * @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.** + * @param {Object} [returnValue=null] (optional) The value to return if the passed function return `false`. + * @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 (`this` 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, + args = Array.prototype.slice.call(arguments); + + setTimeout(function() { + fn.apply(me, args); + }, delay); + } + }, + + /** + * Calls this function after the number of milliseconds specified, optionally in a specific scope. Example usage: + * + * 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); + * + * {@link Ext#defer Ext.defer} is alias for {@link Ext.Function#defer Ext.Function.defer} + * + * @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 (`this` reference) in which the function is executed. + * If omitted, defaults to the browser window. + * @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, scope, args, appendArgs) { + fn = Ext.Function.bind(fn, scope, 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: + * + * 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 + * + * @param {Function} originalFn 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(originalFn, newFn, scope) { + if (!newFn) { + return originalFn; + } + else { + return function() { + var result = originalFn.apply(this, arguments); + newFn.apply(scope || this, arguments); + return result; + }; + } + }, + + /** + * 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. + * + * @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 (`this` 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) { + var timerId; + + return function() { + var callArgs = args || Array.prototype.slice.call(arguments, 0), + me = scope || this; + + if (timerId) { + clearTimeout(timerId); + } + + timerId = setTimeout(function(){ + fn.apply(me, callArgs); + }, buffer); + }; + }, + + /** + * 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. + * + * 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. + * + * @param {Function} fn The function to execute at a regular time interval. + * @param {Number} interval The interval, in milliseconds, on which the passed function is executed. + * @param {Object} scope (optional) The scope (`this` reference) in which + * the passed function is executed. If omitted, defaults to the scope specified by the caller. + * @return {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); + } + }; + }, + + interceptBefore: function(object, methodName, fn) { + var method = object[methodName] || Ext.emptyFn; + + return object[methodName] = function() { + var ret = fn.apply(this, arguments); + method.apply(this, arguments); + + return ret; + }; + }, + + interceptAfter: function(object, methodName, fn) { + var method = object[methodName] || Ext.emptyFn; + + return object[methodName] = function() { + method.apply(this, arguments); + return fn.apply(this, arguments); + }; + } +}; + +/** + * @method + * @member Ext + * @alias Ext.Function#defer + */ +Ext.defer = Ext.Function.alias(Ext.Function, 'defer'); + +/** + * @method + * @member Ext + * @alias Ext.Function#pass + */ +Ext.pass = Ext.Function.alias(Ext.Function, 'pass'); + +/** + * @method + * @member Ext + * @alias Ext.Function#bind + */ +Ext.bind = Ext.Function.alias(Ext.Function, 'bind'); + +//@tag foundation,core +//@define Ext.JSON +//@require Ext.Function + +/** + * @class Ext.JSON + * Modified version of Douglas Crockford's json.js that doesn't + * mess with the Object prototype. + * [http://www.json.org/js.html](http://www.json.org/js.html) + * @singleton + */ +Ext.JSON = new(function() { + var useHasOwn = !! {}.hasOwnProperty, + isNative = function() { + var useNative = null; + + return function() { + if (useNative === null) { + useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]'; + } + + return useNative; + }; + }(), + pad = function(n) { + return n < 10 ? "0" + n : n; + }, + doDecode = function(json) { + return eval("(" + json + ')'); + }, + doEncode = function(o) { + if (!Ext.isDefined(o) || o === null) { + return "null"; + } else if (Ext.isArray(o)) { + return encodeArray(o); + } else if (Ext.isDate(o)) { + return Ext.JSON.encodeDate(o); + } else if (Ext.isString(o)) { + return encodeString(o); + } else if (typeof o == "number") { + //don't use isNumber here, since finite checks happen inside isNumber + return isFinite(o) ? String(o) : "null"; + } else if (Ext.isBoolean(o)) { + return String(o); + } else if (Ext.isObject(o)) { + return encodeObject(o); + } else if (typeof o === "function") { + return "null"; + } + return 'undefined'; + }, + m = { + "\b": '\\b', + "\t": '\\t', + "\n": '\\n', + "\f": '\\f', + "\r": '\\r', + '"': '\\"', + "\\": '\\\\', + '\x0b': '\\u000b' //ie doesn't handle \v + }, + charToReplace = /[\\\"\x00-\x1f\x7f-\uffff]/g, + encodeString = function(s) { + return '"' + s.replace(charToReplace, function(a) { + var c = m[a]; + return typeof c === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"'; + }, + encodeArray = function(o) { + var a = ["[", ""], + // Note empty string in case there are no serializable members. + len = o.length, + i; + for (i = 0; i < len; i += 1) { + a.push(doEncode(o[i]), ','); + } + // Overwrite trailing comma (or empty string) + a[a.length - 1] = ']'; + return a.join(""); + }, + encodeObject = function(o) { + var a = ["{", ""], + // Note empty string in case there are no serializable members. + i; + for (i in o) { + if (!useHasOwn || o.hasOwnProperty(i)) { + a.push(doEncode(i), ":", doEncode(o[i]), ','); + } + } + // Overwrite trailing comma (or empty string) + a[a.length - 1] = '}'; + return a.join(""); + }; + + /** + * Encodes a Date. This returns the actual string which is inserted into the JSON string as the literal expression. + * __The returned value includes enclosing double quotation marks.__ + * + * The default return format is "yyyy-mm-ddThh:mm:ss". + * + * To override this: + * + * Ext.JSON.encodeDate = function(d) { + * return Ext.Date.format(d, '"Y-m-d"'); + * }; + * + * @param {Date} d The Date to encode. + * @return {String} The string literal to use in a JSON string. + */ + this.encodeDate = function(o) { + return '"' + o.getFullYear() + "-" + + pad(o.getMonth() + 1) + "-" + + pad(o.getDate()) + "T" + + pad(o.getHours()) + ":" + + pad(o.getMinutes()) + ":" + + pad(o.getSeconds()) + '"'; + }; + + /** + * Encodes an Object, Array or other value. + * @param {Object} o The variable to encode. + * @return {String} The JSON string. + * @method + */ + this.encode = function() { + var ec; + return function(o) { + if (!ec) { + // setup encoding function on first access + ec = isNative() ? JSON.stringify : doEncode; + } + return ec(o); + }; + }(); + + + /** + * Decodes (parses) a JSON string to an object. If the JSON is invalid, this function throws a Error unless the safe option is set. + * @param {String} json The JSON string. + * @param {Boolean} safe (optional) Whether to return `null` or throw an exception if the JSON is invalid. + * @return {Object/null} The resulting object. + * @method + */ + this.decode = function() { + var dc; + return function(json, safe) { + if (!dc) { + // setup decoding function on first access + dc = isNative() ? JSON.parse : doDecode; + } + try { + return dc(json); + } catch (e) { + if (safe === true) { + return null; + } + Ext.Error.raise({ + sourceClass: "Ext.JSON", + sourceMethod: "decode", + msg: "You're trying to decode an invalid JSON String: " + json + }); + } + }; + }(); + +})(); +/** + * Shorthand for {@link Ext.JSON#encode}. + * @member Ext + * @method encode + * @alias Ext.JSON#encode + */ +Ext.encode = Ext.JSON.encode; +/** + * Shorthand for {@link Ext.JSON#decode}. + * @member Ext + * @method decode + * @alias Ext.JSON#decode + */ +Ext.decode = Ext.JSON.decode; + + +//@tag foundation,core +//@define Ext.Error +//@require Ext.JSON + +Ext.Error = { + raise: function(object) { + throw new Error(object.msg); + } +}; + +//@tag foundation,core +//@define Ext.Date +//@require Ext.Error + +/** + * + */ +Ext.Date = { + /** @ignore */ + now: Date.now, + + /** + * @private + * Private for now + */ + toString: function(date) { + if (!date) { + date = new 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'); + } +}; + + +//@tag foundation,core +//@define Ext.Base +//@require Ext.Date + +/** + * @class Ext.Base + * + * @author Jacky Nguyen + * @aside guide class_system + * @aside video class-system + * + * The root of all classes created with {@link Ext#define}. + * + * Ext.Base is the building block of all Ext classes. All classes in Ext inherit from Ext.Base. All prototype and static + * members of this class are inherited by all other classes. + * + * See the [Class System Guide](#!/guide/class_system) for more. + * + */ +(function(flexSetter) { + +var noArgs = [], + Base = function(){}; + + // These static properties will be copied to every newly created class with {@link Ext#define} + Ext.apply(Base, { + $className: 'Ext.Base', + + $isClass: true, + + /** + * Create a new instance of this Class. + * + * Ext.define('My.cool.Class', { + * // ... + * }); + * + * My.cool.Class.create({ + * someConfig: true + * }); + * + * All parameters are passed to the constructor of the class. + * + * @return {Object} the created instance. + * @static + * @inheritable + */ + create: function() { + return Ext.create.apply(Ext, [this].concat(Array.prototype.slice.call(arguments, 0))); + }, + + /** + * @private + * @static + * @inheritable + */ + extend: function(parent) { + var parentPrototype = parent.prototype, + prototype, i, ln, name, statics; + + prototype = this.prototype = Ext.Object.chain(parentPrototype); + prototype.self = this; + + this.superclass = prototype.superclass = parentPrototype; + + if (!parent.$isClass) { + Ext.apply(prototype, Ext.Base.prototype); + prototype.constructor = function() { + parentPrototype.constructor.apply(this, arguments); + }; + } + + // + // Statics inheritance + statics = parentPrototype.$inheritableStatics; + + if (statics) { + for (i = 0,ln = statics.length; i < ln; i++) { + name = statics[i]; + + if (!this.hasOwnProperty(name)) { + this[name] = parent[name]; + } + } + } + // + + if (parent.$onExtended) { + this.$onExtended = parent.$onExtended.slice(); + } + + // + prototype.config = prototype.defaultConfig = new prototype.configClass; + prototype.initConfigList = prototype.initConfigList.slice(); + prototype.initConfigMap = Ext.Object.chain(prototype.initConfigMap); + // + }, + + /** + * @private + * @static + * @inheritable + */ + '$onExtended': [], + + /** + * @private + * @static + * @inheritable + */ + triggerExtended: function() { + var callbacks = this.$onExtended, + ln = callbacks.length, + i, callback; + + if (ln > 0) { + for (i = 0; i < ln; i++) { + callback = callbacks[i]; + callback.fn.apply(callback.scope || this, arguments); + } + } + }, + + /** + * @private + * @static + * @inheritable + */ + onExtended: function(fn, scope) { + this.$onExtended.push({ + fn: fn, + scope: scope + }); + + return this; + }, + + /** + * @private + * @static + * @inheritable + */ + addConfig: function(config, fullMerge) { + var prototype = this.prototype, + initConfigList = prototype.initConfigList, + initConfigMap = prototype.initConfigMap, + defaultConfig = prototype.defaultConfig, + hasInitConfigItem, name, value; + + fullMerge = Boolean(fullMerge); + + for (name in config) { + if (config.hasOwnProperty(name) && (fullMerge || !(name in defaultConfig))) { + value = config[name]; + hasInitConfigItem = initConfigMap[name]; + + if (value !== null) { + if (!hasInitConfigItem) { + initConfigMap[name] = true; + initConfigList.push(name); + } + } + else if (hasInitConfigItem) { + initConfigMap[name] = false; + Ext.Array.remove(initConfigList, name); + } + } + } + + if (fullMerge) { + Ext.merge(defaultConfig, config); + } + else { + Ext.mergeIf(defaultConfig, config); + } + + prototype.configClass = Ext.Object.classify(defaultConfig); + }, + + /** + * Add / override static properties of this class. + * + * Ext.define('My.cool.Class', { + * // this.se + * }); + * + * 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() { ... }; + * }); + * + * @param {Object} members + * @return {Ext.Base} this + * @static + * @inheritable + */ + addStatics: function(members) { + var member, name; + // + var className = Ext.getClassName(this); + // + + for (name in members) { + if (members.hasOwnProperty(name)) { + member = members[name]; + // + if (typeof member == 'function') { + member.displayName = className + '.' + name; + } + // + this[name] = member; + } + } + + return this; + }, + + /** + * @private + * @static + * @inheritable + */ + addInheritableStatics: function(members) { + var inheritableStatics, + hasInheritableStatics, + prototype = this.prototype, + name, member; + + inheritableStatics = prototype.$inheritableStatics; + hasInheritableStatics = prototype.$hasInheritableStatics; + + if (!inheritableStatics) { + inheritableStatics = prototype.$inheritableStatics = []; + hasInheritableStatics = prototype.$hasInheritableStatics = {}; + } + + // + var className = Ext.getClassName(this); + // + + for (name in members) { + if (members.hasOwnProperty(name)) { + member = members[name]; + // + if (typeof member == 'function') { + member.displayName = className + '.' + name; + } + // + this[name] = member; + + if (!hasInheritableStatics[name]) { + hasInheritableStatics[name] = true; + inheritableStatics.push(name); + } + } + } + + return this; + }, + + /** + * Add methods / properties to the prototype of this class. + * + * @example + * Ext.define('My.awesome.Cat', { + * constructor: function() { + * // ... + * } + * }); + * + * My.awesome.Cat.addMembers({ + * meow: function() { + * alert('Meowww...'); + * } + * }); + * + * var kitty = new My.awesome.Cat(); + * kitty.meow(); + * + * @param {Object} members + * @static + * @inheritable + */ + addMembers: function(members) { + var prototype = this.prototype, + names = [], + name, member; + + // + var className = this.$className || ''; + // + + for (name in members) { + if (members.hasOwnProperty(name)) { + member = members[name]; + + if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) { + member.$owner = this; + member.$name = name; + // + member.displayName = className + '#' + name; + // + } + + prototype[name] = member; + } + } + + return this; + }, + + /** + * @private + * @static + * @inheritable + */ + addMember: function(name, member) { + if (typeof member == 'function' && !member.$isClass && member !== Ext.emptyFn) { + member.$owner = this; + member.$name = name; + // + member.displayName = (this.$className || '') + '#' + name; + // + } + + this.prototype[name] = member; + + return this; + }, + + /** + * @private + * @static + * @inheritable + */ + implement: function() { + this.addMembers.apply(this, arguments); + }, + + /** + * 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 '$$$$$$$' + * + * @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 + * @static + * @inheritable + * @private + */ + borrow: function(fromClass, members) { + var prototype = this.prototype, + fromPrototype = fromClass.prototype, + // + className = Ext.getClassName(this), + // + i, ln, name, fn, toBorrow; + + members = Ext.Array.from(members); + + for (i = 0,ln = members.length; i < ln; i++) { + name = members[i]; + + toBorrow = fromPrototype[name]; + + if (typeof toBorrow == 'function') { + fn = function() { + return toBorrow.apply(this, arguments); + }; + + // + if (className) { + fn.displayName = className + '#' + name; + } + // + + fn.$owner = this; + fn.$name = name; + + prototype[name] = fn; + } + else { + prototype[name] = toBorrow; + } + } + + return this; + }, + + /** + * Override members of this class. Overridden methods can be invoked via + * {@link Ext.Base#callParent}. + * + * Ext.define('My.Cat', { + * constructor: function() { + * alert("I'm a cat!"); + * } + * }); + * + * My.Cat.override({ + * constructor: function() { + * alert("I'm going to be a cat!"); + * + * var instance = this.callParent(arguments); + * + * alert("Meeeeoooowwww"); + * + * return instance; + * } + * }); + * + * var kitty = new My.Cat(); // alerts "I'm going to be a cat!" + * // alerts "I'm a cat!" + * // alerts "Meeeeoooowwww" + * + * As of 2.1, direct use of this method is deprecated. Use {@link Ext#define Ext.define} + * instead: + * + * Ext.define('My.CatOverride', { + * override: 'My.Cat', + * + * constructor: function() { + * alert("I'm going to be a cat!"); + * + * var instance = this.callParent(arguments); + * + * alert("Meeeeoooowwww"); + * + * return instance; + * } + * }); + * + * The above accomplishes the same result but can be managed by the {@link Ext.Loader} + * which can properly order the override and its target class and the build process + * can determine whether the override is needed based on the required state of the + * target class (My.Cat). + * + * @param {Object} members The properties to add to this class. This should be + * specified as an object literal containing one or more properties. + * @return {Ext.Base} this class + * @static + * @inheritable + * @deprecated 2.1.0 Please use {@link Ext#define Ext.define} instead + */ + override: function(members) { + var me = this, + enumerables = Ext.enumerables, + target = me.prototype, + cloneFunction = Ext.Function.clone, + name, index, member, statics, names, previous; + + if (arguments.length === 2) { + name = members; + members = {}; + members[name] = arguments[1]; + enumerables = null; + } + + do { + names = []; // clean slate for prototype (1st pass) and static (2nd pass) + statics = null; // not needed 1st pass, but needs to be cleared for 2nd pass + + for (name in members) { // hasOwnProperty is checked in the next loop... + if (name == 'statics') { + statics = members[name]; + } + else if (name == 'config') { + me.addConfig(members[name], true); + } + else { + names.push(name); + } + } + + if (enumerables) { + names.push.apply(names, enumerables); + } + + for (index = names.length; index--; ) { + name = names[index]; + + if (members.hasOwnProperty(name)) { + member = members[name]; + + if (typeof member == 'function' && !member.$className && member !== Ext.emptyFn) { + if (typeof member.$owner != 'undefined') { + member = cloneFunction(member); + } + + // + var className = me.$className; + if (className) { + member.displayName = className + '#' + name; + } + // + + member.$owner = me; + member.$name = name; + + previous = target[name]; + if (previous) { + member.$previous = previous; + } + } + + target[name] = member; + } + } + + target = me; // 2nd pass is for statics + members = statics; // statics will be null on 2nd pass + } while (members); + + return this; + }, + + /** + * @protected + * @static + * @inheritable + */ + callParent: function(args) { + var method; + + // This code is intentionally inlined for the least amount of debugger stepping + return (method = this.callParent.caller) && (method.$previous || + ((method = method.$owner ? method : method.caller) && + method.$owner.superclass.$class[method.$name])).apply(this, args || noArgs); + }, + + // + /** + * Used internally by the mixins pre-processor + * @private + * @static + * @inheritable + */ + mixin: function(name, mixinClass) { + var mixin = mixinClass.prototype, + prototype = this.prototype, + key; + + if (typeof mixin.onClassMixedIn != 'undefined') { + mixin.onClassMixedIn.call(mixinClass, this); + } + + if (!prototype.hasOwnProperty('mixins')) { + if ('mixins' in prototype) { + prototype.mixins = Ext.Object.chain(prototype.mixins); + } + else { + prototype.mixins = {}; + } + } + + for (key in mixin) { + if (key === 'mixins') { + Ext.merge(prototype.mixins, mixin[key]); + } + else if (typeof prototype[key] == 'undefined' && key != 'mixinId' && key != 'config') { + prototype[key] = mixin[key]; + } + } + + // + if ('config' in mixin) { + this.addConfig(mixin.config, false); + } + // + + prototype.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 + * @static + * @inheritable + */ + 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() + * + * @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 + * @static + * @inheritable + * @method + */ + createAlias: flexSetter(function(alias, origin) { + this.override(alias, function() { + return this[origin].apply(this, arguments); + }); + }), + + /** + * @private + * @static + * @inheritable + */ + addXtype: function(xtype) { + var prototype = this.prototype, + xtypesMap = prototype.xtypesMap, + xtypes = prototype.xtypes, + xtypesChain = prototype.xtypesChain; + + if (!prototype.hasOwnProperty('xtypesMap')) { + xtypesMap = prototype.xtypesMap = Ext.merge({}, prototype.xtypesMap || {}); + xtypes = prototype.xtypes = prototype.xtypes ? [].concat(prototype.xtypes) : []; + xtypesChain = prototype.xtypesChain = prototype.xtypesChain ? [].concat(prototype.xtypesChain) : []; + prototype.xtype = xtype; + } + + if (!xtypesMap[xtype]) { + xtypesMap[xtype] = true; + xtypes.push(xtype); + xtypesChain.push(xtype); + Ext.ClassManager.setAlias(this, 'widget.' + xtype); + } + + return this; + } + }); + + Base.implement({ + isInstance: true, + + $className: 'Ext.Base', + + configClass: Ext.emptyFn, + + initConfigList: [], + + initConfigMap: {}, + + /** + * 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++; + * }, + * + * 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 {Ext.Class} + */ + statics: function() { + var method = this.statics.caller, + self = this.self; + + if (!method) { + return self; + } + + return method.$owner; + }, + + /** + * Call the "parent" method of the current method. That is the method previously + * overridden by derivation or by an override (see {@link Ext#define}). + * + * Ext.define('My.Base', { + * constructor: function (x) { + * this.x = x; + * }, + * + * statics: { + * method: function (x) { + * return x; + * } + * } + * }); + * + * Ext.define('My.Derived', { + * extend: 'My.Base', + * + * constructor: function () { + * this.callParent([21]); + * } + * }); + * + * var obj = new My.Derived(); + * + * alert(obj.x); // alerts 21 + * + * This can be used with an override as follows: + * + * Ext.define('My.DerivedOverride', { + * override: 'My.Derived', + * + * constructor: function (x) { + * this.callParent([x*2]); // calls original My.Derived constructor + * } + * }); + * + * var obj = new My.Derived(); + * + * alert(obj.x); // now alerts 42 + * + * This also works with static methods. + * + * Ext.define('My.Derived2', { + * extend: 'My.Base', + * + * statics: { + * method: function (x) { + * return this.callParent([x*2]); // calls My.Base.method + * } + * } + * }); + * + * alert(My.Base.method(10)); // alerts 10 + * alert(My.Derived2.method(10)); // alerts 20 + * + * Lastly, it also works with overridden static methods. + * + * Ext.define('My.Derived2Override', { + * override: 'My.Derived2', + * + * statics: { + * method: function (x) { + * return this.callParent([x*2]); // calls My.Derived2.method + * } + * } + * }); + * + * alert(My.Derived2.method(10)); // now alerts 40 + * + * To override a method and replace it and also call the superclass method, use + * {@link #callSuper}. This is often done to patch a method to fix a bug. + * + * @protected + * @param {Array/Arguments} args The arguments, either an array or the `arguments` object + * from the current method, for example: `this.callParent(arguments)` + * @return {Object} Returns the result of calling the parent method + */ + callParent: function(args) { + // NOTE: this code is deliberately as few expressions (and no function calls) + // as possible so that a debugger can skip over this noise with the minimum number + // of steps. Basically, just hit Step Into until you are where you really wanted + // to be. + var method, + superMethod = (method = this.callParent.caller) && (method.$previous || + ((method = method.$owner ? method : method.caller) && + method.$owner.superclass[method.$name])); + + // + if (!superMethod) { + method = this.callParent.caller; + var parentClass, methodName; + + if (!method.$owner) { + if (!method.caller) { + throw new Error("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)) { + throw new Error("this.callParent() was called but there's no such method (" + methodName + + ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"); + } + } + // + + return superMethod.apply(this, args || noArgs); + }, + + /** + * This method is used by an override to call the superclass method but bypass any + * overridden method. This is often done to "patch" a method that contains a bug + * but for whatever reason cannot be fixed directly. + * + * Consider: + * + * Ext.define('Ext.some.Class', { + * method: function () { + * console.log('Good'); + * } + * }); + * + * Ext.define('Ext.some.DerivedClass', { + * method: function () { + * console.log('Bad'); + * + * // ... logic but with a bug ... + * + * this.callParent(); + * } + * }); + * + * To patch the bug in `DerivedClass.method`, the typical solution is to create an + * override: + * + * Ext.define('App.paches.DerivedClass', { + * override: 'Ext.some.DerivedClass', + * + * method: function () { + * console.log('Fixed'); + * + * // ... logic but with bug fixed ... + * + * this.callSuper(); + * } + * }); + * + * The patch method cannot use `callParent` to call the superclass `method` since + * that would call the overridden method containing the bug. In other words, the + * above patch would only produce "Fixed" then "Good" in the console log, whereas, + * using `callParent` would produce "Fixed" then "Bad" then "Good". + * + * @protected + * @param {Array/Arguments} args The arguments, either an array or the `arguments` object + * from the current method, for example: `this.callSuper(arguments)` + * @return {Object} Returns the result of calling the superclass method + */ + callSuper: function(args) { + var method, + superMethod = (method = this.callSuper.caller) && ((method = method.$owner ? method : method.caller) && + method.$owner.superclass[method.$name]); + + // + if (!superMethod) { + method = this.callSuper.caller; + var parentClass, methodName; + + if (!method.$owner) { + if (!method.caller) { + throw new Error("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)) { + throw new Error("this.callSuper() was called but there's no such method (" + methodName + + ") found in the parent class (" + (Ext.getClassName(parentClass) || 'Object') + ")"); + } + } + // + + return superMethod.apply(this, args || noArgs); + }, + + /** + * Call the original method that was previously overridden with {@link Ext.Base#override}, + * + * This method is deprecated as {@link #callParent} does the same thing. + * + * Ext.define('My.Cat', { + * constructor: function() { + * alert("I'm a cat!"); + * } + * }); + * + * 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 + * from the current method, for example: `this.callOverridden(arguments)` + * @return {Object} Returns the result of calling the overridden method + * @protected + * @deprecated Use callParent instead + */ + callOverridden: function(args) { + var method; + + return (method = this.callOverridden.caller) && method.$previous.apply(this, args || noArgs); + }, + + /** + * @property {Ext.Class} self + * + * 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' + * }, + * + * 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' + * + * @protected + */ + self: Base, + + // Default constructor, simply returns `this` + constructor: function() { + return this; + }, + + // + + wasInstantiated: false, + + /** + * 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); + * } + * }); + * + * var awesome = new My.awesome.Class({ + * name: 'Super Awesome' + * }); + * + * alert(awesome.getName()); // 'Super Awesome' + * + * @protected + * @param {Object} instanceConfig + * @return {Object} mixins The mixin prototypes as key - value pairs + */ + initConfig: function(instanceConfig) { + // +// if (instanceConfig && instanceConfig.breakOnInitConfig) { +// debugger; +// } + // + var configNameCache = Ext.Class.configNameCache, + prototype = this.self.prototype, + initConfigList = this.initConfigList, + initConfigMap = this.initConfigMap, + config = new this.configClass, + defaultConfig = this.defaultConfig, + i, ln, name, value, nameMap, getName; + + this.initConfig = Ext.emptyFn; + + this.initialConfig = instanceConfig || {}; + + if (instanceConfig) { + Ext.merge(config, instanceConfig); + } + + this.config = config; + + // Optimize initConfigList *once* per class based on the existence of apply* and update* methods + // Happens only once during the first instantiation + if (!prototype.hasOwnProperty('wasInstantiated')) { + prototype.wasInstantiated = true; + + for (i = 0,ln = initConfigList.length; i < ln; i++) { + name = initConfigList[i]; + nameMap = configNameCache[name]; + value = defaultConfig[name]; + + if (!(nameMap.apply in prototype) + && !(nameMap.update in prototype) + && prototype[nameMap.set].$isDefault + && typeof value != 'object') { + prototype[nameMap.internal] = defaultConfig[name]; + initConfigMap[name] = false; + Ext.Array.remove(initConfigList, name); + i--; + ln--; + } + } + } + + if (instanceConfig) { + initConfigList = initConfigList.slice(); + + for (name in instanceConfig) { + if (name in defaultConfig && !initConfigMap[name]) { + initConfigList.push(name); + } + } + } + + // Point all getters to the initGetters + for (i = 0,ln = initConfigList.length; i < ln; i++) { + name = initConfigList[i]; + nameMap = configNameCache[name]; + this[nameMap.get] = this[nameMap.initGet]; + } + + this.beforeInitConfig(config); + + for (i = 0,ln = initConfigList.length; i < ln; i++) { + name = initConfigList[i]; + nameMap = configNameCache[name]; + getName = nameMap.get; + + if (this.hasOwnProperty(getName)) { + this[nameMap.set].call(this, config[name]); + delete this[getName]; + } + } + + return this; + }, + + beforeInitConfig: Ext.emptyFn, + + /** + * @private + */ + getCurrentConfig: function() { + var defaultConfig = this.defaultConfig, + configNameCache = Ext.Class.configNameCache, + config = {}, + name, nameMap; + + for (name in defaultConfig) { + nameMap = configNameCache[name]; + config[name] = this[nameMap.get].call(this); + } + + return config; + }, + + /** + * @private + */ + setConfig: function(config, applyIfNotSet) { + if (!config) { + return this; + } + + var configNameCache = Ext.Class.configNameCache, + currentConfig = this.config, + defaultConfig = this.defaultConfig, + initialConfig = this.initialConfig, + configList = [], + name, i, ln, nameMap; + + applyIfNotSet = Boolean(applyIfNotSet); + + for (name in config) { + if ((applyIfNotSet && (name in initialConfig))) { + continue; + } + + currentConfig[name] = config[name]; + + if (name in defaultConfig) { + configList.push(name); + nameMap = configNameCache[name]; + this[nameMap.get] = this[nameMap.initGet]; + } + } + + for (i = 0,ln = configList.length; i < ln; i++) { + name = configList[i]; + nameMap = configNameCache[name]; + this[nameMap.set].call(this, config[name]); + delete this[nameMap.get]; + } + + return this; + }, + + set: function(name, value) { + return this[Ext.Class.configNameCache[name].set].call(this, value); + }, + + get: function(name) { + return this[Ext.Class.configNameCache[name].get].call(this); + }, + + /** + * @private + */ + getConfig: function(name) { + return this[Ext.Class.configNameCache[name].get].call(this); + }, + + /** + * @private + */ + hasConfig: function(name) { + return (name in this.defaultConfig); + }, + + /** + * Returns the initial configuration passed to constructor. + * + * @param {String} [name] When supplied, value for particular configuration + * option is returned, otherwise the full config object is returned. + * @return {Object/Mixed} + */ + getInitialConfig: function(name) { + var config = this.config; + + if (!name) { + return config; + } + else { + return config[name]; + } + }, + + /** + * @private + */ + onConfigUpdate: function(names, callback, scope) { + var self = this.self, + // + className = self.$className, + // + i, ln, name, + updaterName, updater, newUpdater; + + names = Ext.Array.from(names); + + scope = scope || this; + + for (i = 0,ln = names.length; i < ln; i++) { + name = names[i]; + updaterName = 'update' + Ext.String.capitalize(name); + updater = this[updaterName] || Ext.emptyFn; + newUpdater = function() { + updater.apply(this, arguments); + scope[callback].apply(scope, arguments); + }; + newUpdater.$name = updaterName; + newUpdater.$owner = self; + // + newUpdater.displayName = className + '#' + updaterName; + // + + this[updaterName] = newUpdater; + } + }, + // + + /** + * @private + * @param name + * @param value + * @return {Mixed} + */ + link: function(name, value) { + this.$links = {}; + this.link = this.doLink; + return this.link.apply(this, arguments); + }, + + doLink: function(name, value) { + this.$links[name] = true; + + this[name] = value; + + return value; + }, + + /** + * @private + */ + unlink: function() { + var i, ln, link, value; + + for (i = 0, ln = arguments.length; i < ln; i++) { + link = arguments[i]; + if (this.hasOwnProperty(link)) { + value = this[link]; + if (value) { + if (value.isInstance && !value.isDestroyed) { + value.destroy(); + } + else if (value.parentNode && 'nodeType' in value) { + value.parentNode.removeChild(value); + } + } + delete this[link]; + } + } + + return this; + }, + + /** + * @protected + */ + destroy: function() { + this.destroy = Ext.emptyFn; + this.isDestroyed = true; + + if (this.hasOwnProperty('$links')) { + this.unlink.apply(this, Ext.Object.getKeys(this.$links)); + delete this.$links; + } + } + }); + + Ext.Base = Base; + +})(Ext.Function.flexSetter); + +//@tag foundation,core +//@define Ext.Class +//@require Ext.Base + +/** + * @class Ext.Class + * + * @author Jacky Nguyen + * @aside guide class_system + * @aside video class-system + * + * Handles class creation throughout the framework. This is a low level factory that is used by Ext.ClassManager and generally + * should not be used directly. If you choose to use Ext.Class you will lose out on the namespace, aliasing and dependency loading + * features made available by Ext.ClassManager. The only time you would use Ext.Class directly is to create an anonymous class. + * + * If you wish to create a class you should use {@link Ext#define Ext.define} which aliases + * {@link Ext.ClassManager#create Ext.ClassManager.create} to enable namespacing and dynamic dependency resolution. + * + * Ext.Class is the factory and **not** the superclass of everything. For the base class that **all** Ext classes inherit + * from, see {@link Ext.Base}. + */ +(function() { + var ExtClass, + Base = Ext.Base, + baseStaticMembers = [], + baseStaticMember, baseStaticMemberLength; + + for (baseStaticMember in Base) { + if (Base.hasOwnProperty(baseStaticMember)) { + baseStaticMembers.push(baseStaticMember); + } + } + + baseStaticMemberLength = baseStaticMembers.length; + + /** + * @method constructor + * Creates a new anonymous class. + * + * @param {Object} data An object represent the properties of this class. + * @param {Function} onCreated (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 = ExtClass = function(Class, data, onCreated) { + if (typeof Class != 'function') { + onCreated = data; + data = Class; + Class = null; + } + + if (!data) { + data = {}; + } + + Class = ExtClass.create(Class); + + ExtClass.process(Class, data, onCreated); + + return Class; + }; + + Ext.apply(ExtClass, { + /** + * @private + * @static + */ + onBeforeCreated: function(Class, data, hooks) { + Class.addMembers(data); + + hooks.onCreated.call(Class, Class); + }, + + /** + * @private + * @static + */ + create: function(Class) { + var name, i; + + if (!Class) { + Class = function() { + return this.constructor.apply(this, arguments); + }; + } + + for (i = 0; i < baseStaticMemberLength; i++) { + name = baseStaticMembers[i]; + Class[name] = Base[name]; + } + + return Class; + }, + + /** + * @private + * @static + */ + process: function(Class, data, onCreated) { + var preprocessorStack = data.preprocessors || ExtClass.defaultPreprocessors, + preprocessors = this.preprocessors, + hooks = { + onBeforeCreated: this.onBeforeCreated, + onCreated: onCreated || Ext.emptyFn + }, + index = 0, + name, preprocessor, properties, + i, ln, fn, property, process; + + delete data.preprocessors; + + process = function(Class, data, hooks) { + fn = null; + + while (fn === null) { + name = preprocessorStack[index++]; + + if (name) { + preprocessor = preprocessors[name]; + properties = preprocessor.properties; + + if (properties === true) { + fn = preprocessor.fn; + } + else { + for (i = 0,ln = properties.length; i < ln; i++) { + property = properties[i]; + + if (data.hasOwnProperty(property)) { + fn = preprocessor.fn; + break; + } + } + } + } + else { + hooks.onBeforeCreated.apply(this, arguments); + return; + } + } + + if (fn.call(this, Class, data, hooks, process) !== false) { + process.apply(this, arguments); + } + }; + + process.call(this, Class, data, hooks); + }, + + /** + * @private + * @static + */ + preprocessors: {}, + + /** + * Register a new pre-processor to be used during the class creation process. + * + * @private + * @static + * @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); + * } + * }); + * + * @param {Function} fn.cls The created class. + * @param {Object} fn.data The set of properties passed in {@link Ext.Class} constructor. + * @param {Function} fn.fn The callback function that __must__ to be executed when this pre-processor finishes, + * regardless of whether the processing is synchronous or asynchronous. + * + * @return {Ext.Class} this + */ + registerPreprocessor: function(name, fn, properties, position, relativeTo) { + if (!position) { + position = 'last'; + } + + if (!properties) { + properties = [name]; + } + + this.preprocessors[name] = { + name: name, + properties: properties || false, + fn: fn + }; + + this.setDefaultPreprocessorPosition(name, position, relativeTo); + + return this; + }, + + /** + * Retrieve a pre-processor callback function by its name, which has been registered before. + * + * @private + * @static + * @param {String} name + * @return {Function} preprocessor + */ + getPreprocessor: function(name) { + return this.preprocessors[name]; + }, + + /** + * @private + * @static + */ + getPreprocessors: function() { + return this.preprocessors; + }, + + /** + * @private + * @static + */ + defaultPreprocessors: [], + + /** + * Retrieve the array stack of default pre-processors. + * @private + * @static + * @return {Function} defaultPreprocessors + */ + getDefaultPreprocessors: function() { + return this.defaultPreprocessors; + }, + + /** + * Set the default array stack of default pre-processors. + * + * @private + * @static + * @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'); + * + * @private + * @static + * @param {String} name The pre-processor name. Note that it needs to be registered with + * {@link Ext.Class#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 + */ + 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) { + Ext.Array.splice(defaultPreprocessors, Math.max(0, index + offset), 0, name); + } + + return this; + }, + + /** + * @private + * @static + */ + configNameCache: {}, + + /** + * @private + * @static + */ + getConfigNameMap: function(name) { + var cache = this.configNameCache, + map = cache[name], + capitalizedName; + + if (!map) { + capitalizedName = name.charAt(0).toUpperCase() + name.substr(1); + + map = cache[name] = { + name: name, + internal: '_' + name, + initializing: 'is' + capitalizedName + 'Initializing', + apply: 'apply' + capitalizedName, + update: 'update' + capitalizedName, + set: 'set' + capitalizedName, + get: 'get' + capitalizedName, + initGet: 'initGet' + capitalizedName, + doSet : 'doSet' + capitalizedName, + changeEvent: name.toLowerCase() + 'change' + } + } + + return map; + }, + + /** + * @private + * @static + */ + generateSetter: function(nameMap) { + var internalName = nameMap.internal, + getName = nameMap.get, + applyName = nameMap.apply, + updateName = nameMap.update, + setter; + + setter = function(value) { + var oldValue = this[internalName], + applier = this[applyName], + updater = this[updateName]; + + delete this[getName]; + + if (applier) { + value = applier.call(this, value, oldValue); + } + + if (typeof value != 'undefined') { + this[internalName] = value; + + if (updater && value !== oldValue) { + updater.call(this, value, oldValue); + } + } + + return this; + }; + + setter.$isDefault = true; + + return setter; + }, + + /** + * @private + * @static + */ + generateInitGetter: function(nameMap) { + var name = nameMap.name, + setName = nameMap.set, + getName = nameMap.get, + initializingName = nameMap.initializing; + + return function() { + this[initializingName] = true; + delete this[getName]; + + this[setName].call(this, this.config[name]); + delete this[initializingName]; + + return this[getName].apply(this, arguments); + } + }, + + /** + * @private + * @static + */ + generateGetter: function(nameMap) { + var internalName = nameMap.internal; + + return function() { + return this[internalName]; + } + } + }); + + /** + * @cfg {String} extend + * The parent class that this class extends. For example: + * + * @example + * Ext.define('Person', { + * say: function(text) { + * alert(text); + * } + * }); + * + * Ext.define('Developer', { + * extend: 'Person', + * say: function(text) { + * this.callParent(["print " + text]); + * } + * }); + * + * var person1 = Ext.create("Person"); + * person1.say("Bill"); + * + * var developer1 = Ext.create("Developer"); + * developer1.say("Ted"); + */ + ExtClass.registerPreprocessor('extend', function(Class, data) { + var Base = Ext.Base, + extend = data.extend, + Parent; + + delete data.extend; + + if (extend && extend !== Object) { + Parent = extend; + } + else { + Parent = Base; + } + + Class.extend(Parent); + + Class.triggerExtended.apply(Class, arguments); + + if (data.onClassExtended) { + Class.onExtended(data.onClassExtended, Class); + delete data.onClassExtended; + } + + }, true); + + // + /** + * @cfg {Object} statics + * List of static methods for this class. For example: + * + * 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'); + */ + ExtClass.registerPreprocessor('statics', function(Class, data) { + Class.addStatics(data.statics); + + delete data.statics; + }); + // + + // + /** + * @cfg {Object} inheritableStatics + * List of inheritable static methods for this class. + * Otherwise just like {@link #statics} but subclasses inherit these methods. + */ + ExtClass.registerPreprocessor('inheritableStatics', function(Class, data) { + Class.addInheritableStatics(data.inheritableStatics); + + delete data.inheritableStatics; + }); + // + + // + /** + * @cfg {Object} config + * + * List of configuration options with their default values. + * + * __Note:__ You need to make sure {@link Ext.Base#initConfig} is called from your constructor if you are defining + * your own class or singleton, unless you are extending a Component. Otherwise the generated getter and setter + * methods will not be initialized. + * + * Each config item will have its own setter and getter method automatically generated inside the class prototype + * during class creation time, if the class does not have those methods explicitly defined. + * + * As an example, let's convert the name property of a Person class to be a config item, then add extra age and + * gender items. + * + * Ext.define('My.sample.Person', { + * config: { + * name: 'Mr. Unknown', + * age: 0, + * gender: 'Male' + * }, + * + * constructor: function(config) { + * this.initConfig(config); + * + * return this; + * } + * + * // ... + * }); + * + * Within the class, this.name still has the default value of "Mr. Unknown". However, it's now publicly accessible + * without sacrificing encapsulation, via setter and getter methods. + * + * var jacky = new Person({ + * name: "Jacky", + * age: 35 + * }); + * + * alert(jacky.getAge()); // alerts 35 + * alert(jacky.getGender()); // alerts "Male" + * + * jacky.walk(10); // alerts "Jacky is walking 10 steps" + * + * jacky.setName("Mr. Nguyen"); + * alert(jacky.getName()); // alerts "Mr. Nguyen" + * + * jacky.walk(10); // alerts "Mr. Nguyen is walking 10 steps" + * + * Notice that we changed the class constructor to invoke this.initConfig() and pass in the provided config object. + * Two key things happened: + * + * - The provided config object when the class is instantiated is recursively merged with the default config object. + * - All corresponding setter methods are called with the merged values. + * + * Beside storing the given values, throughout the frameworks, setters generally have two key responsibilities: + * + * - Filtering / validation / transformation of the given value before it's actually stored within the instance. + * - Notification (such as firing events) / post-processing after the value has been set, or changed from a + * previous value. + * + * By standardize this common pattern, the default generated setters provide two extra template methods that you + * can put your own custom logics into, i.e: an "applyFoo" and "updateFoo" method for a "foo" config item, which are + * executed before and after the value is actually set, respectively. Back to the example class, let's validate that + * age must be a valid positive number, and fire an 'agechange' if the value is modified. + * + * Ext.define('My.sample.Person', { + * config: { + * // ... + * }, + * + * constructor: { + * // ... + * }, + * + * applyAge: function(age) { + * if (typeof age !== 'number' || age < 0) { + * console.warn("Invalid age, must be a positive number"); + * return; + * } + * + * return age; + * }, + * + * updateAge: function(newAge, oldAge) { + * // age has changed from "oldAge" to "newAge" + * this.fireEvent('agechange', this, newAge, oldAge); + * } + * + * // ... + * }); + * + * var jacky = new Person({ + * name: "Jacky", + * age: 'invalid' + * }); + * + * alert(jacky.getAge()); // alerts 0 + * + * alert(jacky.setAge(-100)); // alerts 0 + * alert(jacky.getAge()); // alerts 0 + * + * alert(jacky.setAge(35)); // alerts 0 + * alert(jacky.getAge()); // alerts 35 + * + * In other words, when leveraging the config feature, you mostly never need to define setter and getter methods + * explicitly. Instead, "apply*" and "update*" methods should be implemented where necessary. Your code will be + * consistent throughout and only contain the minimal logic that you actually care about. + * + * When it comes to inheritance, the default config of the parent class is automatically, recursively merged with + * the child's default config. The same applies for mixins. + */ + ExtClass.registerPreprocessor('config', function(Class, data) { + var config = data.config, + prototype = Class.prototype, + defaultConfig = prototype.config, + nameMap, name, setName, getName, initGetName, internalName, value; + + delete data.config; + + for (name in config) { + // Once per config item, per class hierarchy + if (config.hasOwnProperty(name) && !(name in defaultConfig)) { + value = config[name]; + nameMap = this.getConfigNameMap(name); + setName = nameMap.set; + getName = nameMap.get; + initGetName = nameMap.initGet; + internalName = nameMap.internal; + + data[initGetName] = this.generateInitGetter(nameMap); + + if (value === null && !data.hasOwnProperty(internalName)) { + data[internalName] = null; + } + + if (!data.hasOwnProperty(getName)) { + data[getName] = this.generateGetter(nameMap); + } + + if (!data.hasOwnProperty(setName)) { + data[setName] = this.generateSetter(nameMap); + } + } + } + + Class.addConfig(config, true); + }); + // + + // + /** + * @cfg {Object} mixins + * List of classes to mix into this class. For example: + * + * Ext.define('CanSing', { + * sing: function() { + * alert("I'm on the highway to hell..."); + * } + * }); + * + * Ext.define('Musician', { + * extend: 'Person', + * + * mixins: { + * canSing: 'CanSing' + * } + * }); + */ + ExtClass.registerPreprocessor('mixins', function(Class, data, hooks) { + var mixins = data.mixins, + name, mixin, i, ln; + + delete data.mixins; + + Ext.Function.interceptBefore(hooks, 'onCreated', function() { + if (mixins instanceof Array) { + for (i = 0,ln = mixins.length; i < ln; i++) { + mixin = mixins[i]; + name = mixin.prototype.mixinId || mixin.$className; + + Class.mixin(name, mixin); + } + } + else { + for (name in mixins) { + if (mixins.hasOwnProperty(name)) { + Class.mixin(name, mixins[name]); + } + } + } + }); + }); + // + + // + // Backwards compatible + Ext.extend = function(Class, Parent, members) { + if (arguments.length === 2 && Ext.isObject(Parent)) { + members = Parent; + Parent = Class; + Class = null; + } + + var cls; + + if (!Parent) { + throw new Error("[Ext.extend] Attempting to extend from a class which has not been loaded on the page."); + } + + members.extend = Parent; + members.preprocessors = [ + 'extend' + // + ,'statics' + // + // + ,'inheritableStatics' + // + // + ,'mixins' + // + // + ,'config' + // + ]; + + if (Class) { + cls = new ExtClass(Class, members); + } + else { + cls = new ExtClass(members); + } + + cls.prototype.override = function(o) { + for (var m in o) { + if (o.hasOwnProperty(m)) { + this[m] = o[m]; + } + } + }; + + return cls; + }; + // +})(); + +//@tag foundation,core +//@define Ext.ClassManager +//@require Ext.Class + +/** + * @class Ext.ClassManager + * + * @author Jacky Nguyen + * @aside guide class_system + * @aside video class-system + * + * 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.ClassManager#create Ext.create} + * - {@link Ext#widget Ext.widget} + * - {@link Ext#getClass Ext.getClass} + * - {@link Ext#getClassName Ext.getClassName} + * + * ## 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. + * + * @example + * 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.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 + * + * @singleton + */ +(function(Class, alias, arraySlice, arrayFrom, global) { + 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: {}, + nameToAlternates: {} + }, + + /** @private */ + enableNamespaceParseCache: true, + + /** @private */ + namespaceParseCache: {}, + + /** @private */ + instantiators: [], + + /** + * Checks if a class has already been created. + * + * @param {String} className + * @return {Boolean} exist + */ + isCreated: function(className) { + var existCache = this.existCache, + i, ln, part, root, parts; + + // + if (typeof className != 'string' || className.length < 1) { + throw new Error("[Ext.ClassManager] Invalid classname, must be a string and must not be empty"); + } + // + + if (this.classes[className] || existCache[className]) { + return true; + } + + root = 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]; + } + } + + existCache[className] = true; + + this.triggerCreated(className); + + return true; + }, + + /** + * @private + */ + createdListeners: [], + + /** + * @private + */ + nameCreatedListeners: {}, + + /** + * @private + */ + triggerCreated: function(className) { + var listeners = this.createdListeners, + nameListeners = this.nameCreatedListeners, + alternateNames = this.maps.nameToAlternates[className], + names = [className], + i, ln, j, subLn, listener, name; + + for (i = 0,ln = listeners.length; i < ln; i++) { + listener = listeners[i]; + listener.fn.call(listener.scope, className); + } + + if (alternateNames) { + names.push.apply(names, alternateNames); + } + + for (i = 0,ln = names.length; i < ln; i++) { + name = names[i]; + listeners = nameListeners[name]; + + if (listeners) { + for (j = 0,subLn = listeners.length; j < subLn; j++) { + listener = listeners[j]; + listener.fn.call(listener.scope, name); + } + delete nameListeners[name]; + } + } + }, + + /** + * @private + */ + onCreated: function(fn, scope, className) { + var listeners = this.createdListeners, + nameListeners = this.nameCreatedListeners, + listener = { + fn: fn, + scope: scope + }; + + if (className) { + if (this.isCreated(className)) { + fn.call(scope, className); + return; + } + + if (!nameListeners[className]) { + nameListeners[className] = []; + } + + nameListeners[className].push(listener); + } + else { + listeners.push(listener); + } + }, + + /** + * Supports namespace rewriting. + * @private + */ + parseNamespace: function(namespace) { + // + if (typeof namespace != 'string') { + throw new Error("[Ext.ClassManager] 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, + root = global, + name = namespace, + rewrite, from, to, i, ln; + + for (i = 0, ln = rewrites.length; i < ln; i++) { + rewrite = rewrites[i]; + from = rewrite.from; + to = rewrite.to; + + if (name === from || name.substring(0, from.length) === from) { + name = name.substring(from.length); + + if (typeof to != 'string') { + root = to; + } else { + parts = parts.concat(to.split('.')); + } + + break; + } + } + + parts.push(root); + + parts = parts.concat(name.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 + */ + setNamespace: function(name, value) { + var root = global, + parts = this.parseNamespace(name), + ln = parts.length - 1, + leaf = parts[ln], + i, part; + + for (i = 0; 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 = 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 me = this, + maps = me.maps, + nameToAlternates = maps.nameToAlternates, + targetName = me.getName(value), + alternates; + + me.classes[name] = me.setNamespace(name, value); + + if (targetName && targetName !== name) { + maps.alternateToName[name] = targetName; + alternates = nameToAlternates[targetName] || (nameToAlternates[targetName] = []); + alternates.push(name); + } + + return this; + }, + + /** + * Retrieve a class by its name. + * + * @param {String} name + * @return {Ext.Class} class + */ + get: function(name) { + var classes = this.classes; + + if (classes[name]) { + return classes[name]; + } + + var root = 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 {Ext.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[alias]) { + Ext.Logger.info("[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; + }, + + /** + * Adds a batch of class name to alias mappings + * @param {Object} aliases The set of mappings of the form + * className : [values...] + */ + addNameAliasMappings: function(aliases){ + var aliasToNameMap = this.maps.aliasToName, + nameToAliasesMap = this.maps.nameToAliases, + className, aliasList, alias, i; + + for (className in aliases) { + aliasList = nameToAliasesMap[className] || + (nameToAliasesMap[className] = []); + + for (i = 0; i < aliases[className].length; i++) { + alias = aliases[className][i]; + if (!aliasToNameMap[alias]) { + aliasToNameMap[alias] = className; + aliasList.push(alias); + } + } + + } + return this; + }, + + /** + * + * @param {Object} alternates The set of mappings of the form + * className : [values...] + */ + addNameAlternateMappings: function(alternates) { + var alternateToName = this.maps.alternateToName, + nameToAlternates = this.maps.nameToAlternates, + className, aliasList, alternate, i; + + for (className in alternates) { + aliasList = nameToAlternates[className] || + (nameToAlternates[className] = []); + + for (i = 0; i < alternates[className].length; i++) { + alternate = alternates[className]; + if (!alternateToName[alternate]) { + alternateToName[alternate] = className; + aliasList.push(alternate); + } + } + + } + return this; + }, + + /** + * Get a reference to the class by its alias. + * + * @param {String} alias + * @return {Ext.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 {Ext.Class/Object} object + * @return {String} className + */ + 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 {Ext.Class} class + */ + getClass: function(object) { + return object && object.self || null; + }, + + /** + * @private + */ + create: function(className, data, createdFn) { + // + if (typeof className != 'string') { + throw new Error("[Ext.define] 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, process, i, ln, j, subLn, postprocessorProperties, postprocessorProperty; + + delete data.postprocessors; + + for (i = 0,ln = postprocessorStack.length; i < ln; i++) { + postprocessor = postprocessorStack[i]; + + if (typeof postprocessor == 'string') { + postprocessor = registeredPostprocessors[postprocessor]; + postprocessorProperties = postprocessor.properties; + + if (postprocessorProperties === true) { + postprocessors.push(postprocessor.fn); + } + else if (postprocessorProperties) { + for (j = 0,subLn = postprocessorProperties.length; j < subLn; j++) { + postprocessorProperty = postprocessorProperties[j]; + + if (data.hasOwnProperty(postprocessorProperty)) { + postprocessors.push(postprocessor.fn); + break; + } + } + } + } + else { + postprocessors.push(postprocessor); + } + } + + process = function(clsName, cls, clsData) { + postprocessor = postprocessors[index++]; + + if (!postprocessor) { + Manager.set(className, cls); + + if (createdFn) { + createdFn.call(cls, cls); + } + + Manager.triggerCreated(className); + return; + } + + if (postprocessor.call(this, clsName, cls, clsData, process) !== false) { + process.apply(this, arguments); + } + }; + + process.call(Manager, className, this, data); + }); + }, + + createOverride: function(className, data) { + var overriddenClassName = data.override, + requires = Ext.Array.from(data.requires); + + delete data.override; + delete data.requires; + + this.existCache[className] = true; + + Ext.require(requires, function() { + // Override the target class right after it's created + this.onCreated(function() { + this.get(overriddenClassName).override(data); + + // This push the overridding file itself into Ext.Loader.history + // Hence if the target class never exists, the overriding file will + // never be included in the build + this.triggerCreated(className); + }, this, overriddenClassName); + }, this); + + return this; + }, + + /** + * 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 + */ + instantiateByAlias: function() { + var alias = arguments[0], + args = arraySlice.call(arguments), + className = this.getNameByAlias(alias); + + if (!className) { + className = this.maps.aliasToName[alias]; + + // + if (!className) { + throw new Error("[Ext.createByAlias] Cannot create an instance of unrecognized alias: " + alias); + } + // + + // + Ext.Logger.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.ClassManager#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 formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 }); + * + * // alternate name + * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 }); + * + * // full class name + * var formPanel = Ext.create('Ext.form.Panel', { 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 + */ + instantiate: function() { + var name = arguments[0], + args = arraySlice.call(arguments, 1), + alias = name, + possibleName, cls; + + if (typeof name != 'function') { + // + if ((typeof name != 'string' || name.length < 1)) { + throw new Error("[Ext.create] 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) { + // + Ext.Logger.warn("[Ext.Loader] Synchronously loading '" + name + "'; consider adding '" + + ((possibleName) ? alias : name) + "' explicitly as a require of the corresponding class"); + // + + Ext.syncRequire(name); + + cls = this.get(name); + } + + // + if (!cls) { + throw new Error("[Ext.create] Cannot create an instance of unrecognized class name / alias: " + alias); + } + + if (typeof cls != 'function') { + throw new Error("[Ext.create] '" + name + "' is a singleton and cannot be instantiated"); + } + // + + return this.getInstantiator(args.length)(cls, args); + }, + + /** + * @private + * @param name + * @param args + */ + dynInstantiate: function(name, args) { + args = arrayFrom(args, true); + args.unshift(name); + + return this.instantiate.apply(this, args); + }, + + /** + * @private + * @param length + */ + getInstantiator: function(length) { + var instantiators = this.instantiators, + instantiator; + + instantiator = instantiators[length]; + + if (!instantiator) { + var i = length, + args = []; + + for (i = 0; i < length; i++) { + args.push('a[' + i + ']'); + } + + instantiator = instantiators[length] = new Function('c', 'a', 'return new c(' + args.join(',') + ')'); + // + instantiator.displayName = "Ext.ClassManager.instantiate" + length; + // + } + + return instantiator; + }, + + /** + * @private + */ + postprocessors: {}, + + /** + * @private + */ + defaultPostprocessors: [], + + /** + * Register a post-processor function. + * + * @private + * @param {String} name + * @param {Function} postprocessor + */ + registerPostprocessor: function(name, fn, properties, position, relativeTo) { + if (!position) { + position = 'last'; + } + + if (!properties) { + properties = [name]; + } + + this.postprocessors[name] = { + name: name, + properties: properties || false, + fn: fn + }; + + this.setDefaultPostprocessorPosition(name, position, relativeTo); + + return this; + }, + + /** + * Set the default post processors array stack which are applied to every class. + * + * @private + * @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 = arrayFrom(postprocessors); + + return this; + }, + + /** + * Insert this post-processor at a specific position in the stack, optionally relative to + * any existing post-processor + * + * @private + * @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) { + Ext.Array.splice(defaultPostprocessors, 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 + */ + getNamesByExpression: function(expression) { + var nameToAliasesMap = this.maps.nameToAliases, + names = [], + name, alias, aliases, possibleName, regex, i, ln; + + // + if (typeof expression != 'string' || expression.length < 1) { + throw new Error("[Ext.ClassManager.getNamesByExpression] 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; + } + }; + + // + /** + * @cfg {String[]} alias + * @member Ext.Class + * List of short aliases for class names. Most useful for defining xtypes for widgets: + * + * Ext.define('MyApp.CoolPanel', { + * extend: 'Ext.panel.Panel', + * alias: ['widget.coolpanel'], + * title: 'Yeah!' + * }); + * + * // Using Ext.create + * Ext.create('widget.coolpanel'); + * + * // Using the shorthand for widgets and in xtypes + * Ext.widget('panel', { + * items: [ + * {xtype: 'coolpanel', html: 'Foo'}, + * {xtype: 'coolpanel', html: 'Bar'} + * ] + * }); + */ + Manager.registerPostprocessor('alias', function(name, cls, data) { + var aliases = data.alias, + i, ln; + + for (i = 0,ln = aliases.length; i < ln; i++) { + alias = aliases[i]; + + this.setAlias(cls, alias); + } + + }, ['xtype', 'alias']); + // + + // + /** + * @cfg {Boolean} singleton + * @member Ext.Class + * When set to true, the class will be instantiated as singleton. For example: + * + * Ext.define('Logger', { + * singleton: true, + * log: function(msg) { + * console.log(msg); + * } + * }); + * + * Logger.log('Hello'); + */ + Manager.registerPostprocessor('singleton', function(name, cls, data, fn) { + fn.call(this, name, new cls(), data); + return false; + }); + // + + // + /** + * @cfg {String/String[]} alternateClassName + * @member Ext.Class + * Defines alternate names for this class. For example: + * + * @example + * Ext.define('Developer', { + * alternateClassName: ['Coder', 'Hacker'], + * code: function(msg) { + * alert('Typing... ' + msg); + * } + * }); + * + * var joe = Ext.create('Developer'); + * joe.code('stackoverflow'); + * + * var rms = Ext.create('Hacker'); + * rms.code('hack hack'); + */ + 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') { + throw new Error("[Ext.define] Invalid alternate of: '" + alternate + "' for class: '" + name + "'; must be a valid string"); + } + // + + this.set(alternate, cls); + } + }); + // + + Ext.apply(Ext, { + /** + * Instantiate a class by either full name, alias or alternate name. + * + * 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 formPanel = Ext.create('widget.formpanel', { width: 600, height: 800 }); + * + * // alternate name + * var formPanel = Ext.create('Ext.form.FormPanel', { width: 600, height: 800 }); + * + * // full class name + * var formPanel = Ext.create('Ext.form.Panel', { 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 + * @member Ext + */ + create: alias(Manager, 'instantiate'), + + /** + * 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 + */ + widget: function(name) { + var args = arraySlice.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'), + + /** + * Defines a class or override. A basic class is defined like this: + * + * Ext.define('My.awesome.Class', { + * someProperty: 'something', + * + * someMethod: function(s) { + * console.log(s + this.someProperty); + * } + * }); + * + * var obj = new My.awesome.Class(); + * + * obj.someMethod('Say '); // logs 'Say something' to the console + * + * To defines an override, include the `override` property. The content of an + * override is aggregated with the specified class in order to extend or modify + * that class. This can be as simple as setting default property values or it can + * extend and/or replace methods. This can also extend the statics of the class. + * + * One use for an override is to break a large class into manageable pieces. + * + * // File: /src/app/Panel.js + * Ext.define('My.app.Panel', { + * extend: 'Ext.panel.Panel', + * requires: [ + * 'My.app.PanelPart2', + * 'My.app.PanelPart3' + * ], + * + * constructor: function (config) { + * this.callParent(arguments); // calls Ext.panel.Panel's constructor + * // ... + * }, + * + * statics: { + * method: function () { + * return 'abc'; + * } + * } + * }); + * + * // File: /src/app/PanelPart2.js + * Ext.define('My.app.PanelPart2', { + * override: 'My.app.Panel', + * + * constructor: function (config) { + * this.callParent(arguments); // calls My.app.Panel's constructor + * // ... + * } + * }); + * + * Another use for an override is to provide optional parts of classes that can be + * independently required. In this case, the class may even be unaware of the + * override altogether. + * + * Ext.define('My.ux.CoolTip', { + * override: 'Ext.tip.ToolTip', + * + * constructor: function (config) { + * this.callParent(arguments); // calls Ext.tip.ToolTip's constructor + * // ... + * } + * }); + * + * The above override can now be required as normal. + * + * Ext.define('My.app.App', { + * requires: [ + * 'My.ux.CoolTip' + * ] + * }); + * + * Overrides can also contain statics: + * + * Ext.define('My.app.BarMod', { + * override: 'Ext.foo.Bar', + * + * statics: { + * method: function (x) { + * return this.callParent([x * 2]); // call Ext.foo.Bar.method + * } + * } + * }); + * + * __IMPORTANT:__ An override is only included in a build if the class it overrides is + * required. Otherwise, the override, like the target class, is not included. + * + * @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` + * - `override` + * + * @param {Function} [createdFn] Optional callback to execute after the class (or override) + * is created. The execution scope (`this`) will be the newly created class itself. + * @return {Ext.Base} + * + * @member Ext + * @method define + */ + define: function (className, data, createdFn) { + if ('override' in data) { + return Manager.createOverride.apply(Manager, arguments); + } + + return Manager.create.apply(Manager, arguments); + }, + + /** + * Convenient shorthand for {@link Ext.ClassManager#getName}. + * @member Ext + * @method getClassName + * @inheritdoc Ext.ClassManager#getName + */ + getClassName: alias(Manager, 'getName'), + + /** + * Returns the display name for object. This name is looked for in order from the following places: + * + * - `displayName` field of the object. + * - `$name` and `$class` fields of the object. + * - '$className` field of the object. + * + * This method is used by {@link Ext.Logger#log} to display information about objects. + * + * @param {Mixed} [object] The object who's display name to determine. + * @return {String} The determined display name, or "Anonymous" if none found. + * @member Ext + */ + getDisplayName: function(object) { + if (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 getClass + */ + 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. + * @member Ext + * @method namespace + */ + namespace: alias(Manager, 'createNamespaces') + }); + + /** + * Old name for {@link Ext#widget}. + * @deprecated 4.0.0 Please use {@link Ext#widget} instead. + * @method createWidget + * @member Ext + */ + 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, 'first'); + + Class.registerPreprocessor('alias', function(cls, data) { + var prototype = cls.prototype, + xtypes = arrayFrom(data.xtype), + aliases = arrayFrom(data.alias), + widgetPrefix = 'widget.', + widgetPrefixLength = widgetPrefix.length, + xtypesChain = Array.prototype.slice.call(prototype.xtypesChain || []), + xtypesMap = Ext.merge({}, prototype.xtypesMap || {}), + i, ln, alias, xtype; + + for (i = 0,ln = aliases.length; i < ln; i++) { + alias = aliases[i]; + + // + if (typeof alias != 'string' || alias.length < 1) { + throw new Error("[Ext.define] Invalid alias of: '" + alias + "' for class: '" + name + "'; must be a valid string"); + } + // + + if (alias.substring(0, widgetPrefixLength) === widgetPrefix) { + xtype = alias.substring(widgetPrefixLength); + Ext.Array.include(xtypes, xtype); + } + } + + cls.xtype = data.xtype = xtypes[0]; + data.xtypes = xtypes; + + for (i = 0,ln = xtypes.length; i < ln; i++) { + xtype = xtypes[i]; + + if (!xtypesMap[xtype]) { + xtypesMap[xtype] = true; + xtypesChain.push(xtype); + } + } + + data.xtypesChain = xtypesChain; + data.xtypesMap = xtypesMap; + + Ext.Function.interceptAfter(data, 'onClassCreated', function() { + var mixins = prototype.mixins, + key, mixin; + + for (key in mixins) { + if (mixins.hasOwnProperty(key)) { + mixin = mixins[key]; + + xtypes = mixin.xtypes; + + if (xtypes) { + for (i = 0,ln = xtypes.length; i < ln; i++) { + xtype = xtypes[i]; + + if (!xtypesMap[xtype]) { + xtypesMap[xtype] = true; + xtypesChain.push(xtype); + } + } + } + } + } + }); + + for (i = 0,ln = xtypes.length; i < ln; i++) { + xtype = xtypes[i]; + + // + if (typeof xtype != 'string' || xtype.length < 1) { + throw new Error("[Ext.define] Invalid xtype of: '" + xtype + "' for class: '" + name + "'; must be a valid non-empty string"); + } + // + + Ext.Array.include(aliases, widgetPrefix + xtype); + } + + data.alias = aliases; + + }, ['xtype', 'alias']); + +})(Ext.Class, Ext.Function.alias, Array.prototype.slice, Ext.Array.from, Ext.global); + +//@tag foundation,core +//@define Ext.Loader +//@require Ext.ClassManager + +/** + * @class Ext.Loader + * + * @author Jacky Nguyen + * @docauthor Jacky Nguyen + * @aside guide mvc_dependencies + * + * 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. + * + * __Note:__ The Loader is only enabled by default in development versions of the library (eg sencha-touch-debug.js). To + * explicitly enable the loader, use `Ext.Loader.setConfig({ enabled: true });` before the start of your script. + * + * ## 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(function () { + * // ... + * }); + * + * 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 + */ +(function(Manager, Class, flexSetter, alias, pass, arrayFrom, arrayErase, arrayInclude) { + + var + dependencyProperties = ['extend', 'mixins', 'requires'], + Loader, + setPathCount = 0;; + + Loader = Ext.Loader = { + + /** + * @private + */ + isInHistory: {}, + + /** + * An array of class names to keep track of the dependency loading order. + * This is not guaranteed to be the same every time 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. + * @cfg {Boolean} enabled + */ + enabled: true, + + /** + * @cfg {Boolean} disableCaching + * Appends current timestamp to script files to prevent caching. + */ + disableCaching: true, + + /** + * @cfg {String} disableCachingParam + * The get parameter name for the cache buster's timestamp. + */ + 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, `Other.awesome.Class` + * will simply be loaded from `./Other/awesome/Class.js`. + */ + paths: { + 'Ext': '.' + } + }, + + /** + * Set the configuration for the loader. This should be called right after ext-(debug).js + * is included in the page, and before Ext.onReady. i.e: + * + * + * + * + * Refer to config options of {@link Ext.Loader} for the list of possible properties. + * + * @param {Object} config The config object to override the default values. + * @return {Ext.Loader} this + */ + setConfig: function(name, value) { + if (Ext.isObject(name) && arguments.length === 1) { + Ext.merge(this.config, name); + } + else { + this.config[name] = (Ext.isObject(value)) ? Ext.merge(this.config[name], value) : value; + } + setPathCount += 1; + 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 + * @method + */ + setPath: flexSetter(function(name, path) { + this.config.paths[name] = path; + setPathCount += 1; + return this; + }), + + /** + * Sets a batch of path entries + * + * @param {Object } paths a set of className: path mappings + * @return {Ext.Loader} this + */ + addClassPathMappings: function(paths) { + var name; + + if(setPathCount == 0){ + Loader.config.paths = paths; + } else { + for(name in paths){ + Loader.config.paths[name] = paths[name]; + } + } + setPathCount++; + return Loader; + }, + + /** + * 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 + */ + 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; + }, + + /** + * 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. + */ + require: function(expressions, fn, scope, excludes) { + if (fn) { + fn.call(scope); + } + }, + + /** + * 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 + */ + syncRequire: function() {}, + + /** + * 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. + */ + 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); + } + }; + }, + + /** + * 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 (`this`) 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); + }; + } + + fn.call(scope); + } + }; + + // + Ext.apply(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 + */ + isClassFileLoaded: {}, + + /** + * @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: {}, + + /** + * @private + */ + syncModeEnabled: false, + + scriptElements: {}, + + /** + * 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 queue = this.queue, + ln = queue.length, + i, item, j, requires, references; + + if (ln === 0) { + this.triggerReady(); + return; + } + + for (i = 0; i < ln; i++) { + item = queue[i]; + + if (item) { + requires = item.requires; + references = item.references; + + // 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 + arrayErase(requires, j, 1); + } + else { + j++; + } + } while (j < requires.length); + + if (item.requires.length === 0) { + arrayErase(queue, 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; + }, + + removeScriptElement: function(url) { + var scriptElements = this.scriptElements; + + if (scriptElements[url]) { + this.cleanupScriptElement(scriptElements[url], true); + delete scriptElements[url]; + } + + return this; + }, + + /** + * @private + */ + cleanupScriptElement: function(script, remove) { + script.onload = null; + script.onreadystatechange = null; + script.onerror = null; + + if (remove) { + this.documentHead.removeChild(script); + } + + return this; + }, + + /** + * Load a script file, supports both asynchronous and synchronous approaches + * + * @param {String} url + * @param {Function} onLoad + * @param {Object} scope + * @param {Boolean} synchronous + * @private + */ + loadScriptFile: function(url, onLoad, onError, scope, synchronous) { + var me = this, + isFileLoaded = this.isFileLoaded, + scriptElements = this.scriptElements, + noCacheUrl = url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''), + xhr, status, content, onScriptError; + + if (isFileLoaded[url]) { + return this; + } + + 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() { + if (!isFileLoaded[url]) { + scriptElements[url] = me.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope); + } + }); + } + else { + scriptElements[url] = 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) { + // + 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); + // + } + + status = (xhr.status == 1223) ? 204 : xhr.status; + content = xhr.responseText; + + if ((status >= 200 && status < 300) || status == 304 || (status == 0 && content.length > 0)) { + // Debugger friendly, file names are still shown even though they're eval'ed code + // Breakpoints work on both Firebug and Chrome's Web Inspector + Ext.globalEval(content + "\n//@ sourceURL=" + url); + 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; + } + }, + + // documented above + syncRequire: function() { + var syncModeEnabled = this.syncModeEnabled; + + if (!syncModeEnabled) { + this.syncModeEnabled = true; + } + + this.require.apply(this, arguments); + + if (!syncModeEnabled) { + this.syncModeEnabled = false; + } + + this.refreshQueue(); + }, + + // documented above + require: function(expressions, fn, scope, excludes) { + var excluded = {}, + included = {}, + queue = this.queue, + classNameToFilePathMap = this.classNameToFilePathMap, + isClassFileLoaded = this.isClassFileLoaded, + excludedClassNames = [], + possibleClassNames = [], + classNames = [], + references = [], + callback, + syncModeEnabled, + filePath, expression, exclude, className, + possibleClassName, i, j, ln, subLn; + + if (excludes) { + excludes = arrayFrom(excludes); + + 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; + } + } + } + } + + expressions = arrayFrom(expressions); + + if (fn) { + if (fn.length > 0) { + callback = function() { + var classes = [], + i, ln, name; + + for (i = 0,ln = references.length; i < ln; i++) { + name = references[i]; + classes.push(Manager.get(name)); + } + + return fn.apply(this, classes); + }; + } + else { + callback = fn; + } + } + else { + callback = Ext.emptyFn; + } + + scope = scope || Ext.global; + + for (i = 0,ln = expressions.length; i < ln; i++) { + expression = expressions[i]; + + if (typeof expression == 'string' && expression.length > 0) { + possibleClassNames = Manager.getNamesByExpression(expression); + subLn = possibleClassNames.length; + + for (j = 0; j < subLn; j++) { + possibleClassName = possibleClassNames[j]; + + if (excluded[possibleClassName] !== true) { + references.push(possibleClassName); + + if (!Manager.isCreated(possibleClassName) && !included[possibleClassName]) { + included[possibleClassName] = true; + classNames.push(possibleClassName); + } + } + } + } + } + + // If the dynamic dependency feature is not being used, throw an error + // if the dependencies are not defined + if (classNames.length > 0) { + if (!this.config.enabled) { + throw new Error("Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " + + "Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')); + } + } + else { + callback.call(scope); + return this; + } + + syncModeEnabled = this.syncModeEnabled; + + if (!syncModeEnabled) { + queue.push({ + requires: classNames.slice(), // this array will be modified as the queue is processed, + // so we need a copy of it + callback: callback, + scope: scope + }); + } + + ln = classNames.length; + + for (i = 0; i < ln; i++) { + className = classNames[i]; + + filePath = this.getPath(className); + + // If we are synchronously loading a file that has already been asynchronously loaded before + // we need to destroy the script tag and revert the count + // This file will then be forced loaded in synchronous + if (syncModeEnabled && isClassFileLoaded.hasOwnProperty(className)) { + this.numPendingFiles--; + this.removeScriptElement(filePath); + delete isClassFileLoaded[className]; + } + + if (!isClassFileLoaded.hasOwnProperty(className)) { + isClassFileLoaded[className] = false; + + classNameToFilePathMap[className] = filePath; + + this.numPendingFiles++; + + this.loadScriptFile( + filePath, + pass(this.onFileLoaded, [className, filePath], this), + pass(this.onFileLoadError, [className, filePath]), + this, + syncModeEnabled + ); + } + } + + if (syncModeEnabled) { + callback.call(scope); + + if (ln === 1) { + return Manager.get(className); + } + } + + return this; + }, + + /** + * @private + * @param {String} className + * @param {String} filePath + */ + onFileLoaded: function(className, filePath) { + this.numLoadedFiles++; + + this.isClassFileLoaded[className] = true; + this.isFileLoaded[filePath] = true; + + this.numPendingFiles--; + + if (this.numPendingFiles === 0) { + this.refreshQueue(); + } + + // + if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) { + var queue = this.queue, + missingClasses = [], + missingPaths = [], + requires, + i, ln, j, subLn; + + for (i = 0,ln = queue.length; i < ln; i++) { + requires = queue[i].requires; + + for (j = 0,subLn = requires.length; j < subLn; j++) { + if (this.isClassFileLoaded[requires[j]]) { + missingClasses.push(requires[j]); + } + } + } + + if (missingClasses.length < 1) { + return; + } + + missingClasses = Ext.Array.filter(Ext.Array.unique(missingClasses), function(item) { + return !this.requiresMap.hasOwnProperty(item); + }, this); + + for (i = 0,ln = missingClasses.length; i < ln; i++) { + missingPaths.push(this.classNameToFilePathMap[missingClasses[i]]); + } + + throw new Error("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; + + // + throw new Error("[Ext.Loader] " + errorMessage); + // + }, + + /** + * @private + */ + addOptionalRequires: function(requires) { + var optionalRequires = this.optionalRequires, + i, ln, require; + + requires = arrayFrom(requires); + + for (i = 0, ln = requires.length; i < ln; i++) { + require = requires[i]; + + arrayInclude(optionalRequires, require); + } + + return this; + }, + + /** + * @private + */ + triggerReady: function(force) { + var readyListeners = this.readyListeners, + optionalRequires = this.optionalRequires, + listener; + + if (this.isLoading || force) { + this.isLoading = false; + + if (optionalRequires.length !== 0) { + // Clone then empty the array to eliminate potential recursive loop issue + optionalRequires = optionalRequires.slice(); + + // Empty the original array + this.optionalRequires.length = 0; + + this.require(optionalRequires, 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; + }, + + // duplicate definition (documented above) + 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) { + var isInHistory = this.isInHistory; + + if (className && this.isClassFileLoaded.hasOwnProperty(className) && !isInHistory[className]) { + isInHistory[className] = true; + this.history.push(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 + * @inheritdoc Ext.Loader#require + */ + Ext.require = alias(Loader, 'require'); + + /** + * Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}. + * @member Ext + * @method syncRequire + * @inheritdoc Ext.Loader#syncRequire + */ + Ext.syncRequire = alias(Loader, 'syncRequire'); + + /** + * Convenient shortcut to {@link Ext.Loader#exclude}. + * @member Ext + * @method exclude + * @inheritdoc Ext.Loader#exclude + */ + Ext.exclude = alias(Loader, 'exclude'); + + /** + * Adds a listener to be notified when the document is ready and all dependencies are loaded. + * + * @param {Function} fn The method the event invokes. + * @param {Object} [scope] The scope in which the handler function executes. Defaults to the browser window. + * @param {Boolean} [options] Options object as passed to {@link Ext.Element#addListener}. It is recommended + * that the options `{single: true}` be used so that the handler is removed on first invocation. + * @member Ext + * @method onReady + */ + Ext.onReady = function(fn, scope, options) { + Loader.onReady(fn, scope, true, options); + }; + + Class.registerPreprocessor('loader', function(cls, data, hooks, continueFn) { + var me = this, + dependencies = [], + className = Manager.getName(cls), + i, j, ln, subLn, value, propertyName, propertyValue; + + /* + 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.mixin.Observable'; + } + } + which will later be transformed into: + { + extend: Ext.MyClass, + requires: [Ext.some.OtherClass], + mixins: { + observable: Ext.mixin.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 if (typeof propertyValue != 'function') { + for (j in propertyValue) { + if (propertyValue.hasOwnProperty(j)) { + value = propertyValue[j]; + + if (typeof value == 'string') { + dependencies.push(value); + } + } + } + } + } + } + + if (dependencies.length === 0) { + 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; + // + if (!Loader.requiredByMap) Loader.requiredByMap = {}; + Ext.Array.each(dependencies, function(dependency){ + if (!Loader.requiredByMap[dependency]) Loader.requiredByMap[dependency] = []; + Loader.requiredByMap[dependency].push(className); + }); + // + detectDeadlock = function(cls) { + deadlockPath.push(cls); + + if (requiresMap[cls]) { + if (Ext.Array.contains(requiresMap[cls], className)) { + throw new Error("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 if (typeof propertyValue != 'function') { + 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, hooks); + }); + + return false; + }, true, 'after', 'className'); + + // + /** + * @cfg {String[]} uses + * @member Ext.Class + * List of optional classes to load together with this class. These aren't necessarily loaded before + * this class is created, but are guaranteed to be available before Ext.onReady listeners are + * invoked + */ + Manager.registerPostprocessor('uses', function(name, cls, data) { + var uses = arrayFrom(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.onCreated(function(className) { + this.historyPush(className); + }, Loader); + // + +})(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias, + Ext.Function.pass, Ext.Array.from, Ext.Array.erase, Ext.Array.include); + +// initalize the default path of the framework +// trimmed down version of sench-touch-debug-suffix.js +// with alias / alternates removed, as those are handled separately by +// compiler-generated metadata +(function() { + var scripts = document.getElementsByTagName('script'), + currentScript = scripts[scripts.length - 1], + src = currentScript.src, + path = src.substring(0, src.lastIndexOf('/') + 1), + Loader = Ext.Loader; + + // + // if we're running in dev mode out of the repo src tree, then this + // file will potentially be loaded from the touch/src/core/class folder + // so we'll need to adjust for that + if(src.indexOf("src/core/class/") != -1) { + path = path + "../../../"; + } + // + + + Loader.setConfig({ + enabled: true, + disableCaching: !/[?&](cache|breakpoint)/i.test(location.search), + paths: { + 'Ext' : path + 'src' + } + }); + +})(); + +//@tag dom,core +//@define Ext.EventManager +//@define Ext.core.EventManager +//@require Ext.Loader + +/** + * @class Ext.EventManager + * + * This object has been deprecated in Sencha Touch 2.0.0. Please refer to the method documentation for specific alternatives. + * + * @deprecated 2.0.0 + * @singleton + * @private + */ + + +//@tag dom,core +//@define Ext-more +//@require Ext.EventManager + +/** + * @class Ext + * + * Ext is the global namespace for the whole Sencha Touch framework. Every class, function and configuration for the + * whole framework exists under this single global variable. The Ext singleton itself contains a set of useful helper + * functions (like {@link #apply}, {@link #min} and others), but most of the framework that you use day to day exists + * in specialized classes (for example {@link Ext.Panel}, {@link Ext.Carousel} and others). + * + * If you are new to Sencha Touch we recommend starting with the [Getting Started Guide][getting_started] to + * get a feel for how the framework operates. After that, use the more focused guides on subjects like panels, forms and data + * to broaden your understanding. The MVC guides take you through the process of building full applications using the + * framework, and detail how to deploy them to production. + * + * The functions listed below are mostly utility functions used internally by many of the classes shipped in the + * framework, but also often useful in your own apps. + * + * A method that is crucial to beginning your application is {@link #setup Ext.setup}. Please refer to it's documentation, or the + * [Getting Started Guide][getting_started] as a reference on beginning your application. + * + * Ext.setup({ + * onReady: function() { + * Ext.Viewport.add({ + * xtype: 'component', + * html: 'Hello world!' + * }); + * } + * }); + * + * [getting_started]: #!/guide/getting_started + */ +Ext.setVersion('touch', '2.1.0'); + +Ext.apply(Ext, { + /** + * The version of the framework + * @type String + */ + version: Ext.getVersion('touch'), + + /** + * @private + */ + idSeed: 0, + + /** + * Repaints the whole page. This fixes frequently encountered painting issues in mobile Safari. + */ + repaint: function() { + var mask = Ext.getBody().createChild({ + cls: Ext.baseCSSPrefix + 'mask ' + Ext.baseCSSPrefix + 'mask-transparent' + }); + setTimeout(function() { + mask.destroy(); + }, 0); + }, + + /** + * Generates unique ids. If the element already has an `id`, it is unchanged. + * @param {Mixed} el (optional) The element to generate an id for. + * @param {String} [prefix=ext-gen] (optional) The `id` prefix. + * @return {String} The generated `id`. + */ + id: function(el, prefix) { + if (el && el.id) { + return el.id; + } + + el = Ext.getDom(el) || {}; + + if (el === document || el === document.documentElement) { + el.id = 'ext-application'; + } + else if (el === document.body) { + el.id = 'ext-viewport'; + } + else if (el === window) { + el.id = 'ext-window'; + } + + el.id = el.id || ((prefix || 'ext-element-') + (++Ext.idSeed)); + + return el.id; + }, + + /** + * Returns the current document body as an {@link Ext.Element}. + * @return {Ext.Element} The document body. + */ + getBody: function() { + if (!Ext.documentBodyElement) { + if (!document.body) { + throw new Error("[Ext.getBody] document.body does not exist at this point"); + } + + Ext.documentBodyElement = Ext.get(document.body); + } + + return Ext.documentBodyElement; + }, + + /** + * Returns the current document head as an {@link Ext.Element}. + * @return {Ext.Element} The document head. + */ + getHead: function() { + if (!Ext.documentHeadElement) { + Ext.documentHeadElement = Ext.get(document.head || document.getElementsByTagName('head')[0]); + } + + return Ext.documentHeadElement; + }, + + /** + * Returns the current HTML document object as an {@link Ext.Element}. + * @return {Ext.Element} The document. + */ + getDoc: function() { + if (!Ext.documentElement) { + Ext.documentElement = Ext.get(document); + } + + return Ext.documentElement; + }, + + /** + * This is shorthand reference to {@link Ext.ComponentMgr#get}. + * Looks up an existing {@link Ext.Component Component} by {@link Ext.Component#getId id} + * @param {String} id The component {@link Ext.Component#getId id} + * @return {Ext.Component} The Component, `undefined` if not found, or `null` if a + * Class was found. + */ + getCmp: function(id) { + return Ext.ComponentMgr.get(id); + }, + + /** + * Copies a set of named properties from the source object to the destination object. + * + * Example: + * + * ImageComponent = Ext.extend(Ext.Component, { + * initComponent: function() { + * this.autoEl = { tag: 'img' }; + * MyComponent.superclass.initComponent.apply(this, arguments); + * this.initialBox = Ext.copyTo({}, this.initialConfig, 'x,y,width,height'); + * } + * }); + * + * Important note: To borrow class prototype methods, use {@link Ext.Base#borrow} instead. + * + * @param {Object} dest The destination object. + * @param {Object} source The source object. + * @param {String/String[]} names Either an Array of property names, or a comma-delimited list + * of property names to copy. + * @param {Boolean} [usePrototypeKeys=false] (optional) Pass `true` to copy keys off of the prototype as well as the instance. + * @return {Object} The modified object. + */ + copyTo : function(dest, source, names, usePrototypeKeys) { + if (typeof names == 'string') { + names = names.split(/[,;\s]/); + } + Ext.each (names, function(name) { + if (usePrototypeKeys || source.hasOwnProperty(name)) { + dest[name] = source[name]; + } + }, this); + return dest; + }, + + /** + * Attempts to destroy any objects passed to it by removing all event listeners, removing them from the + * DOM (if applicable) and calling their destroy functions (if available). This method is primarily + * intended for arguments of type {@link Ext.Element} and {@link Ext.Component}. + * Any number of elements and/or components can be passed into this function in a single + * call as separate arguments. + * @param {Mixed...} args An {@link Ext.Element}, {@link Ext.Component}, or an Array of either of these to destroy. + */ + destroy: function() { + var args = arguments, + ln = args.length, + i, item; + + for (i = 0; i < ln; i++) { + item = args[i]; + + if (item) { + if (Ext.isArray(item)) { + this.destroy.apply(this, item); + } + else if (Ext.isFunction(item.destroy)) { + item.destroy(); + } + } + } + }, + + /** + * Return the dom node for the passed String (id), dom node, or Ext.Element. + * Here are some examples: + * + * // gets dom node based on id + * var elDom = Ext.getDom('elId'); + * + * // gets dom node based on the dom node + * var elDom1 = Ext.getDom(elDom); + * + * // If we don't know if we are working with an + * // Ext.Element or a dom node use Ext.getDom + * function(el){ + * var dom = Ext.getDom(el); + * // do something with the dom node + * } + * + * __Note:__ the dom node to be found actually needs to exist (be rendered, etc) + * when this method is called to be successful. + * @param {Mixed} el + * @return {HTMLElement} + */ + getDom: function(el) { + if (!el || !document) { + return null; + } + + return el.dom ? el.dom : (typeof el == 'string' ? document.getElementById(el) : el); + }, + + /** + * Removes this element from the document, removes all DOM event listeners, and deletes the cache reference. + * All DOM event listeners are removed from this element. + * @param {HTMLElement} node The node to remove. + */ + removeNode: function(node) { + if (node && node.parentNode && node.tagName != 'BODY') { + Ext.get(node).clearListeners(); + node.parentNode.removeChild(node); + delete Ext.cache[node.id]; + } + }, + + /** + * @private + */ + defaultSetupConfig: { + eventPublishers: { + dom: { + xclass: 'Ext.event.publisher.Dom' + }, + touchGesture: { + xclass: 'Ext.event.publisher.TouchGesture', + recognizers: { + drag: { + xclass: 'Ext.event.recognizer.Drag' + }, + tap: { + xclass: 'Ext.event.recognizer.Tap' + }, + doubleTap: { + xclass: 'Ext.event.recognizer.DoubleTap' + }, + longPress: { + xclass: 'Ext.event.recognizer.LongPress' + }, + swipe: { + xclass: 'Ext.event.recognizer.HorizontalSwipe' + }, + pinch: { + xclass: 'Ext.event.recognizer.Pinch' + }, + rotate: { + xclass: 'Ext.event.recognizer.Rotate' + } + } + }, + componentDelegation: { + xclass: 'Ext.event.publisher.ComponentDelegation' + }, + componentPaint: { + xclass: 'Ext.event.publisher.ComponentPaint' + }, +// componentSize: { +// xclass: 'Ext.event.publisher.ComponentSize' +// }, + elementPaint: { + xclass: 'Ext.event.publisher.ElementPaint' + }, + elementSize: { + xclass: 'Ext.event.publisher.ElementSize' + } + // + ,seriesItemEvents: { + xclass: 'Ext.chart.series.ItemPublisher' + } + // + }, + + // + logger: { + enabled: true, + xclass: 'Ext.log.Logger', + minPriority: 'deprecate', + writers: { + console: { + xclass: 'Ext.log.writer.Console', + throwOnErrors: true, + formatter: { + xclass: 'Ext.log.formatter.Default' + } + } + } + }, + // + + animator: { + xclass: 'Ext.fx.Runner' + }, + + viewport: { + xclass: 'Ext.viewport.Viewport' + } + }, + + /** + * @private + */ + isSetup: false, + + /** + * This indicate the start timestamp of current cycle. + * It is only reliable during dom-event-initiated cycles and + * {@link Ext.draw.Animator} initiated cycles. + */ + frameStartTime: +new Date(), + + /** + * @private + */ + setupListeners: [], + + /** + * @private + */ + onSetup: function(fn, scope) { + if (Ext.isSetup) { + fn.call(scope); + } + else { + Ext.setupListeners.push({ + fn: fn, + scope: scope + }); + } + }, + + /** + * Ext.setup() is the entry-point to initialize a Sencha Touch application. Note that if your application makes + * use of MVC architecture, use {@link Ext#application} instead. + * + * This method accepts one single argument in object format. The most basic use of Ext.setup() is as follows: + * + * Ext.setup({ + * onReady: function() { + * // ... + * } + * }); + * + * This sets up the viewport, initializes the event system, instantiates a default animation runner, and a default + * logger (during development). When all of that is ready, it invokes the callback function given to the `onReady` key. + * + * The default scope (`this`) of `onReady` is the main viewport. By default the viewport instance is stored in + * {@link Ext.Viewport}. For example, this snippet adds a 'Hello World' button that is centered on the screen: + * + * Ext.setup({ + * onReady: function() { + * this.add({ + * xtype: 'button', + * centered: true, + * text: 'Hello world!' + * }); // Equivalent to Ext.Viewport.add(...) + * } + * }); + * + * @param {Object} config An object with the following config options: + * + * @param {Function} config.onReady + * A function to be called when the application is ready. Your application logic should be here. + * + * @param {Object} config.viewport + * A custom config object to be used when creating the global {@link Ext.Viewport} instance. Please refer to the + * {@link Ext.Viewport} documentation for more information. + * + * Ext.setup({ + * viewport: { + * width: 500, + * height: 500 + * }, + * onReady: function() { + * // ... + * } + * }); + * + * @param {String/Object} config.icon + * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed + * when the application is added to the device's Home Screen. + * + * Ext.setup({ + * icon: { + * 57: 'resources/icons/Icon.png', + * 72: 'resources/icons/Icon~ipad.png', + * 114: 'resources/icons/Icon@2x.png', + * 144: 'resources/icons/Icon~ipad@2x.png' + * }, + * onReady: function() { + * // ... + * } + * }); + * + * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57 + * icon image. Here is the breakdown of each dimension and its device target: + * + * - 57: Non-retina iPhone, iPod touch, and all Android devices + * - 72: Retina iPhone and iPod touch + * - 114: Non-retina iPad (first and second generation) + * - 144: Retina iPad (third generation) + * + * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively. + * + * It is highly recommended that you provide all these different sizes to accommodate a full range of + * devices currently available. However if you only have one icon in one size, make it 57x57 in size and + * specify it as a string value. This same icon will be used on all supported devices. + * + * Ext.setup({ + * icon: 'resources/icons/Icon.png', + * onReady: function() { + * // ... + * } + * }); + * + * @param {Object} config.startupImage + * Specifies a set of URLs to the application startup images for different device form factors. This image is + * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies + * to iOS devices. + * + * Ext.setup({ + * startupImage: { + * '320x460': 'resources/startup/320x460.jpg', + * '640x920': 'resources/startup/640x920.png', + * '640x1096': 'resources/startup/640x1096.png', + * '768x1004': 'resources/startup/768x1004.png', + * '748x1024': 'resources/startup/748x1024.png', + * '1536x2008': 'resources/startup/1536x2008.png', + * '1496x2048': 'resources/startup/1496x2048.png' + * }, + * onReady: function() { + * // ... + * } + * }); + * + * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image. + * Here is the breakdown of each dimension and its device target: + * + * - 320x460: Non-retina iPhone, iPod touch, and all Android devices + * - 640x920: Retina iPhone and iPod touch + * - 640x1096: iPhone 5 and iPod touch (fifth generation) + * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation + * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation + * - 1536x2008: Retina iPad (third generation) in portrait orientation + * - 1496x2048: Retina iPad (third generation) in landscape orientation + * + * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify + * a valid image for a certain device, nothing will be displayed while the application is being launched on that device. + * + * @param {Boolean} isIconPrecomposed + * True to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently + * only applies to iOS devices. + * + * @param {String} statusBarStyle + * The style of status bar to be shown on applications added to the iOS home screen. Valid options are: + * + * * `default` + * * `black` + * * `black-translucent` + * + * @param {String[]} config.requires + * An array of required classes for your application which will be automatically loaded before `onReady` is invoked. + * Please refer to {@link Ext.Loader} and {@link Ext.Loader#require} for more information. + * + * Ext.setup({ + * requires: ['Ext.Button', 'Ext.tab.Panel'], + * onReady: function() { + * // ... + * } + * }); + * + * @param {Object} config.eventPublishers + * Sencha Touch, by default, includes various {@link Ext.event.recognizer.Recognizer} subclasses to recognize events fired + * in your application. The list of default recognizers can be found in the documentation for + * {@link Ext.event.recognizer.Recognizer}. + * + * To change the default recognizers, you can use the following syntax: + * + * Ext.setup({ + * eventPublishers: { + * touchGesture: { + * recognizers: { + * swipe: { + * // this will include both vertical and horizontal swipe recognizers + * xclass: 'Ext.event.recognizer.Swipe' + * } + * } + * } + * }, + * onReady: function() { + * // ... + * } + * }); + * + * You can also disable recognizers using this syntax: + * + * Ext.setup({ + * eventPublishers: { + * touchGesture: { + * recognizers: { + * swipe: null, + * pinch: null, + * rotate: null + * } + * } + * }, + * onReady: function() { + * // ... + * } + * }); + */ + setup: function(config) { + var defaultSetupConfig = Ext.defaultSetupConfig, + emptyFn = Ext.emptyFn, + onReady = config.onReady || emptyFn, + onUpdated = config.onUpdated || emptyFn, + scope = config.scope, + requires = Ext.Array.from(config.requires), + extOnReady = Ext.onReady, + head = Ext.getHead(), + callback, viewport, precomposed; + + Ext.setup = function() { + throw new Error("Ext.setup has already been called before"); + }; + + delete config.requires; + delete config.onReady; + delete config.onUpdated; + delete config.scope; + + Ext.require(['Ext.event.Dispatcher']); + + callback = function() { + var listeners = Ext.setupListeners, + ln = listeners.length, + i, listener; + + delete Ext.setupListeners; + Ext.isSetup = true; + + for (i = 0; i < ln; i++) { + listener = listeners[i]; + listener.fn.call(listener.scope); + } + + Ext.onReady = extOnReady; + Ext.onReady(onReady, scope); + }; + + Ext.onUpdated = onUpdated; + Ext.onReady = function(fn, scope) { + var origin = onReady; + + onReady = function() { + origin(); + Ext.onReady(fn, scope); + }; + }; + + config = Ext.merge({}, defaultSetupConfig, config); + + Ext.onDocumentReady(function() { + Ext.factoryConfig(config, function(data) { + Ext.event.Dispatcher.getInstance().setPublishers(data.eventPublishers); + + if (data.logger) { + Ext.Logger = data.logger; + } + + if (data.animator) { + Ext.Animator = data.animator; + } + + if (data.viewport) { + Ext.Viewport = viewport = data.viewport; + + if (!scope) { + scope = viewport; + } + + Ext.require(requires, function() { + Ext.Viewport.on('ready', callback, null, {single: true}); + }); + } + else { + Ext.require(requires, callback); + } + }); + }); + + function addMeta(name, content) { + var meta = document.createElement('meta'); + meta.setAttribute('name', name); + meta.setAttribute('content', content); + head.append(meta); + } + + function addIcon(href, sizes, precomposed) { + var link = document.createElement('link'); + link.setAttribute('rel', 'apple-touch-icon' + (precomposed ? '-precomposed' : '')); + link.setAttribute('href', href); + if (sizes) { + link.setAttribute('sizes', sizes); + } + head.append(link); + } + + function addStartupImage(href, media) { + var link = document.createElement('link'); + link.setAttribute('rel', 'apple-touch-startup-image'); + link.setAttribute('href', href); + if (media) { + link.setAttribute('media', media); + } + head.append(link); + } + + var icon = config.icon, + isIconPrecomposed = Boolean(config.isIconPrecomposed), + startupImage = config.startupImage || {}, + statusBarStyle = config.statusBarStyle, + devicePixelRatio = window.devicePixelRatio || 1; + + if (navigator.standalone) { + addMeta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'); + } + else { + addMeta('viewport', 'initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0'); + } + addMeta('apple-mobile-web-app-capable', 'yes'); + addMeta('apple-touch-fullscreen', 'yes'); + + // status bar style + if (statusBarStyle) { + addMeta('apple-mobile-web-app-status-bar-style', statusBarStyle); + } + + if (Ext.isString(icon)) { + icon = { + 57: icon, + 72: icon, + 114: icon, + 144: icon + }; + } + else if (!icon) { + icon = {}; + } + + + if (Ext.os.is.iPad) { + if (devicePixelRatio >= 2) { + // Retina iPad - Landscape + if ('1496x2048' in startupImage) { + addStartupImage(startupImage['1496x2048'], '(orientation: landscape)'); + } + // Retina iPad - Portrait + if ('1536x2008' in startupImage) { + addStartupImage(startupImage['1536x2008'], '(orientation: portrait)'); + } + + // Retina iPad + if ('144' in icon) { + addIcon(icon['144'], '144x144', isIconPrecomposed); + } + } + else { + // Non-Retina iPad - Landscape + if ('748x1024' in startupImage) { + addStartupImage(startupImage['748x1024'], '(orientation: landscape)'); + } + // Non-Retina iPad - Portrait + if ('768x1004' in startupImage) { + addStartupImage(startupImage['768x1004'], '(orientation: portrait)'); + } + + // Non-Retina iPad + if ('72' in icon) { + addIcon(icon['72'], '72x72', isIconPrecomposed); + } + } + } + else { + // Retina iPhone, iPod touch with iOS version >= 4.3 + if (devicePixelRatio >= 2 && Ext.os.version.gtEq('4.3')) { + if (Ext.os.is.iPhone5) { + addStartupImage(startupImage['640x1096']); + } else { + addStartupImage(startupImage['640x920']); + } + + // Retina iPhone and iPod touch + if ('114' in icon) { + addIcon(icon['114'], '114x114', isIconPrecomposed); + } + } + else { + addStartupImage(startupImage['320x460']); + + // Non-Retina iPhone, iPod touch, and Android devices + if ('57' in icon) { + addIcon(icon['57'], null, isIconPrecomposed); + } + } + } + }, + + /** + * @member Ext + * @method application + * + * Loads Ext.app.Application class and starts it up with given configuration after the page is ready. + * + * Ext.application({ + * launch: function() { + * alert('Application launched!'); + * } + * }); + * + * See {@link Ext.app.Application} for details. + * + * @param {Object} config An object with the following config options: + * + * @param {Function} config.launch + * A function to be called when the application is ready. Your application logic should be here. Please see {@link Ext.app.Application} + * for details. + * + * @param {Object} config.viewport + * An object to be used when creating the global {@link Ext.Viewport} instance. Please refer to the {@link Ext.Viewport} + * documentation for more information. + * + * Ext.application({ + * viewport: { + * layout: 'vbox' + * }, + * launch: function() { + * Ext.Viewport.add({ + * flex: 1, + * html: 'top (flex: 1)' + * }); + * + * Ext.Viewport.add({ + * flex: 4, + * html: 'bottom (flex: 4)' + * }); + * } + * }); + * + * @param {String/Object} config.icon + * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed + * when the application is added to the device's Home Screen. + * + * Ext.application({ + * icon: { + * 57: 'resources/icons/Icon.png', + * 72: 'resources/icons/Icon~ipad.png', + * 114: 'resources/icons/Icon@2x.png', + * 144: 'resources/icons/Icon~ipad@2x.png' + * }, + * launch: function() { + * // ... + * } + * }); + * + * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57 + * icon image. Here is the breakdown of each dimension and its device target: + * + * - 57: Non-retina iPhone, iPod touch, and all Android devices + * - 72: Retina iPhone and iPod touch + * - 114: Non-retina iPad (first and second generation) + * - 144: Retina iPad (third generation) + * + * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively. + * + * It is highly recommended that you provide all these different sizes to accommodate a full range of + * devices currently available. However if you only have one icon in one size, make it 57x57 in size and + * specify it as a string value. This same icon will be used on all supported devices. + * + * Ext.setup({ + * icon: 'resources/icons/Icon.png', + * onReady: function() { + * // ... + * } + * }); + * + * @param {Object} config.startupImage + * Specifies a set of URLs to the application startup images for different device form factors. This image is + * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies + * to iOS devices. + * + * Ext.application({ + * startupImage: { + * '320x460': 'resources/startup/320x460.jpg', + * '640x920': 'resources/startup/640x920.png', + * '640x1096': 'resources/startup/640x1096.png', + * '768x1004': 'resources/startup/768x1004.png', + * '748x1024': 'resources/startup/748x1024.png', + * '1536x2008': 'resources/startup/1536x2008.png', + * '1496x2048': 'resources/startup/1496x2048.png' + * }, + * launch: function() { + * // ... + * } + * }); + * + * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image. + * Here is the breakdown of each dimension and its device target: + * + * - 320x460: Non-retina iPhone, iPod touch, and all Android devices + * - 640x920: Retina iPhone and iPod touch + * - 640x1096: iPhone 5 and iPod touch (fifth generation) + * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation + * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation + * - 1536x2008: Retina iPad (third generation) in portrait orientation + * - 1496x2048: Retina iPad (third generation) in landscape orientation + * + * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify + * a valid image for a certain device, nothing will be displayed while the application is being launched on that device. + * + * @param {Boolean} config.isIconPrecomposed + * True to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently + * only applies to iOS devices. + * + * @param {String} config.statusBarStyle + * The style of status bar to be shown on applications added to the iOS home screen. Valid options are: + * + * * `default` + * * `black` + * * `black-translucent` + * + * @param {String[]} config.requires + * An array of required classes for your application which will be automatically loaded if {@link Ext.Loader#enabled} is set + * to `true`. Please refer to {@link Ext.Loader} and {@link Ext.Loader#require} for more information. + * + * Ext.application({ + * requires: ['Ext.Button', 'Ext.tab.Panel'], + * launch: function() { + * // ... + * } + * }); + * + * @param {Object} config.eventPublishers + * Sencha Touch, by default, includes various {@link Ext.event.recognizer.Recognizer} subclasses to recognize events fired + * in your application. The list of default recognizers can be found in the documentation for {@link Ext.event.recognizer.Recognizer}. + * + * To change the default recognizers, you can use the following syntax: + * + * Ext.application({ + * eventPublishers: { + * touchGesture: { + * recognizers: { + * swipe: { + * // this will include both vertical and horizontal swipe recognizers + * xclass: 'Ext.event.recognizer.Swipe' + * } + * } + * } + * }, + * launch: function() { + * // ... + * } + * }); + * + * You can also disable recognizers using this syntax: + * + * Ext.application({ + * eventPublishers: { + * touchGesture: { + * recognizers: { + * swipe: null, + * pinch: null, + * rotate: null + * } + * } + * }, + * launch: function() { + * // ... + * } + * }); + */ + application: function(config) { + var appName = config.name, + onReady, scope, requires; + + if (!config) { + config = {}; + } + + if (!Ext.Loader.config.paths[appName]) { + Ext.Loader.setPath(appName, config.appFolder || 'app'); + } + + requires = Ext.Array.from(config.requires); + config.requires = ['Ext.app.Application']; + + onReady = config.onReady; + scope = config.scope; + + config.onReady = function() { + config.requires = requires; + new Ext.app.Application(config); + + if (onReady) { + onReady.call(scope); + } + }; + + Ext.setup(config); + }, + + /** + * @private + * @param config + * @param callback + * @member Ext + */ + factoryConfig: function(config, callback) { + var isSimpleObject = Ext.isSimpleObject(config); + + if (isSimpleObject && config.xclass) { + var className = config.xclass; + + delete config.xclass; + + Ext.require(className, function() { + Ext.factoryConfig(config, function(cfg) { + callback(Ext.create(className, cfg)); + }); + }); + + return; + } + + var isArray = Ext.isArray(config), + keys = [], + key, value, i, ln; + + if (isSimpleObject || isArray) { + if (isSimpleObject) { + for (key in config) { + if (config.hasOwnProperty(key)) { + value = config[key]; + if (Ext.isSimpleObject(value) || Ext.isArray(value)) { + keys.push(key); + } + } + } + } + else { + for (i = 0,ln = config.length; i < ln; i++) { + value = config[i]; + + if (Ext.isSimpleObject(value) || Ext.isArray(value)) { + keys.push(i); + } + } + } + + i = 0; + ln = keys.length; + + if (ln === 0) { + callback(config); + return; + } + + function fn(value) { + config[key] = value; + i++; + factory(); + } + + function factory() { + if (i >= ln) { + callback(config); + return; + } + + key = keys[i]; + value = config[key]; + + Ext.factoryConfig(value, fn); + } + + factory(); + return; + } + + callback(config); + }, + + /** + * A global factory method to instantiate a class from a config object. For example, these two calls are equivalent: + * + * Ext.factory({ text: 'My Button' }, 'Ext.Button'); + * Ext.create('Ext.Button', { text: 'My Button' }); + * + * If an existing instance is also specified, it will be updated with the supplied config object. This is useful + * if you need to either create or update an object, depending on if an instance already exists. For example: + * + * var button; + * button = Ext.factory({ text: 'New Button' }, 'Ext.Button', button); // Button created + * button = Ext.factory({ text: 'Updated Button' }, 'Ext.Button', button); // Button updated + * + * @param {Object} config The config object to instantiate or update an instance with. + * @param {String} classReference The class to instantiate from. + * @param {Object} [instance] The instance to update. + * @param [aliasNamespace] + * @member Ext + */ + factory: function(config, classReference, instance, aliasNamespace) { + var manager = Ext.ClassManager, + newInstance; + + // If config is falsy or a valid instance, destroy the current instance + // (if it exists) and replace with the new one + if (!config || config.isInstance) { + if (instance && instance !== config) { + instance.destroy(); + } + + return config; + } + + if (aliasNamespace) { + // If config is a string value, treat it as an alias + if (typeof config == 'string') { + return manager.instantiateByAlias(aliasNamespace + '.' + config); + } + // Same if 'type' is given in config + else if (Ext.isObject(config) && 'type' in config) { + return manager.instantiateByAlias(aliasNamespace + '.' + config.type, config); + } + } + + if (config === true) { + return instance || manager.instantiate(classReference); + } + + // + if (!Ext.isObject(config)) { + Ext.Logger.error("Invalid config, must be a valid config object"); + } + // + + if ('xtype' in config) { + newInstance = manager.instantiateByAlias('widget.' + config.xtype, config); + } + else if ('xclass' in config) { + newInstance = manager.instantiate(config.xclass, config); + } + + if (newInstance) { + if (instance) { + instance.destroy(); + } + + return newInstance; + } + + if (instance) { + return instance.setConfig(config); + } + + return manager.instantiate(classReference, config); + }, + + /** + * @private + * @member Ext + */ + deprecateClassMember: function(cls, oldName, newName, message) { + return this.deprecateProperty(cls.prototype, oldName, newName, message); + }, + + /** + * @private + * @member Ext + */ + deprecateClassMembers: function(cls, members) { + var prototype = cls.prototype, + oldName, newName; + + for (oldName in members) { + if (members.hasOwnProperty(oldName)) { + newName = members[oldName]; + + this.deprecateProperty(prototype, oldName, newName); + } + } + }, + + /** + * @private + * @member Ext + */ + deprecateProperty: function(object, oldName, newName, message) { + if (!message) { + message = "'" + oldName + "' is deprecated"; + } + if (newName) { + message += ", please use '" + newName + "' instead"; + } + + if (newName) { + Ext.Object.defineProperty(object, oldName, { + get: function() { + // + Ext.Logger.deprecate(message, 1); + // + return this[newName]; + }, + set: function(value) { + // + Ext.Logger.deprecate(message, 1); + // + + this[newName] = value; + }, + configurable: true + }); + } + }, + + /** + * @private + * @member Ext + */ + deprecatePropertyValue: function(object, name, value, message) { + Ext.Object.defineProperty(object, name, { + get: function() { + // + Ext.Logger.deprecate(message, 1); + // + return value; + }, + configurable: true + }); + }, + + /** + * @private + * @member Ext + */ + deprecateMethod: function(object, name, method, message) { + object[name] = function() { + // + Ext.Logger.deprecate(message, 2); + // + if (method) { + return method.apply(this, arguments); + } + }; + }, + + /** + * @private + * @member Ext + */ + deprecateClassMethod: function(cls, name, method, message) { + if (typeof name != 'string') { + var from, to; + + for (from in name) { + if (name.hasOwnProperty(from)) { + to = name[from]; + Ext.deprecateClassMethod(cls, from, to); + } + } + return; + } + + var isLateBinding = typeof method == 'string', + member; + + if (!message) { + message = "'" + name + "()' is deprecated, please use '" + (isLateBinding ? method : method.name) + + "()' instead"; + } + + if (isLateBinding) { + member = function() { + // + Ext.Logger.deprecate(message, this); + // + + return this[method].apply(this, arguments); + }; + } + else { + member = function() { + // + Ext.Logger.deprecate(message, this); + // + + return method.apply(this, arguments); + }; + } + + if (name in cls.prototype) { + Ext.Object.defineProperty(cls.prototype, name, { + value: null, + writable: true, + configurable: true + }); + } + + cls.addMember(name, member); + }, + + // + /** + * Useful snippet to show an exact, narrowed-down list of top-level Components that are not yet destroyed. + * @private + */ + showLeaks: function() { + var map = Ext.ComponentManager.all.map, + leaks = [], + parent; + + Ext.Object.each(map, function(id, component) { + while ((parent = component.getParent()) && map.hasOwnProperty(parent.getId())) { + component = parent; + } + + if (leaks.indexOf(component) === -1) { + leaks.push(component); + } + }); + + console.log(leaks); + }, + // + + /** + * True when the document is fully initialized and ready for action + * @type Boolean + * @member Ext + * @private + */ + isReady : false, + + /** + * @private + * @member Ext + */ + readyListeners: [], + + /** + * @private + * @member Ext + */ + triggerReady: function() { + var listeners = Ext.readyListeners, + i, ln, listener; + + if (!Ext.isReady) { + Ext.isReady = true; + + for (i = 0,ln = listeners.length; i < ln; i++) { + listener = listeners[i]; + listener.fn.call(listener.scope); + } + delete Ext.readyListeners; + } + }, + + /** + * @private + * @member Ext + */ + onDocumentReady: function(fn, scope) { + if (Ext.isReady) { + fn.call(scope); + } + else { + var triggerFn = Ext.triggerReady; + + Ext.readyListeners.push({ + fn: fn, + scope: scope + }); + + if (Ext.browser.is.PhoneGap && !Ext.os.is.Desktop) { + if (!Ext.readyListenerAttached) { + Ext.readyListenerAttached = true; + document.addEventListener('deviceready', triggerFn, false); + } + } + else { + if (document.readyState.match(/interactive|complete|loaded/) !== null) { + triggerFn(); + } + else if (!Ext.readyListenerAttached) { + Ext.readyListenerAttached = true; + window.addEventListener('DOMContentLoaded', triggerFn, false); + } + } + } + }, + + /** + * Calls function after specified delay, or right away when delay == 0. + * @param {Function} callback The callback to execute. + * @param {Object} scope (optional) The scope to execute in. + * @param {Array} args (optional) The arguments to pass to the function. + * @param {Number} delay (optional) Pass a number to delay the call by a number of milliseconds. + * @member Ext + */ + callback: function(callback, scope, args, delay) { + if (Ext.isFunction(callback)) { + args = args || []; + scope = scope || window; + if (delay) { + Ext.defer(callback, delay, scope, args); + } else { + callback.apply(scope, args); + } + } + } +}); + +// +Ext.Object.defineProperty(Ext, 'Msg', { + get: function() { + Ext.Logger.error("Using Ext.Msg without requiring Ext.MessageBox"); + return null; + }, + set: function(value) { + Ext.Object.defineProperty(Ext, 'Msg', { + value: value + }); + return value; + }, + configurable: true +}); +// + + +//@tag dom,core +//@require Ext-more + +/** + * Provides information about browser. + * + * Should not be manually instantiated unless for unit-testing. + * Access the global instance stored in {@link Ext.browser} instead. + * @private + */ +Ext.define('Ext.env.Browser', { + requires: ['Ext.Version'], + + statics: { + browserNames: { + ie: 'IE', + firefox: 'Firefox', + safari: 'Safari', + chrome: 'Chrome', + opera: 'Opera', + dolfin: 'Dolfin', + webosbrowser: 'webOSBrowser', + chromeMobile: 'ChromeMobile', + silk: 'Silk', + other: 'Other' + }, + engineNames: { + webkit: 'WebKit', + gecko: 'Gecko', + presto: 'Presto', + trident: 'Trident', + other: 'Other' + }, + enginePrefixes: { + webkit: 'AppleWebKit/', + gecko: 'Gecko/', + presto: 'Presto/', + trident: 'Trident/' + }, + browserPrefixes: { + ie: 'MSIE ', + firefox: 'Firefox/', + chrome: 'Chrome/', + safari: 'Version/', + opera: 'Opera/', + dolfin: 'Dolfin/', + webosbrowser: 'wOSBrowser/', + chromeMobile: 'CrMo/', + silk: 'Silk/' + } + }, + + styleDashPrefixes: { + WebKit: '-webkit-', + Gecko: '-moz-', + Trident: '-ms-', + Presto: '-o-', + Other: '' + }, + + stylePrefixes: { + WebKit: 'Webkit', + Gecko: 'Moz', + Trident: 'ms', + Presto: 'O', + Other: '' + }, + + propertyPrefixes: { + WebKit: 'webkit', + Gecko: 'moz', + Trident: 'ms', + Presto: 'o', + Other: '' + }, + + // scope: Ext.env.Browser.prototype + + /** + * A "hybrid" property, can be either accessed as a method call, for example: + * + * if (Ext.browser.is('IE')) { + * // ... + * } + * + * Or as an object with Boolean properties, for example: + * + * if (Ext.browser.is.IE) { + * // ... + * } + * + * Versions can be conveniently checked as well. For example: + * + * if (Ext.browser.is.IE6) { + * // Equivalent to (Ext.browser.is.IE && Ext.browser.version.equals(6)) + * } + * + * __Note:__ Only {@link Ext.Version#getMajor major component} and {@link Ext.Version#getShortVersion simplified} + * value of the version are available via direct property checking. + * + * Supported values are: + * + * - IE + * - Firefox + * - Safari + * - Chrome + * - Opera + * - WebKit + * - Gecko + * - Presto + * - Trident + * - WebView + * - Other + * + * @param {String} value The OS name to check. + * @return {Boolean} + */ + is: Ext.emptyFn, + + /** + * The full name of the current browser. + * Possible values are: + * + * - IE + * - Firefox + * - Safari + * - Chrome + * - Opera + * - Other + * @type String + * @readonly + */ + name: null, + + /** + * Refer to {@link Ext.Version}. + * @type Ext.Version + * @readonly + */ + version: null, + + /** + * The full name of the current browser's engine. + * Possible values are: + * + * - WebKit + * - Gecko + * - Presto + * - Trident + * - Other + * @type String + * @readonly + */ + engineName: null, + + /** + * Refer to {@link Ext.Version}. + * @type Ext.Version + * @readonly + */ + engineVersion: null, + + setFlag: function(name, value) { + if (typeof value == 'undefined') { + value = true; + } + + this.is[name] = value; + this.is[name.toLowerCase()] = value; + + return this; + }, + + constructor: function(userAgent) { + /** + * @property {String} + * Browser User Agent string. + */ + this.userAgent = userAgent; + + is = this.is = function(name) { + return is[name] === true; + }; + + var statics = this.statics(), + browserMatch = userAgent.match(new RegExp('((?:' + Ext.Object.getValues(statics.browserPrefixes).join(')|(?:') + '))([\\w\\._]+)')), + engineMatch = userAgent.match(new RegExp('((?:' + Ext.Object.getValues(statics.enginePrefixes).join(')|(?:') + '))([\\w\\._]+)')), + browserNames = statics.browserNames, + browserName = browserNames.other, + engineNames = statics.engineNames, + engineName = engineNames.other, + browserVersion = '', + engineVersion = '', + isWebView = false, + is, i, name; + + if (browserMatch) { + browserName = browserNames[Ext.Object.getKey(statics.browserPrefixes, browserMatch[1])]; + + browserVersion = new Ext.Version(browserMatch[2]); + } + + if (engineMatch) { + engineName = engineNames[Ext.Object.getKey(statics.enginePrefixes, engineMatch[1])]; + engineVersion = new Ext.Version(engineMatch[2]); + } + + // Facebook changes the userAgent when you view a website within their iOS app. For some reason, the strip out information + // about the browser, so we have to detect that and fake it... + if (userAgent.match(/FB/) && browserName == "Other") { + browserName = browserNames.safari; + engineName = engineNames.webkit; + } + + if (userAgent.match(/Android.*Chrome/g)) { + browserName = 'ChromeMobile'; + } + + Ext.apply(this, { + engineName: engineName, + engineVersion: engineVersion, + name: browserName, + version: browserVersion + }); + + this.setFlag(browserName); + + if (browserVersion) { + this.setFlag(browserName + (browserVersion.getMajor() || '')); + this.setFlag(browserName + browserVersion.getShortVersion()); + } + + for (i in browserNames) { + if (browserNames.hasOwnProperty(i)) { + name = browserNames[i]; + + this.setFlag(name, browserName === name); + } + } + + this.setFlag(name); + + if (engineVersion) { + this.setFlag(engineName + (engineVersion.getMajor() || '')); + this.setFlag(engineName + engineVersion.getShortVersion()); + } + + for (i in engineNames) { + if (engineNames.hasOwnProperty(i)) { + name = engineNames[i]; + + this.setFlag(name, engineName === name); + } + } + + this.setFlag('Standalone', !!navigator.standalone); + + if (typeof window.PhoneGap != 'undefined' || typeof window.Cordova != 'undefined' || typeof window.cordova != 'undefined') { + isWebView = true; + this.setFlag('PhoneGap'); + } + else if (!!window.isNK) { + isWebView = true; + this.setFlag('Sencha'); + } + + // Check if running in UIWebView + if (/(iPhone|iPod|iPad).*AppleWebKit(?!.*Safari)(?!.*FBAN)/i.test(userAgent)) { + isWebView = true; + } + + // Flag to check if it we are in the WebView + this.setFlag('WebView', isWebView); + + /** + * @property {Boolean} + * `true` if browser is using strict mode. + */ + this.isStrict = document.compatMode == "CSS1Compat"; + + /** + * @property {Boolean} + * `true` if page is running over SSL. + */ + this.isSecure = /^https/i.test(window.location.protocol); + + return this; + }, + + getStyleDashPrefix: function() { + return this.styleDashPrefixes[this.engineName]; + }, + + getStylePrefix: function() { + return this.stylePrefixes[this.engineName]; + }, + + getVendorProperyName: function(name) { + var prefix = this.propertyPrefixes[this.engineName]; + + if (prefix.length > 0) { + return prefix + Ext.String.capitalize(name); + } + + return name; + } + +}, function() { + /** + * @class Ext.browser + * @extends Ext.env.Browser + * @singleton + * Provides useful information about the current browser. + * + * Example: + * + * if (Ext.browser.is.IE) { + * // IE specific code here + * } + * + * if (Ext.browser.is.WebKit) { + * // WebKit specific code here + * } + * + * console.log("Version " + Ext.browser.version); + * + * For a full list of supported values, refer to {@link #is} property/method. + * + * @aside guide environment_package + */ + var browserEnv = Ext.browser = new this(Ext.global.navigator.userAgent); + +}); + +//@tag dom,core +//@require Ext.env.Browser + +/** + * Provides information about operating system environment. + * + * Should not be manually instantiated unless for unit-testing. + * Access the global instance stored in {@link Ext.os} instead. + * @private + */ +Ext.define('Ext.env.OS', { + + requires: ['Ext.Version'], + + statics: { + names: { + ios: 'iOS', + android: 'Android', + webos: 'webOS', + blackberry: 'BlackBerry', + rimTablet: 'RIMTablet', + mac: 'MacOS', + win: 'Windows', + linux: 'Linux', + bada: 'Bada', + other: 'Other' + }, + prefixes: { + ios: 'i(?:Pad|Phone|Pod)(?:.*)CPU(?: iPhone)? OS ', + android: '(Android |HTC_|Silk/)', // Some HTC devices ship with an OSX userAgent by default, + // so we need to add a direct check for HTC_ + blackberry: 'BlackBerry(?:.*)Version\/', + rimTablet: 'RIM Tablet OS ', + webos: '(?:webOS|hpwOS)\/', + bada: 'Bada\/' + } + }, + + /** + * A "hybrid" property, can be either accessed as a method call, i.e: + * + * if (Ext.os.is('Android')) { + * // ... + * } + * + * or as an object with boolean properties, i.e: + * + * if (Ext.os.is.Android) { + * // ... + * } + * + * Versions can be conveniently checked as well. For example: + * + * if (Ext.os.is.Android2) { + * // Equivalent to (Ext.os.is.Android && Ext.os.version.equals(2)) + * } + * + * if (Ext.os.is.iOS32) { + * // Equivalent to (Ext.os.is.iOS && Ext.os.version.equals(3.2)) + * } + * + * Note that only {@link Ext.Version#getMajor major component} and {@link Ext.Version#getShortVersion simplified} + * value of the version are available via direct property checking. Supported values are: + * + * - iOS + * - iPad + * - iPhone + * - iPhone5 (also true for 4in iPods). + * - iPod + * - Android + * - WebOS + * - BlackBerry + * - Bada + * - MacOS + * - Windows + * - Linux + * - Other + * @param {String} value The OS name to check. + * @return {Boolean} + */ + is: Ext.emptyFn, + + /** + * @property {String} [name=null] + * @readonly + * The full name of the current operating system. Possible values are: + * + * - iOS + * - Android + * - WebOS + * - BlackBerry, + * - MacOS + * - Windows + * - Linux + * - Other + */ + name: null, + + /** + * @property {Ext.Version} [version=null] + * Refer to {@link Ext.Version} + * @readonly + */ + version: null, + + setFlag: function(name, value) { + if (typeof value == 'undefined') { + value = true; + } + + this.is[name] = value; + this.is[name.toLowerCase()] = value; + + return this; + }, + + constructor: function(userAgent, platform) { + var statics = this.statics(), + names = statics.names, + prefixes = statics.prefixes, + name, + version = '', + i, prefix, match, item, is; + + is = this.is = function(name) { + return this.is[name] === true; + }; + + for (i in prefixes) { + if (prefixes.hasOwnProperty(i)) { + prefix = prefixes[i]; + + match = userAgent.match(new RegExp('(?:'+prefix+')([^\\s;]+)')); + + if (match) { + name = names[i]; + + // This is here because some HTC android devices show an OSX Snow Leopard userAgent by default. + // And the Kindle Fire doesn't have any indicator of Android as the OS in its User Agent + if (match[1] && (match[1] == "HTC_" || match[1] == "Silk/")) { + version = new Ext.Version("2.3"); + } else { + version = new Ext.Version(match[match.length - 1]); + } + + break; + } + } + } + + if (!name) { + name = names[(userAgent.toLowerCase().match(/mac|win|linux/) || ['other'])[0]]; + version = new Ext.Version(''); + } + + this.name = name; + this.version = version; + + if (platform) { + this.setFlag(platform.replace(/ simulator$/i, '')); + } + + this.setFlag(name); + + if (version) { + this.setFlag(name + (version.getMajor() || '')); + this.setFlag(name + version.getShortVersion()); + } + + for (i in names) { + if (names.hasOwnProperty(i)) { + item = names[i]; + + if (!is.hasOwnProperty(name)) { + this.setFlag(item, (name === item)); + } + } + } + + // Detect if the device is the iPhone 5. + if (this.name == "iOS" && window.screen.height == 568) { + this.setFlag('iPhone5'); + } + + return this; + } + +}, function() { + + var navigation = Ext.global.navigator, + userAgent = navigation.userAgent, + osEnv, osName, deviceType; + + + /** + * @class Ext.os + * @extends Ext.env.OS + * @singleton + * Provides useful information about the current operating system environment. + * + * Example: + * + * if (Ext.os.is.Windows) { + * // Windows specific code here + * } + * + * if (Ext.os.is.iOS) { + * // iPad, iPod, iPhone, etc. + * } + * + * console.log("Version " + Ext.os.version); + * + * For a full list of supported values, refer to the {@link #is} property/method. + * + * @aside guide environment_package + */ + Ext.os = osEnv = new this(userAgent, navigation.platform); + + osName = osEnv.name; + + var search = window.location.search.match(/deviceType=(Tablet|Phone)/), + nativeDeviceType = window.deviceType; + + // Override deviceType by adding a get variable of deviceType. NEEDED FOR DOCS APP. + // E.g: example/kitchen-sink.html?deviceType=Phone + if (search && search[1]) { + deviceType = search[1]; + } + else if (nativeDeviceType === 'iPhone') { + deviceType = 'Phone'; + } + else if (nativeDeviceType === 'iPad') { + deviceType = 'Tablet'; + } + else { + if (!osEnv.is.Android && !osEnv.is.iOS && /Windows|Linux|MacOS/.test(osName)) { + deviceType = 'Desktop'; + + // always set it to false when you are on a desktop + Ext.browser.is.WebView = false; + } + else if (osEnv.is.iPad || osEnv.is.Android3 || (osEnv.is.Android4 && userAgent.search(/mobile/i) == -1)) { + deviceType = 'Tablet'; + } + else { + deviceType = 'Phone'; + } + } + + /** + * @property {String} deviceType + * The generic type of the current device. + * + * Possible values: + * + * - Phone + * - Tablet + * - Desktop + * + * For testing purposes the deviceType can be overridden by adding + * a deviceType parameter to the URL of the page, like so: + * + * http://localhost/mypage.html?deviceType=Tablet + * + */ + osEnv.setFlag(deviceType, true); + osEnv.deviceType = deviceType; + + + /** + * @class Ext.is + * Used to detect if the current browser supports a certain feature, and the type of the current browser. + * @deprecated 2.0.0 + * Please refer to the {@link Ext.browser}, {@link Ext.os} and {@link Ext.feature} classes instead. + */ +}); + +//@tag dom,core + +/** + * Provides information about browser. + * + * Should not be manually instantiated unless for unit-testing. + * Access the global instance stored in {@link Ext.browser} instead. + * @private + */ +Ext.define('Ext.env.Feature', { + + requires: ['Ext.env.Browser', 'Ext.env.OS'], + + constructor: function() { + this.testElements = {}; + + this.has = function(name) { + return !!this.has[name]; + }; + + return this; + }, + + getTestElement: function(tag, createNew) { + if (tag === undefined) { + tag = 'div'; + } + else if (typeof tag !== 'string') { + return tag; + } + + if (createNew) { + return document.createElement(tag); + } + + if (!this.testElements[tag]) { + this.testElements[tag] = document.createElement(tag); + } + + return this.testElements[tag]; + }, + + isStyleSupported: function(name, tag) { + var elementStyle = this.getTestElement(tag).style, + cName = Ext.String.capitalize(name); + + if (typeof elementStyle[name] !== 'undefined' + || typeof elementStyle[Ext.browser.getStylePrefix(name) + cName] !== 'undefined') { + return true; + } + + return false; + }, + + isEventSupported: function(name, tag) { + if (tag === undefined) { + tag = window; + } + + var element = this.getTestElement(tag), + eventName = 'on' + name.toLowerCase(), + isSupported = (eventName in element); + + if (!isSupported) { + if (element.setAttribute && element.removeAttribute) { + element.setAttribute(eventName, ''); + isSupported = typeof element[eventName] === 'function'; + + if (typeof element[eventName] !== 'undefined') { + element[eventName] = undefined; + } + + element.removeAttribute(eventName); + } + } + + return isSupported; + }, + + getSupportedPropertyName: function(object, name) { + var vendorName = Ext.browser.getVendorProperyName(name); + + if (vendorName in object) { + return vendorName; + } + else if (name in object) { + return name; + } + + return null; + }, + + registerTest: Ext.Function.flexSetter(function(name, fn) { + this.has[name] = fn.call(this); + + return this; + }) + +}, function() { + + /** + * @class Ext.feature + * @extend Ext.env.Feature + * @singleton + * + * A simple class to verify if a browser feature exists or not on the current device. + * + * if (Ext.feature.has.Canvas) { + * // do some cool things with canvas here + * } + * + * See the {@link #has} property/method for details of the features that can be detected. + * + * @aside guide environment_package + */ + Ext.feature = new this; + + var has = Ext.feature.has; + + /** + * @method has + * @member Ext.feature + * Verifies if a browser feature exists or not on the current device. + * + * A "hybrid" property, can be either accessed as a method call, i.e: + * + * if (Ext.feature.has('Canvas')) { + * // ... + * } + * + * or as an object with boolean properties, i.e: + * + * if (Ext.feature.has.Canvas) { + * // ... + * } + * + * Possible properties/parameter values: + * + * - Canvas + * - Svg + * - Vml + * - Touch - supports touch events (`touchstart`). + * - Orientation - supports different orientations. + * - OrientationChange - supports the `orientationchange` event. + * - DeviceMotion - supports the `devicemotion` event. + * - Geolocation + * - SqlDatabase + * - WebSockets + * - Range - supports [DOM document fragments.][1] + * - CreateContextualFragment - supports HTML fragment parsing using [range.createContextualFragment()][2]. + * - History - supports history management with [history.pushState()][3]. + * - CssTransforms + * - Css3dTransforms + * - CssAnimations + * - CssTransitions + * - Audio - supports the `