Source: jscore/DataContainer.js

(function() {
    /**
     * to be used as a base class for data containers
     * this is a Set of objects that will not allow duplicates!
     * @augments Help4.jscore.Base
     */
    Help4.jscore.DataContainer = class extends Help4.jscore.Base {
        /**
         * @override
         * @param {?Object[]} [init = []]
         */
        constructor(init) {
            super({
                statics: {
                    _store:   {init: init || [], destroy: false},
                    onChange: {init: new Help4.EmbeddedEvent()}
                }
            });
        }

        /** @returns {Help4.jscore.DataContainer} */
        clean() {
            if (this._store.length) {
                const oldValue = this.toArray();
                this._store = [];
                this.onChange.onEvent({value: [], oldValue});
            }
            return this;
        }

        /** @returns {number} */
        count() {
            return this._store.length;
        }

        /** @returns {Help4.jscore.DataContainer} */
        clone() {
            return new this.constructor(this.toArray());
        }

        /**
         * @param {Help4.jscore.DataContainer} instance
         * @returns {boolean}
         */
        equals(instance) {
            if (instance === this) return true;

            const a1 = this.toArray();
            const a2 = instance.toArray();
            return Help4.equalArrays(a1, a2);
        }

        /**
         * @param {Object} item
         * @returns {boolean}
         */
        includes(item) {
            return !!this._store.find(i => Help4.equalObjects(i, item));
        }

        /**
         * @param {number} index
         * @returns {Object|null}
         */
        get(index) {
            return this._store[index] || null;
        }

        /**
         * @param {Object} items
         * @returns {Help4.jscore.DataContainer}
         */
        add(...items) {
            const {_store} = this;
            const oldValue = this.toArray();

            let changed = false;
            for (const item of items) {
                if (!this.includes(item)) {
                    _store.push(Help4.cloneObject(item));
                    changed = true;
                }
            }

            changed && this.onChange.onEvent({value: this.toArray(), oldValue});
            return this;
        }

        /**
         * @param {Array<Object>} items
         * @returns {Help4.jscore.DataContainer}
         */
        set(...items) {
            const changed = this._store.length || items.length;
            const oldValue = this.toArray();

            this._store = [];

            const {_store} = this;
            for (const item of items) {
                this.includes(item) || _store.push(Help4.cloneObject(item));
            }

            changed && this.onChange.onEvent({value: this.toArray(), oldValue});
            return this;
        }

        /**
         * @param {Object} item
         * @returns {Help4.jscore.DataContainer}
         */
        remove(item) {
            const {_store} = this;
            const index = _store.findIndex(i => Help4.equalObjects(i, item));
            if (index >= 0) {
                const oldValue = this.toArray();
                _store.splice(index, 1);
                this.onChange.onEvent({value: this.toArray(), oldValue});
            }
            return this;
        }

        /**
         * @param {Object} query
         * @returns {Object|null}
         */
        find(query) {
            /**
             * @param {*} query
             * @param {*} store
             * @returns {boolean|null|Object}
             */
            const find = (query, store) => {
                if (query == null || store == null) return query === store;

                const isQueryObject = typeof query === 'object';
                const isStoreObject = typeof store === 'object';
                const isStoreArray = Help4.isArray(store);

                if (isQueryObject && isStoreArray) {
                    const keys = Object.keys(query);
                    for (const item of store) {
                        if (keys.every(key => !!find(query[key], item[key]))) return item;
                    }
                    return null;
                }

                if (isQueryObject && isStoreObject) {
                    for (const [key, value] of Object.entries(query)) {
                        if (!Help4.equalValues(value, store[key])) return false;
                    }
                    return true;
                }

                return Help4.equalValues(query, store);
            }

            return find(query, this._store);
        }

        /** @returns {Array<Object>} */
        toArray() {
            return Help4.cloneArray(this._store);
        }

        /**
         * for (const control of container) {
         *     ...
         * }
         *
         * @returns {{next: (function(): {value: *, done: boolean})}}
         */
        [Symbol.iterator]() {
            let index = -1;
            return {
                next: () => ({
                    value: this._store[++index],
                    done: !(index in this._store)
                })
            };
        }
    }
})();