3.0 source code

This commit is contained in:
agolybev
2015-04-28 17:59:00 +03:00
parent c69fd34bdd
commit 7b3b2248e5
16311 changed files with 1445974 additions and 3108429 deletions

View File

@@ -0,0 +1,69 @@
/**
*
*/
Ext.define('Ext.mixin.Bindable', {
extend: 'Ext.mixin.Mixin',
mixinConfig: {
id: 'bindable'
},
bind: function(instance, boundMethod, bindingMethod, preventDefault) {
if (!bindingMethod) {
bindingMethod = boundMethod;
}
var boundFn = instance[boundMethod],
fn;
instance[boundMethod] = fn = function() {
var binding = fn.$binding,
scope = binding.bindingScope,
args = Array.prototype.slice.call(arguments);
args.push(arguments);
if (!binding.preventDefault && scope[binding.bindingMethod].apply(scope, args) !== false) {
return binding.boundFn.apply(this, arguments);
}
};
fn.$binding = {
preventDefault: !!preventDefault,
boundFn: boundFn,
bindingMethod: bindingMethod,
bindingScope: this
};
return this;
},
unbind: function(instance, boundMethod, bindingMethod) {
if (!bindingMethod) {
bindingMethod = boundMethod;
}
var fn = instance[boundMethod],
binding = fn.$binding,
boundFn, currentBinding;
while (binding) {
boundFn = binding.boundFn;
if (binding.bindingMethod === bindingMethod && binding.bindingScope === this) {
if (currentBinding) {
currentBinding.boundFn = boundFn;
}
else {
instance[boundMethod] = boundFn;
}
return this;
}
currentBinding = binding;
binding = binding.boundFn;
}
return this;
}
});

View File

@@ -0,0 +1,289 @@
/**
* @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 whether the currently cashed filter function is still valid.
* @readonly
*/
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.
* @readonly
*/
filterFn: null,
/**
* @property {Boolean} filtered
* A read-only flag indicating if this object is filtered.
* @readonly
*/
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 {@link Ext.util.Filter},
* an object representing an Ext.util.Filter configuration, or a filter function.
* @return {Object}
*/
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 {@link Ext.util.Filter},
* an object representing an Ext.util.Filter configuration, or a filter function.
* @return {Object}
*/
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 {@link Ext.util.Filter},
* an object representing an Ext.util.Filter configuration, or a filter function.
* @return {Array}
*/
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
// <debug>
else {
Ext.Logger.warn('Invalid filter specified:', filter);
}
// </debug>
// 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;
}
});

View File

@@ -0,0 +1,83 @@
//@tag dom,core
//@require Ext.dom.Helper
/**
* An Identifiable mixin.
* @private
*/
Ext.define('Ext.mixin.Identifiable', {
statics: {
uniqueIds: {}
},
isIdentifiable: true,
mixinId: 'identifiable',
idCleanRegex: /\.|[^\w\-]/g,
defaultIdPrefix: 'ext-',
defaultIdSeparator: '-',
getOptimizedId: function() {
return this.id;
},
getUniqueId: function() {
var id = this.id,
prototype, separator, xtype, uniqueIds, prefix;
if (!id) {
prototype = this.self.prototype;
separator = this.defaultIdSeparator;
uniqueIds = Ext.mixin.Identifiable.uniqueIds;
if (!prototype.hasOwnProperty('identifiablePrefix')) {
xtype = this.xtype;
if (xtype) {
prefix = this.defaultIdPrefix + xtype + separator;
}
else {
prefix = prototype.$className.replace(this.idCleanRegex, separator).toLowerCase() + separator;
}
prototype.identifiablePrefix = prefix;
}
prefix = this.identifiablePrefix;
if (!uniqueIds.hasOwnProperty(prefix)) {
uniqueIds[prefix] = 0;
}
id = this.id = prefix + (++uniqueIds[prefix]);
}
this.getUniqueId = this.getOptimizedId;
return id;
},
setId: function(id) {
this.id = id;
},
/**
* Retrieves the id of this component. Will autogenerate an id if one has not already been set.
* @return {String} id
*/
getId: function() {
var id = this.id;
if (!id) {
id = this.getUniqueId();
}
this.getId = this.getOptimizedId;
return id;
}
});

