Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.

Commit 7e0640e

Browse files
authored
fix(menu): context menu to copy cell with queryFieldNameGetterFn (#537)
* fix(menu): context menu to copy cell with `queryFieldNameGetterFn`
1 parent 944df30 commit 7e0640e

File tree

3 files changed

+123
-5
lines changed

3 files changed

+123
-5
lines changed

src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -990,7 +990,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
990990
// using jQuery extend to do a deep clone has an unwanted side on objects and pageSizes but ES6 spread has other worst side effects
991991
// so we will just overwrite the pageSizes when needed, this is the only one causing issues so far.
992992
// jQuery wrote this on their docs:: On a deep extend, Object and Array are extended, but object wrappers on primitive types such as String, Boolean, and Number are not.
993-
if ((gridOptions.enablePagination || gridOptions.backendServiceApi) && gridOptions.pagination && Array.isArray(gridOptions.pagination.pageSizes)) {
993+
if (options && options.pagination && (gridOptions.enablePagination || gridOptions.backendServiceApi) && gridOptions.pagination && Array.isArray(gridOptions.pagination.pageSizes)) {
994994
options.pagination.pageSizes = gridOptions.pagination.pageSizes;
995995
}
996996

src/app/modules/angular-slickgrid/extensions/__tests__/contextMenuExtension.spec.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -659,6 +659,54 @@ describe('contextMenuExtension', () => {
659659
expect(execSpy).toHaveBeenCalledWith('copy', false, 'JOHN');
660660
});
661661

662+
it('should call "copyToClipboard" and get the value even when there is a "queryFieldNameGetterFn" callback defined with dot notation the command triggered is "copy"', () => {
663+
const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption;
664+
const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName', queryFieldNameGetterFn: () => 'lastName' } as Column;
665+
const dataContextMock = { id: 123, firstName: 'John', lastName: 'Doe', age: 50 };
666+
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
667+
const execSpy = jest.spyOn(window.document, 'execCommand');
668+
extension.register();
669+
extension.register();
670+
671+
const menuItemCommand = copyGridOptionsMock.contextMenu.commandItems.find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem;
672+
menuItemCommand.action(new CustomEvent('change'), {
673+
command: 'copy',
674+
cell: 2,
675+
row: 5,
676+
grid: gridStub,
677+
column: columnMock,
678+
dataContext: dataContextMock,
679+
item: menuItemCommand,
680+
value: 'John'
681+
});
682+
683+
expect(execSpy).toHaveBeenCalledWith('copy', false, 'Doe');
684+
});
685+
686+
it('should call "copyToClipboard" and get the value even when there is a "queryFieldNameGetterFn" callback defined with dot notation the command triggered is "copy"', () => {
687+
const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption;
688+
const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName', queryFieldNameGetterFn: () => 'user.lastName' } as Column;
689+
const dataContextMock = { id: 123, user: { firstName: 'John', lastName: 'Doe', age: 50 } };
690+
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
691+
const execSpy = jest.spyOn(window.document, 'execCommand');
692+
extension.register();
693+
extension.register();
694+
695+
const menuItemCommand = copyGridOptionsMock.contextMenu.commandItems.find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem;
696+
menuItemCommand.action(new CustomEvent('change'), {
697+
command: 'copy',
698+
cell: 2,
699+
row: 5,
700+
grid: gridStub,
701+
column: columnMock,
702+
dataContext: dataContextMock,
703+
item: menuItemCommand,
704+
value: 'John'
705+
});
706+
707+
expect(execSpy).toHaveBeenCalledWith('copy', false, 'Doe');
708+
});
709+
662710
it('should expect "itemUsabilityOverride" callback from the "copy" command to return True when a value to copy is found in the dataContext object', () => {
663711
const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption;
664712
const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName' } as Column;
@@ -735,6 +783,44 @@ describe('contextMenuExtension', () => {
735783
expect(isCommandUsable).toBe(false);
736784
});
737785

786+
it('should expect "itemUsabilityOverride" callback from the "copy" command to return True when there is a "queryFieldNameGetterFn" which itself returns a value', () => {
787+
const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption;
788+
const columnMock = { id: 'firstName', name: 'First Name', field: 'firstName', queryFieldNameGetterFn: () => 'lastName' } as Column;
789+
const dataContextMock = { id: 123, firstName: null, lastName: 'Doe', age: 50 };
790+
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
791+
extension.register();
792+
793+
const menuItemCommand = copyGridOptionsMock.contextMenu.commandItems.find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem;
794+
const isCommandUsable = menuItemCommand.itemUsabilityOverride({
795+
cell: 2,
796+
row: 2,
797+
grid: gridStub,
798+
column: columnMock,
799+
dataContext: dataContextMock,
800+
});
801+
802+
expect(isCommandUsable).toBe(true);
803+
});
804+
805+
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', () => {
806+
const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: false, enableExport: false, contextMenu: { hideCopyCellValueCommand: false } } as GridOption;
807+
const columnMock = { id: 'firstName', name: 'First Name', field: 'user.firstName', queryFieldNameGetterFn: () => 'user.lastName' } as Column;
808+
const dataContextMock = { id: 123, user: { firstName: null, lastName: 'Doe', age: 50 } };
809+
jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(copyGridOptionsMock);
810+
extension.register();
811+
812+
const menuItemCommand = copyGridOptionsMock.contextMenu.commandItems.find((item: MenuCommandItem) => item.command === 'copy') as MenuCommandItem;
813+
const isCommandUsable = menuItemCommand.itemUsabilityOverride({
814+
cell: 2,
815+
row: 2,
816+
grid: gridStub,
817+
column: columnMock,
818+
dataContext: dataContextMock,
819+
});
820+
821+
expect(isCommandUsable).toBe(true);
822+
});
823+
738824
it('should call "exportToExcel" when the command triggered is "export-excel"', () => {
739825
const excelExportSpy = jest.spyOn(excelExportServiceStub, 'exportToExcel');
740826
const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, enableExport: false, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false } } as GridOption;

src/app/modules/angular-slickgrid/extensions/contextMenuExtension.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { ExportService } from '../services/export.service';
2020
import { ExcelExportService } from '../services/excelExport.service';
2121
import { TreeDataService } from '../services/treeData.service';
2222
import { exportWithFormatterWhenDefined } from '../services/export-utilities';
23-
import { getTranslationPrefix } from '../services/utilities';
23+
import { getDescendantProperty, getTranslationPrefix } from '../services/utilities';
2424

2525
// using external non-typed js libraries
2626
declare const Slick: any;
@@ -194,7 +194,12 @@ export class ContextMenuExtension implements Extension {
194194
// make sure there's an item to copy before enabling this command
195195
const columnDef = args && args.column as Column;
196196
const dataContext = args && args.dataContext;
197-
if (columnDef && dataContext.hasOwnProperty(columnDef.field)) {
197+
if (typeof columnDef.queryFieldNameGetterFn === 'function') {
198+
const cellValue = this.getCellValueFromQueryFieldGetter(columnDef, dataContext);
199+
if (cellValue !== '' && cellValue !== undefined) {
200+
return true;
201+
}
202+
} else if (columnDef && dataContext.hasOwnProperty(columnDef.field)) {
198203
return dataContext[columnDef.field] !== '' && dataContext[columnDef.field] !== null && dataContext[columnDef.field] !== undefined;
199204
}
200205
return false;
@@ -376,11 +381,15 @@ export class ContextMenuExtension implements Extension {
376381
const gridOptions = this.sharedService && this.sharedService.gridOptions || {};
377382
const cell = args && args.cell || 0;
378383
const row = args && args.row || 0;
379-
const column = args && args.column;
384+
const columnDef = args && args.column;
380385
const dataContext = args && args.dataContext;
381386
const grid = this.sharedService && this.sharedService.grid;
382387
const exportOptions = gridOptions && (gridOptions.excelExportOptions || gridOptions.exportOptions);
383-
const textToCopy = exportWithFormatterWhenDefined(row, cell, dataContext, column, grid, exportOptions);
388+
let textToCopy = exportWithFormatterWhenDefined(row, cell, dataContext, columnDef, grid, exportOptions);
389+
390+
if (typeof columnDef.queryFieldNameGetterFn === 'function') {
391+
textToCopy = this.getCellValueFromQueryFieldGetter(columnDef, dataContext);
392+
}
384393

385394
// create fake <div> to copy into clipboard & delete it from the DOM once we're done
386395
const range = document.createRange();
@@ -399,4 +408,27 @@ export class ContextMenuExtension implements Extension {
399408
}
400409
} catch (e) { }
401410
}
411+
412+
/**
413+
* When a queryFieldNameGetterFn is defined, then get the value from that getter callback function
414+
* @param columnDef
415+
* @param dataContext
416+
* @return cellValue
417+
*/
418+
private getCellValueFromQueryFieldGetter(columnDef: Column, dataContext: any): string {
419+
let cellValue = '';
420+
421+
if (typeof columnDef.queryFieldNameGetterFn === 'function') {
422+
const queryFieldName = columnDef.queryFieldNameGetterFn(dataContext);
423+
424+
// get the cell value from the item or when it's a dot notation then exploded the item and get the final value
425+
if (queryFieldName && queryFieldName.indexOf('.') >= 0) {
426+
cellValue = getDescendantProperty(dataContext, queryFieldName);
427+
} else {
428+
cellValue = dataContext[queryFieldName];
429+
}
430+
}
431+
432+
return cellValue;
433+
}
402434
}

0 commit comments

Comments
 (0)