/**
* This class provides an **unordered** collection similar to `Ext.util.Collection`. The
* removal of order maintenance provides a significant performance increase. Further, this
* class does not provide events or other high-level features. It maintains an array of
* `items` and a map to quickly find items by their `id`.
*
* @private
* @since 5.1.1
*/
Ext.define('Ext.util.Bag', {
isBag: true,
constructor: function() {
/**
* @property {Object[]} items
* An array containing the items.
* @private
* @since 5.1.1
*/
this.items = [];
/**
* @property {Object} map
* An object used as a map to find items based on their key.
* @private
* @since 5.1.1
*/
this.map = {};
},
/**
* @property {Number} generation
* Mutation counter which is incremented when the collection changes.
* @readonly
* @since 5.1.1
*/
generation: 0,
/**
* @property {Number} length
* The count of items in the collection.
* @readonly
* @since 5.1.1
*/
length: 0,
beginUpdate: Ext.emptyFn,
endUpdate: Ext.emptyFn,
add: function(item) {
var me = this,
items = me.items,
map = me.map,
n = 1,
old, i, idx, id, it, ret, was;
if (Ext.isArray(item)) {
old = ret = [];
n = item.length;
}
for (i = 0; i < n; i++) {
id = me.getKey(it = old ? item[i] : item);
idx = map[id];
if (idx === undefined) {
items.push(it);
map[id] = me.length++;
if (old) {
old.push(it);
}
else {
ret = it;
}
}
else {
was = items[idx];
if (old) {
old.push(was);
}
else {
ret = was;
}
items[idx] = it;
}
}
++me.generation;
return ret;
},
clear: function() {
var me = this,
needsClear = me.generation || me.length,
ret = needsClear ? me.items : [];
if (needsClear) {
me.items = [];
me.length = 0;
me.map = {};
++me.generation;
}
return ret;
},
clone: function() {
var me = this,
ret = new me.self(),
len = me.length;
if (len) {
Ext.apply(ret.map, me.map);
ret.items = me.items.slice();
ret.length = me.length;
}
return ret;
},
contains: function(item) {
var ret = false,
map = this.map,
key;
if (item != null) {
key = this.getKey(item);
if (key in map) {
ret = this.items[map[key]] === item;
}
}
return ret;
},
containsKey: function(key) {
return key in this.map;
},
destroy: function() {
this.items = this.map = null;
this.callParent();
},
each: function(fn, scope) {
var items = this.items,
len = items.length,
i, ret;
if (len) {
scope = scope || this;
items = items.slice(0); // safe for re-entrant calls
for (i = 0; i < len; i++) {
ret = fn.call(scope, items[i], i, len);
if (ret === false) {
break;
}
}
}
return ret;
},
getAt: function(index) {
var out = null;
if (index < this.length) {
out = this.items[index];
}
return out;
},
get: function(key) {
return this.getByKey(key);
},
getByKey: function(key) {
var map = this.map,
ret = (key in map) ? this.items[map[key]] : null;
return ret;
},
indexOfKey: function(key) {
var map = this.map,
ret = (key in map) ? map[key] : -1;
return ret;
},
last: function() {
return this.items[this.length - 1];
},
updateKey: function(item, oldKey) {
var me = this,
map = me.map,
newKey;
if (!item || !oldKey) {
return;
}
if ((newKey = me.getKey(item)) !== oldKey) {
if (me.getAt(map[oldKey]) === item && !(newKey in map)) {
me.generation++;
map[newKey] = map[oldKey];
delete map[oldKey];
}
}
//<debug>
else {
// It may be that the item is (somehow) already in the map using the
// newKey or that there is no item in the map with the oldKey. These
// are not errors.
if (newKey in map && me.getAt(map[newKey]) !== item) {
// There is a different item in the map with the newKey which is an
// error. To properly handle this, add the item instead.
Ext.raise('Duplicate newKey "' + newKey +
'" for item with oldKey "' + oldKey + '"');
}
if (oldKey in map && me.getAt(map[oldKey]) !== item) {
// There is a different item in the map with the oldKey which is also
// an error. Do not call this method for items that are not part of
// the collection.
Ext.raise('Incorrect oldKey "' + oldKey +
'" for item with newKey "' + newKey + '"');
}
}
//</debug>
},
getCount: function() {
return this.length;
},
getKey: function(item) {
return item.id || item.getId();
},
getRange: function(begin, end) {
var items = this.items,
length = items.length,
range;
if (!length) {
range = [];
}
else {
range = Ext.Number.clipIndices(length, [begin, end]);
range = items.slice(range[0], range[1]);
}
return range;
},
remove: function(item) {
var me = this,
map = me.map,
items = me.items,
ret = null,
n = 1,
changed, old, i, idx, id, last, was;
if (Ext.isArray(item)) {
n = item.length;
old = ret = [];
}
if (me.length) {
for (i = 0; i < n; i++) {
idx = map[id = me.getKey(old ? item[i] : item)];
if (idx !== undefined) {
delete map[id];
was = items[idx];
if (old) {
old.push(was);
}
else {
ret = was;
}
last = items.pop();
if (idx < --me.length) {
items[idx] = last;
map[me.getKey(last)] = idx;
}
changed = true;
}
}
if (changed) {
++me.generation;
}
}
return ret;
},
removeByKey: function(key) {
var item = this.getByKey(key);
if (item) {
this.remove(item);
}
return item || null;
},
replace: function(item) {
this.add(item);
return item;
},
sort: function(fn) {
var me = this,
items = me.items,
n = items.length,
item;
if (n) {
Ext.Array.sort(items, fn);
me.map = {};
while (n-- > 0) {
item = items[n];
me.map[me.getKey(item)] = n;
}
++me.generation;
}
}
});