Skip to content

Commit 4b62a6b

Browse files
authored
perf(common): improve Select All row selections toggling on large dataset by 9x faster (#1295)
1 parent a4dea00 commit 4b62a6b

3 files changed

Lines changed: 94 additions & 25 deletions

File tree

src/plugins/slick.checkboxselectcolumn.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ export class SlickCheckboxSelectColumn<T = any> implements SlickPlugin {
333333
ids.push(dataviewRowItem[this._dataView.getIdPropertyName() as keyof T] as number | string);
334334
}
335335
}
336-
this._dataView.setSelectedIds(ids, { isRowBeingAdded: isAllSelected });
336+
this._dataView.setSelectedIds(ids, { isRowBeingAdded: isAllSelected, shouldTriggerEvent: false, applyRowSelectionToGrid: false });
337337
}
338338
this._grid.setSelectedRows(rows, caller);
339339
e.stopPropagation();
@@ -460,4 +460,3 @@ if (IIFE_ONLY && window.Slick) {
460460
}
461461
});
462462
}
463-

src/slick.dataview.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export class SlickDataView<TData extends SlickDataItem = any> implements CustomD
120120
protected toggledGroupsByLevel: any[] = [];
121121
protected groupingDelimiter = ':|:';
122122
protected selectedRowIds: DataIdType[] = [];
123+
protected pendingSelectedFilteredIds?: { ids: DataIdType[]; selectedRowIds: DataIdType[] };
123124
protected preSelectedRowIdsChangeFn?: (args?: any) => void;
124125

125126
protected pagesize = 0;
@@ -1285,8 +1286,21 @@ export class SlickDataView<TData extends SlickDataItem = any> implements CustomD
12851286
if (rowIds === false) {
12861287
this.selectedRowIds = [];
12871288
} else {
1288-
if (this.selectedRowIds!.sort().join(',') !== rowIds.sort().join(',')) {
1289-
this.selectedRowIds = rowIds;
1289+
const sortedRowIds = rowIds.every((id, index) => index === 0 || `${rowIds[index - 1]}` <= `${id}`) ? rowIds : rowIds.slice().sort();
1290+
const sortedSelectedRowIds = this.selectedRowIds!.every(
1291+
(id, index) => index === 0 || `${this.selectedRowIds![index - 1]}` <= `${id}`
1292+
)
1293+
? this.selectedRowIds
1294+
: this.selectedRowIds!.slice().sort();
1295+
let selectedRowIdsChanged = this.selectedRowIds!.length !== sortedRowIds.length;
1296+
if (!selectedRowIdsChanged) {
1297+
const selectedRowIdsSet = new Set(this.selectedRowIds);
1298+
selectedRowIdsChanged = sortedRowIds.some((id) => !selectedRowIdsSet.has(id));
1299+
}
1300+
if (selectedRowIdsChanged) {
1301+
this.selectedRowIds = sortedRowIds;
1302+
} else if (sortedSelectedRowIds !== this.selectedRowIds) {
1303+
this.selectedRowIds = sortedSelectedRowIds;
12901304
}
12911305
}
12921306
};
@@ -1313,7 +1327,7 @@ export class SlickDataView<TData extends SlickDataItem = any> implements CustomD
13131327
}
13141328
};
13151329

