Home Reference Source

src/extensions/advancedGrid/adapterEzEditTable.js

import {Feature} from '../../feature';
import {tag} from '../../dom';
import {INPUT} from '../../const';
import {defaultsStr} from '../../settings';
import {root} from '../../root';

const INSTANTIATION_ERROR = `Failed to instantiate EditTable object.
    \n"ezEditTable" dependency not found.`;

/**
 * Adapter module for ezEditTable, an external library providing advanced
 * grid features (selection and edition):
 * http://codecanyon.net/item/ezedittable-enhance-html-tables/2425123?ref=koalyptus
 */
export default class AdapterEzEditTable extends Feature {

    /**
     * Creates an instance of AdapterEzEditTable
     *
     * @param {TableFilter} tf TableFilter instance
     * @param {Object} cfg Configuration options for ezEditTable library
     */
    constructor(tf, cfg) {
        super(tf, AdapterEzEditTable);

        /**
         * Module description
         * @type {String}
         */
        this.desc = defaultsStr(cfg.description, 'ezEditTable adapter');

        /**
         * Filename of ezEditTable library
         * @type {String}
         */
        this.filename = defaultsStr(cfg.filename, 'ezEditTable.js');

        /**
         * Path to ezEditTable library
         * @type {String}
         */
        this.vendorPath = cfg.vendor_path;

        /**
         * Load ezEditTable stylesheet
         * @type {Boolean}
         */
        this.loadStylesheet = Boolean(cfg.load_stylesheet);

        /**
         * Path to ezEditTable stylesheet
         * @type {String}
         */
        this.stylesheet = defaultsStr(cfg.stylesheet,
            this.vendorPath + 'ezEditTable.css');

        /**
         * Name of ezEditTable stylesheet
         * @type {String}
         */
        this.stylesheetName = defaultsStr(cfg.stylesheet_name,
            'ezEditTableCss');

        // Enable the ezEditTable's scroll into view behaviour if grid layout on
        cfg.scroll_into_view = cfg.scroll_into_view === false ?
            false : tf.gridLayout;

        /**
         * ezEditTable instance
         * @type {EditTable}
         * @private
         */
        this._ezEditTable = null;

        /**
         * ezEditTable configuration
         * @private
         */
        this.cfg = cfg;

        this.enable();
    }

    /**
     * Conditionally load ezEditTable library and set advanced grid
     */
    init() {
        if (this.initialized) {
            return;
        }
        let tf = this.tf;
        if (root.EditTable) {
            this._setAdvancedGrid();
        } else {
            let path = this.vendorPath + this.filename;
            tf.import(this.filename, path, () => this._setAdvancedGrid());
        }
        if (this.loadStylesheet && !tf.isImported(this.stylesheet, 'link')) {
            tf.import(this.stylesheetName, this.stylesheet, null, 'link');
        }

        // TODO: hack to prevent ezEditTable enter key event hijaking.
        // Needs to be fixed in the vendor's library
        this.emitter.on(['filter-focus', 'filter-blur'],
            () => this._toggleForInputFilter());

        /**
         * @inherited
         */
        this.initialized = true;
    }