View File

@@ -0,0 +1,56 @@
//@require Ext.Class
//@require Ext.ClassManager
//@require Ext.Loader
/**
* Base class for all mixins.
* @private
*/
Ext.define('Ext.mixin.Mixin', {
onClassExtended: function(cls, data) {
var mixinConfig = data.mixinConfig,
parentClassMixinConfig,
beforeHooks, afterHooks;
if (mixinConfig) {
parentClassMixinConfig = cls.superclass.mixinConfig;
if (parentClassMixinConfig) {
mixinConfig = data.mixinConfig = Ext.merge({}, parentClassMixinConfig, mixinConfig);
}
data.mixinId = mixinConfig.id;
beforeHooks = mixinConfig.beforeHooks;
afterHooks = mixinConfig.hooks || mixinConfig.afterHooks;
if (beforeHooks || afterHooks) {
Ext.Function.interceptBefore(data, 'onClassMixedIn', function(targetClass) {
var mixin = this.prototype;
if (beforeHooks) {
Ext.Object.each(beforeHooks, function(from, to) {
targetClass.override(to, function() {
if (mixin[from].apply(this, arguments) !== false) {
return this.callOverridden(arguments);
}
});
});
}
if (afterHooks) {
Ext.Object.each(afterHooks, function(from, to) {
targetClass.override(to, function() {
var ret = this.callOverridden(arguments);
mixin[from].apply(this, arguments);
return ret;
});
});
}
});
}
}
}
});

View File

