/** * @class Ext.util.MixedCollection * <p> * 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: * <pre><code> 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 * </code></pre> * * <p> * The MixedCollection also has support for sorting and filtering of the values in the collection. * <pre><code> 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 * </code></pre> * </p> * * @constructor * @param {Boolean} allowFunctions Specify <tt>true</tt> if the {@link #addAll} * function should add function references to the collection. Defaults to * <tt>false</tt>. * @param {Function} keyFn 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 providing an implementation for the {@link #getKey} method. */ Ext.define('Ext.util.MixedCollection', { extend: 'Ext.util.AbstractMixedCollection', mixins: { sortable: 'Ext.util.Sortable' }, constructor: function() { var me = this; me.callParent(arguments); me.addEvents('sort'); me.mixins.sortable.initSortable.call(me); }, 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; //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){ var v = fn(a[property], b[property]) * dsc; if(v === 0){ v = (a.index < b.index ? -1 : 1); } return v; }); //copy the temporary array back into the main this.items and this.keys objects for(i = 0, len = c.length; i < len; i++){ items[i] = c[i].value; keys[i] = c[i].key; } 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, keys = me.keys, length = items.length, temp = [], i; //first we create a copy of the items array so that we can sort it for (i = 0; i < length; i++) { temp[i] = { key : keys[i], value: items[i], index: i }; } Ext.Array.sort(temp, function(a, b) { var v = sorterFn(a.value, b.value); if (v === 0) { v = (a.index < b.index ? -1 : 1); } return v; }); //copy the temporary array back into the main this.items and this.keys objects for (i = 0; i < length; i++) { items[i] = temp[i].value; keys[i] = temp[i].key; } me.fireEvent('sort', me, items, keys); }, /** * Reorders each of the items based on a mapping from old index to new index. Internally this * just translates into a sort. The 'sort' event is fired whenever reordering has occured. * @param {Object} mapping Mapping from old item index to new item index */ reorder: function(mapping) { var me = this, items = me.items, index = 0, length = items.length, order = [], remaining = [], oldIndex; me.suspendEvents(); //object of {oldPosition: newPosition} reversed to {newPosition: oldPosition} for (oldIndex in mapping) { order[mapping[oldIndex]] = items[oldIndex]; } for (index = 0; index < length; index++) { if (mapping[index] == undefined) { remaining.push(items[index]); } } for (index = 0; index < length; index++) { if (order[index] == undefined) { order[index] = remaining.shift(); } } me.clear(); me.addAll(order); me.resumeEvents(); me.fireEvent('sort', me); }, /** * 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); }); } });