/*
This file is part of Sencha Touch 2
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
Commercial Usage
Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @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, {
/**
* 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.
* @markdown
*/
iterate: function(object, fn, scope) {
if (Ext.isEmpty(object)) {
return;
}
if (scope === undefined) {
scope = object;
}
if (Ext.isIterable(object)) {
Ext.Array.each.call(Ext.Array, object, fn, scope);
}
else {
Ext.Object.each.call(Ext.Object, object, fn, scope);
}
}
});
Ext.apply(Ext, {
/**
* This method deprecated. Use {@link Ext#define Ext.define} instead.
* @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
* @markdown
* @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 (optional) true to allow zero length strings to qualify as non-empty (defaults to false)
* @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}
* @markdown
*/
typeOf: function(value) {
if (value === null) {
return 'null';
}
var type = typeof value;
if (type === 'undefined' || type === 'string' || type === 'number' || type === 'boolean') {
return type;
}
var typeToString = toString.call(value);
switch(typeToString) {
case '[object Array]':
return 'array';
case '[object Date]':
return 'date';
case '[object Boolean]':
return 'boolean';
case '[object Number]':
return 'number';
case '[object RegExp]':
return 'regexp';
}
if (type === 'function') {
return 'function';
}
if (type === 'object') {
if (value.nodeType !== undefined) {
if (value.nodeType === 3) {
return (/\S/).test(value.nodeValue) ? 'textnode' : 'whitespace';
}
else {
return 'element';
}
}
return 'object';
}
Ext.Error.raise({
sourceClass: 'Ext',
sourceMethod: 'typeOf',
msg: 'Failed to determine the type of the specified value "' + value + '". This is most likely a bug.'
});
},
/**
* Returns true if the passed value is empty, false otherwise. The value is deemed to be empty if it is either:
*
* - `null`
* - `undefined`
* - a zero-length array
* - a zero-length string (Unless the `allowEmptyString` parameter is set to `true`)
*
* @param {Object} value The value to test
* @param {Boolean} allowEmptyString (optional) true to allow empty strings (defaults to false)
* @return {Boolean}
* @markdown
*/
isEmpty: function(value, allowEmptyString) {
return (value === null) || (value === undefined) || (!allowEmptyString ? value === '' : false) || (Ext.isArray(value) && value.length === 0);
},
/**
* Returns true if the passed value is a JavaScript Array, false otherwise.
*
* @param {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 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: 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'
}
},
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,
/**
* @private
*/
setupListeners: [],
/**
* @private
*/
onSetup: function(fn, scope) {
if (Ext.isSetup) {
fn.call(scope);
}
else {
Ext.setupListeners.push({
fn: fn,
scope: scope
});
}
},
/**
* Ext.setup is used to launch a basic application. It handles creating an {@link Ext.Viewport} instance for you.
*
* Ext.setup({
* onReady: function() {
* Ext.Viewport.add({
* xtype: 'component',
* html: 'Hello world!'
* });
* }
* });
*
* @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. Please see the example above.
*
* @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.setup({
* viewport: {
* layout: 'vbox'
* },
* onReady: function() {
* Ext.Viewport.add({
* flex: 1,
* html: 'top (flex: 1)'
* });
*
* Ext.Viewport.add({
* flex: 4,
* html: 'bottom (flex: 4)'
* });
* }
* });
*
* @param {String/Object} config.icon
* A icon configuration for this application. This will only apply to iOS applications which are saved to the homescreen.
*
* You can either pass a string which will be applied to all different sizes:
*
* Ext.setup({
* icon: 'icon.png',
* onReady: function() {
* console.log('Launch...');
* }
* });
*
* Or an object which has a location for different sizes:
*
* Ext.setup({
* icon: {
* '57': 'icon57.png',
* '77': 'icon77.png',
* '114': 'icon114.png'
* },
* onReady: function() {
* console.log('Launch...');
* }
* });
*
* @param {String} config.icon.57 The icon to be used on non-retna display devices (iPhone 3GS and below).
* @param {String} config.icon.77 The icon to be used on the iPad.
* @param {String} config.icon.114 The icon to be used on retna display devices (iPhone 4 and above).
*
* @param {Boolean} glossOnIcon
* True to add a gloss effect to the icon.
*
* @param {String} phoneStartupScreen
* Sets the apple-touch-icon `` tag so your home screen application can have a startup screen on phones.
* Please look here for more information: http://developer.apple.com/library/IOs/#documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html
*
* @param {String} tabletStartupScreen
* Sets the apple-touch-icon `` tag so your home screen application can have a startup screen on tablets.
* Please look here for more information: http://developer.apple.com/library/IOs/#documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html
*
* @param {String} statusBarStyle
* The style of status bar to be shown on applications added to the iOS homescreen. 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.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 recognise events fired
* in your application. The list of default recognisers can be found in the documentation for {@link Ext.event.recognizer.Recognizer}.
*
* To change the default recognisers, you can use the following syntax:
*
* Ext.setup({
* eventPublishers: {
* touchGesture: {
* recognizers: {
* swipe: {
* //this will include both vertical and horizontal swipe recognisers
* 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,
onReady = config.onReady || Ext.emptyFn,
scope = config.scope,
requires = Ext.Array.from(config.requires),
extOnReady = Ext.onReady,
icon = config.icon,
callback, viewport, precomposed;
Ext.setup = function() {
throw new Error("Ext.setup has already been called before");
};
delete config.requires;
delete config.onReady;
delete config.scope;
Ext.require(['Ext.event.Dispatcher', 'Ext.MessageBox']);
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.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);
}
});
});
/*
* Note: previously we only added these icon meta tags to iOS devices but as Android 2.1+ reads the same tags
* we now add them if they're defined
*/
if (!document.body) {
var phoneIcon = config.phoneIcon,
tabletIcon = config.tabletIcon,
tabletStartupScreen = config.tabletStartupScreen,
statusBarStyle = config.statusBarStyle,
phoneStartupScreen = config.phoneStartupScreen,
isIpad = Ext.os.is.iPad;
// Inject meta viewport tag
document.write(
'');
document.write('');
document.write('');
//status bar style
if (Ext.isString(statusBarStyle)) {
document.write('');
}
//startup screens
if (tabletStartupScreen && isIpad) {
document.write('');
}
if (phoneStartupScreen && !isIpad) {
document.write('');
}
// icon
if (Ext.isString(icon) || Ext.isString(phoneIcon) || Ext.isString(tabletIcon)) {
icon = {
'57': phoneIcon || tabletIcon || icon,
'72': tabletIcon || phoneIcon || icon,
'114': phoneIcon || tabletIcon || icon
};
}
precomposed = (config.glossOnIcon === false) ? '-precomposed' : '';
if (icon) {
var icon72 = icon['72'],
icon57 = icon['57'],
icon114 = icon['114'],
iconString = '');
}
else if (!isIpad) {
if (icon57) {
document.write(iconString + precomposed + '" href="' + icon57 + '">');
}
if (icon114) {
document.write(iconString + precomposed + '" sizes="114x114" href="' + icon114 + '">');
}
}
}
}
},
/**
* @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
* A icon configuration for this application. This will only apply to iOS applications which are saved to the homescreen.
*
* You can either pass a string which will be applied to all different sizes:
*
* Ext.setup({
* icon: 'icon.png',
* onReady: function() {
* console.log('Launch...');
* }
* });
*
* Or an object which has a location for different sizes:
*
* Ext.setup({
* icon: {
* '57': 'icon57.png',
* '77': 'icon77.png',
* '114': 'icon114.png'
* },
* onReady: function() {
* console.log('Launch...');
* }
* });
*
* @param {String} config.icon.57 The icon to be used on non-retna display devices (iPhone 3GS and below).
* @param {String} config.icon.77 The icon to be used on the iPad.
* @param {String} config.icon.114 The icon to be used on retna display devices (iPhone 4 and above).
*
* @param {Boolean} glossOnIcon
* True to add a gloss effect to the icon.
*
* @param {String} phoneStartupScreen
* Sets the apple-touch-icon `` tag so your home screen application can have a startup screen on phones.
* Please look here for more information: http://developer.apple.com/library/IOs/#documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html
*
* @param {String} tabletStartupScreen
* Sets the apple-touch-icon `` tag so your home screen application can have a startup screen on tablets.
* Please look here for more information: http://developer.apple.com/library/IOs/#documentation/AppleApplications/Reference/SafariWebContent/ConfiguringWebApplications/ConfiguringWebApplications.html
*
* @param {String} statusBarStyle
* The style of status bar to be shown on applications added to the iOS homescreen. 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 recognise events fired
* in your application. The list of default recognisers can be found in the documentation for {@link Ext.event.recognizer.Recognizer}.
*
* To change the default recognisers, you can use the following syntax:
*
* Ext.application({
* eventPublishers: {
* touchGesture: {
* recognizers: {
* swipe: {
* //this will include both vertical and horizontal swipe recognisers
* 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 onReady,
scope;
if (!config) {
config = {};
}
config.requires = Ext.Array.from(config.requires);
config.requires.push('Ext.app.Application');
onReady = config.onReady;
scope = config.scope;
config.onReady = function() {
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);
},
/**
* @private
* @param config
* @param classReference
* @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 is 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);
}
}
else if (typeof config == 'string') {
return Ext.getCmp(config);
}
if (config === true) {
if (instance) {
return instance;
}
else {
return 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);
}
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
*/
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);
}
}
}
});
/**
* Provides useful information about the current browser. Should not be manually instantiated unless for unit-testing;
* access the global instance stored in Ext.browser instead. Example:
*
*
* @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
* @markdown
*/
deprecate: function(packageName, since, closure, scope) {
if (Version.compare(Ext.getVersion(packageName), since) < 1) {
closure.call(scope);
}
}
}); // End Versioning
Ext.setVersion('core', version);
})();
/**
* @class Ext.String
*
* A collection of useful static methods to deal with strings
* @singleton
*/
Ext.String = {
trimRegex: /^[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+|[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000]+$/g,
escapeRe: /('|\\)/g,
formatRe: /\{(\d+)\}/g,
escapeRegexRe: /([-.*+?^${}()|[\]\/\\])/g,
/**
* Convert certain characters (&, <, >, and ") to their HTML character equivalents for literal display in web pages.
* @param {String} value The string to encode
* @return {String} The encoded text
* @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.
*
* 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} 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:
*
*
// alternate sort directions
sort = Ext.String.toggle(sort, 'ASC', 'DESC');
// instead of conditional logic:
sort = (sort == 'ASC' ? 'DESC' : 'ASC');
* @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 s = Ext.String.leftPad('123', 5, '0');
// s now contains the string: '00123'
* @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 repititions 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;
/**
* @class Ext.Array
* @singleton
* @author Jacky Nguyen
var cls = 'my-class', text = 'Some text';
var s = Ext.String.format('<div class="{0}">{1}</div>', cls, text);
// s now contains the string: '<div class="my-class">Some text</div>'
* @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
*/
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 SyntaxError 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} The resulting object
*/
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;
Ext.Error = {
raise: function(object) {
throw new Error(object.msg);
}
};
/**
*
*/
Ext.Date = {
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');
}
};
/**
* @author Jacky Nguyen
Ext.JSON.encodeDate = function(d) {
return Ext.Date.format(d, '"Y-m-d"');
};
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
* @markdown
*/
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;
}
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
* @markdown
*/
setPath: flexSetter(function(name, path) {
this.config.paths[name] = path;
return this;
}),
/**
* Translates a className to a file path by adding the
* the proper prefix and converting the .'s to /'s. For example:
Ext.Loader.setPath('My', '/path/to/My');
alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
* Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
Ext.Loader.setPath({
'My': '/path/to/lib',
'My.awesome': '/other/path/for/awesome/stuff',
'My.awesome.more': '/more/awesome/path'
});
alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
* @param {String} className
* @return {String} path
* @markdown
*/
getPath: function(className) {
var path = '',
paths = this.config.paths,
prefix = this.getPrefix(className);
if (prefix.length > 0) {
if (prefix === className) {
return paths[prefix];
}
path = paths[prefix];
className = className.substring(prefix.length + 1);
}
if (path.length > 0) {
path += '/';
}
return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
},
/**
* @private
* @param {String} className
*/
getPrefix: function(className) {
var paths = this.config.paths,
prefix, deepestPrefix = '';
if (paths.hasOwnProperty(className)) {
return className;
}
for (prefix in paths) {
if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
if (prefix.length > deepestPrefix.length) {
deepestPrefix = prefix;
}
}
}
return deepestPrefix;
},
/**
* Loads all classes by the given names and all their direct dependencies; optionally executes the given callback function when
* finishes, within the optional scope. This method is aliased by {@link Ext#require Ext.require} for convenience
* @param {String/Array} expressions Can either be a string or an array of string
* @param {Function} fn (Optional) The callback function
* @param {Object} scope (Optional) The execution scope (`this`) of the callback function
* @param {String/Array} excludes (Optional) Classes to be excluded, useful when being used with expressions
* @markdown
*/
require: function(expressions, fn, scope, excludes) {
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
* @markdown
*/
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
* @markdown
*/
exclude: function(excludes) {
var me = this;
return {
require: function(expressions, fn, scope) {
return me.require(expressions, fn, scope, excludes);
},
syncRequire: function(expressions, fn, scope) {
return me.syncRequire(expressions, fn, scope, excludes);
}
};
},
/**
* 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()) : ''),
isCrossOriginRestricted = false,
xhr, status, 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) {
isCrossOriginRestricted = true;
}
status = (xhr.status === 1223) ? 204 : xhr.status;
if (!isCrossOriginRestricted) {
isCrossOriginRestricted = (status === 0);
}
if (isCrossOriginRestricted
) {
onError.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
"being loaded from a different domain or from the local file system whereby cross origin " +
"requests are not allowed due to security reasons. Use asynchronous loading with " +
"Ext.require instead.", synchronous);
}
else if (status >= 200 && status < 300
) {
// 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(xhr.responseText + "\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 asychronously 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;
},
// @ignore
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
*/
Ext.require = alias(Loader, 'require');
/**
* Synchronous version of {@link Ext#require}, convenient alias of {@link Ext.Loader#syncRequire}.
*
* @member Ext
* @method syncRequire
*/
Ext.syncRequire = alias(Loader, 'syncRequire');
/**
* Convenient shortcut to {@link Ext.Loader#exclude}
* @member Ext
* @method exclude
*/
Ext.exclude = alias(Loader, 'exclude');
/**
* @member Ext
* @method onReady
*/
Ext.onReady = function(fn, scope, options) {
Loader.onReady(fn, scope, true, options);
};
Class.registerPreprocessor('loader', function(cls, data, 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.util.Observable';
}
}
which will later be transformed into:
{
extend: Ext.MyClass,
requires: [Ext.some.OtherClass],
mixins: {
observable: Ext.util.Observable;
}
}
*/
for (i = 0,ln = dependencyProperties.length; i < ln; i++) {
propertyName = dependencyProperties[i];
if (data.hasOwnProperty(propertyName)) {
propertyValue = data[propertyName];
if (typeof propertyValue == 'string') {
dependencies.push(propertyValue);
}
else if (propertyValue instanceof Array) {
for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
value = propertyValue[j];
if (typeof value == 'string') {
dependencies.push(value);
}
}
}
else 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 neccessarily 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);
/*
This file is part of Sencha Touch 2
Copyright (c) 2011 Sencha Inc
Contact: http://www.sencha.com/contact
Commercial Usage
Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.
If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
*/
/**
* @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
*/
/**
* @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.0.0.beta3');
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 (optional) Id prefix (defaults "ext-gen")
* @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 fom 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 (Optional) Defaults to false. 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}, but any subclass of
* {@link Ext.util.Observable} can be passed in. 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:
*
* 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);
},
/**
*
// 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
}
*
* For a full list of supported values, refer to: {@link Ext.env.Browser#is}
*/
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, i.e:
*
* if (Ext.browser.is.IE) {
* // IE specific code here
* }
*
* if (Ext.browser.is.WebKit) {
* // WebKit specific code here
* }
*
* console.log("Version " + Ext.browser.version);
*
*
* or as an object with boolean properties, i.e:
*
* if (Ext.browser.is('IE')) { ... }
*
*
* Versions can be conveniently checked as well. For example:
*
* if (Ext.browser.is.IE) { ... }
*
*
* 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: IE, Firefox, Safari, Chrome, Opera, WebKit, Gecko, Presto, Trident and Other
*
* @param {String} value The OS name to check
* @return {Boolean}
*/
is: Ext.emptyFn,
/**
* Read-only - the full name of the current browser
* Possible values are: IE, Firefox, Safari, Chrome, Opera and Other
* @type String
*/
name: null,
/**
* Read-only, refer to {@link Ext.Version}
* @type Ext.Version
*/
version: null,
/**
* Read-only - the full name of the current browser's engine
* Possible values are: WebKit, Gecko, Presto, Trident and Other
* @type String
*/
engineName: null,
/**
* Read-only, refer to {@link Ext.Version}
* @type Ext.Version
*/
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]);
}
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') {
isWebView = true;
this.setFlag('PhoneGap');
}
else if (!!window.isNK) {
isWebView = true;
this.setFlag('Sencha');
}
// 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() {
var browserEnv = Ext.browser = new this(Ext.global.navigator.userAgent);
});
/**
* Provide useful information about the current operating system environment. Access the global instance stored in
* Ext.os. 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: {@link Ext.env.OS#is}
*/
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, iPod,
* Android, WebOS, BlackBerry, Bada, MacOSX, Windows, Linux and Other
* @param {String} value The OS name to check
* @return {Boolean}
*/
is: Ext.emptyFn,
/**
* @property {String} [name=null]
* Read-only - the full name of the current operating system Possible values are: iOS, Android, WebOS, BlackBerry,
* MacOSX, Windows, Linux and Other
*/
name: null,
/**
* @property {Ext.Version} [version=null]
* Read-only, refer to {@link Ext.Version}
*/
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);
}
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));
}
}
}
return this;
}
}, function() {
var navigation = Ext.global.navigator,
userAgent = navigation.userAgent,
osEnv, osName, deviceType;
Ext.os = osEnv = new this(userAgent, navigation.platform);
osName = osEnv.name;
var search = window.location.search.match(/deviceType=(Tablet|Phone)/);
// 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 (!osEnv.is.Android && !osEnv.is.iOS && /Windows|Linux|MacOS/.test(osName)) {
deviceType = 'Desktop';
}
else if (osEnv.is.iPad || osEnv.is.Android3 || (osEnv.is.Android4 && userAgent.search(/mobile/i) == -1)) {
deviceType = 'Tablet';
}
else {
deviceType = 'Phone';
}
}
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.env.Browser}, {@link Ext.env.OS} and {@link Ext.feature.has} classes instead.
*/
});
/**
* A class to detect if the current browser supports various features.
*
* Please refer to the documentation of {@link Ext.feature.has} on how to use it.
*
* if (Ext.feature.has.Canvas) {
* // do some cool things with canvas here
* }
*/
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() {
Ext.feature = new this;
var has = Ext.feature.has;
/**
* @class Ext.feature.has
* 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 list of properties below too see which features are available for detection.
*/
Ext.feature.registerTest({
/**
* @member Ext.feature.has
* @property {Boolean} Canvas
* True if the current device supports Canvas.
*/
Canvas: function() {
var element = this.getTestElement('canvas');
return !!(element && element.getContext && element.getContext('2d'));
},
/**
* @member Ext.feature.has
* @property {Boolean} Svg
* True if the current device supports SVG.
*/
Svg: function() {
var doc = document;
return !!(doc.createElementNS && !!doc.createElementNS("http:/" + "/www.w3.org/2000/svg", "svg").createSVGRect);
},
/**
* @member Ext.feature.has
* @property {Boolean} Vml
* True if the current device supports VML.
*/
Vml: function() {
var element = this.getTestElement(),
ret = false;
element.innerHTML = "";
ret = (element.childNodes.length === 1);
element.innerHTML = "";
return ret;
},
/**
* @member Ext.feature.has
* @property {Boolean} Touch
* True if the current device supports touch events (`touchstart`).
*/
Touch: function() {
return this.isEventSupported('touchstart') && !(Ext.os && Ext.os.name.match(/Windows|MacOSX|Linux/));
},
/**
* @member Ext.feature.has
* @property {Boolean} Orientation
* True if the current device supports different orientations.
*/
Orientation: function() {
return ('orientation' in window) && this.isEventSupported('orientationchange');
},
/**
* @member Ext.feature.has
* @property {Boolean} OrientationChange
* True if the current device supports the `orientationchange` event.
*/
OrientationChange: function() {
return this.isEventSupported('orientationchange');
},
/**
* @member Ext.feature.has
* @property {Boolean} DeviceMotion
* True if the current device supports the `devicemotion` event.
*/
DeviceMotion: function() {
return this.isEventSupported('devicemotion');
},
/**
* @member Ext.feature.has
* @property {Boolean} Geolocation
* True if the current device supports Geolocation.
*/
Geolocation: function() {
return 'geolocation' in window.navigator;
},
/**
* @member Ext.feature.has
* @property {Boolean} SqlDatabase
* True if the current device supports SQL Databases.
*/
SqlDatabase: function() {
return 'openDatabase' in window;
},
/**
* @member Ext.feature.has
* @property {Boolean} WebSockets
*/
WebSockets: function() {
return 'WebSocket' in window;
},
/**
* @member Ext.feature.has
* @property {Boolean} Range
*/
Range: function() {
return !!document.createRange;
},
/**
* @member Ext.feature.has
* @property {Boolean} CreateContextualFragment
*/
CreateContextualFragment: function() {
var range = !!document.createRange ? document.createRange() : false;
return range && !!range.createContextualFragment;
},
/**
* @member Ext.feature.has
* @property {Boolean} History
*/
History: function() {
return ('history' in window && 'pushState' in window.history);
},
/**
* @member Ext.feature.has
* @property {Boolean} CssTransforms
* True if the current device supports CSS Transform animations.
*/
CssTransforms: function() {
return this.isStyleSupported('transform');
},
/**
* @member Ext.feature.has
* @property {Boolean} Css3dTransforms
* True if the current device supports CSS 3D Transform animations.
*/
Css3dTransforms: function() {
// See https://sencha.jira.com/browse/TOUCH-1544
return this.has('CssTransforms') && this.isStyleSupported('perspective') && !Ext.os.is.Android2;
},
/**
* @member Ext.feature.has
* @property {Boolean} CssAnimations
* True if the current device supports CSS Animations.
*/
CssAnimations: function() {
return this.isStyleSupported('animationName');
},
/**
* @member Ext.feature.has
* @property {Boolean} CssTransitions
* True if the current device supports CSS Transitions.
*/
CssTransitions: function() {
return this.isStyleSupported('transitionProperty');
},
/**
* @member Ext.feature.has
* @property {Boolean} Audio
* True if the current device supports the `
* if (Ext.browser.is.IE6) { ... } // Equivalent to (Ext.browser.is.IE && Ext.browser.version.equals(6))
*
Ext.Router.draw(function(map) {
map.connect('activate/:token', {controller: 'users', action: 'activate'});
map.connect('home', {controller: 'index', action: 'home'});
});
* @param {Function} fn The fn to call
*/
draw: function(fn) {
fn.call(this, this);
},
/**
* @private
*/
clear: function() {
this.setRoutes([]);
}
}, function() {
});
/**
* @private
*/
Ext.define('Ext.behavior.Behavior', {
constructor: function(component) {
this.component = component;
component.on('destroy', 'onComponentDestroy', this);
},
onComponentDestroy: Ext.emptyFn
});
/**
* @singleton
*
* This class is used to create JsonP requests. JsonP is a mechanism that allows for making requests for data cross
* domain. More information is available [here](http://en.wikipedia.org/wiki/JSONP).
*
* ## Example
*
* @example preview
* Ext.Viewport.add({
* xtype: 'button',
* text: 'Make JsonP Request',
* centered: true,
* handler: function(button) {
* // Mask the viewport
* Ext.Viewport.mask();
*
* // Remove the button
* button.destroy();
*
* // Make the JsonP request
* Ext.data.JsonP.request({
* url: 'http://free.worldweatheronline.com/feed/weather.ashx',
* callbackKey: 'callback',
* params: {
* key: '23f6a0ab24185952101705',
* q: '94301', // Palo Alto
* format: 'json',
* num_of_days: 5
* },
* success: function(result) {
* // Unmask the viewport
* Ext.Viewport.unmask();
*
* // Get the weather data from the json object result
* var weather = result.data.weather;
* if (weather) {
* // Style the viewport html, and set the html of the max temperature
* Ext.Viewport.setStyleHtmlContent(true);
* Ext.Viewport.setHtml('The temperature in Palo Alto is ' + weather[0].tempMaxF + '° F');
* }
* }
* });
* }
* });
*
* See the {@link #request} method for more details on making a JsonP request.
*/
Ext.define('Ext.data.JsonP', {
alternateClassName: 'Ext.util.JSONP',
/* Begin Definitions */
singleton: true,
statics: {
requestCount: 0,
requests: {}
},
/* End Definitions */
/**
* @property {Number} [timeout=30000]
* A default timeout for any JsonP requests. If the request has not completed in this time the failure callback will
* be fired. The timeout is in ms. Defaults to 30000.
*/
timeout: 30000,
/**
* @property {Boolean} [disableCaching=true]
* True to add a unique cache-buster param to requests. Defaults to true.
*/
disableCaching: true,
/**
* @property {String} [disableCachingParam="_dc"]
* Change the parameter which is sent went disabling caching through a cache buster. Defaults to '_dc'.
*/
disableCachingParam: '_dc',
/**
* @property {String} [callbackKey="callback"]
* Specifies the GET parameter that will be sent to the server containing the function name to be executed when the
* request completes. Defaults to callback. Thus, a common request will be in the form of
* url?callback=Ext.data.JsonP.callback1
*/
callbackKey: 'callback',
/**
* Makes a JSONP request.
* @param {Object} options An object which may contain the following properties. Note that options will take
* priority over any defaults that are specified in the class.
*
* @param {String} options.url The URL to request.
* @param {Object} [options.params] An object containing a series of key value pairs that will be sent along with the request.
* @param {Number} [options.timeout] See {@link #timeout}
* @param {String} [options.callbackKey] See {@link #callbackKey}
* @param {String} [options.callbackName] See {@link #callbackKey}
* The function name to use for this request. By default this name will be auto-generated: Ext.data.JsonP.callback1,
* Ext.data.JsonP.callback2, etc. Setting this option to "my_name" will force the function name to be
* Ext.data.JsonP.my_name. Use this if you want deterministic behavior, but be careful - the callbackName should be
* different in each JsonP request that you make.
* @param {Boolean} [options.disableCaching] See {@link #disableCaching}
* @param {String} [options.disableCachingParam] See {@link #disableCachingParam}
* @param {Function} [options.success] A function to execute if the request succeeds.
* @param {Function} [options.failure] A function to execute if the request fails.
* @param {Function} [options.callback] A function to execute when the request completes, whether it is a success or failure.
* @param {Object} [options.scope] The scope in which to execute the callbacks: The "this" object for the
* callback function. Defaults to the browser window.
*
* @return {Object} request An object containing the request details.
*/
request: function(options){
options = Ext.apply({}, options);
if (!options.url) {
Ext.Logger.error('A url must be specified for a JSONP request.');
}
var me = this,
disableCaching = Ext.isDefined(options.disableCaching) ? options.disableCaching : me.disableCaching,
cacheParam = options.disableCachingParam || me.disableCachingParam,
id = ++me.statics().requestCount,
callbackName = options.callbackName || 'callback' + id,
callbackKey = options.callbackKey || me.callbackKey,
timeout = Ext.isDefined(options.timeout) ? options.timeout : me.timeout,
params = Ext.apply({}, options.params),
url = options.url,
name = Ext.isSandboxed ? Ext.getUniqueGlobalNamespace() : 'Ext',
request,
script;
params[callbackKey] = name + '.data.JsonP.' + callbackName;
if (disableCaching) {
params[cacheParam] = new Date().getTime();
}
script = me.createScript(url, params, options);
me.statics().requests[id] = request = {
url: url,
params: params,
script: script,
id: id,
scope: options.scope,
success: options.success,
failure: options.failure,
callback: options.callback,
callbackKey: callbackKey,
callbackName: callbackName
};
if (timeout > 0) {
request.timeout = setTimeout(Ext.bind(me.handleTimeout, me, [request]), timeout);
}
me.setupErrorHandling(request);
me[callbackName] = Ext.bind(me.handleResponse, me, [request], true);
me.loadScript(request);
return request;
},
/**
* Abort a request. If the request parameter is not specified all open requests will be aborted.
* @param {Object/String} request The request to abort
*/
abort: function(request){
var requests = this.statics().requests,
key;
if (request) {
if (!request.id) {
request = requests[request];
}
this.abort(request);
} else {
for (key in requests) {
if (requests.hasOwnProperty(key)) {
this.abort(requests[key]);
}
}
}
},
/**
* Sets up error handling for the script
* @private
* @param {Object} request The request
*/
setupErrorHandling: function(request){
request.script.onerror = Ext.bind(this.handleError, this, [request]);
},
/**
* Handles any aborts when loading the script
* @private
* @param {Object} request The request
*/
handleAbort: function(request){
request.errorType = 'abort';
this.handleResponse(null, request);
},
/**
* Handles any script errors when loading the script
* @private
* @param {Object} request The request
*/
handleError: function(request){
request.errorType = 'error';
this.handleResponse(null, request);
},
/**
* Cleans up anu script handling errors
* @private
* @param {Object} request The request
*/
cleanupErrorHandling: function(request){
request.script.onerror = null;
},
/**
* Handle any script timeouts
* @private
* @param {Object} request The request
*/
handleTimeout: function(request){
request.errorType = 'timeout';
this.handleResponse(null, request);
},
/**
* Handle a successful response
* @private
* @param {Object} result The result from the request
* @param {Object} request The request
*/
handleResponse: function(result, request){
var success = true;
if (request.timeout) {
clearTimeout(request.timeout);
}
delete this[request.callbackName];
delete this.statics()[request.id];
this.cleanupErrorHandling(request);
Ext.fly(request.script).destroy();
if (request.errorType) {
success = false;
Ext.callback(request.failure, request.scope, [request.errorType]);
} else {
Ext.callback(request.success, request.scope, [result]);
}
Ext.callback(request.callback, request.scope, [success, result, request.errorType]);
},
/**
* Create the script tag given the specified url, params and options. The options
* parameter is passed to allow an override to access it.
* @private
* @param {String} url The url of the request
* @param {Object} params Any extra params to be sent
* @param {Object} options The object passed to {@link #request}.
*/
createScript: function(url, params, options) {
var script = document.createElement('script');
script.setAttribute("src", Ext.urlAppend(url, Ext.Object.toQueryString(params)));
script.setAttribute("async", true);
script.setAttribute("type", "text/javascript");
return script;
},
/**
* Loads the script for the given request by appending it to the HEAD element. This is
* its own method so that users can override it (as well as {@link #createScript}).
* @private
* @param request The request object.
*/
loadScript: function (request) {
Ext.getHead().appendChild(request.script);
}
});
/**
* @author Ed Spencer
*
* Represents a single read or write operation performed by a {@link Ext.data.proxy.Proxy Proxy}. Operation objects are
* used to enable communication between Stores and Proxies. Application developers should rarely need to interact with
* Operation objects directly.
*
* Note that when you define an Operation directly, you need to specify at least the {@link #model} configuration.
*
* Several Operations can be batched together in a {@link Ext.data.Batch batch}.
*/
Ext.define('Ext.data.Operation', {
config: {
/**
* @cfg {Boolean} synchronous
* True if this Operation is to be executed synchronously. This property is inspected by a
* {@link Ext.data.Batch Batch} to see if a series of Operations can be executed in parallel or not.
* @accessor
* @private
*/
synchronous: true,
/**
* @cfg {String} action
* The action being performed by this Operation. Should be one of 'create', 'read', 'update' or 'destroy'.
* @accessor
*/
action: null,
/**
* @cfg {Ext.util.Filter[]} filters
* Optional array of filter objects. Only applies to 'read' actions.
* @accessor
* @private
*/
filters: null,
/**
* @cfg {Ext.util.Sorter[]} sorters
* Optional array of sorter objects. Only applies to 'read' actions.
* @accessor
* @private
*/
sorters: null,
/**
* @cfg {Ext.util.Grouper} grouper
* Optional grouping configuration. Only applies to 'read' actions where grouping is desired.
* @accessor
* @private
*/
grouper: null,
/**
* @cfg {Number} start
* The start index (offset), used in paging when running a 'read' action.
* @accessor
* @private
*/
start: null,
/**
* @cfg {Number} limit
* The number of records to load. Used on 'read' actions when paging is being used.
* @accessor
* @private
*/
limit: null,
/**
* @cfg {Ext.data.Batch} batch
* The batch that this Operation is a part of.
* @accessor
* @private
*/
batch: null,
/**
* @cfg {Function} callback
* Function to execute when operation completed.
* @cfg {Ext.data.Model[]} callback.records Array of records.
* @cfg {Ext.data.Operation} callback.operation The Operation itself.
* @cfg {Boolean} callback.success True when operation completed successfully.
* @accessor
* @private
*/
callback: null,
/**
* @cfg {Object} scope
* Scope for the {@link #callback} function.
* @accessor
* @private
*/
scope: null,
/**
* @cfg {Ext.data.ResultSet} resultSet
* The ResultSet for this operation.
* @accessor
*/
resultSet: null,
/**
* @cfg {Array} records
* The records associated to this operation. Before an operation starts, these
* are the records you are updating, creating, or destroying. After an operation
* is completed, a Proxy usually sets these records on the Operation to become
* the processed records. If you don't set these records on your operation in
* your proxy, then the getter will return the ones defined on the {@link #resultSet}
* @accessor
*/
records: null,
/**
* @cfg {Ext.data.Request} request
* The request used for this Operation. Operations don't usually care about Request and Response data, but in the
* ServerProxy and any of its subclasses they may be useful for further processing.
* @accessor
*/
request: null,
/**
* @cfg {Object} response
* The response that was gotten from the server if there was one.
* @accessor
*/
response: null,
/**
* @cfg {Object} params
* The params send along with this operation. These usually apply to a Server proxy if you are
* creating your own custom proxy,
* @accessor
*/
params: null,
url: null,
page: null,
node: null,
/**
* @cfg {Ext.data.Model} model
* The Model that this Operation will be dealing with. This configuration is required when defining any Operation.
* Since Operations take care of creating, updating, destroying and reading records, it needs access to the Model.
*/
model: undefined,
addRecords: false
},
/**
* @property {Boolean} started
* Read-only property tracking the start status of this Operation. Use {@link #isStarted}.
* @private
*/
started: false,
/**
* @property {Boolean} running
* Read-only property tracking the run status of this Operation. Use {@link #isRunning}.
* @private
*/
running: false,
/**
* @property {Boolean} complete
* Read-only property tracking the completion status of this Operation. Use {@link #isComplete}.
* @private
*/
complete: false,
/**
* @property {Boolean} success
* Read-only property tracking whether the Operation was successful or not. This starts as undefined and is set to true
* or false by the Proxy that is executing the Operation. It is also set to false by {@link #setException}. Use
* {@link #wasSuccessful} to query success status.
* @private
*/
success: undefined,
/**
* @property {Boolean} exception
* Read-only property tracking the exception status of this Operation. Use {@link #hasException} and see {@link #getError}.
* @private
*/
exception: false,
/**
* @property {String/Object} error
* The error object passed when {@link #setException} was called. This could be any object or primitive.
* @private
*/
error: undefined,
/**
* Creates new Operation object.
* @param {Object} config (optional) Config object.
*/
constructor: function(config) {
this.initConfig(config);
},
applyModel: function(model) {
if (typeof model == 'string') {
model = Ext.data.ModelManager.getModel(model);
if (!model) {
Ext.Logger.error('Model with name ' + arguments[0] + ' doesnt exist.');
}
}
if (model && !model.prototype.isModel && Ext.isObject(model)) {
model = Ext.data.ModelManager.registerType(model.storeId || model.id || Ext.id(), model);
}
if (!model) {
Ext.Logger.error('An Operation needs to have a model defined.');
}
return model;
},
getRecords: function() {
var resultSet = this.getResultSet();
return this._records || (resultSet ? resultSet.getRecords() : []);
},
/**
* Marks the Operation as started.
*/
setStarted: function() {
this.started = true;
this.running = true;
},
/**
* Marks the Operation as completed.
*/
setCompleted: function() {
this.complete = true;
this.running = false;
},
/**
* Marks the Operation as successful.
*/
setSuccessful: function() {
this.success = true;
},
/**
* Marks the Operation as having experienced an exception. Can be supplied with an option error message/object.
* @param {String/Object} error (optional) error string/object
*/
setException: function(error) {
this.exception = true;
this.success = false;
this.running = false;
this.error = error;
},
/**
* Returns true if this Operation encountered an exception (see also {@link #getError})
* @return {Boolean} True if there was an exception
*/
hasException: function() {
return this.exception === true;
},
/**
* Returns the error string or object that was set using {@link #setException}
* @return {String/Object} The error object
*/
getError: function() {
return this.error;
},
/**
* Returns true if the Operation has been started. Note that the Operation may have started AND completed, see
* {@link #isRunning} to test if the Operation is currently running.
* @return {Boolean} True if the Operation has started
*/
isStarted: function() {
return this.started === true;
},
/**
* Returns true if the Operation has been started but has not yet completed.
* @return {Boolean} True if the Operation is currently running
*/
isRunning: function() {
return this.running === true;
},
/**
* Returns true if the Operation has been completed
* @return {Boolean} True if the Operation is complete
*/
isComplete: function() {
return this.complete === true;
},
/**
* Returns true if the Operation has completed and was successful
* @return {Boolean} True if successful
*/
wasSuccessful: function() {
return this.isComplete() && this.success === true;
},
/**
* Checks whether this operation should cause writing to occur.
* @return {Boolean} Whether the operation should cause a write to occur.
*/
allowWrite: function() {
return this.getAction() != 'read';
},
process: function(action, resultSet, request, response) {
if (resultSet.getSuccess() !== false) {
this.setResponse(response);
this.setResultSet(resultSet);
this.setCompleted();
this.setSuccessful();
} else {
return false;
}
return this['process' + Ext.String.capitalize(action)].call(this, resultSet, request, response);
},
processRead: function(resultSet) {
var records = resultSet.getRecords(),
processedRecords = [],
Model = this.getModel(),
ln = records.length,
i, record;
for (i = 0; i < ln; i++) {
record = records[i];
processedRecords.push(new Model(record.data, record.id, record.node));
}
this.setRecords(processedRecords);
return true;
},
processCreate: function(resultSet) {
var updatedRecords = resultSet.getRecords(),
ln = updatedRecords.length,
i, currentRecord, updatedRecord;
for (i = 0; i < ln; i++) {
updatedRecord = updatedRecords[i];
currentRecord = this.findCurrentRecord(updatedRecord.clientId);
if (currentRecord) {
this.updateRecord(currentRecord, updatedRecord);
}
else {
Ext.Logger.warn('Unable to match the record that came back from the server.');
}
}
return true;
},
processUpdate: function(resultSet) {
var updatedRecords = resultSet.getRecords(),
currentRecords = this.getRecords(),
ln = updatedRecords.length,
i, currentRecord, updatedRecord;
for (i = 0; i < ln; i++) {
updatedRecord = updatedRecords[i];
currentRecord = currentRecords[i];
if (currentRecord) {
this.updateRecord(currentRecord, updatedRecord);
}
else {
Ext.Logger.warn('Unable to match the updated record that came back from the server.');
}
}
return true;
},
processDestroy: function(resultSet) {
var updatedRecords = resultSet.getRecords(),
ln = updatedRecords.length,
i, currentRecord, updatedRecord;
for (i = 0; i < ln; i++) {
updatedRecord = updatedRecords[i];
currentRecord = this.findCurrentRecord(updatedRecord.id);
if (currentRecord) {
currentRecord.setIsErased(true);
currentRecord.notifyStores('afterErase', currentRecord);
}
else {
Ext.Logger.warn('Unable to match the destroyed record that came back from the server.');
}
}
},
findCurrentRecord: function(clientId) {
var currentRecords = this.getRecords(),
ln = currentRecords.length,
i, currentRecord;
for (i = 0; i < ln; i++) {
currentRecord = currentRecords[i];
if (currentRecord.getId() === clientId) {
return currentRecord;
}
}
},
updateRecord: function(currentRecord, updatedRecord) {
var recordData = updatedRecord.data,
recordId = updatedRecord.id;
currentRecord.beginEdit();
currentRecord.set(recordData);
if (recordId !== null) {
currentRecord.setId(recordId);
}
// We call endEdit with silent: true because the commit below already makes
// sure any store is notified of the record being updated.
currentRecord.endEdit(true);
currentRecord.commit();
}
});
/**
* @author Ed Spencer
*
* Simple class that represents a Request that will be made by any {@link Ext.data.proxy.Server} subclass.
* All this class does is standardize the representation of a Request as used by any ServerProxy subclass,
* it does not contain any actual logic or perform the request itself.
*/
Ext.define('Ext.data.Request', {
config: {
/**
* @cfg {String} action
* The name of the action this Request represents. Usually one of 'create', 'read', 'update' or 'destroy'.
*/
action: null,
/**
* @cfg {Object} params
* HTTP request params. The Proxy and its Writer have access to and can modify this object.
*/
params: null,
/**
* @cfg {String} method
* The HTTP method to use on this Request. Should be one of 'GET', 'POST', 'PUT' or 'DELETE'.
*/
method: 'GET',
/**
* @cfg {String} url
* The url to access on this Request.
*/
url: null,
/**
* @cfg {Ext.data.Operation} operation
* The operation this request belongs to.
*/
operation: null,
/**
* @cfg {Ext.data.proxy.Proxy} proxy
* The proxy this request belongs to.
*/
proxy: null,
/**
* @cfg {Boolean} disableCaching
* Wether or not to disable caching for this request.
* Defaults to false.
*/
disableCaching: false,
/**
* @cfg {Object} headers
* Some requests (like XMLHttpRequests) want to send additional server headers.
* This configuration can be set for those types of requests.
*/
headers: {},
/**
* @cfg {String} callbackKey
* Some requests (like JsonP) want to send an additional key that contains
* the name of the callback function.
*/
callbackKey: null,
/**
* @cfg {Ext.data.JsonP} jsonp
* JsonP requests return a handle that might be useful in the callback function.
*/
jsonP: null,
/**
* @cfg {Object} jsonData
* This is used by some write actions to attach data to the request without encoding it
* as a parameter.
*/
jsonData: null,
/**
* @cfg {Object} xmlData
* This is used by some write actions to attach data to the request without encoding it
* as a parameter, but instead sending it as XML.
*/
xmlData: null,
callback: null,
scope: null,
timeout: 30000,
records: null
},
/**
* Creates the Request object.
* @param {Object} [config] Config object.
*/
constructor: function(config) {
this.initConfig(config);
}
});
/**
* @author Ed Spencer
*
* Simple wrapper class that represents a set of records returned by a Proxy.
*/
Ext.define('Ext.data.ResultSet', {
config: {
/**
* @cfg {Boolean} loaded
* True if the records have already been loaded. This is only meaningful when dealing with
* SQL-backed proxies.
*/
loaded: true,
/**
* @cfg {Number} count
* The number of records in this ResultSet. Note that total may differ from this number.
*/
count: null,
/**
* @cfg {Number} total
* The total number of records reported by the data source. This ResultSet may form a subset of
* those records (see {@link #count}).
*/
total: null,
/**
* @cfg {Boolean} success
* True if the ResultSet loaded successfully, false if any errors were encountered.
*/
success: false,
/**
* @cfg {Ext.data.Model[]} records (required)
* The array of record instances.
*/
records: null
},
/**
* Creates the resultSet
* @param {Object} [config] Config object.
*/
constructor: function(config) {
this.initConfig(config);
},
applyCount: function(count) {
if (!count && count !== 0) {
return this.getRecords().length;
}
return count;
},
/**
* @private
* Make sure we set the right count when new records have been sent in
*/
updateRecords: function(records) {
this.setCount(records.length);
}
});
/**
* @class Ext.data.SortTypes
* This class defines a series of static methods that are used on a
* {@link Ext.data.Field} for performing sorting. The methods cast the
* underlying values into a data type that is appropriate for sorting on
* that particular field. If a {@link Ext.data.Field#type} is specified,
* the sortType will be set to a sane default if the sortType is not
* explicitly defined on the field. The sortType will make any necessary
* modifications to the value and return it.
* * It is also possible to create a custom sortType that can be used throughout * an application. *
Ext.apply(Ext.data.SortTypes, {
asPerson: function(person){
// expects an object with a first and last name property
return person.lastName.toUpperCase() + person.firstName.toLowerCase();
}
});
Ext.define('Employee', {
extend: 'Ext.data.Model',
config: {
fields: [{
name: 'person',
sortType: 'asPerson'
}, {
name: 'salary',
type: 'float' // sortType set to asFloat
}]
}
});
*
*
* @singleton
* @docauthor Evan Trimboli This is s static class containing the system-supplied data types which may be given to a {@link Ext.data.Field Field}.
*The properties in this class are used as type indicators in the {@link Ext.data.Field Field} class, so to * test whether a Field is of a certain type, compare the {@link Ext.data.Field#type type} property against properties * of this class.
*Developers may add their own application-specific data types to this class. Definition names must be UPPERCASE. * each type definition must contain three properties:
*convert
: FunctionsortType
: Function type
: String For example, to create a VELatLong field (See the Microsoft Bing Mapping API) containing the latitude/longitude value of a datapoint on a map from a JsonReader data block
* which contained the properties lat
and long
, you would define a new data type like this:
// Add a new Field data type which stores a VELatLong object in the Record.
Ext.data.Types.VELATLONG = {
convert: function(v, data) {
return new VELatLong(data.lat, data.long);
},
sortType: function(v) {
return v.Latitude; // When sorting, order by latitude
},
type: 'VELatLong'
};
* Then, when declaring a Model, use
var types = Ext.data.Types; // allow shorthand type access
Ext.define('Unit', {
extend: 'Ext.data.Model',
config: {
fields: [
{ name: 'unitName', mapping: 'UnitName' },
{ name: 'curSpeed', mapping: 'CurSpeed', type: types.INT },
{ name: 'latitude', mapping: 'lat', type: types.FLOAT },
{ name: 'position', type: types.VELATLONG }
]
}
});
* @singleton
*/
Ext.define('Ext.data.Types', {
singleton: true,
requires: ['Ext.data.SortTypes'],
/**
* @property {RegExp} stripRe
* A regular expression for stripping non-numeric characters from a numeric value. Defaults to /[\$,%]/g.
* This should be overridden for localization.
*/
stripRe: /[\$,%]/g
}, function() {
var Types = this,
sortTypes = Ext.data.SortTypes;
Ext.apply(Types, {
/**
* @property {Object} AUTO
* This data type means that no conversion is applied to the raw data before it is placed into a Record.
*/
AUTO: {
convert: function(value) {
return value;
},
sortType: sortTypes.none,
type: 'auto'
},
/**
* @property {Object} STRING
* This data type means that the raw data is converted into a String before it is placed into a Record.
*/
STRING: {
convert: function(value) {
// 'this' is the actual field that calls this convert method
return (value === undefined || value === null)
? (this.getAllowNull() ? null : '')
: String(value);
},
sortType: sortTypes.asUCString,
type: 'string'
},
/**
* @property {Object} INT
* This data type means that the raw data is converted into an integer before it is placed into a Record.
* The synonym INTEGER
is equivalent.
The synonym NUMBER
is equivalent.
This data type means that the raw data is converted into a boolean before it is placed into
* a Record. The string "true" and the number 1 are converted to boolean true
.
The synonym BOOLEAN
is equivalent.
This data type means that the raw data is converted into a boolean before it is placed into
* a Record. The string "true" and the number 1 are converted to boolean true
.
The synonym BOOL
is equivalent.
The synonym INT
is equivalent.
The synonym FLOAT
is equivalent.
Ext.define('Person', {
extend: 'Ext.data.Model',
fields: [{
name: 'first',
mapping: 'firstName'
}, {
name: 'last',
mapping: 'lastName'
}, {
name: 'age'
}]
});
new Ext.data.writer.Writer({
writeAllFields: true,
nameProperty: 'mapping'
});
// This will be sent to the server
{
firstName: 'first name value',
lastName: 'last name value',
age: 1
}
*
* Defaults to name. If the value is not present, the field name will always be used.
*/
nameProperty: 'name'
},
/**
* Creates new Writer.
* @param {Object} config (optional) Config object.
*/
constructor: function(config) {
this.initConfig(config);
},
/**
* Prepares a Proxy's Ext.data.Request object
* @param {Ext.data.Request} request The request object
* @return {Ext.data.Request} The modified request object
*/
write: function(request) {
var operation = request.getOperation(),
records = operation.getRecords() || [],
len = records.length,
i = 0,
data = [];
for (; i < len; i++) {
data.push(this.getRecordData(records[i]));
}
return this.writeRecords(request, data);
},
writeDate: function(field, date) {
var dateFormat = field.dateFormat || 'timestamp';
switch (dateFormat) {
case 'timestamp':
return date.getTime()/1000;
case 'time':
return date.getTime();
default:
return Ext.Date.format(date, dateFormat);
}
},
/**
* Formats the data for each record before sending it to the server. This
* method should be overridden to format the data in a way that differs from the default.
* @param {Object} record The record that we are writing to the server.
* @return {Object} An object literal of name/value keys to be written to the server.
* By default this method returns the data property on the record.
*/
getRecordData: function(record) {
var isPhantom = record.phantom === true,
writeAll = this.getWriteAllFields() || isPhantom,
nameProperty = this.getNameProperty(),
fields = record.getFields(),
data = {},
changes, name, field, key, value, fieldConfig;
if (writeAll) {
fields.each(function(field) {
fieldConfig = field.config;
if (fieldConfig.persist) {
name = fieldConfig[nameProperty] || fieldConfig.name;
value = record.get(fieldConfig.name);
if (fieldConfig.type.type == 'date') {
value = this.writeDate(fieldConfig, value);
}
data[name] = value;
}
}, this);
} else {
// Only write the changes
changes = record.getChanges();
for (key in changes) {
if (changes.hasOwnProperty(key)) {
field = fields.get(key);
fieldConfig = field.config;
if (fieldConfig.persist) {
name = fieldConfig[nameProperty] || field.name;
value = changes[key];
if (fieldConfig.type.type == 'date') {
value = this.writeDate(fieldConfig, value);
}
data[name] = value;
}
}
}
if (!isPhantom) {
// always include the id for non phantoms
data[record.idProperty] = record.getId();
}
}
return data;
}
// Convert old properties in data into a config object
});
/**
* @author Ed Spencer
* @class Ext.data.writer.Xml
This class is used to write {@link Ext.data.Model} data to the server in an XML format.
The {@link #documentRoot} property is used to specify the root element in the XML document.
The {@link #record} option is used to specify the element name for each record that will make
up the XML document.
* @markdown
*/
Ext.define('Ext.data.writer.Xml', {
/* Begin Definitions */
extend: 'Ext.data.writer.Writer',
alternateClassName: 'Ext.data.XmlWriter',
alias: 'writer.xml',
/* End Definitions */
config: {
/**
* @cfg {String} documentRoot The name of the root element of the document. Defaults to 'xmlData'.
* If there is more than 1 record and the root is not specified, the default document root will still be used
* to ensure a valid XML document is created.
*/
documentRoot: 'xmlData',
/**
* @cfg {String} defaultDocumentRoot The root to be used if {@link #documentRoot} is empty and a root is required
* to form a valid XML document.
*/
defaultDocumentRoot: 'xmlData',
/**
* @cfg {String} header A header to use in the XML document (such as setting the encoding or version).
* Defaults to ''.
*/
header: '',
/**
* @cfg {String} record The name of the node to use for each record. Defaults to 'record'.
*/
record: 'record'
},
//inherit docs
writeRecords: function(request, data) {
var me = this,
xml = [],
i = 0,
len = data.length,
root = me.getDocumentRoot(),
record = me.getRecord(),
needsRoot = data.length !== 1,
item,
key;
// may not exist
xml.push(me.getHeader() || '');
if (!root && needsRoot) {
root = me.getDefaultDocumentRoot();
}
if (root) {
xml.push('<', root, '>');
}
for (; i < len; ++i) {
item = data[i];
xml.push('<', record, '>');
for (key in item) {
if (item.hasOwnProperty(key)) {
xml.push('<', key, '>', item[key], '', key, '>');
}
}
xml.push('', record, '>');
}
if (root) {
xml.push('', root, '>');
}
request.setXmlData(xml.join(''));
return request;
}
});
/**
* Small utility class used internally to represent a Direct method.
* @class Ext.direct.RemotingMethod
* @ignore
*/
Ext.define('Ext.direct.RemotingMethod', {
config: {
name: null,
params: null,
formHandler: null,
len: null,
ordered: true
},
constructor: function(config) {
this.initConfig(config);
},
applyParams: function(params) {
if (Ext.isNumber(params)) {
this.setLen(params);
} else if (Ext.isArray(params)) {
this.setOrdered(false);
var ln = params.length,
ret = [],
i, param, name;
for (i = 0; i < ln; i++) {
param = params[i];
name = Ext.isObject(param) ? param.name : param;
ret.push(name);
}
return ret;
}
},
// getArgs: function(params, paramOrder, paramsAsHash) {
// var args = [],
// i,
// len;
//
// if (this.ordered) {
// if (this.len > 0) {
// // If a paramOrder was specified, add the params into the argument list in that order.
// if (paramOrder) {
// for (i = 0, len = paramOrder.length; i < len; i++) {
// args.push(params[paramOrder[i]]);
// }
// } else if (paramsAsHash) {
// // If paramsAsHash was specified, add all the params as a single object argument.
// args.push(params);
// }
// }
// } else {
// args.push(params);
// }
//
// return args;
// },
/**
* Takes the arguments for the Direct function and splits the arguments
* from the scope and the callback.
* @param {Array} args The arguments passed to the direct call
* @return {Object} An object with 3 properties, args, callback & scope.
*/
getCallData: function(args) {
var me = this,
data = null,
len = me.getLen(),
params = me.getParams(),
callback, scope, name;
if (me.getOrdered()) {
callback = args[len];
scope = args[len + 1];
if (len !== 0) {
data = args.slice(0, len);
}
} else {
data = Ext.apply({}, args[0]);
callback = args[1];
scope = args[2];
for (name in data) {
if (data.hasOwnProperty(name)) {
if (!Ext.Array.contains(params, name)) {
delete data[name];
}
}
}
}
return {
data: data,
callback: callback,
scope: scope
};
}
});
/**
* Supporting Class for Ext.Direct (not intended to be used directly).
*/
Ext.define('Ext.direct.Transaction', {
alias: 'direct.transaction',
alternateClassName: 'Ext.Direct.Transaction',
statics: {
TRANSACTION_ID: 0
},
config: {
id: undefined,
provider: null,
retryCount: 0,
args: null,
action: null,
method: null,
data: null,
callback: null,
form: null
},
constructor: function(config) {
this.initConfig(config);
},
applyId: function(id) {
if (id === undefined) {
id = ++this.self.TRANSACTION_ID;
}
return id;
},
updateId: function(id) {
this.id = this.tid = id;
},
getTid: function() {
return this.tid;
},
send: function(){
this.getProvider().queueTransaction(this);
},
retry: function(){
this.setRetryCount(this.getRetryCount() + 1);
this.send();
}
});
/**
* This class encapsulates a _collection_ of DOM elements, providing methods to filter members, or to perform collective
* actions upon the whole set.
*
* Although they are not listed, this class supports all of the methods of {@link Ext.dom.Element}. The methods from
* these classes will be performed on all the elements in this collection.
*
* All methods return _this_ and can be chained.
*
* Usage:
*
* var els = Ext.select("#some-el div.some-class", true);
* // or select directly from an existing element
* var el = Ext.get('some-el');
* el.select('div.some-class', true);
*
* els.setWidth(100); // all elements become 100 width
* els.hide(true); // all elements fade out and hide
* // or
* els.setWidth(100).hide(true);
*/
Ext.define('Ext.dom.CompositeElement', {
alternateClassName: 'Ext.CompositeElement',
extend: 'Ext.dom.CompositeElementLite',
// private
getElement: function(el) {
// In this case just return it, since we already have a reference to it
return el;
},
// private
transformElement: function(el) {
return Ext.get(el);
}
}, function() {
Ext.dom.Element.select = function(selector, unique, root) {
var elements;
if (typeof selector == "string") {
elements = Ext.dom.Element.selectorFunction(selector, root);
}
else if (selector.length !== undefined) {
elements = selector;
}
else {
throw new Error("[Ext.select] Invalid selector specified: " + selector);
}
return (unique === true) ? new Ext.CompositeElement(elements) : new Ext.CompositeElementLite(elements);
};
});
/**
* @private
*/
Ext.define('Ext.event.Controller', {
isFiring: false,
listenerStack: null,
constructor: function(info) {
this.firingListeners = [];
this.firingArguments = [];
this.setInfo(info);
return this;
},
setInfo: function(info) {
this.info = info;
},
getInfo: function() {
return this.info;
},
setListenerStacks: function(listenerStacks) {
this.listenerStacks = listenerStacks;
},
fire: function(args, action) {
var listenerStacks = this.listenerStacks,
firingListeners = this.firingListeners,
firingArguments = this.firingArguments,
push = firingListeners.push,
ln = listenerStacks.length,
listeners, beforeListeners, currentListeners, afterListeners,
isActionBefore = false,
isActionAfter = false,
i;
firingListeners.length = 0;
if (action) {
if (action.order !== 'after') {
isActionBefore = true;
}
else {
isActionAfter = true;
}
}
if (ln === 1) {
listeners = listenerStacks[0].listeners;
beforeListeners = listeners.before;
currentListeners = listeners.current;
afterListeners = listeners.after;
if (beforeListeners.length > 0) {
push.apply(firingListeners, beforeListeners);
}
if (isActionBefore) {
push.call(firingListeners, action);
}
if (currentListeners.length > 0) {
push.apply(firingListeners, currentListeners);
}
if (isActionAfter) {
push.call(firingListeners, action);
}
if (afterListeners.length > 0) {
push.apply(firingListeners, afterListeners);
}
}
else {
for (i = 0; i < ln; i++) {
beforeListeners = listenerStacks[i].listeners.before;
if (beforeListeners.length > 0) {
push.apply(firingListeners, beforeListeners);
}
}
if (isActionBefore) {
push.call(firingListeners, action);
}
for (i = 0; i < ln; i++) {
currentListeners = listenerStacks[i].listeners.current;
if (currentListeners.length > 0) {
push.apply(firingListeners, currentListeners);
}
}
if (isActionAfter) {
push.call(firingListeners, action);
}
for (i = 0; i < ln; i++) {
afterListeners = listenerStacks[i].listeners.after;
if (afterListeners.length > 0) {
push.apply(firingListeners, afterListeners);
}
}
}
if (firingListeners.length === 0) {
return this;
}
if (!args) {
args = [];
}
firingArguments.length = 0;
firingArguments.push.apply(firingArguments, args);
// Backwards compatibility
firingArguments.push(null, this);
this.doFire();
return this;
},
doFire: function() {
var firingListeners = this.firingListeners,
firingArguments = this.firingArguments,
optionsArgumentIndex = firingArguments.length - 2,
i, ln, listener, options, fn, firingFn,
boundFn, isLateBinding, scope, args, result;
this.isPausing = false;
this.isPaused = false;
this.isStopped = false;
this.isFiring = true;
for (i = 0,ln = firingListeners.length; i < ln; i++) {
listener = firingListeners[i];
options = listener.options;
fn = listener.fn;
firingFn = listener.firingFn;
boundFn = listener.boundFn;
isLateBinding = listener.isLateBinding;
scope = listener.scope;
// Re-bind the callback if it has changed since the last time it's bound (overridden)
if (isLateBinding && boundFn && boundFn !== scope[fn]) {
boundFn = false;
firingFn = false;
}
if (!boundFn) {
if (isLateBinding) {
boundFn = scope[fn];
if (!boundFn) {
continue;
}
}
else {
boundFn = fn;
}
listener.boundFn = boundFn;
}
if (!firingFn) {
firingFn = boundFn;
if (options.buffer) {
firingFn = Ext.Function.createBuffered(firingFn, options.buffer, scope);
}
if (options.delay) {
firingFn = Ext.Function.createDelayed(firingFn, options.delay, scope);
}
listener.firingFn = firingFn;
}
firingArguments[optionsArgumentIndex] = options;
args = firingArguments;
if (options.args) {
args = options.args.concat(args);
}
if (options.single === true) {
listener.stack.remove(fn, scope, listener.order);
}
result = firingFn.apply(scope, args);
if (result === false) {
this.stop();
}
if (this.isStopped) {
break;
}
if (this.isPausing) {
this.isPaused = true;
firingListeners.splice(0, i + 1);
return;
}
}
this.isFiring = false;
this.listenerStacks = null;
firingListeners.length = 0;
firingArguments.length = 0;
this.connectingController = null;
},
connect: function(controller) {
this.connectingController = controller;
},
resume: function() {
var connectingController = this.connectingController;
this.isPausing = false;
if (this.isPaused && this.firingListeners.length > 0) {
this.isPaused = false;
this.doFire();
}
if (connectingController) {
connectingController.resume();
}
return this;
},
isInterrupted: function() {
return this.isStopped || this.isPaused;
},
stop: function() {
var connectingController = this.connectingController;
this.isStopped = true;
if (connectingController) {
this.connectingController = null;
connectingController.stop();
}
this.isFiring = false;
this.listenerStacks = null;
return this;
},
pause: function() {
var connectingController = this.connectingController;
this.isPausing = true;
if (connectingController) {
connectingController.pause();
}
return this;
}
});
// Using @mixins to include all members of Ext.event.Touch
// into here to keep documentation simpler
/**
* @mixins Ext.event.Touch
*
* Just as {@link Ext.dom.Element} wraps around a native DOM node, {@link Ext.event.Event} wraps the browser's native
* event-object normalizing cross-browser differences such as mechanisms to stop event-propagation along with a method
* to prevent default actions from taking place.
*
* Here is a simple example of how you use it:
*
* @example preview
* Ext.Viewport.add({
* layout: 'fit',
* items: [
* {
* docked: 'top',
* xtype: 'toolbar',
* title: 'Ext.event.Event example!'
* },
* {
* id: 'logger',
* styleHtmlContent: true,
* html: 'Tap somewhere!',
* padding: 5
* }
* ]
* });
*
* Ext.Viewport.element.on({
* tap: function(e, node) {
* var string = '';
*
* string += 'You tapped at: { x: ' + e.pageX + ', y: ' + e.pageY + ' } (e.pageX & e.pageY)';
* string += 'true to disable selection. * This configuration will lock the selection model that the DataView uses.
* @accessor */ disableSelection: null, /** * @cfg {String} mode * Modes of selection. * Valid values are SINGLE, SIMPLE, and MULTI. Defaults to 'SINGLE' * @accessor */ mode: 'SINGLE', /** * @cfg {Boolean} allowDeselect * Allow users to deselect a record in a DataView, List or Grid. Only applicable when the Selectable's mode is * 'SINGLE'. Defaults to false. * @accessor */ allowDeselect: false, /** * @cfg {Ext.data.Model} lastSelected * @private * @accessor */ lastSelected: null, /** * @cfg {Ext.data.Model} lastFocused * @private * @accessor */ lastFocused: null, /** * @cfg {Boolean} deselectOnContainerClick True to deselect current selection when the container body is * clicked. Defaults to true * @accessor */ deselectOnContainerClick: true }, modes: { SINGLE: true, SIMPLE: true, MULTI: true }, selectableEventHooks: { addrecords: 'onSelectionStoreAdd', removerecords: 'onSelectionStoreRemove', updaterecord: 'onSelectionStoreUpdate', load: 'refreshSelection', refresh: 'refreshSelection' }, constructor: function() { this.selected = new Ext.util.MixedCollection(); this.callParent(arguments); }, /** * @private */ applyMode: function(mode) { mode = mode ? mode.toUpperCase() : 'SINGLE'; // set to mode specified unless it doesnt exist, in that case // use single. return this.modes[mode] ? mode : 'SINGLE'; }, /** * @private */ updateStore: function(newStore, oldStore) { var me = this, bindEvents = Ext.apply({}, me.selectableEventHooks, { scope: me }); if (oldStore && Ext.isObject(oldStore) && oldStore.isStore) { if (oldStore.autoDestroy) { oldStore.destroy(); } else { oldStore.un(bindEvents); } } if (newStore) { newStore.on(bindEvents); me.refreshSelection(); } }, /** * Selects all records. * @param {Boolean} silent True to suppress all select events. */ selectAll: function(silent) { var me = this, selections = me.getStore().getRange(), ln = selections.length, i = 0; for (; i < ln; i++) { me.select(selections[i], true, silent); } }, /** * Deselects all records. */ deselectAll: function() { var me = this, selections = me.getStore().getRange(), ln = selections.length, i = 0; for (; i < ln; i++) { me.deselect(selections[i]); } me.selected.clear(); me.setLastSelected(null); me.setLastFocused(null); }, // Provides differentiation of logic between MULTI, SIMPLE and SINGLE // selection modes. selectWithEvent: function(record) { var me = this, isSelected = me.isSelected(record); switch (me.getMode()) { case 'MULTI': case 'SIMPLE': if (isSelected) { me.deselect(record); } else { me.select(record, true); } break; case 'SINGLE': if (me.getAllowDeselect() && isSelected) { // if allowDeselect is on and this record isSelected, deselect it me.deselect(record); } else { // select the record and do NOT maintain existing selections me.select(record, false); } break; } }, /** * Selects a range of rows if the selection model {@link Ext.mixin.Selectable#getDisableSelection is not locked}. * All rows in between startRow and endRow are also selected. * @param {Number} startRow The index of the first row in the range * @param {Number} endRow The index of the last row in the range * @param {Boolean} keepExisting (optional) True to retain existing selections */ selectRange: function(startRecord, endRecord, keepExisting, dir) { var me = this, store = me.getStore(), startRow = store.indexOf(startRecord), endRow = store.indexOf(endRecord), selectedCount = 0, tmp, dontDeselect, i; if (me.getDisableSelection()) { return; } // swap values if (startRow > endRow) { tmp = endRow; endRow = startRow; startRow = tmp; } for (i = startRow; i <= endRow; i++) { if (me.isSelected(store.getAt(i))) { selectedCount++; } } if (!dir) { dontDeselect = -1; } else { dontDeselect = (dir == 'up') ? startRow : endRow; } for (i = startRow; i <= endRow; i++) { if (selectedCount == (endRow - startRow + 1)) { if (i != dontDeselect) { me.deselect(i, true); } } else { me.select(i, true); } } }, /** * Adds the given records to the currently selected set * @param {Ext.data.Model/Array/Number} records The records to select * @param {Boolean} keepExisting If true, the existing selection will be added to (if not, the old selection is replaced) * @param {Boolean} suppressEvent If true, the 'select' event will not be fired */ select: function(records, keepExisting, suppressEvent) { var me = this, record; if (me.getDisableSelection()) { return; } if (typeof records === "number") { records = [me.getStore().getAt(records)]; } if (!records) { return; } if (me.getMode() == "SINGLE" && records) { record = records.length ? records[0] : records; me.doSingleSelect(record, suppressEvent); } else { me.doMultiSelect(records, keepExisting, suppressEvent); } }, /** * Selects a single record * @private */ doSingleSelect: function(record, suppressEvent) { var me = this, selected = me.selected; if (me.getDisableSelection()) { return; } // already selected. // should we also check beforeselect? if (me.isSelected(record)) { return; } if (selected.getCount() > 0) { me.deselect(me.getLastSelected(), suppressEvent); } selected.add(record); me.setLastSelected(record); me.onItemSelect(record, suppressEvent); me.setLastFocused(record); me.fireSelectionChange(!suppressEvent); }, /** * Selects a set of multiple records * @private */ doMultiSelect: function(records, keepExisting, suppressEvent) { if (records === null || this.getDisableSelection()) { return; } records = !Ext.isArray(records) ? [records] : records; var me = this, selected = me.selected, ln = records.length, change = false, i = 0, record; if (!keepExisting && selected.getCount() > 0) { change = true; me.deselect(me.getSelection(), true); } for (; i < ln; i++) { record = records[i]; if (keepExisting && me.isSelected(record)) { continue; } change = true; me.setLastSelected(record); selected.add(record); if (!suppressEvent) { me.setLastFocused(record); } me.onItemSelect(record, suppressEvent); } this.fireSelectionChange(change && !suppressEvent); }, /** * Deselects the given record(s). If many records are currently selected, it will only deselect those you pass in. * @param {Number/Array/Ext.data.Model} records The record(s) to deselect. Can also be a number to reference by index * @param {Boolean} suppressEvent If true the deselect event will not be fired */ deselect: function(records, suppressEvent) { var me = this; if (me.getDisableSelection()) { return; } records = Ext.isArray(records) ? records : [records]; var selected = me.selected, change = false, i = 0, store = me.getStore(), ln = records.length, record; for (; i < ln; i++) { record = records[i]; if (typeof record === 'number') { record = store.getAt(record); } if (selected.remove(record)) { if (me.getLastSelected() == record) { me.setLastSelected(selected.last()); } change = true; } if (record) { me.onItemDeselect(record, suppressEvent); } } me.fireSelectionChange(change && !suppressEvent); }, /** * @param {Ext.data.Record} record * Set a record as the last focused record. This does NOT mean * that the record has been selected. */ updateLastFocused: function(newRecord, oldRecord) { this.onLastFocusChanged(oldRecord, newRecord); }, fireSelectionChange: function(fireEvent) { var me = this; if (fireEvent) { me.fireEvent('selectionchange', me, me.getSelection()); } }, /** * Returns an array of the currently selected records. * @return {Array} An array of selected records */ getSelection: function() { return this.selected.getRange(); }, /** * Returns true if the specified row is selected. * @param {Ext.data.Model/Number} record The record or index of the record to check * @return {Boolean} */ isSelected: function(record) { record = Ext.isNumber(record) ? this.getStore().getAt(record) : record; return this.selected.indexOf(record) !== -1; }, /** * Returns true if there is a selected record. * @return {Boolean} */ hasSelection: function() { return this.selected.getCount() > 0; }, /** * @private */ refreshSelection: function() { var me = this, newSelection = [], oldSelections = me.getSelection(), ln = oldSelections.length, i = 0, selection, change; // check to make sure that there are no records // missing after the refresh was triggered, prune // them from what is to be selected if so for (; i < ln; i++) { selection = oldSelections[i]; if (me.getStore().indexOf(selection) != -1) { newSelection.push(selection); } } // there was a change from the old selected and // the new selection if (me.selected.getCount() != newSelection.length) { change = true; } me.deselect(oldSelections, true); if (newSelection.length) { // perform the selection again me.select(newSelection, false, true); } me.fireSelectionChange(change); }, // when a store is cleared remove all selections // (if there were any) onSelectionStoreClear: function() { var me = this, selected = me.selected; if (selected.getCount() > 0) { selected.clear(); me.setLastSelected(null); me.setLastFocused(null); me.fireSelectionChange(true); } }, // prune records from the SelectionModel if // they were selected at the time they were // removed. onSelectionStoreRemove: function(store, record) { var me = this, selected = me.selected; if (me.getDisableSelection()) { return; } if (selected.remove(record)) { if (me.getLastSelected() == record) { me.setLastSelected(null); } if (me.getLastFocused() == record) { me.setLastFocused(null); } me.fireSelectionChange(true); } }, /** * Returns the number of selections. * @return {Number} */ getSelectionCount: function() { return this.selected.getCount(); }, onSelectionStoreAdd: Ext.emptyFn, onSelectionStoreUpdate: Ext.emptyFn, onItemSelect: Ext.emptyFn, onItemDeselect: Ext.emptyFn, onLastFocusChanged: Ext.emptyFn, onEditorKey: Ext.emptyFn }, function() { /** * Selects a record instance by record instance or index. * @member Ext.mixin.Selectable * @method doSelect * @param {Ext.data.Model/Number} records An array of records or an index * @param {Boolean} keepExisting * @param {Boolean} suppressEvent Set to false to not fire a select event * @deprecated 2.0.0 Please use {@link #select} instead. */ /** * Deselects a record instance by record instance or index. * @member Ext.mixin.Selectable * @method doDeselect * @param {Ext.data.Model/Number} records An array of records or an index * @param {Boolean} suppressEvent Set to false to not fire a deselect event * @deprecated 2.0.0 Please use {@link #deselect} instead. */ /** * Returns the selection mode currently used by this Selectable * @member Ext.mixin.Selectable * @method getSelectionMode * @return {String} The current mode * @deprecated 2.0.0 Please use {@link #getMode} instead. */ /** * Returns the array of previously selected items * @member Ext.mixin.Selectable * @method getLastSelected * @return {Array} The previous selection * @deprecated 2.0.0 */ /** * Returns true if the Selectable is currently locked * @member Ext.mixin.Selectable * @method isLocked * @return {Boolean} True if currently locked * @deprecated 2.0.0 Please use {@link #getDisableSelection} instead. */ /** * This was an internal function accidentally exposed in 1.x and now deprecated. Calling it has no effect * @member Ext.mixin.Selectable * @method setLastFocused * @deprecated 2.0.0 */ /** * Deselects any currently selected records and clears all stored selections * @member Ext.mixin.Selectable * @method clearSelections * @deprecated 2.0.0 Please use {@link #deselectAll} instead. */ /** * Returns the number of selections. * @member Ext.mixin.Selectable * @method getCount * @return {Number} * @deprecated 2.0.0 Please use {@link #getSelectionCount} instead. */ /** * @cfg {Boolean} locked * @deprecated 2.0.0 Please use {@link #disableSelection} instead. */ }); /** * A Traversable mixin. * @private */ Ext.define('Ext.mixin.Traversable', { extend: 'Ext.mixin.Mixin', mixinConfig: { id: 'traversable' }, setParent: function(parent) { this.parent = parent; return this; }, /** * @member Ext.Component * Returns `true` if this component has a parent. * @return {Boolean} `true` if this component has a parent. */ hasParent: function() { return Boolean(this.parent); }, /** * @member Ext.Component * Returns the parent of this component, if it has one. * @return {Ext.Component} The parent of this component. */ getParent: function() { return this.parent; }, getAncestors: function() { var ancestors = [], parent = this.getParent(); while (parent) { ancestors.push(parent); parent = parent.getParent(); } return ancestors; }, getAncestorIds: function() { var ancestorIds = [], parent = this.getParent(); while (parent) { ancestorIds.push(parent.getId()); parent = parent.getParent(); } return ancestorIds; } }); /** * The DelayedTask class provides a convenient way to "buffer" the execution of a method, * performing setTimeout where a new timeout cancels the old timeout. When called, the * task will wait the specified time period before executing. If durng that time period, * the task is called again, the original call will be cancelled. This continues so that * the function is only called a single time for each iteration. * * This method is especially useful for things like detecting whether a user has finished * typing in a text field. An example would be performing validation on a keypress. You can * use this class to buffer the keypress events for a certain number of milliseconds, and * perform only if they stop for that amount of time. * * Using {@link Ext.util.DelayedTask} is very simple: * * //create the delayed task instance with our callback * var task = Ext.create('Ext.util.DelayedTask', function() { * console.log('callback!'); * }); * * task.delay(1500); //the callback function will now be called after 1500ms * * task.cancel(); //the callback function will never be called now, unless we call delay() again * * ## Example * * @example * //create a textfield where we can listen to text * var field = Ext.create('Ext.field.Text', { * xtype: 'textfield', * label: 'Length: 0' * }); * * //add the textfield into a fieldset * Ext.Viewport.add({ * xtype: 'formpanel', * items: [{ * xtype: 'fieldset', * items: [field], * instructions: 'Type into the field and watch the count go up after 500ms.' * }] * }); * * //create our delayed task with a function that returns the fields length as the fields label * var task = Ext.create('Ext.util.DelayedTask', function() { * field.setLabel('Length: ' + field.getValue().length); * }); * * // Wait 500ms before calling our function. If the user presses another key * // during that 500ms, it will be cancelled and we'll wait another 500ms. * field.on('keyup', function() { * task.delay(500); * }); * * @constructor * The parameters to this constructor serve as defaults and are not required. * @param {Function} fn The default function to call. * @param {Object} scope The default scope (The `this` reference) in which the function is called. If * not specified, `this` will refer to the browser window. * @param {Array} args The default Array of arguments. */ Ext.define('Ext.util.DelayedTask', { config: { interval: null, delay: null, fn: null, scope: null, args: null }, constructor: function(fn, scope, args) { var config = { fn: fn, scope: scope, args: args }; this.initConfig(config); }, /** * Cancels any pending timeout and queues a new one. * @param {Number} delay The milliseconds to delay * @param {Function} newFn Overrides the original function passed when instantiated. * @param {Object} newScope Overrides the original `scope` passed when instantiated. Remember that if no scope * is specified, `this` will refer to the browser window. * @param {Array} newArgs Overrides the original `args` passed when instantiated. */ delay: function(delay, newFn, newScope, newArgs) { var me = this; //cancel any existing queued functions me.cancel(); //set all the new configurations me.setConfig({ delay: delay, fn: newFn, scope: newScope, args: newArgs }); //create the callback method for this delayed task var call = function() { me.getFn().apply(me.getScope(), me.getArgs() || []); me.cancel(); }; me.setInterval(setInterval(call, me.getDelay())); }, /** * Cancel the last queued timeout */ cancel: function() { this.setInterval(null); }, /** * @private * Clears the old interval */ updateInterval: function(newInterval, oldInterval) { if (oldInterval) { clearInterval(oldInterval); } }, /** * @private * Changes the value into an array if it isn't one. */ applyArgs: function(config) { if (!Ext.isArray(config)) { config = [config]; } return config; } }); /** * Represents a filter that can be applied to a {@link Ext.util.MixedCollection MixedCollection}. Can either simply * filter on a property/value pair or pass in a filter function with custom logic. Filters are always used in the * context of MixedCollections, though {@link Ext.data.Store Store}s frequently create them when filtering and searching * on their records. Example usage: * * // Set up a fictional MixedCollection containing a few people to filter on * var allNames = new Ext.util.MixedCollection(); * allNames.addAll([ * { id: 1, name: 'Ed', age: 25 }, * { id: 2, name: 'Jamie', age: 37 }, * { id: 3, name: 'Abe', age: 32 }, * { id: 4, name: 'Aaron', age: 26 }, * { id: 5, name: 'David', age: 32 } * ]); * * var ageFilter = new Ext.util.Filter({ * property: 'age', * value : 32 * }); * * var longNameFilter = new Ext.util.Filter({ * filterFn: function(item) { * return item.name.length > 4; * } * }); * * // a new MixedCollection with the 3 names longer than 4 characters * var longNames = allNames.filter(longNameFilter); * * // a new MixedCollection with the 2 people of age 24: * var youngFolk = allNames.filter(ageFilter); */ Ext.define('Ext.util.Filter', { isFilter: true, config: { /** * @cfg {String} [property=null] * The property to filter on. Required unless a `filter` is passed */ property: null, /** * @cfg {RegExp/Mixed} [value=null] * The value you want to match against. Can be a regular expression which will be used as matcher or any other * value. */ value: null, /** * @cfg {Function} filterFn * A custom filter function which is passed each item in the {@link Ext.util.MixedCollection} in turn. Should * return true to accept each item or false to reject it */ filterFn: Ext.emptyFn, /** * @cfg {Boolean} [anyMatch=false] * True to allow any match - no regex start/end line anchors will be added. */ anyMatch: false, /** * @cfg {Boolean} [exactMatch=false] * True to force exact match (^ and $ characters added to the regex). Ignored if anyMatch is true. */ exactMatch: false, /** * @cfg {Boolean} [caseSensitive=false] * True to make the regex case sensitive (adds 'i' switch to regex). */ caseSensitive: false, /** * @cfg {String} [root=null] * Optional root property. This is mostly useful when filtering a Store, in which case we set the root to 'data' * to make the filter pull the {@link #property} out of the data object of each item */ root: null, /** * @cfg {String} id * An optional id this filter can be keyed by in Collections. If no id is specified it will generate an id by * first trying a combination of property-value, and if none if these were specified (like when having a * filterFn) it will generate a random id. */ id: undefined, /** * @cfg {Object} [scope=null] * The scope in which to run the filterFn */ scope: null }, applyId: function(id) { if (!id) { if (this.getProperty()) { id = this.getProperty() + '-' + String(this.getValue()); } if (!id) { id = Ext.id(null, 'ext-filter-'); } } return id; }, /** * Creates new Filter. * @param {Object} config Config object */ constructor: function(config) { this.initConfig(config); }, applyFilterFn: function(filterFn) { if (filterFn === Ext.emptyFn) { filterFn = this.getInitialConfig('filter'); if (filterFn) { return filterFn; } var value = this.getValue(); if (!this.getProperty() && !value && value !== 0) { Ext.Logger.error('A Filter requires either a property and value, or a filterFn to be set'); return Ext.emptyFn; } else { return this.createFilterFn(); } } return filterFn; }, /** * @private * Creates a filter function for the configured property/value/anyMatch/caseSensitive options for this Filter */ createFilterFn: function() { var me = this, matcher = me.createValueMatcher(); return function(item) { var root = me.getRoot(), property = me.getProperty(); if (root) { item = item[root]; } return matcher.test(item[property]); }; }, /** * @private * Returns a regular expression based on the given value and matching options */ createValueMatcher: function() { var me = this, value = me.getValue(), anyMatch = me.getAnyMatch(), exactMatch = me.getExactMatch(), caseSensitive = me.getCaseSensitive(), escapeRe = Ext.String.escapeRegex; if (value === null || value === undefined || !value.exec) { // not a regex value = String(value); if (anyMatch === true) { value = escapeRe(value); } else { value = '^' + escapeRe(value); if (exactMatch === true) { value += '$'; } } value = new RegExp(value, caseSensitive ? '' : 'i'); } return value; } }); /** * Represents a 2D point with x and y properties, useful for comparison and instantiation * from an event: * * var point = Ext.util.Point.fromEvent(e); * */ Ext.define('Ext.util.Point', { radianToDegreeConstant: 180 / Math.PI, statics: { /** * Returns a new instance of Ext.util.Point based on the pageX / pageY values of the given event * @static * @param {Event} e The event * @return Ext.util.Point */ fromEvent: function(e) { var changedTouches = e.changedTouches, touch = (changedTouches && changedTouches.length > 0) ? changedTouches[0] : e; return this.fromTouch(touch); }, /** * Returns a new instance of Ext.util.Point based on the pageX / pageY values of the given touch * @static * @param {Event} touch * @return Ext.util.Point */ fromTouch: function(touch) { return new this(touch.pageX, touch.pageY); }, /** * Returns a new point from an object that has 'x' and 'y' properties, if that object is not an instance * of Ext.util.Point. Otherwise, returns the given point itself. * @param object */ from: function(object) { if (!object) { return new this(0, 0); } if (!(object instanceof this)) { return new this(object.x, object.y); } return object; } }, /** * Creates point on 2D plane. * @param {Number} [x=0] X coordinate. * @param {Number} [y=0] Y coordinate. */ constructor: function(x, y) { if (typeof x == 'undefined') { x = 0; } if (typeof y == 'undefined') { y = 0; } this.x = x; this.y = y; return this; }, /** * Copy a new instance of this point * @return {Ext.util.Point} the new point */ clone: function() { return new this.self(this.x, this.y); }, /** * Clones this Point. * @deprecated 2.0.0 Please use {@link #clone} instead */ copy: function() { return this.clone.apply(this, arguments); }, /** * Copy the x and y values of another point / object to this point itself * @param {Ext.util.Point/Object} point * @return {Ext.util.Point} this This point */ copyFrom: function(point) { this.x = point.x; this.y = point.y; return this; }, /** * Returns a human-eye-friendly string that represents this point, * useful for debugging. * @return {String} For example `Point[12,8]` */ toString: function() { return "Point[" + this.x + "," + this.y + "]"; }, /** * Compare this point and another point * @param {Ext.util.Point/Object} The point to compare with, either an instance * of Ext.util.Point or an object with x and y properties * @return {Boolean} Returns whether they are equivalent */ equals: function(point) { return (this.x === point.x && this.y === point.y); }, /** * Whether the given point is not away from this point within the given threshold amount * @param {Ext.util.Point/Object} The point to check with, either an instance * of Ext.util.Point or an object with x and y properties * @param {Object/Number} threshold Can be either an object with x and y properties or a number * @return {Boolean} */ isCloseTo: function(point, threshold) { if (typeof threshold == 'number') { threshold = {x: threshold}; threshold.y = threshold.x; } var x = point.x, y = point.y, thresholdX = threshold.x, thresholdY = threshold.y; return (this.x <= x + thresholdX && this.x >= x - thresholdX && this.y <= y + thresholdY && this.y >= y - thresholdY); }, /** * Returns true if this point is close to another one. * @deprecated 2.0.0 Please use {@link #isCloseTo} instead */ isWithin: function() { return this.isCloseTo.apply(this, arguments); }, /** * Translate this point by the given amounts * @param {Number} x Amount to translate in the x-axis * @param {Number} y Amount to translate in the y-axis * @return {Boolean} */ translate: function(x, y) { this.x += x; this.y += y; return this; }, /** * Compare this point with another point when the x and y values of both points are rounded. E.g: * [100.3,199.8] will equals to [100, 200] * @param {Ext.util.Point/Object} The point to compare with, either an instance * of Ext.util.Point or an object with x and y properties * @return {Boolean} */ roundedEquals: function(point) { return (Math.round(this.x) === Math.round(point.x) && Math.round(this.y) === Math.round(point.y)); }, getDistanceTo: function(point) { var deltaX = this.x - point.x, deltaY = this.y - point.y; return Math.sqrt(deltaX * deltaX + deltaY * deltaY); }, getAngleTo: function(point) { var deltaX = this.x - point.x, deltaY = this.y - point.y; return Math.atan2(deltaY, deltaX) * this.radianToDegreeConstant; } }); /** * Represents a rectangular region and provides a number of utility methods * to compare regions. */ Ext.define('Ext.util.Region', { statics: { /** * @static * @param {Mixed} el A string, DomElement or Ext.Element representing an element * on the page. * @returns {Ext.util.Region} region * Retrieves an Ext.util.Region for a particular element. */ getRegion: function(el) { return Ext.fly(el).getPageBox(true); }, /** * @static * @param {Object} o An object with top, right, bottom, left properties * @return {Ext.util.Region} region The region constructed based on the passed object */ from: function(o) { return new this(o.top, o.right, o.bottom, o.left); } }, /** * Creates new Region. * @param {Number} top Top * @param {Number} right Right * @param {Number} bottom Bottom * @param {Number} left Left */ constructor: function(t, r, b, l) { var me = this; me.top = t; me[1] = t; me.right = r; me.bottom = b; me.left = l; me[0] = l; }, /** * Checks if this region completely contains the region that is passed in. * @param {Ext.util.Region} region */ contains: function(region) { var me = this; return (region.left >= me.left && region.right <= me.right && region.top >= me.top && region.bottom <= me.bottom); }, /** * Checks if this region intersects the region passed in. * @param {Ext.util.Region} region * @return {Ext.util.Region/Boolean} Returns the intersected region or false if there is no intersection. */ intersect: function(region) { var me = this, t = Math.max(me.top, region.top), r = Math.min(me.right, region.right), b = Math.min(me.bottom, region.bottom), l = Math.max(me.left, region.left); if (b > t && r > l) { return new Ext.util.Region(t, r, b, l); } else { return false; } }, /** * Returns the smallest region that contains the current AND targetRegion. * @param {Ext.util.Region} region */ union: function(region) { var me = this, t = Math.min(me.top, region.top), r = Math.max(me.right, region.right), b = Math.max(me.bottom, region.bottom), l = Math.min(me.left, region.left); return new Ext.util.Region(t, r, b, l); }, /** * Modifies the current region to be constrained to the targetRegion. * @param {Ext.util.Region} targetRegion */ constrainTo: function(r) { var me = this, constrain = Ext.util.Numbers.constrain; me.top = constrain(me.top, r.top, r.bottom); me.bottom = constrain(me.bottom, r.top, r.bottom); me.left = constrain(me.left, r.left, r.right); me.right = constrain(me.right, r.left, r.right); return me; }, /** * Modifies the current region to be adjusted by offsets. * @param {Number} top top offset * @param {Number} right right offset * @param {Number} bottom bottom offset * @param {Number} left left offset */ adjust: function(t, r, b, l) { var me = this; me.top += t; me.left += l; me.right += r; me.bottom += b; return me; }, /** * Get the offset amount of a point outside the region * @param {String} axis optional * @param {Ext.util.Point} p the point * @return {Ext.util.Region} */ getOutOfBoundOffset: function(axis, p) { if (!Ext.isObject(axis)) { if (axis == 'x') { return this.getOutOfBoundOffsetX(p); } else { return this.getOutOfBoundOffsetY(p); } } else { p = axis; var d = new Ext.util.Offset(); d.x = this.getOutOfBoundOffsetX(p.x); d.y = this.getOutOfBoundOffsetY(p.y); return d; } }, /** * Get the offset amount on the x-axis * @param {Number} p the offset * @return {Number} */ getOutOfBoundOffsetX: function(p) { if (p <= this.left) { return this.left - p; } else if (p >= this.right) { return this.right - p; } return 0; }, /** * Get the offset amount on the y-axis * @param {Number} p the offset * @return {Number} */ getOutOfBoundOffsetY: function(p) { if (p <= this.top) { return this.top - p; } else if (p >= this.bottom) { return this.bottom - p; } return 0; }, /** * Check whether the point / offset is out of bound * @param {String} axis optional * @param {Ext.util.Point/Number} p the point / offset * @return {Boolean} */ isOutOfBound: function(axis, p) { if (!Ext.isObject(axis)) { if (axis == 'x') { return this.isOutOfBoundX(p); } else { return this.isOutOfBoundY(p); } } else { p = axis; return (this.isOutOfBoundX(p.x) || this.isOutOfBoundY(p.y)); } }, /** * Check whether the offset is out of bound in the x-axis * @param {Number} p the offset * @return {Boolean} */ isOutOfBoundX: function(p) { return (p < this.left || p > this.right); }, /** * Check whether the offset is out of bound in the y-axis * @param {Number} p the offset * @return {Boolean} */ isOutOfBoundY: function(p) { return (p < this.top || p > this.bottom); }, /* * Restrict a point within the region by a certain factor. * @param {String} axis Optional * @param {Ext.util.Point/Ext.util.Offset/Object} p * @param {Number} factor * @return {Ext.util.Point/Ext.util.Offset/Object/Number} */ restrict: function(axis, p, factor) { if (Ext.isObject(axis)) { var newP; factor = p; p = axis; if (p.copy) { newP = p.copy(); } else { newP = { x: p.x, y: p.y }; } newP.x = this.restrictX(p.x, factor); newP.y = this.restrictY(p.y, factor); return newP; } else { if (axis == 'x') { return this.restrictX(p, factor); } else { return this.restrictY(p, factor); } } }, /* * Restrict an offset within the region by a certain factor, on the x-axis * @param {Number} p * @param {Number} factor The factor, optional, defaults to 1 * @return */ restrictX: function(p, factor) { if (!factor) { factor = 1; } if (p <= this.left) { p -= (p - this.left) * factor; } else if (p >= this.right) { p -= (p - this.right) * factor; } return p; }, /* * Restrict an offset within the region by a certain factor, on the y-axis * @param {Number} p * @param {Number} factor The factor, optional, defaults to 1 */ restrictY: function(p, factor) { if (!factor) { factor = 1; } if (p <= this.top) { p -= (p - this.top) * factor; } else if (p >= this.bottom) { p -= (p - this.bottom) * factor; } return p; }, /* * Get the width / height of this region * @return {Object} an object with width and height properties */ getSize: function() { return { width: this.right - this.left, height: this.bottom - this.top }; }, /** * Copy a new instance * @return {Ext.util.Region} */ copy: function() { return new Ext.util.Region(this.top, this.right, this.bottom, this.left); }, /** * Dump this to an eye-friendly string, great for debugging * @return {String} For example `Region[0,1,3,2]` */ toString: function() { return "Region[" + this.top + "," + this.right + "," + this.bottom + "," + this.left + "]"; }, /** * Translate this region by the given offset amount * @param {Object} offset * @return {Ext.util.Region} this This Region */ translateBy: function(offset) { this.left += offset.x; this.right += offset.x; this.top += offset.y; this.bottom += offset.y; return this; }, /** * Round all the properties of this region * @return {Ext.util.Region} this This Region */ round: function() { this.top = Math.round(this.top); this.right = Math.round(this.right); this.bottom = Math.round(this.bottom); this.left = Math.round(this.left); return this; }, /** * Check whether this region is equivalent to the given region * @param {Ext.util.Region} region The region to compare with * @return {Boolean} */ equals: function(region) { return (this.top == region.top && this.right == region.right && this.bottom == region.bottom && this.left == region.left) } }); /** * Represents a single sorter that can be used as part of the sorters configuration in Ext.mixin.Sortable. */ Ext.define('Ext.util.Sorter', { isSorter: true, config: { /** * @cfg {String} property The property to sort by. Required unless `sorterFn` is provided */ property: null, /** * @cfg {Function} sorterFn A specific sorter function to execute. Can be passed instead of {@link #property} */ sorterFn: null, /** * @cfg {String} root Optional root property. This is mostly useful when sorting a Store, in which case we set the * root to 'data' to make the filter pull the {@link #property} out of the data object of each item */ root: null, /** * @cfg {Function} transform A function that will be run on each value before * it is compared in the sorter. The function will receive a single argument, * the value. */ transform: null, /** * @cfg {String} direction The direction to sort by. */ direction: "ASC", /** * @cfg {Mixed} id An optional id this sorter can be keyed by in Collections. If * no id is specified it will use the property name used in this Sorter. If no * property is specified, e.g. when adding a custom sorter function we will generate * a random id. */ id: undefined }, constructor: function(config) { this.initConfig(config); }, applySorterFn: function(sorterFn) { if (!sorterFn && !this.getProperty()) { Ext.Logger.error("A Sorter requires either a property or a sorterFn."); } return sorterFn; }, applyProperty: function(property) { if (!property && !this.getSorterFn()) { Ext.Logger.error("A Sorter requires either a property or a sorterFn."); } return property; }, applyId: function(id) { if (!id) { id = this.getProperty(); if (!id) { id = Ext.id(null, 'ext-sorter-'); } } return id; }, /** * @private * Creates and returns a function which sorts an array by the given property and direction * @return {Function} A function which sorts by the property/direction combination provided */ createSortFunction: function(sorterFn) { var me = this, modifier = me.getDirection().toUpperCase() == "DESC" ? -1 : 1; //create a comparison function. Takes 2 objects, returns 1 if object 1 is greater, //-1 if object 2 is greater or 0 if they are equal return function(o1, o2) { return modifier * sorterFn.call(me, o1, o2); }; }, /** * @private * Basic default sorter function that just compares the defined property of each object */ defaultSortFn: function(item1, item2) { var me = this, transform = me._transform, root = me._root, value1, value2, property = me._property; if (root !== null) { item1 = item1[root]; item2 = item2[root]; } value1 = item1[property]; value2 = item2[property]; if (transform) { value1 = transform(value1); value2 = transform(value2); } return value1 > value2 ? 1 : (value1 < value2 ? -1 : 0); }, updateDirection: function() { this.updateSortFn(); }, updateSortFn: function() { this.sort = this.createSortFunction(this.getSorterFn() || this.defaultSortFn); }, /** * Toggles the direction of this Sorter. Note that when you call this function, * the Collection this Sorter is part of does not get refreshed automatically. */ toggle: function() { this.setDirection(Ext.String.toggle(this.getDirection(), "ASC", "DESC")); } }); /** *General purpose inflector class that {@link #pluralize pluralizes}, {@link #singularize singularizes} and * {@link #ordinalize ordinalizes} words. Sample usage:
*
//turning singular words into plurals
Ext.util.Inflector.pluralize('word'); //'words'
Ext.util.Inflector.pluralize('person'); //'people'
Ext.util.Inflector.pluralize('sheep'); //'sheep'
//turning plurals into singulars
Ext.util.Inflector.singularize('words'); //'word'
Ext.util.Inflector.singularize('people'); //'person'
Ext.util.Inflector.singularize('sheep'); //'sheep'
//ordinalizing numbers
Ext.util.Inflector.ordinalize(11); //"11th"
Ext.util.Inflector.ordinalize(21); //"21th"
Ext.util.Inflector.ordinalize(1043); //"1043rd"
*
* Customization
* *The Inflector comes with a default set of US English pluralization rules. These can be augmented with additional * rules if the default rules do not meet your application's requirements, or swapped out entirely for other languages. * Here is how we might add a rule that pluralizes "ox" to "oxen":
*
Ext.util.Inflector.plural(/^(ox)$/i, "$1en");
*
* Each rule consists of two items - a regular expression that matches one or more rules, and a replacement string. * In this case, the regular expression will only match the string "ox", and will replace that match with "oxen". * Here's how we could add the inverse rule:
*
Ext.util.Inflector.singular(/^(ox)en$/i, "$1");
*
* Note that the ox/oxen rules are present by default.
*/ Ext.define('Ext.util.Inflector', { /* Begin Definitions */ singleton: true, /* End Definitions */ /** * @private * The registered plural tuples. Each item in the array should contain two items - the first must be a regular * expression that matchers the singular form of a word, the second must be a String that replaces the matched * part of the regular expression. This is managed by the {@link #plural} method. * @property plurals * @type Array */ plurals: [ [(/(quiz)$/i), "$1zes" ], [(/^(ox)$/i), "$1en" ], [(/([m|l])ouse$/i), "$1ice" ], [(/(matr|vert|ind)ix|ex$/i), "$1ices" ], [(/(x|ch|ss|sh)$/i), "$1es" ], [(/([^aeiouy]|qu)y$/i), "$1ies" ], [(/(hive)$/i), "$1s" ], [(/(?:([^f])fe|([lr])f)$/i), "$1$2ves"], [(/sis$/i), "ses" ], [(/([ti])um$/i), "$1a" ], [(/(buffal|tomat|potat)o$/i), "$1oes" ], [(/(bu)s$/i), "$1ses" ], [(/(alias|status|sex)$/i), "$1es" ], [(/(octop|vir)us$/i), "$1i" ], [(/(ax|test)is$/i), "$1es" ], [(/^person$/), "people" ], [(/^man$/), "men" ], [(/^(child)$/), "$1ren" ], [(/s$/i), "s" ], [(/$/), "s" ] ], /** * @private * The set of registered singular matchers. Each item in the array should contain two items - the first must be a * regular expression that matches the plural form of a word, the second must be a String that replaces the * matched part of the regular expression. This is managed by the {@link #singular} method. * @property singulars * @type Array */ singulars: [ [(/(quiz)zes$/i), "$1" ], [(/(matr)ices$/i), "$1ix" ], [(/(vert|ind)ices$/i), "$1ex" ], [(/^(ox)en/i), "$1" ], [(/(alias|status)es$/i), "$1" ], [(/(octop|vir)i$/i), "$1us" ], [(/(cris|ax|test)es$/i), "$1is" ], [(/(shoe)s$/i), "$1" ], [(/(o)es$/i), "$1" ], [(/(bus)es$/i), "$1" ], [(/([m|l])ice$/i), "$1ouse" ], [(/(x|ch|ss|sh)es$/i), "$1" ], [(/(m)ovies$/i), "$1ovie" ], [(/(s)eries$/i), "$1eries"], [(/([^aeiouy]|qu)ies$/i), "$1y" ], [(/([lr])ves$/i), "$1f" ], [(/(tive)s$/i), "$1" ], [(/(hive)s$/i), "$1" ], [(/([^f])ves$/i), "$1fe" ], [(/(^analy)ses$/i), "$1sis" ], [(/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i), "$1$2sis"], [(/([ti])a$/i), "$1um" ], [(/(n)ews$/i), "$1ews" ], [(/people$/i), "person" ], [(/s$/i), "" ] ], /** * @private * The registered uncountable words * @property uncountable * @type Array */ uncountable: [ "sheep", "fish", "series", "species", "money", "rice", "information", "equipment", "grass", "mud", "offspring", "deer", "means" ], /** * Adds a new singularization rule to the Inflector. See the intro docs for more information * @param {RegExp} matcher The matcher regex * @param {String} replacer The replacement string, which can reference matches from the matcher argument */ singular: function(matcher, replacer) { this.singulars.unshift([matcher, replacer]); }, /** * Adds a new pluralization rule to the Inflector. See the intro docs for more information * @param {RegExp} matcher The matcher regex * @param {String} replacer The replacement string, which can reference matches from the matcher argument */ plural: function(matcher, replacer) { this.plurals.unshift([matcher, replacer]); }, /** * Removes all registered singularization rules */ clearSingulars: function() { this.singulars = []; }, /** * Removes all registered pluralization rules */ clearPlurals: function() { this.plurals = []; }, /** * Returns true if the given word is transnumeral (the word is its own singular and plural form - e.g. sheep, fish) * @param {String} word The word to test * @return {Boolean} True if the word is transnumeral */ isTransnumeral: function(word) { return Ext.Array.indexOf(this.uncountable, word) != -1; }, /** * Returns the pluralized form of a word (e.g. Ext.util.Inflector.pluralize('word') returns 'words') * @param {String} word The word to pluralize * @return {String} The pluralized form of the word */ pluralize: function(word) { if (this.isTransnumeral(word)) { return word; } var plurals = this.plurals, length = plurals.length, tuple, regex, i; for (i = 0; i < length; i++) { tuple = plurals[i]; regex = tuple[0]; if (regex == word || (regex.test && regex.test(word))) { return word.replace(regex, tuple[1]); } } return word; }, /** * Returns the singularized form of a word (e.g. Ext.util.Inflector.singularize('words') returns 'word') * @param {String} word The word to singularize * @return {String} The singularized form of the word */ singularize: function(word) { if (this.isTransnumeral(word)) { return word; } var singulars = this.singulars, length = singulars.length, tuple, regex, i; for (i = 0; i < length; i++) { tuple = singulars[i]; regex = tuple[0]; if (regex == word || (regex.test && regex.test(word))) { return word.replace(regex, tuple[1]); } } return word; }, /** * Returns the correct {@link Ext.data.Model Model} name for a given string. Mostly used internally by the data * package * @param {String} word The word to classify * @return {String} The classified version of the word */ classify: function(word) { return Ext.String.capitalize(this.singularize(word)); }, /** * Ordinalizes a given number by adding a prefix such as 'st', 'nd', 'rd' or 'th' based on the last digit of the * number. 21 -> 21st, 22 -> 22nd, 23 -> 23rd, 24 -> 24th etc * @param {Number} number The number to ordinalize * @return {String} The ordinalized number */ ordinalize: function(number) { var parsed = parseInt(number, 10), mod10 = parsed % 10, mod100 = parsed % 100; //11 through 13 are a special case if (11 <= mod100 && mod100 <= 13) { return number + "th"; } else { switch(mod10) { case 1 : return number + "st"; case 2 : return number + "nd"; case 3 : return number + "rd"; default: return number + "th"; } } } }, function() { //aside from the rules above, there are a number of words that have irregular pluralization so we add them here var irregulars = { alumnus: 'alumni', cactus : 'cacti', focus : 'foci', nucleus: 'nuclei', radius: 'radii', stimulus: 'stimuli', ellipsis: 'ellipses', paralysis: 'paralyses', oasis: 'oases', appendix: 'appendices', index: 'indexes', beau: 'beaux', bureau: 'bureaux', tableau: 'tableaux', woman: 'women', child: 'children', man: 'men', corpus: 'corpora', criterion: 'criteria', curriculum: 'curricula', genus: 'genera', memorandum: 'memoranda', phenomenon: 'phenomena', foot: 'feet', goose: 'geese', tooth: 'teeth', antenna: 'antennae', formula: 'formulae', nebula: 'nebulae', vertebra: 'vertebrae', vita: 'vitae' }, singular; for (singular in irregulars) { this.plural(singular, irregulars[singular]); this.singular(irregulars[singular], singular); } }); /** * Ext.Anim is used to excute simple animations defined in {@link Ext.anims}. The {@link #run} method can take any of the * properties defined below. * * Ext.Anim.run(this, 'fade', { * out: false, * autoClear: true * }); * * When using {@link Ext.Anim#run}, ensure you require {@link Ext.Anim} in your application. Either do this using {@link Ext#require}: * * Ext.requires('Ext.Anim'); * * when using {@link Ext#setup}: * * Ext.setup({ * requires: ['Ext.Anim'], * onReady: function() { * //do something * } * }); * * or when using {@link Ext#application}: * * Ext.application({ * requires: ['Ext.Anim'], * launch: function() { * //do something * } * }); * * @singleton */ Ext.Anim = Ext.extend(Object, { isAnim: true, /** * @cfg {Boolean} disableAnimations * True to disable animations. */ disableAnimations: false, defaultConfig: { /** * @cfg {Object} from * An object of CSS values which the animation begins with. If you define a CSS property here, you must also * define it in the {@link #to} config. */ from: {}, /** * @cfg {Object} to * An object of CSS values which the animation ends with. If you define a CSS property here, you must also * define it in the {@link #from} config. */ to: {}, /** * @cfg {Number} duration * Time in milliseconds for the animation to last. */ duration: 250, /** * @cfg {Number} delay Time to delay before starting the animation. */ delay: 0, /** * @cfg {String} easing * Valid values are 'ease', 'linear', ease-in', 'ease-out', 'ease-in-out' or a cubic-bezier curve as defined by CSS. */ easing: 'ease-in-out', /** * @cfg {Boolean} autoClear * True to remove all custom CSS defined in the {@link #to} config when the animation is over. */ autoClear: true, /** * @cfg {Boolean} out * True if you want the animation to slide out of the screen. */ out: true, /** * @cfg {String} direction * Valid values are 'left', 'right', 'up', 'down' and null. */ direction: null, /** * @cfg {Boolean} reverse * True to reverse the animation direction. For example, if the animation direction was set to 'left', it would * then use 'right'. */ reverse: false }, /** * @cfg {Function} before * Code to execute before starting the animation. */ /** * @cfg {Function} after * Code to execute after the animation ends. */ /** * @cfg {Object} scope * Scope to run the {@link #before} function in. */ opposites: { 'left': 'right', 'right': 'left', 'up': 'down', 'down': 'up' }, constructor: function(config) { config = Ext.apply({}, config || {}, this.defaultConfig); this.config = config; Ext.Anim.superclass.constructor.call(this); this.running = []; }, initConfig: function(el, runConfig) { var me = this, config = Ext.apply({}, runConfig || {}, me.config); config.el = el = Ext.get(el); if (config.reverse && me.opposites[config.direction]) { config.direction = me.opposites[config.direction]; } if (me.config.before) { me.config.before.call(config, el, config); } if (runConfig.before) { runConfig.before.call(config.scope || config, el, config); } return config; }, run: function(el, config) { el = Ext.get(el); config = config || {}; var me = this, style = el.dom.style, property, after = config.after; if (me.running[el.id]) { me.onTransitionEnd(null, el, { config: config, after: after }); } config = this.initConfig(el, config); if (this.disableAnimations) { for (property in config.to) { if (!config.to.hasOwnProperty(property)) { continue; } style[property] = config.to[property]; } this.onTransitionEnd(null, el, { config: config, after: after }); return me; } el.un('transitionend', me.onTransitionEnd, me); style.webkitTransitionDuration = '0ms'; for (property in config.from) { if (!config.from.hasOwnProperty(property)) { continue; } style[property] = config.from[property]; } setTimeout(function() { // If this element has been destroyed since the timeout started, do nothing if (!el.dom) { return; } // If this is a 3d animation we have to set the perspective on the parent if (config.is3d === true) { el.parent().setStyle({ // See https://sencha.jira.com/browse/TOUCH-1498 '-webkit-perspective': '1200', '-webkit-transform-style': 'preserve-3d' }); } style.webkitTransitionDuration = config.duration + 'ms'; style.webkitTransitionProperty = 'all'; style.webkitTransitionTimingFunction = config.easing; // Bind our listener that fires after the animation ends el.on('transitionend', me.onTransitionEnd, me, { single: true, config: config, after: after }); for (property in config.to) { if (!config.to.hasOwnProperty(property)) { continue; } style[property] = config.to[property]; } }, config.delay || 5); me.running[el.id] = config; return me; }, onTransitionEnd: function(ev, el, o) { el = Ext.get(el); if (this.running[el.id] === undefined) { return; } var style = el.dom.style, config = o.config, me = this, property; if (config.autoClear) { for (property in config.to) { if (!config.to.hasOwnProperty(property) || config[property] === false) { continue; } style[property] = ''; } } style.webkitTransitionDuration = null; style.webkitTransitionProperty = null; style.webkitTransitionTimingFunction = null; if (config.is3d) { el.parent().setStyle({ '-webkit-perspective': '', '-webkit-transform-style': '' }); } if (me.config.after) { me.config.after.call(config, el, config); } if (o.after) { o.after.call(config.scope || me, el, config); } delete me.running[el.id]; } }); Ext.Anim.seed = 1000; /** * Used to run an animation on a specific element. Use the config argument to customize the animation * @param {Ext.Element/HTMLElement} el The element to animate * @param {String} anim The animation type, defined in {@link Ext.anims} * @param {Object} config The config object for the animation * @method run */ Ext.Anim.run = function(el, anim, config) { if (el.isComponent) { el = el.el; } config = config || {}; if (anim.isAnim) { anim.run(el, config); } else { if (Ext.isObject(anim)) { if (config.before && anim.before) { config.before = Ext.createInterceptor(config.before, anim.before, anim.scope); } if (config.after && anim.after) { config.after = Ext.createInterceptor(config.after, anim.after, anim.scope); } config = Ext.apply({}, config, anim); anim = anim.type; } if (!Ext.anims[anim]) { throw anim + ' is not a valid animation type.'; } else { // add el check to make sure dom exists. if (el && el.dom) { Ext.anims[anim].run(el, config); } } } }; /** * @class Ext.anims *Defines different types of animations. flip, cube, wipe animations do not work on Android.
*Please refer to {@link Ext.Anim} on how to use animations.
* @singleton */ Ext.anims = { /** * Fade Animation */ fade: new Ext.Anim({ type: 'fade', before: function(el) { var fromOpacity = 1, toOpacity = 1, curZ = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'), zIndex = curZ; if (this.out) { toOpacity = 0; } else { zIndex = Math.abs(curZ) + 1; fromOpacity = 0; } this.from = { 'opacity': fromOpacity, 'z-index': zIndex }; this.to = { 'opacity': toOpacity, 'z-index': zIndex }; } }), /** * Slide Animation */ slide: new Ext.Anim({ direction: 'left', cover: false, reveal: false, opacity: false, 'z-index': false, before: function(el) { var currentZIndex = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'), currentOpacity = el.getStyle('opacity'), zIndex = currentZIndex + 1, out = this.out, direction = this.direction, toX = 0, toY = 0, fromX = 0, fromY = 0, elH = el.getHeight(), elW = el.getWidth(); if (direction == 'left' || direction == 'right') { if (out) { toX = -elW; } else { fromX = elW; } } else if (direction == 'up' || direction == 'down') { if (out) { toY = -elH; } else { fromY = elH; } } if (direction == 'right' || direction == 'down') { toY *= -1; toX *= -1; fromY *= -1; fromX *= -1; } if (this.cover && out) { toX = 0; toY = 0; zIndex = currentZIndex; } else if (this.reveal && !out) { fromX = 0; fromY = 0; zIndex = currentZIndex; } this.from = { '-webkit-transform': 'translate3d(' + fromX + 'px, ' + fromY + 'px, 0)', 'z-index': zIndex, 'opacity': currentOpacity - 0.01 }; this.to = { '-webkit-transform': 'translate3d(' + toX + 'px, ' + toY + 'px, 0)', 'z-index': zIndex, 'opacity': currentOpacity }; } }), /** * Pop Animation */ pop: new Ext.Anim({ scaleOnExit: true, before: function(el) { var fromScale = 1, toScale = 1, fromOpacity = 1, toOpacity = 1, curZ = el.getStyle('z-index') == 'auto' ? 0 : el.getStyle('z-index'), fromZ = curZ, toZ = curZ; if (!this.out) { fromScale = 0.01; fromZ = curZ + 1; toZ = curZ + 1; fromOpacity = 0; } else { if (this.scaleOnExit) { toScale = 0.01; toOpacity = 0; } else { toOpacity = 0.8; } } this.from = { '-webkit-transform': 'scale(' + fromScale + ')', '-webkit-transform-origin': '50% 50%', 'opacity': fromOpacity, 'z-index': fromZ }; this.to = { '-webkit-transform': 'scale(' + toScale + ')', '-webkit-transform-origin': '50% 50%', 'opacity': toOpacity, 'z-index': toZ }; } }), /** * Flip Animation */ flip: new Ext.Anim({ is3d: true, direction: 'left', before: function(el) { var rotateProp = 'Y', fromScale = 1, toScale = 1, fromRotate = 0, toRotate = 0; if (this.out) { toRotate = -180; toScale = 0.8; } else { fromRotate = 180; fromScale = 0.8; } if (this.direction == 'up' || this.direction == 'down') { rotateProp = 'X'; } if (this.direction == 'right' || this.direction == 'left') { toRotate *= -1; fromRotate *= -1; } this.from = { '-webkit-transform': 'rotate' + rotateProp + '(' + fromRotate + 'deg) scale(' + fromScale + ')', '-webkit-backface-visibility': 'hidden' }; this.to = { '-webkit-transform': 'rotate' + rotateProp + '(' + toRotate + 'deg) scale(' + toScale + ')', '-webkit-backface-visibility': 'hidden' }; } }), /** * Cube Animation */ cube: new Ext.Anim({ is3d: true, direction: 'left', style: 'outer', before: function(el) { var origin = '0% 0%', fromRotate = 0, toRotate = 0, rotateProp = 'Y', fromZ = 0, toZ = 0, fromOpacity = 1, toOpacity = 1, elW = el.getWidth(), elH = el.getHeight(), showTranslateZ = true, fromTranslate = ' translateX(0)', toTranslate = ''; if (this.direction == 'left' || this.direction == 'right') { if (this.out) { origin = '100% 100%'; toZ = elW; toOpacity = 0.5; toRotate = -90; } else { origin = '0% 0%'; fromZ = elW; fromOpacity = 0.5; fromRotate = 90; } } else if (this.direction == 'up' || this.direction == 'down') { rotateProp = 'X'; if (this.out) { origin = '100% 100%'; toZ = elH; toRotate = 90; } else { origin = '0% 0%'; fromZ = elH; fromRotate = -90; } } if (this.direction == 'down' || this.direction == 'right') { fromRotate *= -1; toRotate *= -1; origin = (origin == '0% 0%') ? '100% 100%': '0% 0%'; } if (this.style == 'inner') { fromZ *= -1; toZ *= -1; fromRotate *= -1; toRotate *= -1; if (!this.out) { toTranslate = ' translateX(0px)'; origin = '0% 50%'; } else { toTranslate = fromTranslate; origin = '100% 50%'; } } this.from = { '-webkit-transform': 'rotate' + rotateProp + '(' + fromRotate + 'deg)' + (showTranslateZ ? ' translateZ(' + fromZ + 'px)': '') + fromTranslate, '-webkit-transform-origin': origin }; this.to = { '-webkit-transform': 'rotate' + rotateProp + '(' + toRotate + 'deg) translateZ(' + toZ + 'px)' + toTranslate, '-webkit-transform-origin': origin }; }, duration: 250 }), /** * Wipe Animation. *Because of the amount of calculations involved, this animation is best used on small display * changes or specifically for phone environments. Does not currently accept any parameters.
*/ wipe: new Ext.Anim({ before: function(el) { var curZ = el.getStyle('z-index'), zIndex, mask = ''; if (!this.out) { zIndex = curZ + 1; mask = '-webkit-gradient(linear, left bottom, right bottom, from(transparent), to(#000), color-stop(66%, #000), color-stop(33%, transparent))'; this.from = { '-webkit-mask-image': mask, '-webkit-mask-size': el.getWidth() * 3 + 'px ' + el.getHeight() + 'px', 'z-index': zIndex, '-webkit-mask-position-x': 0 }; this.to = { '-webkit-mask-image': mask, '-webkit-mask-size': el.getWidth() * 3 + 'px ' + el.getHeight() + 'px', 'z-index': zIndex, '-webkit-mask-position-x': -el.getWidth() * 2 + 'px' }; } }, duration: 500 }) }; /** * This class compiles the XTemplate syntax into a function object. The function is used * like so: * * function (out, values, parent, xindex, xcount) { * // out is the output array to store results * // values, parent, xindex and xcount have their historical meaning * } * * @markdown * @private */ Ext.define('Ext.XTemplateCompiler', { extend: 'Ext.XTemplateParser', // Chrome really likes "new Function" to realize the code block (as in it is // 2x-3x faster to call it than using eval), but Firefox chokes on it badly. // IE and Opera are also fine with the "new Function" technique. useEval: Ext.isGecko, useFormat: true, propNameRe: /^[\w\d\$]*$/, compile: function (tpl) { var me = this, code = me.generate(tpl); // When using "new Function", we have to pass our "Ext" variable to it in order to // support sandboxing. If we did not, the generated function would use the global // "Ext", not the "Ext" from our sandbox (scope chain). // return me.useEval ? me.evalTpl(code) : (new Function('Ext', code))(Ext); }, generate: function (tpl) { var me = this; me.body = [ 'var c0=values, p0=parent, n0=xcount, i0=xindex;\n' ]; me.funcs = [ 'var fm=Ext.util.Format;' // note: Ext here is properly sandboxed ]; me.switches = []; me.parse(tpl); me.funcs.push( (me.useEval ? '$=' : 'return') + ' function (' + me.fnArgs + ') {', me.body.join(''), '}' ); var code = me.funcs.join('\n'); return code; }, //----------------------------------- // XTemplateParser callouts doText: function (text) { text = text.replace(this.aposRe, "\\'"); text = text.replace(this.newLineRe, '\\n'); this.body.push('out.push(\'', text, '\')\n'); }, doExpr: function (expr) { this.body.push('out.push(String(', expr, '))\n'); }, doTag: function (tag) { this.doExpr(this.parseTag(tag)); }, doElse: function () { this.body.push('} else {\n'); }, doEval: function (text) { this.body.push(text, '\n'); }, doIf: function (action, actions) { var me = this; // If it's just a propName, use it directly in the if if (me.propNameRe.test(action)) { me.body.push('if (', me.parseTag(action), ') {\n'); } // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values) else { me.body.push('if (', me.addFn(action), me.callFn, ') {\n'); } if (actions.exec) { me.doExec(actions.exec); } }, doElseIf: function (action, actions) { var me = this; // If it's just a propName, use it directly in the else if if (me.propNameRe.test(action)) { me.body.push('} else if (', me.parseTag(action), ') {\n'); } // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values) else { me.body.push('} else if (', me.addFn(action), me.callFn, ') {\n'); } if (actions.exec) { me.doExec(actions.exec); } }, doSwitch: function (action) { var me = this; // If it's just a propName, use it directly in the switch if (me.propNameRe.test(action)) { me.body.push('switch (', me.parseTag(action), ') {\n'); } // Otherwise, it must be an expression, and needs to be returned from an fn which uses with(values) else { me.body.push('switch (', me.addFn(action), me.callFn, ') {\n'); } me.switches.push(0); }, doCase: function (action) { var me = this, cases = Ext.isArray(action) ? action : [action], n = me.switches.length - 1, match, i; if (me.switches[n]) { me.body.push('break;\n'); } else { me.switches[n]++; } for (i = 0, n = cases.length; i < n; ++i) { match = me.intRe.exec(cases[i]); cases[i] = match ? match[1] : ("'" + cases[i].replace(me.aposRe,"\\'") + "'"); } me.body.push('case ', cases.join(': case '), ':\n'); }, doDefault: function () { var me = this, n = me.switches.length - 1; if (me.switches[n]) { me.body.push('break;\n'); } else { me.switches[n]++; } me.body.push('default:\n'); }, doEnd: function (type, actions) { var me = this, L = me.level-1; if (type == 'for') { /* To exit a for loop we must restore the outer loop's context. The code looks like this (which goes with that produced by doFor: for (...) { // the part generated by doFor ... // the body of the for loop // ... any tpl for exec statement goes here... } parent = p1; values = r2; xcount = n1; xindex = i1 */ if (actions.exec) { me.doExec(actions.exec); } me.body.push('}\n'); me.body.push('parent=p',L,';values=r',L+1,';xcount=n',L,';xindex=i',L,'\n'); } else if (type == 'if' || type == 'switch') { me.body.push('}\n'); } }, doFor: function (action, actions) { var me = this, s = me.addFn(action), L = me.level, up = L-1; /* We are trying to produce a block of code that looks like below. We use the nesting level to uniquely name the control variables. var c2 = f5.call(this, out, values, parent, xindex, xcount), // c2 is the context object for the for loop a2 = Ext.isArray(c2), // a2 is the isArray result for the context p2 = (parent=c1), // p2 is the parent context (of the outer for loop) r2 = values // r2 is the values object to // i2 is the loop index and n2 is the number (xcount) of this for loop for (var i2 = 0, n2 = a2 ? c2.length : (c2 ? 1 : 0), xcount = n2; i2 < n2; ++i2) { values=a2?c2[i2]:c2 // adjust special vars to inner scope xindex=i2+1 // xindex is 1-based The body of the loop is whatever comes between the tpl and /tpl statements (which is handled by doEnd). */ me.body.push('var c',L,'=',s,me.callFn,', a',L,'=Ext.isArray(c',L,'),p',L,'=(parent=c',up,'),r',L,'=values\n', 'for (var i',L,'=0,n',L,'=a',L,'?c',L,'.length:(c',L,'?1:0), xcount=n',L,';i',L,'Format Description Example returned values ------ ----------------------------------------------------------------------- ----------------------- d Day of the month, 2 digits with leading zeros 01 to 31 D A short textual representation of the day of the week Mon to Sun j Day of the month without leading zeros 1 to 31 l A full textual representation of the day of the week Sunday to Saturday N ISO-8601 numeric representation of the day of the week 1 (for Monday) through 7 (for Sunday) S English ordinal suffix for the day of the month, 2 characters st, nd, rd or th. Works well with j w Numeric representation of the day of the week 0 (for Sunday) to 6 (for Saturday) z The day of the year (starting from 0) 0 to 364 (365 in leap years) W ISO-8601 week number of year, weeks starting on Monday 01 to 53 F A full textual representation of a month, such as January or March January to December m Numeric representation of a month, with leading zeros 01 to 12 M A short textual representation of a month Jan to Dec n Numeric representation of a month, without leading zeros 1 to 12 t Number of days in the given month 28 to 31 L Whether it's a leap year 1 if it is a leap year, 0 otherwise. o ISO-8601 year number (identical to (Y), but if the ISO week number (W) Examples: 1998 or 2004 belongs to the previous or next year, that year is used instead) Y A full numeric representation of a year, 4 digits Examples: 1999 or 2003 y A two digit representation of a year Examples: 99 or 03 a Lowercase Ante meridiem and Post meridiem am or pm A Uppercase Ante meridiem and Post meridiem AM or PM g 12-hour format of an hour without leading zeros 1 to 12 G 24-hour format of an hour without leading zeros 0 to 23 h 12-hour format of an hour with leading zeros 01 to 12 H 24-hour format of an hour with leading zeros 00 to 23 i Minutes, with leading zeros 00 to 59 s Seconds, with leading zeros 00 to 59 u Decimal fraction of a second Examples: (minimum 1 digit, arbitrary number of digits allowed) 001 (i.e. 0.001s) or 100 (i.e. 0.100s) or 999 (i.e. 0.999s) or 999876543210 (i.e. 0.999876543210s) O Difference to Greenwich time (GMT) in hours and minutes Example: +1030 P Difference to Greenwich time (GMT) with colon between hours and minutes Example: -08:00 T Timezone abbreviation of the machine running the code Examples: EST, MDT, PDT ... Z Timezone offset in seconds (negative if west of UTC, positive if east) -43200 to 50400 c ISO 8601 date Notes: Examples: 1) If unspecified, the month / day defaults to the current month / day, 1991 or the time defaults to midnight, while the timezone defaults to the 1992-10 or browser's timezone. If a time is specified, it must include both hours 1993-09-20 or and minutes. The "T" delimiter, seconds, milliseconds and timezone 1994-08-19T16:20+01:00 or are optional. 1995-07-18T17:21:28-02:00 or 2) The decimal fraction of a second, if specified, must contain at 1996-06-17T18:22:29.98765+03:00 or least 1 digit (there is no limit to the maximum number 1997-05-16T19:23:30,12345-0400 or of digits allowed), and may be delimited by either a '.' or a ',' 1998-04-15T20:24:31.2468Z or Refer to the examples on the right for the various levels of 1999-03-14T20:24:32Z or date-time granularity which are supported, or see 2000-02-13T21:25:33 http://www.w3.org/TR/NOTE-datetime for more info. 2001-01-12 22:26:34 U Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) 1193432466 or -2138434463 MS Microsoft AJAX serialized dates \/Date(1238606590509)\/ (i.e. UTC milliseconds since epoch) or \/Date(1238606590509+0800)\/* * Example usage (note that you must escape format specifiers with '\\' to render them as character literals): *
// Sample date:
// 'Wed Jan 10 2007 15:05:01 GMT-0600 (Central Standard Time)'
var dt = new Date('1/10/2007 03:05:01 PM GMT-0600');
console.log(Ext.Date.format(dt, 'Y-m-d')); // 2007-01-10
console.log(Ext.Date.format(dt, 'F j, Y, g:i a')); // January 10, 2007, 3:05 pm
console.log(Ext.Date.format(dt, 'l, \\t\\he jS \\of F Y h:i:s A')); // Wednesday, the 10th of January 2007 03:05:01 PM
*
* Here are some standard date/time patterns that you might find helpful. They
* are not part of the source of Ext.Date, but to use them you can simply copy this
* block of code into any script that is included after Ext.Date and they will also become
* globally available on the Date object. Feel free to add or remove patterns as needed in your code.
*
Ext.Date.patterns = {
ISO8601Long:"Y-m-d H:i:s",
ISO8601Short:"Y-m-d",
ShortDate: "n/j/Y",
LongDate: "l, F d, Y",
FullDateTime: "l, F d, Y g:i:s A",
MonthDay: "F d",
ShortTime: "g:i A",
LongTime: "g:i:s A",
SortableDateTime: "Y-m-d\\TH:i:s",
UniversalSortableDateTime: "Y-m-d H:i:sO",
YearMonth: "F, Y"
};
*
* Example usage:
*
var dt = new Date();
console.log(Ext.Date.format(dt, Ext.Date.patterns.ShortDate));
* Developer-written, custom formats may be used by supplying both a formatting and a parsing function * which perform to specialized requirements. The functions are stored in {@link #parseFunctions} and {@link #formatFunctions}.
* @singleton */ /* * Most of the date-formatting functions below are the excellent work of Baron Schwartz. * (see http://www.xaprb.com/blog/2005/12/12/javascript-closures-for-runtime-efficiency/) * They generate precompiled functions from format patterns instead of parsing and * processing each pattern every time a date is formatted. These functions are available * on every Date object. */ (function() { // create private copy of Ext's Ext.util.Format.format() method // - to remove unnecessary dependency // - to resolve namespace conflict with MS-Ajax's implementation function xf(format) { var args = Array.prototype.slice.call(arguments, 1); return format.replace(/\{(\d+)\}/g, function(m, i) { return args[i]; }); } /** * Extra methods to be mixed into Ext.Date. * * Require this class to get Ext.Date with all the methods listed below. * * Using Ext.setup: * * Ext.setup({ * requires: 'Ext.DateExtras', * onReady: function() { * var date = new Date(); * alert(Ext.Date.format(date, 'j/d/Y')); * } * }); * * Using Ext.application: * * Ext.application({ * requires: 'Ext.DateExtras', * launch: function() { * var date = new Date(); * alert(Ext.Date.format(date, 'j/d/Y')); * } * }); * * @singleton */ Ext.DateExtras = { /** * Returns the current timestamp * @return {Date} The current timestamp * @method */ now: Date.now || function() { return +new Date(); }, /** * Returns the number of milliseconds between two dates * @param {Date} dateA The first date * @param {Date} dateB (optional) The second date, defaults to now * @return {Number} The difference in milliseconds */ getElapsed: function(dateA, dateB) { return Math.abs(dateA - (dateB || new Date())); }, /** * Global flag which determines if strict date parsing should be used. * Strict date parsing will not roll-over invalid dates, which is the * default behaviour of javascript Date objects. * (see {@link #parse} for more information) * Defaults to false. * @type Boolean */ useStrict: false, // private formatCodeToRegex: function(character, currentGroup) { // Note: currentGroup - position in regex result array (see notes for Ext.Date.parseCodes below) var p = utilDate.parseCodes[character]; if (p) { p = typeof p == 'function'? p() : p; utilDate.parseCodes[character] = p; // reassign function result to prevent repeated execution } return p ? Ext.applyIf({ c: p.c ? xf(p.c, currentGroup || "{0}") : p.c }, p) : { g: 0, c: null, s: Ext.String.escapeRegex(character) // treat unrecognised characters as literals }; }, /** *An object hash in which each property is a date parsing function. The property name is the * format string which that function parses.
*This object is automatically populated with date parsing functions as * date formats are requested for Ext standard formatting strings.
*Custom parsing functions may be inserted into this object, keyed by a name which from then on * may be used as a format string to {@link #parse}.
*
Example:
Ext.Date.parseFunctions['x-date-format'] = myDateParser;
* A parsing function should return a Date object, and is passed the following parameters:
date
: Stringstrict
: BooleanTo enable Dates to also be formatted according to that format, a corresponding * formatting function must be placed into the {@link #formatFunctions} property. * @property parseFunctions * @type Object */ parseFunctions: { "MS": function(input, strict) { // note: the timezone offset is ignored since the MS Ajax server sends // a UTC milliseconds-since-Unix-epoch value (negative values are allowed) var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/'); var r = (input || '').match(re); return r? new Date(((r[1] || '') + r[2]) * 1) : null; } }, parseRegexes: [], /** *
An object hash in which each property is a date formatting function. The property name is the * format string which corresponds to the produced formatted date string.
*This object is automatically populated with date formatting functions as * date formats are requested for Ext standard formatting strings.
*Custom formatting functions may be inserted into this object, keyed by a name which from then on * may be used as a format string to {@link #format}. Example:
Ext.Date.formatFunctions['x-date-format'] = myDateFormatter;
* A formatting function should return a string representation of the passed Date object, and is passed the following parameters:
date
: DateTo enable date strings to also be parsed according to that format, a corresponding * parsing function must be placed into the {@link #parseFunctions} property. * @property formatFunctions * @type Object */ formatFunctions: { "MS": function() { // UTC milliseconds since Unix epoch (MS-AJAX serialized date format (MRSF)) return '\\/Date(' + this.getTime() + ')\\/'; } }, y2kYear : 50, /** * Date interval constant * @type String */ MILLI : "ms", /** * Date interval constant * @type String */ SECOND : "s", /** * Date interval constant * @type String */ MINUTE : "mi", /** Date interval constant * @type String */ HOUR : "h", /** * Date interval constant * @type String */ DAY : "d", /** * Date interval constant * @type String */ MONTH : "mo", /** * Date interval constant * @type String */ YEAR : "y", /** *
An object hash containing default date values used during date parsing.
*The following properties are available:
y
: Numberm
: Numberd
: Numberh
: Numberi
: Numbers
: Numberms
: NumberOverride these properties to customize the default date values used by the {@link #parse} method.
*Note: In countries which experience Daylight Saving Time (i.e. DST), the h, i, s * and ms properties may coincide with the exact time in which DST takes effect. * It is the responsiblity of the developer to account for this.
* Example Usage: *
// set default day value to the first day of the month
Ext.Date.defaults.d = 1;
// parse a February date string containing only year and month values.
// setting the default day value to 1 prevents weird date rollover issues
// when attempting to parse the following date string on, for example, March 31st 2009.
Ext.Date.parse('2009-02', 'Y-m'); // returns a Date object representing February 1st 2009
* @property defaults
* @type Object
*/
defaults: {},
/**
* An array of textual day names.
* Override these values for international dates.
* Example:
*
Ext.Date.dayNames = [
'SundayInYourLang',
'MondayInYourLang',
...
];
* @type Array
*/
dayNames : [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
],
/**
* An array of textual month names.
* Override these values for international dates.
* Example:
*
Ext.Date.monthNames = [
'JanInYourLang',
'FebInYourLang',
...
];
* @type Array
*/
monthNames : [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
],
/**
* An object hash of zero-based javascript month numbers (with short month names as keys. note: keys are case-sensitive).
* Override these values for international dates.
* Example:
*
Ext.Date.monthNumbers = {
'ShortJanNameInYourLang':0,
'ShortFebNameInYourLang':1,
...
};
* @type Object
*/
monthNumbers : {
Jan:0,
Feb:1,
Mar:2,
Apr:3,
May:4,
Jun:5,
Jul:6,
Aug:7,
Sep:8,
Oct:9,
Nov:10,
Dec:11
},
/**
* The date format string that the {@link Ext.util.Format#date} function uses. * See {@link Ext.Date} for details.
*This defaults to m/d/Y
, but may be overridden in a locale file.
Ext.Date.formatCodes.x = "Ext.util.Format.leftPad(this.getDate(), 2, '0')";
console.log(Ext.Date.format(new Date(), 'X'); // returns the current day of the month
* @type Object
*/
formatCodes : {
d: "Ext.String.leftPad(this.getDate(), 2, '0')",
D: "Ext.Date.getShortDayName(this.getDay())", // get localised short day name
j: "this.getDate()",
l: "Ext.Date.dayNames[this.getDay()]",
N: "(this.getDay() ? this.getDay() : 7)",
S: "Ext.Date.getSuffix(this)",
w: "this.getDay()",
z: "Ext.Date.getDayOfYear(this)",
W: "Ext.String.leftPad(Ext.Date.getWeekOfYear(this), 2, '0')",
F: "Ext.Date.monthNames[this.getMonth()]",
m: "Ext.String.leftPad(this.getMonth() + 1, 2, '0')",
M: "Ext.Date.getShortMonthName(this.getMonth())", // get localised short month name
n: "(this.getMonth() + 1)",
t: "Ext.Date.getDaysInMonth(this)",
L: "(Ext.Date.isLeapYear(this) ? 1 : 0)",
o: "(this.getFullYear() + (Ext.Date.getWeekOfYear(this) == 1 && this.getMonth() > 0 ? +1 : (Ext.Date.getWeekOfYear(this) >= 52 && this.getMonth() < 11 ? -1 : 0)))",
Y: "Ext.String.leftPad(this.getFullYear(), 4, '0')",
y: "('' + this.getFullYear()).substring(2, 4)",
a: "(this.getHours() < 12 ? 'am' : 'pm')",
A: "(this.getHours() < 12 ? 'AM' : 'PM')",
g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
G: "this.getHours()",
h: "Ext.String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
H: "Ext.String.leftPad(this.getHours(), 2, '0')",
i: "Ext.String.leftPad(this.getMinutes(), 2, '0')",
s: "Ext.String.leftPad(this.getSeconds(), 2, '0')",
u: "Ext.String.leftPad(this.getMilliseconds(), 3, '0')",
O: "Ext.Date.getGMTOffset(this)",
P: "Ext.Date.getGMTOffset(this, true)",
T: "Ext.Date.getTimezone(this)",
Z: "(this.getTimezoneOffset() * -60)",
c: function() { // ISO-8601 -- GMT format
for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
var e = c.charAt(i);
code.push(e == "T" ? "'T'" : utilDate.getFormatCode(e)); // treat T as a character literal
}
return code.join(" + ");
},
/*
c: function() { // ISO-8601 -- UTC format
return [
"this.getUTCFullYear()", "'-'",
"Ext.util.Format.leftPad(this.getUTCMonth() + 1, 2, '0')", "'-'",
"Ext.util.Format.leftPad(this.getUTCDate(), 2, '0')",
"'T'",
"Ext.util.Format.leftPad(this.getUTCHours(), 2, '0')", "':'",
"Ext.util.Format.leftPad(this.getUTCMinutes(), 2, '0')", "':'",
"Ext.util.Format.leftPad(this.getUTCSeconds(), 2, '0')",
"'Z'"
].join(" + ");
},
*/
U: "Math.round(this.getTime() / 1000)"
},
/**
* Checks if the passed Date parameters will cause a javascript Date "rollover".
* @param {Number} year 4-digit year
* @param {Number} month 1-based month-of-year
* @param {Number} day Day of month
* @param {Number} hour (optional) Hour
* @param {Number} minute (optional) Minute
* @param {Number} second (optional) Second
* @param {Number} millisecond (optional) Millisecond
* @return {Boolean} true if the passed parameters do not cause a Date "rollover", false otherwise.
*/
isValid : function(y, m, d, h, i, s, ms) {
// setup defaults
h = h || 0;
i = i || 0;
s = s || 0;
ms = ms || 0;
// Special handling for year < 100
var dt = utilDate.add(new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms), utilDate.YEAR, y < 100 ? y - 100 : 0);
return y == dt.getFullYear() &&
m == dt.getMonth() + 1 &&
d == dt.getDate() &&
h == dt.getHours() &&
i == dt.getMinutes() &&
s == dt.getSeconds() &&
ms == dt.getMilliseconds();
},
/**
* Parses the passed string using the specified date format.
* Note that this function expects normal calendar dates, meaning that months are 1-based (i.e. 1 = January).
* The {@link #defaults} hash will be used for any date value (i.e. year, month, day, hour, minute, second or millisecond)
* which cannot be found in the passed string. If a corresponding default date value has not been specified in the {@link #defaults} hash,
* the current date's year, month, day or DST-adjusted zero-hour time value will be used instead.
* Keep in mind that the input date string must precisely match the specified format string
* in order for the parse operation to be successful (failed parse operations return a null value).
* Example:
//dt = Fri May 25 2007 (current date)
var dt = new Date();
//dt = Thu May 25 2006 (today's month/day in 2006)
dt = Ext.Date.parse("2006", "Y");
//dt = Sun Jan 15 2006 (all date parts specified)
dt = Ext.Date.parse("2006-01-15", "Y-m-d");
//dt = Sun Jan 15 2006 15:20:01
dt = Ext.Date.parse("2006-01-15 3:20:01 PM", "Y-m-d g:i:s A");
// attempt to parse Sun Feb 29 2006 03:20:01 in strict mode
dt = Ext.Date.parse("2006-02-29 03:20:01", "Y-m-d H:i:s", true); // returns null
* @param {String} input The raw date string.
* @param {String} format The expected date string format.
* @param {Boolean} strict (optional) True to validate date strings while parsing (i.e. prevents javascript Date "rollover")
(defaults to false). Invalid date strings will return null when parsed.
* @return {Date} The parsed Date.
*/
parse : function(input, format, strict) {
var p = utilDate.parseFunctions;
if (p[format] == null) {
utilDate.createParser(format);
}
return p[format](input, Ext.isDefined(strict) ? strict : utilDate.useStrict);
},
// Backwards compat
parseDate: function(input, format, strict){
return utilDate.parse(input, format, strict);
},
// private
getFormatCode : function(character) {
var f = utilDate.formatCodes[character];
if (f) {
f = typeof f == 'function'? f() : f;
utilDate.formatCodes[character] = f; // reassign function result to prevent repeated execution
}
// note: unknown characters are treated as literals
return f || ("'" + Ext.String.escape(character) + "'");
},
// private
createFormat : function(format) {
var code = [],
special = false,
ch = '';
for (var i = 0; i < format.length; ++i) {
ch = format.charAt(i);
if (!special && ch == "\\") {
special = true;
} else if (special) {
special = false;
code.push("'" + Ext.String.escape(ch) + "'");
} else {
code.push(utilDate.getFormatCode(ch));
}
}
utilDate.formatFunctions[format] = Ext.functionFactory("return " + code.join('+'));
},
// private
createParser : (function() {
var code = [
"var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
"def = Ext.Date.defaults,",
"results = String(input).match(Ext.Date.parseRegexes[{0}]);", // either null, or an array of matched strings
"if(results){",
"{1}",
"if(u != null){", // i.e. unix time is defined
"v = new Date(u * 1000);", // give top priority to UNIX time
"}else{",
// create Date object representing midnight of the current day;
// this will provide us with our date defaults
// (note: clearTime() handles Daylight Saving Time automatically)
"dt = Ext.Date.clearTime(new Date);",
// date calculations (note: these calculations create a dependency on Ext.Number.from())
"y = Ext.Number.from(y, Ext.Number.from(def.y, dt.getFullYear()));",
"m = Ext.Number.from(m, Ext.Number.from(def.m - 1, dt.getMonth()));",
"d = Ext.Number.from(d, Ext.Number.from(def.d, dt.getDate()));",
// time calculations (note: these calculations create a dependency on Ext.Number.from())
"h = Ext.Number.from(h, Ext.Number.from(def.h, dt.getHours()));",
"i = Ext.Number.from(i, Ext.Number.from(def.i, dt.getMinutes()));",
"s = Ext.Number.from(s, Ext.Number.from(def.s, dt.getSeconds()));",
"ms = Ext.Number.from(ms, Ext.Number.from(def.ms, dt.getMilliseconds()));",
"if(z >= 0 && y >= 0){",
// both the year and zero-based day of year are defined and >= 0.
// these 2 values alone provide sufficient info to create a full date object
// create Date object representing January 1st for the given year
// handle years < 100 appropriately
"v = Ext.Date.add(new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
// then add day of year, checking for Date "rollover" if necessary
"v = !strict? v : (strict === true && (z <= 364 || (Ext.Date.isLeapYear(v) && z <= 365))? Ext.Date.add(v, Ext.Date.DAY, z) : null);",
"}else if(strict === true && !Ext.Date.isValid(y, m + 1, d, h, i, s, ms)){", // check for Date "rollover"
"v = null;", // invalid date, so return null
"}else{",
// plain old Date object
// handle years < 100 properly
"v = Ext.Date.add(new Date(y < 100 ? 100 : y, m, d, h, i, s, ms), Ext.Date.YEAR, y < 100 ? y - 100 : 0);",
"}",
"}",
"}",
"if(v){",
// favour UTC offset over GMT offset
"if(zz != null){",
// reset to UTC, then add offset
"v = Ext.Date.add(v, Ext.Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
"}else if(o){",
// reset to GMT, then add offset
"v = Ext.Date.add(v, Ext.Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
"}",
"}",
"return v;"
].join('\n');
return function(format) {
var regexNum = utilDate.parseRegexes.length,
currentGroup = 1,
calc = [],
regex = [],
special = false,
ch = "";
for (var i = 0; i < format.length; ++i) {
ch = format.charAt(i);
if (!special && ch == "\\") {
special = true;
} else if (special) {
special = false;
regex.push(Ext.String.escape(ch));
} else {
var obj = utilDate.formatCodeToRegex(ch, currentGroup);
currentGroup += obj.g;
regex.push(obj.s);
if (obj.g && obj.c) {
calc.push(obj.c);
}
}
}
utilDate.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
utilDate.parseFunctions[format] = Ext.functionFactory("input", "strict", xf(code, regexNum, calc.join('')));
};
})(),
// private
parseCodes : {
/*
* Notes:
* g = {Number} calculation group (0 or 1. only group 1 contributes to date calculations.)
* c = {String} calculation method (required for group 1. null for group 0. {0} = currentGroup - position in regex result array)
* s = {String} regex pattern. all matches are stored in results[], and are accessible by the calculation mapped to 'c'
*/
d: {
g:1,
c:"d = parseInt(results[{0}], 10);\n",
s:"(\\d{2})" // day of month with leading zeroes (01 - 31)
},
j: {
g:1,
c:"d = parseInt(results[{0}], 10);\n",
s:"(\\d{1,2})" // day of month without leading zeroes (1 - 31)
},
D: function() {
for (var a = [], i = 0; i < 7; a.push(utilDate.getShortDayName(i)), ++i); // get localised short day names
return {
g:0,
c:null,
s:"(?:" + a.join("|") +")"
};
},
l: function() {
return {
g:0,
c:null,
s:"(?:" + utilDate.dayNames.join("|") + ")"
};
},
N: {
g:0,
c:null,
s:"[1-7]" // ISO-8601 day number (1 (monday) - 7 (sunday))
},
S: {
g:0,
c:null,
s:"(?:st|nd|rd|th)"
},
w: {
g:0,
c:null,
s:"[0-6]" // javascript day number (0 (sunday) - 6 (saturday))
},
z: {
g:1,
c:"z = parseInt(results[{0}], 10);\n",
s:"(\\d{1,3})" // day of the year (0 - 364 (365 in leap years))
},
W: {
g:0,
c:null,
s:"(?:\\d{2})" // ISO-8601 week number (with leading zero)
},
F: function() {
return {
g:1,
c:"m = parseInt(Ext.Date.getMonthNumber(results[{0}]), 10);\n", // get localised month number
s:"(" + utilDate.monthNames.join("|") + ")"
};
},
M: function() {
for (var a = [], i = 0; i < 12; a.push(utilDate.getShortMonthName(i)), ++i); // get localised short month names
return Ext.applyIf({
s:"(" + a.join("|") + ")"
}, utilDate.formatCodeToRegex("F"));
},
m: {
g:1,
c:"m = parseInt(results[{0}], 10) - 1;\n",
s:"(\\d{2})" // month number with leading zeros (01 - 12)
},
n: {
g:1,
c:"m = parseInt(results[{0}], 10) - 1;\n",
s:"(\\d{1,2})" // month number without leading zeros (1 - 12)
},
t: {
g:0,
c:null,
s:"(?:\\d{2})" // no. of days in the month (28 - 31)
},
L: {
g:0,
c:null,
s:"(?:1|0)"
},
o: function() {
return utilDate.formatCodeToRegex("Y");
},
Y: {
g:1,
c:"y = parseInt(results[{0}], 10);\n",
s:"(\\d{4})" // 4-digit year
},
y: {
g:1,
c:"var ty = parseInt(results[{0}], 10);\n"
+ "y = ty > Ext.Date.y2kYear ? 1900 + ty : 2000 + ty;\n", // 2-digit year
s:"(\\d{1,2})"
},
/*
* In the am/pm parsing routines, we allow both upper and lower case
* even though it doesn't exactly match the spec. It gives much more flexibility
* in being able to specify case insensitive regexes.
*/
a: {
g:1,
c:"if (/(am)/i.test(results[{0}])) {\n"
+ "if (!h || h == 12) { h = 0; }\n"
+ "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
s:"(am|pm|AM|PM)"
},
A: {
g:1,
c:"if (/(am)/i.test(results[{0}])) {\n"
+ "if (!h || h == 12) { h = 0; }\n"
+ "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
s:"(AM|PM|am|pm)"
},
g: function() {
return utilDate.formatCodeToRegex("G");
},
G: {
g:1,
c:"h = parseInt(results[{0}], 10);\n",
s:"(\\d{1,2})" // 24-hr format of an hour without leading zeroes (0 - 23)
},
h: function() {
return utilDate.formatCodeToRegex("H");
},
H: {
g:1,
c:"h = parseInt(results[{0}], 10);\n",
s:"(\\d{2})" // 24-hr format of an hour with leading zeroes (00 - 23)
},
i: {
g:1,
c:"i = parseInt(results[{0}], 10);\n",
s:"(\\d{2})" // minutes with leading zeros (00 - 59)
},
s: {
g:1,
c:"s = parseInt(results[{0}], 10);\n",
s:"(\\d{2})" // seconds with leading zeros (00 - 59)
},
u: {
g:1,
c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
s:"(\\d+)" // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
},
O: {
g:1,
c:[
"o = results[{0}];",
"var sn = o.substring(0,1),", // get + / - sign
"hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
"mn = o.substring(3,5) % 60;", // get minutes
"o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
].join("\n"),
s: "([+\-]\\d{4})" // GMT offset in hrs and mins
},
P: {
g:1,
c:[
"o = results[{0}];",
"var sn = o.substring(0,1),", // get + / - sign
"hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),", // get hours (performs minutes-to-hour conversion also, just in case)
"mn = o.substring(4,6) % 60;", // get minutes
"o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + Ext.String.leftPad(hr, 2, '0') + Ext.String.leftPad(mn, 2, '0')) : null;\n" // -12hrs <= GMT offset <= 14hrs
].join("\n"),
s: "([+\-]\\d{2}:\\d{2})" // GMT offset in hrs and mins (with colon separator)
},
T: {
g:0,
c:null,
s:"[A-Z]{1,4}" // timezone abbrev. may be between 1 - 4 chars
},
Z: {
g:1,
c:"zz = results[{0}] * 1;\n" // -43200 <= UTC offset <= 50400
+ "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
s:"([+\-]?\\d{1,5})" // leading '+' sign is optional for UTC offset
},
c: function() {
var calc = [],
arr = [
utilDate.formatCodeToRegex("Y", 1), // year
utilDate.formatCodeToRegex("m", 2), // month
utilDate.formatCodeToRegex("d", 3), // day
utilDate.formatCodeToRegex("h", 4), // hour
utilDate.formatCodeToRegex("i", 5), // minute
utilDate.formatCodeToRegex("s", 6), // second
{c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"}, // decimal fraction of a second (minimum = 1 digit, maximum = unlimited)
{c:[ // allow either "Z" (i.e. UTC) or "-0530" or "+08:00" (i.e. UTC offset) timezone delimiters. assumes local timezone if no timezone is specified
"if(results[8]) {", // timezone specified
"if(results[8] == 'Z'){",
"zz = 0;", // UTC
"}else if (results[8].indexOf(':') > -1){",
utilDate.formatCodeToRegex("P", 8).c, // timezone offset with colon separator
"}else{",
utilDate.formatCodeToRegex("O", 8).c, // timezone offset without colon separator
"}",
"}"
].join('\n')}
];
for (var i = 0, l = arr.length; i < l; ++i) {
calc.push(arr[i].c);
}
return {
g:1,
c:calc.join(""),
s:[
arr[0].s, // year (required)
"(?:", "-", arr[1].s, // month (optional)
"(?:", "-", arr[2].s, // day (optional)
"(?:",
"(?:T| )?", // time delimiter -- either a "T" or a single blank space
arr[3].s, ":", arr[4].s, // hour AND minute, delimited by a single colon (optional). MUST be preceded by either a "T" or a single blank space
"(?::", arr[5].s, ")?", // seconds (optional)
"(?:(?:\\.|,)(\\d+))?", // decimal fraction of a second (e.g. ",12345" or ".98765") (optional)
"(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?", // "Z" (UTC) or "-0530" (UTC offset without colon delimiter) or "+08:00" (UTC offset with colon delimiter) (optional)
")?",
")?",
")?"
].join("")
};
},
U: {
g:1,
c:"u = parseInt(results[{0}], 10);\n",
s:"(-?\\d+)" // leading minus sign indicates seconds before UNIX epoch
}
},
//Old Ext.Date prototype methods.
// private
dateFormat: function(date, format) {
return utilDate.format(date, format);
},
/**
* Formats a date given the supplied format string.
* @param {Date} date The date to format
* @param {String} format The format string
* @return {String} The formatted date
*/
format: function(date, format) {
if (utilDate.formatFunctions[format] == null) {
utilDate.createFormat(format);
}
var result = utilDate.formatFunctions[format].call(date);
return result + '';
},
/**
* Get the timezone abbreviation of the current date (equivalent to the format specifier 'T').
*
* Note: The date string returned by the javascript Date object's toString() method varies
* between browsers (e.g. FF vs IE) and system region settings (e.g. IE in Asia vs IE in America).
* For a given date string e.g. "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)",
* getTimezone() first tries to get the timezone abbreviation from between a pair of parentheses
* (which may or may not be present), failing which it proceeds to get the timezone abbreviation
* from the GMT offset portion of the date string.
* @param {Date} date The date
* @return {String} The abbreviated timezone name (e.g. 'CST', 'PDT', 'EDT', 'MPST' ...).
*/
getTimezone : function(date) {
// the following list shows the differences between date strings from different browsers on a WinXP SP2 machine from an Asian locale:
//
// Opera : "Thu, 25 Oct 2007 22:53:45 GMT+0800" -- shortest (weirdest) date string of the lot
// Safari : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone (same as FF)
// FF : "Thu Oct 25 2007 22:55:35 GMT+0800 (Malay Peninsula Standard Time)" -- value in parentheses always gives the correct timezone
// IE : "Thu Oct 25 22:54:35 UTC+0800 2007" -- (Asian system setting) look for 3-4 letter timezone abbrev
// IE : "Thu Oct 25 17:06:37 PDT 2007" -- (American system setting) look for 3-4 letter timezone abbrev
//
// this crazy regex attempts to guess the correct timezone abbreviation despite these differences.
// step 1: (?:\((.*)\) -- find timezone in parentheses
// step 2: ([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?) -- if nothing was found in step 1, find timezone from timezone offset portion of date string
// step 3: remove all non uppercase characters found in step 1 and 2
return date.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
},
/**
* Get the offset from GMT of the current date (equivalent to the format specifier 'O').
* @param {Date} date The date
* @param {Boolean} colon (optional) true to separate the hours and minutes with a colon (defaults to false).
* @return {String} The 4-character offset string prefixed with + or - (e.g. '-0600').
*/
getGMTOffset : function(date, colon) {
var offset = date.getTimezoneOffset();
return (offset > 0 ? "-" : "+")
+ Ext.String.leftPad(Math.floor(Math.abs(offset) / 60), 2, "0")
+ (colon ? ":" : "")
+ Ext.String.leftPad(Math.abs(offset % 60), 2, "0");
},
/**
* Get the numeric day number of the year, adjusted for leap year.
* @param {Date} date The date
* @return {Number} 0 to 364 (365 in leap years).
*/
getDayOfYear: function(date) {
var num = 0,
d = Ext.Date.clone(date),
m = date.getMonth(),
i;
for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
num += utilDate.getDaysInMonth(d);
}
return num + date.getDate() - 1;
},
/**
* Get the numeric ISO-8601 week number of the year.
* (equivalent to the format specifier 'W', but without a leading zero).
* @param {Date} date The date
* @return {Number} 1 to 53
* @method
*/
getWeekOfYear : (function() {
// adapted from http://www.merlyn.demon.co.uk/weekcalc.htm
var ms1d = 864e5, // milliseconds in a day
ms7d = 7 * ms1d; // milliseconds in a week
return function(date) { // return a closure so constants get calculated only once
var DC3 = Date.UTC(date.getFullYear(), date.getMonth(), date.getDate() + 3) / ms1d, // an Absolute Day Number
AWN = Math.floor(DC3 / 7), // an Absolute Week Number
Wyr = new Date(AWN * ms7d).getUTCFullYear();
return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
};
})(),
/**
* Checks if the current date falls within a leap year.
* @param {Date} date The date
* @return {Boolean} True if the current date falls within a leap year, false otherwise.
*/
isLeapYear : function(date) {
var year = date.getFullYear();
return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
},
/**
* Get the first day of the current month, adjusted for leap year. The returned value
* is the numeric day index within the week (0-6) which can be used in conjunction with
* the {@link #monthNames} array to retrieve the textual day name.
* Example:
*
var dt = new Date('1/10/2007'),
firstDay = Ext.Date.getFirstDayOfMonth(dt);
console.log(Ext.Date.dayNames[firstDay]); //output: 'Monday'
*
* @param {Date} date The date
* @return {Number} The day number (0-6).
*/
getFirstDayOfMonth : function(date) {
var day = (date.getDay() - (date.getDate() - 1)) % 7;
return (day < 0) ? (day + 7) : day;
},
/**
* Get the last day of the current month, adjusted for leap year. The returned value
* is the numeric day index within the week (0-6) which can be used in conjunction with
* the {@link #monthNames} array to retrieve the textual day name.
* Example:
*
var dt = new Date('1/10/2007'),
lastDay = Ext.Date.getLastDayOfMonth(dt);
console.log(Ext.Date.dayNames[lastDay]); //output: 'Wednesday'
*
* @param {Date} date The date
* @return {Number} The day number (0-6).
*/
getLastDayOfMonth : function(date) {
return utilDate.getLastDateOfMonth(date).getDay();
},
/**
* Get the date of the first day of the month in which this date resides.
* @param {Date} date The date
* @return {Date}
*/
getFirstDateOfMonth : function(date) {
return new Date(date.getFullYear(), date.getMonth(), 1);
},
/**
* Get the date of the last day of the month in which this date resides.
* @param {Date} date The date
* @return {Date}
*/
getLastDateOfMonth : function(date) {
return new Date(date.getFullYear(), date.getMonth(), utilDate.getDaysInMonth(date));
},
/**
* Get the number of days in the current month, adjusted for leap year.
* @param {Date} date The date
* @return {Number} The number of days in the month.
* @method
*/
getDaysInMonth: (function() {
var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
return function(date) { // return a closure for efficiency
var m = date.getMonth();
return m == 1 && utilDate.isLeapYear(date) ? 29 : daysInMonth[m];
};
})(),
/**
* Get the English ordinal suffix of the current day (equivalent to the format specifier 'S').
* @param {Date} date The date
* @return {String} 'st, 'nd', 'rd' or 'th'.
*/
getSuffix : function(date) {
switch (date.getDate()) {
case 1:
case 21:
case 31:
return "st";
case 2:
case 22:
return "nd";
case 3:
case 23:
return "rd";
default:
return "th";
}
},
/**
* Creates and returns a new Date instance with the exact same date value as the called instance.
* Dates are copied and passed by reference, so if a copied date variable is modified later, the original
* variable will also be changed. When the intention is to create a new variable that will not
* modify the original instance, you should create a clone.
*
* Example of correctly cloning a date:
*
//wrong way:
var orig = new Date('10/1/2006');
var copy = orig;
copy.setDate(5);
console.log(orig); //returns 'Thu Oct 05 2006'!
//correct way:
var orig = new Date('10/1/2006'),
copy = Ext.Date.clone(orig);
copy.setDate(5);
console.log(orig); //returns 'Thu Oct 01 2006'
*
* @param {Date} date The date
* @return {Date} The new Date instance.
*/
clone : function(date) {
return new Date(date.getTime());
},
/**
* Checks if the current date is affected by Daylight Saving Time (DST).
* @param {Date} date The date
* @return {Boolean} True if the current date is affected by DST.
*/
isDST : function(date) {
// adapted from http://sencha.com/forum/showthread.php?p=247172#post247172
// courtesy of @geoffrey.mcgill
return new Date(date.getFullYear(), 0, 1).getTimezoneOffset() != date.getTimezoneOffset();
},
/**
* Attempts to clear all time information from this Date by setting the time to midnight of the same day,
* automatically adjusting for Daylight Saving Time (DST) where applicable.
* (note: DST timezone information for the browser's host operating system is assumed to be up-to-date)
* @param {Date} date The date
* @param {Boolean} clone true to create a clone of this date, clear the time and return it (defaults to false).
* @return {Date} this or the clone.
*/
clearTime : function(date, clone) {
if (clone) {
return Ext.Date.clearTime(Ext.Date.clone(date));
}
// get current date before clearing time
var d = date.getDate();
// clear time
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
if (date.getDate() != d) { // account for DST (i.e. day of month changed when setting hour = 0)
// note: DST adjustments are assumed to occur in multiples of 1 hour (this is almost always the case)
// refer to http://www.timeanddate.com/time/aboutdst.html for the (rare) exceptions to this rule
// increment hour until cloned date == current date
for (var hr = 1, c = utilDate.add(date, Ext.Date.HOUR, hr); c.getDate() != d; hr++, c = utilDate.add(date, Ext.Date.HOUR, hr));
date.setDate(d);
date.setHours(c.getHours());
}
return date;
},
/**
* Provides a convenient method for performing basic date arithmetic. This method
* does not modify the Date instance being called - it creates and returns
* a new Date instance containing the resulting date value.
*
* Examples:
*
// Basic usage:
var dt = Ext.Date.add(new Date('10/29/2006'), Ext.Date.DAY, 5);
console.log(dt); //returns 'Fri Nov 03 2006 00:00:00'
// Negative values will be subtracted:
var dt2 = Ext.Date.add(new Date('10/1/2006'), Ext.Date.DAY, -5);
console.log(dt2); //returns 'Tue Sep 26 2006 00:00:00'
*
*
* @param {Date} date The date to modify
* @param {String} interval A valid date interval enum value.
* @param {Number} value The amount to add to the current date.
* @return {Date} The new Date instance.
*/
add : function(date, interval, value) {
var d = Ext.Date.clone(date),
Date = Ext.Date;
if (!interval || value === 0) return d;
switch(interval.toLowerCase()) {
case Ext.Date.MILLI:
d.setMilliseconds(d.getMilliseconds() + value);
break;
case Ext.Date.SECOND:
d.setSeconds(d.getSeconds() + value);
break;
case Ext.Date.MINUTE:
d.setMinutes(d.getMinutes() + value);
break;
case Ext.Date.HOUR:
d.setHours(d.getHours() + value);
break;
case Ext.Date.DAY:
d.setDate(d.getDate() + value);
break;
case Ext.Date.MONTH:
var day = date.getDate();
if (day > 28) {
day = Math.min(day, Ext.Date.getLastDateOfMonth(Ext.Date.add(Ext.Date.getFirstDateOfMonth(date), 'mo', value)).getDate());
}
d.setDate(day);
d.setMonth(date.getMonth() + value);
break;
case Ext.Date.YEAR:
d.setFullYear(date.getFullYear() + value);
break;
}
return d;
},
/**
* Checks if a date falls on or between the given start and end dates.
* @param {Date} date The date to check
* @param {Date} start Start date
* @param {Date} end End date
* @return {Boolean} true if this date falls on or between the given start and end dates.
*/
between : function(date, start, end) {
var t = date.getTime();
return start.getTime() <= t && t <= end.getTime();
}
};
var utilDate = Ext.DateExtras;
Ext.apply(Ext.Date, utilDate);
})();
/**
* @private
*/
Ext.define('Ext.fx.Easing', {
requires: ['Ext.fx.easing.Linear'],
constructor: function(easing) {
return Ext.factory(easing, Ext.fx.easing.Linear, null, 'easing');
}
});
/**
* @class Ext.fx.easing.BoundMomentum
*
* This easing is typically used for {@link Ext.scroll.Scroller}. It's a combination of
* {@link Ext.fx.easing.Momentum} and {@link Ext.fx.easing.Bounce}, which emulates deceleration when the animated element
* is still within its boundary, then bouncing back (snapping) when it's out-of-bound.
*/
Ext.define('Ext.fx.easing.BoundMomentum', {
extend: 'Ext.fx.easing.Abstract',
requires: [
'Ext.fx.easing.Momentum',
'Ext.fx.easing.Bounce'
],
config: {
/**
* @cfg {Object} momentum
* A valid config object for {@link Ext.fx.easing.Momentum}
* @accessor
*/
momentum: null,
/**
* @cfg {Object} bounce
* A valid config object for {@link Ext.fx.easing.Bounce}
* @accessor
*/
bounce: null,
minMomentumValue: 0,
maxMomentumValue: 0,
/**
* @cfg {Number} minVelocity
* The minimum velocity to end this easing
* @accessor
*/
minVelocity: 0.01,
/**
* @cfg {Number} startVelocity
* The start velocity
* @accessor
*/
startVelocity: 0
},
applyMomentum: function(config, currentEasing) {
return Ext.factory(config, Ext.fx.easing.Momentum, currentEasing);
},
applyBounce: function(config, currentEasing) {
return Ext.factory(config, Ext.fx.easing.Bounce, currentEasing);
},
updateStartTime: function(startTime) {
this.getMomentum().setStartTime(startTime);
this.callParent(arguments);
},
updateStartVelocity: function(startVelocity) {
this.getMomentum().setStartVelocity(startVelocity);
},
updateStartValue: function(startValue) {
this.getMomentum().setStartValue(startValue);
},
reset: function() {
this.lastValue = null;
this.isBouncingBack = false;
this.isOutOfBound = false;
return this.callParent(arguments);
},
getValue: function() {
var momentum = this.getMomentum(),
bounce = this.getBounce(),
startVelocity = momentum.getStartVelocity(),
direction = startVelocity > 0 ? 1 : -1,
minValue = this.getMinMomentumValue(),
maxValue = this.getMaxMomentumValue(),
boundedValue = (direction == 1) ? maxValue : minValue,
lastValue = this.lastValue,
value, velocity;
if (startVelocity === 0) {
return this.getStartValue();
}
if (!this.isOutOfBound) {
value = momentum.getValue();
velocity = momentum.getVelocity();
if (Math.abs(velocity) < this.getMinVelocity()) {
this.isEnded = true;
}
if (value >= minValue && value <= maxValue) {
return value;
}
this.isOutOfBound = true;
bounce.setStartTime(Ext.Date.now())
.setStartVelocity(velocity)
.setStartValue(boundedValue);
}
value = bounce.getValue();
if (!this.isEnded) {
if (!this.isBouncingBack) {
if (lastValue !== null) {
if ((direction == 1 && value < lastValue) || (direction == -1 && value > lastValue)) {
this.isBouncingBack = true;
}
}
}
else {
if (Math.round(value) == boundedValue) {
this.isEnded = true;
}
}
}
this.lastValue = value;
return value;
}
});
/**
* @private
*/
Ext.define('Ext.fx.easing.EaseIn', {
extend: 'Ext.fx.easing.Linear',
alias: 'easing.ease-in',
config: {
exponent: 4,
duration: 1500
},
getValue: function() {
var deltaTime = Ext.Date.now() - this.getStartTime(),
duration = this.getDuration(),
startValue = this.getStartValue(),
endValue = this.getEndValue(),
distance = this.distance,
theta = deltaTime / duration,
thetaEnd = Math.pow(theta, this.getExponent()),
currentValue = startValue + (thetaEnd * distance);
if (deltaTime >= duration) {
this.isEnded = true;
return endValue;
}
return currentValue;
}
});
/**
* @private
*/
Ext.define('Ext.fx.easing.EaseOut', {
extend: 'Ext.fx.easing.Linear',
alias: 'easing.ease-out',
config: {
exponent: 4,
duration: 1500
},
getValue: function() {
var deltaTime = Ext.Date.now() - this.getStartTime(),
duration = this.getDuration(),
startValue = this.getStartValue(),
endValue = this.getEndValue(),
distance = this.distance,
theta = deltaTime / duration,
thetaC = 1 - theta,
thetaEnd = 1 - Math.pow(thetaC, this.getExponent()),
currentValue = startValue + (thetaEnd * distance);
if (deltaTime >= duration) {
this.isEnded = true;
return endValue;
}
return currentValue;
}
});
Ext.define('Ext.log.formatter.Default', {
extend: 'Ext.log.formatter.Formatter',
config: {
messageFormat: "[{priorityName}][{callerDisplayName}] {message}"
},
format: function(event) {
var event = Ext.merge({}, event, {
priorityName: event.priorityName.toUpperCase()
});
return this.callParent([event]);
}
});
Ext.define('Ext.log.formatter.Identity', {
extend: 'Ext.log.formatter.Default',
config: {
messageFormat: "[{osIdentity}][{browserIdentity}][{timestamp}][{priorityName}][{callerDisplayName}] {message}"
},
format: function(event) {
event.timestamp = Ext.Date.toString();
event.browserIdentity = Ext.browser.name + ' ' + Ext.browser.version;
event.osIdentity = Ext.os.name + ' ' + Ext.os.version;
return this.callParent(arguments);
}
});
Ext.define('Ext.log.writer.Console', {
extend: 'Ext.log.writer.Writer',
config: {
throwOnErrors: true,
throwOnWarnings: false
},
doWrite: function(event) {
var message = event.message,
priority = event.priorityName,
consoleMethod;
if (priority === 'error' && this.getThrowOnErrors()) {
throw new Error(message);
}
if (typeof console !== 'undefined') {
consoleMethod = priority;
if (consoleMethod === 'deprecate') {
consoleMethod = 'warn';
}
if (consoleMethod === 'warn' && this.getThrowOnWarnings()) {
throw new Error(message);
}
if (!(consoleMethod in console)) {
consoleMethod = 'log';
}
console[consoleMethod](message);
}
}
});
Ext.define('Ext.log.writer.DocumentTitle', {
extend: 'Ext.log.writer.Writer',
doWrite: function(event) {
var message = event.message;
document.title = message;
}
});
/**
* @private
*/
Ext.define('Ext.mixin.Filterable', {
extend: 'Ext.mixin.Mixin',
requires: [
'Ext.util.Filter'
],
mixinConfig: {
id: 'filterable'
},
config: {
/**
* @cfg {Array} filters
* An array with filters. A filter can be an instance of Ext.util.Filter,
* an object representing an Ext.util.Filter configuration, or a filter function.
*/
filters: null,
/**
* @cfg {String} filterRoot
* The root inside each item in which the properties exist that we want to filter on.
* This is useful for filtering records in which the data exists inside a 'data' property.
*/
filterRoot: null
},
/**
* @property {Boolean} dirtyFilterFn
* A flag indicating wether the currently cashed filter function is still valid. Read-only.
*/
dirtyFilterFn: false,
/**
* @property currentSortFn
* This is the cached sorting function which is a generated function that calls all the
* configured sorters in the correct order. This is a read-only property.
*/
filterFn: null,
/**
* @property {Boolean} filtered
* A read-only flag indicating if this object is filtered
*/
filtered: false,
applyFilters: function(filters, collection) {
if (!collection) {
collection = this.createFiltersCollection();
}
collection.clear();
this.filtered = false;
this.dirtyFilterFn = true;
if (filters) {
this.addFilters(filters);
}
return collection;
},
createFiltersCollection: function() {
this._filters = Ext.create('Ext.util.Collection', function(filter) {
return filter.getId();
});
return this._filters;
},
/**
* This method adds a filter.
* @param {Ext.util.Sorter/Function/Object} filter Can be an instance of Ext.util.Filter,
* an object representing an Ext.util.Filter configuration, or a filter function.
*/
addFilter: function(filter) {
this.addFilters([filter]);
},
/**
* This method adds all the filters in a passed array.
* @param {Array} filters An array with filters. A filter can be an instance of Ext.util.Filter,
* an object representing an Ext.util.Filter configuration, or a filter function.
*/
addFilters: function(filters) {
var currentFilters = this.getFilters();
return this.insertFilters(currentFilters ? currentFilters.length : 0, filters);
},
/**
* This method adds a filter at a given index.
* @param {Number} index The index at which to insert the filter.
* @param {Ext.util.Sorter/Function/Object} filter Can be an instance of Ext.util.Filter,
* an object representing an Ext.util.Filter configuration, or a filter function.
*/
insertFilter: function(index, filter) {
return this.insertFilters(index, [filter]);
},
/**
* This method inserts all the filters in the passed array at the given index.
* @param {Number} index The index at which to insert the filters.
* @param {Array} filters Each filter can be an instance of Ext.util.Filter,
* an object representing an Ext.util.Filter configuration, or a filter function.
*/
insertFilters: function(index, filters) {
// We begin by making sure we are dealing with an array of sorters
if (!Ext.isArray(filters)) {
filters = [filters];
}
var ln = filters.length,
filterRoot = this.getFilterRoot(),
currentFilters = this.getFilters(),
newFilters = [],
filterConfig, i, filter;
if (!currentFilters) {
currentFilters = this.createFiltersCollection();
}
// We first have to convert every sorter into a proper Sorter instance
for (i = 0; i < ln; i++) {
filter = filters[i];
filterConfig = {
root: filterRoot
};
if (Ext.isFunction(filter)) {
filterConfig.filterFn = filter;
}
// If we are dealing with an object, we assume its a Sorter configuration. In this case
// we create an instance of Sorter passing this configuration.
else if (Ext.isObject(filter)) {
if (!filter.isFilter) {
if (filter.fn) {
filter.filterFn = filter.fn;
delete filter.fn;
}
filterConfig = Ext.apply(filterConfig, filter);
}
else {
newFilters.push(filter);
if (!filter.getRoot()) {
filter.setRoot(filterRoot);
}
continue;
}
}
// Finally we get to the point where it has to be invalid
else {
Ext.Logger.warn('Invalid filter specified:', filter);
}
// If a sorter config was created, make it an instance
filter = Ext.create('Ext.util.Filter', filterConfig);
newFilters.push(filter);
}
// Now lets add the newly created sorters.
for (i = 0, ln = newFilters.length; i < ln; i++) {
currentFilters.insert(index + i, newFilters[i]);
}
this.dirtyFilterFn = true;
if (currentFilters.length) {
this.filtered = true;
}
return currentFilters;
},
/**
* This method removes all the filters in a passed array.
* @param {Array} filters Each value in the array can be a string (property name),
* function (sorterFn), an object containing a property and value keys or
* {@link Ext.util.Sorter Sorter} instance.
*/
removeFilters: function(filters) {
// We begin by making sure we are dealing with an array of sorters
if (!Ext.isArray(filters)) {
filters = [filters];
}
var ln = filters.length,
currentFilters = this.getFilters(),
i, filter;
for (i = 0; i < ln; i++) {
filter = filters[i];
if (typeof filter === 'string') {
currentFilters.each(function(item) {
if (item.getProperty() === filter) {
currentFilters.remove(item);
}
});
}
else if (typeof filter === 'function') {
currentFilters.each(function(item) {
if (item.getFilterFn() === filter) {
currentFilters.remove(item);
}
});
}
else {
if (filter.isFilter) {
currentFilters.remove(filter);
}
else if (filter.property !== undefined && filter.value !== undefined) {
currentFilters.each(function(item) {
if (item.getProperty() === filter.property && item.getValue() === filter.value) {
currentFilters.remove(item);
}
});
}
}
}
if (!currentFilters.length) {
this.filtered = false;
}
},
/**
* This updates the cached sortFn based on the current sorters.
* @return {Function} sortFn The generated sort function.
* @private
*/
updateFilterFn: function() {
var filters = this.getFilters().items;
this.filterFn = function(item) {
var isMatch = true,
length = filters.length,
i;
for (i = 0; i < length; i++) {
var filter = filters[i],
fn = filter.getFilterFn(),
scope = filter.getScope() || this;
isMatch = isMatch && fn.call(scope, item);
}
return isMatch;
};
this.dirtyFilterFn = false;
return this.filterFn;
},
/**
* This method will sort an array based on the currently configured {@link Ext.data.Store#sorters sorters}.
* @param {Array} data The array you want to have sorted
* @return {Array} The array you passed after it is sorted
*/
filter: function(data) {
return this.getFilters().length ? Ext.Array.filter(data, this.getFilterFn()) : data;
},
isFiltered: function(item) {
return this.getFilters().length ? !this.getFilterFn()(item) : false;
},
/**
* Returns an up to date sort function.
* @return {Function} sortFn The sort function.
*/
getFilterFn: function() {
if (this.dirtyFilterFn) {
return this.updateFilterFn();
}
return this.filterFn;
}
});
/**
* Mixin that provides a common interface for publishing events. Classes using this mixin can use the {@link #fireEvent}
* and {@link #fireAction} methods to notify listeners of events on the class.
*
* Classes can also define a {@link #listeners} config to add an event hanler to the current object. See
* {@link #addListener} for more details.
*
* ## Example
*
* Ext.define('Employee', {
* mixins: ['Ext.mixin.Observable'],
*
* config: {
* fullName: ''
* },
*
* constructor: function(config) {
* this.initConfig(config); // We need to initialize the config options when the class is instantiated
* },
*
* quitJob: function() {
* this.fireEvent('quit');
* }
* });
*
* var newEmployee = Ext.create('Employee', {
*
* fullName: 'Ed Spencer',
*
* listeners: {
* quit: function() { // This function will be called when the 'quit' event is fired
* // By default, "this" will be the object that fired the event.
* console.log(this.getFullName() + " has quit!");
* }
* }
* });
*
* newEmployee.quitJob(); // Will log 'Ed Spencer has quit!'
*/
Ext.define('Ext.mixin.Observable', {
requires: ['Ext.event.Dispatcher'],
extend: 'Ext.mixin.Mixin',
mixins: ['Ext.mixin.Identifiable'],
mixinConfig: {
id: 'observable',
hooks: {
destroy: 'destroy'
}
},
alternateClassName: 'Ext.util.Observable',
// @private
isObservable: true,
observableType: 'observable',
validIdRegex: /^([\w\-]+)$/,
observableIdPrefix: '#',
listenerOptionsRegex: /^(?:delegate|single|delay|buffer|args|prepend)$/,
config: {
/**
* @cfg {Object} listeners
* A config object containing one or more event handlers to be added to this object during initialization. This
* should be a valid listeners config object as specified in the {@link #addListener} example for attaching
* multiple handlers at once.
* @accessor
*/
listeners: null,
/**
* @cfg {String/String[]} bubbleEvents The event name to bubble, or an Array of event names.
* @accessor
*/
bubbleEvents: null
},
constructor: function(config) {
this.initConfig(config);
},
applyListeners: function(listeners) {
if (listeners) {
this.addListener(listeners);
}
},
applyBubbleEvents: function(bubbleEvents) {
if (bubbleEvents) {
this.enableBubble(bubbleEvents);
}
},
getOptimizedObservableId: function() {
return this.observableId;
},
getObservableId: function() {
if (!this.observableId) {
var id = this.getUniqueId();
if (!id.match(this.validIdRegex)) {
Ext.Logger.error("Invalid unique id of '" + id + "' for this object", this);
}
this.observableId = this.observableIdPrefix + id;
this.getObservableId = this.getOptimizedObservableId;
}
return this.observableId;
},
getOptimizedEventDispatcher: function() {
return this.eventDispatcher;
},
getEventDispatcher: function() {
if (!this.eventDispatcher) {
this.eventDispatcher = Ext.event.Dispatcher.getInstance();
this.getEventDispatcher = this.getOptimizedEventDispatcher;
this.getListeners();
this.getBubbleEvents();
}
return this.eventDispatcher;
},
getManagedListeners: function(object, eventName) {
var id = object.getUniqueId(),
managedListeners = this.managedListeners;
if (!managedListeners) {
this.managedListeners = managedListeners = {};
}
if (!managedListeners[id]) {
managedListeners[id] = {};
object.doAddListener('destroy', 'clearManagedListeners', this, {
single: true,
args: [object]
});
}
if (!managedListeners[id][eventName]) {
managedListeners[id][eventName] = [];
}
return managedListeners[id][eventName];
},
getUsedSelectors: function() {
var selectors = this.usedSelectors;
if (!selectors) {
selectors = this.usedSelectors = [];
selectors.$map = {};
}
return selectors;
},
/**
* Fires the specified event with the passed parameters (minus the event name, plus the `options` object passed
* to {@link #addListener}).
*
* An event may be set to bubble up an Observable parent hierarchy by calling {@link #enableBubble}.
*
* @param {String} eventName The name of the event to fire.
* @param {Object...} args Variable number of parameters are passed to handlers.
* @return {Boolean} returns false if any of the handlers return false otherwise it returns true.
*/
fireEvent: function(eventName) {
var args = Array.prototype.slice.call(arguments, 1);
return this.doFireEvent(eventName, args);
},
/**
* Fires the specified event with the passed parameters and execute a function (action)
* at the end if there are no listeners that return false.
*
* @param {String} eventName The name of the event to fire.
* @param {Array} args Arguments to pass to handers
* @param {Function} fn Action
* @param {Object} scope scope of fn
*/
fireAction: function(eventName, args, fn, scope, options, order) {
var fnType = typeof fn,
action;
if (args === undefined) {
args = [];
}
if (fnType != 'undefined') {
action = {
fn: fn,
isLateBinding: fnType == 'string',
scope: scope || this,
options: options || {},
order: order
};
}
return this.doFireEvent(eventName, args, action);
},
doFireEvent: function(eventName, args, action, connectedController) {
if (this.eventFiringSuspended) {
return;
}
var id = this.getObservableId(),
dispatcher = this.getEventDispatcher();
return dispatcher.dispatchEvent(this.observableType, id, eventName, args, action, connectedController);
},
/**
* @private
* @param name
* @param fn
* @param scope
* @param options
*/
doAddListener: function(name, fn, scope, options, order) {
var isManaged = (scope && scope !== this && scope.isIdentifiable),
usedSelectors = this.getUsedSelectors(),
usedSelectorsMap = usedSelectors.$map,
selector = this.getObservableId(),
isAdded, managedListeners, delegate;
if (!options) {
options = {};
}
if (!scope) {
scope = this;
}
if (options.delegate) {
delegate = options.delegate;
// See https://sencha.jira.com/browse/TOUCH-1579
selector += ' ' + delegate;
}
if (!(selector in usedSelectorsMap)) {
usedSelectorsMap[selector] = true;
usedSelectors.push(selector);
}
isAdded = this.addDispatcherListener(selector, name, fn, scope, options, order);
if (isAdded && isManaged) {
managedListeners = this.getManagedListeners(scope, name);
managedListeners.push({
delegate: delegate,
scope: scope,
fn: fn,
order: order
});
}
return isAdded;
},
addDispatcherListener: function(selector, name, fn, scope, options, order) {
return this.getEventDispatcher().addListener(this.observableType, selector, name, fn, scope, options, order);
},
doRemoveListener: function(name, fn, scope, options, order) {
var isManaged = (scope && scope !== this && scope.isIdentifiable),
selector = this.getObservableId(),
isRemoved,
managedListeners, i, ln, listener, delegate;
if (options && options.delegate) {
delegate = options.delegate;
// See https://sencha.jira.com/browse/TOUCH-1579
selector += ' ' + delegate;
}
if (!scope) {
scope = this;
}
isRemoved = this.removeDispatcherListener(selector, name, fn, scope, order);
if (isRemoved && isManaged) {
managedListeners = this.getManagedListeners(scope, name);
for (i = 0,ln = managedListeners.length; i < ln; i++) {
listener = managedListeners[i];
if (listener.fn === fn && listener.scope === scope && listener.delegate === delegate && listener.order === order) {
managedListeners.splice(i, 1);
break;
}
}
}
return isRemoved;
},
removeDispatcherListener: function(selector, name, fn, scope, order) {
return this.getEventDispatcher().removeListener(this.observableType, selector, name, fn, scope, order);
},
clearManagedListeners: function(object) {
var managedListeners = this.managedListeners,
id, namedListeners, listeners, eventName, i, ln, listener, options;
if (!managedListeners) {
return this;
}
if (object) {
if (typeof object != 'string') {
id = object.getUniqueId();
}
else {
id = object;
}
namedListeners = managedListeners[id];
for (eventName in namedListeners) {
if (namedListeners.hasOwnProperty(eventName)) {
listeners = namedListeners[eventName];
for (i = 0,ln = listeners.length; i < ln; i++) {
listener = listeners[i];
options = {};
if (listener.delegate) {
options.delegate = listener.delegate;
}
if (this.doRemoveListener(eventName, listener.fn, listener.scope, options, listener.order)) {
i--;
ln--;
}
}
}
}
delete managedListeners[id];
return this;
}
for (id in managedListeners) {
if (managedListeners.hasOwnProperty(id)) {
this.clearManagedListeners(id);
}
}
},
/**
* @private
* @param operation
* @param eventName
* @param fn
* @param scope
* @param options
* @param order
*/
changeListener: function(actionFn, eventName, fn, scope, options, order) {
var eventNames,
listeners,
listenerOptionsRegex,
actualOptions,
name, value, i, ln, listener, valueType;
if (typeof fn != 'undefined') {
// Support for array format to add multiple listeners
if (typeof eventName != 'string') {
for (i = 0,ln = eventName.length; i < ln; i++) {
name = eventName[i];
actionFn.call(this, name, fn, scope, options, order);
}
return this;
}
actionFn.call(this, eventName, fn, scope, options, order);
}
else if (Ext.isArray(eventName)) {
listeners = eventName;
for (i = 0,ln = listeners.length; i < ln; i++) {
listener = listeners[i];
actionFn.call(this, listener.event, listener.fn, listener.scope, listener, listener.order);
}
}
else {
listenerOptionsRegex = this.listenerOptionsRegex;
options = eventName;
eventNames = [];
listeners = [];
actualOptions = {};
for (name in options) {
value = options[name];
if (name === 'scope') {
scope = value;
continue;
}
else if (name === 'order') {
order = value;
continue;
}
if (!listenerOptionsRegex.test(name)) {
valueType = typeof value;
if (valueType != 'string' && valueType != 'function') {
actionFn.call(this, name, value.fn, value.scope || scope, value, value.order || order);
continue;
}
eventNames.push(name);
listeners.push(value);
}
else {
actualOptions[name] = value;
}
}
for (i = 0,ln = eventNames.length; i < ln; i++) {
actionFn.call(this, eventNames[i], listeners[i], scope, actualOptions, order);
}
}
},
/**
* Appends an event handler to this object.
*
* @param {String} eventName The name of the event to listen for. May also be an object who's property names are
* event names.
* @param {Function} fn The method the event invokes. Will be called with arguments given to
* {@link #fireEvent} plus the `options` parameter described below.
* @param {Object} [scope] The scope (`this` reference) in which the handler function is executed. **If
* omitted, defaults to the object which fired the event.**
* @param {Object} [options] An object containing handler configuration.
*
* This object may contain any of the following properties:
*
* - **scope** : Object
*
* The scope (`this` reference) in which the handler function is executed. **If omitted, defaults to the object
* which fired the event.**
*
* - **delay** : Number
*
* The number of milliseconds to delay the invocation of the handler after the event fires.
*
* - **single** : Boolean
*
* True to add a handler to handle just the next firing of the event, and then remove itself.
*
* - **order** : String
*
* The order of when the listener should be added into the listener queue.
*
* If you set an order of `before` and the event you are listening to is preventable, you can return `false` and it will stop the event.
*
* Available options are `before`, `current` and `after`. Defaults to `current`.
*
* - **buffer** : Number
*
* Causes the handler to be delayed by the specified number of milliseconds. If the event fires again within that
* time, the original handler is _not_ invoked, but the new handler is scheduled in its place.
*
* - **delegate** : String
*
* Uses {@link Ext.ComponentQuery} to delegate events to a specified query selector within this item.
*
* // Create a container with a two children; a button and a toolbar
* var container = Ext.create('Ext.Container', {
* items: [
* {
* xtype: 'toolbar',
* docked: 'top',
* title: 'My Toolbar'
* },
* {
* xtype: 'button',
* text: 'My Button'
* }
* ]
* });
*
* container.on({
* // Ext.Buttons have an xtype of 'button', so we use that are a selector for our delegate
* delegate: 'button',
*
* tap: function() {
* alert('Button tapped!');
* }
* });
*
* **Combining Options**
*
* Using the options argument, it is possible to combine different types of listeners:
*
* A delayed, one-time listener.
*
* container.on('tap', this.handleTap, this, {
* single: true,
* delay: 100
* });
*
* **Attaching multiple handlers in 1 call**
*
* The method also allows for a single argument to be passed which is a config object containing properties which
* specify multiple events. For example:
*
* container.on({
* tap : this.onTap,
* swipe: this.onSwipe,
*
* scope: this // Important. Ensure "this" is correct during handler execution
* });
*
* One can also specify options for each event handler separately:
*
* container.on({
* tap : { fn: this.onTap, scope: this, single: true },
* swipe: { fn: button.onSwipe, scope: button }
* });
*
*/
addListener: function(eventName, fn, scope, options, order) {
return this.changeListener(this.doAddListener, eventName, fn, scope, options, order);
},
addBeforeListener: function(eventName, fn, scope, options) {
return this.addListener(eventName, fn, scope, options, 'before');
},
addAfterListener: function(eventName, fn, scope, options) {
return this.addListener(eventName, fn, scope, options, 'after');
},
/**
* Removes an event handler.
*
* @param {String} eventName The type of event the handler was associated with.
* @param {Function} fn The handler to remove. **This must be a reference to the function passed into the
* {@link #addListener} call.**
* @param {Object} scope (optional) The scope originally specified for the handler. It must be the same as the
* scope argument specified in the original call to {@link #addListener} or the listener will not be removed.
*/
removeListener: function(eventName, fn, scope, options, order) {
return this.changeListener(this.doRemoveListener, eventName, fn, scope, options, order);
},
removeBeforeListener: function(eventName, fn, scope, options) {
return this.removeListener(eventName, fn, scope, options, 'before');
},
removeAfterListener: function(eventName, fn, scope, options) {
return this.removeListener(eventName, fn, scope, options, 'after');
},
/**
* Removes all listeners for this object.
*/
clearListeners: function() {
var usedSelectors = this.getUsedSelectors(),
dispatcher = this.getEventDispatcher(),
i, ln, selector;
for (i = 0,ln = usedSelectors.length; i < ln; i++) {
selector = usedSelectors[i];
dispatcher.clearListeners(this.observableType, selector);
}
},
/**
* Checks to see if this object has any listeners for a specified event
*
* @param {String} eventName The name of the event to check for
* @return {Boolean} True if the event is being listened for, else false
*/
hasListener: function(eventName) {
return this.getEventDispatcher().hasListener(this.observableType, this.getObservableId(), eventName);
},
/**
* Suspends the firing of all events. (see {@link #resumeEvents})
*
* @param {Boolean} queueSuspended Pass as true to queue up suspended events to be fired
* after the {@link #resumeEvents} call instead of discarding all suspended events.
*/
suspendEvents: function(queueSuspended) {
this.eventFiringSuspended = true;
},
/**
* Resumes firing events (see {@link #suspendEvents}).
*
* If events were suspended using the `queueSuspended` parameter, then all events fired
* during event suspension will be sent to any listeners now.
*/
resumeEvents: function() {
this.eventFiringSuspended = false;
},
/**
* Relays selected events from the specified Observable as if the events were fired by this
.
* @param {Object} object The Observable whose events this object is to relay.
* @param {String/Array/Object} events Array of event names to relay.
*/
relayEvents: function(object, events, prefix) {
var i, ln, oldName, newName;
if (typeof prefix == 'undefined') {
prefix = '';
}
if (typeof events == 'string') {
events = [events];
}
if (Ext.isArray(events)) {
for (i = 0,ln = events.length; i < ln; i++) {
oldName = events[i];
newName = prefix + oldName;
object.addListener(oldName, this.createEventRelayer(newName), this);
}
}
else {
for (oldName in events) {
if (events.hasOwnProperty(oldName)) {
newName = prefix + events[oldName];
object.addListener(oldName, this.createEventRelayer(newName), this);
}
}
}
return this;
},
/**
* @private
* @param args
* @param fn
*/
relayEvent: function(args, fn, scope, options, order) {
var fnType = typeof fn,
controller = args[args.length - 1],
eventName = controller.getInfo().eventName,
action;
args = Array.prototype.slice.call(args, 0, -2);
args[0] = this;
if (fnType != 'undefined') {
action = {
fn: fn,
scope: scope || this,
options: options || {},
order: order,
isLateBinding: fnType == 'string'
};
}
return this.doFireEvent(eventName, args, action, controller);
},
/**
* @private
* Creates an event handling function which refires the event from this object as the passed event name.
* @param newName
* @returns {Function}
*/
createEventRelayer: function(newName){
return function() {
return this.doFireEvent(newName, Array.prototype.slice.call(arguments, 0, -2));
}
},
/**
* Enables events fired by this Observable to bubble up an owner hierarchy by calling `this.getBubbleTarget()` if
* present. There is no implementation in the Observable base class.
*
* @param {String/String[]} events The event name to bubble, or an Array of event names.
*/
enableBubble: function(events) {
var isBubblingEnabled = this.isBubblingEnabled,
i, ln, name;
if (!isBubblingEnabled) {
isBubblingEnabled = this.isBubblingEnabled = {};
}
if (typeof events == 'string') {
events = Ext.Array.clone(arguments);
}
for (i = 0,ln = events.length; i < ln; i++) {
name = events[i];
if (!isBubblingEnabled[name]) {
isBubblingEnabled[name] = true;
this.addListener(name, this.createEventBubbler(name), this);
}
}
},
createEventBubbler: function(name) {
return function doBubbleEvent() {
var bubbleTarget = ('getBubbleTarget' in this) ? this.getBubbleTarget() : null;
if (bubbleTarget && bubbleTarget !== this && bubbleTarget.isObservable) {
bubbleTarget.fireAction(name, Array.prototype.slice.call(arguments, 0, -2), doBubbleEvent, bubbleTarget, null, 'after');
}
}
},
getBubbleTarget: function() {
return false;
},
destroy: function() {
if (this.observableId) {
this.fireEvent('destroy', this);
this.clearListeners();
this.clearManagedListeners();
}
},
addEvents: Ext.emptyFn
}, function() {
this.createAlias({
on: 'addListener',
un: 'removeListener',
onBefore: 'addBeforeListener',
onAfter: 'addAfterListener',
unBefore: 'removeBeforeListener',
unAfter: 'removeAfterListener'
});
});
/**
* @private
*/
Ext.define('Ext.Evented', {
alternateClassName: 'Ext.EventedBase',
mixins: ['Ext.mixin.Observable'],
statics: {
generateSetter: function(nameMap) {
var internalName = nameMap.internal,
applyName = nameMap.apply,
changeEventName = nameMap.changeEvent,
doSetName = nameMap.doSet;
return function(value) {
var initialized = this.initialized,
oldValue = this[internalName],
applier = this[applyName];
if (applier) {
value = applier.call(this, value, oldValue);
if (typeof value == 'undefined') {
return this;
}
}
if (value !== oldValue) {
if (initialized) {
this.fireAction(changeEventName, [this, value, oldValue], this.doSet, this, {
nameMap: nameMap
});
}
else {
this[internalName] = value;
this[doSetName].call(this, value, oldValue);
}
}
return this;
}
}
},
initialized: false,
constructor: function(config) {
this.initialConfig = config;
this.initialize();
},
initialize: function() {
this.initConfig(this.initialConfig);
this.initialized = true;
},
doSet: function(me, value, oldValue, options) {
var nameMap = options.nameMap;
me[nameMap.internal] = value;
me[nameMap.doSet].call(this, value, oldValue);
},
onClassExtended: function(Class, data) {
if (!data.hasOwnProperty('eventedConfig')) {
return;
}
var ExtClass = Ext.Class,
config = data.config,
eventedConfig = data.eventedConfig,
name, nameMap;
data.config = (config) ? Ext.applyIf(config, eventedConfig) : eventedConfig;
/*
* These are generated setters for eventedConfig
*
* If the component is initialized, it invokes fireAction to fire the event as well,
* which indicate something has changed. Otherwise, it just executes the action
* (happens during initialization)
*
* This is helpful when we only want the event to be fired for subsequent changes.
* Also it's a major performance improvement for instantiation when fired events
* are mostly useless since there's no listeners
*/
for (name in eventedConfig) {
if (eventedConfig.hasOwnProperty(name)) {
nameMap = ExtClass.getConfigNameMap(name);
data[nameMap.set] = this.generateSetter(nameMap);
}
}
}
});
/**
* @private
* This is the abstract class for {@link Ext.Component}.
*
* This should never be overriden.
*/
Ext.define('Ext.AbstractComponent', {
extend: 'Ext.Evented',
onClassExtended: function(Class, members) {
if (!members.hasOwnProperty('cachedConfig')) {
return;
}
var prototype = Class.prototype,
config = members.config,
cachedConfig = members.cachedConfig,
cachedConfigList = prototype.cachedConfigList,
hasCachedConfig = prototype.hasCachedConfig,
name, value;
delete members.cachedConfig;
prototype.cachedConfigList = cachedConfigList = (cachedConfigList) ? cachedConfigList.slice() : [];
prototype.hasCachedConfig = hasCachedConfig = (hasCachedConfig) ? Ext.Object.chain(hasCachedConfig) : {};
if (!config) {
members.config = config = {};
}
for (name in cachedConfig) {
if (cachedConfig.hasOwnProperty(name)) {
value = cachedConfig[name];
if (!hasCachedConfig[name]) {
hasCachedConfig[name] = true;
cachedConfigList.push(name);
}
config[name] = value;
}
}
},
getElementConfig: Ext.emptyFn,
referenceAttributeName: 'reference',
referenceSelector: '[reference]',
/**
* @private
* Significantly improve instantiation time for Component with multiple references
* Ext.Element instance of the reference domNode is only created the very first time
* it's ever used
*/
addReferenceNode: function(name, domNode) {
Ext.Object.defineProperty(this, name, {
get: function() {
var reference;
delete this[name];
this[name] = reference = new Ext.Element(domNode);
return reference;
},
configurable: true
});
},
initElement: function() {
var prototype = this.self.prototype,
id = this.getId(),
referenceList = [],
cleanAttributes = true,
referenceAttributeName = this.referenceAttributeName,
needsOptimization = false,
renderTemplate, renderElement, element,
referenceNodes, i, ln, referenceNode, reference,
configNameCache, defaultConfig, cachedConfigList, initConfigList, initConfigMap, configList,
elements, name, nameMap, internalName;
if (prototype.hasOwnProperty('renderTemplate')) {
renderTemplate = this.renderTemplate.cloneNode(true);
renderElement = renderTemplate.firstChild;
}
else {
cleanAttributes = false;
needsOptimization = true;
renderTemplate = document.createDocumentFragment();
renderElement = Ext.Element.create(this.getElementConfig(), true);
renderTemplate.appendChild(renderElement);
}
referenceNodes = renderTemplate.querySelectorAll(this.referenceSelector);
for (i = 0,ln = referenceNodes.length; i < ln; i++) {
referenceNode = referenceNodes[i];
reference = referenceNode.getAttribute(referenceAttributeName);
if (cleanAttributes) {
referenceNode.removeAttribute(referenceAttributeName);
}
if (reference == 'element') {
referenceNode.id = id;
this.element = element = new Ext.Element(referenceNode);
}
else {
this.addReferenceNode(reference, referenceNode);
}
referenceList.push(reference);
}
this.referenceList = referenceList;
if (!this.innerElement) {
this.innerElement = element;
}
if (renderElement === element.dom) {
this.renderElement = element;
}
else {
this.addReferenceNode('renderElement', renderElement);
}
// This happens only *once* per class, during the very first instantiation
// to optimize renderTemplate based on cachedConfig
if (needsOptimization) {
configNameCache = Ext.Class.configNameCache;
defaultConfig = this.config;
cachedConfigList = this.cachedConfigList;
initConfigList = this.initConfigList;
initConfigMap = this.initConfigMap;
configList = [];
for (i = 0,ln = cachedConfigList.length; i < ln; i++) {
name = cachedConfigList[i];
nameMap = configNameCache[name];
if (initConfigMap[name]) {
initConfigMap[name] = false;
Ext.Array.remove(initConfigList, name);
}
if (defaultConfig[name] !== null) {
configList.push(name);
this[nameMap.get] = this[nameMap.initGet];
}
}
for (i = 0,ln = configList.length; i < ln; i++) {
name = configList[i];
nameMap = configNameCache[name];
internalName = nameMap.internal;
this[internalName] = null;
this[nameMap.set].call(this, defaultConfig[name]);
delete this[nameMap.get];
prototype[internalName] = this[internalName];
}
renderElement = this.renderElement.dom;
prototype.renderTemplate = renderTemplate = document.createDocumentFragment();
renderTemplate.appendChild(renderElement.cloneNode(true));
elements = renderTemplate.querySelectorAll('[id]');
for (i = 0,ln = elements.length; i < ln; i++) {
element = elements[i];
element.removeAttribute('id');
}
for (i = 0,ln = referenceList.length; i < ln; i++) {
reference = referenceList[i];
this[reference].dom.removeAttribute('reference');
}
}
return this;
}
});
/**
* @author Ed Spencer
*
* Controllers are responsible for responding to events that occur within your app. If your app contains a Logout
* {@link Ext.Button button} that your user can tap on, a Controller would listen to the Button's tap event and take
* the appropriate action. It allows the View classes to handle the display of data and the Model classes to handle the
* loading and saving of data - the Controller is the glue that binds them together.
*
* ## Relation to Ext.app.Application
*
* Controllers exist within the context of an {@link Ext.app.Application Application}. An Application usually consists
* of a number of Controllers, each of which handle a specific part of the app. For example, an Application that
* handles the orders for an online shopping site might have controllers for Orders, Customers and Products.
*
* All of the Controllers that an Application uses are specified in the Application's
* {@link Ext.app.Application#controllers} config. The Application automatically instantiates each Controller and keeps
* references to each, so it is unusual to need to instantiate Controllers directly. By convention each Controller is
* named after the thing (usually the Model) that it deals with primarily, usually in the plural - for example if your
* app is called 'MyApp' and you have a Controller that manages Products, convention is to create a
* MyApp.controller.Products class in the file app/controller/Products.js.
*
* ## Refs and Control
*
* The centerpiece of Controllers is the twin configurations {@link #refs} and {@link #cfg-control}. These are used to
* easily gain references to Components inside your app and to take action on them based on events that they fire.
* Let's look at {@link #refs} first:
*
* ### Refs
*
* Refs leverage the powerful {@link Ext.ComponentQuery ComponentQuery} syntax to easily locate Components on your
* page. We can define as many refs as we like for each Controller, for example here we define a ref called 'nav' that
* finds a Component on the page with the ID 'mainNav'. We then use that ref in the addLogoutButton beneath it:
*
* Ext.define('MyApp.controller.Main', {
* extend: 'Ext.app.Controller',
*
* config: {
* refs: {
* nav: '#mainNav'
* }
* },
*
* addLogoutButton: function() {
* this.getNav().add({
* text: 'Logout'
* });
* }
* });
*
* Usually, a ref is just a key/value pair - the key ('nav' in this case) is the name of the reference that will be
* generated, the value ('#mainNav' in this case) is the {@link Ext.ComponentQuery ComponentQuery} selector that will
* be used to find the Component.
*
* Underneath that, we have created a simple function called addLogoutButton which uses this ref via its generated
* 'getNav' function. These getter functions are generated based on the refs you define and always follow the same
* format - 'get' followed by the capitalized ref name. In this case we're treating the nav reference as though it's a
* {@link Ext.Toolbar Toolbar}, and adding a Logout button to it when our function is called. This ref would recognize
* a Toolbar like this:
*
* Ext.create('Ext.Toolbar', {
* id: 'mainNav',
*
* items: [
* {
* text: 'Some Button'
* }
* ]
* });
*
* Assuming this Toolbar has already been created by the time we run our 'addLogoutButton' function (we'll see how that
* is invoked later), it will get the second button added to it.
*
* ### Advanced Refs
*
* Refs can also be passed a couple of additional options, beyond name and selector. These are autoCreate and xtype,
* which are almost always used together:
*
* Ext.define('MyApp.controller.Main', {
* extend: 'Ext.app.Controller',
*
* config: {
* refs: {
* nav: '#mainNav',
*
* infoPanel: {
* selector: 'tabpanel panel[name=fish] infopanel',
* xtype: 'infopanel',
* autoCreate: true
* }
* }
* }
* });
*
* We've added a second ref to our Controller. Again the name is the key, 'infoPanel' in this case, but this time we've
* passed an object as the value instead. This time we've used a slightly more complex selector query - in this example
* imagine that your app contains a {@link Ext.tab.Panel tab panel} and that one of the items in the tab panel has been
* given the name 'fish'. Our selector matches any Component with the xtype 'infopanel' inside that tab panel item.
*
* The difference here is that if that infopanel does not exist already inside the 'fish' panel, it will be
* automatically created when you call this.getInfoPanel inside your Controller. The Controller is able to do this
* because we provided the xtype to instantiate with in the event that the selector did not return anything.
*
* ### Control
*
* The sister config to {@link #refs} is {@link #cfg-control}. {@link #cfg-control Control} is the means by which your listen
* to events fired by Components and have your Controller react in some way. Control accepts both ComponentQuery
* selectors and refs as its keys, and listener objects as values - for example:
*
* Ext.define('MyApp.controller.Main', {
* extend: 'Ext.app.Controller',
*
* config: {
* control: {
* loginButton: {
* tap: 'doLogin'
* },
* 'button[action=logout]': {
* tap: 'doLogout'
* }
* },
*
* refs: {
* loginButton: 'button[action=login]'
* }
* },
*
* doLogin: function() {
* //called whenever the Login button is tapped
* },
*
* doLogout: function() {
* //called whenever any Button with action=logout is tapped
* }
* });
*
* Here we have set up two control declarations - one for our loginButton ref and the other for any Button on the page
* that has been given the action 'logout'. For each declaration we passed in a single event handler - in each case
* listening for the 'tap' event, specifying the action that should be called when that Button fires the tap event.
* Note that we specified the 'doLogin' and 'doLogout' methods as strings inside the control block - this is important.
*
* You can listen to as many events as you like in each control declaration, and mix and match ComponentQuery selectors
* and refs as the keys.
*
* ## Routes
*
* As of Sencha Touch 2, Controllers can now directly specify which routes they are interested in. This enables us to
* provide history support within our app, as well as the ability to deeply link to any part of the application that we
* provide a route for.
*
* For example, let's say we have a Controller responsible for logging in and viewing user profiles, and want to make
* those screens accessible via urls. We could achieve that like this:
*
* Ext.define('MyApp.controller.Users', {
* extend: 'Ext.app.Controller',
*
* config: {
* routes: {
* 'login': 'showLogin',
* 'user/:id': 'showUserById'
* },
*
* refs: {
* main: '#mainTabPanel'
* }
* },
*
* //uses our 'main' ref above to add a loginpanel to our main TabPanel (note that
* //'loginpanel' is a custom xtype created for this application)
* showLogin: function() {
* this.getMain().add({
* xtype: 'loginpanel'
* });
* },
*
* //Loads the User then adds a 'userprofile' view to the main TabPanel
* showUserById: function(id) {
* MyApp.model.User.load(id, {
* scope: this,
* success: function(user) {
* this.getMain().add({
* xtype: 'userprofile',
* user: user
* });
* }
* });
* }
* });
*
* The routes we specified above simply map the contents of the browser address bar to a Controller function to call
* when that route is matched. The routes can be simple text like the login route, which matches against
* http://myapp.com/#login, or contain wildcards like the 'user/:id' route, which matches urls like
* http://myapp.com/#user/123. Whenever the address changes the Controller automatically calls the function specified.
*
* Note that in the showUserById function we had to first load the User instance. Whenever you use a route, the
* function that is called by that route is completely responsible for loading its data and restoring state. This is
* because your user could either send that url to another person or simply refresh the page, which we wipe clear any
* cached data you had already loaded. There is a more thorough discussion of restoring state with routes in the
* application architecture guides.
*
* ## Advanced Usage
*
* See the Controllers guide for advanced Controller usage including before filters
* and customizing for different devices.
*/
Ext.define('Ext.app.Controller', {
mixins: {
observable: "Ext.mixin.Observable"
},
config: {
/**
* @cfg {Object} refs A collection of named {@link Ext.ComponentQuery ComponentQuery} selectors that makes it
* easy to get references to key Components on your page. Example usage:
*
* refs: {
* main: '#mainTabPanel',
* loginButton: '#loginWindow button[action=login]',
*
* infoPanel: {
* selector: 'infopanel',
* xtype: 'infopanel',
* autoCreate: true
* }
* }
*
* The first two are simple ComponentQuery selectors, the third (infoPanel) also passes in the autoCreate and
* xtype options, which will first run the ComponentQuery to see if a Component matching that selector exists
* on the page. If not, it will automatically create one using the xtype provided:
*
* someControllerFunction: function() {
* //if the info panel didn't exist before, calling its getter will instantiate
* //it automatically and return the new instance
* this.getInfoPanel().show();
* }
*
* @accessor
*/
refs: {},
/**
* @cfg {Object} routes Provides a mapping of urls to Controller actions. Whenever the specified url is matched
* in the address bar, the specified Controller action is called. Example usage:
*
* routes: {
* 'login': 'showLogin',
* 'users/:id': 'showUserById'
* }
*
* The first route will match against http://myapp.com/#login and call the Controller's showLogin function. The
* second route contains a wildcard (':id') and will match all urls like http://myapp.com/#users/123, calling
* the showUserById function with the matched ID as the first argument.
*
* @accessor
*/
routes: {},
/**
* @cfg {Object} control Provides a mapping of Controller functions that should be called whenever certain
* Component events are fired. The Components can be specified using {@link Ext.ComponentQuery ComponentQuery}
* selectors or {@link #refs}. Example usage:
*
* control: {
* 'button[action=logout]': {
* tap: 'doLogout'
* },
* main: {
* activeitemchange: 'doUpdate'
* }
* }
*
* The first item uses a ComponentQuery selector to run the Controller's doLogout function whenever any Button
* with action=logout is tapped on. The second calls the Controller's doUpdate function whenever the
* activeitemchange event is fired by the Component referenced by our 'main' ref. In this case main is a tab
* panel (see {@link #refs} for how to set that reference up).
*
* @accessor
*/
control: {},
/**
* @cfg {Object} before Provides a mapping of Controller functions to filter functions that are run before them
* when dispatched to from a route. These are usually used to run pre-processing functions like authentication
* before a certain function is executed. They are only called when dispatching from a route. Example usage:
*
* Ext.define('MyApp.controller.Products', {
* config: {
* before: {
* editProduct: 'authenticate'
* },
*
* routes: {
* 'product/edit/:id': 'editProduct'
* }
* },
*
* //this is not directly because our before filter is called first
* editProduct: function() {
* //... performs the product editing logic
* },
*
* //this is run before editProduct
* authenticate: function(action) {
* MyApp.authenticate({
* success: function() {
* action.resume();
* },
* failure: function() {
* Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
* }
* });
* }
* });
*
* @accessor
*/
before: {},
/**
* @cfg {Ext.app.Application} application The Application instance this Controller is attached to. This is
* automatically provided when using the MVC architecture so should rarely need to be set directly.
* @accessor
*/
application: {}
},
/**
* Constructs a new Controller instance
*/
constructor: function(config) {
this.initConfig(config);
this.mixins.observable.constructor.call(this, config);
},
/**
* @cfg
* Called by the Controller's {@link #application} to initialize the Controller. This is always called before the
* {@link Ext.app.Application Application} launches, giving the Controller a chance to run any pre-launch logic.
* See also {@link #launch}, which is called after the {@link Ext.app.Application#launch Application's launch function}
*/
init: Ext.emptyFn,
/**
* @cfg
* Called by the Controller's {@link #application} immediately after the Application's own
* {@link Ext.app.Application#launch launch function} has been called. This is usually a good place to run any
* logic that has to run after the app UI is initialized. See also {@link #init}, which is called before the
* {@link Ext.app.Application#launch Application's launch function}.
*/
launch: Ext.emptyFn,
/**
* Convenient way to redirect to a new url. See {@link Ext.app.Application#redirectTo} for full usage information
*/
redirectTo: function(place) {
return this.getApplication().redirectTo(place);
},
/**
* @private
* Executes an Ext.app.Action by giving it the correct before filters and kicking off execution
*/
execute: function(action, skipFilters) {
action.setBeforeFilters(this.getBefore()[action.getAction()]);
action.execute();
},
/**
* @private
* Massages the before filters into an array of function references for each controller action
*/
applyBefore: function(before) {
var filters, name, length, i;
for (name in before) {
filters = Ext.Array.from(before[name]);
length = filters.length;
for (i = 0; i < length; i++) {
filters[i] = this[filters[i]];
}
before[name] = filters;
}
return before;
},
/**
* @private
*/
applyControl: function(config) {
this.control(config, this);
return config;
},
/**
* @private
*/
applyRefs: function(refs) {
if (Ext.isArray(refs)) {
Ext.Logger.deprecate("In Sencha Touch 2 the refs config accepts an object but you have passed it an array.");
}
this.ref(refs);
return refs;
},
/**
* @private
* Adds any routes specified in this Controller to the global Application router
*/
applyRoutes: function(routes) {
var app = this instanceof Ext.app.Application ? this : this.getApplication(),
router = app.getRouter(),
route, url, config;
for (url in routes) {
route = routes[url];
config = {
controller: this.$className
};
if (Ext.isString(route)) {
config.action = route;
} else {
Ext.apply(config, route);
}
router.connect(url, config);
}
return routes;
},
/**
* @private
*/
control: function(selectors) {
this.getApplication().control(selectors, this);
},
/**
* @private
* 1.x-inspired ref implementation
*/
ref: function(refs) {
var refName, getterName, selector, info;
for (refName in refs) {
selector = refs[refName];
getterName = "get" + Ext.String.capitalize(refName);
if (!this[getterName]) {
if (Ext.isString(refs[refName])) {
info = {
ref: refName,
selector: selector
};
} else {
info = refs[refName];
}
this[getterName] = Ext.Function.pass(this.getRef, [refName, info], this);
}
this.references = this.references || [];
this.references.push(refName.toLowerCase());
}
},
/**
* @private
*/
getRef: function(ref, info, config) {
this.refCache = this.refCache || {};
info = info || {};
config = config || {};
Ext.apply(info, config);
if (info.forceCreate) {
return Ext.ComponentManager.create(info, 'component');
}
var me = this,
cached = me.refCache[ref];
if (!cached) {
me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
if (!cached && info.autoCreate) {
me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
}
if (cached) {
cached.on('destroy', function() {
me.refCache[ref] = null;
});
}
}
return cached;
},
/**
* @private
*/
hasRef: function(ref) {
return this.references && this.references.indexOf(ref.toLowerCase()) !== -1;
},
//TO IMPLEMENT
addRefs: Ext.emptyFn,
addRoutes: Ext.emptyFn,
addStores: Ext.emptyFn,
addProfiles: Ext.emptyFn,
addModels: Ext.emptyFn
}, function() {
});
/**
* @author Ed Spencer
* @ignore
* @private
*
* Manages the stack of {@link Ext.app.Action} instances that have been decoded, pushes new urls into the browser's
* location object and listens for changes in url, firing the {@link #change} event when a change is detected.
*
* This is tied to an {@link Ext.app.Application Application} instance. The Application performs all of the
* interactions with the History object, no additional integration should be required.
*/
Ext.define('Ext.app.History', {
mixins: ['Ext.mixin.Observable'],
/**
* @event change
* Fires when a change in browser url is detected
* @param {String} url The new url, after the hash (e.g. http://myapp.com/#someUrl returns 'someUrl')
*/
config: {
/**
* @cfg {Array} actions The stack of {@link Ext.app.Action action} instances that have occured so far
*/
actions: [],
/**
* @cfg {Boolean} updateUrl True to automatically update the browser's url when {@link #add} is called
*/
updateUrl: true,
/**
* @cfg {String} token The current token as read from the browser's location object
*/
token: ''
},
constructor: function(config) {
if (Ext.feature.has.History) {
window.addEventListener('hashchange', Ext.bind(this.detectStateChange, this));
}
else {
setInterval(Ext.bind(this.detectStateChange, this), 50);
}
this.initConfig(config);
},
/**
* Adds an {@link Ext.app.Action Action} to the stack, optionally updating the browser's url and firing the
* {@link #change} event.
* @param {Ext.app.Action} action The Action to add to the stack
* @param {Boolean} silent Cancels the firing of the {@link #change} event if true
*/
add: function(action, silent) {
this.getActions().push(Ext.factory(action, Ext.app.Action));
var url = action.getUrl();
if (this.getUpdateUrl()) {
// history.pushState({}, action.getTitle(), "#" + action.getUrl());
this.setToken(url);
window.location.hash = url;
}
if (silent !== true) {
this.fireEvent('change', url);
}
this.setToken(url);
},
/**
* @private
*/
back: function() {
this.getActions().pop().run();
},
/**
* @private
*/
applyToken: function(token) {
return token[0] == '#' ? token.substr(1) : token;
},
/**
* @private
*/
detectStateChange: function() {
var newToken = this.applyToken(window.location.hash),
oldToken = this.getToken();
if (newToken != oldToken) {
this.onStateChange();
this.setToken(newToken);
}
},
/**
* @private
*/
onStateChange: function() {
this.fireEvent('change', window.location.hash.substr(1));
}
});
/**
* @author Ed Spencer
*
* A Profile represents a range of devices that fall under a common category. For the vast majority of apps that use
* device profiles, the app defines a Phone profile and a Tablet profile. Doing this enables you to easily customize
* the experience for the different sized screens offered by those device types.
*
* Only one Profile can be active at a time, and each Profile defines a simple {@link #isActive} function that should
* return either true or false. The first Profile to return true from its isActive function is set as your Application's
* {@link Ext.app.Application#currentProfile current profile}.
*
* A Profile can define any number of {@link #models}, {@link #views}, {@link #controllers} and {@link #stores} which
* will be loaded if the Profile is activated. It can also define a {@link #launch} function that will be called after
* all of its dependencies have been loaded, just before the {@link Ext.app.Application#launch application launch}
* function is called.
*
* ## Sample Usage
*
* First you need to tell your Application about your Profile(s):
*
* Ext.application({
* name: 'MyApp',
* profiles: ['Phone', 'Tablet']
* });
*
* This will load app/profile/Phone.js and app/profile/Tablet.js. Here's how we might define the Phone profile:
*
* Ext.define('MyApp.profile.Phone', {
* extend: 'Ext.app.Profile',
*
* views: ['Main'],
*
* isActive: function() {
* return Ext.os.is.Phone;
* }
* });
*
* The isActive function returns true if we detect that we are running on a phone device. If that is the case the
* Application will set this Profile active and load the 'Main' view specified in the Profile's {@link #views} config.
*
* ## Class Specializations
*
* Because Profiles are specializations of an application, all of the models, views, controllers and stores defined
* in a Profile are expected to be namespaced under the name of the Profile. Here's an expanded form of the example
* above:
*
* Ext.define('MyApp.profile.Phone', {
* extend: 'Ext.app.Profile',
*
* views: ['Main'],
* controllers: ['Signup'],
* models: ['MyApp.model.Group'],
*
* isActive: function() {
* return Ext.os.is.Phone;
* }
* });
*
* In this case, the Profile is going to load *app/view/phone/Main.js*, *app/controller/phone/Signup.js* and
* *app/model/Group.js*. Notice that in each of the first two cases the name of the profile ('phone' in this case) was
* injected into the class names. In the third case we specified the full Model name (for Group) so the Profile name
* was not injected.
*
* For a fuller understanding of the ideas behind Profiles and how best to use them in your app, we suggest you read
* the device profiles guide.
*/
Ext.define('Ext.app.Profile', {
mixins: {
observable: "Ext.mixin.Observable"
},
config: {
/**
* @cfg {String} namespace The namespace that this Profile's classes can be found in. Defaults to the lowercased
* Profile {@link #name}, for example a Profile called MyApp.profile.Phone will by default have a 'phone'
* namespace, which means that this Profile's additional models, stores, views and controllers will be loaded
* from the MyApp.model.phone.*, MyApp.store.phone.*, MyApp.view.phone.* and MyApp.controller.phone.* namespaces
* respectively.
* @accessor
*/
namespace: 'auto',
/**
* @cfg {String} name The name of this Profile. Defaults to the last section of the class name (e.g. a profile
* called MyApp.profile.Phone will default the name to 'Phone').
* @accessor
*/
name: 'auto',
/**
* @cfg {Array} controllers Any additional {@link Ext.app.Application#controllers Controllers} to load for this
* profile. Note that each item here will be prepended with the Profile namespace when loaded. Example usage:
*
* controllers: [
* 'Users',
* 'MyApp.controller.Products'
* ]
*
* This will load *MyApp.controller.tablet.Users* and *MyApp.controller.Products*.
* @accessor
*/
controllers: [],
/**
* @cfg {Array} models Any additional {@link Ext.app.Application#models Models} to load for this profile. Note
* that each item here will be prepended with the Profile namespace when loaded. Example usage:
*
* models: [
* 'Group',
* 'MyApp.model.User'
* ]
*
* This will load *MyApp.model.tablet.Group* and *MyApp.model.User*.
* @accessor
*/
models: [],
/**
* @cfg {Array} views Any additional {@link Ext.app.Application#views views} to load for this profile. Note
* that each item here will be prepended with the Profile namespace when loaded. Example usage:
*
* views: [
* 'Main',
* 'MyApp.view.Login'
* ]
*
* This will load *MyApp.view.tablet.Main* and *MyApp.view.Login*.
* @accessor
*/
views: [],
/**
* @cfg {Array} stores Any additional {@link Ext.app.Application#stores Stores} to load for this profile. Note
* that each item here will be prepended with the Profile namespace when loaded. Example usage:
*
* stores: [
* 'Users',
* 'MyApp.store.Products'
* ]
*
* This will load *MyApp.store.tablet.Users* and *MyApp.store.Products*.
* @accessor
*/
stores: [],
/**
* @cfg {Ext.app.Application} application The {@link Ext.app.Application Application} instance that this
* Profile is bound to. This is set automatically. Read only.
* @accessor
*/
application: null
},
/**
* Creates a new Profile instance
*/
constructor: function(config) {
this.initConfig(config);
this.mixins.observable.constructor.apply(this, arguments);
},
/**
* Determines whether or not this Profile is active on the device isActive is executed on. Should return true if
* this profile is meant to be active on this device, false otherwise. Each Profile should implement this function
* (the default implementation just returns false).
* @return {Boolean} True if this Profile should be activated on the device it is running on, false otherwise
*/
isActive: function() {
return false;
},
/**
* @method
* The launch function is called by the {@link Ext.app.Application Application} if this Profile's {@link #isActive}
* function returned true. This is typically the best place to run any profile-specific app launch code. Example
* usage:
*
* launch: function() {
* Ext.create('MyApp.view.tablet.Main');
* }
*/
launch: Ext.emptyFn,
/**
* @private
*/
applyNamespace: function(name) {
if (name == 'auto') {
name = this.getName();
}
return name.toLowerCase();
},
/**
* @private
*/
applyName: function(name) {
if (name == 'auto') {
var pieces = this.$className.split('.');
name = pieces[pieces.length - 1];
}
return name;
},
/**
* @private
* Computes the full class names of any specified model, view, controller and store dependencies, returns them in
* an object map for easy loading
*/
getDependencies: function() {
var allClasses = [],
format = Ext.String.format,
appName = this.getApplication().getName(),
namespace = this.getNamespace(),
map = {
model: this.getModels(),
view: this.getViews(),
controller: this.getControllers(),
store: this.getStores()
},
classType, classNames, fullyQualified;
for (classType in map) {
classNames = [];
Ext.each(map[classType], function(className) {
if (Ext.isString(className)) {
//if there is a '.' anywhere in the string we treat it as fully qualified, if not compute the
//namespaced classname below
if (className.match("\\.")) {
fullyQualified = className;
} else {
fullyQualified = format('{0}.{1}.{2}.{3}', appName, classType, namespace, className);
}
classNames.push(fullyQualified);
allClasses.push(fullyQualified);
}
});
map[classType] = classNames;
}
map.all = allClasses;
return map;
}
});
/**
* @author Ed Spencer
*
* Ext.app.Application defines the set of {@link Ext.data.Model Models}, {@link Ext.app.Controller Controllers},
* {@link Ext.app.Profile Profiles}, {@link Ext.data.Store Stores} and {@link Ext.Component Views} that an application
* consists of. It automatically loads all of those dependencies and can optionally specify a {@link #launch} function
* that will be called when everthing is ready.
*
* Sample usage:
*
* Ext.application({
* name: 'MyApp',
*
* models: ['User', 'Group'],
* stores: ['Users'],
* controllers: ['Users'],
* views: ['Main', 'ShowUser'],
*
* launch: function() {
* Ext.create('MyApp.view.Main');
* }
* });
*
* Creating an Application instance is the only time in Sencha Touch 2 that we don't use Ext.create to create the new
* instance. Instead, the {@link Ext#application} function instantiates an Ext.app.Application internally,
* automatically loading the Ext.app.Application class if it is not present on the page already and hooking in to
* {@link Ext#onReady} before creating the instance itself. An alternative is to use Ext.create inside an Ext.onReady
* callback, but Ext.application is preferred.
*
* ## Dependencies
*
* Application follows a simple convention when it comes to specifying the controllers, views, models, stores and
* profiles it requires. By default it expects each of them to be found inside the *app/controller*, *app/view*,
* *app/model*, *app/store* and *app/profile* directories in your app - if you follow this convention you can just
* specify the last part of each class name and Application will figure out the rest for you:
*
* Ext.application({
* name: 'MyApp',
*
* controllers: ['Users'],
* models: ['User', 'Group'],
* stores: ['Users'],
* views: ['Main', 'ShowUser']
* });
*
* The example above will load 6 files:
*
* * app/model/User.js
* * app/model/Group.js
* * app/store/Users.js
* * app/controller/Users.js
* * app/view/Main.js
* * app/view/ShowUser.js
*
* ### Nested Dependencies
*
* For larger apps it's common to split the models, views and controllers into subfolders so keep the project
* organized. This is especially true of views - it's not unheard of for large apps to have over a hundred separate
* view classes so organizing them into folders can make maintenance much simpler.
*
* To specify dependencies in subfolders just use a period (".") to specify the folder:
*
* Ext.application({
* name: 'MyApp',
*
* controllers: ['Users', 'nested.MyController'],
* views: ['products.Show', 'products.Edit', 'user.Login']
* });
*
* In this case these 5 files will be loaded:
*
* * app/controller/Users.js
* * app/controller/nested/MyController.js
* * app/view/products/Show.js
* * app/view/products/Edit.js
* * app/view/user/Login.js
*
* Note that we can mix and match within each configuration here - for each model, view, controller, profile or store
* you can specify either just the final part of the class name (if you follow the directory conventions), or the full
* class name.
*
* ### External Dependencies
*
* Finally, we can specify application dependencies from outside our application by fully-qualifying the classes we
* want to load. A common use case for this is sharing authentication logic between multiple applications. Perhaps you
* have several apps that login via a common user database and you want to share that code between them. An easy way to
* do this is to create a folder alongside your app folder and then add its contents as dependencies for your app.
*
* For example, let's say our shared login code contains a login controller, a user model and a login form view. We
* want to use all of these in our application:
*
* Ext.Loader.setPath({
* 'Auth': 'Auth'
* });
*
* Ext.application({
* views: ['Auth.view.LoginForm', 'Welcome'],
* controllers: ['Auth.controller.Sessions', 'Main'],
* models: ['Auth.model.User']
* });
*
* This will load the following files:
*
* * Auth/view/LoginForm.js
* * Auth/controller/Sessions.js
* * Auth/model/User.js
* * app/view/Welcome.js
* * app/controller/Main.js
*
* The first three were loaded from outside our application, the last two from the application itself. Note how we can
* still mix and match application files and external dependency files.
*
* Note that to enable the loading of external dependencies we just have to tell the Loader where to find those files,
* which is what we do with the Ext.Loader.setPath call above. In this case we're telling the Loader to find any class
* starting with the 'Auth' namespace inside our 'Auth' folder. This means we can drop our common Auth code into our
* application alongside the app folder and the framework will be able to figure out how to load everything.
*
* ## Launching
*
* The final item in the example above is a launch function. This is called as soon as all of the dependencies have
* been loaded and the Controllers instantiated. Usually this function is used to create the initial UI of your
* application, check authentication or initialize any other application-launching behavior.
*
* ## Adding to Home Screen
*
* iOS devices allow your users to add your app to their home screen for easy access. iOS allows you to customize
* several aspects of this, including the icon that will appear on the home screen and the startup image. These can be
* specified in the Ext.application setup block:
*
* Ext.application({
* name: 'MyApp',
*
* {@link #icon}: 'resources/img/icon.png',
* {@link #glossOnIcon}: false,
* {@link #phoneStartupScreen}: 'resources/img/phone_startup.png',
* {@link #tabletStartupScreen}: 'resources/img/tablet_startup.png'
* });
*
* When the user adds your app to the home screen, your resources/img/icon.png file will be used as the application
* icon. We also used the {@link #glossOnIcon} configuration to turn off the gloss effect that is automatically added
* to icons in iOS. Finally we used the {@link #phoneStartupScreen} and {@link #tabletStartupScreen} configurations to
* provide the images that will be displayed while your application is starting up. See also {@link #phoneIcon},
* {@link #tabletIcon} and {@link #statusBarStyle}.
*
* ## Find out more
*
* If you are not already familiar with writing applications with Sencha Touch 2 we recommend reading the
* intro to applications guide, which lays out the core principles of writing apps
* with Sencha Touch 2.
*/
Ext.define('Ext.app.Application', {
extend: 'Ext.app.Controller',
requires: [
'Ext.app.History',
'Ext.app.Profile',
'Ext.app.Router',
'Ext.app.Action'
],
config: {
/**
* @cfg {String/Object} icon Path to the .png image file to use when your app is added to the home screen on an
* iOS device. When passed in as a String, the same icon will be used for both phone and tablet devices. To set
* different icons for tablets and phones see the {@link #tabletIcon} and {@link #phoneIcon} configs.
* @accessor
*/
/**
* @cfg {String} tabletIcon Path to the .png image file to use when your app is added to the home screen on an
* iOS **tablet** device (iPad).
* @accessor
*/
/**
* @cfg {String} phoneIcon Path to the .png image file to use when your app is added to the home screen on an
* iOS **phone** device (iPhone or iPod).
* @accessor
*/
/**
* @cfg {Boolean} glossOnIcon If set to false, the 'gloss' effect added to home screen {@link #icon icons} on
* iOS devices will be removed.
* @accessor
*/
/**
* @cfg {String} statusBarStyle Allows you to set the style of the status bar when your app is added to the
* home screen on iOS devices. Defaults to 'black'. Alternative is to set to 'black-translucent', which turns
* the status bar semi-transparent and overlaps the app content. This is usually not a good option for web apps
* @accessor
*/
/**
* @cfg {String} phoneStartupScreen Path to the .png image file that will be displayed while the app is
* starting up once it has been added to the home screen of an iOS phone device (iPhone or iPod). This .png
* file should be 320px wide and 460px high.
* @accessor
*/
/**
* @cfg {String} tabletStartupScreen Path to the .png image file that will be displayed while the app is
* starting up once it has been added to the home screen of an iOS tablet device (iPad). This .png file should
* be 768px wide and 1004px high.
* @accessor
*/
/**
* @cfg {Array} profiles The set of profiles to load for this Application. Each profile is expected to
* exist inside the *app/profile* directory and define a class following the convention
* AppName.profile.ProfileName. For example, in the code below, the classes *AppName.profile.Phone*
* and *AppName.profile.Tablet* will be loaded. Note that we are able to specify
* either the full class name (as with *AppName.profile.Tablet*) or just the final part of the class name
* and leave Application to automatically prepend *AppName.profile.’* to each:
*
* profiles: [
* 'Phone',
* 'AppName.profile.Tablet',
* 'SomeCustomNamespace.profile.Desktop'
* ]
* @accessor
*/
profiles: [],
/**
* @cfg {Array} stores The set of stores to load for this Application. Each store is expected to
* exist inside the *app/store* directory and define a class following the convention
* AppName.store.StoreName. For example, in the code below, the *AppName.store.Users* class will be loaded.
* Note that we are able to specify either the full class name (as with *AppName.store.Groups*) or just the
* final part of the class name and leave Application to automatically prepend *AppName.store.’* to each:
*
* stores: [
* 'Users',
* 'AppName.store.Groups',
* 'SomeCustomNamespace.store.Orders'
* ]
* @accessor
*/
stores: [],
/**
* @cfg {Array} controllers The set of controllers to load for this Application. Each controller is expected to
* exist inside the *app/controller* directory and define a class following the convention
* AppName.controller.ControllerName. For example, in the code below, the classes *AppName.controller.Users*,
* *AppName.controller.Groups* and *AppName.controller.Products* will be loaded. Note that we are able to specify
* either the full class name (as with *AppName.controller.Products*) or just the final part of the class name
* and leave Application to automatically prepend *AppName.controller.’* to each:
*
* controllers: [
* 'Users',
* 'Groups',
* 'AppName.controller.Products',
* 'SomeCustomNamespace.controller.Orders'
* ]
* @accessor
*/
controllers: [],
/**
* @cfg {Array} models The set of models to load for this Application. Each model is expected to exist inside the
* *app/model* directory and define a class following the convention AppName.model.ModelName. For example, in the
* code below, the classes *AppName.model.User*, *AppName.model.Group* and *AppName.model.Product* will be loaded.
* Note that we are able to specify either the full class name (as with *AppName.model.Product*) or just the
* final part of the class name and leave Application to automatically prepend *AppName.model.* to each:
*
* models: [
* 'User',
* 'Group',
* 'AppName.model.Product',
* 'SomeCustomNamespace.model.Order'
* ]
* @accessor
*/
models: [],
/**
* @cfg {Array} views The set of views to load for this Application. Each view is expected to exist inside the
* *app/view* directory and define a class following the convention AppName.view.ViewName. For example, in the
* code below, the classes *AppName.view.Users*, *AppName.view.Groups* and *AppName.view.Products* will be loaded.
* Note that we are able to specify either the full class name (as with *AppName.view.Products*) or just the
* final part of the class name and leave Application to automatically prepend *AppName.view.* to each:
*
* views: [
* 'Users',
* 'Groups',
* 'AppName.view.Products',
* 'SomeCustomNamespace.view.Orders'
* ]
* @accessor
*/
views: [],
/**
* @cfg {Ext.app.History} history The global {@link Ext.app.History History} instance attached to this
* Application. Read only
* @accessor
*/
history: {},
/**
* @cfg {String} name The name of the Application. This should be a single word without spaces or periods
* because it is used as the Application's global namespace. All classes in your application should be
* namespaced undef the Application's name - for example if your application name is 'MyApp', your classes
* should be named 'MyApp.model.User', 'MyApp.controller.Users', 'MyApp.view.Main' etc
* @accessor
*/
name: null,
/**
* @cfg {String} appFolder The path to the directory which contains all application's classes.
* This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
* Defaults to 'app'
* @accessor
*/
appFolder : 'app',
/**
* @cfg {Ext.app.Router} router The global {@link Ext.app.Router Router} instance attached to this Application.
* Read only.
* @accessor
*/
router: {},
/**
* @cfg
* @private
* Used internally as the collection of instantiated controllers. Use {@link #getController} instead
* @accessor
*/
controllerInstances: [],
/**
* @cfg
* @private
* Used internally as the collection of instantiated profiles
* @accessor
*/
profileInstances: [],
/**
* @cfg {Ext.app.Profile} currentProfile The {@link Ext.app.Profile Profile} that is currently active for the
* Application. This is set once, automatically by the Application before launch. Read only.
* @accessor
*/
currentProfile: null,
/**
* @cfg {Function} launch An optional function that will be called when the Application is ready to be
* launched. This is normally used to render any initial UI required by your application
* @accessor
*/
launch: Ext.emptyFn,
/**
* @private
* @cfg {Boolean} enableLoader Private config to disable loading of Profiles at application construct time.
* This is used by Sencha's unit test suite to test Application.js in isolation and is likely to be removed
* in favor of a more pleasing solution by the time you use it.
* @accessor
*/
enableLoader: true
},
/**
* Constructs a new Application instance
*/
constructor: function(config) {
this.initConfig(config);
//it's common to pass in functions to an application but because they are not predictable config names they
//aren't ordinarily placed onto this so we need to do it manually
for (var key in config) {
this[key] = config[key];
}
if (this.getEnableLoader() !== false) {
Ext.require(this.getProfiles(), this.onProfilesLoaded, this);
}
},
/**
* Dispatches a given {@link Ext.app.Action} to the relevant Controller instance. This is not usually called
* directly by the developer, instead Sencha Touch's History support picks up on changes to the browser's url
* and calls dispatch automatically.
* @param {Ext.app.Action} action The action to dispatch
* @param {Boolean} addToHistory True by default, sets the browser's url to the action's url
*/
dispatch: function(action, addToHistory) {
action = action || {};
Ext.applyIf(action, {
application: this
});
action = Ext.factory(action, Ext.app.Action);
if (action) {
var profile = this.getCurrentProfile(),
profileNS = profile ? profile.getNamespace() : undefined,
controller = this.getController(action.getController(), profileNS);
if (controller) {
if (addToHistory !== false) {
this.getHistory().add(action, true);
}
controller.execute(action);
}
}
},
/**
* Redirects the browser to the given url. This only affects the url after the #. You can pass in either a String
* or a Model instance - if a Model instance is defined its {@link Ext.data.Model#toUrl toUrl} function is called,
* which returns a string representing the url for that model. Internally, this uses your application's
* {@link Ext.app.Router Router} to decode the url into a matching controller action and then calls
* {@link #dispatch}.
* @param {String/Ext.data.Model} url The String url to redirect to
*/
redirectTo: function(url) {
if (Ext.data && Ext.data.Model && url instanceof Ext.data.Model) {
var record = url;
url = record.toUrl();
}
var decoded = this.getRouter().recognize(url);
if (decoded) {
decoded.url = url;
if (record) {
decoded.data = {};
decoded.data.record = record;
}
return this.dispatch(decoded);
}
},
/**
* @private
* (documented on Controller's control config)
*/
control: function(selectors, controller) {
//if the controller is not defined, use this instead (the application instance)
controller = controller || this;
var dispatcher = this.getEventDispatcher(),
refs = (controller) ? controller.getRefs() : {},
selector, eventName, listener, listeners, ref;
for (selector in selectors) {
if (selectors.hasOwnProperty(selector)) {
listeners = selectors[selector];
ref = refs[selector];
//refs can be used in place of selectors
if (ref) {
selector = ref.selector || ref;
}
for (eventName in listeners) {
listener = listeners[eventName];
if (Ext.isString(listener)) {
listener = controller[listener];
}
dispatcher.addListener('component', selector, eventName, listener, controller);
}
}
}
},
/**
* @private
* Returns the Controller instance for the given controller name
* @param {String} name The name of the Controller
* @param {String} profileName Optional profile name. If passed, this is the same as calling
* getController('profileName.controllerName')
*/
getController: function(name, profileName) {
var instances = this.getControllerInstances(),
appName = this.getName(),
format = Ext.String.format,
topLevelName;
if (name instanceof Ext.app.Controller) {
return name;
}
if (instances[name]) {
return instances[name];
} else {
topLevelName = format("{0}.controller.{1}", appName, name);
profileName = format("{0}.controller.{1}.{2}", appName, profileName, name);
return instances[profileName] || instances[topLevelName];
}
},
/**
* @private
* Callback that is invoked when all of the configured Profiles have been loaded. Detects the current profile and
* gathers any additional dependencies from that profile, then loads all of those dependencies.
*/
onProfilesLoaded: function() {
var profiles = this.getProfiles(),
length = profiles.length,
instances = [],
requires = this.gatherDependencies(),
current, i, profileDeps;
for (i = 0; i < length; i++) {
instances[i] = Ext.create(profiles[i], {
application: this
});
/*
* Note that we actually require all of the dependencies for all Profiles - this is so that we can produce
* a single build file that will work on all defined Profiles. Although the other classes will be loaded,
* the correct Profile will still be identified and the other classes ignored. While this feels somewhat
* inefficient, the majority of the bulk of an application is likely to be the framework itself. The bigger
* the app though, the bigger the effect of this inefficiency so ideally we will create a way to create and
* load Profile-specific builds in a future release.
*/
profileDeps = instances[i].getDependencies();
requires = requires.concat(profileDeps.all);
if (instances[i].isActive() && !current) {
current = instances[i];
this.setCurrentProfile(current);
this.setControllers(this.getControllers().concat(profileDeps.controller));
this.setModels(this.getModels().concat(profileDeps.model));
this.setViews(this.getViews().concat(profileDeps.view));
this.setStores(this.getStores().concat(profileDeps.store));
}
}
this.setProfileInstances(instances);
Ext.require(requires, this.loadControllerDependencies, this);
},
/**
* @private
* This is solely present for backwards compatibility with 1.x. In 1.x a Controller could specify additional
* Models, Views and Stores to load. Here we look to see if any Controller is doing so and load them before
* finalizing application bootup.
*/
loadControllerDependencies: function() {
var controllers = this.getControllers(),
length = controllers.length,
classes = [],
name = this.getName(),
format = Ext.String.format,
controller, proto, i;
for (i = 0; i < length; i++) {
controller = Ext.ClassManager.classes[controllers[i]];
proto = controller.prototype;
Ext.each(proto.models, function(modelName) {
classes.push(format('{0}.model.{1}', name, modelName));
}, this);
Ext.each(proto.views, function(viewName) {
classes.push(format('{0}.view.{1}', name, viewName));
}, this);
Ext.each(proto.stores, function(storeName) {
classes.push(format('{0}.store.{1}', name, storeName));
this.setStores(this.getStores().concat([storeName]));
}, this);
}
Ext.require(classes, this.onDependenciesLoaded, this);
},
/**
* @private
* Callback that is invoked when all of the Application + current Profile dependencies have been loaded.
* Instantiates all of the controllers and stores then launches the app
*/
onDependenciesLoaded: function() {
var me = this,
profile = this.getCurrentProfile(),
launcher = this.getLaunch(),
controllers, name;
me.instantiateStores();
me.instantiateControllers();
controllers = this.getControllerInstances();
if (profile) {
profile.launch();
}
launcher.call(me);
for (name in controllers) {
controllers[name].launch(this);
}
me.redirectTo(window.location.hash.substr(1));
},
/**
* @private
* Gathers up all of the previously computed MVCS dependencies into a single array that we can pass to Ext.require
*/
gatherDependencies: function() {
var classes = this.getModels().concat(this.getViews()).concat(this.getControllers());
Ext.each(this.getStores(), function(storeName) {
if (Ext.isString(storeName)) {
classes.push(storeName);
}
}, this);
return classes;
},
/**
* @private
* Should be called after dependencies are loaded, instantiates all of the Stores specified in the {@link #stores}
* config. For each item in the stores array we make sure the Store is instantiated. When strings are specified,
* the corresponding app/store/StoreName.js was loaded so we now instantiate a MyApp.store.StoreName, giving it the
* id StoreName.
*/
instantiateStores: function() {
var stores = this.getStores(),
length = stores.length,
store, storeClass, storeName, splits, i;
for (i = 0; i < length; i++) {
store = stores[i];
if (Ext.data && Ext.data.Store && !(store instanceof Ext.data.Store)) {
if (Ext.isString(store)) {
storeName = store;
storeClass = Ext.ClassManager.classes[store];
store = {
xclass: store
};
//we don't want to wipe out a configured storeId in the app's Store subclass so need
//to check for this first
if (storeClass.prototype.defaultConfig.storeId === undefined) {
splits = storeName.split('.');
store.id = splits[splits.length - 1];
}
}
stores[i] = Ext.factory(store, Ext.data.Store);
}
}
this.setStores(stores);
},
/**
* @private
* Called once all of our controllers have been loaded
*/
instantiateControllers: function() {
var controllerNames = this.getControllers(),
instances = {},
length = controllerNames.length,
name, i;
for (i = 0; i < length; i++) {
name = controllerNames[i];
instances[name] = Ext.create(name, {
application: this
});
instances[name].init();
}
return this.setControllerInstances(instances);
},
/**
* @private
* As a convenience developers can locally qualify controller names (e.g. 'MyController' vs
* 'MyApp.controller.MyController'). This just makes sure everything ends up fully qualified
*/
applyControllers: function(controllers) {
var length = controllers.length,
appName = this.getName(),
name, i;
for (i = 0; i < length; i++) {
name = controllers[i];
//we check name === appName to allow MyApp.controller.MyApp to exist
if (Ext.Loader.getPrefix(name) === "" || name === appName) {
controllers[i] = appName + '.controller.' + name;
}
}
return controllers;
},
/**
* @private
* As a convenience developers can locally qualify store names (e.g. 'MyStore' vs
* 'MyApp.store.MyStore'). This just makes sure everything ends up fully qualified
*/
applyStores: function(stores) {
var length = stores.length,
appName = this.getName(),
name, i;
for (i = 0; i < length; i++) {
name = stores[i];
//we check name === appName to allow MyApp.store.MyApp to exist
if (Ext.isString(name) && (Ext.Loader.getPrefix(name) === "" || name === appName)) {
stores[i] = appName + '.store.' + name;
}
}
return stores;
},
/**
* @private
* As a convenience developers can locally qualify model names (e.g. 'MyModel' vs
* 'MyApp.model.MyModel'). This just makes sure everything ends up fully qualified
*/
applyModels: function(models) {
var length = models.length,
appName = this.getName(),
name, i;
for (i = 0; i < length; i++) {
name = models[i];
//we check name === appName to allow MyApp.model.MyApp to exist
if (Ext.isString(name) && (Ext.Loader.getPrefix(name) === "" || name === appName)) {
models[i] = appName + '.model.' + name;
}
}
return models;
},
/**
* @private
* As a convenience developers can locally qualify view names (e.g. 'MyView' vs
* 'MyApp.view.MyView'). This just makes sure everything ends up fully qualified
*/
applyViews: function(views) {
var length = views.length,
appName = this.getName(),
name, i;
for (i = 0; i < length; i++) {
name = views[i];
//we check name === appName to allow MyApp.view.MyApp to exist
if (Ext.isString(name) && (Ext.Loader.getPrefix(name) === "" || name === appName)) {
views[i] = appName + '.view.' + name;
}
}
return views;
},
/**
* @private
* As a convenience developers can locally qualify profile names (e.g. 'MyProfile' vs
* 'MyApp.profile.MyProfile'). This just makes sure everything ends up fully qualified
*/
applyProfiles: function(profiles) {
var length = profiles.length,
appName = this.getName(),
name, i;
for (i = 0; i < length; i++) {
name = profiles[i];
//we check name === appName to allow MyApp.profile.MyApp to exist
if (Ext.isString(name) && (Ext.Loader.getPrefix(name) === "" || name === appName)) {
profiles[i] = appName + '.profile.' + name;
}
}
return profiles;
},
/**
* @private
* Checks that the name configuration has any whitespace, and trims them if found.
*/
applyName: function(name) {
var oldName;
if (name && name.match(/ /g)) {
oldName = name;
name = name.replace(/ /g, "");
Ext.Logger.warn('Attempting to create an application with a name which contains whitespace ("' + oldName + '"). Renamed to "' + name + '".');
}
return name;
},
/**
* @private
* Makes sure the app namespace exists, sets the `app` property of the namespace to this application and sets its
* loading path (checks to make sure the path hadn't already been set via Ext.Loader.setPath)
*/
updateName: function(newName) {
Ext.ClassManager.setNamespace(newName + '.app', this);
if (!Ext.Loader.config.paths[newName]) {
Ext.Loader.setPath(newName, this.getAppFolder());
}
},
/**
* @private
*/
applyRouter: function(config) {
return Ext.factory(config, Ext.app.Router, this.getRouter());
},
/**
* @private
*/
applyHistory: function(config) {
var history = Ext.factory(config, Ext.app.History, this.getHistory());
history.on('change', this.onHistoryChange, this);
return history;
},
/**
* @private
*/
onHistoryChange: function(url) {
this.dispatch(this.getRouter().recognize(url), false);
}
}, function() {
});
/**
* @author Ed Spencer
* @class Ext.data.Batch
*
* Provides a mechanism to run one or more {@link Ext.data.Operation operations} in a given order. Fires the 'operationcomplete' event * after the completion of each Operation, and the 'complete' event when all Operations have been successfully executed. Fires an 'exception' * event if any of the Operations encounter an exception.
* *Usually these are only used internally by {@link Ext.data.proxy.Proxy} classes
* */ Ext.define('Ext.data.Batch', { mixins: { observable: 'Ext.mixin.Observable' }, config: { /** * @cfg {Boolean} autoStart true to immediately start processing the batch as soon as it is constructed (defaults to false) */ autoStart: false, /** * @cfg {Boolean} pauseOnException true to automatically pause the execution of the batch if any operation encounters an exception (defaults to true) */ pauseOnException: true, /** * @cfg {Ext.data.Proxy} proxy The proxy this Batch belongs to. Used to make the requests for each operation in the Batch. */ proxy: null }, /** * The index of the current operation being executed * @property current * @type Number */ current: -1, /** * The total number of operations in this batch. Read only * @property total * @type Number */ total: 0, /** * True if the batch is currently running * @property isRunning * @type Boolean */ isRunning: false, /** * True if this batch has been executed completely * @property isComplete * @type Boolean */ isComplete: false, /** * True if this batch has encountered an exception. This is cleared at the start of each operation * @property hasException * @type Boolean */ hasException: false, /** * @event complete * Fired when all operations of this batch have been completed * @param {Ext.data.Batch} batch The batch object * @param {Object} operation The last operation that was executed */ /** * @event exception * Fired when a operation encountered an exception * @param {Ext.data.Batch} batch The batch object * @param {Object} operation The operation that encountered the exception */ /** * @event operationcomplete * Fired when each operation of the batch completes * @param {Ext.data.Batch} batch The batch object * @param {Object} operation The operation that just completed */ /** * Creates new Batch object. * @param {Object} config (optional) Config object */ constructor: function(config) { var me = this; me.initConfig(config); /** * Ordered array of operations that will be executed by this batch * @property {Ext.data.Operation[]} operations */ me.operations = []; }, /** * Adds a new operation to this batch * @param {Object} operation The {@link Ext.data.Operation Operation} object */ add: function(operation) { this.total++; operation.setBatch(this); this.operations.push(operation); }, /** * Kicks off the execution of the batch, continuing from the next operation if the previous * operation encountered an exception, or if execution was paused */ start: function() { this.hasException = false; this.isRunning = true; this.runNextOperation(); }, /** * @private * Runs the next operation, relative to this.current. */ runNextOperation: function() { this.runOperation(this.current + 1); }, /** * Pauses execution of the batch, but does not cancel the current operation */ pause: function() { this.isRunning = false; }, /** * Executes a operation by its numeric index * @param {Number} index The operation index to run */ runOperation: function(index) { var me = this, operations = me.operations, operation = operations[index], onProxyReturn; if (operation === undefined) { me.isRunning = false; me.isComplete = true; me.fireEvent('complete', me, operations[operations.length - 1]); } else { me.current = index; onProxyReturn = function(operation) { var hasException = operation.hasException(); if (hasException) { me.hasException = true; me.fireEvent('exception', me, operation); } else { me.fireEvent('operationcomplete', me, operation); } if (hasException && me.getPauseOnException()) { me.pause(); } else { operation.setCompleted(); me.runNextOperation(); } }; operation.setStarted(); me.getProxy()[operation.getAction()](operation, onProxyReturn, me); } } }); /** * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either * to a configured URL, or to a URL specified at request time. * * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available * to the statement immediately following the {@link #request} call. To process returned data, use a success callback * in the request options object, or an {@link #requestcomplete event listener}. * * # File Uploads * * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests. * Instead the form is submitted in the standard manner with the DOM <form> element temporarily modified to have its * target set to refer to a dynamically generated, hidden <iframe> which is inserted into the document but removed * after the return data has been gathered. * * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to * insert the text unchanged into the document body. * * Characters which are significant to an HTML parser must be sent as HTML entities, so encode `<` as `<`, `&` as * `&` etc. * * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a * responseText property in order to conform to the requirements of event handlers and callbacks. * * Be aware that file upload packets are sent with the content type multipart/form and some server technologies * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the * packet content. * * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire. */ Ext.define('Ext.data.Connection', { mixins: { observable: 'Ext.util.Observable' }, statics: { requestId: 0 }, config: { /** * @cfg {String} url * The default URL to be used for requests to the server. * @accessor */ url: null, async: true, /** * @cfg {String} [method=undefined] * The default HTTP method to be used for requests. Note that this is case-sensitive and * should be all caps. * * Defaults to undefined; if not set but params are present will use "POST", otherwise "GET". */ method: null, username: '', password: '', /** * @cfg {Boolean} disableCaching * True to add a unique cache-buster param to GET requests. * @accessor */ disableCaching: true, /** * @cfg {String} disableCachingParam * Change the parameter which is sent went disabling caching through a cache buster. * @accessor */ disableCachingParam: '_dc', /** * @cfg {Number} timeout * The timeout in milliseconds to be used for requests. * @accessor */ timeout : 30000, /** * @cfg {Object} extraParams * Any parameters to be appended to the request. * @accessor */ extraParams: null, /** * @cfg {Object} defaultHeaders * An object containing request headers which are added to each request made by this object. * @accessor */ defaultHeaders: null, useDefaultHeader : true, defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8', useDefaultXhrHeader : true, defaultXhrHeader : 'XMLHttpRequest', autoAbort: false }, textAreaRe: /textarea/i, multiPartRe: /multipart\/form-data/i, lineBreakRe: /\r\n/g, constructor : function(config) { this.initConfig(config); /** * @event beforerequest * Fires before a network request is made to retrieve a data object. * @param {Ext.data.Connection} conn This Connection object. * @param {Object} options The options config object passed to the {@link #request} method. */ /** * @event requestcomplete * Fires if the request was successfully completed. * @param {Ext.data.Connection} conn This Connection object. * @param {Object} response The XHR object containing the response data. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details. * @param {Object} options The options config object passed to the {@link #request} method. */ /** * @event requestexception * Fires if an error HTTP status was returned from the server. * See [HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) * for details of HTTP status codes. * @param {Ext.data.Connection} conn This Connection object. * @param {Object} response The XHR object containing the response data. * See [The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/) for details. * @param {Object} options The options config object passed to the {@link #request} method. */ this.requests = {}; }, /** * Sends an HTTP request to a remote server. * * **Important:** Ajax server requests are asynchronous, and this call will * return before the response has been received. Process any returned data * in a callback function. * * Ext.Ajax.request({ * url: 'ajax_demo/sample.json', * success: function(response, opts) { * var obj = Ext.decode(response.responseText); * console.dir(obj); * }, * failure: function(response, opts) { * console.log('server-side failure with status code ' + response.status); * } * }); * * To execute a callback function in the correct scope, use the `scope` option. * * @param {Object} options An object which may contain the following properties: * * (The options object may also contain any other property which might be needed to perform * postprocessing in a callback because it is passed to callback functions.) * * @param {String/Function} options.url The URL to which to send the request, or a function * to call which returns a URL string. The scope of the function is specified by the `scope` option. * Defaults to the configured `url`. * * @param {Object/String/Function} options.params An object containing properties which are * used as parameters to the request, a url encoded string or a function to call to get either. The scope * of the function is specified by the `scope` option. * * @param {String} options.method The HTTP method to use * for the request. Defaults to the configured method, or if no method was configured, * "GET" if no parameters are being sent, and "POST" if parameters are being sent. Note that * the method name is case-sensitive and should be all caps. * * @param {Function} options.callback The function to be called upon receipt of the HTTP response. * The callback is called regardless of success or failure and is passed the following parameters: * @param {Object} options.callback.options The parameter to the request call. * @param {Boolean} options.callback.success True if the request succeeded. * @param {Object} options.callback.response The XMLHttpRequest object containing the response data. * See [www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/) for details about * accessing elements of the response. * * @param {Function} options.success The function to be called upon success of the request. * The callback is passed the following parameters: * @param {Object} options.success.response The XMLHttpRequest object containing the response data. * @param {Object} options.success.options The parameter to the request call. * * @param {Function} options.failure The function to be called upon success of the request. * The callback is passed the following parameters: * @param {Object} options.failure.response The XMLHttpRequest object containing the response data. * @param {Object} options.failure.options The parameter to the request call. * * @param {Object} options.scope The scope in which to execute the callbacks: The "this" object for * the callback function. If the `url`, or `params` options were specified as functions from which to * draw values, then this also serves as the scope for those function calls. Defaults to the browser * window. * * @param {Number} options.timeout The timeout in milliseconds to be used for this request. * Defaults to 30 seconds. * * @param {HTMLElement/HTMLElement/String} options.form The `