@@ -0,0 +1,939 @@
/**
* 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 handler 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!'
*
* @aside guide events
*/
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.
*
* See the [Event guide](#!/guide/events) for more
*
* __Note:__ It is bad practice to specify a listener's `config` when you are defining a class using `Ext.define()`.
* Instead, only specify listeners when you are instantiating your class with `Ext.create()`.
* @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();
//<debug error>
if (!id.match(this.validIdRegex)) {
Ext.Logger.error("Invalid unique id of '" + id + "' for this object", this);
}
//</debug>
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}).
*
* The first argument is the name of the event. Every other argument passed will be available when you listen for
* the event.
*
* ## Example
*
* Firstly, we set up a listener for our new event.
*
* this.on('myevent', function(arg1, arg2, arg3, arg4, options, e) {
* console.log(arg1); // true
* console.log(arg2); // 2
* console.log(arg3); // { test: 'foo' }
* console.log(arg4); // 14
* console.log(options); // the options added when adding the listener
* console.log(e); // the event object with information about the event
* });
*
* And then we can fire off the event.
*
* this.fireEvent('myevent', true, 2, { test: 'foo' }, 14);
*
* 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.
* @return {Object}
*/
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
* @return {Boolean}
*/
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
* @return {Object}
*/
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);
}
}
return this;
},
/**
* Appends an event handler to this object. You can review the available handlers by looking at the 'events'
* section of the documentation for the component you are working with.
*
* ## 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 }
* });
*
* See the [Events Guide](#!/guide/events) for more.
*
* @param {String/String[]/Object} 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:
* @param {Object} [options.scope] The scope (`this` reference) in which the handler function is executed. If omitted, defaults to the object
* which fired the event.
* @param {Number} [options.delay] The number of milliseconds to delay the invocation of the handler after the event fires.
* @param {Boolean} [options.single] `true` to add a handler to handle just the next firing of the event, and then remove itself.
* @param {String} [options.order=current] 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`.
*
* @param {Number} [options.buffer] 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.
* @param {String} [options.element] Allows you to add a listener onto a element of this component using the elements reference.
*
* Ext.create('Ext.Component', {
* listeners: {
* element: 'element',
* tap: function() {
* alert('element tap!');
* }
* }
* });
*
* All components have the `element` reference, which is the outer most element of the component. {@link Ext.Container} also has the
* `innerElement` element which contains all children. In most cases `element` is adequate.
*
* @param {String} [options.delegate] 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!');
* }
* });
*
* @param {String} [order='current'] The order of when the listener should be added into the listener queue.
* Possible values are `before`, `current` and `after`.
*/
addListener: function(eventName, fn, scope, options, order) {
return this.changeListener(this.doAddListener, eventName, fn, scope, options, order);
},
toggleListener: function(toggle, eventName, fn, scope, options, order) {
return this.changeListener(toggle ? this.doAddListener : this.doRemoveListener, eventName, fn, scope, options, order);
},
/**
* Appends a before-event handler. Returning `false` from the handler will stop the event.
*
* Same as {@link #addListener} with `order` set to `'before'`.
*
* @param {String/String[]/Object} eventName The name of the event to listen for.
* @param {Function} fn The method the event invokes.
* @param {Object} [scope] The scope for `fn`.
* @param {Object} [options] An object containing handler configuration.
*/
addBeforeListener: function(eventName, fn, scope, options) {
return this.addListener(eventName, fn, scope, options, 'before');
},
/**
* Appends an after-event handler.
*
* Same as {@link #addListener} with `order` set to `'after'`.
*
* @param {String/String[]/Object} eventName The name of the event to listen for.
* @param {Function} fn The method the event invokes.
* @param {Object} [scope] The scope for `fn`.
* @param {Object} [options] An object containing handler configuration.
*/
addAfterListener: function(eventName, fn, scope, options) {
return this.addListener(eventName, fn, scope, options, 'after');
},
/**
* Removes an event handler.
*
* @param {String/String[]/Object} 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] 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.
* @param {Object} [options] Extra options object. See {@link #addListener} for details.
* @param {String} [order='current'] The order of the listener to remove.
* Possible values are `before`, `current` and `after`.
*/
removeListener: function(eventName, fn, scope, options, order) {
return this.changeListener(this.doRemoveListener, eventName, fn, scope, options, order);
},
/**
* Removes a before-event handler.
*
* Same as {@link #removeListener} with `order` set to `'before'`.
*
* @param {String/String[]/Object} eventName The name of the event the handler was associated with.
* @param {Function} fn The handler to remove.
* @param {Object} [scope] The scope originally specified for `fn`.
* @param {Object} [options] Extra options object.
*/
removeBeforeListener: function(eventName, fn, scope, options) {
return this.removeListener(eventName, fn, scope, options, 'before');
},
/**
* Removes a before-event handler.
*
* Same as {@link #removeListener} with `order` set to `'after'`.
*
* @param {String/String[]/Object} eventName The name of the event the handler was associated with.
* @param {Function} fn The handler to remove.
* @param {Object} [scope] The scope originally specified for `fn`.
* @param {Object} [options] Extra options object.
*/
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 re-fires the event from this object as the passed event name.
* @param newName
* @return {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();
}
},
/**
* @ignore
*/
addEvents: Ext.emptyFn
}, function() {
this.createAlias({
/**
* @method
* Alias for {@link #addListener}.
* @inheritdoc Ext.mixin.Observable#addListener
*/
on: 'addListener',
/**
* @method
* Alias for {@link #removeListener}.
* @inheritdoc Ext.mixin.Observable#removeListener
*/
un: 'removeListener',
/**
* @method
* Alias for {@link #addBeforeListener}.
* @inheritdoc Ext.mixin.Observable#addBeforeListener
*/
onBefore: 'addBeforeListener',
/**
* @method
* Alias for {@link #addAfterListener}.
* @inheritdoc Ext.mixin.Observable#addAfterListener
*/
onAfter: 'addAfterListener',
/**
* @method
* Alias for {@link #removeBeforeListener}.
* @inheritdoc Ext.mixin.Observable#removeBeforeListener
*/
unBefore: 'removeBeforeListener',
/**
* @method
* Alias for {@link #removeAfterListener}.
* @inheritdoc Ext.mixin.Observable#removeAfterListener
*/
unAfter: 'removeAfterListener'
});
//<deprecated product=touch since=2.0>
/**
* @method addEvents
* Adds the specified events to the list of events which this Observable may fire.
* @param {Object/String...} eventNames Either an object with event names as properties with a value of `true`
* or the first event name string if multiple event names are being passed as separate parameters.
* @deprecated 2.0 It's no longer needed to add events before firing.
*/
Ext.deprecateClassMethod(this, 'addEvents', function(){}, "addEvents() is deprecated. It's no longer needed to add events before firing");
/**
* @method addManagedListener
* Adds listeners to any Observable object (or Element) which are automatically removed when this Component
* is destroyed.
* @param {Ext.mixin.Observable/HTMLElement} object The item to which to add a listener/listeners.
* @param {Object/String} eventName The event name, or an object containing event name properties.
* @param {Function} [fn] If the `eventName` parameter was an event name, this is the handler function.
* @param {Object} [scope] If the `eventName` parameter was an event name, this is the scope in which
* the handler function is executed.
* @param {Object} [options] If the `eventName` parameter was an event name, this is the
* {@link #addListener} options.
* @deprecated 2.0 All listeners are now automatically managed where necessary. Simply use {@link #addListener}.
*/
Ext.deprecateClassMethod(this, 'addManagedListener', function(object, eventName, fn, scope, options) {
return object.addListener(eventName, fn, scope, options);
}, "addManagedListener() / mon() is deprecated, simply use addListener() / on(). All listeners are now automatically managed where necessary.");
/**
* @method removeManagedListener
* Adds listeners to any Observable object (or Element) which are automatically removed when this Component
* is destroyed.
* @param {Ext.mixin.Observable/HTMLElement} object The item to which to add a listener/listeners.
* @param {Object/String} eventName The event name, or an object containing event name properties.
* @param {Function} [fn] If the `eventName` parameter was an event name, this is the handler function.
* @param {Object} [scope] If the `eventName` parameter was an event name, this is the scope in which
* the handler function is executed.
* @deprecated 2.0 All listeners are now automatically managed where necessary. Simply use {@link #removeListener}.
*/
Ext.deprecateClassMethod(this, 'removeManagedListener', function(object, eventName, fn, scope) {
return object.removeListener(eventName, fn, scope);
}, "removeManagedListener() / mun() is deprecated, simply use removeListener() / un(). All listeners are now automatically managed where necessary.");
this.createAlias({
/**
* @method
* Alias for {@link #addManagedListener}.
* @inheritdoc Ext.mixin.Observable#addManagedListener
* @deprecated 2.0.0 This is now done automatically
*/
mon: 'addManagedListener',
/**
* @method
* Alias for {@link #removeManagedListener}.
* @inheritdoc Ext.mixin.Observable#removeManagedListener
* @deprecated 2.0.0 This is now done automatically
*/
mun: 'removeManagedListener'
});
//</deprecated>
});

