From 2170bb4e3f02ef6f45ad13a1c59730047942651e Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Thu, 9 Sep 2021 00:09:59 -0400 Subject: [PATCH] feat(plugins): move external Context Menu into Slickgrid-Universal --- .../__tests__/columnPickerControl.spec.ts | 6 +- .../__tests__/gridMenuControl.spec.ts | 30 +- .../src/controls/columnPicker.control.ts | 20 +- .../common/src/controls/gridMenu.control.ts | 11 +- .../__tests__/contextMenuExtension.spec.ts | 1112 ------------ .../src/extensions/contextMenuExtension.ts | 464 ----- packages/common/src/extensions/index.ts | 1 - .../src/interfaces/contextMenu.interface.ts | 17 +- .../interfaces/contextMenuOption.interface.ts | 6 + .../src/interfaces/domEvent.interface.ts | 8 +- .../plugins/__tests__/cellMenu.plugin.spec.ts | 13 +- .../__tests__/contextMenu.plugin.spec.ts | 1544 +++++++++++++++++ .../common/src/plugins/baseMenu.plugin.ts | 0 .../common/src/plugins/cellMenu.plugin.ts | 58 +- .../common/src/plugins/contextMenu.plugin.ts | 990 +++++++++++ packages/common/src/plugins/index.ts | 1 + .../__tests__/extension.service.spec.ts | 47 +- packages/common/src/services/domUtilities.ts | 4 + .../common/src/services/extension.service.ts | 22 +- .../components/slick-vanilla-grid-bundle.ts | 4 +- 20 files changed, 2670 insertions(+), 1688 deletions(-) delete mode 100644 packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts delete mode 100644 packages/common/src/extensions/contextMenuExtension.ts create mode 100644 packages/common/src/plugins/__tests__/contextMenu.plugin.spec.ts create mode 100644 packages/common/src/plugins/baseMenu.plugin.ts create mode 100644 packages/common/src/plugins/contextMenu.plugin.ts diff --git a/packages/common/src/controls/__tests__/columnPickerControl.spec.ts b/packages/common/src/controls/__tests__/columnPickerControl.spec.ts index 11dd0ed86..c71d0eef4 100644 --- a/packages/common/src/controls/__tests__/columnPickerControl.spec.ts +++ b/packages/common/src/controls/__tests__/columnPickerControl.spec.ts @@ -103,7 +103,7 @@ describe('ColumnPickerControl', () => { const inputElm = control.menuElement.querySelector('input[type="checkbox"]'); inputElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); expect(setSelectionSpy).toHaveBeenCalledWith(mockRowSelection); expect(control.getAllColumns()).toEqual(columnsMock); expect(control.getVisibleColumns()).toEqual(columnsMock); @@ -121,12 +121,12 @@ describe('ColumnPickerControl', () => { const eventData = { ...new Slick.EventData(), preventDefault: jest.fn() }; gridStub.onHeaderContextMenu.notify({ column: columnsMock[1], grid: gridStub }, eventData, gridStub); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); const bodyElm = document.body; bodyElm.dispatchEvent(new Event('mousedown', { bubbles: true })); - expect(control.menuElement.style.visibility).toBe('hidden'); + expect(control.menuElement.style.display).toBe('none'); }); it('should query an input checkbox change event and expect "readjustFrozenColumnIndexWhenNeeded" method to be called when the grid is detected to be a frozen grid', () => { diff --git a/packages/common/src/controls/__tests__/gridMenuControl.spec.ts b/packages/common/src/controls/__tests__/gridMenuControl.spec.ts index 63ab31a15..eb881a3f7 100644 --- a/packages/common/src/controls/__tests__/gridMenuControl.spec.ts +++ b/packages/common/src/controls/__tests__/gridMenuControl.spec.ts @@ -210,7 +210,7 @@ describe('GridMenuControl', () => { const inputElm = control.menuElement.querySelector('input[type="checkbox"]'); inputElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); expect(setSelectionSpy).toHaveBeenCalledWith(mockRowSelection); expect(control.getAllColumns()).toEqual(columnsMock); expect(control.getVisibleColumns()).toEqual(columnsMock); @@ -232,13 +232,13 @@ describe('GridMenuControl', () => { buttonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); const headerRowElm = document.querySelector('.slick-headerrow') as HTMLDivElement; - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); expect(headerRowElm.style.width).toBe(`calc(100% - 16px)`) const bodyElm = document.body; bodyElm.dispatchEvent(new Event('mousedown', { bubbles: true })); - expect(control.menuElement.style.visibility).toBe('hidden'); + expect(control.menuElement.style.display).toBe('none'); }); it('should query an input checkbox change event and expect "readjustFrozenColumnIndexWhenNeeded" method to be called when the grid is detected to be a frozen grid', () => { @@ -362,7 +362,7 @@ describe('GridMenuControl', () => { control.showGridMenu(spanEvent, { alignDropSide: 'left' }); const gridMenuElm = document.querySelector('.slick-gridmenu') as HTMLDivElement; - expect(gridMenuElm.style.visibility).toBe('visible'); + expect(gridMenuElm.style.display).toBe('block'); }); it('should open the Grid Menu and expect "Forcefit" to be checked when "hideForceFitButton" is false', () => { @@ -504,7 +504,7 @@ describe('GridMenuControl', () => { const forceFitElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-forcefit'); const inputSyncElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-syncresize'); - expect(control.menuElement.style.visibility).toBe('hidden'); + expect(control.menuElement.style.display).toBe('none'); expect(forceFitElm).toBeFalsy(); expect(inputSyncElm).toBeFalsy(); }); @@ -521,7 +521,7 @@ describe('GridMenuControl', () => { const forceFitElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-forcefit'); const inputSyncElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-syncresize'); - expect(control.menuElement.style.visibility).toBe('hidden'); + expect(control.menuElement.style.display).toBe('none'); expect(forceFitElm).toBeFalsy(); expect(inputSyncElm).toBeFalsy(); expect(pubSubSpy).toHaveBeenCalledWith('gridMenu:onBeforeMenuShow', { @@ -543,7 +543,7 @@ describe('GridMenuControl', () => { const forceFitElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-forcefit'); const inputSyncElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-syncresize'); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); expect(forceFitElm).toBeTruthy(); expect(inputSyncElm).toBeTruthy(); }); @@ -558,10 +558,10 @@ describe('GridMenuControl', () => { buttonElm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); expect(onAfterSpy).toHaveBeenCalled(); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); control.hideMenu(new Event('click', { bubbles: true, cancelable: true, composed: false }) as DOMEvent); - expect(control.menuElement.style.visibility).toBe('hidden'); + expect(control.menuElement.style.display).toBe('none'); expect(pubSubSpy).toHaveBeenCalledWith('gridMenu:onAfterMenuShow', { grid: gridStub, menu: document.querySelector('.slick-gridmenu'), @@ -583,12 +583,12 @@ describe('GridMenuControl', () => { const forceFitElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-forcefit'); const inputSyncElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-syncresize'); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); expect(forceFitElm).toBeTruthy(); expect(inputSyncElm).toBeTruthy(); control.hideMenu(new Event('click', { bubbles: true, cancelable: true, composed: false }) as DOMEvent); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); expect(pubSubSpy).toHaveBeenCalledWith('gridMenu:onMenuClose', { grid: gridStub, menu: document.querySelector('.slick-gridmenu'), @@ -610,12 +610,12 @@ describe('GridMenuControl', () => { const forceFitElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-forcefit'); const inputSyncElm = control.menuElement.querySelector('#slickgrid_124343-gridmenu-colpicker-syncresize'); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); expect(forceFitElm).toBeTruthy(); expect(inputSyncElm).toBeTruthy(); control.hideMenu(new Event('click', { bubbles: true, cancelable: true, composed: false }) as DOMEvent); - expect(control.menuElement.style.visibility).toBe('hidden'); + expect(control.menuElement.style.display).toBe('none'); expect(autosizeSpy).not.toHaveBeenCalled(); }); @@ -637,13 +637,13 @@ describe('GridMenuControl', () => { pickerField1Elm.checked = false; pickerField1Elm.dispatchEvent(new Event('click', { bubbles: true, cancelable: true, composed: false })); - expect(control.menuElement.style.visibility).toBe('visible'); + expect(control.menuElement.style.display).toBe('block'); expect(forceFitElm).toBeTruthy(); expect(inputSyncElm).toBeTruthy(); expect(pickerField1Elm.checked).toBeFalse(); control.hideMenu(new Event('click', { bubbles: true, cancelable: true, composed: false }) as DOMEvent); - expect(control.menuElement.style.visibility).toBe('hidden'); + expect(control.menuElement.style.display).toBe('none'); expect(autosizeSpy).toHaveBeenCalled(); }); diff --git a/packages/common/src/controls/columnPicker.control.ts b/packages/common/src/controls/columnPicker.control.ts index ae573f55e..e0de99a47 100644 --- a/packages/common/src/controls/columnPicker.control.ts +++ b/packages/common/src/controls/columnPicker.control.ts @@ -1,7 +1,7 @@ import { Column, ColumnPickerOption, - DOMEvent, + DOMMouseEvent, GetSlickEventType, GridOption, SlickEventHandler, @@ -104,7 +104,7 @@ export class ColumnPickerControl { this._menuElm = document.createElement('div'); this._menuElm.className = `slick-columnpicker ${this._gridUid}`; - this._menuElm.style.visibility = 'hidden'; + this._menuElm.style.display = 'none'; const closePickerButtonElm = document.createElement('button'); closePickerButtonElm.className = 'close'; @@ -192,14 +192,14 @@ export class ColumnPickerControl { // ------------------ /** Mouse down handler when clicking anywhere in the DOM body */ - protected handleBodyMouseDown(e: DOMEvent) { + protected handleBodyMouseDown(e: DOMMouseEvent) { if ((this._menuElm !== e.target && !this._menuElm.contains(e.target)) || e.target.className === 'close') { - this._menuElm.style.visibility = 'hidden'; + this._menuElm.style.display = 'none'; } } /** Mouse header context handler when doing a right+click on any of the header column title */ - protected handleHeaderContextMenu(e: DOMEvent) { + protected handleHeaderContextMenu(e: DOMMouseEvent) { e.preventDefault(); emptyElement(this._listElm); updateColumnPickerOrder.call(this); @@ -209,11 +209,11 @@ export class ColumnPickerControl { this.repositionMenu(e); } - protected repositionMenu(e: DOMEvent) { - this._menuElm.style.top = `${(e as any).pageY - 10}px`; - this._menuElm.style.left = `${(e as any).pageX - 10}px`; - this._menuElm.style.maxHeight = `${document.body.clientHeight - (e as any).pageY - 10}px`; - this._menuElm.style.visibility = 'visible'; + protected repositionMenu(event: DOMMouseEvent) { + this._menuElm.style.top = `${event.pageY - 10}px`; + this._menuElm.style.left = `${event.pageX - 10}px`; + this._menuElm.style.maxHeight = `${document.body.clientHeight - event.pageY - 10}px`; + this._menuElm.style.display = 'block'; this._menuElm.appendChild(this._listElm); } diff --git a/packages/common/src/controls/gridMenu.control.ts b/packages/common/src/controls/gridMenu.control.ts index 1fbbe0945..e9f55ab4f 100644 --- a/packages/common/src/controls/gridMenu.control.ts +++ b/packages/common/src/controls/gridMenu.control.ts @@ -173,7 +173,7 @@ export class GridMenuControl { this._bindEventService.unbindAll(); const gridMenuElm = document.querySelector(`div.slick-gridmenu.${this._gridUid}`); if (gridMenuElm) { - gridMenuElm.style.visibility = 'hidden'; + this._gridMenuElm.style.display = 'none'; } this._gridMenuButtonElm?.remove(); this._gridMenuElm?.remove(); @@ -244,7 +244,7 @@ export class GridMenuControl { this._gridMenuElm = document.createElement('div'); this._gridMenuElm.classList.add('slick-gridmenu', this._gridUid); - this._gridMenuElm.style.visibility = 'hidden'; + this._gridMenuElm.style.display = 'none'; const closePickerButtonElm = document.createElement('button'); closePickerButtonElm.className = 'close'; @@ -299,7 +299,7 @@ export class GridMenuControl { * @returns */ hideMenu(event: Event) { - if (this._gridMenuElm?.style?.visibility === 'visible') { + if (this._gridMenuElm?.style?.display === 'block') { const callbackArgs = { grid: this.grid, menu: this._gridMenuElm, @@ -313,7 +313,7 @@ export class GridMenuControl { return; } - this._gridMenuElm.style.visibility = 'hidden'; + this._gridMenuElm.style.display = 'none'; this._isMenuOpen = false; // we also want to resize the columns if the user decided to hide certain column(s) @@ -438,6 +438,7 @@ export class GridMenuControl { if (!buttonElm) { buttonElm = (e.target as HTMLElement).parentElement as HTMLButtonElement; // external grid menu might fall in this last case if wrapped in a span/div } + this._gridMenuElm.style.display = 'block'; const menuIconOffset = getHtmlElementOffset(buttonElm as HTMLButtonElement); const buttonComptStyle = getComputedStyle(buttonElm as HTMLButtonElement); const buttonWidth = parseInt(buttonComptStyle?.width ?? this._defaults?.menuWidth, 10); @@ -466,7 +467,7 @@ export class GridMenuControl { this._gridMenuElm.style.maxHeight = `${window.innerHeight - e.clientY - menuMarginBottom}px`; } - this._gridMenuElm.style.visibility = 'visible'; + this._gridMenuElm.style.display = 'block'; this._gridMenuElm.appendChild(this._listElm); this._isMenuOpen = true; } diff --git a/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts b/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts deleted file mode 100644 index c37bbd248..000000000 --- a/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts +++ /dev/null @@ -1,1112 +0,0 @@ -import 'jest-extended'; - -import { ContextMenuExtension } from '../contextMenuExtension'; -import { ExtensionUtility } from '../extensionUtility'; -import { Formatters } from '../../formatters'; -import { SharedService } from '../../services/shared.service'; -import { DelimiterType, FileType } from '../../enums/index'; -import { Column, SlickDataView, GridOption, MenuCommandItem, SlickGrid, SlickNamespace, ContextMenu, SlickContextMenu } from '../../interfaces/index'; -import { TranslateServiceStub } from '../../../../../test/translateServiceStub'; -import { BackendUtilityService, ExcelExportService, TextExportService, TreeDataService } from '../../services'; - -declare const Slick: SlickNamespace; - -const excelExportServiceStub = { - className: 'ExcelExportService', - exportToExcel: jest.fn(), -} as unknown as ExcelExportService; - -const exportServiceStub = { - className: 'TextExportService', - exportToFile: jest.fn(), -} as unknown as TextExportService; - -const dataViewStub = { - collapseAllGroups: jest.fn(), - expandAllGroups: jest.fn(), - refresh: jest.fn(), - getItems: jest.fn(), - getGrouping: jest.fn(), - setGrouping: jest.fn(), - setItems: jest.fn(), -} as unknown as SlickDataView; - -const gridStub = { - autosizeColumns: jest.fn(), - getColumnIndex: jest.fn(), - getColumns: jest.fn(), - getOptions: jest.fn(), - invalidate: jest.fn(), - registerPlugin: jest.fn(), - onClick: new Slick.Event(), - setColumns: jest.fn(), - setActiveCell: jest.fn(), - setHeaderRowVisibility: jest.fn(), - setTopPanelVisibility: jest.fn(), - setPreHeaderPanelVisibility: jest.fn(), -} as unknown as SlickGrid; - -const treeDataServiceStub = { - init: jest.fn(), - dispose: jest.fn(), - handleOnCellClick: jest.fn(), - toggleTreeDataCollapse: jest.fn(), -} as unknown as TreeDataService; - -const mockAddon = jest.fn().mockImplementation(() => ({ - init: jest.fn(), - destroy: jest.fn(), - closeMenu: jest.fn(), - setOptions: jest.fn(), - onBeforeMenuClose: new Slick.Event(), - onBeforeMenuShow: new Slick.Event(), - onAfterMenuShow: new Slick.Event(), - onColumnsChanged: new Slick.Event(), - onCommand: new Slick.Event(), - onOptionSelected: new Slick.Event(), -})); - -describe('contextMenuExtension', () => { - jest.mock('slickgrid/plugins/slick.contextmenu', () => mockAddon); - Slick.Plugins = { - ContextMenu: mockAddon - } as any; - - const columnsMock: Column[] = [{ id: 'field1', field: 'field1', width: 100, nameKey: 'TITLE' }, { id: 'field2', field: 'field2', width: 75 }]; - let extensionUtility: ExtensionUtility; - let backendUtilityService: BackendUtilityService; - let translateService: TranslateServiceStub; - let extension: ContextMenuExtension; - let sharedService: SharedService; - - const gridOptionsMock = { - enableAutoSizeColumns: true, - enableContextMenu: true, - enableTranslate: true, - backendServiceApi: { - service: { - buildQuery: jest.fn(), - }, - internalPostProcess: jest.fn(), - preProcess: jest.fn(), - process: jest.fn(), - postProcess: jest.fn(), - }, - contextMenu: { - commandItems: [], - autoAdjustDrop: true, - autoAlignSide: true, - hideCloseButton: false, - hideClearAllGrouping: false, - hideCollapseAllGroups: false, - hideCommandSection: false, - hideCopyCellValueCommand: false, - hideExpandAllGroups: false, - hideExportCsvCommand: false, - hideExportExcelCommand: false, - hideExportTextDelimitedCommand: true, - hideMenuOnScroll: true, - hideOptionSection: false, - onExtensionRegistered: jest.fn(), - onCommand: () => { }, - onBeforeMenuShow: () => { }, - onBeforeMenuClose: () => { }, - onAfterMenuShow: () => { }, - onOptionSelected: () => { }, - }, - pagination: { - totalItems: 0 - }, - showHeaderRow: false, - showTopPanel: false, - showPreHeaderPanel: false - } as unknown as GridOption; - - describe('with I18N Service', () => { - beforeEach(() => { - sharedService = new SharedService(); - backendUtilityService = new BackendUtilityService(); - translateService = new TranslateServiceStub(); - extensionUtility = new ExtensionUtility(sharedService, backendUtilityService, translateService); - extension = new ContextMenuExtension(extensionUtility, sharedService, treeDataServiceStub, translateService); - translateService.use('fr'); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('should return null when either the grid object or the grid options is missing', () => { - const output = extension.register(); - expect(output).toBeNull(); - }); - - describe('registered addon', () => { - beforeEach(() => { - jest.spyOn(SharedService.prototype, 'dataView', 'get').mockReturnValue(dataViewStub); - jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); - jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(columnsMock); - jest.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(columnsMock.slice(0, 1)); - jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(columnsMock); - }); - - it('should register the addon', () => { - const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onExtensionRegistered'); - - const instance = extension.register() as SlickContextMenu; - const addonInstance = extension.getAddonInstance(); - - expect(instance).toBeTruthy(); - expect(instance).toEqual(addonInstance); - expect(onRegisteredSpy).toHaveBeenCalledWith(instance); - expect(mockAddon).toHaveBeenCalledWith({ - commandItems: [{ - action: expect.anything(), - command: 'copy', - disabled: false, - iconCssClass: 'fa fa-clone', - itemUsabilityOverride: expect.anything(), - positionOrder: 50, - title: 'Copier', - }], - autoAdjustDrop: true, - autoAlignSide: true, - hideCloseButton: false, - hideClearAllGrouping: false, - hideCollapseAllGroups: false, - hideCommandSection: false, - hideCopyCellValueCommand: false, - hideExpandAllGroups: false, - hideExportCsvCommand: false, - hideExportExcelCommand: false, - hideExportTextDelimitedCommand: true, - hideMenuOnScroll: true, - hideOptionSection: false, - onCommand: expect.anything(), - onOptionSelected: expect.anything(), - onBeforeMenuClose: expect.anything(), - onBeforeMenuShow: expect.anything(), - onAfterMenuShow: expect.anything(), - onExtensionRegistered: expect.anything(), - }); - }); - - it('should call internal event handler subscribe and expect the "onBeforeMenuShow" option to be called when addon notify is called', () => { - const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); - const onb4CloseSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuClose'); - const onb4ShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuShow'); - const onAfterShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onAfterMenuShow'); - const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onCommand'); - const onOptionSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onOptionSelected'); - - const instance = extension.register() as SlickContextMenu; - instance.onBeforeMenuShow.notify({ cell: 0, row: 0, grid: gridStub }, new Slick.EventData(), gridStub); - - expect(handlerSpy).toHaveBeenCalledTimes(5); - expect(handlerSpy).toHaveBeenCalledWith( - { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, - expect.anything() - ); - expect(onb4ShowSpy).toHaveBeenCalledWith(expect.anything(), { cell: 0, row: 0, grid: gridStub }); - expect(onb4CloseSpy).not.toHaveBeenCalled(); - expect(onCommandSpy).not.toHaveBeenCalled(); - expect(onOptionSpy).not.toHaveBeenCalled(); - expect(onAfterShowSpy).not.toHaveBeenCalled(); - }); - - it('should call internal event handler subscribe and expect the "onBeforeMenuClose" option to be called when addon notify is called', () => { - const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); - const onb4CloseSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuClose'); - const onb4ShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuShow'); - const onAfterShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onAfterMenuShow'); - const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onCommand'); - const onOptionSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onOptionSelected'); - - const menuElm = document.createElement('div'); - const instance = extension.register() as SlickContextMenu; - instance.onBeforeMenuClose.notify({ cell: 0, row: 0, grid: gridStub, menu: menuElm }, new Slick.EventData(), gridStub); - - expect(handlerSpy).toHaveBeenCalledTimes(5); - expect(handlerSpy).toHaveBeenCalledWith( - { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, - expect.anything() - ); - expect(onb4CloseSpy).toHaveBeenCalledWith(expect.anything(), { cell: 0, row: 0, grid: gridStub, menu: menuElm }); - expect(onb4ShowSpy).not.toHaveBeenCalled(); - expect(onCommandSpy).not.toHaveBeenCalled(); - expect(onOptionSpy).not.toHaveBeenCalled(); - expect(onAfterShowSpy).not.toHaveBeenCalled(); - }); - - it('should call internal event handler subscribe and expect the "onAfterMenuShow" option to be called when addon notify is called', () => { - const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); - const onb4CloseSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuClose'); - const onb4ShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuShow'); - const onAfterShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onAfterMenuShow'); - const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onCommand'); - const onOptionSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onOptionSelected'); - - const instance = extension.register() as SlickContextMenu; - instance.onAfterMenuShow.notify({ cell: 0, row: 0, grid: gridStub }, new Slick.EventData(), gridStub); - - expect(handlerSpy).toHaveBeenCalledTimes(5); - expect(handlerSpy).toHaveBeenCalledWith( - { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, - expect.anything() - ); - expect(onAfterShowSpy).toHaveBeenCalledWith(expect.anything(), { cell: 0, row: 0, grid: gridStub }); - expect(onb4ShowSpy).not.toHaveBeenCalled(); - expect(onb4CloseSpy).not.toHaveBeenCalled(); - expect(onCommandSpy).not.toHaveBeenCalled(); - expect(onOptionSpy).not.toHaveBeenCalled(); - }); - - it('should call internal event handler subscribe and expect the "onCommand" option to be called when addon notify is called', () => { - const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); - const onb4CloseSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuClose'); - const onb4ShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuShow'); - const onAfterShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onAfterMenuShow'); - const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onCommand'); - const onOptionSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onOptionSelected'); - - const instance = extension.register() as SlickContextMenu; - instance.onCommand.notify({ item: { command: 'help' }, column: {} as Column, grid: gridStub, command: 'help' }, new Slick.EventData(), gridStub); - - expect(handlerSpy).toHaveBeenCalledTimes(5); - expect(handlerSpy).toHaveBeenCalledWith( - { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, - expect.anything() - ); - expect(onCommandSpy).toHaveBeenCalledWith(expect.anything(), { item: { command: 'help' }, column: {} as Column, grid: gridStub, command: 'help' }); - expect(onOptionSpy).not.toHaveBeenCalled(); - expect(onb4CloseSpy).not.toHaveBeenCalled(); - expect(onb4ShowSpy).not.toHaveBeenCalled(); - expect(onAfterShowSpy).not.toHaveBeenCalled(); - }); - - it('should call internal event handler subscribe and expect the "onOptionSelected" option to be called when addon notify is called', () => { - const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); - const onb4CloseSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuClose'); - const onb4ShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onBeforeMenuShow'); - const onAfterShowSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onAfterMenuShow'); - const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onCommand'); - const onOptionSpy = jest.spyOn(SharedService.prototype.gridOptions.contextMenu as ContextMenu, 'onOptionSelected'); - - const instance = extension.register() as SlickContextMenu; - instance.onOptionSelected.notify({ item: { option: 'help' }, column: {} as Column, grid: gridStub, option: 'help' }, new Slick.EventData(), gridStub); - - expect(handlerSpy).toHaveBeenCalledTimes(5); - expect(handlerSpy).toHaveBeenCalledWith( - { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, - expect.anything() - ); - expect(onOptionSpy).toHaveBeenCalledWith(expect.anything(), { item: { option: 'help' }, column: {} as Column, grid: gridStub, option: 'help' }); - expect(onCommandSpy).not.toHaveBeenCalled(); - expect(onb4CloseSpy).not.toHaveBeenCalled(); - expect(onb4ShowSpy).not.toHaveBeenCalled(); - expect(onAfterShowSpy).not.toHaveBeenCalled(); - }); - - it('should dispose of the addon', () => { - const instance = extension.register() as SlickContextMenu; - const destroySpy = jest.spyOn(instance, 'destroy'); - - extension.dispose(); - - expect(destroySpy).toHaveBeenCalled(); - }); - }); - - describe('addContextMenuCommandCommands method', () => { - afterEach(() => { - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); - }); - - it('should expect an empty "commandItems" array when "hideCopyCellValueCommand" is set True', () => { - const copyGridOptionsMock = { ...gridOptionsMock, contextMenu: { hideCopyCellValueCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([]); - }); - - it('should have the "copy" menu command when "hideCopyCellValueCommand" is set to False', () => { - const copyGridOptionsMock = { ...gridOptionsMock, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-clone', title: 'Copier', disabled: false, command: 'copy', positionOrder: 50, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should have the "export-csv" menu command when "enableTextExport" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, contextMenu: { hideCopyCellValueCommand: true, hideExportExcelCommand: true, hideExportTextDelimitedCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-download', title: 'Exporter en format CSV', disabled: false, command: 'export-csv', positionOrder: 51 } - ]); - }); - - it('should not have the "export-csv" menu command when "enableTextExport" and "hideExportCsvCommand" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, contextMenu: { hideCopyCellValueCommand: true, hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([]); - }); - - it('should have the "export-excel" menu command when "enableTextExport" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-file-excel-o text-success', title: 'Exporter vers Excel', disabled: false, command: 'export-excel', positionOrder: 52 } - ]); - }); - - it('should have the "export-text-delimited" menu command when "enableTextExport" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-download', title: 'Exporter en format texte (délimité par tabulation)', disabled: false, command: 'export-text-delimited', positionOrder: 53 } - ]); - }); - - it('should not have the "export-text-delimited" menu command when "enableTextExport" and "hideExportCsvCommand" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTextExport: true, contextMenu: { hideCopyCellValueCommand: true, hideExportExcelCommand: true, hideExportCsvCommand: true, hideExportTextDelimitedCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([]); - }); - - it('should have the "clear-grouping" menu command when "enableDraggableGrouping" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableDraggableGrouping: true, contextMenu: { hideCopyCellValueCommand: true, hideCollapseAllGroups: true, hideExpandAllGroups: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-times', title: 'Supprimer tous les groupes', disabled: false, command: 'clear-grouping', positionOrder: 55, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should have the "clear-grouping" menu command when "enableGrouping" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideCopyCellValueCommand: true, hideCollapseAllGroups: true, hideExpandAllGroups: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-times', title: 'Supprimer tous les groupes', disabled: false, command: 'clear-grouping', positionOrder: 55, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should NOT have the "clear-grouping" menu command when "enableGrouping" and "hideClearAllGrouping" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideCopyCellValueCommand: true, hideCollapseAllGroups: true, hideExpandAllGroups: true, hideClearAllGrouping: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([]); - }); - - it('should have the "collapse-all-groups" menu command when "enableGrouping" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideCopyCellValueCommand: true, hideClearAllGrouping: true, hideCollapseAllGroups: false, hideExpandAllGroups: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-compress', title: 'Réduire tous les groupes', disabled: false, command: 'collapse-all-groups', positionOrder: 56, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should have the "collapse-all-groups" menu command when "enableTreeData" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTreeData: true, contextMenu: { hideCopyCellValueCommand: true, hideClearAllGrouping: true, hideCollapseAllGroups: false, hideExpandAllGroups: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-compress', title: 'Réduire tous les groupes', disabled: false, command: 'collapse-all-groups', positionOrder: 56, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should NOT have the "collapse-all-groups" menu command when "enableGrouping" and "hideClearAllGrouping" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideCopyCellValueCommand: true, hideClearAllGrouping: true, hideCollapseAllGroups: true, hideExpandAllGroups: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([]); - }); - - it('should have the "expand-all-groups" menu command when "enableGrouping" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideCopyCellValueCommand: true, hideClearAllGrouping: true, hideCollapseAllGroups: true, hideExpandAllGroups: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-expand', title: 'Étendre tous les groupes', disabled: false, command: 'expand-all-groups', positionOrder: 57, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should have the "expand-all-groups" menu command when "enableTreeData" is set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTreeData: true, contextMenu: { hideCopyCellValueCommand: true, hideClearAllGrouping: true, hideCollapseAllGroups: true, hideExpandAllGroups: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-expand', title: 'Étendre tous les groupes', disabled: false, command: 'expand-all-groups', positionOrder: 57, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should NOT have the "expand-all-groups" menu command when "enableGrouping" and "hideClearAllGrouping" are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideCopyCellValueCommand: true, hideClearAllGrouping: true, hideCollapseAllGroups: true, hideExpandAllGroups: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([]); - }); - - it('should have 2 Grouping commands (collapse, expand) when enableTreeData is set and none of the hidden flags are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTreeData: true, contextMenu: { hideCopyCellValueCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-compress', title: 'Réduire tous les groupes', disabled: false, command: 'collapse-all-groups', positionOrder: 56, itemUsabilityOverride: expect.anything() }, - { action: expect.anything(), iconCssClass: 'fa fa-expand', title: 'Étendre tous les groupes', disabled: false, command: 'expand-all-groups', positionOrder: 57, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should have all 3 Grouping commands (clear, collapse, expand) when grouping is enabled and none of the hidden flags are set', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideCopyCellValueCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-times', title: 'Supprimer tous les groupes', disabled: false, command: 'clear-grouping', positionOrder: 55, itemUsabilityOverride: expect.anything() }, - { action: expect.anything(), iconCssClass: 'fa fa-compress', title: 'Réduire tous les groupes', disabled: false, command: 'collapse-all-groups', positionOrder: 56, itemUsabilityOverride: expect.anything() }, - { action: expect.anything(), iconCssClass: 'fa fa-expand', title: 'Étendre tous les groupes', disabled: false, command: 'expand-all-groups', positionOrder: 57, itemUsabilityOverride: expect.anything() } - ]); - }); - - it('should have all 3 Grouping commands (clear, collapse, expand) & the copy command when there no hidden flags in the contextMenu object', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), iconCssClass: 'fa fa-clone', title: 'Copier', disabled: false, command: 'copy', positionOrder: 50, itemUsabilityOverride: expect.anything() }, - { divider: true, command: '', positionOrder: 54 }, - { action: expect.anything(), iconCssClass: 'fa fa-times', title: 'Supprimer tous les groupes', disabled: false, command: 'clear-grouping', positionOrder: 55, itemUsabilityOverride: expect.anything() }, - { action: expect.anything(), iconCssClass: 'fa fa-compress', title: 'Réduire tous les groupes', disabled: false, command: 'collapse-all-groups', positionOrder: 56, itemUsabilityOverride: expect.anything() }, - { action: expect.anything(), iconCssClass: 'fa fa-expand', title: 'Étendre tous les groupes', disabled: false, command: 'expand-all-groups', positionOrder: 57, itemUsabilityOverride: expect.anything() } - ]); - }); - }); - - describe('adding Context Menu Command Items', () => { - const commandItemsMock = [{ - iconCssClass: 'fa fa-question-circle', - title: 'Help', - titleKey: 'HELP', - disabled: false, - command: 'help', - positionOrder: 99 - }]; - - beforeEach(() => { - const copyGridOptionsMock = { - ...gridOptionsMock, - enableTextExport: true, - contextMenu: { - commandItems: commandItemsMock, - hideCopyCellValueCommand: true, - hideExportCsvCommand: false, - hideExportExcelCommand: false, - hideExportTextDelimitedCommand: true, - hideClearAllGrouping: true, - hideCollapseAllGroups: true, - hideExpandAllGroups: true, - } - } as GridOption; - - jest.spyOn(SharedService.prototype, 'dataView', 'get').mockReturnValue(dataViewStub); - jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); - jest.spyOn(SharedService.prototype, 'allColumns', 'get').mockReturnValue(columnsMock); - jest.spyOn(SharedService.prototype, 'visibleColumns', 'get').mockReturnValue(columnsMock); - jest.spyOn(SharedService.prototype, 'columnDefinitions', 'get').mockReturnValue(columnsMock); - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - }); - - afterEach(() => { - extension.dispose(); - }); - - it('should have user Context Menu Command items when "enableTranslate" is disabled', () => { - const copyGridOptionsMock = { - ...gridOptionsMock, enableTranslate: false, enableTextExport: true, - contextMenu: { - commandItems: commandItemsMock, - hideCopyCellValueCommand: true, - hideExportCsvCommand: false, - hideExportExcelCommand: true, - hideExportTextDelimitedCommand: true, - hideClearAllGrouping: true, - hideCollapseAllGroups: true, - hideExpandAllGroups: true, - } - } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 51, title: 'Export in CSV format' }, - // { action: expect.anything(), command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 54, title: 'Exporter vers Excel' }, - { command: 'help', disabled: false, iconCssClass: 'fa fa-question-circle', positionOrder: 99, title: 'Help', titleKey: 'HELP' }, - ]); - }); - - it('should have user Context Menu Command items when "enableTranslate" is active', () => { - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 51, title: 'Exporter en format CSV' }, - // { action: expect.anything(), command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 54, title: 'Exporter vers Excel' }, - { command: 'help', disabled: false, iconCssClass: 'fa fa-question-circle', positionOrder: 99, title: 'Aide', titleKey: 'HELP' }, - ]); - }); - - it('should have same user Context Menu Command items even when Context Menu extension is registered multiple times', () => { - extension.register(); - extension.register(); - expect((SharedService.prototype.gridOptions.contextMenu as ContextMenu).commandItems).toEqual([ - { action: expect.anything(), command: 'export-csv', disabled: false, iconCssClass: 'fa fa-download', positionOrder: 51, title: 'Exporter en format CSV' }, - // { action: expect.anything(), command: 'export-excel', disabled: false, iconCssClass: 'fa fa-file-excel-o text-success', positionOrder: 54, title: 'Exporter vers Excel' }, - { command: 'help', disabled: false, iconCssClass: 'fa fa-question-circle', positionOrder: 99, title: 'Aide', titleKey: 'HELP' }, - ]); - }); - }); - - describe('executeContextMenuInternalCommandCommands method', () => { - beforeEach(() => { - if (window.document) { - window.document.createRange = () => ({ - selectNodeContents: () => { }, - setStart: () => { }, - setEnd: () => { }, - commonAncestorContainer: { nodeName: 'BODY', ownerDocument: document }, - } as any); - - window.document.execCommand = () => (true); - - window.getSelection = () => ({ - removeAllRanges: () => { }, - addRange: () => { }, - } as any); - } - }); - - it('should call "copyToClipboard", without export formatter, when the command triggered is "copy"', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName' } as Column; - const dataContextMock = { id: 123, firstName: 'John', lastName: 'Doe', age: 50 }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - const execSpy = jest.spyOn(window.document, 'execCommand'); - extension.register(); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { - command: 'copy', - cell: 2, - row: 5, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - item: menuItemCommand, - value: 'John' - }); - - expect(execSpy).toHaveBeenCalledWith('copy', false, 'John'); - }); - - it('should call "copyToClipboard", WITH export formatter, when the command triggered is "copy"', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, exportOptions: { exportWithFormatter: true } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName', formatter: Formatters.uppercase } as Column; - const dataContextMock = { id: 123, firstName: 'John', lastName: 'Doe', age: 50 }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - const execSpy = jest.spyOn(window.document, 'execCommand'); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { - command: 'copy', - cell: 2, - row: 5, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - item: menuItemCommand, - value: 'John' - }); - - expect(execSpy).toHaveBeenCalledWith('copy', false, 'JOHN'); - }); - - it('should call "copyToClipboard" and get the value even when there is a "queryFieldNameGetterFn" callback defined with dot notation the command triggered is "copy"', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName', queryFieldNameGetterFn: () => 'lastName' } as Column; - const dataContextMock = { id: 123, firstName: 'John', lastName: 'Doe', age: 50 }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - const execSpy = jest.spyOn(window.document, 'execCommand'); - extension.register(); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { - command: 'copy', - cell: 2, - row: 5, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - item: menuItemCommand, - value: 'John' - }); - - expect(execSpy).toHaveBeenCalledWith('copy', false, 'Doe'); - }); - - it('should call "copyToClipboard" and get the value even when there is a "queryFieldNameGetterFn" callback defined with dot notation the command triggered is "copy"', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName', queryFieldNameGetterFn: () => 'user.lastName' } as Column; - const dataContextMock = { id: 123, user: { firstName: 'John', lastName: 'Doe', age: 50 } }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - const execSpy = jest.spyOn(window.document, 'execCommand'); - extension.register(); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { - command: 'copy', - cell: 2, - row: 5, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - item: menuItemCommand, - value: 'John' - }); - - expect(execSpy).toHaveBeenCalledWith('copy', false, 'Doe'); - }); - - it('should expect "itemUsabilityOverride" callback from the "copy" command to return True when a value to copy is found in the dataContext object', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName' } as Column; - const dataContextMock = { id: 123, firstName: 'John', lastName: 'Doe', age: 50 }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - const isCommandUsable = menuItemCommand.itemUsabilityOverride!({ - cell: 2, - row: 2, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - }); - - expect(isCommandUsable).toBe(true); - }); - - it('should expect "itemUsabilityOverride" callback from the "copy" command to return False when a value to copy is an empty string', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName' } as Column; - const dataContextMock = { id: 123, firstName: '', lastName: 'Doe', age: 50 }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - const isCommandUsable = menuItemCommand.itemUsabilityOverride!({ - cell: 2, - row: 2, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - }); - - expect(isCommandUsable).toBe(false); - }); - - it('should expect "itemUsabilityOverride" callback from the "copy" command to return False when a value to copy is null', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName' } as Column; - const dataContextMock = { id: 123, firstName: null, lastName: 'Doe', age: 50 }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - const isCommandUsable = menuItemCommand.itemUsabilityOverride!({ - cell: 2, - row: 2, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - }); - - expect(isCommandUsable).toBe(false); - }); - - it('should expect "itemUsabilityOverride" callback from the "copy" command to return False when the dataContext object does not contain the field property specified', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName' } as Column; - const dataContextMock = { id: 123, lastName: 'Doe', age: 50 }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - const isCommandUsable = menuItemCommand.itemUsabilityOverride!({ - cell: 2, - row: 2, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - }); - - expect(isCommandUsable).toBe(false); - }); - - it('should expect "itemUsabilityOverride" callback from the "copy" command to return True when there is a "queryFieldNameGetterFn" which itself returns a value', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName', queryFieldNameGetterFn: () => 'lastName' } as Column; - const dataContextMock = { id: 123, firstName: null, lastName: 'Doe', age: 50 }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - const isCommandUsable = menuItemCommand.itemUsabilityOverride!({ - cell: 2, - row: 2, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - }); - - expect(isCommandUsable).toBe(true); - }); - - it('should expect "itemUsabilityOverride" callback from the "copy" command to return True when there is a "queryFieldNameGetterFn" and a dot notation field which does return a value', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; - const columnMock = { id: 'firstName', name: 'First Name', field: 'user.firstName', queryFieldNameGetterFn: () => 'user.lastName' } as Column; - const dataContextMock = { id: 123, user: { firstName: null, lastName: 'Doe', age: 50 } }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem; - const isCommandUsable = menuItemCommand.itemUsabilityOverride!({ - cell: 2, - row: 2, - grid: gridStub, - column: columnMock, - dataContext: dataContextMock, - }); - - expect(isCommandUsable).toBe(true); - }); - - it('should call "exportToExcel" and expect an error thrown when ExcelExportService is not registered prior to calling the method', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([]); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'export-excel') as MenuCommandItem; - expect(() => menuItemCommand.action!(new CustomEvent('change'), { command: 'export-excel', cell: 0, row: 0 } as any)) - .toThrow('[Slickgrid-Universal] You must register the ExcelExportService to properly use Export to Excel in the Context Menu.'); - }); - - it('should call "exportToFile" with CSV and expect an error thrown when TextExportService is not registered prior to calling the method', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: true, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: false, hideExportExcelCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([]); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'export-csv') as MenuCommandItem; - expect(() => menuItemCommand.action!(new CustomEvent('change'), { command: 'export-excel', cell: 0, row: 0 } as any)) - .toThrow('[Slickgrid-Universal] You must register the TextExportService to properly use Export to File in the Context Menu.'); - }); - - 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, enableExcelExport: false, enableTextExport: true, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: false, hideExportExcelCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'export-text-delimited') as MenuCommandItem; - expect(() => menuItemCommand.action!(new CustomEvent('change'), { command: 'export-excel', cell: 0, row: 0 } as any)) - .toThrow('[Slickgrid-Universal] You must register the TextExportService to properly use Export to File in the Context Menu.'); - }); - - - it('should call "exportToExcel" when the command triggered is "export-excel"', () => { - const excelExportSpy = jest.spyOn(excelExportServiceStub, 'exportToExcel'); - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, enableTextExport: false, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([excelExportServiceStub]); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'export-excel') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { command: 'export-excel', cell: 0, row: 0 } as any); - - expect(excelExportSpy).toHaveBeenCalled(); - }); - - it('should call "exportToFile" with CSV set when the command triggered is "export-csv"', () => { - const exportSpy = jest.spyOn(exportServiceStub, 'exportToFile'); - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: true, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: false, hideExportExcelCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([exportServiceStub]); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'export-csv') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { command: 'export-excel', cell: 0, row: 0 } as any); - - expect(exportSpy).toHaveBeenCalledWith({ - delimiter: DelimiterType.comma, - format: FileType.csv, - }); - }); - - it('should call "exportToFile" with Text Delimited set when the command triggered is "export-text-delimited"', () => { - const exportSpy = jest.spyOn(exportServiceStub, 'exportToFile'); - const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableTextExport: true, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: false, hideExportExcelCommand: true } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - jest.spyOn(SharedService.prototype, 'externalRegisteredResources', 'get').mockReturnValue([exportServiceStub]); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'export-text-delimited') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { command: 'export-excel', cell: 0, row: 0 } as any); - - expect(exportSpy).toHaveBeenCalledWith({ - delimiter: DelimiterType.tab, - format: FileType.txt, - }); - }); - - it('should call "setGrouping" from the DataView when Grouping is enabled and the command triggered is "clear-grouping"', () => { - const dataviewSpy = jest.spyOn(SharedService.prototype.dataView, 'setGrouping'); - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideClearAllGrouping: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'clear-grouping') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { command: 'clear-grouping', cell: 0, row: 0 } as any); - - expect(dataviewSpy).toHaveBeenCalledWith([]); - }); - - it('should call "collapseAllGroups" from the DataView when Grouping is enabled and the command triggered is "collapse-all-groups"', () => { - const dataviewSpy = jest.spyOn(SharedService.prototype.dataView, 'collapseAllGroups'); - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideCollapseAllGroups: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'collapse-all-groups') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { command: 'collapse-all-groups', cell: 0, row: 0 } as any); - - expect(dataviewSpy).toHaveBeenCalledWith(); - }); - - it('should call "collapseAllGroups" from the DataView when Tree Data is enabled and the command triggered is "collapse-all-groups"', () => { - jest.spyOn(SharedService.prototype.dataView, 'getItems').mockReturnValueOnce(columnsMock); - const treeDataSpy = jest.spyOn(treeDataServiceStub, 'toggleTreeDataCollapse'); - const copyGridOptionsMock = { ...gridOptionsMock, enableTreeData: true, contextMenu: { hideCollapseAllGroups: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'collapse-all-groups') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { command: 'collapse-all-groups', cell: 0, row: 0 } as any); - - expect(treeDataSpy).toHaveBeenCalledWith(true); - }); - - it('should call "expandAllGroups" from the DataView when Grouping is enabled and the command triggered is "expand-all-groups"', () => { - const dataviewSpy = jest.spyOn(SharedService.prototype.dataView, 'expandAllGroups'); - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideExpandAllGroups: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'expand-all-groups') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { command: 'expand-all-groups', cell: 0, row: 0 } as any); - - expect(dataviewSpy).toHaveBeenCalledWith(); - }); - - it('should call "expandAllGroups" from the DataView when Tree Data is enabled and the command triggered is "expand-all-groups"', () => { - const treeDataSpy = jest.spyOn(treeDataServiceStub, 'toggleTreeDataCollapse'); - jest.spyOn(SharedService.prototype.dataView, 'getItems').mockReturnValueOnce(columnsMock); - const copyGridOptionsMock = { ...gridOptionsMock, enableTreeData: true, contextMenu: { hideExpandAllGroups: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuItemCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'expand-all-groups') as MenuCommandItem; - menuItemCommand.action!(new CustomEvent('change'), { command: 'expand-all-groups', cell: 0, row: 0 } as any); - - expect(treeDataSpy).toHaveBeenCalledWith(false); - }); - - it('should expect "itemUsabilityOverride" callback on all the Grouping command to return False when there are NO Groups in the grid', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideClearAllGrouping: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - const dataviewSpy = jest.spyOn(SharedService.prototype.dataView, 'getGrouping').mockReturnValue([]); - extension.register(); - - const menuClearCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'clear-grouping') as MenuCommandItem; - const isClearCommandUsable = menuClearCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - const menuCollapseCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'collapse-all-groups') as MenuCommandItem; - const isCollapseCommandUsable = menuCollapseCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - const menuExpandCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'expand-all-groups') as MenuCommandItem; - const isExpandCommandUsable = menuExpandCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - - expect(isClearCommandUsable).toBe(false); - expect(isCollapseCommandUsable).toBe(false); - expect(isExpandCommandUsable).toBe(false); - expect(dataviewSpy).toHaveBeenCalled(); - }); - - it('should expect "itemUsabilityOverride" callback on all the Grouping command to return True when there are Groups defined in the grid', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableGrouping: true, contextMenu: { hideClearAllGrouping: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - const dataviewSpy = jest.spyOn(SharedService.prototype.dataView, 'getGrouping').mockReturnValue([{ collapsed: true }]); - extension.register(); - - const menuClearCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'clear-grouping') as MenuCommandItem; - const isClearCommandUsable = menuClearCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - const menuCollapseCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'collapse-all-groups') as MenuCommandItem; - const isCollapseCommandUsable = menuCollapseCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - const menuExpandCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'expand-all-groups') as MenuCommandItem; - const isExpandCommandUsable = menuExpandCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - - expect(isClearCommandUsable).toBe(true); - expect(isCollapseCommandUsable).toBe(true); - expect(isExpandCommandUsable).toBe(true); - expect(dataviewSpy).toHaveBeenCalled(); - }); - - it('should expect "itemUsabilityOverride" callback on all the Tree Data Grouping command to return Tree (collapse, expand) at all time even when there are NO Groups in the grid', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTreeData: true, contextMenu: { hideClearAllGrouping: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuCollapseCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'collapse-all-groups') as MenuCommandItem; - const isCollapseCommandUsable = menuCollapseCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - const menuExpandCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'expand-all-groups') as MenuCommandItem; - const isExpandCommandUsable = menuExpandCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - - expect(isCollapseCommandUsable).toBe(true); - expect(isExpandCommandUsable).toBe(true); - }); - - it('should expect "itemUsabilityOverride" callback on all the Tree Data Grouping command to return True (collapse, expand) when there are Groups defined in the grid', () => { - const copyGridOptionsMock = { ...gridOptionsMock, enableTreeData: true, contextMenu: { hideClearAllGrouping: false } } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - const menuCollapseCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'collapse-all-groups') as MenuCommandItem; - const isCollapseCommandUsable = menuCollapseCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - const menuExpandCommand = ((copyGridOptionsMock.contextMenu as ContextMenu).commandItems as MenuCommandItem[]).find((item: MenuCommandItem) => item.command === 'expand-all-groups') as MenuCommandItem; - const isExpandCommandUsable = menuExpandCommand.itemUsabilityOverride!({ cell: 2, row: 2, grid: gridStub, } as any); - - expect(isCollapseCommandUsable).toBe(true); - expect(isExpandCommandUsable).toBe(true); - }); - }); - - describe('translateContextMenu method', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should have all context menu commands translated and an extra internal command (copy) and expect not to see duplicate commands', () => { - const copyGridOptionsMock = { - ...gridOptionsMock, - enableExcelExport: true, - enableTextExport: true, - contextMenu: { - hideExportTextDelimitedCommand: false, - commandTitleKey: 'COMMANDS', - commandItems: [ - { iconCssClass: 'fa fa-clone', title: 'Copy', command: 'copy', disabled: false, positionOrder: 50 }, - { iconCssClass: 'fa fa-download', title: 'Export to CSV', command: 'export-csv', disabled: false, positionOrder: 51 }, - { iconCssClass: 'fa fa-file-excel-o text-success', title: 'Export to Excel', command: 'export-excel', disabled: false, positionOrder: 52 }, - { iconCssClass: 'fa fa-download', title: 'Export in Text format (Tab delimited)', command: 'export-text-delimited', disabled: false, positionOrder: 53 }, - { iconCssClass: 'fa fa-sort-asc', title: 'Trier par ordre croissant', titleKey: 'SORT_ASCENDING', command: 'sort-asc', positionOrder: 60 }, - { iconCssClass: 'fa fa-sort-desc', title: 'Trier par ordre décroissant', titleKey: 'SORT_DESCENDING', command: 'sort-desc', positionOrder: 61 }, - { divider: true, command: '', positionOrder: 62 }, - { iconCssClass: 'fa fa-filter', title: 'Supprimer le filtre', titleKey: 'REMOVE_FILTER', command: 'clear-filter', positionOrder: 63 }, - { iconCssClass: 'fa fa-unsorted', title: 'Supprimer le tri', titleKey: 'REMOVE_SORT', command: 'clear-sort', positionOrder: 64 }, - { iconCssClass: 'fa fa-times', command: 'hide', titleKey: 'HIDE_COLUMN', positionOrder: 65, title: 'Cacher la colonne' }, - ] - } - } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - translateService.use('en'); - extension.translateContextMenu(); - - expect(copyGridOptionsMock.contextMenu).toEqual({ - hideExportTextDelimitedCommand: false, - commandTitle: 'Commands', - commandTitleKey: 'COMMANDS', - commandItems: [ - { iconCssClass: 'fa fa-clone', title: 'Copy', command: 'copy', disabled: false, positionOrder: 50 }, - { iconCssClass: 'fa fa-download', title: 'Export to CSV', command: 'export-csv', disabled: false, positionOrder: 51 }, - { iconCssClass: 'fa fa-file-excel-o text-success', title: 'Export to Excel', command: 'export-excel', disabled: false, positionOrder: 52 }, - { iconCssClass: 'fa fa-download', title: 'Export in Text format (Tab delimited)', command: 'export-text-delimited', disabled: false, positionOrder: 53 }, - { iconCssClass: 'fa fa-sort-asc', title: 'Sort Ascending', titleKey: 'SORT_ASCENDING', command: 'sort-asc', positionOrder: 60 }, - { iconCssClass: 'fa fa-sort-desc', title: 'Sort Descending', titleKey: 'SORT_DESCENDING', command: 'sort-desc', positionOrder: 61 }, - { divider: true, command: '', positionOrder: 62 }, - { iconCssClass: 'fa fa-filter', title: 'Remove Filter', titleKey: 'REMOVE_FILTER', command: 'clear-filter', positionOrder: 63 }, - { iconCssClass: 'fa fa-unsorted', title: 'Remove Sort', titleKey: 'REMOVE_SORT', command: 'clear-sort', positionOrder: 64 }, - { iconCssClass: 'fa fa-times', titleKey: 'HIDE_COLUMN', command: 'hide', positionOrder: 65, title: 'Hide Column' }, - ] - }); - }); - - it('should have all context menu options translated', () => { - const copyGridOptionsMock = { - ...gridOptionsMock, - contextMenu: { - optionTitleKey: 'OPTIONS_LIST', - optionItems: [ - { iconCssClass: 'fa fa-sort-asc', title: 'Trier par ordre croissant', titleKey: 'SORT_ASCENDING', option: 'sort-asc', positionOrder: 60 }, - { iconCssClass: 'fa fa-sort-desc', title: 'Trier par ordre décroissant', titleKey: 'SORT_DESCENDING', option: 'sort-desc', positionOrder: 61 }, - 'divider', - { iconCssClass: 'fa fa-filter', title: 'Supprimer le filtre', titleKey: 'REMOVE_FILTER', option: 'clear-filter', positionOrder: 63 }, - { iconCssClass: 'fa fa-unsorted', title: 'Supprimer le tri', titleKey: 'REMOVE_SORT', option: 'clear-sort', positionOrder: 64 }, - { iconCssClass: 'fa fa-times', option: 'hide', titleKey: 'HIDE_COLUMN', positionOrder: 65, title: 'Cacher la colonne' }, - ] - } - } as GridOption; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); - extension.register(); - - translateService.use('en'); - extension.translateContextMenu(); - - expect(copyGridOptionsMock.contextMenu).toEqual({ - optionTitle: 'Options List', - optionTitleKey: 'OPTIONS_LIST', - optionItems: [ - { iconCssClass: 'fa fa-sort-asc', title: 'Sort Ascending', titleKey: 'SORT_ASCENDING', option: 'sort-asc', positionOrder: 60 }, - { iconCssClass: 'fa fa-sort-desc', title: 'Sort Descending', titleKey: 'SORT_DESCENDING', option: 'sort-desc', positionOrder: 61 }, - 'divider', - { iconCssClass: 'fa fa-filter', title: 'Remove Filter', titleKey: 'REMOVE_FILTER', option: 'clear-filter', positionOrder: 63 }, - { iconCssClass: 'fa fa-unsorted', title: 'Remove Sort', titleKey: 'REMOVE_SORT', option: 'clear-sort', positionOrder: 64 }, - { iconCssClass: 'fa fa-times', titleKey: 'HIDE_COLUMN', option: 'hide', positionOrder: 65, title: 'Hide Column' }, - ], - commandItems: [ - { action: expect.anything(), iconCssClass: 'fa fa-clone', title: 'Copy', command: 'copy', disabled: false, itemUsabilityOverride: expect.anything(), positionOrder: 50 }, - ] - }); - }); - }); - // }); - - describe('without Translate Service', () => { - beforeEach(() => { - translateService = undefined as any; - extension = new ContextMenuExtension({} as ExtensionUtility, { gridOptions: { enableTranslate: true } } as SharedService, treeDataServiceStub, translateService); - }); - - it('should throw an error if "enableTranslate" is set but the I18N Service is null', () => { - expect(() => extension.register()).toThrowError('[Slickgrid-Universal] requires a Translate Service to be installed and configured'); - }); - }); - }); -}); diff --git a/packages/common/src/extensions/contextMenuExtension.ts b/packages/common/src/extensions/contextMenuExtension.ts deleted file mode 100644 index 7d94f9f13..000000000 --- a/packages/common/src/extensions/contextMenuExtension.ts +++ /dev/null @@ -1,464 +0,0 @@ -import 'slickgrid/plugins/slick.contextmenu'; - -import { - Column, - ContextMenu, - Extension, - GetSlickEventType, - MenuCallbackArgs, - MenuCommandItem, - MenuCommandItemCallbackArgs, - SlickContextMenu, - SlickEventHandler, - SlickNamespace, -} from '../interfaces/index'; -import { DelimiterType, FileType, } from '../enums/index'; -import { ExtensionUtility } from './extensionUtility'; -import { exportWithFormatterWhenDefined } from '../formatters/formatterUtilities'; -import { SharedService } from '../services/shared.service'; -import { getDescendantProperty, getTranslationPrefix } from '../services/utilities'; -import { ExcelExportService, TextExportService, TranslaterService, TreeDataService } from '../services/index'; - -// using external non-typed js libraries -declare const Slick: SlickNamespace; - -export class ContextMenuExtension implements Extension { - private _addon: SlickContextMenu | null = null; - private _contextMenuOptions: ContextMenu | null = null; - private _eventHandler: SlickEventHandler; - private _userOriginalContextMenu: ContextMenu | undefined; - - constructor( - private readonly extensionUtility: ExtensionUtility, - private readonly sharedService: SharedService, - private readonly treeDataService: TreeDataService, - private readonly translaterService?: TranslaterService, - ) { - this._eventHandler = new Slick.EventHandler(); - } - - get eventHandler(): SlickEventHandler { - return this._eventHandler; - } - - dispose() { - // unsubscribe all SlickGrid events - this._eventHandler.unsubscribeAll(); - - if (this._addon && this._addon.destroy) { - this._addon.destroy(); - } - if (this.sharedService.gridOptions && this.sharedService.gridOptions.contextMenu && this.sharedService.gridOptions.contextMenu.commandItems) { - this.sharedService.gridOptions.contextMenu = this._userOriginalContextMenu; - } - - this.extensionUtility.nullifyFunctionNameStartingWithOn(this._contextMenuOptions); - this._addon = null; - this._contextMenuOptions = null; - } - - /** Get the instance of the SlickGrid addon (control or plugin). */ - getAddonInstance(): SlickContextMenu | null { - return this._addon; - } - - /** Register the 3rd party addon (plugin) */ - register(): SlickContextMenu | null { - if (this.sharedService.gridOptions && this.sharedService.gridOptions.enableTranslate && (!this.translaterService || !this.translaterService.translate)) { - throw new Error('[Slickgrid-Universal] requires a Translate Service to be installed and configured when the grid option "enableTranslate" is enabled.'); - } - - if (this.sharedService && this.sharedService.slickGrid && this.sharedService.gridOptions && this.sharedService.gridOptions.contextMenu) { - this._contextMenuOptions = this.sharedService.gridOptions.contextMenu; - // keep original user context menu, useful when switching locale to translate - this._userOriginalContextMenu = { ...this._contextMenuOptions }; - - // merge the original commands with the built-in internal commands - const originalCommandItems = this._userOriginalContextMenu && Array.isArray(this._userOriginalContextMenu.commandItems) ? this._userOriginalContextMenu.commandItems : []; - this._contextMenuOptions.commandItems = [...originalCommandItems, ...this.addMenuCustomCommands(originalCommandItems)]; - this._contextMenuOptions = { ...this._contextMenuOptions }; - this.sharedService.gridOptions.contextMenu = this._contextMenuOptions; - - // sort all menu items by their position order when defined - this.extensionUtility.sortItems(this._contextMenuOptions.commandItems || [], 'positionOrder'); - this.extensionUtility.sortItems(this._contextMenuOptions.optionItems || [], 'positionOrder'); - - this._addon = new Slick.Plugins.ContextMenu(this._contextMenuOptions); - if (this._addon) { - this.sharedService.slickGrid.registerPlugin(this._addon); - } - - // translate the item keys when necessary - if (this.sharedService.gridOptions.enableTranslate) { - this.translateContextMenu(); - } - - // hook all events - if (this.sharedService.slickGrid && this._contextMenuOptions) { - if (this._addon && this._contextMenuOptions.onExtensionRegistered) { - this._contextMenuOptions.onExtensionRegistered(this._addon); - } - if (this._contextMenuOptions && typeof this._contextMenuOptions.onCommand === 'function') { - const onCommandHandler = this._addon.onCommand; - (this._eventHandler as SlickEventHandler>).subscribe(onCommandHandler, (event, args) => { - if (this._contextMenuOptions?.onCommand) { - this._contextMenuOptions.onCommand(event, args); - } - }); - } - if (this._contextMenuOptions && typeof this._contextMenuOptions.onOptionSelected === 'function') { - const onOptionSelectedHandler = this._addon.onOptionSelected; - (this._eventHandler as SlickEventHandler>).subscribe(onOptionSelectedHandler, (event, args) => { - if (this._contextMenuOptions?.onOptionSelected) { - this._contextMenuOptions.onOptionSelected(event, args); - } - }); - } - if (this._contextMenuOptions && typeof this._contextMenuOptions.onBeforeMenuShow === 'function') { - const onBeforeMenuShowHandler = this._addon.onBeforeMenuShow; - (this._eventHandler as SlickEventHandler>).subscribe(onBeforeMenuShowHandler, (event, args) => { - if (this._contextMenuOptions?.onBeforeMenuShow) { - this._contextMenuOptions.onBeforeMenuShow(event, args); - } - }); - } - if (this._contextMenuOptions && typeof this._contextMenuOptions.onBeforeMenuClose === 'function') { - const onBeforeMenuCloseHandler = this._addon.onBeforeMenuClose; - (this._eventHandler as SlickEventHandler>).subscribe(onBeforeMenuCloseHandler, (event, args) => { - if (this._contextMenuOptions?.onBeforeMenuClose) { - this._contextMenuOptions.onBeforeMenuClose(event, args); - } - }); - } - if (this._contextMenuOptions && typeof this._contextMenuOptions.onAfterMenuShow === 'function') { - const onAfterMenuShowHandler = this._addon.onAfterMenuShow; - (this._eventHandler as SlickEventHandler>).subscribe(onAfterMenuShowHandler, (event, args) => { - if (this._contextMenuOptions?.onAfterMenuShow) { - this._contextMenuOptions.onAfterMenuShow(event, args); - } - }); - } - } - return this._addon; - } - return null; - } - - /** Translate the Context Menu titles, we need to loop through all column definition to re-translate them */ - translateContextMenu() { - if (this.sharedService && this.sharedService.gridOptions && this.sharedService.gridOptions.contextMenu) { - const contextMenu = this.sharedService.gridOptions.contextMenu; - const menuOptions: Partial = {}; - - if (contextMenu.commandTitleKey) { - contextMenu.commandTitle = this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(contextMenu.commandTitleKey) || contextMenu.commandTitle; - menuOptions.commandTitle = contextMenu.commandTitle; - } - if (contextMenu.optionTitleKey) { - contextMenu.optionTitle = this.translaterService && this.translaterService.translate && this.translaterService.getCurrentLanguage && this.translaterService.getCurrentLanguage() && this.translaterService.translate(contextMenu.optionTitleKey) || contextMenu.optionTitle; - menuOptions.optionTitle = contextMenu.optionTitle; - } - const originalCommandItems = this._userOriginalContextMenu && Array.isArray(this._userOriginalContextMenu.commandItems) ? this._userOriginalContextMenu.commandItems : []; - contextMenu.commandItems = [...originalCommandItems, ...this.addMenuCustomCommands(originalCommandItems)]; - menuOptions.commandItems = contextMenu.commandItems; // copy it also to the menuOptions else they won't be translated when locale changes - - // translate all command/options and resort them afterward - this.extensionUtility.translateItems(contextMenu.commandItems || [], 'titleKey', 'title'); - this.extensionUtility.translateItems(contextMenu.optionItems || [], 'titleKey', 'title'); - this.extensionUtility.sortItems(contextMenu.commandItems || [], 'positionOrder'); - this.extensionUtility.sortItems(contextMenu.optionItems || [], 'positionOrder'); - - // update the title options so that it has latest translated values - if (this._addon && this._addon.setOptions) { - this._addon.setOptions(menuOptions); - } - } - } - - // -- - // private functions - // ------------------ - - /** Create Context Menu with Custom Commands (copy cell value, export) */ - private addMenuCustomCommands(originalCustomItems: Array) { - const menuCustomItems: Array = []; - const gridOptions = this.sharedService && this.sharedService.gridOptions || {}; - const contextMenu = gridOptions?.contextMenu; - const dataView = this.sharedService?.dataView; - const translationPrefix = getTranslationPrefix(gridOptions); - - // 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( - { - iconCssClass: contextMenu.iconCopyCellValueCommand || 'fa fa-clone', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}COPY`, 'TEXT_COPY'), - disabled: false, - command: commandName, - positionOrder: 50, - action: (_e: Event, args: MenuCommandItemCallbackArgs) => { - this.copyToClipboard(args); - }, - itemUsabilityOverride: (args: MenuCallbackArgs) => { - // make sure there's an item to copy before enabling this command - const columnDef = args && args.column as Column; - const dataContext = args && args.dataContext; - if (typeof columnDef.queryFieldNameGetterFn === 'function') { - const cellValue = this.getCellValueFromQueryFieldGetter(columnDef, dataContext); - if (cellValue !== '' && cellValue !== undefined) { - return true; - } - } else if (columnDef && dataContext.hasOwnProperty(columnDef.field)) { - return dataContext[columnDef.field] !== '' && dataContext[columnDef.field] !== null && dataContext[columnDef.field] !== undefined; - } - return false; - } - } - ); - } - } - - // 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( - { - iconCssClass: contextMenu.iconExportCsvCommand || 'fa fa-download', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_CSV`, 'TEXT_EXPORT_TO_CSV'), - disabled: false, - command: commandName, - positionOrder: 51, - action: () => { - const registedServices = this.sharedService?.externalRegisteredResources || []; - const excelService: TextExportService = registedServices.find((service: any) => service.className === 'TextExportService'); - if (excelService?.exportToFile) { - excelService.exportToFile({ - delimiter: DelimiterType.comma, - format: FileType.csv, - }); - } else { - throw new Error(`[Slickgrid-Universal] You must register the TextExportService to properly use Export to File in the Context Menu. Example:: this.gridOptions = { enableTextExport: true, registerExternalResources: [new TextExportService()] };`); - } - }, - } - ); - } - } - - // 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( - { - iconCssClass: contextMenu.iconExportExcelCommand || 'fa fa-file-excel-o text-success', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_EXCEL`, 'TEXT_EXPORT_TO_EXCEL'), - disabled: false, - command: commandName, - positionOrder: 52, - action: () => { - const registedServices = this.sharedService?.externalRegisteredResources || []; - const excelService: ExcelExportService = registedServices.find((service: any) => service.className === 'ExcelExportService'); - if (excelService?.exportToExcel) { - excelService.exportToExcel(); - } else { - throw new Error(`[Slickgrid-Universal] You must register the ExcelExportService to properly use Export to Excel in the Context Menu. Example:: this.gridOptions = { enableExcelExport: true, registerExternalResources: [new ExcelExportService()] };`); - } - }, - } - ); - } - } - - // 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( - { - iconCssClass: contextMenu.iconExportTextDelimitedCommand || 'fa fa-download', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPORT_TO_TAB_DELIMITED`, 'TEXT_EXPORT_TO_TAB_DELIMITED'), - disabled: false, - command: commandName, - positionOrder: 53, - action: () => { - const registedServices = this.sharedService?.externalRegisteredResources || []; - const excelService: TextExportService = registedServices.find((service: any) => service.className === 'TextExportService'); - if (excelService?.exportToFile) { - excelService.exportToFile({ - delimiter: DelimiterType.tab, - format: FileType.txt, - }); - } else { - throw new Error(`[Slickgrid-Universal] You must register the TextExportService to properly use Export to File in the Context Menu. Example:: this.gridOptions = { enableTextExport: true, registerExternalResources: [new TextExportService()] };`); - } - }, - } - ); - } - } - - // -- Grouping Commands - 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 }); - } - - // 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( - { - iconCssClass: contextMenu.iconClearGroupingCommand || 'fa fa-times', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}CLEAR_ALL_GROUPING`, 'TEXT_CLEAR_ALL_GROUPING'), - disabled: false, - command: commandName, - positionOrder: 55, - action: () => dataView.setGrouping([]), - itemUsabilityOverride: () => { - // only enable the command when there's an actually grouping in play - const groupingArray = dataView && dataView.getGrouping && dataView.getGrouping(); - return Array.isArray(groupingArray) && groupingArray.length > 0; - } - } - ); - } - } - - // 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( - { - iconCssClass: contextMenu.iconCollapseAllGroupsCommand || 'fa fa-compress', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}COLLAPSE_ALL_GROUPS`, 'TEXT_COLLAPSE_ALL_GROUPS'), - disabled: false, - command: commandName, - positionOrder: 56, - action: () => { - if (gridOptions.enableTreeData) { - this.treeDataService.toggleTreeDataCollapse(true); - } else { - dataView.collapseAllGroups(); - } - }, - itemUsabilityOverride: () => { - if (gridOptions.enableTreeData) { - return true; - } - // only enable the command when there's an actually grouping in play - const groupingArray = dataView && dataView.getGrouping && dataView.getGrouping(); - return Array.isArray(groupingArray) && groupingArray.length > 0; - } - } - ); - } - } - - // 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( - { - iconCssClass: contextMenu.iconExpandAllGroupsCommand || 'fa fa-expand', - title: this.extensionUtility.translateWhenEnabledAndServiceExist(`${translationPrefix}EXPAND_ALL_GROUPS`, 'TEXT_EXPAND_ALL_GROUPS'), - disabled: false, - command: commandName, - positionOrder: 57, - action: () => { - if (gridOptions.enableTreeData) { - this.treeDataService.toggleTreeDataCollapse(false); - } else { - dataView.expandAllGroups(); - } - }, - itemUsabilityOverride: () => { - if (gridOptions.enableTreeData) { - return true; - } - // only enable the command when there's an actually grouping in play - const groupingArray = dataView && dataView.getGrouping && dataView.getGrouping(); - return Array.isArray(groupingArray) && groupingArray.length > 0; - } - } - ); - } - } - } - - return menuCustomItems; - } - - /** - * First get the value, if "exportWithFormatter" is set then we'll use the formatter output - * Then we create the DOM trick to copy a text value by creating a fake
that is not shown to the user - * and from there we can call the execCommand 'copy' command and expect the value to be in clipboard - * @param args - */ - private copyToClipboard(args: MenuCommandItemCallbackArgs) { - try { - if (args && args.grid && args.command) { - // get the value, if "exportWithFormatter" is set then we'll use the formatter output - const gridOptions = this.sharedService && this.sharedService.gridOptions || {}; - const cell = args && args.cell || 0; - const row = args && args.row || 0; - const columnDef = args && args.column; - const dataContext = args && args.dataContext; - const grid = this.sharedService && this.sharedService.slickGrid; - const exportOptions = gridOptions && (gridOptions.excelExportOptions || { ...gridOptions.exportOptions, ...gridOptions.textExportOptions }); - let textToCopy = exportWithFormatterWhenDefined(row, cell, columnDef, dataContext, grid, exportOptions); - - if (typeof columnDef.queryFieldNameGetterFn === 'function') { - textToCopy = this.getCellValueFromQueryFieldGetter(columnDef, dataContext); - } - - // create fake