Source: muze-utils/src/DataSystem/selection.js

/**
 * This class represents a selection applied on a data array.
 *
 * @class Selection
 */
class Selection {
    /**
     * Creates an instance of Selection.
     * @param {Array<DataObject>} data Array of DataObjects.
     * @memberof Selection
     */
    constructor () {
        this._data = [];
        // // map of id to data
        this._idMap = {};

        this._mode = '';
        // data.forEach((item, idx) => {
        //     const index = item.id || idx;
        //     this._idMap[index] = item;
        // });
        // // array to store data in enter phase
        this._enterdata = [];
        // // array to store data in exit phase
        this._exitdata = [];
    }
    /**
     *  Gets the object bound to a class
     *
     * @return {Object} current data set bound to the class
     * @memberof Selection
     */
    getObjects () {
        return Object.keys(this._idMap).map(e => this._idMap[e]);
    }

    /**
     * This method is used to supply seed data to a selection.
     *
     * @param {Array} newData Seed data to create the enter selection.
     * @param {Functon | undefined } idGetter This function is used to uniqely identify a data entry.
     * @return {Selection} Modified selection.
     * @memberof Selection
     */
    data (newData, idGetter) {
        if (idGetter) {
            this._data = [];
            this._idGetter = idGetter;
            const tempMap = {};

            newData.forEach((...params) => {
                const index = idGetter(...params);
                tempMap[index] = params[0];
            });
            // check if any data items have been removed
            const purgedIds = [];

            Object.keys(this._idMap).forEach((id) => {
                if (!tempMap[id]) {
                    purgedIds.push(id);
                }
            });

            Object.keys(tempMap).forEach((id) => {
                if (!this._idMap[id]) {
                    this._enterdata.push(tempMap[id]);
                } else {
                    this._idMap[id] = tempMap[id];
                    this._data.push(tempMap[id]);
                }
            });
            // move the purged items to exit selection
            purgedIds.forEach((id) => {
                const purged = this._idMap[id];
                this._exitdata.push(purged);
                // this._data = this._data.slice(id, 1);
                delete this._idMap[id];
            });
            // this._data = this._data.slice(temp, this._data.length);

            return this;
        }
        // no id getter supplied so use indices
        if (newData.length > this._data.length) {
            const startIdx = this._data.length;
            for (let i = startIdx; i < newData.length; i += 1) {
                this._enterdata.push(newData[i]);
            }
        } else {
            // push to exit selection
            const temp = newData.length;
            for (let i = temp; i < this._data.length; i += 1) {
                const purged = this._data[i];
                delete this._idMap[purged.id];
                this._exitdata.push(purged);
            }
            this._data = this._data.slice(temp, this._data.length);
        }
        return this;
    }

    /**
     * Applies the supplied callback to each data element
     * and returns a new selection.
     *
     * @param {Function} callback Callback to execute on each item.
     * @return {Selection} New selection with data created using callback.
     * @memberof EnterSelection
     */
    append (callback) {
        this[`_${this._mode}data`].forEach((...params) => {
            const data = params[0];
            const id = this._idGetter ? this._idGetter(...params) : (data.id || params[1]);
            this._idMap[id] = callback(...params);
        });
        this._mode = '';
        return this;
    }

    /**
     * This method returns an enter selection that
     * allows or update operations.
     *
     * @return {EnterSelection} Instance of enter selection.
     * @memberof Selection
     */
    enter () {
        this._mode = 'enter';
        return this;
        // return new EnterSelection(this._enterdata, this._idMap, this._idGetter);
    }

    /**
     * This method is used to set key value pairs
     * on data objects.
     *
     * @param {string} key Name of property.
     * @param {any} value Value of property.
     * @return {Selection} Modified selection.
     * @memberof Selection
     */
    attr (key, value) {
        this._data.forEach(item => item.attr(key, value));
        return this;
    }

    /**
     * This method merges the data of one selection with another.
     *
     * @param {Selection} selection Instance of selection.
     * @return {Selection} Modified selection.
     * @memberof Selection
     */
    merge (selection) {
        selection._data.forEach((...params) => {
            const id = this._idGetter ? this._idGetter(...params) : (params[0].id || params[1]);
            this._idMap[id] = params[0];
            this._data.push(params[0]);
        });
        // reset enter selection
        this._enterdata = [];
        return this;
    }

    /**
     * Returns a selection with exit data.
     *
     * @return {Selection} Instance of selection.
     * @memberof Selection
     */
    exit () {
        this._mode = 'exit';
        // const exitdata = this._exitdata;
        // const exitSelection = new Selection(exitdata);
        // this._exitdata = [];
        return this;
    }

    each (fn) {
        Object.keys(this._idMap).forEach((e) => {
            fn(this._idMap[e]);
        });
        return this;
    }

    map (fn) {
        Object.keys(this._idMap).forEach((...params) => {
            this._idMap[params[0]] = fn(this._idMap[params[0]], ...params);
        });
        return this;
    }

    /**
     * Executes the cleanup operation associated with data objets.
     *
     * @memberof Selection
     */
    remove () {
        // do cleanup on DDO's
        const data = this[`_${this._mode}data`];

        data.forEach(item => item.remove());
        if (this._mode === '') {
            this.each(e => e.remove());
        }
        this[`_${this._mode}data`] = [];
        this._mode = '';
    }
}

export default Selection;