From abe344b025b385630077bfb63d5534a88b3b7d71 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Mon, 15 Jan 2024 14:39:30 -0500 Subject: [PATCH] feat: add `name` option to CheckboxSelectColumn plugin on columDef (#1331) - this will only work when the "Select All" checkbox is NOT shown in the column header titles row (`hideInColumnTitleRow: true`) --- .../src/examples/example04.html | 8 ++- .../src/examples/example04.ts | 17 ++++++ .../slickCheckboxSelectColumn.spec.ts | 45 +++++++++++--- .../extensions/slickCheckboxSelectColumn.ts | 10 +++- .../checkboxSelectorOption.interface.ts | 13 ++++ .../__tests__/extension.service.spec.ts | 36 +++++++++-- .../common/src/services/extension.service.ts | 5 +- test/cypress/e2e/example04.cy.ts | 59 ++++++++++++++++--- 8 files changed, 166 insertions(+), 27 deletions(-) diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example04.html b/examples/vite-demo-vanilla-bundle/src/examples/example04.html index 54fb9bdef..21d7bdd3d 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example04.html +++ b/examples/vite-demo-vanilla-bundle/src/examples/example04.html @@ -35,14 +35,18 @@
Remove Frozen Columns - - + diff --git a/examples/vite-demo-vanilla-bundle/src/examples/example04.ts b/examples/vite-demo-vanilla-bundle/src/examples/example04.ts index ce23fddb3..63c67105a 100644 --- a/examples/vite-demo-vanilla-bundle/src/examples/example04.ts +++ b/examples/vite-demo-vanilla-bundle/src/examples/example04.ts @@ -11,6 +11,7 @@ import { type GridOption, OperatorType, type SlickDataView, + type SlickCheckboxSelectColumn, } from '@slickgrid-universal/common'; import { BindingEventService } from '@slickgrid-universal/binding'; import { ExcelExportService } from '@slickgrid-universal/excel-export'; @@ -61,6 +62,8 @@ export default class Example04 { frozenRowCount = 3; isFrozenBottom = false; sgb: SlickVanillaGridBundle; + checkboxSelectorInstance: SlickCheckboxSelectColumn; + isSelectAllShownAsColumnTitle = false; constructor() { this._bindingEventService = new BindingEventService(); @@ -372,6 +375,12 @@ export default class Example04 { selectActiveRow: false }, enableCheckboxSelector: true, + checkboxSelector: { + hideInColumnTitleRow: !this.isSelectAllShownAsColumnTitle, + hideInFilterHeaderRow: this.isSelectAllShownAsColumnTitle, + name: 'Sel', // column name will only show when `hideInColumnTitleRow` is true + onExtensionRegistered: (instance) => this.checkboxSelectorInstance = instance, + }, enableRowSelection: true, frozenColumn: this.frozenColumnCount, frozenRow: this.frozenRowCount, @@ -546,6 +555,14 @@ export default class Example04 { } } + toggleWhichRowToShowSelectAll() { + this.isSelectAllShownAsColumnTitle = !this.isSelectAllShownAsColumnTitle; + this.checkboxSelectorInstance.setOptions({ + hideInColumnTitleRow: !this.isSelectAllShownAsColumnTitle, + hideInFilterHeaderRow: this.isSelectAllShownAsColumnTitle, + }); + } + executeCommand(_e, args) { // const columnDef = args.column; const command = args.command; diff --git a/packages/common/src/extensions/__tests__/slickCheckboxSelectColumn.spec.ts b/packages/common/src/extensions/__tests__/slickCheckboxSelectColumn.spec.ts index f59475647..fd4da157d 100644 --- a/packages/common/src/extensions/__tests__/slickCheckboxSelectColumn.spec.ts +++ b/packages/common/src/extensions/__tests__/slickCheckboxSelectColumn.spec.ts @@ -64,14 +64,6 @@ const gridStub = { onSelectedRowsChanged: new SlickEvent(), } as unknown as SlickGrid; -const mockAddon = jest.fn().mockImplementation(() => ({ - init: jest.fn(), - dispose: jest.fn(), - getColumnDefinition: jest.fn(), - onBeforeMoveRows: new SlickEvent(), - onMoveRows: new SlickEvent(), -})); - const mockRowSelectionModel = { constructor: jest.fn(), init: jest.fn(), @@ -121,6 +113,7 @@ describe('SlickCheckboxSelectColumn Plugin', () => { cssClass: null, field: '_checkbox_selector', hideSelectAllCheckbox: false, + name: '', toolTip: 'Select/Deselect All', width: 30, hideInColumnTitleRow: false, @@ -144,6 +137,7 @@ describe('SlickCheckboxSelectColumn Plugin', () => { cssClass: 'some-class', field: '_checkbox_selector', hideSelectAllCheckbox: true, + name: '', toolTip: 'Select/Deselect All', width: 30, hideInColumnTitleRow: true, @@ -162,6 +156,17 @@ describe('SlickCheckboxSelectColumn Plugin', () => { expect(updateColHeaderSpy).toHaveBeenCalledWith('_checkbox_selector', '', ''); }); + it('should create the plugin and call "setOptions" and expect options changed and call grid "updateColumnHeader()" when setting "hideInColumnTitleRow: true" and a column "name"', () => { + const colName = 'Selection'; + const updateColHeaderSpy = jest.spyOn(gridStub, 'updateColumnHeader'); + + plugin.init(gridStub); + plugin.setOptions({ hideInColumnTitleRow: true, hideSelectAllCheckbox: false, cssClass: 'some-class', name: colName }); + + expect(plugin).toBeTruthy(); + expect(updateColHeaderSpy).toHaveBeenCalledWith('_checkbox_selector', colName, ''); + }); + it('should create the plugin and call "setOptions" and expect options changed and render the Select All toggle when "hideInColumnTitleRow: false"', () => { const updateColHeaderSpy = jest.spyOn(gridStub, 'updateColumnHeader'); jest.spyOn(gridStub.getEditorLock(), 'isActive').mockReturnValue(true); @@ -463,6 +468,30 @@ describe('SlickCheckboxSelectColumn Plugin', () => { }); }); + it('should add a "name" and "hideSelectAllCheckbox: true" and call the "create" method and expect plugin to be created with a column name and without a checkbox', () => { + const colName = 'Selection'; + plugin.create(mockColumns, { checkboxSelector: { columnIndexPosition: 1, name: colName, hideSelectAllCheckbox: true } }); + + expect(plugin).toBeTruthy(); + expect(mockColumns[1]).toEqual({ + cssClass: null, + excludeFromColumnPicker: true, + excludeFromExport: true, + excludeFromGridMenu: true, + excludeFromHeaderMenu: true, + excludeFromQuery: true, + field: '_checkbox_selector', + formatter: expect.toBeFunction(), + hideSelectAllCheckbox: true, + id: '_checkbox_selector', + name: colName, + resizable: false, + sortable: false, + toolTip: '', + width: 30, + }); + }); + it('should process the "checkboxSelectionFormatter" and expect necessary Formatter to return null when selectableOverride is returning False', () => { plugin.selectableOverride(() => false); plugin.create(mockColumns, {}); diff --git a/packages/common/src/extensions/slickCheckboxSelectColumn.ts b/packages/common/src/extensions/slickCheckboxSelectColumn.ts index 759d85e4d..bee0f635e 100644 --- a/packages/common/src/extensions/slickCheckboxSelectColumn.ts +++ b/packages/common/src/extensions/slickCheckboxSelectColumn.ts @@ -14,6 +14,7 @@ export class SlickCheckboxSelectColumn { cssClass: null, field: '_checkbox_selector', hideSelectAllCheckbox: false, + name: '', toolTip: 'Select/Deselect All', width: 30, applySelectOnAllPages: true, // when that is enabled the "Select All" will be applied to all pages (when using Pagination) @@ -158,6 +159,9 @@ export class SlickCheckboxSelectColumn { this._eventHandler.subscribe(this._grid.onHeaderClick, this.handleHeaderClick.bind(this)); } else { this.hideSelectAllFromColumnHeaderTitleRow(); + if (this._addonOptions.name) { + this._grid.updateColumnHeader(this._addonOptions.columnId || '', this._addonOptions.name, ''); + } } if (!this._addonOptions.hideInFilterHeaderRow) { @@ -203,7 +207,9 @@ export class SlickCheckboxSelectColumn { return { id: columnId, - name: (this._addonOptions.hideSelectAllCheckbox || this._addonOptions.hideInColumnTitleRow) ? '' : ``, + name: (this._addonOptions.hideSelectAllCheckbox || this._addonOptions.hideInColumnTitleRow) + ? this._addonOptions.name || '' + : ``, toolTip: (this._addonOptions.hideSelectAllCheckbox || this._addonOptions.hideInColumnTitleRow) ? '' : this._addonOptions.toolTip, field: columnId, cssClass: this._addonOptions.cssClass, @@ -221,7 +227,7 @@ export class SlickCheckboxSelectColumn { } hideSelectAllFromColumnHeaderTitleRow() { - this._grid.updateColumnHeader(this._addonOptions.columnId || '', '', ''); + this._grid.updateColumnHeader(this._addonOptions.columnId || '', this._addonOptions.name || '', ''); } hideSelectAllFromColumnHeaderFilterRow() { diff --git a/packages/common/src/interfaces/checkboxSelectorOption.interface.ts b/packages/common/src/interfaces/checkboxSelectorOption.interface.ts index a090f8275..687d78c81 100644 --- a/packages/common/src/interfaces/checkboxSelectorOption.interface.ts +++ b/packages/common/src/interfaces/checkboxSelectorOption.interface.ts @@ -1,5 +1,6 @@ import type { SlickEventData } from '../core'; import type { UsabilityOverrideFn } from '../enums/usabilityOverrideFn.type'; +import type { SlickCheckboxSelectColumn } from '../extensions/slickCheckboxSelectColumn'; export interface CheckboxSelectorOption { /** @@ -33,6 +34,12 @@ export interface CheckboxSelectorOption { /** defaults to true, do we want to hide the "Select All" checkbox from the Column Header Filter Row? */ hideInFilterHeaderRow?: boolean; + /** + * defaults to empty string, column name. + * This will only work when the "Select All" checkbox is NOT shown in the column header row (`hideInColumnTitleRow: true`) + */ + name?: string; + /** Defaults to "Select/Deselect All", provide a tooltip that will be shown over the "Select All" checkbox */ toolTip?: string; @@ -42,6 +49,12 @@ export interface CheckboxSelectorOption { /** Override the logic for showing (or not) the expand icon (use case example: only every 2nd row is expandable) */ selectableOverride?: UsabilityOverrideFn; + // -- + // Events + + /** Fired after extension is registered by SlickGrid */ + onExtensionRegistered?: (plugin: SlickCheckboxSelectColumn) => void; + /** Optional callback method to be executed when the row checkbox gets clicked but prior to the actual toggling itself. */ onRowToggleStart?: (e: SlickEventData | Event | null, args: { row: number; previousSelectedRows: number[]; }) => void; diff --git a/packages/common/src/services/__tests__/extension.service.spec.ts b/packages/common/src/services/__tests__/extension.service.spec.ts index 4fa2a069d..2c1cc05a5 100644 --- a/packages/common/src/services/__tests__/extension.service.spec.ts +++ b/packages/common/src/services/__tests__/extension.service.spec.ts @@ -311,7 +311,7 @@ describe('ExtensionService', () => { it('should register the ColumnPicker addon when "enableColumnPicker" is set in the grid options', () => { const gridOptionsMock = { enableColumnPicker: true } as GridOption; - jest.spyOn(extensionColumnPickerStub, 'register').mockReturnValue(instanceMock); + jest.spyOn(extensionColumnPickerStub, 'register').mockReturnValueOnce(instanceMock); const gridSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); service.bindDifferentExtensions(); @@ -382,7 +382,7 @@ describe('ExtensionService', () => { it('should register the CheckboxSelector addon when "enableCheckboxSelector" is set in the grid options', () => { const columnsMock = [{ id: 'field1', field: 'field1', width: 100, cssClass: 'red' }] as Column[]; const gridOptionsMock = { enableCheckboxSelector: true } as GridOption; - const extCreateSpy = jest.spyOn(mockCheckboxSelectColumn, 'create').mockReturnValue(mockCheckboxSelectColumn); + const extCreateSpy = jest.spyOn(mockCheckboxSelectColumn, 'create').mockReturnValueOnce(mockCheckboxSelectColumn); const gridSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); const rowSelectionSpy = jest.spyOn(SlickRowSelectionModel.prototype, 'constructor' as any); @@ -398,6 +398,32 @@ describe('ExtensionService', () => { expect(output).toEqual({ name: ExtensionName.checkboxSelector, instance: mockCheckboxSelectColumn as unknown } as ExtensionModel); }); + it('should call "onExtensionRegistered" when defined in grid option and the CheckboxSelectColumn plugin gets created', () => { + const onRegisteredMock = jest.fn(); + const columnsMock = [{ id: 'field1', field: 'field1', width: 100, cssClass: 'red' }] as Column[]; + const gridOptionsMock = { + enableCheckboxSelector: true, + checkboxSelector: { + onExtensionRegistered: onRegisteredMock + } + } as GridOption; + const extCreateSpy = jest.spyOn(mockCheckboxSelectColumn, 'create').mockReturnValueOnce(mockCheckboxSelectColumn); + const gridSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + const rowSelectionSpy = jest.spyOn(SlickRowSelectionModel.prototype, 'constructor' as any); + + service.createExtensionsBeforeGridCreation(columnsMock, gridOptionsMock); + service.bindDifferentExtensions(); + const rowSelectionInstance = service.getExtensionByName(ExtensionName.rowSelection); + const output = service.getExtensionByName(ExtensionName.checkboxSelector); + + expect(gridSpy).toHaveBeenCalled(); + expect(extCreateSpy).toHaveBeenCalledWith(columnsMock, gridOptionsMock); + expect(rowSelectionInstance).not.toBeNull(); + expect(rowSelectionSpy).toHaveBeenCalledWith({}); + expect(output).toEqual({ name: ExtensionName.checkboxSelector, instance: mockCheckboxSelectColumn as unknown } as ExtensionModel); + expect(onRegisteredMock).toHaveBeenCalledWith(expect.any(Object)); + }); + it('should register the RowMoveManager addon when "enableRowMoveManager" is set in the grid options', () => { const columnsMock = [{ id: 'field1', field: 'field1', width: 100, cssClass: 'red' }] as Column[]; const gridOptionsMock = { enableRowMoveManager: true } as GridOption; @@ -538,7 +564,7 @@ describe('ExtensionService', () => { service.createExtensionsBeforeGridCreation(columnsMock, gridOptionsMock); const instance = service.getCreatedExtensionByName(ExtensionName.draggableGrouping); - service.addExtensionToList(ExtensionName.draggableGrouping, { name: ExtensionName.draggableGrouping, instance }) + service.addExtensionToList(ExtensionName.draggableGrouping, { name: ExtensionName.draggableGrouping, instance }); const instance2 = service.getCreatedExtensionByName(ExtensionName.draggableGrouping); expect(instance).toBeTruthy(); @@ -794,7 +820,7 @@ describe('ExtensionService', () => { ] as Column[]; const instanceMock = { translateColumnPicker: jest.fn() }; const gridOptionsMock = { enableColumnPicker: true } as GridOption; - jest.spyOn(extensionColumnPickerStub, 'register').mockReturnValue(instanceMock); + jest.spyOn(extensionColumnPickerStub, 'register').mockReturnValueOnce(instanceMock); jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(columnsMock); const setColumnsSpy = jest.spyOn(gridStub, 'setColumns'); @@ -813,7 +839,7 @@ describe('ExtensionService', () => { ] as Column[]; const instanceMock = { translateGridMenu: jest.fn() }; const gridOptionsMock = { enableGridMenu: true } as GridOption; - jest.spyOn(extensionGridMenuStub, 'register').mockReturnValue(instanceMock); + jest.spyOn(extensionGridMenuStub, 'register').mockReturnValueOnce(instanceMock); jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(columnsMock); const setColumnsSpy = jest.spyOn(gridStub, 'setColumns'); diff --git a/packages/common/src/services/extension.service.ts b/packages/common/src/services/extension.service.ts index c49083c83..b0798bc16 100644 --- a/packages/common/src/services/extension.service.ts +++ b/packages/common/src/services/extension.service.ts @@ -207,8 +207,11 @@ export class ExtensionService { this._checkboxSelectColumn = this._checkboxSelectColumn || new SlickCheckboxSelectColumn(this.pubSubService, this.gridOptions.checkboxSelector); this._checkboxSelectColumn.init(this.sharedService.slickGrid); const createdExtension = this.getCreatedExtensionByName(ExtensionName.checkboxSelector); // get the instance from when it was really created earlier - const instance = createdExtension && createdExtension.instance; + const instance = createdExtension?.instance; if (instance) { + if (this.gridOptions.checkboxSelector?.onExtensionRegistered) { + this.gridOptions.checkboxSelector.onExtensionRegistered(instance); + } this._extensionList[ExtensionName.checkboxSelector] = { name: ExtensionName.checkboxSelector, instance: this._checkboxSelectColumn }; } } diff --git a/test/cypress/e2e/example04.cy.ts b/test/cypress/e2e/example04.cy.ts index 1581e808f..1ae72e0f3 100644 --- a/test/cypress/e2e/example04.cy.ts +++ b/test/cypress/e2e/example04.cy.ts @@ -1,7 +1,8 @@ describe('Example 04 - Frozen Grid', () => { // NOTE: everywhere there's a * 2 is because we have a top+bottom (frozen rows) containers even after Unfreeze Columns/Rows - const fullTitles = ['', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const withTitleRowTitles = ['Sel', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const withoutTitleRowTitles = ['', 'Title', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; const GRID_ROW_HEIGHT = 45; it('should display Example title', () => { @@ -13,14 +14,14 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid4') .find('.slick-header-columns') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); it('should have exact Column Header Titles in the grid', () => { cy.get('.grid4') .find('.slick-header-columns:nth(0)') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); it('should have a frozen grid with 4 containers on page load with 3 columns on the left and 6 columns on the right', () => { @@ -36,7 +37,7 @@ describe('Example 04 - Frozen Grid', () => { }); it('should hide "Title" column from Grid Menu and expect last frozen column to be "% Complete"', () => { - const newColumnList = ['', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const newColumnList = ['Sel', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; cy.get('.grid4') .find('button.slick-grid-menu-button') @@ -81,7 +82,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid4') .find('.slick-header-columns') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); cy.get('.grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 3 * 2); cy.get('.grid-canvas-right > [style="top: 0px;"]').children().should('have.length', 6 * 2); @@ -94,7 +95,7 @@ describe('Example 04 - Frozen Grid', () => { }); it('should hide "Title" column from Header Menu and expect last frozen column to be "% Complete"', () => { - const newColumnList = ['', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; + const newColumnList = ['Sel', '% Complete', 'Start', 'Finish', 'Completed', 'Cost | Duration', 'City of Origin', 'Action']; cy.get('.grid4') .find('.slick-header-column:nth(1)') @@ -146,7 +147,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid4') .find('.slick-header-columns') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); cy.get('.grid-canvas-left > [style="top: 0px;"]').children().should('have.length', 3 * 2); cy.get('.grid-canvas-right > [style="top: 0px;"]').children().should('have.length', 6 * 2); @@ -176,7 +177,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid4') .find('.slick-header-columns:nth(0)') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); it('should click on the "Set 3 Frozen Columns" button to switch frozen columns grid and expect 3 frozen columns on the left and 4 columns on the right', () => { @@ -198,7 +199,7 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.grid4') .find('.slick-header-columns:nth(0)') .children() - .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); }); it('should click on the Grid Menu command "Unfreeze Columns/Rows" to switch to a regular grid without frozen columns/rows', () => { @@ -516,6 +517,46 @@ describe('Example 04 - Frozen Grid', () => { cy.get('.slick-submenu').should('have.length', 0); }); + it('should toggle Select All checkbox and expect back "Sel" column title to show when Select All checkbox is shown in the header row', () => { + cy.get('.slick-header-column:nth(0)') + .find('.slick-column-name') + .should('contain', 'Sel'); + cy.get('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); + + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)') + .find('.slick-column-name') + .should('not.contain', 'Sel'); + + cy.get('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(withoutTitleRowTitles[index])); + }); + + it('should toggle back Select All checkbox and expect back "Sel" column title to show when Select All checkbox is shown in the header row', () => { + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)') + .find('.slick-column-name') + .should('contain', 'Sel'); + cy.get('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(withTitleRowTitles[index])); + + cy.get('[data-test="toggle-select-all-row"]').click(); + + cy.get('.slick-header-column:nth(0)') + .find('.slick-column-name') + .should('not.contain', 'Sel'); + + cy.get('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(withoutTitleRowTitles[index])); + }); + describe('Test UI rendering after Scrolling with large columns', () => { it('should unfreeze all columns/rows', () => { cy.get('.grid4')