/**
* This class provides a flexible means to control the
* `{@link Ext.util.Collection#cfg!filters filters}` of a
* `{@link Ext.util.Collection Collection}`. Instances of this class are created
* automatically when filters are added to added to Collections.
*
* This collection can be directly manipulated by application code to gain full
* control over the filters of the owner collection. Be aware that some components
* create filters internally (such as `Ext.form.field.ComboBox` and the
* `Ext.grid.filters.Filters` plugin) so be careful in such cases to not disturb
* these additional filters.
*
* Items in this collection are `Ext.util.Filter` instances and can be managed
* individually by their `id`. This is the recommended way to manage application
* filters while preserving filters applied from other sources.
*
* Bulk changes to this collection should be wrapped in
* `{@link Ext.util.Collection#method!beginUpdate beginUpdate}` and
* `{@link Ext.util.Collection#method!endUpdate endUpdate}` (as with any collection).
* During these bulk updates all reactions to filter changes will be suspended.
*/
Ext.define('Ext.util.FilterCollection', {
extend: 'Ext.util.Collection',
requires: [
'Ext.util.Filter'
],
isFilterCollection: true,
/**
* @property {Ext.util.Collection} $filterable
* The owning filterable instance. The filterable's configuration governs this
* collection.
* @private
* @readonly
*/
$filterable: null,
/**
* @property filterFn
* This is the cached filter function.
* @readonly
*/
filterFn: null,
constructor: function(config) {
var me = this;
// Because this closure operates on the collection, we are able to use it for as
// long as we have the Collection instance.
me.filterFn = Ext.util.Filter.createFilterFn(me);
me.callParent([config]);
me.setDecoder(me.decodeFilter);
},
/**
* This method will filter an array based on the currently configured `filters`.
* @param {Array} data The array you want to have filtered.
* @return {Array} The array you passed after it is filtered.
*/
filterData: function(data) {
return this.filtered ? Ext.Array.filter(data, this.filterFn) : data;
},
/**
* Returns the filter function.
* @return {Function} The filter function.
*/
getFilterFn: function() {
return this.filterFn;
},
isItemFiltered: function(item) {
return !this.filterFn(item);
},
/**
* returns the number of *enabled* filters in this `FilterCollection`
* @returns {Number}
*/
getFilterCount: function() {
var filters = this.items,
len = filters.length,
i;
for (i = len - 1; i >= 0; i--) {
if (filters[i].getDisabled()) {
len--;
}
}
return len;
},
//-------------------------------------------------------------------------
// Private
decodeFilter: function(filter) {
var options = this.getOptions(),
filterRoot = options.getRootProperty(),
filterConfig;
if (filter.isFilter) {
if (filter.setRoot && !filter.getRoot()) {
filter.setRoot(filterRoot);
}
}
else {
filterConfig = {
root: filterRoot
};
if (Ext.isFunction(filter)) {
filterConfig.filterFn = filter;
}
// If we are dealing with an object, we assume its a Filter configuration. In
// this case we create an instance of Ext.util.Filter passing the config.
else {
// Finally we get to the point where it has to be invalid
//<debug>
if (!Ext.isObject(filter)) {
Ext.raise('Invalid filter specified: ' + filter);
}
//</debug>
filterConfig = Ext.apply(filterConfig, filter);
if (filterConfig.fn) {
filterConfig.filterFn = filterConfig.fn;
delete filterConfig.fn;
}
if (Ext.util.Filter.isInvalid(filterConfig)) {
return false;
}
}
filter = new Ext.util.Filter(filterConfig);
}
return filter;
},
decodeRemoveItems: function(args, index) {
var me = this,
ret = (index === undefined) ? args : args[index];
if (!ret.$cloned) {
if (args.length > index + 1 || !Ext.isIterable(ret)) {
ret = Ext.Array.slice(args, index);
}
// eslint-disable-next-line vars-on-top
var currentFilters = me.items,
ln = ret.length,
remove = [],
filter, i, isFunction, isProp, isString, item, match, n, type;
for (i = 0; i < ln; i++) {
filter = ret[i];
if (filter && filter.isFilter) {
remove.push(filter);
}
else {
type = typeof filter;
isFunction = type === 'function';
isProp = filter.property !== undefined && filter.value !== undefined;
isString = type === 'string';
//<debug>
if (!isFunction && !isProp && !isString) {
Ext.raise('Invalid filter specification: ' + filter);
}
//</debug>
for (n = currentFilters.length; n-- > 0;) {
item = currentFilters[n];
match = false;
if (isString) {
match = item.getProperty() === filter;
}
else if (isFunction) {
match = item.getFilterFn() === filter;
}
else if (isProp) {
match = item.getProperty() === filter.property &&
item.getValue() === filter.value;
}
if (match) {
remove.push(item);
}
}
}
}
ret = remove;
ret.$cloned = true;
}
return ret;
},
getOptions: function() {
// Odd thing this. We need a Filterable to know how to manage our collection, but
// we may not have one. Of course as a Collection, we *are* one as well... just
// that is not really useful to filter the filters themselves, but we do have the
// default options for Filterable baked in, so we'll do.
return this.$filterable || this;
}
});