Skip to content

Commit

Permalink
fix(menu): context menu to copy cell with queryFieldNameGetterFn (#537)
Browse files Browse the repository at this point in the history
* fix(menu): context menu to copy cell with `queryFieldNameGetterFn`
  • Loading branch information
ghiscoding committed Jul 20, 2020
1 parent 944df30 commit 7e0640e
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -990,7 +990,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
// using jQuery extend to do a deep clone has an unwanted side on objects and pageSizes but ES6 spread has other worst side effects
// so we will just overwrite the pageSizes when needed, this is the only one causing issues so far.
// 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.
if ((gridOptions.enablePagination || gridOptions.backendServiceApi) && gridOptions.pagination && Array.isArray(gridOptions.pagination.pageSizes)) {
if (options && options.pagination && (gridOptions.enablePagination || gridOptions.backendServiceApi) && gridOptions.pagination && Array.isArray(gridOptions.pagination.pageSizes)) {
options.pagination.pageSizes = gridOptions.pagination.pageSizes;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,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;
Expand Down Expand Up @@ -735,6 +783,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" when the command triggered is "export-excel"', () => {
const excelExportSpy = jest.spyOn(excelExportServiceStub, 'exportToExcel');
const copyGridOptionsMock = { ...gridOptionsMock, enableExcelExport: true, enableExport: false, contextMenu: { hideCopyCellValueCommand: true, hideExportCsvCommand: true, hideExportExcelCommand: false } } as GridOption;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { ExportService } from '../services/export.service';
import { ExcelExportService } from '../services/excelExport.service';
import { TreeDataService } from '../services/treeData.service';
import { exportWithFormatterWhenDefined } from '../services/export-utilities';
import { getTranslationPrefix } from '../services/utilities';
import { getDescendantProperty, getTranslationPrefix } from '../services/utilities';

// using external non-typed js libraries
declare const Slick: any;
Expand Down Expand Up @@ -194,7 +194,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;
Expand Down Expand Up @@ -376,11 +381,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 <div> to copy into clipboard & delete it from the DOM once we're done
const range = document.createRange();
Expand All @@ -399,4 +408,27 @@ export class ContextMenuExtension implements Extension {
}
} catch (e) { }
}

/**
* When a queryFieldNameGetterFn is defined, then get the value from that getter callback function
* @param columnDef
* @param dataContext
* @return cellValue
*/
private getCellValueFromQueryFieldGetter(columnDef: Column, dataContext: any): string {
let cellValue = '';

if (typeof columnDef.queryFieldNameGetterFn === 'function') {
const queryFieldName = columnDef.queryFieldNameGetterFn(dataContext);

// get the cell value from the item or when it's a dot notation then exploded the item and get the final value
if (queryFieldName && queryFieldName.indexOf('.') >= 0) {
cellValue = getDescendantProperty(dataContext, queryFieldName);
} else {
cellValue = dataContext[queryFieldName];
}
}

return cellValue;
}
}

0 comments on commit 7e0640e

Please sign in to comment.