    /**
     * Instantiate ezEditTable component for advanced grid features
     * @private
     */
    _setAdvancedGrid() {
        let tf = this.tf;

        //start row for EditTable constructor needs to be calculated
        let startRow,
            cfg = this.cfg,
            thead = tag(tf.dom(), 'thead');

        //if thead exists and startRow not specified, startRow is calculated
        //automatically by EditTable
        if (thead.length > 0 && !cfg.startRow) {
            startRow = undefined;
        }
        //otherwise startRow config property if any or TableFilter refRow
        else {
            startRow = cfg.startRow || tf.refRow;
        }

        cfg.base_path = cfg.base_path || tf.basePath + 'ezEditTable/';
        let editable = cfg.editable;
        let selectable = cfg.selection;

        if (selectable) {
            cfg.default_selection = cfg.default_selection || 'row';
        }
        //CSS Styles
        cfg.active_cell_css = cfg.active_cell_css || 'ezETSelectedCell';

        let _lastValidRowIndex = 0;
        let _lastRowIndex = 0;

        if (selectable) {
            //Row navigation needs to be calculated according to TableFilter's
            //validRowsIndex array
            let onAfterSelection = function (et, selectedElm, e) {
                let slc = et.Selection;
                //Next valid filtered row needs to be selected
                let doSelect = function (nextRowIndex) {
                    if (et.defaultSelection === 'row') {
                        /* eslint-disable */
                        slc.SelectRowByIndex(nextRowIndex);
                        /* eslint-enable */
                    } else {
                        /* eslint-disable */
                        et.ClearSelections();
                        /* eslint-enable */
                        let cellIndex = selectedElm.cellIndex,
                            row = tf.dom().rows[nextRowIndex];
                        if (et.defaultSelection === 'both') {
                            /* eslint-disable */
                            slc.SelectRowByIndex(nextRowIndex);
                            /* eslint-enable */
                        }
                        if (row) {
                            /* eslint-disable */
                            slc.SelectCell(row.cells[cellIndex]);
                            /* eslint-enable */
                        }
                    }
                    //Table is filtered
                    if (tf.validRowsIndex.length !== tf.getRowsNb()) {
                        let r = tf.dom().rows[nextRowIndex];
                        if (r) {
                            r.scrollIntoView(false);
                        }
                        if (cell) {
                            if (cell.cellIndex === (tf.getCellsNb() - 1) &&
                                tf.gridLayout) {
                                tf.tblCont.scrollLeft = 100000000;
                            }
                            else if (cell.cellIndex === 0 && tf.gridLayout) {
                                tf.tblCont.scrollLeft = 0;
                            } else {
                                cell.scrollIntoView(false);
                            }
                        }
                    }
                };

                //table is not filtered
                if (!tf.validRowsIndex) {
                    return;
                }
                let validIndexes = tf.validRowsIndex,
                    validIdxLen = validIndexes.length,
                    row = et.defaultSelection !== 'row' ?
                        selectedElm.parentNode : selectedElm,
                    //cell for default_selection = 'both' or 'cell'
                    cell = selectedElm.nodeName === 'TD' ? selectedElm : null,
                    /* eslint-disable */
                    keyCode = e !== undefined ? et.Event.GetKey(e) : 0,
                    /* eslint-enable */
                    isRowValid = validIndexes.indexOf(row.rowIndex) !== -1,
                    nextRowIndex,
                    paging = tf.feature('paging'),
                    //pgup/pgdown keys
                    d = keyCode === 34 || keyCode === 33 ?
                        (paging && paging.pageLength || et.nbRowsPerPage) :
                        1;

                //If next row is not valid, next valid filtered row needs to be
                //calculated
                if (!isRowValid) {
                    //Selection direction up/down
                    if (row.rowIndex > _lastRowIndex) {
                        //last row
                        if (row.rowIndex >= validIndexes[validIdxLen - 1]) {
                            nextRowIndex = validIndexes[validIdxLen - 1];
                        } else {
                            let calcRowIndex = (_lastValidRowIndex + d);
                            if (calcRowIndex > (validIdxLen - 1)) {
                                nextRowIndex = validIndexes[validIdxLen - 1];
                            } else {
                                nextRowIndex = validIndexes[calcRowIndex];
                            }
                        }
                    } else {
                        //first row
                        if (row.rowIndex <= validIndexes[0]) {
                            nextRowIndex = validIndexes[0];
                        } else {
                            let v = validIndexes[_lastValidRowIndex - d];
                            nextRowIndex = v ? v : validIndexes[0];
                        }
                    }
                    _lastRowIndex = row.rowIndex;
                    doSelect(nextRowIndex);
                } else {
                    //If filtered row is valid, special calculation for
                    //pgup/pgdown keys
                    if (keyCode !== 34 && keyCode !== 33) {
                        _lastValidRowIndex = validIndexes.indexOf(row.rowIndex);
                        _lastRowIndex = row.rowIndex;
                    } else {
                        if (keyCode === 34) { //pgdown
                            //last row
                            if ((_lastValidRowIndex + d) <= (validIdxLen - 1)) {
                                nextRowIndex = validIndexes[
                                    _lastValidRowIndex + d];
                            } else {
                                nextRowIndex = [validIdxLen - 1];
                            }
                        } else { //pgup
                            //first row
                            if ((_lastValidRowIndex - d) <= validIndexes[0]) {
                                nextRowIndex = validIndexes[0];
                            } else {
                                nextRowIndex = validIndexes[
                                    _lastValidRowIndex - d];
                            }
                        }
                        _lastRowIndex = nextRowIndex;
                        _lastValidRowIndex = validIndexes.indexOf(nextRowIndex);
                        doSelect(nextRowIndex);
                    }
                }
            };

            //Page navigation has to be enforced whenever selected row is out of
            //the current page range
            let onBeforeSelection = function (et, selectedElm) {
                let row = et.defaultSelection !== 'row' ?
                    selectedElm.parentNode : selectedElm;
                if (tf.paging) {
                    if (tf.feature('paging').nbPages > 1) {
                        let paging = tf.feature('paging');
                        //page length is re-assigned in case it has changed
                        et.nbRowsPerPage = paging.pageLength;
                        let validIndexes = tf.validRowsIndex,
                            validIdxLen = validIndexes.length,
                            pagingEndRow = parseInt(paging.startPagingRow, 10) +
                                parseInt(paging.pageLength, 10);
                        let rowIndex = row.rowIndex;

                        if ((rowIndex === validIndexes[validIdxLen - 1]) &&
                            paging.currentPageNb !== paging.nbPages) {
                            paging.setPage('last');
                        }
                        else if ((rowIndex === validIndexes[0]) &&
                            paging.currentPageNb !== 1) {
                            paging.setPage('first');
                        }
                        else if (rowIndex > validIndexes[pagingEndRow - 1] &&
                            rowIndex < validIndexes[validIdxLen - 1]) {
                            paging.setPage('next');
                        }
                        else if (
                            rowIndex < validIndexes[paging.startPagingRow] &&
                            rowIndex > validIndexes[0]) {
                            paging.setPage('previous');
                        }
                    }
                }
            };

            //Selected row needs to be visible when paging is activated
            if (tf.paging) {
                tf.feature('paging').onAfterChangePage = function (paging) {
                    let advGrid = paging.tf.extension('advancedGrid');
                    let et = advGrid._ezEditTable;
                    let slc = et.Selection;
                    /* eslint-disable */
                    let row = slc.GetActiveRow();
                    /* eslint-enable */
                    if (row) {
                        row.scrollIntoView(false);
                    }
                    /* eslint-disable */
                    let cell = slc.GetActiveCell();
                    /* eslint-enable */
                    if (cell) {
                        cell.scrollIntoView(false);
                    }
                };
            }

            //Rows navigation when rows are filtered is performed with the
            //EditTable row selection callback events
            if (cfg.default_selection === 'row') {
                let fnB = cfg.on_before_selected_row;
                cfg.on_before_selected_row = function () {
                    var args = arguments;
                    onBeforeSelection(args[0], args[1], args[2]);
                    if (fnB) {
                        fnB.call(null, args[0], args[1], args[2]);
                    }
                };
                let fnA = cfg.on_after_selected_row;
                cfg.on_after_selected_row = function () {
                    var args = arguments;
                    onAfterSelection(args[0], args[1], args[2]);
                    if (fnA) {
                        fnA.call(null, args[0], args[1], args[2]);
                    }
                };
            } else {
                let fnD = cfg.on_before_selected_cell;
                cfg.on_before_selected_cell = function () {
                    var args = arguments;
                    onBeforeSelection(args[0], args[1], args[2]);
                    if (fnD) {
                        fnD.call(null, args[0], args[1], args[2]);
                    }
                };
                let fnC = cfg.on_after_selected_cell;
                cfg.on_after_selected_cell = function () {
                    var args = arguments;
                    onAfterSelection(args[0], args[1], args[2]);
                    if (fnC) {
                        fnC.call(null, args[0], args[1], args[2]);
                    }
                };
            }
        }
        if (editable) {
            //Added or removed rows, TF rows number needs to be re-calculated
            let fnE = cfg.on_added_dom_row;
            cfg.on_added_dom_row = function () {
                var args = arguments;
                tf.nbFilterableRows++;
                if (!tf.paging) {
                    tf.emitter.emit('rows-changed', tf, this);
                } else {
                    tf.nbFilterableRows++;
                    tf.paging = false;
                    tf.feature('paging').destroy();
                    tf.feature('paging').reset();
                }
                if (tf.alternateRows) {
                    tf.feature('alternateRows').init();
                }
                if (fnE) {
                    fnE.call(null, args[0], args[1], args[2]);
                }
            };
            if (cfg.actions && cfg.actions['delete']) {
                let fnF = cfg.actions['delete'].on_after_submit;
                cfg.actions['delete'].on_after_submit = function () {
                    var args = arguments;
                    tf.nbFilterableRows--;
                    if (!tf.paging) {
                        tf.emitter.emit('rows-changed', tf, this);
                    } else {
                        tf.nbFilterableRows--;
                        tf.paging = false;
                        tf.feature('paging').destroy();
                        tf.feature('paging').reset(false);
                    }
                    if (tf.alternateRows) {
                        tf.feature('alternateRows').init();
                    }
                    if (fnF) {
                        fnF.call(null, args[0], args[1]);
                    }
                };
            }
        }

        try {
            /* eslint-disable */
            this._ezEditTable = new EditTable(tf.id, cfg, startRow);
            this._ezEditTable.Init();
            /* eslint-enable */
        } catch (e) { throw new Error(INSTANTIATION_ERROR); }

        this.initialized = true;
    }

