UNPKG

@revolist/revogrid

Version:

Virtual reactive data grid spreadsheet component - RevoGrid.

224 lines (223 loc) 10.1 kB
/*! * Built by Revolist OU ❤️ */ import size from "lodash/size"; import debounce from "lodash/debounce"; import { BasePlugin } from "../base.plugin"; import { getColumnByProp } from "../../utils/column.utils"; import { columnTypes, rowTypes } from "../../store/index"; import { getComparer, getNextOrder, sortIndexByItems } from "./sorting.func"; export * from './sorting.types'; export * from './sorting.func'; export * from './sorting.sign'; /** * Lifecycle * 1. @event `beforesorting` - Triggered when sorting just starts. Nothing has happened yet. This can be triggered from a column or from the source. If the type is from rows, the column will be undefined. * 2. @event `beforesourcesortingapply` - Triggered before the sorting data is applied to the data source. You can prevent this event, and the data will not be sorted. * 3. @event `beforesortingapply` - Triggered before the sorting data is applied to the data source. You can prevent this event, and the data will not be sorted. This event is only called from a column sorting click. * 4. @event `aftersortingapply` - Triggered after sorting has been applied and completed. This event occurs for both row and column sorting. * * Note: If you prevent an event, it will not proceed to the subsequent steps. */ export class SortingPlugin extends BasePlugin { constructor(revogrid, providers, config) { super(revogrid, providers); this.revogrid = revogrid; /** * Delayed sorting promise */ this.sortingPromise = null; /** * We need to sort only so often */ this.postponeSort = debounce((order, comparison, ignoreViewportUpdate) => this.runSorting(order, comparison, ignoreViewportUpdate), 50); const setConfig = (cfg) => { var _a; if (cfg) { const sortingFunc = {}; const order = {}; (_a = cfg.columns) === null || _a === void 0 ? void 0 : _a.forEach(col => { sortingFunc[col.prop] = getComparer(col, col.order); order[col.prop] = col.order; }); if (cfg.additive) { this.sorting = Object.assign(Object.assign({}, this.sorting), order); this.sortingFunc = Object.assign(Object.assign({}, this.sortingFunc), sortingFunc); } else { // // set sorting this.sorting = order; this.sortingFunc = sortingFunc; } } }; setConfig(config); this.addEventListener('sortingconfigchanged', ({ detail }) => { config = detail; setConfig(detail); this.startSorting(this.sorting, this.sortingFunc); }); this.addEventListener('beforeheaderrender', ({ detail, }) => { var _a; const { data: column } = detail; if (column.sortable) { detail.data = Object.assign(Object.assign({}, column), { order: (_a = this.sorting) === null || _a === void 0 ? void 0 : _a[column.prop] }); } }); this.addEventListener('beforeanysource', ({ detail: { type }, }) => { // if sorting was provided - sort data if (!!this.sorting && this.sortingFunc) { const event = this.emit('beforesourcesortingapply', { type, sorting: this.sorting }); if (event.defaultPrevented) { return; } this.startSorting(this.sorting, this.sortingFunc); } }); this.addEventListener('aftercolumnsset', ({ detail: { order }, }) => { // if config provided - do nothing, read from config if (config) { return; } const columns = this.providers.column.getColumns(); const sortingFunc = {}; for (let prop in order) { const cmp = getComparer(getColumnByProp(columns, prop), order[prop]); sortingFunc[prop] = cmp; } // set sorting this.sorting = order; this.sortingFunc = order && sortingFunc; }); this.addEventListener('beforeheaderclick', (e) => { var _a, _b, _c, _d; if (e.defaultPrevented) { return; } if (!((_b = (_a = e.detail) === null || _a === void 0 ? void 0 : _a.column) === null || _b === void 0 ? void 0 : _b.sortable)) { return; } this.headerclick(e.detail.column, (_d = (_c = e.detail) === null || _c === void 0 ? void 0 : _c.originalEvent) === null || _d === void 0 ? void 0 : _d.shiftKey); }); } /** * Entry point for sorting, waits for all delays, registers jobs */ startSorting(order, sortingFunc, ignoreViewportUpdate) { if (!this.sortingPromise) { // add job before render this.revogrid.jobsBeforeRender.push(new Promise(resolve => { this.sortingPromise = resolve; })); } this.postponeSort(order, sortingFunc, ignoreViewportUpdate); } /** * Apply sorting to data on header click * If additive - add to existing sorting, multiple columns can be sorted */ headerclick(column, additive) { var _a, _b, _c; const columnProp = column.prop; let order = getNextOrder((_a = this.sorting) === null || _a === void 0 ? void 0 : _a[columnProp]); const beforeEvent = this.emit('beforesorting', { column, order, additive }); if (beforeEvent.defaultPrevented) { return; } order = beforeEvent.detail.order; // apply sort data const beforeApplyEvent = this.emit('beforesortingapply', { column: beforeEvent.detail.column, order, additive, }); if (beforeApplyEvent.defaultPrevented) { return; } const cmp = getComparer(beforeApplyEvent.detail.column, beforeApplyEvent.detail.order); if (beforeApplyEvent.detail.additive && this.sorting) { const sorting = {}; const sortingFunc = {}; if (columnProp in sorting && size(sorting) > 1 && order === undefined) { delete sorting[columnProp]; delete sortingFunc[columnProp]; } else { sorting[columnProp] = order; sortingFunc[columnProp] = cmp; } this.sorting = Object.assign(Object.assign({}, this.sorting), sorting); // extend sorting function with new sorting for multiple columns sorting this.sortingFunc = Object.assign(Object.assign({}, this.sortingFunc), sortingFunc); } else { if (order) { // reset sorting this.sorting = { [columnProp]: order }; this.sortingFunc = { [columnProp]: cmp }; } else { (_b = this.sorting) === null || _b === void 0 ? true : delete _b[columnProp]; (_c = this.sortingFunc) === null || _c === void 0 ? true : delete _c[columnProp]; } } this.startSorting(this.sorting, this.sortingFunc); } runSorting(order, comparison, ignoreViewportUpdate) { var _a; this.sort(order, comparison, undefined, ignoreViewportUpdate); (_a = this.sortingPromise) === null || _a === void 0 ? void 0 : _a.call(this); this.sortingPromise = null; } /** * Sort items by sorting function * @requires proxyItems applied to row store * @requires source applied to row store * * @param sorting - per column sorting * @param data - this.stores['rgRow'].store.get('source') */ sort(sorting, sortingFunc, types = rowTypes, ignoreViewportUpdate = false) { // if no sorting - reset if (!Object.keys(sorting || {}).length) { for (let type of types) { const storeService = this.providers.data.stores[type]; // row data const source = storeService.store.get('source'); // row indexes const proxyItems = storeService.store.get('proxyItems'); // row indexes const newItemsOrder = Array.from({ length: source.length }, (_, i) => i); // recover indexes range(0, source.length) this.providers.dimension.updateSizesPositionByNewDataIndexes(type, newItemsOrder, proxyItems); storeService.setData({ proxyItems: newItemsOrder, source: [...source], }); } } else { for (let type of types) { const storeService = this.providers.data.stores[type]; // row data const source = storeService.store.get('source'); // row indexes const proxyItems = storeService.store.get('proxyItems'); const newItemsOrder = sortIndexByItems([...proxyItems], source, sortingFunc); // take row indexes before trim applied and proxy items const prevItems = storeService.store.get('items'); storeService.setData({ proxyItems: newItemsOrder, source: [...source], }); // take currently visible row indexes const newItems = storeService.store.get('items'); if (!ignoreViewportUpdate) { this.providers.dimension .updateSizesPositionByNewDataIndexes(type, newItems, prevItems); } } } // refresh columns to redraw column headers and show correct icon columnTypes.forEach((type) => { this.providers.column.dataSources[type].refresh(); }); this.emit('aftersortingapply'); } }