@revolist/revogrid
Version:
Virtual reactive data grid spreadsheet component - RevoGrid.
224 lines (223 loc) • 10.1 kB
JavaScript
/*!
* 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');
}
}