View File

@@ -0,0 +1,538 @@
/**
* Tracks what records are currently selected in a databound widget. This class is mixed in to {@link Ext.dataview.DataView} and
* all subclasses.
* @private
*/
Ext.define('Ext.mixin.Selectable', {
extend: 'Ext.mixin.Mixin',
mixinConfig: {
id: 'selectable',
hooks: {
updateStore: 'updateStore'
}
},
/**
* @event beforeselectionchange
* Fires before an item is selected.
* @param {Ext.mixin.Selectable} this
* @preventable selectionchange
* @deprecated 2.0.0 Please listen to the {@link #selectionchange} event with an order of `before` instead.
*/
/**
* @event selectionchange
* Fires when a selection changes.
* @param {Ext.mixin.Selectable} this
* @param {Ext.data.Model[]} records The records whose selection has changed.
*/
config: {
/**
* @cfg {Boolean} disableSelection `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'`.
* @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'`.
* @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.
* @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);
newStore.un('clear', 'onSelectionStoreClear', this);
}
}
if (newStore) {
newStore.on(bindEvents);
newStore.onBefore('clear', 'onSelectionStoreClear', this);
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(supress) {
var me = this,
selections = me.getStore().getRange();
me.deselect(selections, supress);
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) {
var me = this,
store = me.getStore(),
records = [],
tmp, i;
if (me.getDisableSelection()) {
return;
}
// swap values
if (startRecord > endRecord) {
tmp = endRecord;
endRecord = startRecord;
startRecord = tmp;
}
for (i = startRecord; i <= endRecord; i++) {
records.push(store.getAt(i));
}
this.doMultiSelect(records, keepExisting);
},
/**
* 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);
if (!suppressEvent) {
me.fireSelectionChange([record]);
}
},
/**
* 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);
}
if (change && !suppressEvent) {
this.fireSelectionChange(records);
}
},
/**
* 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);
}
}
if (change && !suppressEvent) {
me.fireSelectionChange(records);
}
},
/**
* Sets a record as the last focused record. This does NOT mean
* that the record has been selected.
* @param {Ext.data.Record} newRecord
* @param {Ext.data.Record} oldRecord
*/
updateLastFocused: function(newRecord, oldRecord) {
this.onLastFocusChanged(oldRecord, newRecord);
},
fireSelectionChange: function(records) {
var me = this;
//<deprecated product=touch since=2.0>
me.fireAction('beforeselectionchange', [me], function() {
//</deprecated>
me.fireAction('selectionchange', [me, records], 'getSelection');
//<deprecated product=touch since=2.0>
});
//</deprecated>
},
/**
* 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,
selections = me.getSelection();
me.deselectAll(true);
if (selections.length) {
me.select(selections, false, true);
}
},
// prune records from the SelectionModel if
// they were selected at the time they were
// removed.
onSelectionStoreRemove: function(store, records) {
var me = this,
selected = me.selected,
ln = records.length,
record, i;
if (me.getDisableSelection()) {
return;
}
for (i = 0; i < ln; i++) {
record = records[i];
if (selected.remove(record)) {
if (me.getLastSelected() == record) {
me.setLastSelected(null);
}
if (me.getLastFocused() == record) {
me.setLastFocused(null);
}
me.fireSelectionChange([record]);
}
}
},
onSelectionStoreClear: function(store) {
var records = store.getData().items;
this.onSelectionStoreRemove(store, records);
},
/**
* 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
* @inheritdoc Ext.mixin.Selectable#disableSelection
* @deprecated 2.0.0 Please use {@link #disableSelection} instead.
*/
});

View File

@@ -0,0 +1,344 @@
/**
* @private
*/
Ext.define('Ext.mixin.Sortable', {
extend: 'Ext.mixin.Mixin',
requires: [
'Ext.util.Sorter'
],
mixinConfig: {
id: 'sortable'
},
config: {
/**
* @cfg {Array} sorters
* An array with sorters. A sorter can be an instance of {@link Ext.util.Sorter}, a string
* indicating a property name, an object representing an Ext.util.Sorter configuration,
* or a sort function.
*/
sorters: null,
/**
* @cfg {String} defaultSortDirection
* The default sort direction to use if one is not specified.
*/
defaultSortDirection: "ASC",
/**
* @cfg {String} sortRoot
* The root inside each item in which the properties exist that we want to sort on.
* This is useful for sorting records in which the data exists inside a `data` property.
*/
sortRoot: null
},
/**
* @property {Boolean} dirtySortFn
* A flag indicating whether the currently cashed sort function is still valid.
* @readonly
*/
dirtySortFn: false,
/**
* @property currentSortFn
* This is the cached sorting function which is a generated function that calls all the
* configured sorters in the correct order.
* @readonly
*/
sortFn: null,
/**
* @property {Boolean} sorted
* A read-only flag indicating if this object is sorted.
* @readonly
*/
sorted: false,
applySorters: function(sorters, collection) {
if (!collection) {
collection = this.createSortersCollection();
}
collection.clear();
this.sorted = false;
if (sorters) {
this.addSorters(sorters);
}
return collection;
},
createSortersCollection: function() {
this._sorters = Ext.create('Ext.util.Collection', function(sorter) {
return sorter.getId();
});
return this._sorters;
},
/**
* This method adds a sorter.
* @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of
* Ext.util.Sorter, a string indicating a property name, an object representing an Ext.util.Sorter
* configuration, or a sort function.
* @param {String} defaultDirection The default direction for each sorter in the array. Defaults
* to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
*/
addSorter: function(sorter, defaultDirection) {
this.addSorters([sorter], defaultDirection);
},
/**
* This method adds all the sorters in a passed array.
* @param {Array} sorters An array with sorters. A sorter can be an instance of Ext.util.Sorter, a string
* indicating a property name, an object representing an Ext.util.Sorter configuration,
* or a sort function.
* @param {String} defaultDirection The default direction for each sorter in the array. Defaults
* to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
*/
addSorters: function(sorters, defaultDirection) {
var currentSorters = this.getSorters();
return this.insertSorters(currentSorters ? currentSorters.length : 0, sorters, defaultDirection);
},
/**
* This method adds a sorter at a given index.
* @param {Number} index The index at which to insert the sorter.
* @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of Ext.util.Sorter,
* a string indicating a property name, an object representing an Ext.util.Sorter configuration,
* or a sort function.
* @param {String} defaultDirection The default direction for each sorter in the array. Defaults
* to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
*/
insertSorter: function(index, sorter, defaultDirection) {
return this.insertSorters(index, [sorter], defaultDirection);
},
/**
* This method inserts all the sorters in the passed array at the given index.
* @param {Number} index The index at which to insert the sorters.
* @param {Array} sorters Can be an instance of Ext.util.Sorter, a string indicating a property name,
* an object representing an Ext.util.Sorter configuration, or a sort function.
* @param {String} defaultDirection The default direction for each sorter in the array. Defaults
* to the value of {@link #defaultSortDirection}. Can be either 'ASC' or 'DESC'.
*/
insertSorters: function(index, sorters, defaultDirection) {
// We begin by making sure we are dealing with an array of sorters
if (!Ext.isArray(sorters)) {
sorters = [sorters];
}
var ln = sorters.length,
direction = defaultDirection || this.getDefaultSortDirection(),
sortRoot = this.getSortRoot(),
currentSorters = this.getSorters(),
newSorters = [],
sorterConfig, i, sorter, currentSorter;
if (!currentSorters) {
// This will guarantee that we get the collection
currentSorters = this.createSortersCollection();
}
// We first have to convert every sorter into a proper Sorter instance
for (i = 0; i < ln; i++) {
sorter = sorters[i];
sorterConfig = {
direction: direction,
root: sortRoot
};
// If we are dealing with a string we assume it is a property they want to sort on.
if (typeof sorter === 'string') {
currentSorter = currentSorters.get(sorter);
if (!currentSorter) {
sorterConfig.property = sorter;
} else {
if (defaultDirection) {
currentSorter.setDirection(defaultDirection);
} else {
// If we already have a sorter for this property we just toggle its direction.
currentSorter.toggle();
}
continue;
}
}
// If it is a function, we assume its a sorting function.
else if (Ext.isFunction(sorter)) {
sorterConfig.sorterFn = sorter;
}
// 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(sorter)) {
if (!sorter.isSorter) {
if (sorter.fn) {
sorter.sorterFn = sorter.fn;
delete sorter.fn;
}
sorterConfig = Ext.apply(sorterConfig, sorter);
}
else {
newSorters.push(sorter);
if (!sorter.getRoot()) {
sorter.setRoot(sortRoot);
}
continue;
}
}
// Finally we get to the point where it has to be invalid
// <debug>
else {
Ext.Logger.warn('Invalid sorter specified:', sorter);
}
// </debug>
// If a sorter config was created, make it an instance
sorter = Ext.create('Ext.util.Sorter', sorterConfig);
newSorters.push(sorter);
}
// Now lets add the newly created sorters.
for (i = 0, ln = newSorters.length; i < ln; i++) {
currentSorters.insert(index + i, newSorters[i]);
}
this.dirtySortFn = true;
if (currentSorters.length) {
this.sorted = true;
}
return currentSorters;
},
/**
* This method removes a sorter.
* @param {Ext.util.Sorter/String/Function/Object} sorter Can be an instance of Ext.util.Sorter,
* a string indicating a property name, an object representing an Ext.util.Sorter configuration,
* or a sort function.
*/
removeSorter: function(sorter) {
return this.removeSorters([sorter]);
},
/**
* This method removes all the sorters in a passed array.
* @param {Array} sorters Each value in the array can be a string (property name),
* function (sorterFn) or {@link Ext.util.Sorter Sorter} instance.
*/
removeSorters: function(sorters) {
// We begin by making sure we are dealing with an array of sorters
if (!Ext.isArray(sorters)) {
sorters = [sorters];
}
var ln = sorters.length,
currentSorters = this.getSorters(),
i, sorter;
for (i = 0; i < ln; i++) {
sorter = sorters[i];
if (typeof sorter === 'string') {
currentSorters.removeAtKey(sorter);
}
else if (typeof sorter === 'function') {
currentSorters.each(function(item) {
if (item.getSorterFn() === sorter) {
currentSorters.remove(item);
}
});
}
else if (sorter.isSorter) {
currentSorters.remove(sorter);
}
}
if (!currentSorters.length) {
this.sorted = false;
}
},
/**
* This updates the cached sortFn based on the current sorters.
* @return {Function} The generated sort function.
* @private
*/
updateSortFn: function() {
var sorters = this.getSorters().items;
this.sortFn = function(r1, r2) {
var ln = sorters.length,
result, i;
// We loop over each sorter and check if r1 should be before or after r2
for (i = 0; i < ln; i++) {
result = sorters[i].sort.call(this, r1, r2);
// If the result is -1 or 1 at this point it means that the sort is done.
// Only if they are equal (0) we continue to see if a next sort function
// actually might find a winner.
if (result !== 0) {
break;
}
}
return result;
};
this.dirtySortFn = false;
return this.sortFn;
},
/**
* Returns an up to date sort function.
* @return {Function} The sort function.
*/
getSortFn: function() {
if (this.dirtySortFn) {
return this.updateSortFn();
}
return this.sortFn;
},
/**
* This method will sort an array based on the currently configured {@link #sorters}.
* @param {Array} data The array you want to have sorted.
* @return {Array} The array you passed after it is sorted.
*/
sort: function(data) {
Ext.Array.sort(data, this.getSortFn());
return data;
},
/**
* This method returns the index that a given item would be inserted into a given array based
* on the current sorters.
* @param {Array} items The array that you want to insert the item into.
* @param {Mixed} item The item that you want to insert into the items array.
* @return {Number} The index for the given item in the given array based on the current sorters.
*/
findInsertionIndex: function(items, item, sortFn) {
var start = 0,
end = items.length - 1,
sorterFn = sortFn || this.getSortFn(),
middle,
comparison;
while (start <= end) {
middle = (start + end) >> 1;
comparison = sorterFn(item, items[middle]);
if (comparison >= 0) {
start = middle + 1;
} else if (comparison < 0) {
end = middle - 1;
}
}
return start;
}
});