1316-
grid.onSelectedRowsChanged.subscribe((_e: SlickEventData_, args: { rows: number[]; }) => {
1330+
grid.onSelectedRowsChanged.subscribe((_e: SlickEventData_, args: { rows: number[]; caller?: string; }) => {
13171331
if (!inHandler) {
13181332
const newSelectedRowIds = this.mapRowsToIds(args.rows);
13191333
const selectedRowsChangedArgs = {
@@ -1324,9 +1338,16 @@ export class SlickDataView<TData extends SlickDataItem = any> implements CustomD
13241338
dataView: this
13251339
};
13261340
this.preSelectedRowIdsChangeFn!(selectedRowsChangedArgs);
1341+
const isBulkSelection = args.caller === 'click.selectAll' || args.caller === 'click.unselectAll';
1342+
const pendingSelectedFilteredIds = this.pendingSelectedFilteredIds;
1343+
const filteredIds =
1344+
isBulkSelection && pendingSelectedFilteredIds?.selectedRowIds === this.selectedRowIds
1345+
? pendingSelectedFilteredIds.ids
1346+
: (this.getAllSelectedFilteredIds() as DataIdType[]);
1347+
this.pendingSelectedFilteredIds = undefined;
13271348
this.onSelectedRowIdsChanged.notify(Object.assign(selectedRowsChangedArgs, {
13281349
selectedRowIds: this.selectedRowIds,
1329-
filteredIds: this.getAllSelectedFilteredIds() as DataIdType[],
1350+
filteredIds,
13301351
}), new SlickEventData(), this);
13311352
}
13321353
});
@@ -1413,6 +1434,10 @@ export class SlickDataView<TData extends SlickDataItem = any> implements CustomD
14131434
};
14141435
this.preSelectedRowIdsChangeFn?.(selectedRowsChangedArgs);
14151436

1437+
if (shouldTriggerEvent === false && applyRowSelectionToGrid === false) {
1438+
this.pendingSelectedFilteredIds = { ids: isRowBeingAdded ? selectedIds.slice() : [], selectedRowIds: this.selectedRowIds };
1439+
}
1440+
14161441
if (shouldTriggerEvent !== false) {
14171442
this.onSelectedRowIdsChanged.notify(Object.assign(selectedRowsChangedArgs, {
14181443
selectedRowIds: this.selectedRowIds,

src/slick.grid.ts

Lines changed: 64 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4217,7 +4217,7 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
42174217
throw new Error('SlickGrid Selection model is not set');
42184218
}
42194219
if (this && this.getEditorLock && !this.getEditorLock()?.isActive()) {
4220-
this.selectionModel.setSelectedRanges(this.rowsToRanges(rows), caller || 'SlickGrid.setSelectedRows');
4220+
this.selectionModel.setSelectedRanges(this.rowsToRanges(rows, caller === 'click.selectAll'), caller || 'SlickGrid.setSelectedRows');
42214221
}
42224222
}
42234223

@@ -4288,7 +4288,10 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
42884288
protected handleSelectedRangesChanged(e: SlickEventData_, ranges: SlickRange_[]) {
42894289
const ne = e.getNativeEvent<CustomEvent>();
42904290
const selectionMode = ne?.detail?.selectionMode ?? '';
4291+
const caller = ne?.detail?.caller ?? 'click';
4292+
const isBulkSelection = caller === 'click.selectAll' || caller === 'click.unselectAll';
42914293
let addDragHandle = !!ne?.detail?.addDragHandle;
4294+
const selectedCellCssClass = this._options.selectedCellCssClass || '';
42924295
const selectionType = this.getSelectionModel()?.getOptions()?.selectionType;
42934296
const showDragHandle = this.getDragHandleVisibility();
42944297
addDragHandle = selectionType === 'cell' || selectionType === 'mixed';
@@ -4336,15 +4339,21 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
43364339

43374340
this.selectedRows = [];
43384341
const hash: CssStyleHash = Object.create(null);
4342+
const selectedRowsSet = ranges.length > 1 ? new Set<number>() : undefined;
4343+
let rangesAreOrdered = true;
43394344
for (let i = 0; i < ranges.length; i++) {
4345+
if (i > 0 && ranges[i - 1].toRow >= ranges[i].fromRow) {
4346+
rangesAreOrdered = false;
4347+
}
43404348
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
4341-
if (!hash[j]) { // prevent duplicates
4349+
if (!selectedRowsSet || !selectedRowsSet.has(j)) {
4350+
selectedRowsSet?.add(j);
43424351
this.selectedRows.push(j);
4343-
hash[j] = Object.create(null);
43444352
}
4353+
const rowHash = this.rowsCache[j] ? (hash[j] ??= Object.create(null)) : undefined;
43454354
for (let k = ranges[i].fromCell; k <= ranges[i].toCell; k++) {
4346-
if (this.canCellBeSelected(j, k)) {
4347-
hash[j][this.columns[k].id] = this._options.selectedCellCssClass;
4355+
if (rowHash && this.canCellBeSelected(j, k)) {
4356+
rowHash[this.columns[k].id] = selectedCellCssClass;
43484357
}
43494358
}
43504359
}
@@ -4356,25 +4365,32 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
43564365
this.selectionRightCell = activeRange.toCell;
43574366
}
43584367

4359-
this.setCellCssStyles(this._options.selectedCellCssClass || '', hash);
4368+
if (!isBulkSelection || !rangesAreOrdered) {
4369+
// Preserve the legacy default sort order (numeric values are compared as strings).
4370+
this.selectedRows.sort();
4371+
}
4372+
4373+
this.setCellCssStyles(selectedCellCssClass, hash);
43604374

43614375
if (this.selectionBottomRow >= 0 && this.selectionRightCell >= 0 && addDragHandle && showDragHandle !== false) {
43624376
const lowerRightCell = this.getCellNode(this.selectionBottomRow, this.selectionRightCell)
43634377
this.dragReplaceEl.createEl(lowerRightCell, showDragHandle);
43644378
}
43654379

4366-
// check if the selected rows have changed (index order isn't important, so we'll sort them both before comparing them)
4367-
if (!this.arrayEquals(previousSelectedRows.sort(), this.selectedRows.sort())) {
4368-
const caller = ne?.detail?.caller ?? 'click';
4369-
// Use Set for faster performance
4370-
const selectedRowsSet = new Set(this.getSelectedRows());
4380+
let selectedRowsChanged = previousSelectedRows.length !== this.selectedRows.length;
4381+
if (!selectedRowsChanged) {
43714382
const previousSelectedRowsSet = new Set(previousSelectedRows);
4372-
4373-
const newSelectedAdditions = Array.from(selectedRowsSet).filter(i => !previousSelectedRowsSet.has(i));
4374-
const newSelectedDeletions = Array.from(previousSelectedRowsSet).filter(i => !selectedRowsSet.has(i));
4383+
selectedRowsChanged = this.selectedRows.some((row) => !previousSelectedRowsSet.has(row));
4384+
}
4385+
if (selectedRowsChanged) {
4386+
const selectedRows = this.getSelectedRows();
4387+
const selectedRowsSet = selectedRows.length ? new Set(selectedRows) : undefined;
4388+
const previousSelectedRowsSet = previousSelectedRows.length ? new Set(previousSelectedRows) : undefined;
4389+
const newSelectedAdditions = previousSelectedRowsSet ? selectedRows.filter((i) => !previousSelectedRowsSet.has(i)) : selectedRows;
4390+
const newSelectedDeletions = selectedRowsSet ? previousSelectedRows.filter((i) => !selectedRowsSet.has(i)) : previousSelectedRows;
43754391

43764392
this.trigger(this.onSelectedRowsChanged, {
4377-
rows: this.getSelectedRows(),
4393+
rows: selectedRows,
43784394
previousSelectedRows,
43794395
caller,
43804396
changedSelectedRows: newSelectedAdditions,
@@ -5821,6 +5837,9 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
58215837
if (cellCssClasses) {
58225838
cellCss += ` ${cellCssClasses}`;
58235839
}
5840+
if (this.isCellSelected(row, cell) && !cellCssClasses?.includes(this._options.selectedCellCssClass || '')) {
5841+
cellCss += ` ${this._options.selectedCellCssClass}`;
5842+
}
58245843

58255844
let value: any = null;
58265845
let formatterResult: FormatterResultWithHtml | FormatterResultWithText | HTMLElement | DocumentFragment | string = '';
@@ -8103,6 +8122,14 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
81038122
return this.cellCssClasses[key];
81048123
}
81058124

8125+
protected isCellSelected(row: number, cell: number): boolean {
8126+
return (
8127+
!!this._options.selectedCellCssClass &&
8128+
this.selectedRanges.some((range) => range.contains(row, cell)) &&
8129+
this.canCellBeSelected(row, cell)
8130+
);
8131+
}
8132+
81068133
/**
81078134
* Flashes the cell twice by toggling the CSS class 4 times.
81088135
* @param {Number} row A row index.
@@ -8848,11 +8875,29 @@ export class SlickGrid<TData = any, C extends Column<TData> = Column<TData>, O e
88488875
* @param {number[]} rows - The row indices.
88498876
* @returns {SlickRange_[]} An array of ranges covering the specified rows.
88508877
*/
8851-
protected rowsToRanges(rows: number[]) {
8852-
const ranges: SlickRange_[] = [];
8878+
protected rowsToRanges(rows: number[], compactRows = false) {
88538879
const lastCell = this.columns.length - 1;
8854-
for (let i = 0; i < rows.length; i++) {
8855-
ranges.push(new SlickRange(rows[i], 0, rows[i], lastCell));
8880+
const ranges: SlickRange_[] = [];
8881+
if (!compactRows) {
8882+
rows.forEach((row) => ranges.push(new SlickRange(row, 0, row, lastCell)));
8883+
return ranges;
8884+
}
8885+
8886+
let rangeStart = rows[0];
8887+
let previousRow = rangeStart;
8888+
for (let i = 1; i < rows.length; i++) {
8889+
const row = rows[i];
8890+
if (row <= previousRow) {
8891+
return rows.map((row) => new SlickRange(row, 0, row, lastCell));
8892+
}
8893+
if (row !== previousRow + 1) {
8894+
ranges.push(new SlickRange(rangeStart, 0, previousRow, lastCell));
8895+
rangeStart = row;
8896+
}
8897+
previousRow = row;
8898+
}
8899+
if (rangeStart !== undefined) {
8900+
ranges.push(new SlickRange(rangeStart, 0, previousRow, lastCell));
88568901
}
88578902
return ranges;
88588903
}

0 commit comments

Comments
 (0)