/** * @module ol/Collection */ import AssertionError from './AssertionError.js'; import BaseObject from './Object.js'; import CollectionEventType from './CollectionEventType.js'; import Event from './events/Event.js'; /** * @enum {string} * @private */ const Property = { LENGTH: 'length', }; /** * @classdesc * Events emitted by {@link module:ol/Collection~Collection} instances are instances of this * type. * @template T */ export class CollectionEvent extends Event { /** * @param {import("./CollectionEventType.js").default} type Type. * @param {T} element Element. * @param {number} index The index of the added or removed element. */ constructor(type, element, index) { super(type); /** * The element that is added to or removed from the collection. * @type {T} * @api */ this.element = element; /** * The index of the added or removed element. * @type {number} * @api */ this.index = index; } } /*** * @template T * @template Return * @typedef {import("./Observable").OnSignature & * import("./Observable").OnSignature & * import("./Observable").OnSignature<'add'|'remove', CollectionEvent, Return> & * import("./Observable").CombinedOnSignature} CollectionOnSignature */ /** * @typedef {Object} Options * @property {boolean} [unique=false] Disallow the same item from being added to * the collection twice. */ /** * @classdesc * An expanded version of standard JS Array, adding convenience methods for * manipulation. Add and remove changes to the Collection trigger a Collection * event. Note that this does not cover changes to the objects _within_ the * Collection; they trigger events on the appropriate object, not on the * Collection as a whole. * * @fires CollectionEvent * * @template T * @api */ class Collection extends BaseObject { /** * @param {Array} [array] Array. * @param {Options} [options] Collection options. */ constructor(array, options) { super(); /*** * @type {CollectionOnSignature} */ this.on; /*** * @type {CollectionOnSignature} */ this.once; /*** * @type {CollectionOnSignature} */ this.un; options = options || {}; /** * @private * @type {boolean} */ this.unique_ = !!options.unique; /** * @private * @type {!Array} */ this.array_ = array ? array : []; if (this.unique_) { for (let i = 0, ii = this.array_.length; i < ii; ++i) { this.assertUnique_(this.array_[i], i); } } this.updateLength_(); } /** * Remove all elements from the collection. * @api */ clear() { while (this.getLength() > 0) { this.pop(); } } /** * Add elements to the collection. This pushes each item in the provided array * to the end of the collection. * @param {!Array} arr Array. * @return {Collection} This collection. * @api */ extend(arr) { for (let i = 0, ii = arr.length; i < ii; ++i) { this.push(arr[i]); } return this; } /** * Iterate over each element, calling the provided callback. * @param {function(T, number, Array): *} f The function to call * for every element. This function takes 3 arguments (the element, the * index and the array). The return value is ignored. * @api */ forEach(f) { const array = this.array_; for (let i = 0, ii = array.length; i < ii; ++i) { f(array[i], i, array); } } /** * Get a reference to the underlying Array object. Warning: if the array * is mutated, no events will be dispatched by the collection, and the * collection's "length" property won't be in sync with the actual length * of the array. * @return {!Array} Array. * @api */ getArray() { return this.array_; } /** * Get the element at the provided index. * @param {number} index Index. * @return {T} Element. * @api */ item(index) { return this.array_[index]; } /** * Get the length of this collection. * @return {number} The length of the array. * @observable * @api */ getLength() { return this.get(Property.LENGTH); } /** * Insert an element at the provided index. * @param {number} index Index. * @param {T} elem Element. * @api */ insertAt(index, elem) { if (index < 0 || index > this.getLength()) { throw new Error('Index out of bounds: ' + index); } if (this.unique_) { this.assertUnique_(elem); } this.array_.splice(index, 0, elem); this.updateLength_(); this.dispatchEvent( new CollectionEvent(CollectionEventType.ADD, elem, index) ); } /** * Remove the last element of the collection and return it. * Return `undefined` if the collection is empty. * @return {T|undefined} Element. * @api */ pop() { return this.removeAt(this.getLength() - 1); } /** * Insert the provided element at the end of the collection. * @param {T} elem Element. * @return {number} New length of the collection. * @api */ push(elem) { if (this.unique_) { this.assertUnique_(elem); } const n = this.getLength(); this.insertAt(n, elem); return this.getLength(); } /** * Remove the first occurrence of an element from the collection. * @param {T} elem Element. * @return {T|undefined} The removed element or undefined if none found. * @api */ remove(elem) { const arr = this.array_; for (let i = 0, ii = arr.length; i < ii; ++i) { if (arr[i] === elem) { return this.removeAt(i); } } return undefined; } /** * Remove the element at the provided index and return it. * Return `undefined` if the collection does not contain this index. * @param {number} index Index. * @return {T|undefined} Value. * @api */ removeAt(index) { if (index < 0 || index >= this.getLength()) { return undefined; } const prev = this.array_[index]; this.array_.splice(index, 1); this.updateLength_(); this.dispatchEvent( /** @type {CollectionEvent} */ ( new CollectionEvent(CollectionEventType.REMOVE, prev, index) ) ); return prev; } /** * Set the element at the provided index. * @param {number} index Index. * @param {T} elem Element. * @api */ setAt(index, elem) { const n = this.getLength(); if (index >= n) { this.insertAt(index, elem); return; } if (index < 0) { throw new Error('Index out of bounds: ' + index); } if (this.unique_) { this.assertUnique_(elem, index); } const prev = this.array_[index]; this.array_[index] = elem; this.dispatchEvent( /** @type {CollectionEvent} */ ( new CollectionEvent(CollectionEventType.REMOVE, prev, index) ) ); this.dispatchEvent( /** @type {CollectionEvent} */ ( new CollectionEvent(CollectionEventType.ADD, elem, index) ) ); } /** * @private */ updateLength_() { this.set(Property.LENGTH, this.array_.length); } /** * @private * @param {T} elem Element. * @param {number} [except] Optional index to ignore. */ assertUnique_(elem, except) { for (let i = 0, ii = this.array_.length; i < ii; ++i) { if (this.array_[i] === elem && i !== except) { throw new AssertionError(58); } } } } export default Collection;