View File

@@ -0,0 +1,54 @@
/**
*
*/
Ext.define('Ext.mixin.Templatable', {
extend: 'Ext.mixin.Mixin',
mixinConfig: {
id: 'templatable'
},
referenceAttributeName: 'reference',
referenceSelector: '[reference]',
getElementConfig: function() {
return {
reference: 'element'
};
},
getElementTemplate: function() {
var elementTemplate = document.createDocumentFragment();
elementTemplate.appendChild(Ext.Element.create(this.getElementConfig(), true));
return elementTemplate;
},
initElement: function() {
var prototype = this.self.prototype;
prototype.elementTemplate = this.getElementTemplate();
prototype.initElement = prototype.doInitElement;
this.initElement.apply(this, arguments);
},
linkElement: function(reference, node) {
this.link(reference, node);
},
doInitElement: function() {
var referenceAttributeName = this.referenceAttributeName,
renderElement, referenceNodes, i, ln, referenceNode, reference;
renderElement = this.elementTemplate.cloneNode(true);
referenceNodes = renderElement.querySelectorAll(this.referenceSelector);
for (i = 0,ln = referenceNodes.length; i < ln; i++) {
referenceNode = referenceNodes[i];
reference = referenceNode.getAttribute(referenceAttributeName);
referenceNode.removeAttribute(referenceAttributeName);
this.linkElement(reference, referenceNode);
}
}
});

View File

@@ -0,0 +1,59 @@
/**
* 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;
}
});