From 53c50f9d716725330681d3617082b1fa33f90c12 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Mon, 20 Jul 2020 17:12:56 -0400 Subject: [PATCH] fix(menu): context menu to copy cell with queryFieldNameGetterFn (#21) --- .../src/examples/example50.ts | 3 - packages/common/package.json | 2 +- .../__tests__/contextMenuExtension.spec.ts | 86 ++++++++++++++++++ .../src/extensions/contextMenuExtension.ts | 40 +++++++- .../slickgrid-vanilla-bundle.zip | Bin 693944 -> 694194 bytes yarn.lock | 2 +- 6 files changed, 124 insertions(+), 9 deletions(-) diff --git a/examples/web-demo-vanilla-bundle/src/examples/example50.ts b/examples/web-demo-vanilla-bundle/src/examples/example50.ts index f97b2c913..c19a29e80 100644 --- a/examples/web-demo-vanilla-bundle/src/examples/example50.ts +++ b/examples/web-demo-vanilla-bundle/src/examples/example50.ts @@ -167,9 +167,6 @@ export class Example50 { autoResize: { container: '.demo-container', }, - contextMenu: { - hideCopyCellValueCommand: true - }, enableAutoSizeColumns: true, enableAutoResize: true, enableExport: true, diff --git a/packages/common/package.json b/packages/common/package.json index ebcf06c1a..c0f7b3974 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -58,7 +58,7 @@ "dependencies": { "dompurify": "^2.0.12", "flatpickr": "^4.6.3", - "jquery": "~3.4.1", + "jquery": "^3.4.1", "jquery-ui-dist": "^1.12.1", "lodash.isequal": "^4.5.0", "moment-mini": "^2.24.0", diff --git a/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts b/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts index 632b985aa..124348e97 100644 --- a/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts +++ b/packages/common/src/extensions/__tests__/contextMenuExtension.spec.ts @@ -615,6 +615,54 @@ describe('contextMenuExtension', () => { 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, enableExport: 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.commandItems.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, enableExport: 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.commandItems.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, enableExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption; const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName' } as Column; @@ -691,6 +739,44 @@ describe('contextMenuExtension', () => { 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, enableExport: 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.commandItems.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, enableExport: 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.commandItems.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, enableExport: false, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false } } as GridOption; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock); diff --git a/packages/common/src/extensions/contextMenuExtension.ts b/packages/common/src/extensions/contextMenuExtension.ts index b13d42963..da8c45c39 100644 --- a/packages/common/src/extensions/contextMenuExtension.ts +++ b/packages/common/src/extensions/contextMenuExtension.ts @@ -14,7 +14,7 @@ import { DelimiterType, ExtensionName, FileType, } from '../enums/index'; import { ExtensionUtility } from './extensionUtility'; import { exportWithFormatterWhenDefined } from '../services/export-utilities'; import { SharedService } from '../services/shared.service'; -import { getTranslationPrefix } from '../services/utilities'; +import { getDescendantProperty, getTranslationPrefix } from '../services/utilities'; import { ExcelExportService, FileExportService, TranslaterService, TreeDataService } from '../services/index'; // using external non-typed js libraries @@ -197,7 +197,12 @@ export class ContextMenuExtension implements Extension { // 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 (columnDef && dataContext.hasOwnProperty(columnDef.field)) { + 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; @@ -403,11 +408,15 @@ export class ContextMenuExtension implements Extension { const gridOptions = this.sharedService && this.sharedService.gridOptions || {}; const cell = args && args.cell || 0; const row = args && args.row || 0; - const column = args && args.column; + const columnDef = args && args.column; const dataContext = args && args.dataContext; const grid = this.sharedService && this.sharedService.grid; const exportOptions = gridOptions && (gridOptions.excelExportOptions || gridOptions.exportOptions); - const textToCopy = exportWithFormatterWhenDefined(row, cell, dataContext, column, grid, exportOptions); + let textToCopy = exportWithFormatterWhenDefined(row, cell, dataContext, columnDef, grid, exportOptions); + + if (typeof columnDef.queryFieldNameGetterFn === 'function') { + textToCopy = this.getCellValueFromQueryFieldGetter(columnDef, dataContext); + } // create fake