Skip to content
This repository was archived by the owner on Jun 1, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}