    /**
     * Reset advanced grid when previously removed
     */
    reset() {
        let ezEditTable = this._ezEditTable;
        if (ezEditTable) {
            if (this.cfg.selection) {
                /* eslint-disable */
                ezEditTable.Selection.Set();
                /* eslint-enable */
            }
            if (this.cfg.editable) {
                /* eslint-disable */
                ezEditTable.Editable.Set();
                /* eslint-enable */
            }
        }
    }

    /**
     * Toggle behaviour
     */
    toggle() {
        let ezEditTable = this._ezEditTable;
        if (ezEditTable.editable) {
            /* eslint-disable */
            ezEditTable.Editable.Remove();
            /* eslint-enable */
        } else {
            /* eslint-disable */
            ezEditTable.Editable.Set();
            /* eslint-enable */
        }
        if (ezEditTable.selection) {
            /* eslint-disable */
            ezEditTable.Selection.Remove();
            /* eslint-enable */
        } else {
            /* eslint-disable */
            ezEditTable.Selection.Set();
            /* eslint-enable */
        }
    }

    _toggleForInputFilter() {
        let tf = this.tf;
        if (!tf.getActiveFilterId()) {
            return;
        }
        let colIndex = tf.getColumnIndexFromFilterId(tf.getActiveFilterId());
        let filterType = tf.getFilterType(colIndex);
        if (filterType === INPUT) {
            this.toggle();
        }
    }

    /**
     * Remove advanced grid
     */
    destroy() {
        if (!this.initialized) {
            return;
        }
        let ezEditTable = this._ezEditTable;
        if (ezEditTable) {
            if (this.cfg.selection) {
                /* eslint-disable */
                ezEditTable.Selection.ClearSelections();
                ezEditTable.Selection.Remove();
                /* eslint-enable */
            }
            if (this.cfg.editable) {
                /* eslint-disable */
                ezEditTable.Editable.Remove();
                /* eslint-enable */
            }
        }

        this.emitter.off(['filter-focus', 'filter-blur'],
            () => this._toggleForInputFilter());
        this.initialized = false;
    }
}

AdapterEzEditTable.meta = {altName: 'advancedGrid'};