/** * Maintains an additional key map for an `Ext.util.Collection`. Instances of this class * are seldom created manually. Rather they are created by the `Ext.util.Collection' when * given an `extraKeys` config. * * @since 5.0.0 */Ext.define('Ext.util.CollectionKey', { mixins: [ 'Ext.mixin.Identifiable' ], isCollectionKey: true, observerPriority: -200, config: { collection: null, /** * @cfg {Function/String} [keyFn] * A function to retrieve the key of an item in the collection. This can be normal * function that takes an item and returns the key or it can be the name of the * method to call on an item to get the key. * * For example: * * new Ext.util.Collection({ * keys: { * byName: { * keyFn: 'getName' // each item has a "getName" method * } * } * }); * * Or equivalently: * * new Ext.util.Collection({ * keys: { * byName: { * keyFn: function (item) { * return item.getName(); * } * } * } * }); * * @since 5.0.0 */ keyFn: null, /** * @cfg {String} property * The name of the property on each item that is its key. * * new Ext.util.Collection({ * keys: { * byName: 'name' * } * }); * * Or equivalently: * * new Ext.util.Collection({ * keys: { * byName: { * property: 'name' * } * } * }); * * var item = collection.byName.get('fooname'); */ property: null, /** * @cfg {String} rootProperty * The name of the sub-object property on each item that is its key. This value * overrides `{@link Ext.util.Collection#rootProperty}`. * * new Ext.util.Collection({ * keys: { * byName: { * property: 'name', * rootProperty: 'data' * } * } * }); * * var item = collection.byName.get('fooname'); */ rootProperty: null, unique: true }, /** * This property is used to know when this `Index` is in sync with the `Collection`. * When the two are synchronized, their `generation` values match. * @private * @readonly * @since 5.0.0 */ generation: 0, /** * @property {Object} map * An object used as map to get an object based on its key. * @since 5.0.0 * @private */ map: null, /** * @property {Number} mapRebuilds * The number of times the `map` has been rebuilt. This is for diagnostic use. * @private * @readonly */ mapRebuilds: 0, /** * @property {String} name * This property is set by `Ext.util.Collection` when added via `extraKeys`. * @readonly */ constructor: function (config) { this.initConfig(config); //<debug> if (!Ext.isFunction(this.getKey)) { Ext.raise('CollectionKey requires a keyFn or property config'); } //</debug> }, /** * Returns the item or, if not `unique` possibly array of items that have the given * key. * @param {Mixed} key The key that will match the `keyFn` return value or value of * the specified `property`. * @return {Object} */ get: function (key) { var map = this.map || this.getMap(); return map[key] || null; }, /** * @private * Clears this index; * * Called by {@link Ext.util.Collection#clear} when the collection is cleared. */ clear: function() { this.map = null; }, getRootProperty: function () { var me = this, root = this.callParent(); return root !== null ? root : me.getCollection().getRootProperty(); }, /** * Returns the index of the item with the given key in the collection. If this is not * a `unique` result, the index of the first item in the collection with the matching * key. * * To iterate the indices of all items with a matching (not `unique`) key: * * for (index = collection.byName.indexOf('foo'); * index >= 0; * index = collection.byName.indexOf('foo', index)) { * // process item at "index" * } * * @param {Mixed} key The key that will match the `keyFn` return value or value of * the specified `property`. * @param {Number} [startAt=-1] The index at which to start. Only occurrences beyond * this index are returned. * @return {Number} The index of the first item with the given `key` beyond the given * `startAt` index or -1 if there are no such items. */ indexOf: function (key, startAt) { var map = this.map || this.getMap(), item = map[key], collection = this.getCollection(), length = collection.length, i, index, items, n; if (!item) { return -1; } if (startAt === undefined) { startAt = -1; } if (item instanceof Array) { items = item; index = length; // greater than any actual indexOf for (n = items.length; n-- > 0; ) { i = collection.indexOf(items[n]); if (i < index && i > startAt) { index = i; } } if (index === length) { return -1; } } else { index = collection.indexOf(item); } return (index > startAt) ? index : -1; }, /** * Change the key for an existing item in the collection. If the old key does not * exist this call does nothing. * @param {Object} item The item whose key has changed. * @param {String} oldKey The old key for the `item`. * @since 5.0.0 */ updateKey: function (item, oldKey) { var me = this, map = me.map, bucket, index; if (map) { bucket = map[oldKey]; if (bucket instanceof Array) { index = Ext.Array.indexOf(bucket, item); if (index >= 0) { if (bucket.length > 2) { bucket.splice(index, 1); } else { // If there is an array of 2 items, replace the array with the // one remaining item. Since index then is either 0 or 1, the // index of the other item is easy. map[oldKey] = bucket[1 - index]; // "1 - 0" = 1, "1 - 1" = 0 } } } else if (bucket) { //<debug> if (me.getUnique() && bucket !== item) { Ext.raise('Incorrect oldKey "' + oldKey + '" for item with newKey "' + me.getKey(item) + '"'); } //</debug> delete map[oldKey]; } me.add([ item ]); } }, //------------------------------------------------------------------------- // Calls from our Collection: onCollectionAdd: function (collection, add) { if (this.map) { this.add(add.items); } }, onCollectionItemChange: function (collection, details) { this.map = null; }, onCollectionRefresh: function () { this.map = null; }, onCollectionRemove: function (collection, remove) { var me = this, map = me.map, items = remove.items, length = items.length, i, item, key; if (map) { if (me.getUnique() && length < collection.length / 2) { for (i = 0; i < length; ++i) { key = me.getKey(item = items[i]); delete map[key]; } } else { me.map = null; } } }, //------------------------------------------------------------------------- // Private add: function (items) { var me = this, map = me.map, bucket, i, item, key, length, unique; length = items.length; unique = me.getUnique(); for (i = 0; i < length; ++i) { key = me.getKey(item = items[i]); if (unique || !(key in map)) { map[key] = item; } else { if (!((bucket = map[key]) instanceof Array)) { map[key] = bucket = [ bucket ]; } bucket.push(item); } } }, applyKeyFn: function (keyFn) { if (Ext.isString(keyFn)) { this.getKey = function (item) { return item[keyFn](); }; } else { this.getKey = keyFn; } }, updateProperty: function (property) { var root = this.getRootProperty(); this.getKey = function (item) { return (root ? item[root] : item)[property]; }; }, getMap: function () { var me = this, map = me.map; if (!map) { me.map = map = {}; me.keysByItemKey = {}; ++me.mapRebuilds; me.add(me.getCollection().items); } return map; }, updateCollection: function (collection) { collection.addObserver(this); }, clone: function() { return new Ext.util.CollectionKey(this.getCurrentConfig()); }});