Skip to content

Commit

Permalink
feat(plugins): add extra callback methods to checkbox selector (#570)
Browse files Browse the repository at this point in the history
- for our use case, we wanted to expand the full Tree before executing the full row selections, these new callbacks allow to do that and more
  • Loading branch information
ghiscoding authored Dec 9, 2021
1 parent bf0c912 commit a9245f9
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,8 @@ export class Example5 {
// checkboxSelector: {
// hideInFilterHeaderRow: false,
// hideInColumnTitleRow: true,
// onRowToggleStart: (e, args) => console.log('onBeforeRowToggle', args),
// onSelectAllToggleStart: () => this.sgb.treeDataService.toggleTreeDataCollapse(false, false),
// },
enableTreeData: true, // you must enable this flag for the filtering & sorting to work as expected
treeDataOptions: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'jest-extended';
import { SlickCheckboxSelectColumn } from '../slickCheckboxSelectColumn';
import { Column, OnSelectedRowsChangedEventArgs, SlickGrid, SlickNamespace, } from '../../interfaces/index';
import { SlickRowSelectionModel } from '../../extensions/slickRowSelectionModel';
Expand Down Expand Up @@ -173,10 +174,12 @@ describe('SlickCheckboxSelectColumn Plugin', () => {
.mockReturnValueOnce({ firstName: 'Jane', lastName: 'Doe', age: 28 })
.mockReturnValueOnce({ __group: true, __groupTotals: { age: { sum: 58 } } });
const setSelectedRowSpy = jest.spyOn(gridStub, 'setSelectedRows');
const onToggleEndMock = jest.fn();
const onToggleStartMock = jest.fn();

plugin.selectedRowsLookup = { 1: false, 2: true };
plugin.init(gridStub);
plugin.setOptions({ hideInColumnTitleRow: false, hideInFilterHeaderRow: true, hideSelectAllCheckbox: false, });
plugin.setOptions({ hideInColumnTitleRow: false, hideInFilterHeaderRow: true, hideSelectAllCheckbox: false, onSelectAllToggleStart: onToggleStartMock, onSelectAllToggleEnd: onToggleEndMock });

const checkboxElm = document.createElement('input');
checkboxElm.type = 'checkbox';
Expand All @@ -190,6 +193,9 @@ describe('SlickCheckboxSelectColumn Plugin', () => {
expect(stopPropagationSpy).toHaveBeenCalled();
expect(stopImmediatePropagationSpy).toHaveBeenCalled();
expect(setSelectedRowSpy).toHaveBeenCalledWith([0, 1, 2], 'click.selectAll');
expect(onToggleStartMock).toHaveBeenCalledWith(expect.anything(), { caller: 'click.selectAll', previousSelectedRows: undefined, });
expect(onToggleEndMock).toHaveBeenCalledWith(expect.anything(), { caller: 'click.selectAll', previousSelectedRows: undefined, rows: [0, 2] });

});

it('should create the plugin and call "setOptions" and expect options changed and hide both Select All toggle when setting "hideSelectAllCheckbox: false" and "hideInColumnTitleRow: true"', () => {
Expand Down Expand Up @@ -336,7 +342,7 @@ describe('SlickCheckboxSelectColumn Plugin', () => {
inputCheckboxElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true }));

expect(inputCheckboxElm).toBeTruthy();
expect(setSelectedRowSpy).toHaveBeenCalledWith([], 'click.selectAll');
expect(setSelectedRowSpy).toHaveBeenCalledWith([], 'click.unselectAll');
});

it('should call the "create" method and expect plugin to be created with checkbox column to be created at position 0 when using default', () => {
Expand Down Expand Up @@ -405,9 +411,48 @@ describe('SlickCheckboxSelectColumn Plugin', () => {
});

it('should trigger "onClick" event and expect toggleRowSelection to be called', () => {
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelection');
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelectionWithEvent');

plugin.init(gridStub);
const checkboxElm = document.createElement('input');
checkboxElm.type = 'checkbox';
const clickEvent = addJQueryEventPropagation(new Event('click'), '', '', checkboxElm);
const stopPropagationSpy = jest.spyOn(clickEvent, 'stopPropagation');
const stopImmediatePropagationSpy = jest.spyOn(clickEvent, 'stopImmediatePropagation');
gridStub.onClick.notify({ cell: 0, row: 2, grid: gridStub }, clickEvent);

expect(plugin).toBeTruthy();
expect(toggleRowSpy).toHaveBeenCalledWith(expect.anything(), 2);
expect(stopPropagationSpy).toHaveBeenCalled();
expect(stopImmediatePropagationSpy).toHaveBeenCalled();
});

it('should trigger "onClick" event and expect toggleRowSelection and "onRowToggleStart" be called when defined', () => {
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelectionWithEvent');
const onToggleStartMock = jest.fn();

plugin.init(gridStub);
plugin.setOptions({ onRowToggleStart: onToggleStartMock });
const checkboxElm = document.createElement('input');
checkboxElm.type = 'checkbox';
const clickEvent = addJQueryEventPropagation(new Event('click'), '', '', checkboxElm);
const stopPropagationSpy = jest.spyOn(clickEvent, 'stopPropagation');
const stopImmediatePropagationSpy = jest.spyOn(clickEvent, 'stopImmediatePropagation');
gridStub.onClick.notify({ cell: 0, row: 2, grid: gridStub }, clickEvent);

expect(plugin).toBeTruthy();
expect(onToggleStartMock).toHaveBeenCalledWith(expect.anything(), { previousSelectedRows: [1, 2], row: 2, });
expect(toggleRowSpy).toHaveBeenCalledWith(expect.anything(), 2);
expect(stopPropagationSpy).toHaveBeenCalled();
expect(stopImmediatePropagationSpy).toHaveBeenCalled();
});

it('should trigger "onClick" event and expect toggleRowSelection and "onRowToggleEnd" be called when defined', () => {
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelectionWithEvent');
const onToggleEndMock = jest.fn();

plugin.init(gridStub);
plugin.setOptions({ onRowToggleEnd: onToggleEndMock });
const checkboxElm = document.createElement('input');
checkboxElm.type = 'checkbox';
const clickEvent = addJQueryEventPropagation(new Event('click'), '', '', checkboxElm);
Expand All @@ -416,13 +461,14 @@ describe('SlickCheckboxSelectColumn Plugin', () => {
gridStub.onClick.notify({ cell: 0, row: 2, grid: gridStub }, clickEvent);

expect(plugin).toBeTruthy();
expect(toggleRowSpy).toHaveBeenCalledWith(2);
expect(onToggleEndMock).toHaveBeenCalledWith(expect.anything(), { previousSelectedRows: [1, 2], row: 2, });
expect(toggleRowSpy).toHaveBeenCalledWith(expect.anything(), 2);
expect(stopPropagationSpy).toHaveBeenCalled();
expect(stopImmediatePropagationSpy).toHaveBeenCalled();
});

it('should trigger "onClick" event and NOT expect toggleRowSelection to be called when editor "isActive" returns True and "commitCurrentEdit" returns False', () => {
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelection');
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelectionWithEvent');
jest.spyOn(gridStub.getEditorLock(), 'isActive').mockReturnValue(true);
jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit').mockReturnValue(false);

Expand All @@ -441,7 +487,7 @@ describe('SlickCheckboxSelectColumn Plugin', () => {
});

it('should trigger "onKeyDown" event and expect toggleRowSelection to be called when editor "isActive" returns False', () => {
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelection');
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelectionWithEvent');
jest.spyOn(gridStub.getEditorLock(), 'isActive').mockReturnValue(false);

plugin.init(gridStub);
Expand All @@ -453,13 +499,13 @@ describe('SlickCheckboxSelectColumn Plugin', () => {
gridStub.onKeyDown.notify({ cell: 0, row: 2, grid: gridStub }, keyboardEvent);

expect(plugin).toBeTruthy();
expect(toggleRowSpy).toHaveBeenCalledWith(2);
expect(toggleRowSpy).toHaveBeenCalledWith(expect.anything(), 2);
expect(preventDefaultSpy).toHaveBeenCalled();
expect(stopImmediatePropagationSpy).toHaveBeenCalled();
});

it('should trigger "onKeyDown" event and expect toggleRowSelection to be called when editor "commitCurrentEdit" returns True', () => {
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelection');
const toggleRowSpy = jest.spyOn(plugin, 'toggleRowSelectionWithEvent');
jest.spyOn(gridStub.getEditorLock(), 'commitCurrentEdit').mockReturnValue(true);

plugin.init(gridStub);
Expand All @@ -471,7 +517,7 @@ describe('SlickCheckboxSelectColumn Plugin', () => {
gridStub.onKeyDown.notify({ cell: 0, row: 2, grid: gridStub }, keyboardEvent);

expect(plugin).toBeTruthy();
expect(toggleRowSpy).toHaveBeenCalledWith(2);
expect(toggleRowSpy).toHaveBeenCalledWith(expect.anything(), 2);
expect(preventDefaultSpy).toHaveBeenCalled();
expect(stopImmediatePropagationSpy).toHaveBeenCalled();
});
Expand Down
65 changes: 55 additions & 10 deletions packages/common/src/extensions/slickCheckboxSelectColumn.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { KeyCode } from '../enums/keyCode.enum';
import { CheckboxSelectorOption, Column, GridOption, SelectableOverrideCallback, SlickEventData, SlickEventHandler, SlickGrid, SlickNamespace } from '../interfaces/index';
import { CheckboxSelectorOption, Column, DOMMouseEvent, GridOption, SelectableOverrideCallback, SlickEventData, SlickEventHandler, SlickGrid, SlickNamespace } from '../interfaces/index';
import { SlickRowSelectionModel } from './slickRowSelectionModel';
import { createDomElement, emptyElement } from '../services/domUtilities';
import { BindingEventService } from '../services/bindingEvent.service';
Expand Down Expand Up @@ -200,15 +200,40 @@ export class SlickCheckboxSelectColumn<T = any> {
}
}

/**
* Toggle a row selection by providing a row number
* @param {Number} row - grid row number to toggle
*/
toggleRowSelection(row: number) {
this.toggleRowSelectionWithEvent(null, row);
}

/**
* Toggle a row selection and also provide the event that triggered it
* @param {Object} event - event that triggered the row selection change
* @param {Number} row - grid row number to toggle
* @returns
*/
toggleRowSelectionWithEvent(event: Event | null, row: number) {
const dataContext = this._grid.getDataItem(row);
if (!this.checkSelectableOverride(row, dataContext, this._grid)) {
return;
}

// user can optionally execute a callback defined in its grid options prior to toggling the row
const previousSelectedRows = this._grid.getSelectedRows();
if (this._addonOptions.onRowToggleStart) {
this._addonOptions.onRowToggleStart(event, { row, previousSelectedRows });
}

const newSelectedRows = this._selectedRowsLookup[row] ? this._grid.getSelectedRows().filter((n) => n !== row) : this._grid.getSelectedRows().concat(row);
this._grid.setSelectedRows(newSelectedRows, 'click.toggle');
this._grid.setActiveCell(row, this.getCheckboxColumnCellIndex());

// user can optionally execute a callback defined in its grid options after the row toggle is completed
if (this._addonOptions.onRowToggleEnd) {
this._addonOptions.onRowToggleEnd(event, { row, previousSelectedRows });
}
}


Expand Down Expand Up @@ -241,7 +266,7 @@ export class SlickCheckboxSelectColumn<T = any> {
args.node.appendChild(spanElm);
this._headerRowNode = args.node;

this._bindEventService.bind(spanElm, 'click', ((evnt: Event) => this.handleHeaderClick(evnt, args)) as EventListener);
this._bindEventService.bind(spanElm, 'click', ((e: DOMMouseEvent<HTMLInputElement>) => this.handleHeaderClick(e, args)) as EventListener);
}
});
}
Expand Down Expand Up @@ -278,7 +303,7 @@ export class SlickCheckboxSelectColumn<T = any> {
return this._checkboxColumnCellIndex;
}

protected handleClick(e: any, args: any) {
protected handleClick(e: DOMMouseEvent<HTMLInputElement>, args: { row: number; cell: number; grid: SlickGrid; }) {
// clicking on a row select checkbox
if (this._grid.getColumns()[args.cell].id === this._addonOptions.columnId && e.target.type === 'checkbox') {
// if editing, try to commit
Expand All @@ -288,13 +313,13 @@ export class SlickCheckboxSelectColumn<T = any> {
return;
}

this.toggleRowSelection(args.row);
this.toggleRowSelectionWithEvent(e, args.row);
e.stopPropagation();
e.stopImmediatePropagation();
}
}

protected handleHeaderClick(e: any, args: any) {
protected handleHeaderClick(e: DOMMouseEvent<HTMLInputElement>, args: { column: Column; node: HTMLDivElement; grid: SlickGrid; }) {
if (args.column.id === this._addonOptions.columnId && e.target.type === 'checkbox') {
// if editing, try to commit
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
Expand All @@ -303,7 +328,20 @@ export class SlickCheckboxSelectColumn<T = any> {
return;
}

if (e.target.checked) {
// who called the selection?
const isExecutingSelectAll = e.target.checked;
const caller = isExecutingSelectAll ? 'click.selectAll' : 'click.unselectAll';

// trigger event before the real selection so that we have an event before & the next one after the change
const previousSelectedRows = this._grid.getSelectedRows();

// user can optionally execute a callback defined in its grid options prior to the Select All toggling
if (this._addonOptions.onSelectAllToggleStart) {
this._addonOptions.onSelectAllToggleStart(e, { previousSelectedRows, caller });
}

let newSelectedRows: number[] = []; // when unselecting all, the array will become empty
if (isExecutingSelectAll) {
const rows = [];
for (let i = 0; i < this._grid.getDataLength(); i++) {
// Get the row and check it's a selectable row before pushing it onto the stack
Expand All @@ -312,10 +350,17 @@ export class SlickCheckboxSelectColumn<T = any> {
rows.push(i);
}
}
this._grid.setSelectedRows(rows, 'click.selectAll');
} else {
this._grid.setSelectedRows([], 'click.selectAll');
newSelectedRows = rows;
}

// we finally need to call the actual row selection from SlickGrid method
this._grid.setSelectedRows(newSelectedRows, caller);

// user can optionally execute a callback defined in its grid options after the Select All toggling is completed
if (this._addonOptions.onSelectAllToggleEnd) {
this._addonOptions.onSelectAllToggleEnd(e, { rows: newSelectedRows, previousSelectedRows, caller });
}

e.stopPropagation();
e.stopImmediatePropagation();
}
Expand All @@ -326,7 +371,7 @@ export class SlickCheckboxSelectColumn<T = any> {
if (this._grid.getColumns()[args.cell].id === this._addonOptions.columnId) {
// if editing, try to commit
if (!this._grid.getEditorLock().isActive() || this._grid.getEditorLock().commitCurrentEdit()) {
this.toggleRowSelection(args.row);
this.toggleRowSelectionWithEvent(e, args.row);
}
e.preventDefault();
e.stopImmediatePropagation();
Expand Down
15 changes: 15 additions & 0 deletions packages/common/src/interfaces/checkboxSelectorOption.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,19 @@ export interface CheckboxSelectorOption {

/** Override the logic for showing (or not) the expand icon (use case example: only every 2nd row is expandable) */
selectableOverride?: UsabilityOverrideFn;

/** Optional callback method to be executed when the row checkbox gets clicked but prior to the actual toggling itself. */
onRowToggleStart?: (e: Event | null, args: { row: number; previousSelectedRows: number[]; }) => void;

/** Optional callback method to be executed after the row checkbox toggle is completed. */
onRowToggleEnd?: (e: Event | null, args: { row: number; previousSelectedRows: number[]; }) => void;

/**
* Optional callback method to be executed when the "Select All" gets clicked but prior to the actual toggling itself.
* For example we could expand all Groups or Tree prior to the selection so that we also have the chance to even include Group/Tree children in the selection.
*/
onSelectAllToggleStart?: (e: Event | null, args: { previousSelectedRows: number[]; caller: 'click.selectAll' | 'click.unselectAll'; }) => void;

/** Optional callback method to be executed when the "Select All" toggled action is completed. */
onSelectAllToggleEnd?: (e: Event | null, args: { rows: number[]; previousSelectedRows: number[]; caller: 'click.selectAll' | 'click.unselectAll'; }) => void;
}
Binary file not shown.

0 comments on commit a9245f9

Please sign in to comment.