/** * Represents a collection of a set of key and value pairs. Each key in the MixedCollection * must be unique, the same key cannot exist twice. This collection is ordered, items in the * collection can be accessed by index or via the key. Newly added items are added to * the end of the collection. This class is similar to {@link Ext.util.HashMap} however it * is heavier and provides more functionality. Sample usage: * * var coll = new Ext.util.MixedCollection(); * coll.add('key1', 'val1'); * coll.add('key2', 'val2'); * coll.add('key3', 'val3'); * * console.log(coll.get('key1')); // prints 'val1' * console.log(coll.indexOfKey('key3')); // prints 2 * * The MixedCollection also has support for sorting and filtering of the values in the collection. * * var coll = new Ext.util.MixedCollection(); * coll.add('key1', 100); * coll.add('key2', -100); * coll.add('key3', 17); * coll.add('key4', 0); * var biggerThanZero = coll.filterBy(function(value){ * return value > 0; * }); * console.log(biggerThanZero.getCount()); // prints 2 * */Ext.define('Ext.util.MixedCollection', { extend: 'Ext.util.AbstractMixedCollection', mixins: { sortable: 'Ext.util.Sortable' }, /** * @cfg {Boolean} allowFunctions * Configure as `true` if the {@link #addAll} function should add function references to the collection. */ /** * Creates new MixedCollection. * @param {Object} config A configuration object. * @param {Boolean} [config.allowFunctions=false] Specify `true` if the {@link #addAll} * function should add function references to the collection. * @param {Function} [config.getKey] A function that can accept an item of the type(s) stored in this MixedCollection * and return the key value for that item. This is used when available to look up the key on items that * were passed without an explicit key parameter to a MixedCollection method. Passing this parameter is * equivalent to overriding the {@link #method-getKey} method. */ constructor: function() { this.initConfig(); this.callParent(arguments); }, doSort: function(sorterFn) { this.sortBy(sorterFn); }, /** * @private * Performs the actual sorting based on a direction and a sorting function. Internally, * this creates a temporary array of all items in the MixedCollection, sorts it and then writes * the sorted array data back into this.items and this.keys * @param {String} property Property to sort by ('key', 'value', or 'index') * @param {String} dir (optional) Direction to sort 'ASC' or 'DESC'. Defaults to 'ASC'. * @param {Function} fn (optional) Comparison function that defines the sort order. * Defaults to sorting by numeric value. */ _sort : function(property, dir, fn) { var me = this, i, len, dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1, //this is a temporary array used to apply the sorting function c = [], keys = me.keys, items = me.items, o; //default to a simple sorter function if one is not provided fn = fn || function(a, b) { return a - b; }; //copy all the items into a temporary array, which we will sort for (i = 0, len = items.length; i < len; i++) { c[c.length] = { key : keys[i], value: items[i], index: i }; } //sort the temporary array Ext.Array.sort(c, function(a, b) { return fn(a[property], b[property]) * dsc || // In case of equality, ensure stable sort by comparing collection index (a.index < b.index ? -1 : 1); }); // Copy the temporary array back into the main this.items and this.keys objects // Repopulate the indexMap hash if configured to do so. for (i = 0, len = c.length; i < len; i++) { o = c[i]; items[i] = o.value; keys[i] = o.key; me.indexMap[o.key] = i; } me.generation++; me.indexGeneration = me.generation; me.fireEvent('sort', me); }, /** * Sorts the collection by a single sorter function * @param {Function} sorterFn The function to sort by */ sortBy: function(sorterFn) { var me = this, items = me.items, item, keys = me.keys, key, length = items.length, i; // Stamp the collection index into each item so that we can implement stable sort for (i = 0; i < length; i++) { items[i].$extCollectionIndex = i; } Ext.Array.sort(items, function(a, b) { return sorterFn(a, b) || // In case of equality, ensure stable sort by comparing collection index (a.$extCollectionIndex < b.$extCollectionIndex ? -1 : 1); }); // Update the keys array, and remove the index for (i = 0; i < length; i++) { item = items[i]; key = me.getKey(item); keys[i] = key; me.indexMap[key] = i; delete item.$extCollectionIndex; } me.generation++; me.indexGeneration = me.generation; me.fireEvent('sort', me, items, keys); }, /** * Calculates the insertion index of the new item based upon the comparison function passed, or the current sort order. * @param {Object} newItem The new object to find the insertion position of. * @param {Function} [sorterFn] The function to sort by. This is the same as the sorting function * passed to {@link #sortBy}. It accepts 2 items from this MixedCollection, and returns -1 0, or 1 * depending on the relative sort positions of the 2 compared items. * * If omitted, a function {@link #generateComparator generated} from the currently defined set of * {@link #cfg-sorters} will be used. * * @return {Number} The insertion point to add the new item into this MixedCollection at using {@link #insert} */ findInsertionIndex: function(newItem, sorterFn) { var me = this, items = me.items, start = 0, end = items.length - 1, middle, comparison; if (!sorterFn) { sorterFn = me.generateComparator(); } while (start <= end) { middle = (start + end) >> 1; comparison = sorterFn(newItem, items[middle]); if (comparison >= 0) { start = middle + 1; } else if (comparison < 0) { end = middle - 1; } } return start; }, /** * @inheritdoc Ext.util.AbstractMixedCollection#method-reorder */ reorder: function (mapping) { this.callParent([mapping]); this.fireEvent('sort', this); }, /** * Sorts this collection by <b>key</b>s. * @param {String} direction (optional) 'ASC' or 'DESC'. Defaults to 'ASC'. * @param {Function} fn (optional) Comparison function that defines the sort order. * Defaults to sorting by case insensitive string. */ sortByKey : function(dir, fn){ this._sort('key', dir, fn || function(a, b){ var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase(); return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0); }); }});