From 038fa3f56f202465f2b40af57e8acf752fe31f60 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Sat, 30 Oct 2021 00:31:09 -0400 Subject: [PATCH] fix: apply fixes & refactoring after testing in Aurelia-Slickgrid --- .../src/examples/example01.ts | 2 +- .../src/examples/example06.ts | 9 +- .../src/examples/example07.ts | 3 + .../src/examples/example11.ts | 2 +- .../src/examples/icons.ts | 1 + .../controls/__tests__/slickGridMenu.spec.ts | 133 ++++--- packages/common/src/controls/slickGridMenu.ts | 351 +++++++++--------- .../common/src/extensions/extensionUtility.ts | 6 +- .../__tests__/treeFormatter.spec.ts | 18 +- .../common/src/formatters/treeFormatter.ts | 2 +- .../interfaces/gridMenuOption.interface.ts | 5 +- .../__tests__/slickContextMenu.spec.ts | 2 +- .../slickGroupItemMetadataProvider.spec.ts | 6 +- .../plugins/__tests__/slickHeaderMenu.spec.ts | 4 + .../src/plugins/menuFromCellBaseClass.ts | 12 +- packages/common/src/plugins/slickCellMenu.ts | 4 +- .../common/src/plugins/slickContextMenu.ts | 45 +-- .../plugins/slickGroupItemMetadataProvider.ts | 3 +- .../common/src/plugins/slickHeaderMenu.ts | 42 ++- .../services/__tests__/filter.service.spec.ts | 6 +- .../services/__tests__/sort.service.spec.ts | 8 +- .../common/src/services/extension.service.ts | 8 +- .../common/src/services/filter.service.ts | 5 +- .../common/src/services/shared.service.ts | 2 +- packages/common/src/services/sort.service.ts | 5 +- .../common/src/styles/material-svg-icons.scss | 5 + 26 files changed, 385 insertions(+), 304 deletions(-) diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example01.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example01.ts index b858e6747..792caa412 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example01.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example01.ts @@ -68,7 +68,7 @@ export class Example1 { onColumnsChanged: (e, args) => console.log('onColumnPickerColumnsChanged - visible columns count', args.visibleColumns.length), }, gridMenu: { - // customItems: [ + // commandItems: [ // { command: 'help', title: 'Help', positionOrder: 70, action: (e, args) => console.log(args) }, // { command: '', divider: true, positionOrder: 72 }, // { command: 'hello', title: 'Hello', positionOrder: 69, action: (e, args) => alert('Hello World'), cssClass: 'red', tooltip: 'Hello World', iconCssClass: 'mdi mdi-close' }, diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example06.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example06.ts index 9ba01a6d5..9c2d0e93e 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example06.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example06.ts @@ -72,6 +72,9 @@ export class Example6 { exportWithFormatter: true, sanitizeDataExport: true }, + gridMenu: { + iconCssClass: 'mdi mdi-dots-grid', + }, registerExternalResources: [new ExcelExportService()], enableFiltering: true, enableTreeData: true, // you must enable this flag for the filtering & sorting to work as expected @@ -149,12 +152,12 @@ export class Example6 { if (data[idx + 1] && data[idx + 1][treeLevelPropName] > data[idx][treeLevelPropName]) { const folderPrefix = ``; if (dataContext.__collapsed) { - return `${spacer} ${folderPrefix} ${prefix} ${value}`; + return `${spacer} ${folderPrefix} ${prefix} ${value}`; } else { - return `${spacer} ${folderPrefix} ${prefix} ${value}`; + return `${spacer} ${folderPrefix} ${prefix} ${value}`; } } else { - return `${spacer} ${prefix} ${value}`; + return `${spacer} ${prefix} ${value}`; } } diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example07.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example07.ts index 6732b3a0a..8f7b1bbe9 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example07.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example07.ts @@ -219,6 +219,9 @@ export class Example7 { container: '.demo-container', rightPadding: 10 }, + gridMenu: { + customTitleKey: 'CUSTOM_COMMANDS', + }, autoEdit: true, autoCommitEdit: true, editable: true, diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts index bb83ed8df..b245c88de 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example11.ts @@ -333,7 +333,7 @@ export class Example11 { }, gridMenu: { hideClearFrozenColumnsCommand: false, - customItems: [ + commandItems: [ { command: 'modal', title: 'Mass Update', diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/icons.ts b/examples/webpack-demo-vanilla-bundle/src/examples/icons.ts index abf573878..4b0b36143 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/icons.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/icons.ts @@ -109,6 +109,7 @@ export class Icons { '.mdi.mdi-database-refresh', '.mdi.mdi-delete', '.mdi.mdi-delete-outline', + '.mdi.mdi-dots-grid', '.mdi.mdi-dots-vertical', '.mdi.mdi-download', '.mdi.mdi-drag', diff --git a/packages/common/src/controls/__tests__/slickGridMenu.spec.ts b/packages/common/src/controls/__tests__/slickGridMenu.spec.ts index e5319926f..049b9c63d 100644 --- a/packages/common/src/controls/__tests__/slickGridMenu.spec.ts +++ b/packages/common/src/controls/__tests__/slickGridMenu.spec.ts @@ -105,8 +105,10 @@ describe('GridMenuControl', () => { toggleFilterCommandKey: 'TOGGLE_FILTER_ROW', togglePreHeaderCommandKey: 'TOGGLE_PRE_HEADER_ROW', }, + commandTitleKey: 'COMMANDS', customTitleKey: 'COMMANDS', customItems: [], + commandItems: [], hideClearAllFiltersCommand: false, hideClearFrozenColumnsCommand: true, hideForceFitButton: false, @@ -166,7 +168,7 @@ describe('GridMenuControl', () => { jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(columnsMock); control = new SlickGridMenu(extensionUtility, filterServiceStub, pubSubServiceStub, sharedService, sortServiceStub); - translateService.use('fr'); + translateService.use('en'); }); afterEach(() => { @@ -513,6 +515,7 @@ describe('GridMenuControl', () => { }); it('should NOT show the Grid Menu when user defines the callback "onBeforeMenuShow" which returns False', () => { + gridOptionsMock.gridMenu.menuUsabilityOverride = () => true; const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); gridOptionsMock.gridMenu.onBeforeMenuShow = () => false; gridOptionsMock.gridMenu.hideForceFitButton = false; @@ -657,7 +660,7 @@ describe('GridMenuControl', () => { const onCommandMock = jest.fn(); const pubSubSpy = jest.spyOn(pubSubServiceStub, 'publish'); - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', action: helpFnMock }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', action: helpFnMock }]; gridOptionsMock.gridMenu.onCommand = onCommandMock; control.columns = columnsMock; control.init(); @@ -683,7 +686,7 @@ describe('GridMenuControl', () => { const helpFnMock = jest.fn(); const onCommandMock = jest.fn(); - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', action: helpFnMock, disabled: true }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', action: helpFnMock, disabled: true }]; gridOptionsMock.gridMenu.onCommand = onCommandMock; control.columns = columnsMock; control.init(); @@ -702,7 +705,7 @@ describe('GridMenuControl', () => { const helpFnMock = jest.fn(); const onCommandMock = jest.fn(); - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', action: helpFnMock, itemUsabilityOverride: () => false }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', action: helpFnMock, itemUsabilityOverride: () => false }]; gridOptionsMock.gridMenu.onCommand = onCommandMock; control.columns = columnsMock; control.init(); @@ -720,7 +723,7 @@ describe('GridMenuControl', () => { const helpFnMock = jest.fn(); const onCommandMock = jest.fn(); - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', action: helpFnMock, itemUsabilityOverride: () => true }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', action: helpFnMock, itemUsabilityOverride: () => true }]; gridOptionsMock.gridMenu.onCommand = onCommandMock; control.columns = columnsMock; control.init(); @@ -741,7 +744,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item and expect item to be hidden from the DOM list when "hidden" is enabled', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', hidden: true }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', hidden: true }]; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -752,7 +755,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item and expect item to NOT be created in the DOM list when "itemVisibilityOverride" callback returns False', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', itemVisibilityOverride: () => false }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', itemVisibilityOverride: () => false }]; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -763,7 +766,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item and expect item to be disabled when "disabled" is set to True', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', disabled: true }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', disabled: true }]; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -774,7 +777,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu "divider" item object and expect a divider to be created', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'divider', divider: true }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'divider', divider: true }]; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -785,7 +788,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu "divider" string and expect a divider to be created', () => { - gridOptionsMock.gridMenu.customItems = ['divider']; + gridOptionsMock.gridMenu.commandItems = ['divider']; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -796,7 +799,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item with "cssClass" and expect all classes to be added to the item in the DOM', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', cssClass: 'text-danger red' }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', cssClass: 'text-danger red' }]; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -810,7 +813,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item with "iconCssClass" and expect an icon to be included on the item DOM element', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', iconCssClass: 'mdi mdi-close' }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', iconCssClass: 'mdi mdi-close' }]; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -827,7 +830,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item with "iconImage" and expect an icon to be included on the item DOM element', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', iconImage: '/images/some-image.png' }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', iconImage: '/images/some-image.png' }]; gridOptionsMock.gridMenu.iconCssClass = undefined; gridOptionsMock.gridMenu.iconImage = '/images/some-image.png'; @@ -847,7 +850,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item with "tooltip" and expect the item title attribute to be part of the item DOM element', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', tooltip: 'some tooltip text' }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', tooltip: 'some tooltip text' }]; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -858,7 +861,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item with "textCssClass" and expect extra css classes added to the item text DOM element', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', textCssClass: 'red bold' }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', textCssClass: 'red bold' }]; control.columns = columnsMock; control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); @@ -873,7 +876,7 @@ describe('GridMenuControl', () => { }); it('should add a custom Grid Menu item and provide a custom title for the custom items list', () => { - gridOptionsMock.gridMenu.customItems = [{ command: 'help', title: 'Help', textCssClass: 'red bold' }]; + gridOptionsMock.gridMenu.commandItems = [{ command: 'help', title: 'Help', textCssClass: 'red bold' }]; control.columns = columnsMock; control.init(); gridOptionsMock.gridMenu.customTitle = 'Custom Title'; @@ -903,14 +906,19 @@ describe('GridMenuControl', () => { }); describe('addGridMenuCustomCommands method', () => { + beforeEach(() => { + translateService.use('fr'); + control.translateGridMenu(); + }); + afterEach(() => { jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); }); - it('should expect an empty "customItems" array when both Filter & Sort are disabled', () => { + it('should expect an empty "commandItems" array when both Filter & Sort are disabled', () => { control.columns = columnsMock; control.init(); - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([]); + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([]); }); it('should expect menu related to "Unfreeze Columns/Rows"', () => { @@ -918,146 +926,159 @@ describe('GridMenuControl', () => { jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-times', title: 'Dégeler les colonnes/rangées', disabled: false, command: 'clear-pinning', positionOrder: 52 }, + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-times', titleKey: 'CLEAR_PINNING', title: 'Dégeler les colonnes/rangées', disabled: false, command: 'clear-pinning', positionOrder: 52 }, ]); }); it('should expect all menu related to Filter when "enableFilering" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-filter text-danger', title: 'Supprimer tous les filtres', disabled: false, command: 'clear-filter', positionOrder: 50 }, - { iconCssClass: 'fa fa-random', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 53 }, - { iconCssClass: 'fa fa-refresh', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 57 } + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-filter text-danger', titleKey: 'CLEAR_ALL_FILTERS', title: 'Supprimer tous les filtres', disabled: false, command: 'clear-filter', positionOrder: 50 }, + { iconCssClass: 'fa fa-random', titleKey: 'TOGGLE_FILTER_ROW', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 53 }, + { iconCssClass: 'fa fa-refresh', titleKey: 'REFRESH_DATASET', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 57 } ]); }); it('should have only 1 menu "clear-filter" when all other menus are defined as hidden & when "enableFilering" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideToggleFilterCommand: true, hideRefreshDatasetCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-filter text-danger', title: 'Supprimer tous les filtres', disabled: false, command: 'clear-filter', positionOrder: 50 } + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-filter text-danger', titleKey: 'CLEAR_ALL_FILTERS', title: 'Supprimer tous les filtres', disabled: false, command: 'clear-filter', positionOrder: 50 } ]); }); it('should have only 1 menu "toggle-filter" when all other menus are defined as hidden & when "enableFilering" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllFiltersCommand: true, hideRefreshDatasetCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-random', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 53 }, + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-random', titleKey: 'TOGGLE_FILTER_ROW', title: 'Basculer la ligne des filtres', disabled: false, command: 'toggle-filter', positionOrder: 53 }, ]); }); it('should have only 1 menu "refresh-dataset" when all other menus are defined as hidden & when "enableFilering" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllFiltersCommand: true, hideToggleFilterCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-refresh', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 57 } + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-refresh', titleKey: 'REFRESH_DATASET', title: 'Rafraîchir les données', disabled: false, command: 'refresh-dataset', positionOrder: 57 } ]); }); it('should have the "toggle-preheader" menu command when "showPreHeaderPanel" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, showPreHeaderPanel: true } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-random', title: 'Basculer la ligne de pré-en-tête', disabled: false, command: 'toggle-preheader', positionOrder: 53 } + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-random', titleKey: 'TOGGLE_PRE_HEADER_ROW', title: 'Basculer la ligne de pré-en-tête', disabled: false, command: 'toggle-preheader', positionOrder: 53 } ]); }); it('should not have the "toggle-preheader" menu command when "showPreHeaderPanel" and "hideTogglePreHeaderCommand" are set', () => { const copyGridOptionsMock = { ...gridOptionsMock, showPreHeaderPanel: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideTogglePreHeaderCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([]); + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([]); }); it('should have the "clear-sorting" menu command when "enableSorting" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableSorting: true } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-unsorted text-danger', title: 'Supprimer tous les tris', disabled: false, command: 'clear-sorting', positionOrder: 51 } + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-unsorted text-danger', titleKey: 'CLEAR_ALL_SORTING', title: 'Supprimer tous les tris', disabled: false, command: 'clear-sorting', positionOrder: 51 } ]); }); it('should not have the "clear-sorting" menu command when "enableSorting" and "hideClearAllSortingCommand" are set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableSorting: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideClearAllSortingCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([]); + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([]); }); it('should have the "export-csv" menu command when "enableTextExport" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-download', title: 'Exporter en format CSV', disabled: false, command: 'export-csv', positionOrder: 54 } + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-download', titleKey: 'EXPORT_TO_CSV', title: 'Exporter en format CSV', disabled: false, command: 'export-csv', positionOrder: 54 } ]); }); it('should not have the "export-csv" menu command when "enableTextExport" and "hideExportCsvCommand" are set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([]); + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([]); }); it('should have the "export-excel" menu command when "enableTextExport" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, enableTextExport: false, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-file-excel-o text-success', title: 'Exporter vers Excel', disabled: false, command: 'export-excel', positionOrder: 55 } + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-file-excel-o text-success', titleKey: 'EXPORT_TO_EXCEL', title: 'Exporter vers Excel', disabled: false, command: 'export-excel', positionOrder: 55 } ]); }); it('should have the "export-text-delimited" menu command when "enableTextExport" is set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([ - { iconCssClass: 'fa fa-download', title: 'Exporter en format texte (délimité par tabulation)', disabled: false, command: 'export-text-delimited', positionOrder: 56 } + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([ + { iconCssClass: 'fa fa-download', titleKey: 'EXPORT_TO_TAB_DELIMITED', title: 'Exporter en format texte (délimité par tabulation)', disabled: false, command: 'export-text-delimited', positionOrder: 56 } ]); }); it('should not have the "export-text-delimited" menu command when "enableTextExport" and "hideExportCsvCommand" are set', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: true, hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.columns = columnsMock; control.init(); control.init(); // calling 2x register to make sure it doesn't duplicate commands - expect(SharedService.prototype.gridOptions.gridMenu!.customItems).toEqual([]); + expect(SharedService.prototype.gridOptions.gridMenu!.commandItems).toEqual([]); }); }); @@ -1073,6 +1094,7 @@ describe('GridMenuControl', () => { const setColumnsSpy = jest.spyOn(gridStub, 'setColumns'); const copyGridOptionsMock = { ...gridOptionsMock, gridMenu: { commandLabels: gridOptionsMock.gridMenu.commandLabels, hideClearFrozenColumnsCommand: false, } } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.init(); control.init(); @@ -1090,6 +1112,7 @@ describe('GridMenuControl', () => { const refreshSpy = jest.spyOn(SharedService.prototype.dataView, 'refresh'); const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.init(); control.init(); @@ -1107,6 +1130,7 @@ describe('GridMenuControl', () => { const refreshSpy = jest.spyOn(SharedService.prototype.dataView, 'refresh'); const copyGridOptionsMock = { ...gridOptionsMock, enableSorting: true, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.init(); control.init(); @@ -1122,6 +1146,7 @@ describe('GridMenuControl', () => { it('should call "exportToExcel" and expect an error thrown when ExcelExportService is not registered prior to calling the method', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([]); control.init(); @@ -1136,6 +1161,7 @@ describe('GridMenuControl', () => { it('should call "exportToFile" with CSV and expect an error thrown when TextExportService is not registered prior to calling the method', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, hideExportCsvCommand: false, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([]); control.init(); @@ -1150,6 +1176,7 @@ describe('GridMenuControl', () => { it('should call "exportToFile" with Text Delimited and expect an error thrown when TextExportService is not registered prior to calling the method', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, hideExportTextDelimitedCommand: false, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([]); control.init(); @@ -1166,6 +1193,7 @@ describe('GridMenuControl', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([excelExportServiceStub]); jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.init(); control.init(); @@ -1182,6 +1210,7 @@ describe('GridMenuControl', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([textExportServiceStub]); jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.init(); control.init(); @@ -1198,6 +1227,7 @@ describe('GridMenuControl', () => { const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, hideExportTextDelimitedCommand: false } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([textExportServiceStub]); jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.init(); control.init(); @@ -1212,6 +1242,7 @@ describe('GridMenuControl', () => { it('should call the grid "setHeaderRowVisibility" method when the command triggered is "toggle-filter"', () => { let copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: false, hideToggleFilterCommand: false } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); const setHeaderSpy = jest.spyOn(gridStub, 'setHeaderRowVisibility'); const scrollSpy = jest.spyOn(gridStub, 'scrollColumnIntoView'); const setColumnSpy = jest.spyOn(gridStub, 'setColumns'); @@ -1228,6 +1259,7 @@ describe('GridMenuControl', () => { copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, showHeaderRow: true, hideToggleFilterCommand: false } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.menuElement.querySelector('.slick-grid-menu-item[data-command=toggle-filter]').dispatchEvent(clickEvent); expect(setHeaderSpy).toHaveBeenCalledWith(false); @@ -1237,6 +1269,7 @@ describe('GridMenuControl', () => { it('should call the grid "setPreHeaderPanelVisibility" method when the command triggered is "toggle-preheader"', () => { let copyGridOptionsMock = { ...gridOptionsMock, showPreHeaderPanel: true, hideTogglePreHeaderCommand: false } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); const gridSpy = jest.spyOn(SharedService.prototype.slickGrid, 'setPreHeaderPanelVisibility'); control.init(); @@ -1249,6 +1282,7 @@ describe('GridMenuControl', () => { copyGridOptionsMock = { ...gridOptionsMock, showPreHeaderPanel: false, hideTogglePreHeaderCommand: false } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.menuElement.querySelector('.slick-grid-menu-item[data-command=toggle-preheader]').dispatchEvent(clickEvent); expect(gridSpy).toHaveBeenCalledWith(true); @@ -1258,6 +1292,7 @@ describe('GridMenuControl', () => { const refreshSpy = jest.spyOn(extensionUtility, 'refreshBackendDataset'); const copyGridOptionsMock = { ...gridOptionsMock, enableFiltering: true, hideHeaderRowAfterPageLoad: false, hideRefreshDatasetCommand: false, } as unknown as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(copyGridOptionsMock); control.init(); control.columns = columnsMock; @@ -1274,11 +1309,11 @@ describe('GridMenuControl', () => { it('should reorder some columns', () => { const columnsUnorderedMock: Column[] = [ { id: 'field2', field: 'field2', name: 'Field 2', width: 75 }, - { id: 'field1', field: 'field1', name: 'Field 1', width: 100, nameKey: 'TITLE' }, + { id: 'field1', field: 'field1', name: 'Titre', width: 100, nameKey: 'TITLE' }, { id: 'field3', field: 'field3', name: 'Field 3', width: 75, columnGroup: 'Billing' }, ]; const columnsMock: Column[] = [ - { id: 'field1', field: 'field1', name: 'Field 1', width: 100, nameKey: 'TITLE' }, + { id: 'field1', field: 'field1', name: 'Titre', width: 100, nameKey: 'TITLE' }, { id: 'field2', field: 'field2', name: 'Field 2', width: 75 }, { id: 'field3', field: 'field3', name: 'Field 3', width: 75, columnGroup: 'Billing' }, ]; @@ -1317,14 +1352,18 @@ describe('GridMenuControl', () => { const translateSpy = jest.spyOn(extensionUtility, 'translateItems'); jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(undefined).mockReturnValue(1); + translateService.use('fr'); gridOptionsMock.gridMenu.hideForceFitButton = false; gridOptionsMock.gridMenu.hideSyncResizeButton = false; gridOptionsMock.syncColumnCellResize = true; gridOptionsMock.forceFitColumns = true; + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock); + control.columns = columnsMock; control.initEventHandlers(); - control.init(); control.translateGridMenu(); + control.init(); const buttonElm = document.querySelector('.slick-grid-menu-button'); buttonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); control.menuElement.querySelector('input[type="checkbox"]').dispatchEvent(new Event('click', { bubbles: true })); diff --git a/packages/common/src/controls/slickGridMenu.ts b/packages/common/src/controls/slickGridMenu.ts index bdbac18a4..c23300838 100644 --- a/packages/common/src/controls/slickGridMenu.ts +++ b/packages/common/src/controls/slickGridMenu.ts @@ -7,6 +7,7 @@ import { GridMenuEventWithElementCallbackArgs, GridMenuItem, GridMenuOption, + GridOption, MenuCommandItem, SlickEventHandler, SlickNamespace, @@ -33,7 +34,7 @@ declare const Slick: SlickNamespace; * enableGridMenu: true, * gridMenu: { * ... grid menu options ... - * customItems: [{ ...command... }, { ...command... }] + * commandItems: [{ ...command... }, { ...command... }] * } * }]; * @class GridMenuControl @@ -45,6 +46,7 @@ export class SlickGridMenu extends MenuBaseClass { protected _columnCheckboxes: HTMLInputElement[] = []; protected _columnTitleElm!: HTMLDivElement; protected _commandMenuElm!: HTMLDivElement; + protected _gridMenuOptions: GridMenu | null = null; protected _gridMenuButtonElm!: HTMLButtonElement; protected _isMenuOpen = false; protected _listElm!: HTMLSpanElement; @@ -89,10 +91,7 @@ export class SlickGridMenu extends MenuBaseClass { } get addonOptions(): GridMenu { - return this.gridOptions.gridMenu || {}; - } - set addonOptions(newOptions: GridMenu) { - this.sharedService.gridOptions.gridMenu = newOptions; + return this._gridMenuOptions || {}; } get columns(): Column[] { @@ -102,6 +101,10 @@ export class SlickGridMenu extends MenuBaseClass { this._columns = newColumns; } + get gridOptions(): GridOption { + return this.grid.getOptions() || {}; + } + initEventHandlers() { // when grid columns are reordered then we also need to update/resync our picker column in the same order const onColumnsReorderedHandler = this.grid.onColumnsReordered; @@ -116,7 +119,6 @@ export class SlickGridMenu extends MenuBaseClass { const onSetOptionsHandler = this.grid.onSetOptions; (this._eventHandler as SlickEventHandler>).subscribe(onSetOptionsHandler, (_e, args) => { if (args && args.optionsBefore && args.optionsAfter) { - this.sharedService.gridOptions = args.optionsAfter; const switchedFromRegularToFrozen = (args.optionsBefore.frozenColumn! >= 0 && args.optionsAfter.frozenColumn === -1); const switchedFromFrozenToRegular = (args.optionsBefore.frozenColumn === -1 && args.optionsAfter.frozenColumn! >= 0); if (switchedFromRegularToFrozen || switchedFromFrozenToRegular) { @@ -131,15 +133,18 @@ export class SlickGridMenu extends MenuBaseClass { this._gridUid = this.grid.getUID() ?? ''; // keep original user grid menu, useful when switching locale to translate - this._userOriginalGridMenu = { ...this.addonOptions }; - this.addonOptions = { ...this._defaults, ...this.getDefaultGridMenuOptions(), ...this.addonOptions }; + this._userOriginalGridMenu = { ...this.sharedService.gridOptions.gridMenu }; + this._gridMenuOptions = { ...this._defaults, ...this.getDefaultGridMenuOptions(), ...this.sharedService.gridOptions.gridMenu }; + this.sharedService.gridOptions.gridMenu = this._gridMenuOptions; // merge original user grid menu items with internal items // then sort all Grid Menu Custom Items (sorted by pointer, no need to use the return) - const originalCustomItems = this._userOriginalGridMenu && Array.isArray(this._userOriginalGridMenu.customItems) ? this._userOriginalGridMenu.customItems : []; - this.addonOptions.customItems = [...originalCustomItems, ...this.addGridMenuCustomCommands(originalCustomItems)]; - this.extensionUtility.translateItems(this.addonOptions.customItems, 'titleKey', 'title'); - this.extensionUtility.sortItems(this.addonOptions.customItems, 'positionOrder'); + const gridMenuCommandItems = this._userOriginalGridMenu.commandItems || this._userOriginalGridMenu.customItems; + const originalCommandItems = this._userOriginalGridMenu && Array.isArray(gridMenuCommandItems) ? gridMenuCommandItems : []; + this._gridMenuOptions.commandItems = [...originalCommandItems, ...this.addGridMenuCustomCommands(originalCommandItems)]; + this.extensionUtility.translateMenuItemsFromTitleKey(this._gridMenuOptions.commandItems || []); + this.extensionUtility.sortItems(this._gridMenuOptions.commandItems, 'positionOrder'); + this._gridMenuOptions.customItems = this._gridMenuOptions.commandItems; // create the Grid Menu DOM element this.createGridMenu(); @@ -178,16 +183,16 @@ export class SlickGridMenu extends MenuBaseClass { createGridMenu() { this._gridUid = this._gridUid ?? this.grid.getUID() ?? ''; const gridUidSelector = this._gridUid ? `.${this._gridUid}` : ''; - const gridMenuWidth = this.addonOptions?.menuWidth ?? this._defaults.menuWidth; + const gridMenuWidth = this._gridMenuOptions?.menuWidth || this._defaults.menuWidth; const headerSide = (this.gridOptions.hasOwnProperty('frozenColumn') && this.gridOptions.frozenColumn! >= 0) ? 'right' : 'left'; this._menuElm = document.querySelector(`${gridUidSelector} .slick-header-${headerSide}`) as HTMLDivElement; - if (this._menuElm) { + if (this._menuElm && this._gridMenuOptions) { // resize the header row to include the hamburger menu icon this._menuElm.style.width = `calc(100% - ${gridMenuWidth}px)`; // if header row is enabled, we also need to resize its width - const enableResizeHeaderRow = (this.addonOptions && this.addonOptions.resizeOnShowHeaderRow !== undefined) ? this.addonOptions.resizeOnShowHeaderRow : this._defaults.resizeOnShowHeaderRow; + const enableResizeHeaderRow = (this._gridMenuOptions && this._gridMenuOptions.resizeOnShowHeaderRow !== undefined) ? this._gridMenuOptions.resizeOnShowHeaderRow : this._defaults.resizeOnShowHeaderRow; if (enableResizeHeaderRow && this.gridOptions.showHeaderRow) { const headerRowElm = document.querySelector(`${gridUidSelector} .slick-headerrow`); if (headerRowElm) { @@ -195,14 +200,14 @@ export class SlickGridMenu extends MenuBaseClass { } } - const showButton = (this.addonOptions?.showButton !== undefined) ? this.addonOptions.showButton : this._defaults.showButton; + const showButton = (this._gridMenuOptions?.showButton !== undefined) ? this._gridMenuOptions.showButton : this._defaults.showButton; if (showButton) { this._gridMenuButtonElm = document.createElement('button'); this._gridMenuButtonElm.className = 'slick-grid-menu-button'; - if (this.addonOptions && this.addonOptions.iconCssClass) { - this._gridMenuButtonElm.classList.add(...this.addonOptions.iconCssClass.split(' ')); + if (this._gridMenuOptions && this._gridMenuOptions.iconCssClass) { + this._gridMenuButtonElm.classList.add(...this._gridMenuOptions.iconCssClass.split(' ')); } else { - const iconImage = (this.addonOptions && this.addonOptions.iconImage) ? this.addonOptions.iconImage : ''; + const iconImage = (this._gridMenuOptions && this._gridMenuOptions.iconImage) ? this._gridMenuOptions.iconImage : ''; const iconImageElm = document.createElement('img'); iconImageElm.src = iconImage; this._gridMenuButtonElm.appendChild(iconImageElm); @@ -210,14 +215,15 @@ export class SlickGridMenu extends MenuBaseClass { this._menuElm.parentElement!.insertBefore(this._gridMenuButtonElm, this._menuElm.parentElement!.firstChild); // show the Grid Menu when hamburger menu is clicked - this.addonOptions.commandTitle = this.addonOptions.customTitle || this.addonOptions.commandTitle; + this._gridMenuOptions.commandTitle = this._gridMenuOptions.customTitle || this._gridMenuOptions.commandTitle; this._bindEventService.bind(this._gridMenuButtonElm, 'click', this.showGridMenu.bind(this) as EventListener); } - this.gridOptions.gridMenu = { ...this._defaults, ...this.addonOptions }; + this.sharedService.gridOptions.gridMenu = { ...this._defaults, ...this._gridMenuOptions }; // localization support for the picker - this.translateTitleLabels(); + this.translateTitleLabels(this._gridMenuOptions); + this.translateTitleLabels(this.sharedService.gridOptions.gridMenu); this._menuElm = document.createElement('div'); this._menuElm.classList.add('slick-grid-menu', this._gridUid); @@ -232,9 +238,9 @@ export class SlickGridMenu extends MenuBaseClass { this.populateCommandOrOptionItems( 'command', - this.addonOptions, + this._gridMenuOptions, this._commandMenuElm, - this.addonOptions?.customItems || [] as any[], + (this._gridMenuOptions?.commandItems || this._gridMenuOptions?.customItems) || [] as any[], { grid: this.grid, menu: this._menuElm, @@ -288,7 +294,7 @@ export class SlickGridMenu extends MenuBaseClass { // execute optional callback method defined by the user, if it returns false then we won't go further neither close the menu this.pubSubService.publish('onGridMenuMenuClose', callbackArgs); - if ((typeof this.addonOptions?.onMenuClose === 'function' && this.addonOptions.onMenuClose(event, callbackArgs) === false) || this.onMenuClose.notify(callbackArgs, null, this) === false) { + if ((typeof this._gridMenuOptions?.onMenuClose === 'function' && this._gridMenuOptions.onMenuClose(event, callbackArgs) === false) || this.onMenuClose.notify(callbackArgs, null, this) === false) { return; } @@ -310,6 +316,7 @@ export class SlickGridMenu extends MenuBaseClass { } } + /** destroy and recreate the Grid Menu in the DOM */ recreateGridMenu() { this.deleteMenu(); this.init(); @@ -373,65 +380,69 @@ export class SlickGridMenu extends MenuBaseClass { emptyElement(this._listElm); emptyElement(this._commandMenuElm); - const callbackArgs = { - grid: this.grid, - menu: this._menuElm, - columns: this.columns, - allColumns: this.getAllColumns(), - visibleColumns: this.getVisibleColumns() - } as GridMenuEventWithElementCallbackArgs; - - const addonOptions: GridMenu = { ...this.addonOptions, ...options }; // merge optional picker option - addonOptions.customTitle = addonOptions.commandTitle; - - this.populateCommandOrOptionItems( - 'command', - this.addonOptions, - this._commandMenuElm, - addonOptions?.customItems || [] as any[], - callbackArgs, - this.handleMenuItemCommandClick , - ); - - updateColumnPickerOrder.call(this); - this._columnCheckboxes = []; - - // run the override function (when defined), if the result is false then we won't go further - if (addonOptions && !this.extensionUtility.runOverrideFunctionWhenExists(addonOptions.menuUsabilityOverride, callbackArgs)) { - return; - } + if (this._gridMenuOptions) { + const callbackArgs = { + grid: this.grid, + menu: this._menuElm, + columns: this.columns, + allColumns: this.getAllColumns(), + visibleColumns: this.getVisibleColumns() + } as GridMenuEventWithElementCallbackArgs; + + const addonOptions: GridMenu = { ...this._gridMenuOptions, ...options }; // merge optional picker option + addonOptions.customTitle = addonOptions.commandTitle; + + this.populateCommandOrOptionItems( + 'command', + addonOptions, + this._commandMenuElm, + (addonOptions?.commandItems || addonOptions?.customItems) || [] as any[], + callbackArgs, + this.handleMenuItemCommandClick , + ); + + updateColumnPickerOrder.call(this); + this._columnCheckboxes = []; - // execute optional callback method defined by the user, if it returns false then we won't go further and not open the grid menu - if (typeof e.stopPropagation === 'function') { - this.pubSubService.publish('onGridMenuBeforeMenuShow', callbackArgs); - if ((typeof addonOptions?.onBeforeMenuShow === 'function' && addonOptions.onBeforeMenuShow(e, callbackArgs) === false) || this.onBeforeMenuShow.notify(callbackArgs, null, this)) { + // run the override function (when defined), if the result is false then we won't go further + if (addonOptions && !this.extensionUtility.runOverrideFunctionWhenExists(addonOptions.menuUsabilityOverride, callbackArgs)) { return; } - } - // load the column & create column picker list - populateColumnPicker.call(this, addonOptions); + // execute optional callback method defined by the user, if it returns false then we won't go further and not open the grid menu + if (typeof e.stopPropagation === 'function') { + this.pubSubService.publish('onGridMenuBeforeMenuShow', callbackArgs); + if ((typeof addonOptions?.onBeforeMenuShow === 'function' && addonOptions.onBeforeMenuShow(e, callbackArgs) === false) || this.onBeforeMenuShow.notify(callbackArgs, null, this)) { + return; + } + } - // calculate the necessary menu height/width and reposition twice because if we do it only once and the grid menu is wider than the original width, - // it will be offset the 1st time we open the menu but if we do it twice then it will be at the correct position every time - this.repositionMenu(e, addonOptions, false); - this.repositionMenu(e, addonOptions, true); + // load the column & create column picker list + populateColumnPicker.call(this, addonOptions); - // execute optional callback method defined by the user - this.pubSubService.publish('onGridMenuAfterMenuShow', callbackArgs); - if (typeof addonOptions?.onAfterMenuShow === 'function') { - addonOptions.onAfterMenuShow(e, callbackArgs); + // calculate the necessary menu height/width and reposition twice because if we do it only once and the grid menu is wider than the original width, + // it will be offset the 1st time we open the menu but if we do it twice then it will be at the correct position every time + this.repositionMenu(e, addonOptions, false); + this.repositionMenu(e, addonOptions, true); + + // execute optional callback method defined by the user + this.pubSubService.publish('onGridMenuAfterMenuShow', callbackArgs); + if (typeof addonOptions?.onAfterMenuShow === 'function') { + addonOptions.onAfterMenuShow(e, callbackArgs); + } + this.onAfterMenuShow.notify(callbackArgs, null, this); } - this.onAfterMenuShow.notify(callbackArgs, null, this); } /** Update the Titles of each sections (command, commandTitle, ...) */ updateAllTitles(options: GridMenuOption) { if (this._commandTitleElm?.textContent && (options.customTitle || options.commandTitle)) { this._commandTitleElm.textContent = (options.customTitle || options.commandTitle) as string; + this._gridMenuOptions!.commandTitle = this._commandTitleElm.textContent; } if (this._columnTitleElm?.textContent && options.columnTitle) { this._columnTitleElm.textContent = options.columnTitle; + this._gridMenuOptions!.columnTitle = options.columnTitle; } } @@ -439,34 +450,40 @@ export class SlickGridMenu extends MenuBaseClass { translateGridMenu() { // update the properties by pointers, that is the only way to get Grid Menu Control to see the new values // we also need to call the control init so that it takes the new Grid object with latest values - if (this.addonOptions) { - this.addonOptions.customItems = []; - this.addonOptions.commandTitle = ''; - this.addonOptions.customTitle = ''; - this.addonOptions.columnTitle = ''; - this.addonOptions.forceFitTitle = ''; - this.addonOptions.syncResizeTitle = ''; + if (this.sharedService.gridOptions.gridMenu) { + this.sharedService.gridOptions.gridMenu.commandItems = []; + this.sharedService.gridOptions.gridMenu.customItems = []; + this.sharedService.gridOptions.gridMenu.commandTitle = ''; + this.sharedService.gridOptions.gridMenu.customTitle = ''; + this.sharedService.gridOptions.gridMenu.columnTitle = ''; + this.sharedService.gridOptions.gridMenu.forceFitTitle = ''; + this.sharedService.gridOptions.gridMenu.syncResizeTitle = ''; // merge original user grid menu items with internal items // then sort all Grid Menu Custom Items (sorted by pointer, no need to use the return) - const originalCustomItems = this._userOriginalGridMenu && Array.isArray(this._userOriginalGridMenu.customItems) ? this._userOriginalGridMenu.customItems : []; - this.addonOptions.customItems = [...originalCustomItems, ...this.addGridMenuCustomCommands(originalCustomItems)]; - this.extensionUtility.translateItems(this.addonOptions.customItems, 'titleKey', 'title'); - this.extensionUtility.sortItems(this.addonOptions.customItems, 'positionOrder'); - this.translateTitleLabels(); + const originalCommandItems = this._userOriginalGridMenu && Array.isArray(this._userOriginalGridMenu.commandItems) ? this._userOriginalGridMenu.commandItems : []; + this.sharedService.gridOptions.gridMenu.commandItems = [...originalCommandItems, ...this.addGridMenuCustomCommands(originalCommandItems)]; + this.extensionUtility.translateMenuItemsFromTitleKey(this._gridMenuOptions?.commandItems || []); + this.extensionUtility.sortItems(this.sharedService.gridOptions.gridMenu.commandItems, 'positionOrder'); + this.translateTitleLabels(this.sharedService.gridOptions.gridMenu); + this.translateTitleLabels(this._gridMenuOptions); // translate all columns (including non-visible) this.extensionUtility.translateItems(this._columns, 'nameKey', 'name'); // update the Titles of each sections (command, commandTitle, ...) - this.updateAllTitles(this.addonOptions); + this.updateAllTitles(this.sharedService.gridOptions.gridMenu); + this.sharedService.gridOptions.gridMenu.customItems = this.sharedService.gridOptions.gridMenu.commandItems; } } - translateTitleLabels() { - this.addonOptions.columnTitle = this.extensionUtility.getPickerTitleOutputString('columnTitle', 'gridMenu'); - this.addonOptions.forceFitTitle = this.extensionUtility.getPickerTitleOutputString('forceFitTitle', 'gridMenu'); - this.addonOptions.syncResizeTitle = this.extensionUtility.getPickerTitleOutputString('syncResizeTitle', 'gridMenu'); + translateTitleLabels(gridMenuOptions: GridMenu | null) { + if (gridMenuOptions) { + gridMenuOptions.commandTitle = this.extensionUtility.getPickerTitleOutputString('commandTitle', 'gridMenu'); + gridMenuOptions.columnTitle = this.extensionUtility.getPickerTitleOutputString('columnTitle', 'gridMenu'); + gridMenuOptions.forceFitTitle = this.extensionUtility.getPickerTitleOutputString('forceFitTitle', 'gridMenu'); + gridMenuOptions.syncResizeTitle = this.extensionUtility.getPickerTitleOutputString('syncResizeTitle', 'gridMenu'); + } } // -- @@ -474,21 +491,21 @@ export class SlickGridMenu extends MenuBaseClass { // ------------------ /** Create Grid Menu with Custom Commands if user has enabled Filters and/or uses a Backend Service (OData, GraphQL) */ - protected addGridMenuCustomCommands(originalCustomItems: Array) { + protected addGridMenuCustomCommands(originalCommandItems: Array) { const backendApi = this.gridOptions.backendServiceApi || null; - const gridMenuCustomItems: Array = []; + const gridMenuCommandItems: Array = []; const gridOptions = this.gridOptions; const translationPrefix = getTranslationPrefix(gridOptions); - const commandLabels = this.addonOptions?.commandLabels; + const commandLabels = this._gridMenuOptions?.commandLabels; // show grid menu: Unfreeze Columns/Rows - if (this.gridOptions && this.addonOptions && !this.addonOptions.hideClearFrozenColumnsCommand) { + if (this.gridOptions && this._gridMenuOptions && !this._gridMenuOptions.hideClearFrozenColumnsCommand) { const commandName = 'clear-pinning'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconClearFrozenColumnsCommand || 'fa fa-times', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.clearFrozenColumnsCommandKey}`, 'TEXT_CLEAR_PINNING', commandLabels?.clearFrozenColumnsCommand), + iconCssClass: this._gridMenuOptions.iconClearFrozenColumnsCommand || 'fa fa-times', + titleKey: `${translationPrefix}${commandLabels?.clearFrozenColumnsCommandKey ?? 'CLEAR_PINNING'}`, disabled: false, command: commandName, positionOrder: 52 @@ -499,13 +516,13 @@ export class SlickGridMenu extends MenuBaseClass { if (this.gridOptions && (this.gridOptions.enableFiltering && !this.sharedService.hideHeaderRowAfterPageLoad)) { // show grid menu: Clear all Filters - if (this.gridOptions && this.addonOptions && !this.addonOptions.hideClearAllFiltersCommand) { + if (this.gridOptions && this._gridMenuOptions && !this._gridMenuOptions.hideClearAllFiltersCommand) { const commandName = 'clear-filter'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconClearAllFiltersCommand || 'fa fa-filter text-danger', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.clearAllFiltersCommandKey}`, 'TEXT_CLEAR_ALL_FILTERS', commandLabels?.clearAllFiltersCommand), + iconCssClass: this._gridMenuOptions.iconClearAllFiltersCommand || 'fa fa-filter text-danger', + titleKey: `${translationPrefix}${commandLabels?.clearAllFiltersCommandKey ?? 'CLEAR_ALL_FILTERS'}`, disabled: false, command: commandName, positionOrder: 50 @@ -515,13 +532,13 @@ export class SlickGridMenu extends MenuBaseClass { } // show grid menu: toggle filter row - if (this.gridOptions && this.addonOptions && !this.addonOptions.hideToggleFilterCommand) { + if (this.gridOptions && this._gridMenuOptions && !this._gridMenuOptions.hideToggleFilterCommand) { const commandName = 'toggle-filter'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconToggleFilterCommand || 'fa fa-random', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.toggleFilterCommandKey}`, 'TEXT_TOGGLE_FILTER_ROW', commandLabels?.toggleFilterCommand), + iconCssClass: this._gridMenuOptions.iconToggleFilterCommand || 'fa fa-random', + titleKey: `${translationPrefix}${commandLabels?.toggleFilterCommandKey ?? 'TOGGLE_FILTER_ROW'}`, disabled: false, command: commandName, positionOrder: 53 @@ -531,13 +548,13 @@ export class SlickGridMenu extends MenuBaseClass { } // show grid menu: refresh dataset - if (backendApi && this.gridOptions && this.addonOptions && !this.addonOptions.hideRefreshDatasetCommand) { + if (backendApi && this.gridOptions && this._gridMenuOptions && !this._gridMenuOptions.hideRefreshDatasetCommand) { const commandName = 'refresh-dataset'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconRefreshDatasetCommand || 'fa fa-refresh', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.refreshDatasetCommandKey}`, 'TEXT_REFRESH_DATASET', commandLabels?.refreshDatasetCommand), + iconCssClass: this._gridMenuOptions.iconRefreshDatasetCommand || 'fa fa-refresh', + titleKey: `${translationPrefix}${commandLabels?.refreshDatasetCommandKey ?? 'REFRESH_DATASET'}`, disabled: false, command: commandName, positionOrder: 57 @@ -549,13 +566,13 @@ export class SlickGridMenu extends MenuBaseClass { if (this.gridOptions.showPreHeaderPanel) { // show grid menu: toggle pre-header row - if (this.gridOptions && this.addonOptions && !this.addonOptions.hideTogglePreHeaderCommand) { + if (this.gridOptions && this._gridMenuOptions && !this._gridMenuOptions.hideTogglePreHeaderCommand) { const commandName = 'toggle-preheader'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconTogglePreHeaderCommand || 'fa fa-random', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.togglePreHeaderCommandKey}`, 'TEXT_TOGGLE_PRE_HEADER_ROW', commandLabels?.togglePreHeaderCommand), + iconCssClass: this._gridMenuOptions.iconTogglePreHeaderCommand || 'fa fa-random', + titleKey: `${translationPrefix}${commandLabels?.togglePreHeaderCommandKey ?? 'TOGGLE_PRE_HEADER_ROW'}`, disabled: false, command: commandName, positionOrder: 53 @@ -567,13 +584,13 @@ export class SlickGridMenu extends MenuBaseClass { if (this.gridOptions.enableSorting) { // show grid menu: Clear all Sorting - if (this.gridOptions && this.addonOptions && !this.addonOptions.hideClearAllSortingCommand) { + if (this.gridOptions && this._gridMenuOptions && !this._gridMenuOptions.hideClearAllSortingCommand) { const commandName = 'clear-sorting'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconClearAllSortingCommand || 'fa fa-unsorted text-danger', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.clearAllSortingCommandKey}`, 'TEXT_CLEAR_ALL_SORTING', commandLabels?.clearAllSortingCommand), + iconCssClass: this._gridMenuOptions.iconClearAllSortingCommand || 'fa fa-unsorted text-danger', + titleKey: `${translationPrefix}${commandLabels?.clearAllSortingCommandKey ?? 'CLEAR_ALL_SORTING'}`, disabled: false, command: commandName, positionOrder: 51 @@ -584,13 +601,13 @@ export class SlickGridMenu extends MenuBaseClass { } // show grid menu: Export to file - if ((this.gridOptions?.enableExport || this.gridOptions?.enableTextExport) && this.addonOptions && !this.addonOptions.hideExportCsvCommand) { + if ((this.gridOptions?.enableExport || this.gridOptions?.enableTextExport) && this._gridMenuOptions && !this._gridMenuOptions.hideExportCsvCommand) { const commandName = 'export-csv'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconExportCsvCommand || 'fa fa-download', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.exportCsvCommandKey}`, 'TEXT_EXPORT_TO_CSV', commandLabels?.exportCsvCommand), + iconCssClass: this._gridMenuOptions.iconExportCsvCommand || 'fa fa-download', + titleKey: `${translationPrefix}${commandLabels?.exportCsvCommandKey ?? 'EXPORT_TO_CSV'}`, disabled: false, command: commandName, positionOrder: 54 @@ -600,13 +617,13 @@ export class SlickGridMenu extends MenuBaseClass { } // show grid menu: Export to Excel - if (this.gridOptions && this.gridOptions.enableExcelExport && this.addonOptions && !this.addonOptions.hideExportExcelCommand) { + if (this.gridOptions && this.gridOptions.enableExcelExport && this._gridMenuOptions && !this._gridMenuOptions.hideExportExcelCommand) { const commandName = 'export-excel'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconExportExcelCommand || 'fa fa-file-excel-o text-success', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.exportExcelCommandKey}`, 'TEXT_EXPORT_TO_EXCEL', commandLabels?.exportExcelCommand), + iconCssClass: this._gridMenuOptions.iconExportExcelCommand || 'fa fa-file-excel-o text-success', + titleKey: `${translationPrefix}${commandLabels?.exportExcelCommandKey ?? 'EXPORT_TO_EXCEL'}`, disabled: false, command: commandName, positionOrder: 55 @@ -616,13 +633,13 @@ export class SlickGridMenu extends MenuBaseClass { } // show grid menu: export to text file as tab delimited - if ((this.gridOptions?.enableExport || this.gridOptions?.enableTextExport) && this.addonOptions && !this.addonOptions.hideExportTextDelimitedCommand) { + if ((this.gridOptions?.enableExport || this.gridOptions?.enableTextExport) && this._gridMenuOptions && !this._gridMenuOptions.hideExportTextDelimitedCommand) { const commandName = 'export-text-delimited'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - gridMenuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + gridMenuCommandItems.push( { - iconCssClass: this.addonOptions.iconExportTextDelimitedCommand || 'fa fa-download', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}${commandLabels?.exportTextDelimitedCommandKey}`, 'TEXT_EXPORT_TO_TAB_DELIMITED', commandLabels?.exportTextDelimitedCommand), + iconCssClass: this._gridMenuOptions.iconExportTextDelimitedCommand || 'fa fa-download', + titleKey: `${translationPrefix}${commandLabels?.exportTextDelimitedCommandKey ?? 'EXPORT_TO_TAB_DELIMITED'}`, disabled: false, command: commandName, positionOrder: 56 @@ -632,14 +649,13 @@ export class SlickGridMenu extends MenuBaseClass { } // add the custom "Commands" title if there are any commands - if (this.gridOptions && this.addonOptions && (Array.isArray(gridMenuCustomItems) && gridMenuCustomItems.length > 0 || (Array.isArray(this.addonOptions.customItems) && this.addonOptions.customItems.length > 0))) { - if (this.addonOptions?.customTitleKey) { - this.addonOptions.customTitle = this.addonOptions.customTitle || this.extensionUtility.getPickerTitleOutputString('customTitle', 'gridMenu'); - } - this.addonOptions.commandTitle = this.addonOptions.customTitle || this.addonOptions.commandTitle || this.extensionUtility.getPickerTitleOutputString('commandTitle', 'gridMenu'); + const commandItems = (this._gridMenuOptions?.commandItems ?? this._gridMenuOptions?.customItems) || []; + if (this.gridOptions && this._gridMenuOptions && (Array.isArray(gridMenuCommandItems) && gridMenuCommandItems.length > 0 || (Array.isArray(commandItems) && commandItems.length > 0))) { + this._gridMenuOptions.commandTitleKey = this._gridMenuOptions?.customTitleKey; + this._gridMenuOptions.commandTitle = this._gridMenuOptions.customTitle || this._gridMenuOptions.commandTitle || this.extensionUtility.getPickerTitleOutputString('commandTitle', 'gridMenu'); } - return gridMenuCustomItems; + return gridMenuCommandItems; } /** @@ -657,10 +673,10 @@ export class SlickGridMenu extends MenuBaseClass { const visibleColumns = [...this.sharedService.visibleColumns]; const newGridOptions = { frozenColumn: -1, frozenRow: -1, frozenBottom: false, enableMouseWheelScrollHandler: false }; this.grid.setOptions(newGridOptions); - this.gridOptions.frozenColumn = newGridOptions.frozenColumn; - this.gridOptions.frozenRow = newGridOptions.frozenRow; - this.gridOptions.frozenBottom = newGridOptions.frozenBottom; - this.gridOptions.enableMouseWheelScrollHandler = newGridOptions.enableMouseWheelScrollHandler; + this.sharedService.gridOptions.frozenColumn = newGridOptions.frozenColumn; + this.sharedService.gridOptions.frozenRow = newGridOptions.frozenRow; + this.sharedService.gridOptions.frozenBottom = newGridOptions.frozenBottom; + this.sharedService.gridOptions.enableMouseWheelScrollHandler = newGridOptions.enableMouseWheelScrollHandler; // SlickGrid seems to be somehow resetting the columns to their original positions, // so let's re-fix them to the position we kept as reference @@ -747,6 +763,7 @@ export class SlickGridMenu extends MenuBaseClass { iconCssClass: 'fa fa-bars', menuWidth: 18, customItems: [], + commandItems: [], hideClearAllFiltersCommand: false, hideRefreshDatasetCommand: false, hideToggleFilterCommand: false, @@ -760,33 +777,35 @@ export class SlickGridMenu extends MenuBaseClass { } } - protected handleMenuItemCommandClick(event: Event, _type: MenuType, item: ExtractMenuType) { - if (item !== 'divider' && (item as MenuCommandItem).command && !item.disabled && !(item as MenuCommandItem).divider) { - const callbackArgs = { - grid: this.grid, - command: (item as MenuCommandItem).command, - item, - allColumns: this.columns, - visibleColumns: this.getVisibleColumns() - } as unknown as GridMenuCommandItemCallbackArgs; - - // execute Grid Menu callback with command, - // we'll also execute optional user defined onCommand callback when provided - this.executeGridMenuInternalCustomCommands(event, callbackArgs); - this.pubSubService.publish('onGridMenuCommand', callbackArgs); - if (typeof this.addonOptions?.onCommand === 'function') { - this.addonOptions.onCommand(event, callbackArgs); - } - this.onCommand.notify(callbackArgs, null, this); + protected handleMenuItemCommandClick(event: Event, _type: MenuType, item: ExtractMenuType): boolean | void { + if (item === 'divider' || (item as MenuCommandItem).command && (item.disabled || (item as MenuCommandItem).divider)) { + return false; + } - // execute action callback when defined - if (typeof item.action === 'function') { - (item as MenuCommandItem).action!.call(this, event, callbackArgs as any); - } + const callbackArgs = { + grid: this.grid, + command: (item as MenuCommandItem).command, + item, + allColumns: this.columns, + visibleColumns: this.getVisibleColumns() + } as unknown as GridMenuCommandItemCallbackArgs; + + // execute Grid Menu callback with command, + // we'll also execute optional user defined onCommand callback when provided + this.executeGridMenuInternalCustomCommands(event, callbackArgs); + this.pubSubService.publish('onGridMenuCommand', callbackArgs); + if (typeof this._gridMenuOptions?.onCommand === 'function') { + this._gridMenuOptions.onCommand(event, callbackArgs); + } + this.onCommand.notify(callbackArgs, null, this); + + // execute action callback when defined + if (typeof item.action === 'function') { + (item as MenuCommandItem).action!.call(this, event, callbackArgs as any); } // does the user want to leave open the Grid Menu after executing a command? - if (!this.addonOptions.leaveOpen && !event.defaultPrevented) { + if (!this._gridMenuOptions?.leaveOpen && !event.defaultPrevented) { this.hideMenu(event); } diff --git a/packages/common/src/extensions/extensionUtility.ts b/packages/common/src/extensions/extensionUtility.ts index 157c3afbe..1f5dd6558 100644 --- a/packages/common/src/extensions/extensionUtility.ts +++ b/packages/common/src/extensions/extensionUtility.ts @@ -1,5 +1,5 @@ import { Constants } from '../constants'; -import { Column, GridOption, Locale, MenuCommandItem, MenuOptionItem, } from '../interfaces/index'; +import { Column, GridMenuItem, GridOption, Locale, MenuCommandItem, MenuOptionItem, } from '../interfaces/index'; import { BackendUtilityService } from '../services/backendUtility.service'; import { SharedService } from '../services/shared.service'; import { TranslaterService } from '../services/translater.service'; @@ -124,7 +124,7 @@ export class ExtensionUtility { translateItems(items: T[], inputKey: string, outputKey: string) { if (Array.isArray(items)) { for (const item of items) { - if ((item as any)[inputKey]) { + if ((item as any).hasOwnProperty(inputKey)) { (item as any)[outputKey] = this.translaterService?.translate?.((item as any)[inputKey]); } } @@ -136,7 +136,7 @@ export class ExtensionUtility { * @param {Array} items - Menu Command Items array * @param {Object} gridOptions - Grid Options */ - translateMenuItemsFromTitleKey(items: Array) { + translateMenuItemsFromTitleKey(items: Array) { const translationPrefix = getTranslationPrefix(this.sharedService.gridOptions); for (const item of items) { if (typeof item === 'object' && item.titleKey) { diff --git a/packages/common/src/formatters/__tests__/treeFormatter.spec.ts b/packages/common/src/formatters/__tests__/treeFormatter.spec.ts index 0f6f5f530..cdfb2526c 100644 --- a/packages/common/src/formatters/__tests__/treeFormatter.spec.ts +++ b/packages/common/src/formatters/__tests__/treeFormatter.spec.ts @@ -50,7 +50,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, dataset[3]['firstName'], {} as Column, dataset[3], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-0', - text: `Barbara` + text: `Barbara` }); }); @@ -58,7 +58,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, dataset[6]['firstName'], {} as Column, dataset[6], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-1', - text: `Bobby` + text: `Bobby` }); }); @@ -66,7 +66,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, dataset[5]['firstName'], {} as Column, dataset[5], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-3', - text: `Sponge` + text: `Sponge` }); }); @@ -74,7 +74,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, dataset[1]['firstName'], {} as Column, dataset[1], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-1', - text: `Jane` + text: `Jane` }); }); @@ -82,7 +82,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, dataset[4]['firstName'], {} as Column, dataset[4], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-0', - text: `Anonymous` + text: `Anonymous` }); }); @@ -98,7 +98,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, { ...dataset[1]['firstName'], indent: 1 }, { field: 'firstName' } as Column, dataset[1], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-1', - text: `Jane` + text: `Jane` }); }); @@ -107,7 +107,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, null, mockColumn as Column, dataset[3], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-0', - text: `Barbara Cane` + text: `Barbara Cane` }); }); @@ -116,7 +116,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, null, mockColumn as Column, dataset[4], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-0', - text: `Anonymous < Doe` + text: `Anonymous < Doe` }); }); @@ -125,7 +125,7 @@ describe('Tree Formatter', () => { const output = treeFormatter(1, 1, null, mockColumn as Column, dataset[3], gridStub); expect(output).toEqual({ addClasses: 'slick-tree-level-0', - text: `444444` + text: `444444` }); }); }); diff --git a/packages/common/src/formatters/treeFormatter.ts b/packages/common/src/formatters/treeFormatter.ts index 9ef7ae297..aed682670 100644 --- a/packages/common/src/formatters/treeFormatter.ts +++ b/packages/common/src/formatters/treeFormatter.ts @@ -39,6 +39,6 @@ export const treeFormatter: Formatter = (row, cell, value, columnDef, dataContex } const sanitizedOutputValue = sanitizeTextByAvailableSanitizer(gridOptions, outputValue, { ADD_ATTR: ['target'] }); const spanToggleClass = `slick-group-toggle ${toggleClass}`.trim(); - const outputHtml = `${indentSpacer}${sanitizedOutputValue}`; + const outputHtml = `${indentSpacer}${sanitizedOutputValue}`; return { addClasses: slickTreeLevelClass, text: outputHtml }; }; diff --git a/packages/common/src/interfaces/gridMenuOption.interface.ts b/packages/common/src/interfaces/gridMenuOption.interface.ts index 5a769c05b..641f005c3 100644 --- a/packages/common/src/interfaces/gridMenuOption.interface.ts +++ b/packages/common/src/interfaces/gridMenuOption.interface.ts @@ -16,10 +16,13 @@ export interface GridMenuOption { /** Same as "commandTitle", except that it's a translation key which can be used on page load and/or when switching locale */ commandTitleKey?: string; + /** Array of Custom Items (title, command, disabled, ...) */ + commandItems?: Array | 'divider'>; + /** Defaults to 0 (auto), minimum width of grid menu content (command, column list) */ contentMinWidth?: number; - /** Array of Custom Items (title, command, disabled, ...) */ + /** @deprecated @use `commandItems` Array of Custom Items (title, command, disabled, ...) */ customItems?: Array | 'divider'>; /** @deprecated @use `commandTitle` Defaults to "Commands" which is the title that shows up over the custom commands list */ diff --git a/packages/common/src/plugins/__tests__/slickContextMenu.spec.ts b/packages/common/src/plugins/__tests__/slickContextMenu.spec.ts index 7f32a78dd..75c6cd6ac 100644 --- a/packages/common/src/plugins/__tests__/slickContextMenu.spec.ts +++ b/packages/common/src/plugins/__tests__/slickContextMenu.spec.ts @@ -717,7 +717,7 @@ describe('ContextMenu Plugin', () => { }); // -- Copy to Clipboard -- // - it('should populate menuCustomItems with Copy cell action when "hideCopyCellValueCommand" is disabled', () => { + it('should populate menuCommandItems with Copy cell action when "hideCopyCellValueCommand" is disabled', () => { const execSpy = jest.spyOn(window.document, 'execCommand'); gridOptionsMock.contextMenu.hideCopyCellValueCommand = false; plugin.dispose(); diff --git a/packages/common/src/plugins/__tests__/slickGroupItemMetadataProvider.spec.ts b/packages/common/src/plugins/__tests__/slickGroupItemMetadataProvider.spec.ts index 1719937b2..c19846b53 100644 --- a/packages/common/src/plugins/__tests__/slickGroupItemMetadataProvider.spec.ts +++ b/packages/common/src/plugins/__tests__/slickGroupItemMetadataProvider.spec.ts @@ -118,19 +118,19 @@ describe('GroupItemMetadataProvider Service', () => { it('should return Grouping info formatted with a group level 0 without indentation when calling "defaultGroupCellFormatter" with option "enableExpandCollapse" set to True', () => { service.setOptions({ enableExpandCollapse: true }); const output = service.getOptions().groupFormatter(0, 0, 'test', mockColumns[0], { title: 'Some Title' }, gridStub); - expect(output).toBe('Some Title'); + expect(output).toBe('Some Title'); }); it('should return Grouping info formatted with a group level 2 with indentation of 30px when calling "defaultGroupCellFormatter" with option "enableExpandCollapse" set to True and level 2', () => { service.setOptions({ enableExpandCollapse: true, toggleCssClass: 'groupy-toggle', toggleExpandedCssClass: 'groupy-expanded' }); const output = service.getOptions().groupFormatter(0, 0, 'test', mockColumns[0], { level: 2, title: 'Some Title' }, gridStub); - expect(output).toBe('Some Title'); + expect(output).toBe('Some Title'); }); it('should return Grouping info formatted with a group level 2 with indentation of 30px when calling "defaultGroupCellFormatter" with option "enableExpandCollapse" set to True and level 2', () => { service.setOptions({ enableExpandCollapse: true, toggleCssClass: 'groupy-toggle', toggleCollapsedCssClass: 'groupy-collapsed' }); const output = service.getOptions().groupFormatter(0, 0, 'test', mockColumns[0], { collapsed: true, level: 3, title: 'Some Title' }, gridStub); - expect(output).toBe('Some Title'); + expect(output).toBe('Some Title'); }); }); diff --git a/packages/common/src/plugins/__tests__/slickHeaderMenu.spec.ts b/packages/common/src/plugins/__tests__/slickHeaderMenu.spec.ts index decdeeee2..3800cd765 100644 --- a/packages/common/src/plugins/__tests__/slickHeaderMenu.spec.ts +++ b/packages/common/src/plugins/__tests__/slickHeaderMenu.spec.ts @@ -290,6 +290,7 @@ describe('HeaderMenu Plugin', () => { plugin.init(); (columnsMock[0].header.menu.items[1] as MenuCommandItem).itemVisibilityOverride = () => true; (columnsMock[0].header.menu.items[1] as MenuCommandItem).itemUsabilityOverride = () => false; + const publishSpy = jest.spyOn(pubSubServiceStub, 'publish'); const eventData = { ...new Slick.EventData(), preventDefault: jest.fn() }; gridStub.onHeaderCellRendered.notify({ column: columnsMock[0], node: headerDiv, grid: gridStub }, eventData, gridStub); @@ -304,6 +305,9 @@ describe('HeaderMenu Plugin', () => { ` )); + + commandElm.dispatchEvent(new Event('click')); + expect(publishSpy).not.toHaveBeenCalledWith('headerMenu:onCommand'); }); it('should populate a Header Menu and a 2nd button is "disabled" and expect button to be disabled', () => { diff --git a/packages/common/src/plugins/menuFromCellBaseClass.ts b/packages/common/src/plugins/menuFromCellBaseClass.ts index 0ad9e2da4..db4717659 100644 --- a/packages/common/src/plugins/menuFromCellBaseClass.ts +++ b/packages/common/src/plugins/menuFromCellBaseClass.ts @@ -42,11 +42,13 @@ export class MenuFromCellBaseClass extends Men const commandItems = this._addonOptions?.commandItems || []; const optionItems = this._addonOptions?.optionItems || []; + let isColumnOptionAllowed = true; + let isColumnCommandAllowed = true; // make sure there's at least something to show before creating the Menu if (this._camelPluginName === 'contextMenu') { - const isColumnOptionAllowed = this.checkIsColumnAllowed((this._addonOptions as ContextMenu)?.optionShownOverColumnIds ?? [], columnDef.id); - const isColumnCommandAllowed = this.checkIsColumnAllowed((this._addonOptions as ContextMenu)?.commandShownOverColumnIds ?? [], columnDef.id); + isColumnOptionAllowed = this.checkIsColumnAllowed((this._addonOptions as ContextMenu)?.optionShownOverColumnIds ?? [], columnDef.id); + isColumnCommandAllowed = this.checkIsColumnAllowed((this._addonOptions as ContextMenu)?.commandShownOverColumnIds ?? [], columnDef.id); if (!columnDef || ((!isColumnCommandAllowed || !commandItems.length) && (!isColumnOptionAllowed || !optionItems.length))) { this.hideMenu(); return; @@ -104,7 +106,7 @@ export class MenuFromCellBaseClass extends Men closeButtonElm.appendChild(closeSpanElm); // -- Option List section - if (!(this.addonOptions as CellMenu | ContextMenu).hideOptionSection && optionItems.length > 0) { + if (!(this.addonOptions as CellMenu | ContextMenu).hideOptionSection && isColumnOptionAllowed && optionItems.length > 0) { const optionMenuElm = document.createElement('div'); optionMenuElm.className = `${this._menuCssPrefix}-option-list`; if (!this.addonOptions.hideCloseButton) { @@ -123,10 +125,10 @@ export class MenuFromCellBaseClass extends Men } // -- Command List section - if (!(this.addonOptions as CellMenu | ContextMenu).hideCommandSection && commandItems.length > 0) { + if (!(this.addonOptions as CellMenu | ContextMenu).hideCommandSection && isColumnCommandAllowed && commandItems.length > 0) { const commandMenuElm = document.createElement('div'); commandMenuElm.className = `${this._menuCssPrefix}-command-list`; - if (!this.addonOptions.hideCloseButton && (optionItems.length === 0 || (this.addonOptions as CellMenu | ContextMenu).hideOptionSection)) { + if (!this.addonOptions.hideCloseButton && (!isColumnOptionAllowed || optionItems.length === 0 || (this.addonOptions as CellMenu | ContextMenu).hideOptionSection)) { this._bindEventService.bind(closeButtonElm, 'click', ((e: DOMMouseEvent) => this.handleCloseButtonClicked(e)) as EventListener); this._menuElm.appendChild(closeButtonElm); } diff --git a/packages/common/src/plugins/slickCellMenu.ts b/packages/common/src/plugins/slickCellMenu.ts index 636d252d0..58370db04 100644 --- a/packages/common/src/plugins/slickCellMenu.ts +++ b/packages/common/src/plugins/slickCellMenu.ts @@ -122,9 +122,7 @@ export class SlickCellMenu extends MenuFromCellBaseClass { this._addonOptions = { ...this._addonOptions, ...columnDef.cellMenu }; // run the override function (when defined), if the result is false it won't go further - if (!args) { - args = {} as MenuCommandItemCallbackArgs; - } + args = args || {}; args.column = columnDef; args.dataContext = dataContext; args.grid = this.grid; diff --git a/packages/common/src/plugins/slickContextMenu.ts b/packages/common/src/plugins/slickContextMenu.ts index a6bbc66f0..fae45d5dc 100644 --- a/packages/common/src/plugins/slickContextMenu.ts +++ b/packages/common/src/plugins/slickContextMenu.ts @@ -119,13 +119,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { const dataContext = this.grid.getDataItem(cell.row); const columnDef = this.grid.getColumns()[cell.cell]; - // merge the contextMenu of the column definition with the default properties - this._addonOptions = { ...this._addonOptions, ...this.sharedService.gridOptions.contextMenu }; - // run the override function (when defined), if the result is false it won't go further - if (!args) { - args = {} as MenuCommandItemCallbackArgs; - } + args = args || {}; args.cell = cell.cell; args.row = cell.row; args.column = columnDef; @@ -158,8 +153,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { // ------------------ /** Create Context Menu with Custom Commands (copy cell value, export) */ - protected addMenuCustomCommands(originalCustomItems: Array) { - const menuCustomItems: Array = []; + protected addMenuCustomCommands(originalCommandItems: Array) { + const menuCommandItems: Array = []; const gridOptions = this.sharedService && this.sharedService.gridOptions || {}; const contextMenu = gridOptions?.contextMenu; const dataView = this.sharedService?.dataView; @@ -168,8 +163,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { // show context menu: Copy (cell value) if (contextMenu && !contextMenu.hideCopyCellValueCommand) { const commandName = 'copy'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - menuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + menuCommandItems.push( { iconCssClass: contextMenu.iconCopyCellValueCommand || 'fa fa-clone', titleKey: `${translationPrefix}COPY`, @@ -201,8 +196,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { // show context menu: Export to file if ((gridOptions?.enableExport || gridOptions?.enableTextExport) && contextMenu && !contextMenu.hideExportCsvCommand) { const commandName = 'export-csv'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - menuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + menuCommandItems.push( { iconCssClass: contextMenu.iconExportCsvCommand || 'fa fa-download', titleKey: `${translationPrefix}EXPORT_TO_CSV`, @@ -229,8 +224,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { // show context menu: Export to Excel if (gridOptions && gridOptions.enableExcelExport && contextMenu && !contextMenu.hideExportExcelCommand) { const commandName = 'export-excel'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - menuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + menuCommandItems.push( { iconCssClass: contextMenu.iconExportExcelCommand || 'fa fa-file-excel-o text-success', titleKey: `${translationPrefix}EXPORT_TO_EXCEL`, @@ -254,8 +249,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { // show context menu: export to text file as tab delimited if ((gridOptions?.enableExport || gridOptions?.enableTextExport) && contextMenu && !contextMenu.hideExportTextDelimitedCommand) { const commandName = 'export-text-delimited'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - menuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + menuCommandItems.push( { iconCssClass: contextMenu.iconExportTextDelimitedCommand || 'fa fa-download', titleKey: `${translationPrefix}EXPORT_TO_TAB_DELIMITED`, @@ -283,14 +278,14 @@ export class SlickContextMenu extends MenuFromCellBaseClass { if (gridOptions && (gridOptions.enableGrouping || gridOptions.enableDraggableGrouping || gridOptions.enableTreeData)) { // add a divider (separator) between the top sort commands and the other clear commands if (contextMenu && !contextMenu.hideCopyCellValueCommand) { - menuCustomItems.push({ divider: true, command: '', positionOrder: 54 }); + menuCommandItems.push({ divider: true, command: '', positionOrder: 54 }); } // show context menu: Clear Grouping (except for Tree Data which shouldn't have this feature) if (gridOptions && !gridOptions.enableTreeData && contextMenu && !contextMenu.hideClearAllGrouping) { const commandName = 'clear-grouping'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - menuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + menuCommandItems.push( { iconCssClass: contextMenu.iconClearGroupingCommand || 'fa fa-times', titleKey: `${translationPrefix}CLEAR_ALL_GROUPING`, @@ -314,8 +309,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { // show context menu: Collapse all Groups if (gridOptions && contextMenu && !contextMenu.hideCollapseAllGroups) { const commandName = 'collapse-all-groups'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - menuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + menuCommandItems.push( { iconCssClass: contextMenu.iconCollapseAllGroupsCommand || 'fa fa-compress', titleKey: `${translationPrefix}COLLAPSE_ALL_GROUPS`, @@ -346,8 +341,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { // show context menu: Expand all Groups if (gridOptions && contextMenu && !contextMenu.hideExpandAllGroups) { const commandName = 'expand-all-groups'; - if (!originalCustomItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { - menuCustomItems.push( + if (!originalCommandItems.some(item => item !== 'divider' && item.hasOwnProperty('command') && item.command === commandName)) { + menuCommandItems.push( { iconCssClass: contextMenu.iconExpandAllGroupsCommand || 'fa fa-expand', titleKey: `${translationPrefix}EXPAND_ALL_GROUPS`, @@ -376,8 +371,8 @@ export class SlickContextMenu extends MenuFromCellBaseClass { } } - this.extensionUtility.translateMenuItemsFromTitleKey(menuCustomItems); - return menuCustomItems; + this.extensionUtility.translateMenuItemsFromTitleKey(menuCommandItems); + return menuCommandItems; } /** diff --git a/packages/common/src/plugins/slickGroupItemMetadataProvider.ts b/packages/common/src/plugins/slickGroupItemMetadataProvider.ts index d89911570..cfe01e2c1 100644 --- a/packages/common/src/plugins/slickGroupItemMetadataProvider.ts +++ b/packages/common/src/plugins/slickGroupItemMetadataProvider.ts @@ -113,8 +113,9 @@ export class SlickGroupItemMetadataProvider { const groupLevel = item.level || 0; const indentation = this._options?.indentation ?? 15; const marginLeft = `${groupLevel * indentation}px`; + const toggleClass = item.collapsed ? this._options.toggleCollapsedCssClass : this._options.toggleExpandedCssClass; - return `` + + return `` + `${item.title || ''}`; } diff --git a/packages/common/src/plugins/slickHeaderMenu.ts b/packages/common/src/plugins/slickHeaderMenu.ts index c416d5b5f..891221ead 100644 --- a/packages/common/src/plugins/slickHeaderMenu.ts +++ b/packages/common/src/plugins/slickHeaderMenu.ts @@ -230,27 +230,29 @@ export class SlickHeaderMenu extends MenuBaseClass { } } - protected handleMenuItemCommandClick(event: DOMEvent, _type: MenuType, item: ExtractMenuType, columnDef?: Column) { - if (item !== 'divider' && (item as MenuCommandItem).command && !item.disabled && !(item as MenuCommandItem | MenuOptionItem).divider) { - const callbackArgs = { - grid: this.grid, - command: (item as MenuCommandItem).command, - column: columnDef, - item, - } as MenuCommandItemCallbackArgs; - - // execute Grid Menu callback with command, - // we'll also execute optional user defined onCommand callback when provided - this.executeHeaderMenuInternalCommands(event, callbackArgs); - this.pubSubService.publish('headerMenu:onCommand', callbackArgs); - if (typeof this.addonOptions?.onCommand === 'function') { - this.addonOptions.onCommand(event, callbackArgs); - } + protected handleMenuItemCommandClick(event: DOMEvent, _type: MenuType, item: ExtractMenuType, columnDef?: Column): boolean | void { + if (item === 'divider' || (item as MenuCommandItem).command && (item.disabled || (item as MenuCommandItem | MenuOptionItem).divider)) { + return false; + } - // execute action callback when defined - if (typeof item.action === 'function') { - (item as MenuCommandItem).action!.call(this, event, callbackArgs); - } + const callbackArgs = { + grid: this.grid, + command: (item as MenuCommandItem).command, + column: columnDef, + item, + } as MenuCommandItemCallbackArgs; + + // execute Grid Menu callback with command, + // we'll also execute optional user defined onCommand callback when provided + this.executeHeaderMenuInternalCommands(event, callbackArgs); + this.pubSubService.publish('headerMenu:onCommand', callbackArgs); + if (typeof this.addonOptions?.onCommand === 'function') { + this.addonOptions.onCommand(event, callbackArgs); + } + + // execute action callback when defined + if (typeof item.action === 'function') { + (item as MenuCommandItem).action!.call(this, event, callbackArgs); } // does the user want to leave open the Grid Menu after executing a command? diff --git a/packages/common/src/services/__tests__/filter.service.spec.ts b/packages/common/src/services/__tests__/filter.service.spec.ts index cbdd92903..236128da0 100644 --- a/packages/common/src/services/__tests__/filter.service.spec.ts +++ b/packages/common/src/services/__tests__/filter.service.spec.ts @@ -50,7 +50,7 @@ const gridOptionMock = { postProcess: jest.fn(), }, gridMenu: { - customItems: [{ + commandItems: [{ command: 'clear-filter', disabled: false, iconCssClass: 'fa fa-filter mdi mdi-filter-remove-outline', @@ -1495,7 +1495,7 @@ describe('FilterService', () => { mockColumns.forEach(col => col.header.menu.items.forEach(item => { expect((item as MenuCommandItem).hidden).toBeTruthy(); })); - gridOptionMock.gridMenu!.customItems!.forEach(item => { + gridOptionMock.gridMenu!.commandItems!.forEach(item => { expect((item as GridMenuItem).hidden).toBeTruthy(); }); expect(setOptionSpy).toHaveBeenCalledWith({ enableFiltering: false }, false, true); @@ -1523,7 +1523,7 @@ describe('FilterService', () => { mockColumns.forEach(col => col.header.menu.items.forEach(item => { expect((item as MenuCommandItem).hidden).toBeFalsy(); })); - gridOptionMock.gridMenu!.customItems!.forEach(item => { + gridOptionMock.gridMenu!.commandItems!.forEach(item => { expect((item as GridMenuItem).hidden).toBeFalsy(); }); expect(setOptionSpy).toHaveBeenCalledWith({ enableFiltering: true }, false, true); diff --git a/packages/common/src/services/__tests__/sort.service.spec.ts b/packages/common/src/services/__tests__/sort.service.spec.ts index b40993f09..6e7aa48cd 100644 --- a/packages/common/src/services/__tests__/sort.service.spec.ts +++ b/packages/common/src/services/__tests__/sort.service.spec.ts @@ -35,7 +35,7 @@ const gridOptionMock = { postProcess: jest.fn(), }, gridMenu: { - customItems: [{ + commandItems: [{ command: 'clear-sorting', disabled: false, hidden: true, @@ -619,7 +619,7 @@ describe('SortService', () => { mockColumns.forEach(col => col.header!.menu!.items.forEach(item => { expect((item as MenuCommandItem).hidden).toBeTruthy(); })); - gridOptionMock.gridMenu!.customItems!.forEach(item => { + gridOptionMock.gridMenu!.commandItems!.forEach(item => { expect((item as GridMenuItem).hidden).toBeTruthy(); }); }); @@ -640,7 +640,7 @@ describe('SortService', () => { mockColumns.forEach(col => col.header!.menu!.items.forEach(item => { expect((item as MenuCommandItem).hidden).toBeTruthy(); })); - gridOptionMock.gridMenu!.customItems!.forEach(item => { + gridOptionMock.gridMenu!.commandItems!.forEach(item => { expect((item as GridMenuItem).hidden).toBeTruthy(); }); }); @@ -659,7 +659,7 @@ describe('SortService', () => { mockColumns.forEach(col => col.header!.menu!.items.forEach(item => { expect((item as MenuCommandItem).hidden).toBeFalsy(); })); - gridOptionMock.gridMenu!.customItems!.forEach(item => { + gridOptionMock.gridMenu!.commandItems!.forEach(item => { expect((item as GridMenuItem).hidden).toBeFalsy(); }); diff --git a/packages/common/src/services/extension.service.ts b/packages/common/src/services/extension.service.ts index f34076d0b..a23ca968c 100644 --- a/packages/common/src/services/extension.service.ts +++ b/packages/common/src/services/extension.service.ts @@ -173,7 +173,10 @@ export class ExtensionService { // Row Selection Plugin // this extension should be registered BEFORE the CheckboxSelector, RowDetail or RowMoveManager since it can be use by these 2 plugins if (!this._rowSelectionModel && (this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector || this.sharedService.gridOptions.enableRowDetailView || this.sharedService.gridOptions.enableRowMoveManager)) { - this._rowSelectionModel = new SlickRowSelectionModel(); + if (!this._rowSelectionModel || !this.sharedService.slickGrid.getSelectionModel()) { + this._rowSelectionModel = new SlickRowSelectionModel(this.sharedService.gridOptions.rowSelectionOptions); + this.sharedService.slickGrid.setSelectionModel(this._rowSelectionModel); + } this._extensionList[ExtensionName.rowSelection] = { name: ExtensionName.rowSelection, class: this._rowSelectionModel, instance: this._rowSelectionModel }; } @@ -446,8 +449,9 @@ export class ExtensionService { } // replace the Grid Menu columns array list - if (this._gridMenuControl) { + if (this.gridOptions.enableGridMenu && this._gridMenuControl) { this._gridMenuControl.columns = this.sharedService.allColumns ?? []; + this._gridMenuControl.recreateGridMenu(); } } diff --git a/packages/common/src/services/filter.service.ts b/packages/common/src/services/filter.service.ts index ab9c067e6..a417821bc 100644 --- a/packages/common/src/services/filter.service.ts +++ b/packages/common/src/services/filter.service.ts @@ -1145,8 +1145,9 @@ export class FilterService { }); // loop through column definition to hide/show grid menu commands - if (this._gridOptions?.gridMenu?.customItems) { - this._gridOptions.gridMenu.customItems.forEach((menuItem) => { + const commandItems = this._gridOptions?.gridMenu?.commandItems ?? this._gridOptions?.gridMenu?.customItems; + if (commandItems) { + commandItems.forEach((menuItem) => { if (menuItem && typeof menuItem !== 'string') { const menuCommand = menuItem.command; if (menuCommand === 'clear-filter' || menuCommand === 'toggle-filter') { diff --git a/packages/common/src/services/shared.service.ts b/packages/common/src/services/shared.service.ts index 1186d89ca..b830a8af8 100644 --- a/packages/common/src/services/shared.service.ts +++ b/packages/common/src/services/shared.service.ts @@ -91,7 +91,7 @@ export class SharedService { /** Getter for the Grid Options pulled through the Grid Object */ get gridOptions(): GridOption { - return this._gridOptions || this._grid?.getOptions && this._grid.getOptions() || {}; + return this._gridOptions || this._grid?.getOptions() || {}; } /** Setter for the Grid Options pulled through the Grid Object */ diff --git a/packages/common/src/services/sort.service.ts b/packages/common/src/services/sort.service.ts index 4811d1c3e..ebef2b340 100644 --- a/packages/common/src/services/sort.service.ts +++ b/packages/common/src/services/sort.service.ts @@ -603,8 +603,9 @@ export class SortService { }); // loop through column definition to hide/show grid menu commands - if (this._gridOptions?.gridMenu?.customItems) { - this._gridOptions.gridMenu.customItems.forEach((menuItem) => { + const commandItems = this._gridOptions?.gridMenu?.commandItems ?? this._gridOptions?.gridMenu?.customItems; + if (commandItems) { + commandItems.forEach((menuItem) => { if (menuItem && typeof menuItem !== 'string') { const menuCommand = menuItem.command; if (menuCommand === 'clear-sorting') { diff --git a/packages/common/src/styles/material-svg-icons.scss b/packages/common/src/styles/material-svg-icons.scss index c1cf61b8b..41f7a8615 100644 --- a/packages/common/src/styles/material-svg-icons.scss +++ b/packages/common/src/styles/material-svg-icons.scss @@ -421,6 +421,11 @@ $icon-height: $icon-width; "M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19M8,9H16V19H8V9M15.5,4L14.5,3H9.5L8.5,4H5V6H19V4H15.5Z", encodecolor($icon-color), $icon-height, $icon-width, inline-block); +@include loadsvg( + ".mdi.mdi-dots-grid", + "M12 16C13.1 16 14 16.9 14 18S13.1 20 12 20 10 19.1 10 18 10.9 16 12 16M12 10C13.1 10 14 10.9 14 12S13.1 14 12 14 10 13.1 10 12 10.9 10 12 10M12 4C13.1 4 14 4.9 14 6S13.1 8 12 8 10 7.1 10 6 10.9 4 12 4M6 16C7.1 16 8 16.9 8 18S7.1 20 6 20 4 19.1 4 18 4.9 16 6 16M6 10C7.1 10 8 10.9 8 12S7.1 14 6 14 4 13.1 4 12 4.9 10 6 10M6 4C7.1 4 8 4.9 8 6S7.1 8 6 8 4 7.1 4 6 4.9 4 6 4M18 16C19.1 16 20 16.9 20 18S19.1 20 18 20 16 19.1 16 18 16.9 16 18 16M18 10C19.1 10 20 10.9 20 12S19.1 14 18 14 16 13.1 16 12 16.9 10 18 10M18 4C19.1 4 20 4.9 20 6S19.1 8 18 8 16 7.1 16 6 16.9 4 18 4Z", + encodecolor($icon-color), $icon-height, $icon-width, inline-block); + @include loadsvg( ".mdi.mdi-dots-vertical", "M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z",