diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example13.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example13.ts
index cdd84fff9..f9f8a3f3c 100644
--- a/examples/webpack-demo-vanilla-bundle/src/examples/example13.ts
+++ b/examples/webpack-demo-vanilla-bundle/src/examples/example13.ts
@@ -63,6 +63,12 @@ export class Example13 {
container: '.demo-container',
},
enableFiltering: false,
+ enableExcelCopyBuffer: true,
+ excelCopyBufferOptions: {
+ onCopyCells: (e, args) => console.log(e, args),
+ onPasteCells: (e, args) => console.log(e, args),
+ onCopyCancelled: (e, args) => console.log(e, args),
+ },
enableCellNavigation: true,
gridHeight: 275,
headerButton: {
diff --git a/packages/common/src/enums/slickPluginList.enum.ts b/packages/common/src/enums/slickPluginList.enum.ts
index 6666261aa..1d19d4336 100644
--- a/packages/common/src/enums/slickPluginList.enum.ts
+++ b/packages/common/src/enums/slickPluginList.enum.ts
@@ -15,7 +15,13 @@ import {
SlickRowMoveManager,
SlickRowSelectionModel,
} from '../interfaces/index';
-import { AutoTooltipPlugin } from '../plugins/index';
+import {
+ AutoTooltipPlugin,
+ // CellExternalCopyManager,
+ // CellRangeDecorator,
+ // CellRangeSelector,
+ // CellSelectionModel,
+} from '../plugins/index';
export type SlickPluginList =
AutoTooltipPlugin |
diff --git a/packages/common/src/extensions/__tests__/cellExternalCopyManagerExtension.spec.ts b/packages/common/src/extensions/__tests__/cellExternalCopyManagerExtension.spec.ts
deleted file mode 100644
index 6cdf94252..000000000
--- a/packages/common/src/extensions/__tests__/cellExternalCopyManagerExtension.spec.ts
+++ /dev/null
@@ -1,359 +0,0 @@
-import { CellRange, EditCommand, ExcelCopyBufferOption, Formatter, GridOption, SlickCellExternalCopyManager, SlickGrid, SlickNamespace, } from '../../interfaces/index';
-import { Formatters } from '../../formatters';
-import { CellExternalCopyManagerExtension } from '../cellExternalCopyManagerExtension';
-import { ExtensionUtility } from '../extensionUtility';
-import { SharedService } from '../../services/shared.service';
-import { TranslateServiceStub } from '../../../../../test/translateServiceStub';
-import { BackendUtilityService } from '../../services';
-
-declare const Slick: SlickNamespace;
-jest.mock('flatpickr', () => { });
-
-const gridStub = {
- getData: jest.fn(),
- getOptions: jest.fn(),
- registerPlugin: jest.fn(),
- setSelectionModel: jest.fn(),
-} as unknown as SlickGrid;
-
-const addonStub = {
- init: jest.fn(),
- destroy: jest.fn(),
- onCopyCells: new Slick.Event(),
- onCopyCancelled: new Slick.Event(),
- onPasteCells: new Slick.Event(),
-};
-const mockAddon = jest.fn().mockImplementation(() => addonStub);
-const mockSelectionModel = jest.fn().mockImplementation(() => ({
- init: jest.fn(),
- destroy: jest.fn()
-}));
-Slick.CellExternalCopyManager = mockAddon;
-Slick.CellSelectionModel = mockSelectionModel;
-
-describe('cellExternalCopyManagerExtension', () => {
- jest.mock('slickgrid/plugins/slick.cellexternalcopymanager', () => mockAddon);
- jest.mock('slickgrid/plugins/slick.cellselectionmodel', () => mockSelectionModel);
-
- let queueCallback: EditCommand;
- const mockEventCallback = () => { };
- const mockSelectRange = [{ fromCell: 1, fromRow: 1, toCell: 1, toRow: 1 }] as CellRange[];
- const mockSelectRangeEvent = { ranges: mockSelectRange };
-
- let extension: CellExternalCopyManagerExtension;
- let extensionUtility: ExtensionUtility;
- let backendUtilityService: BackendUtilityService;
- let sharedService: SharedService;
- let translateService: TranslateServiceStub;
- const gridOptionsMock = {
- editable: true,
- enableCheckboxSelector: true,
- excelCopyBufferOptions: {
- onExtensionRegistered: jest.fn(),
- onCopyCells: mockEventCallback,
- onCopyCancelled: mockEventCallback,
- onPasteCells: mockEventCallback,
- }
- } as GridOption;
-
- beforeEach(() => {
- sharedService = new SharedService();
- backendUtilityService = new BackendUtilityService();
- translateService = new TranslateServiceStub();
- extensionUtility = new ExtensionUtility(sharedService, backendUtilityService, translateService);
- extension = new CellExternalCopyManagerExtension(extensionUtility, sharedService);
- });
-
- 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, 'slickGrid', 'get').mockReturnValue(gridStub);
- jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock);
- queueCallback = {
- execute: () => { },
- undo: () => { },
- row: 0,
- cell: 0,
- editor: {},
- serializedValue: 'serialize',
- prevSerializedValue: 'previous'
- };
- });
-
- it('should register the addon', () => {
- const pluginSpy = jest.spyOn(SharedService.prototype.slickGrid, 'registerPlugin');
- const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onExtensionRegistered');
-
- const instance = extension.register() as SlickCellExternalCopyManager;
- const addonInstance = extension.getAddonInstance();
-
- expect(instance).toBeTruthy();
- expect(instance).toEqual(addonInstance);
- expect(onRegisteredSpy).toHaveBeenCalledWith(instance);
- expect(pluginSpy).toHaveBeenCalledWith(instance);
- expect(mockSelectionModel).toHaveBeenCalled();
- expect(mockAddon).toHaveBeenCalledWith({
- clipboardCommandHandler: expect.anything(),
- dataItemColumnValueExtractor: expect.anything(),
- newRowCreator: expect.anything(),
- includeHeaderWhenCopying: false,
- readOnlyMode: false,
- onCopyCancelled: expect.anything(),
- onCopyCells: expect.anything(),
- onExtensionRegistered: expect.anything(),
- onPasteCells: expect.anything(),
- });
- });
-
- it('should call internal event handler subscribe and expect the "onCopyCells" option to be called when addon notify is called', () => {
- const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe');
- const onCopySpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onCopyCells');
- const onCancelSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onCopyCancelled');
- const onPasteSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onPasteCells');
-
- const instance = extension.register() as SlickCellExternalCopyManager;
- instance.onCopyCells.notify(mockSelectRangeEvent, new Slick.EventData(), gridStub);
-
- expect(handlerSpy).toHaveBeenCalledTimes(3);
- expect(handlerSpy).toHaveBeenCalledWith(
- { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), },
- expect.anything()
- );
- expect(onCopySpy).toHaveBeenCalledWith(expect.anything(), mockSelectRangeEvent);
- expect(onCancelSpy).not.toHaveBeenCalled();
- expect(onPasteSpy).not.toHaveBeenCalled();
- });
-
- it('should call internal event handler subscribe and expect the "onCopyCancelled" option to be called when addon notify is called', () => {
- const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe');
- const onCopySpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onCopyCells');
- const onCancelSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onCopyCancelled');
- const onPasteSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onPasteCells');
-
- const instance = extension.register() as SlickCellExternalCopyManager;
- instance.onCopyCancelled.notify(mockSelectRangeEvent, new Slick.EventData(), gridStub);
-
- expect(handlerSpy).toHaveBeenCalledTimes(3);
- expect(handlerSpy).toHaveBeenCalledWith(
- { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), },
- expect.anything()
- );
- expect(onCopySpy).not.toHaveBeenCalled();
- expect(onCancelSpy).toHaveBeenCalledWith(expect.anything(), mockSelectRangeEvent);
- expect(onPasteSpy).not.toHaveBeenCalled();
- });
-
- it('should call internal event handler subscribe and expect the "onPasteCells" option to be called when addon notify is called', () => {
- const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe');
- const onCopySpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onCopyCells');
- const onCancelSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onCopyCancelled');
- const onPasteSpy = jest.spyOn(SharedService.prototype.gridOptions.excelCopyBufferOptions as ExcelCopyBufferOption, 'onPasteCells');
-
- const instance = extension.register() as SlickCellExternalCopyManager;
- instance.onPasteCells.notify(mockSelectRangeEvent, new Slick.EventData(), gridStub);
-
- expect(handlerSpy).toHaveBeenCalledTimes(3);
- expect(handlerSpy).toHaveBeenCalledWith(
- { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), },
- expect.anything()
- );
- expect(onCopySpy).not.toHaveBeenCalled();
- expect(onCancelSpy).not.toHaveBeenCalled();
- expect(onPasteSpy).toHaveBeenCalledWith(expect.anything(), mockSelectRangeEvent);
- });
-
- it('should dispose of the addon', () => {
- const instance = extension.register() as SlickCellExternalCopyManager;
- const destroySpy = jest.spyOn(instance, 'destroy');
-
- extension.dispose();
-
- expect(destroySpy).toHaveBeenCalled();
- });
- });
-
- describe('createUndoRedo private method', () => {
- it('should create the UndoRedoBuffer', () => {
- extension.register();
-
- expect(extension.undoRedoBuffer).toEqual({
- queueAndExecuteCommand: expect.anything(),
- undo: expect.anything(),
- redo: expect.anything(),
- });
- });
-
- it('should have called Edit Command "execute" method after creating the UndoRedoBuffer', () => {
- extension.register();
- const undoRedoBuffer = extension.undoRedoBuffer;
-
- const spy = jest.spyOn(queueCallback, 'execute');
- undoRedoBuffer.queueAndExecuteCommand(queueCallback);
-
- expect(spy).toHaveBeenCalled();
- });
-
- it('should not have called Edit Command "undo" method when there is nothing to undo', () => {
- extension.register();
- const undoRedoBuffer = extension.undoRedoBuffer;
-
- const spy = jest.spyOn(queueCallback, 'undo');
- undoRedoBuffer.undo();
-
- expect(spy).not.toHaveBeenCalled();
- });
-
- it('should have called Edit Command "undo" method after calling it from UndoRedoBuffer', () => {
- extension.register();
- const undoRedoBuffer = extension.undoRedoBuffer;
-
- const spy = jest.spyOn(queueCallback, 'undo');
- undoRedoBuffer.queueAndExecuteCommand(queueCallback);
- undoRedoBuffer.undo();
-
- expect(spy).toHaveBeenCalled();
- });
-
- it('should have called Edit Command "execute" method only at first queueing, the "redo" should not call the "execute" method by itself', () => {
- extension.register();
- const undoRedoBuffer = extension.undoRedoBuffer;
-
- const spy = jest.spyOn(queueCallback, 'execute');
- undoRedoBuffer.queueAndExecuteCommand(queueCallback);
- undoRedoBuffer.redo();
-
- expect(spy).toHaveBeenCalledTimes(1);
- });
-
- it('should have called Edit Command "execute" method at first queueing & then inside the "redo" since we did an "undo" just before', () => {
- extension.register();
- const undoRedoBuffer = extension.undoRedoBuffer;
-
- const spy = jest.spyOn(queueCallback, 'execute');
- undoRedoBuffer.queueAndExecuteCommand(queueCallback);
- undoRedoBuffer.undo();
- undoRedoBuffer.redo();
-
- expect(spy).toHaveBeenCalledTimes(2);
- });
-
- it('should have a single entry in the queue buffer after calling "queueAndExecuteCommand" once', () => {
- extension.register();
- extension.undoRedoBuffer.queueAndExecuteCommand(queueCallback);
- expect(extension.commandQueue).toHaveLength(1);
- });
-
- it('should call a redo when Ctrl+Shift+Z keyboard event occurs', () => {
- extension.register();
- const spy = jest.spyOn(queueCallback, 'execute');
-
- extension.undoRedoBuffer.queueAndExecuteCommand(queueCallback);
- const body = window.document.body;
- body.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
- keyCode: 90,
- ctrlKey: true,
- shiftKey: true,
- bubbles: true,
- cancelable: true
- }));
-
- expect(spy).toHaveBeenCalledTimes(2);
- });
-
- it('should call a undo when Ctrl+Z keyboard event occurs', () => {
- extension.register();
- const spy = jest.spyOn(queueCallback, 'undo');
-
- extension.undoRedoBuffer.queueAndExecuteCommand(queueCallback);
- const body = window.document.body;
- body.dispatchEvent(new (window.window as any).KeyboardEvent('keydown', {
- keyCode: 90,
- ctrlKey: true,
- shiftKey: false,
- bubbles: true
- }));
-
- expect(spy).toHaveBeenCalled();
- });
- });
-
- describe('addonOptions callbacks', () => {
- it('should expect "queueAndExecuteCommand" to be called after calling "clipboardCommandHandler" callback', () => {
- extension.register() as SlickCellExternalCopyManager;
- const spy = jest.spyOn(extension.undoRedoBuffer, 'queueAndExecuteCommand');
-
- extension.addonOptions!.clipboardCommandHandler!(queueCallback);
-
- expect(spy).toHaveBeenCalled();
- });
-
- it('should expect "addItem" method to be called after calling "newRowCreator" callback', () => {
- extension.register();
- const mockGetData = { addItem: jest.fn() };
- const getDataSpy = jest.spyOn(gridStub, 'getData').mockReturnValue(mockGetData);
- const addItemSpy = jest.spyOn(mockGetData, 'addItem');
-
- extension.addonOptions!.newRowCreator!(2);
-
- expect(getDataSpy).toHaveBeenCalled();
- expect(addItemSpy).toHaveBeenCalledWith(expect.objectContaining({ id: 'newRow_0' }));
- expect(addItemSpy).toHaveBeenCalledWith(expect.objectContaining({ id: 'newRow_1' }));
- });
-
- it('should expect a formatted output after calling "dataItemColumnValueExtractor" callback', () => {
- extension.register();
- const output = extension.addonOptions!.dataItemColumnValueExtractor!({ firstName: 'John', lastName: 'Doe' }, { id: 'firstName', field: 'firstName', exportWithFormatter: true, formatter: Formatters.bold });
- expect(output).toBe('John');
- });
-
- it('should expect a sanitized formatted and empty output after calling "dataItemColumnValueExtractor" callback', () => {
- gridOptionsMock.textExportOptions = { sanitizeDataExport: true };
- const myBoldFormatter: Formatter = (_row, _cell, value) => value ? { text: `${value}` } : null as any;
- jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock);
- extension.register();
-
- const output = extension.addonOptions!.dataItemColumnValueExtractor!({ firstName: 'John', lastName: null }, { id: 'lastName', field: 'lastName', exportWithFormatter: true, formatter: myBoldFormatter });
-
- expect(output).toBe('');
- });
-
- it('should expect a sanitized formatted output after calling "dataItemColumnValueExtractor" callback', () => {
- gridOptionsMock.textExportOptions = { sanitizeDataExport: true };
- jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock);
- extension.register();
-
- const output = extension.addonOptions!.dataItemColumnValueExtractor!({ firstName: 'John', lastName: 'Doe' }, { id: 'firstName', field: 'firstName', exportWithFormatter: true, formatter: Formatters.bold });
-
- expect(output).toBe('John');
- });
-
- it('should expect a sanitized formatted output, from a Custom Formatter, after calling "dataItemColumnValueExtractor" callback', () => {
- const myBoldFormatter: Formatter = (_row, _cell, value) => value ? { text: `${value}` } : '';
- gridOptionsMock.textExportOptions = { sanitizeDataExport: true };
- jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock);
- extension.register();
-
- const output = extension.addonOptions!.dataItemColumnValueExtractor!({ firstName: 'John', lastName: 'Doe' }, { id: 'firstName', field: 'firstName', exportWithFormatter: true, formatter: myBoldFormatter });
-
- expect(output).toBe('John');
- });
-
- it('should return null when calling "dataItemColumnValueExtractor" callback without editable', () => {
- gridOptionsMock.editable = false;
- jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock);
- extension.register();
-
- const output = extension.addonOptions!.dataItemColumnValueExtractor!({ firstName: 'John', lastName: 'Doe' }, { id: 'firstName', field: 'firstName' });
-
- expect(output).toBeNull();
- });
- });
-});
diff --git a/packages/common/src/extensions/index.ts b/packages/common/src/extensions/index.ts
index 128b46a32..b6fbc6a05 100644
--- a/packages/common/src/extensions/index.ts
+++ b/packages/common/src/extensions/index.ts
@@ -1,4 +1,3 @@
-export * from './cellExternalCopyManagerExtension';
export * from './checkboxSelectorExtension';
export * from './extensionUtility';
export * from './rowDetailViewExtension';
diff --git a/packages/common/src/interfaces/excelCopyBufferOption.interface.ts b/packages/common/src/interfaces/excelCopyBufferOption.interface.ts
index b622c5e13..4c840e4c0 100644
--- a/packages/common/src/interfaces/excelCopyBufferOption.interface.ts
+++ b/packages/common/src/interfaces/excelCopyBufferOption.interface.ts
@@ -2,9 +2,9 @@ import {
Column,
CellRange,
FormatterResultObject,
- SlickCellExternalCopyManager,
SlickEventData,
} from './index';
+import { CellExcelCopyManager, } from '../plugins/cellExcelCopyManager';
export interface ExcelCopyBufferOption {
/** defaults to "copied", sets the css className used for copied cells. */
@@ -48,7 +48,7 @@ export interface ExcelCopyBufferOption {
// ------------
/** Fired after extension (plugin) is registered by SlickGrid */
- onExtensionRegistered?: (plugin: SlickCellExternalCopyManager) => void;
+ onExtensionRegistered?: (plugin: CellExcelCopyManager) => void;
/** Fired when a copy cell is triggered */
onCopyCells?: (e: SlickEventData, args: { ranges: CellRange[] }) => void;
diff --git a/packages/common/src/interfaces/slickCellRangeSelector.interface.ts b/packages/common/src/interfaces/slickCellRangeSelector.interface.ts
index 6c4153e38..6475a7e55 100644
--- a/packages/common/src/interfaces/slickCellRangeSelector.interface.ts
+++ b/packages/common/src/interfaces/slickCellRangeSelector.interface.ts
@@ -1,6 +1,7 @@
import { SlickCellRangeDecorator, SlickGrid, SlickRange } from './index';
import { CellRange } from './cellRange.interface';
import { SlickEvent } from './slickEvent.interface';
+// import { CellRangeDecorator, } from '../plugins/slickCellRangeDecorator';
export interface SlickCellRangeSelector {
pluginName: 'CellRangeSelector'
diff --git a/packages/common/src/interfaces/slickRange.interface.ts b/packages/common/src/interfaces/slickRange.interface.ts
index d9164b253..f904ffe86 100644
--- a/packages/common/src/interfaces/slickRange.interface.ts
+++ b/packages/common/src/interfaces/slickRange.interface.ts
@@ -12,33 +12,15 @@ export interface SlickRange extends CellRange {
*/
constructor: (fromRow: number, fromCell: number, toRow: number, toCell: number) => void;
- /**
- * Returns whether a range contains a given cell.
- * @method contains
- * @param row {Integer}
- * @param cell {Integer}
- * @return {Boolean}
- */
- contains?: (row: number, cell: number) => boolean;
+ /** Returns whether a range represents a single row. */
+ isSingleRow: () => boolean;
- /**
- * Returns whether a range represents a single cell.
- * @method isSingleCell
- * @return {Boolean}
- */
- isSingleCell?: () => boolean;
+ /** Returns whether a range represents a single cell. */
+ isSingleCell: () => boolean;
- /**
- * Returns whether a range represents a single row.
- * @method isSingleRow
- * @return {Boolean}
- */
- isSingleRow?: () => boolean;
+ /** Returns whether a range contains a given cell. */
+ contains: (row: number, cell: number) => boolean;
- /**
- * Returns a readable representation of a range.
- * @method toString
- * @return {String}
- */
- toString?: () => string;
+ /** Returns a readable representation of a range. */
+ toString: () => string;
}
diff --git a/packages/common/src/extensions/cellExternalCopyManagerExtension.ts b/packages/common/src/plugins/cellExcelCopyManager.ts
similarity index 51%
rename from packages/common/src/extensions/cellExternalCopyManagerExtension.ts
rename to packages/common/src/plugins/cellExcelCopyManager.ts
index 0d1537bd2..59f0b6374 100644
--- a/packages/common/src/extensions/cellExternalCopyManagerExtension.ts
+++ b/packages/common/src/plugins/cellExcelCopyManager.ts
@@ -1,38 +1,44 @@
-import 'slickgrid/plugins/slick.cellexternalcopymanager';
-
import {
+ // TypeScript Helper
+ GetSlickEventType,
+
Column,
EditCommand,
EditUndoRedoBuffer,
ExcelCopyBufferOption,
- Extension,
- SlickCellExternalCopyManager,
- SlickCellSelectionModel,
+ GridOption,
SlickDataView,
SlickEventHandler,
+ SlickGrid,
SlickNamespace,
-
- // TypeScript Helper
- GetSlickEventType,
} from '../interfaces/index';
-import { ExtensionUtility } from './extensionUtility';
import { BindingEventService } from '../services/bindingEvent.service';
-import { SharedService } from '../services/shared.service';
import { sanitizeHtmlToText } from '../services/domUtilities';
+import { CellExternalCopyManager, CellSelectionModel } from './index';
// using external SlickGrid JS libraries
declare const Slick: SlickNamespace;
-export class CellExternalCopyManagerExtension implements Extension {
- private _addon: SlickCellExternalCopyManager | null = null;
- private _addonOptions: ExcelCopyBufferOption | null = null;
- private _cellSelectionModel!: SlickCellSelectionModel;
- private _eventHandler: SlickEventHandler;
- private _commandQueue!: EditCommand[];
- private _undoRedoBuffer!: EditUndoRedoBuffer;
- private _bindingEventService: BindingEventService;
-
- constructor(private readonly extensionUtility: ExtensionUtility, private readonly sharedService: SharedService) {
+/*
+ This manager enables users to copy/paste data from/to an external Spreadsheet application
+ such as MS-Excel® or OpenOffice-Spreadsheet.
+
+ Since it is not possible to access directly the clipboard in javascript, the plugin uses
+ a trick to do it's job. After detecting the keystroke, we dynamically create a textarea
+ where the browser copies/pastes the serialized data.
+*/
+export class CellExcelCopyManager {
+ protected _addonOptions!: ExcelCopyBufferOption;
+ protected _bindingEventService: BindingEventService;
+ protected _cellExternalCopyManagerPlugin!: CellExternalCopyManager;
+ protected _cellSelectionModel!: CellSelectionModel;
+ protected _commandQueue!: EditCommand[];
+ protected _eventHandler: SlickEventHandler;
+ protected _grid!: SlickGrid;
+ protected _undoRedoBuffer!: EditUndoRedoBuffer;
+ pluginName = 'CellExcelCopyManager';
+
+ constructor() {
this._eventHandler = new Slick.EventHandler() as SlickEventHandler;
this._bindingEventService = new BindingEventService();
}
@@ -49,74 +55,58 @@ export class CellExternalCopyManagerExtension implements Extension {
return this._commandQueue;
}
- get undoRedoBuffer(): EditUndoRedoBuffer {
- return this._undoRedoBuffer;
- }
-
- /** Dispose of the 3rd party addon (plugin) */
- dispose() {
- // unsubscribe all SlickGrid events
- this._eventHandler.unsubscribeAll();
- if (this._addon && this._addon.destroy) {
- this._addon.destroy();
- }
- if (this._cellSelectionModel?.destroy) {
- this._cellSelectionModel.destroy();
- }
- this.extensionUtility.nullifyFunctionNameStartingWithOn(this._addonOptions);
- this._addonOptions = null;
- this._bindingEventService.unbindAll();
+ get gridOptions(): GridOption {
+ return this._grid?.getOptions?.() ?? {};
}
- /** Get the instance of the SlickGrid addon (control or plugin). */
- getAddonInstance(): SlickCellExternalCopyManager | null {
- return this._addon;
+ get undoRedoBuffer(): EditUndoRedoBuffer {
+ return this._undoRedoBuffer;
}
- /** Register the 3rd party addon (plugin) */
- register(): SlickCellExternalCopyManager | null {
- if (this.sharedService && this.sharedService.slickGrid && this.sharedService.gridOptions) {
- this.createUndoRedoBuffer();
- this._bindingEventService.bind(document.body, 'keydown', this.handleKeyDown.bind(this) as EventListener);
-
- this._addonOptions = { ...this.getDefaultOptions(), ...this.sharedService.gridOptions.excelCopyBufferOptions } as ExcelCopyBufferOption;
- this._cellSelectionModel = new Slick.CellSelectionModel() as SlickCellSelectionModel;
- this.sharedService.slickGrid.setSelectionModel(this._cellSelectionModel);
- this._addon = new Slick.CellExternalCopyManager(this._addonOptions);
- if (this._addon) {
- this.sharedService.slickGrid.registerPlugin(this._addon);
+ init(grid: SlickGrid, options?: ExcelCopyBufferOption) {
+ this._grid = grid;
+ this.createUndoRedoBuffer();
+ this._cellSelectionModel = new CellSelectionModel();
+ this._grid.setSelectionModel(this._cellSelectionModel as any);
+ this._bindingEventService.bind(document.body, 'keydown', this.handleBodyKeyDown.bind(this) as EventListener);
+ this._addonOptions = { ...this.getDefaultOptions(), ...options } as ExcelCopyBufferOption;
+ this._cellExternalCopyManagerPlugin = new CellExternalCopyManager();
+ this._cellExternalCopyManagerPlugin.init(this._grid, this._addonOptions);
+
+ const onCopyCellsHandler = this._cellExternalCopyManagerPlugin.onCopyCells;
+ (this._eventHandler as SlickEventHandler>).subscribe(onCopyCellsHandler, (e, args) => {
+ if (this._addonOptions && typeof this._addonOptions.onCopyCells === 'function') {
+ this._addonOptions.onCopyCells(e, args);
}
+ });
- // hook to all possible events
- if (this.sharedService.slickGrid && this._addonOptions) {
- if (this._addon && this._addonOptions.onExtensionRegistered) {
- this._addonOptions.onExtensionRegistered(this._addon);
- }
+ const onCopyCancelledHandler = this._cellExternalCopyManagerPlugin.onCopyCancelled;
+ (this._eventHandler as SlickEventHandler>).subscribe(onCopyCancelledHandler, (e, args) => {
+ if (this._addonOptions && typeof this._addonOptions.onCopyCancelled === 'function') {
+ this._addonOptions.onCopyCancelled(e, args);
+ }
+ });
- const onCopyCellsHandler = this._addon.onCopyCells;
- (this._eventHandler as SlickEventHandler>).subscribe(onCopyCellsHandler, (e, args) => {
- if (this._addonOptions && typeof this._addonOptions.onCopyCells === 'function') {
- this._addonOptions.onCopyCells(e, args);
- }
- });
+ const onPasteCellsHandler = this._cellExternalCopyManagerPlugin.onPasteCells;
+ (this._eventHandler as SlickEventHandler>).subscribe(onPasteCellsHandler, (e, args) => {
+ if (this._addonOptions && typeof this._addonOptions.onPasteCells === 'function') {
+ this._addonOptions.onPasteCells(e, args);
+ }
+ });
+ }
- const onCopyCancelledHandler = this._addon.onCopyCancelled;
- (this._eventHandler as SlickEventHandler>).subscribe(onCopyCancelledHandler, (e, args) => {
- if (this._addonOptions && typeof this._addonOptions.onCopyCancelled === 'function') {
- this._addonOptions.onCopyCancelled(e, args);
- }
- });
+ /** @deprecated @use `dispose` Destroy plugin. */
+ destroy() {
+ this.dispose();
+ }
- const onPasteCellsHandler = this._addon.onPasteCells;
- (this._eventHandler as SlickEventHandler>).subscribe(onPasteCellsHandler, (e, args) => {
- if (this._addonOptions && typeof this._addonOptions.onPasteCells === 'function') {
- this._addonOptions.onPasteCells(e, args);
- }
- });
- }
- return this._addon;
- }
- return null;
+ /** Dispose of the 3rd party addon (plugin) */
+ dispose() {
+ // unsubscribe all SlickGrid events
+ this._eventHandler.unsubscribeAll();
+ this._bindingEventService.unbindAll();
+ this._cellSelectionModel?.dispose();
+ this._cellExternalCopyManagerPlugin?.dispose();
}
/** Create an undo redo buffer used by the Excel like copy */
@@ -164,11 +154,11 @@ export class CellExternalCopyManagerExtension implements Extension {
dataItemColumnValueExtractor: (item: any, columnDef: Column) => {
// when grid or cell is not editable, we will possibly evaluate the Formatter if it was passed
// to decide if we evaluate the Formatter, we will use the same flag from Export which is "exportWithFormatter"
- if (!this.sharedService.gridOptions.editable || !columnDef.editor) {
- const textExportOptions = { ...this.sharedService.gridOptions.exportOptions, ...this.sharedService.gridOptions.textExportOptions };
+ if (!this.gridOptions.editable || !columnDef.editor) {
+ const textExportOptions = { ...this.gridOptions.exportOptions, ...this.gridOptions.textExportOptions };
const isEvaluatingFormatter = (columnDef.exportWithFormatter !== undefined) ? columnDef.exportWithFormatter : (textExportOptions?.exportWithFormatter);
if (columnDef.formatter && isEvaluatingFormatter) {
- const formattedOutput = columnDef.formatter(0, 0, item[columnDef.field], columnDef, item, this.sharedService.slickGrid);
+ const formattedOutput = columnDef.formatter(0, 0, item[columnDef.field], columnDef, item, this._grid);
if (columnDef.sanitizeDataExport || (textExportOptions?.sanitizeDataExport)) {
let outputString = formattedOutput as string;
if (formattedOutput && typeof formattedOutput === 'object' && formattedOutput.hasOwnProperty('text')) {
@@ -191,14 +181,14 @@ export class CellExternalCopyManagerExtension implements Extension {
includeHeaderWhenCopying: false,
newRowCreator: (count: number) => {
for (let i = 0; i < count; i++) {
- this.sharedService.slickGrid.getData().addItem({ id: `newRow_${newRowIds++}` });
+ this._grid.getData().addItem({ id: `newRow_${newRowIds++}` });
}
}
};
}
/** Hook an undo shortcut key hook that will redo/undo the copy buffer using Ctrl+(Shift)+Z keyboard events */
- private handleKeyDown(e: KeyboardEvent) {
+ private handleBodyKeyDown(e: KeyboardEvent) {
const keyCode = e.keyCode || e.code;
if (keyCode === 90 && (e.ctrlKey || e.metaKey)) {
if (e.shiftKey) {
@@ -208,4 +198,4 @@ export class CellExternalCopyManagerExtension implements Extension {
}
}
}
-}
+}
\ No newline at end of file
diff --git a/packages/common/src/plugins/cellExternalCopyManager.ts b/packages/common/src/plugins/cellExternalCopyManager.ts
new file mode 100644
index 000000000..33b0f64eb
--- /dev/null
+++ b/packages/common/src/plugins/cellExternalCopyManager.ts
@@ -0,0 +1,469 @@
+import { CellRange, Column, ExcelCopyBufferOption, SlickGrid, SlickNamespace } from '../interfaces/index';
+
+// using external SlickGrid JS libraries
+declare const Slick: SlickNamespace;
+
+/*
+ This manager enables users to copy/paste data from/to an external Spreadsheet application
+ such as MS-Excel® or OpenOffice-Spreadsheet.
+
+ Since it is not possible to access directly the clipboard in javascript, the plugin uses
+ a trick to do it's job. After detecting the keystroke, we dynamically create a textarea
+ where the browser copies/pastes the serialized data.
+*/
+export class CellExternalCopyManager {
+ protected _clipCommand: any;
+ protected _grid!: SlickGrid;
+ protected _copiedRanges!: CellRange[] | null;
+ protected _addonOptions!: ExcelCopyBufferOption;
+ protected _copiedCellStyleLayerKey = 'copy-manager';
+ protected _copiedCellStyle = 'copied';
+ protected _clearCopyTI: any = 0;
+ protected _bodyElement = document.body;
+ protected _onCopyInit?: () => void;
+ protected _onCopySuccess?: (rowCount: number) => void;
+ pluginName = 'CellExternalCopyManager';
+ onCopyCells = new Slick.Event();
+ onCopyCancelled = new Slick.Event();
+ onPasteCells = new Slick.Event();
+
+ keyCodes = {
+ 'C': 67,
+ 'V': 86,
+ 'ESC': 27,
+ 'INSERT': 45
+ };
+
+ init(grid: SlickGrid, options: ExcelCopyBufferOption) {
+ this._grid = grid;
+ this._addonOptions = { ...this._addonOptions, ...options };
+ this._copiedCellStyleLayerKey = this._addonOptions.copiedCellStyleLayerKey || 'copy-manager';
+ this._copiedCellStyle = this._addonOptions.copiedCellStyle || 'copied';
+ this._clearCopyTI = 0;
+ this._bodyElement = this._addonOptions.bodyElement || document.body;
+ this._onCopyInit = this._addonOptions.onCopyInit || undefined;
+ this._onCopySuccess = this._addonOptions.onCopySuccess || undefined;
+ this._grid.onKeyDown.subscribe(this.handleKeyDown.bind(this));
+
+ // we need a cell selection model
+ const cellSelectionModel = grid.getSelectionModel();
+ if (!cellSelectionModel) {
+ throw new Error(`Selection model is mandatory for this plugin. Please set a selection model on the grid before adding this plugin: grid.setSelectionModel(new Slick.CellSelectionModel())`);
+ }
+ // we give focus on the grid when a selection is done on it.
+ // without this, if the user selects a range of cell without giving focus on a particular cell, the grid doesn't get the focus and key stroke handles (ctrl+c) don't work
+ cellSelectionModel.onSelectedRangesChanged.subscribe(() => {
+ this._grid.focus();
+ });
+ }
+
+ /** @deprecated @use `dispose` Destroy plugin. */
+ destroy() {
+ this.dispose();
+ }
+
+ dispose() {
+ this._grid.onKeyDown.unsubscribe(this.handleKeyDown.bind(this));
+ }
+
+ getHeaderValueForColumn(columnDef: Column) {
+ if (this._addonOptions.headerColumnValueExtractor) {
+ const val = this._addonOptions.headerColumnValueExtractor(columnDef);
+ if (val) {
+ return val;
+ }
+ }
+
+ return columnDef.name;
+ }
+
+ getDataItemValueForColumn(item: any, columnDef: Column, event: Event) {
+ if (this._addonOptions.dataItemColumnValueExtractor) {
+ const val = this._addonOptions.dataItemColumnValueExtractor(item, columnDef);
+ if (val) {
+ return val;
+ }
+ }
+
+ let retVal = '';
+
+ // if a custom getter is not defined, we call serializeValue of the editor to serialize
+ if (columnDef.editor) {
+ const editorArgs = {
+ container: document.createElement('p'), // a dummy container
+ column: columnDef,
+ position: { top: 0, left: 0 }, // a dummy position required by some editors
+ grid: this._grid,
+ event,
+ };
+ const editor = new (columnDef as any).editor(editorArgs);
+ editor.loadValue(item);
+ retVal = editor.serializeValue();
+ editor.destroy();
+ } else {
+ retVal = item[columnDef.field];
+ }
+
+ return retVal;
+ }
+
+ setDataItemValueForColumn(item: any, columnDef: Column, value: any): any | void {
+ if (!columnDef.denyPaste) {
+ if (this._addonOptions.dataItemColumnValueSetter) {
+ return this._addonOptions.dataItemColumnValueSetter(item, columnDef, value);
+ }
+
+ // if a custom setter is not defined, we call applyValue of the editor to unserialize
+ if (columnDef.editor) {
+ const editorArgs = {
+ container: document.body, // a dummy container
+ column: columnDef,
+ position: { 'top': 0, 'left': 0 }, // a dummy position required by some editors
+ grid: this._grid
+ };
+ const editor = new (columnDef as any).editor(editorArgs);
+ editor.loadValue(item);
+ editor.applyValue(item, value);
+ editor.destroy();
+ } else {
+ item[columnDef.field] = value;
+ }
+ }
+ }
+
+ protected createTextBox(innerText: string) {
+ const ta = document.createElement('textarea');
+ ta.style.position = 'absolute';
+ ta.style.left = '-1000px';
+ ta.style.top = document.body.scrollTop + 'px';
+ ta.value = innerText;
+ this._bodyElement.appendChild(ta);
+ ta.select();
+
+ return ta;
+ }
+
+ protected decodeTabularData(grid: SlickGrid, ta: HTMLTextAreaElement) {
+ const columns = grid.getColumns();
+ const clipText = ta.value;
+ const clipRows = clipText.split(/[\n\f\r]/);
+ // trim trailing CR if present
+ if (clipRows[clipRows.length - 1] === '') { clipRows.pop(); }
+
+ const clippedRange: any[] = [];
+ let j = 0;
+
+ this._bodyElement.removeChild(ta);
+ for (let i = 0; i < clipRows.length; i++) {
+ if (clipRows[i] !== '') {
+ clippedRange[j++] = clipRows[i].split('\t');
+ } else {
+ clippedRange[j++] = [''];
+ }
+ }
+ const selectedCell = this._grid.getActiveCell();
+ const ranges = this._grid.getSelectionModel().getSelectedRanges();
+ const selectedRange = ranges && ranges.length ? ranges[0] : null; // pick only one selection
+ let activeRow: number;
+ let activeCell: number;
+
+ if (selectedRange) {
+ activeRow = selectedRange.fromRow;
+ activeCell = selectedRange.fromCell;
+ } else if (selectedCell) {
+ activeRow = selectedCell.row;
+ activeCell = selectedCell.cell;
+ } else {
+ return; // we don't know where to paste
+ }
+
+ let oneCellToMultiple = false;
+ let destH = clippedRange.length;
+ let destW = clippedRange.length ? clippedRange[0].length : 0;
+ if (clippedRange.length === 1 && clippedRange[0].length === 1 && selectedRange) {
+ oneCellToMultiple = true;
+ destH = selectedRange.toRow - selectedRange.fromRow + 1;
+ destW = selectedRange.toCell - selectedRange.fromCell + 1;
+ }
+ const availableRows = (this._grid.getData() as any[]).length - activeRow;
+ let addRows = 0;
+
+ // ignore new rows if we don't have a "newRowCreator"
+ if (availableRows < destH && this._addonOptions.newRowCreator) {
+ const d: any[] = this._grid.getData();
+ for (addRows = 1; addRows <= destH - availableRows; addRows++) {
+ d.push({});
+ }
+ this._grid.setData(d);
+ this._grid.render();
+ }
+
+ const overflowsBottomOfGrid = activeRow + destH > this._grid.getDataLength();
+
+ if (this._addonOptions.newRowCreator && overflowsBottomOfGrid) {
+ const newRowsNeeded = activeRow + destH - this._grid.getDataLength();
+ this._addonOptions.newRowCreator(newRowsNeeded);
+ }
+
+ this._clipCommand = {
+ isClipboardCommand: true,
+ clippedRange,
+ oldValues: [],
+ cellExternalCopyManager: this,
+ _options: this._addonOptions,
+ setDataItemValueForColumn: this.setDataItemValueForColumn,
+ markCopySelection: this.markCopySelection,
+ oneCellToMultiple,
+ activeRow,
+ activeCell,
+ destH,
+ destW,
+ maxDestY: this._grid.getDataLength(),
+ maxDestX: this._grid.getColumns().length,
+ h: 0,
+ w: 0,
+
+ execute: () => {
+ this._clipCommand.h = 0;
+ for (let y = 0; y < this._clipCommand.destH; y++) {
+ this._clipCommand.oldValues[y] = [];
+ this._clipCommand.w = 0;
+ this._clipCommand.h++;
+ for (let x = 0; x < this._clipCommand.destW; x++) {
+ this._clipCommand.w++;
+ const desty = activeRow + y;
+ const destx = activeCell + x;
+
+ if (desty < this._clipCommand.maxDestY && destx < this._clipCommand.maxDestX) {
+ // const nd = this._grid.getCellNode(desty, destx);
+ const dt = this._grid.getDataItem(desty);
+ this._clipCommand.oldValues[y][x] = dt[columns[destx]['field']];
+ if (oneCellToMultiple) {
+ this.setDataItemValueForColumn(dt, columns[destx], clippedRange[0][0]);
+ } else {
+ this.setDataItemValueForColumn(dt, columns[destx], clippedRange[y] ? clippedRange[y][x] : '');
+ }
+ this._grid.updateCell(desty, destx);
+ this._grid.onCellChange.notify({
+ row: desty,
+ cell: destx,
+ item: dt,
+ grid: this._grid,
+ column: {} as unknown as Column,
+ });
+
+ }
+ }
+ }
+
+ const bRange = {
+ fromCell: activeCell,
+ fromRow: activeRow,
+ toCell: activeCell + this._clipCommand.w - 1,
+ toRow: activeRow + this._clipCommand.h - 1
+ };
+
+ this.markCopySelection([bRange]);
+ this._grid.getSelectionModel().setSelectedRanges([bRange]);
+ this.onPasteCells.notify({ ranges: [bRange] });
+ },
+
+ undo: () => {
+ for (let y = 0; y < this._clipCommand.destH; y++) {
+ for (let x = 0; x < this._clipCommand.destW; x++) {
+ const desty = activeRow + y;
+ const destx = activeCell + x;
+
+ if (desty < this._clipCommand.maxDestY && destx < this._clipCommand.maxDestX) {
+ // const nd = this._grid.getCellNode(desty, destx);
+ const dt = this._grid.getDataItem(desty);
+ if (oneCellToMultiple) {
+ this.setDataItemValueForColumn(dt, columns[destx], this._clipCommand.oldValues[0][0]);
+ } else {
+ this.setDataItemValueForColumn(dt, columns[destx], this._clipCommand.oldValues[y][x]);
+ }
+ this._grid.updateCell(desty, destx);
+ this._grid.onCellChange.notify({
+ row: desty,
+ cell: destx,
+ item: dt,
+ grid: this._grid,
+ column: {} as unknown as Column,
+ });
+ }
+ }
+ }
+
+ const bRange = {
+ fromCell: activeCell,
+ fromRow: activeRow,
+ toCell: activeCell + this._clipCommand.w - 1,
+ toRow: activeRow + this._clipCommand.h - 1
+ };
+
+ this.markCopySelection([bRange]);
+ this._grid.getSelectionModel().setSelectedRanges([bRange]);
+ this.onPasteCells.notify({ ranges: [bRange] });
+ if (this._addonOptions.onPasteCells) {
+ this._addonOptions.onPasteCells.call(this, new Slick.EventData(), { ranges: [bRange] });
+ }
+
+ if (addRows > 1) {
+ const d = this._grid.getData() as any[];
+ for (; addRows > 1; addRows--) {
+ d.splice(d.length - 1, 1);
+ }
+ this._grid.setData(d);
+ this._grid.render();
+ }
+ }
+ };
+
+ if (this._addonOptions.clipboardCommandHandler) {
+ this._addonOptions.clipboardCommandHandler(this._clipCommand);
+ }
+ else {
+ this._clipCommand.execute();
+ }
+ }
+
+
+ handleKeyDown(e: any): boolean | void {
+ let ranges;
+ if (!this._grid.getEditorLock().isActive() || this._grid.getOptions().autoEdit) {
+ if (e.which === this.keyCodes.ESC) {
+ if (this._copiedRanges) {
+ e.preventDefault();
+ this.clearCopySelection();
+ this.onCopyCancelled.notify({ ranges: this._copiedRanges });
+ if (this._addonOptions.onCopyCancelled) {
+ this._addonOptions.onCopyCancelled.call(this, e, { ranges: this._copiedRanges });
+ }
+ this._copiedRanges = null;
+ }
+ }
+
+ if ((e.which === this.keyCodes.C || e.which === this.keyCodes.INSERT) && (e.ctrlKey || e.metaKey) && !e.shiftKey) { // CTRL+C or CTRL+INS
+ if (this._onCopyInit) {
+ // @ts-ignore
+ this._onCopyInit.call();
+ }
+ ranges = this._grid.getSelectionModel().getSelectedRanges();
+ if (ranges.length !== 0) {
+ this._copiedRanges = ranges;
+ this.markCopySelection(ranges);
+ this.onCopyCells.notify({ ranges });
+ if (this._addonOptions.onCopyCells) {
+ this._addonOptions.onCopyCells.call(this, e, { ranges });
+ }
+
+ const columns = this._grid.getColumns();
+ let clipText = '';
+
+ for (let rg = 0; rg < ranges.length; rg++) {
+ const range = ranges[rg];
+ const clipTextRows = [];
+ for (let i = range.fromRow; i < range.toRow + 1; i++) {
+ const clipTextCells = [];
+ const dt = this._grid.getDataItem(i);
+
+ if (clipTextRows.length === 0 && this._addonOptions.includeHeaderWhenCopying) {
+ const clipTextHeaders = [];
+ for (let j = range.fromCell; j < range.toCell + 1; j++) {
+ if (columns[j].name!.length > 0) {
+ clipTextHeaders.push(this.getHeaderValueForColumn(columns[j]));
+ }
+ }
+ clipTextRows.push(clipTextHeaders.join('\t'));
+ }
+
+ for (let j = range.fromCell; j < range.toCell + 1; j++) {
+ clipTextCells.push(this.getDataItemValueForColumn(dt, columns[j], e));
+ }
+ clipTextRows.push(clipTextCells.join('\t'));
+ }
+ clipText += clipTextRows.join('\r\n') + '\r\n';
+ }
+
+ if ((window as any).clipboardData) {
+ (window as any).clipboardData.setData('Text', clipText);
+ return true;
+ }
+ else {
+ const focusEl = document.activeElement as HTMLElement;
+
+ const ta = this.createTextBox(clipText);
+
+ ta.focus();
+
+ setTimeout(() => {
+ this._bodyElement.removeChild(ta);
+ // restore focus
+ if (focusEl) {
+ focusEl.focus();
+ } else {
+ console.log('Not element to restore focus to after copy?');
+ }
+ }, 100);
+
+ if (this._onCopySuccess) {
+ let rowCount = 0;
+ // If it's cell selection, use the toRow/fromRow fields
+ if (ranges.length === 1) {
+ rowCount = (ranges[0].toRow + 1) - ranges[0].fromRow;
+ }
+ else {
+ rowCount = ranges.length;
+ }
+ // @ts-ignore
+ this._onCopySuccess.call(this, rowCount);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ if (!this._addonOptions.readOnlyMode && (
+ (e.which === this.keyCodes.V && (e.ctrlKey || e.metaKey) && !e.shiftKey)
+ || (e.which === this.keyCodes.INSERT && e.shiftKey && !e.ctrlKey)
+ )) { // CTRL+V or Shift+INS
+ const ta = this.createTextBox('');
+
+ setTimeout(() => {
+ this.decodeTabularData(this._grid, ta);
+ }, 100);
+
+ return false;
+ }
+ }
+ }
+
+ markCopySelection(ranges: CellRange[]) {
+ this.clearCopySelection();
+
+ const columns = this._grid.getColumns();
+ const hash: any = {};
+ for (let i = 0; i < ranges.length; i++) {
+ for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
+ hash[j] = {};
+ for (let k = ranges[i].fromCell; k <= ranges[i].toCell && k < columns.length; k++) {
+ hash[j][columns[k].id] = this._copiedCellStyle;
+ }
+ }
+ }
+ this._grid.setCellCssStyles(this._copiedCellStyleLayerKey, hash);
+ clearTimeout(this._clearCopyTI);
+ this._clearCopyTI = setTimeout(() => {
+ this.clearCopySelection();
+ }, 2000);
+ }
+
+ clearCopySelection() {
+ this._grid.removeCellCssStyles(this._copiedCellStyleLayerKey);
+ }
+
+ setIncludeHeaderWhenCopying(includeHeaderWhenCopying: boolean) {
+ this._addonOptions.includeHeaderWhenCopying = includeHeaderWhenCopying;
+ }
+}
\ No newline at end of file
diff --git a/packages/common/src/plugins/cellRangeDecorator.ts b/packages/common/src/plugins/cellRangeDecorator.ts
new file mode 100644
index 000000000..07fb7e9c5
--- /dev/null
+++ b/packages/common/src/plugins/cellRangeDecorator.ts
@@ -0,0 +1,75 @@
+import { CellRange, SlickGrid } from '../interfaces/index';
+
+export interface CellRangeDecoratorOption {
+ selectionCssClass: string;
+ selectionCss: CSSStyleDeclaration;
+ offset: { top: number; left: number; height: number; width: number; };
+}
+
+export type CSSStyleDeclarationReadonly = 'length' | 'parentRule' | 'getPropertyPriority' | 'getPropertyValue' | 'item' | 'removeProperty' | 'setProperty';
+export type CSSStyleDeclarationWritable = keyof Omit;
+
+/**
+ * Displays an overlay on top of a given cell range.
+ * TODO:
+ * Currently, it blocks mouse events to DOM nodes behind it.
+ * Use FF and WebKit-specific "pointer-events" CSS style, or some kind of event forwarding.
+ * Could also construct the borders separately using 4 individual DIVs.
+ */
+export class CellRangeDecorator {
+ protected _addonOptions!: CellRangeDecoratorOption;
+ protected _elem?: HTMLElement | null;
+ protected _grid?: SlickGrid;
+ protected _defaults = {
+ selectionCssClass: 'slick-range-decorator',
+ selectionCss: {
+ border: '2px dashed red',
+ zIndex: '9999',
+ },
+ offset: { top: -1, left: -1, height: -2, width: -2 }
+ } as CellRangeDecoratorOption;
+ pluginName = 'CellRangeDecorator';
+
+ constructor(grid: SlickGrid, options: any) {
+ this._addonOptions = { ...this._defaults, ...options };
+ this._grid = grid;
+ }
+
+ /** @deprecated @use `dispose` Destroy plugin. */
+ destroy() {
+ this.dispose();
+ }
+
+ /** Dispose the plugin. */
+ dispose() {
+ this.hide();
+ }
+
+ show(range: CellRange) {
+ if (!this._elem) {
+ this._elem = document.createElement('div');
+ this._elem.className = this._addonOptions.selectionCssClass;
+ Object.keys(this._addonOptions.selectionCss as CSSStyleDeclaration).forEach((cssStyleKey) => {
+ this._elem!.style[cssStyleKey as CSSStyleDeclarationWritable] = this._addonOptions.selectionCss[cssStyleKey as CSSStyleDeclarationWritable];
+ });
+ this._elem.style.position = 'absolute';
+ this._grid?.getActiveCanvasNode().appendChild(this._elem);
+ }
+
+ const from = this._grid?.getCellNodeBox(range.fromRow, range.fromCell);
+ const to = this._grid?.getCellNodeBox(range.toRow, range.toCell);
+
+ if (from && to && this._addonOptions?.offset) {
+ this._elem.style.top = `${from.top + this._addonOptions.offset.top}px`;
+ this._elem.style.left = `${from.left + this._addonOptions.offset.left}px`;
+ this._elem.style.height = `${to.bottom - from.top + this._addonOptions.offset.height}px`;
+ this._elem.style.width = `${to.right - from.left + this._addonOptions.offset.width}px`;
+ }
+ return this._elem;
+ }
+
+ hide() {
+ this._elem?.remove();
+ this._elem = null;
+ }
+}
\ No newline at end of file
diff --git a/packages/common/src/plugins/cellRangeSelector.ts b/packages/common/src/plugins/cellRangeSelector.ts
new file mode 100644
index 000000000..551c845a6
--- /dev/null
+++ b/packages/common/src/plugins/cellRangeSelector.ts
@@ -0,0 +1,197 @@
+import { CellRange, emptyElement, getHtmlElementOffset, } from '..';
+import { DOMMouseEvent, GridOption, SlickGrid, SlickNamespace } from '../interfaces/index';
+import { CellRangeDecorator } from './index';
+
+// using external SlickGrid JS libraries
+declare const Slick: SlickNamespace;
+
+interface DragPosition {
+ startX: number;
+ startY: number;
+ range: DragRange;
+}
+
+interface DragRange {
+ start: {
+ row?: number;
+ cell?: number;
+ };
+ end: {
+ row?: number;
+ cell?: number;
+ };
+}
+
+export class CellRangeSelector {
+ protected _addonOptions!: any;
+ protected _currentlySelectedRange!: DragRange;
+ protected _canvas!: HTMLElement;
+ protected _grid!: SlickGrid;
+ protected _gridOptions!: GridOption;
+ protected _activeCanvas?: HTMLElement;
+ protected _dragging = false;
+ protected _decorator!: CellRangeDecorator;
+ protected _eventHandler = new Slick.EventHandler();
+
+ // Frozen row & column constiables
+ protected _rowOffset: any;
+ protected _columnOffset: any;
+ protected _isRightCanvas = false;
+ protected _isBottomCanvas = false;
+
+ // Scrollings
+ protected _scrollTop = 0;
+ protected _scrollLeft = 0;
+ protected _defaults = {
+ selectionCss: {
+ border: '2px dashed blue'
+ } as CSSStyleDeclaration
+ };
+ pluginName = 'CellRangeSelector';
+ onBeforeCellRangeSelected = new Slick.Event();
+ onCellRangeSelected = new Slick.Event<{ range: CellRange; }>();
+
+ constructor(options: any) {
+ this._addonOptions = { ...this._defaults, ...options };
+ }
+
+ init(grid: SlickGrid) {
+ this._grid = grid;
+ this._decorator = this._addonOptions.cellDecorator || new CellRangeDecorator(grid, this._addonOptions);
+ this._grid = grid;
+ this._canvas = this._grid.getCanvasNode();
+ this._gridOptions = this._grid.getOptions();
+ this._eventHandler
+ .subscribe(this._grid.onScroll, this.handleScroll.bind(this) as EventListener)
+ .subscribe(this._grid.onDragInit, this.handleDragInit.bind(this) as EventListener)
+ .subscribe(this._grid.onDragStart, this.handleDragStart.bind(this) as EventListener)
+ .subscribe(this._grid.onDrag, this.handleDrag.bind(this) as EventListener)
+ .subscribe(this._grid.onDragEnd, this.handleDragEnd.bind(this) as EventListener);
+ }
+
+ destroy() {
+ this._eventHandler.unsubscribeAll();
+ emptyElement(this._activeCanvas);
+ emptyElement(this._canvas);
+ if (this._decorator?.destroy) {
+ this._decorator.destroy();
+ }
+ }
+
+ getCellDecorator() {
+ return this._decorator;
+ }
+
+ handleScroll(_e: DOMMouseEvent, args: { scrollTop: number; scrollLeft: number; }) {
+ this._scrollTop = args.scrollTop;
+ this._scrollLeft = args.scrollLeft;
+ }
+
+ handleDragInit(e: any) {
+ // Set the active canvas node because the decorator needs to append its
+ // box to the correct canvas
+ this._activeCanvas = this._grid.getActiveCanvasNode(e);
+
+ this._rowOffset = 0;
+ this._columnOffset = 0;
+ this._isBottomCanvas = this._activeCanvas.classList.contains('grid-canvas-bottom');
+
+ if (this._gridOptions.frozenRow! > -1 && this._isBottomCanvas) {
+ this._rowOffset = (this._gridOptions.frozenBottom) ? document.querySelector('.' + this._grid.getUID() + ' .grid-canvas-bottom')?.clientHeight ?? 0 : document.querySelector('.' + this._grid.getUID() + ' .grid-canvas-top')?.clientHeight ?? 0;
+ }
+
+ this._isRightCanvas = this._activeCanvas.classList.contains('grid-canvas-right');
+
+ if (this._gridOptions.frozenColumn! > -1 && this._isRightCanvas) {
+ this._columnOffset = document.querySelector('.' + this._grid.getUID() + ' .grid-canvas-left')?.clientWidth ?? 0;
+ }
+
+ // prevent the grid from cancelling drag'n'drop by default
+ e.stopImmediatePropagation();
+ }
+
+ handleDragStart(e: DOMMouseEvent, dd: DragPosition) {
+ const cell = this._grid.getCellFromEvent(e);
+ if (this.onBeforeCellRangeSelected.notify(cell) !== false) {
+ if (this._grid.canCellBeSelected(cell!.row, cell!.cell)) {
+ this._dragging = true;
+ e.stopImmediatePropagation();
+ }
+ }
+ if (!this._dragging) {
+ return;
+ }
+
+ this._grid.focus();
+
+ let startX = dd.startX - (getHtmlElementOffset(this._canvas)?.left ?? 0);
+ if (this._gridOptions.frozenColumn! >= 0 && this._isRightCanvas) {
+ startX += this._scrollLeft;
+ }
+
+ let startY = dd.startY - (getHtmlElementOffset(this._canvas)?.top ?? 0);
+ if (this._gridOptions.frozenRow! >= 0 && this._isBottomCanvas) {
+ startY += this._scrollTop;
+ }
+
+ const start = this._grid.getCellFromPoint(startX, startY);
+
+ dd.range = { start, end: {} };
+ this._currentlySelectedRange = dd.range;
+ return this._decorator.show(new Slick.Range(start.row, start.cell));
+ }
+
+ handleDrag(e: any, dd: DragPosition) {
+ if (!this._dragging) {
+ return;
+ }
+ e.stopImmediatePropagation();
+
+ const end = this._grid.getCellFromPoint(
+ e.pageX - (getHtmlElementOffset(this._activeCanvas)?.left ?? 0) + this._columnOffset,
+ e.pageY - (getHtmlElementOffset(this._activeCanvas)?.top ?? 0) + this._rowOffset
+ );
+
+ // ... frozen column(s),
+ if (this._gridOptions.frozenColumn! >= 0 && (!this._isRightCanvas && (end.cell > this._gridOptions.frozenColumn!)) || (this._isRightCanvas && (end.cell <= this._gridOptions.frozenColumn!))) {
+ return;
+ }
+
+ // ... or frozen row(s)
+ if (this._gridOptions.frozenRow! >= 0 && (!this._isBottomCanvas && (end.row >= this._gridOptions.frozenRow!)) || (this._isBottomCanvas && (end.row < this._gridOptions.frozenRow!))) {
+ return;
+ }
+
+ // ... or regular grid (without any frozen options)
+ if (!this._grid.canCellBeSelected(end.row, end.cell)) {
+ return;
+ }
+
+ dd.range.end = end;
+
+ this._decorator.show(new Slick.Range(dd.range.start.row, dd.range.start.cell, end.row, end.cell));
+ }
+
+ handleDragEnd(e: any, dd: DragPosition) {
+ if (!this._dragging) {
+ return;
+ }
+
+ this._dragging = false;
+ e.stopImmediatePropagation();
+
+ this._decorator.hide();
+ this.onCellRangeSelected.notify({
+ range: new Slick.Range(
+ dd.range.start.row,
+ dd.range.start.cell,
+ dd.range.end.row,
+ dd.range.end.cell
+ )
+ });
+ }
+
+ getCurrentRange() {
+ return this._currentlySelectedRange;
+ }
+}
\ No newline at end of file
diff --git a/packages/common/src/plugins/cellSelectionModel.ts b/packages/common/src/plugins/cellSelectionModel.ts
new file mode 100644
index 000000000..fbd184216
--- /dev/null
+++ b/packages/common/src/plugins/cellSelectionModel.ts
@@ -0,0 +1,189 @@
+import { CellRange, OnActiveCellChangedEventArgs, SlickGrid, SlickNamespace, SlickRange, } from '../interfaces/index';
+import { CellRangeSelector } from './index';
+
+// using external SlickGrid JS libraries
+declare const Slick: SlickNamespace;
+
+export class CellSelectionModel {
+ protected _addonOptions: any;
+ protected _grid!: SlickGrid;
+ protected _canvas: HTMLElement | null = null;
+ protected _ranges: SlickRange[] = [];
+ protected _selector: CellRangeSelector;
+ protected _defaults = {
+ selectActiveCell: true,
+ };
+ onSelectedRangesChanged = new Slick.Event();
+ pluginName = 'CellSelectionModel';
+
+ constructor(options?: any) {
+ if (options === undefined || typeof options.cellRangeSelector === undefined) {
+ this._selector = new CellRangeSelector({
+ selectionCss: {
+ border: '2px solid black'
+ }
+ });
+ } else {
+ this._selector = options.cellRangeSelector;
+ }
+ this._addonOptions = options;
+ }
+
+
+ init(grid: SlickGrid) {
+ this._addonOptions = { ...this._defaults, ...this._addonOptions };
+ this._grid = grid;
+ this._canvas = this._grid.getCanvasNode();
+ this._grid.onActiveCellChanged.subscribe(this.handleActiveCellChange.bind(this));
+ this._grid.onKeyDown.subscribe(this.handleKeyDown.bind(this));
+ grid.registerPlugin(this._selector);
+ this._selector.onCellRangeSelected.subscribe(this.handleCellRangeSelected.bind(this));
+ this._selector.onBeforeCellRangeSelected.subscribe(this.handleBeforeCellRangeSelected.bind(this));
+ }
+
+ /** @deprecated @use `dispose` Destroy plugin. */
+ destroy() {
+ this.dispose();
+ }
+
+ dispose() {
+ this._grid.onActiveCellChanged.unsubscribe(this.handleActiveCellChange.bind(this));
+ this._grid.onKeyDown.unsubscribe(this.handleKeyDown.bind(this));
+ this._selector.onCellRangeSelected.unsubscribe(this.handleCellRangeSelected.bind(this));
+ this._selector.onBeforeCellRangeSelected.unsubscribe(this.handleBeforeCellRangeSelected.bind(this));
+ this._grid.unregisterPlugin(this._selector as any);
+ this._canvas = null;
+ this._selector?.destroy();
+ }
+
+ removeInvalidRanges(ranges: any[]) {
+ const result = [];
+
+ for (let i = 0; i < ranges.length; i++) {
+ const r = ranges[i];
+ if (this._grid.canCellBeSelected(r.fromRow, r.fromCell) && this._grid.canCellBeSelected(r.toRow, r.toCell)) {
+ result.push(r);
+ }
+ }
+
+ return result;
+ }
+
+ rangesAreEqual(range1: any, range2: any) {
+ let areDifferent = (range1.length !== range2.length);
+ if (!areDifferent) {
+ for (let i = 0; i < range1.length; i++) {
+ if (
+ range1[i].fromCell !== range2[i].fromCell
+ || range1[i].fromRow !== range2[i].fromRow
+ || range1[i].toCell !== range2[i].toCell
+ || range1[i].toRow !== range2[i].toRow
+ ) {
+ areDifferent = true;
+ break;
+ }
+ }
+ }
+ return !areDifferent;
+ }
+
+ setSelectedRanges(ranges: any[]) {
+ // simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged
+ if ((!this._ranges || this._ranges.length === 0) && (!ranges || ranges.length === 0)) { return; }
+
+ // if range has not changed, don't fire onSelectedRangesChanged
+ const rangeHasChanged = !this.rangesAreEqual(this._ranges, ranges);
+
+ this._ranges = this.removeInvalidRanges(ranges);
+ if (rangeHasChanged) {
+ this.onSelectedRangesChanged.notify(this._ranges);
+ }
+ }
+
+ getSelectedRanges() {
+ return this._ranges;
+ }
+
+ handleBeforeCellRangeSelected(e: any): boolean | void {
+ if (this._grid.getEditorLock().isActive()) {
+ e.stopPropagation();
+ return false;
+ }
+ }
+
+ handleCellRangeSelected(_e: any, args: { range: CellRange; }) {
+ this._grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true);
+ this.setSelectedRanges([args.range]);
+ }
+
+ handleActiveCellChange(_e: any, args: OnActiveCellChangedEventArgs) {
+ if (this._addonOptions.selectActiveCell && args.row !== null && args.cell !== null) {
+ this.setSelectedRanges([new Slick.Range(args.row, args.cell)]);
+ } else if (!this._addonOptions.selectActiveCell) {
+ // clear the previous selection once the cell changes
+ this.setSelectedRanges([]);
+ }
+ }
+
+ handleKeyDown(e: any) {
+ /***
+ * Кey codes
+ * 37 left
+ * 38 up
+ * 39 right
+ * 40 down
+ */
+ let ranges: SlickRange[];
+ let last: SlickRange;
+ const active = this._grid.getActiveCell();
+ const metaKey = e.ctrlKey || e.metaKey;
+
+ if (active && e.shiftKey && !metaKey && !e.altKey &&
+ (e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40)) {
+
+ ranges = this.getSelectedRanges().slice();
+ if (!ranges.length) {
+ ranges.push(new Slick.Range(active.row, active.cell));
+ }
+ // keyboard can work with last range only
+ last = ranges.pop() as SlickRange;
+
+ // can't handle selection out of active cell
+ if (!last.contains(active.row, active.cell)) {
+ last = new Slick.Range(active.row, active.cell);
+ }
+ let dRow = last.toRow - last.fromRow;
+ let dCell = last.toCell - last.fromCell;
+ // walking direction
+ const dirRow = active.row === last.fromRow ? 1 : -1;
+ const dirCell = active.cell === last.fromCell ? 1 : -1;
+
+ if (e.which === 37) {
+ dCell -= dirCell;
+ } else if (e.which === 39) {
+ dCell += dirCell;
+ } else if (e.which === 38) {
+ dRow -= dirRow;
+ } else if (e.which === 40) {
+ dRow += dirRow;
+ }
+
+ // define new selection range
+ const newLast = new Slick.Range(active.row, active.cell, active.row + dirRow * dRow, active.cell + dirCell * dCell);
+ if (this.removeInvalidRanges([newLast]).length) {
+ ranges.push(newLast);
+ const viewRow = dirRow > 0 ? newLast.toRow : newLast.fromRow;
+ const viewCell = dirCell > 0 ? newLast.toCell : newLast.fromCell;
+ this._grid.scrollRowIntoView(viewRow);
+ this._grid.scrollCellIntoView(viewRow, viewCell, false);
+ }
+ else {
+ ranges.push(last);
+ }
+ this.setSelectedRanges(ranges);
+
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/common/src/plugins/index.ts b/packages/common/src/plugins/index.ts
index c1371eef1..0c0bcf576 100644
--- a/packages/common/src/plugins/index.ts
+++ b/packages/common/src/plugins/index.ts
@@ -1,5 +1,10 @@
export * from './autoTooltip.plugin';
+export * from './cellExcelCopyManager';
+export * from './cellExternalCopyManager';
export * from './cellMenu.plugin';
+export * from './cellRangeDecorator';
+export * from './cellRangeSelector';
+export * from './cellSelectionModel';
export * from './contextMenu.plugin';
export * from './draggableGrouping.plugin';
export * from './headerButton.plugin';
diff --git a/packages/common/src/services/__tests__/extension.service.spec.ts b/packages/common/src/services/__tests__/extension.service.spec.ts
index 47aadb520..ac387f7a0 100644
--- a/packages/common/src/services/__tests__/extension.service.spec.ts
+++ b/packages/common/src/services/__tests__/extension.service.spec.ts
@@ -3,7 +3,6 @@ import 'jest-extended';
import { ExtensionName } from '../../enums/index';
import { Column, ExtensionModel, GridOption, SlickGrid, SlickNamespace } from '../../interfaces/index';
import {
- CellExternalCopyManagerExtension,
CheckboxSelectorExtension,
ExtensionUtility,
RowDetailViewExtension,
@@ -145,7 +144,6 @@ describe('ExtensionService', () => {
sortServiceStub,
treeDataServiceStub,
// extensions
- extensionStub as unknown as CellExternalCopyManagerExtension,
extensionCheckboxSelectorStub as unknown as CheckboxSelectorExtension,
extensionStub as unknown as RowDetailViewExtension,
extensionRowMoveStub as unknown as RowMoveManagerExtension,
@@ -504,18 +502,18 @@ describe('ExtensionService', () => {
expect(output).toEqual({ name: ExtensionName.headerMenu, instance: pluginInstance, class: pluginInstance } as ExtensionModel);
});
- it('should register the ExcelCopyBuffer addon when "enableExcelCopyBuffer" is set in the grid options', () => {
- const gridOptionsMock = { enableExcelCopyBuffer: true } as GridOption;
- const extSpy = jest.spyOn(extensionStub, 'register').mockReturnValue(instanceMock);
- const gridSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock);
+ // it('should register the ExcelCopyBuffer addon when "enableExcelCopyBuffer" is set in the grid options', () => {
+ // const gridOptionsMock = { enableExcelCopyBuffer: true } as GridOption;
+ // const extSpy = jest.spyOn(extensionStub, 'register').mockReturnValue(instanceMock);
+ // const gridSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock);
- service.bindDifferentExtensions();
- const output = service.getExtensionByName(ExtensionName.cellExternalCopyManager);
+ // service.bindDifferentExtensions();
+ // const output = service.getExtensionByName(ExtensionName.cellExternalCopyManager);
- expect(gridSpy).toHaveBeenCalled();
- expect(extSpy).toHaveBeenCalled();
- expect(output).toEqual({ name: ExtensionName.cellExternalCopyManager, instance: instanceMock as unknown, class: extensionStub } as ExtensionModel);
- });
+ // expect(gridSpy).toHaveBeenCalled();
+ // expect(extSpy).toHaveBeenCalled();
+ // expect(output).toEqual({ name: ExtensionName.cellExternalCopyManager, instance: instanceMock as unknown, class: extensionStub } as ExtensionModel);
+ // });
});
describe('createExtensionsBeforeGridCreation method', () => {
@@ -851,7 +849,6 @@ describe('ExtensionService', () => {
sortServiceStub,
treeDataServiceStub,
// extensions
- extensionStub as unknown as CellExternalCopyManagerExtension,
extensionStub as unknown as CheckboxSelectorExtension,
extensionStub as unknown as RowDetailViewExtension,
extensionStub as unknown as RowMoveManagerExtension,
diff --git a/packages/common/src/services/__tests__/grid.service.spec.ts b/packages/common/src/services/__tests__/grid.service.spec.ts
index abcb46577..7493291a0 100644
--- a/packages/common/src/services/__tests__/grid.service.spec.ts
+++ b/packages/common/src/services/__tests__/grid.service.spec.ts
@@ -14,7 +14,6 @@ const mockSelectionModel = {
const mockSelectionModelImplementation = jest.fn().mockImplementation(() => mockSelectionModel);
jest.mock('flatpickr', () => { });
-jest.mock('slickgrid/plugins/slick.rowselectionmodel', () => mockSelectionModelImplementation);
Slick.RowSelectionModel = mockSelectionModelImplementation;
const filterServiceStub = {
@@ -95,6 +94,7 @@ const treeDataServiceStub = {
} as unknown as TreeDataService;
describe('Grid Service', () => {
+ jest.mock('slickgrid/plugins/slick.rowselectionmodel', () => mockSelectionModelImplementation);
let service: GridService;
const sharedService = new SharedService();
const mockGridOptions = { enableAutoResize: true } as GridOption;
diff --git a/packages/common/src/services/extension.service.ts b/packages/common/src/services/extension.service.ts
index 9b54662ed..345aea416 100644
--- a/packages/common/src/services/extension.service.ts
+++ b/packages/common/src/services/extension.service.ts
@@ -6,7 +6,6 @@ import 'slickgrid/plugins/slick.cellselectionmodel';
import { Column, Extension, ExtensionModel, GridOption, SlickRowSelectionModel, } from '../interfaces/index';
import { ColumnReorderFunction, ExtensionList, ExtensionName, SlickControlList, SlickPluginList } from '../enums/index';
import {
- CellExternalCopyManagerExtension,
CheckboxSelectorExtension,
ExtensionUtility,
RowDetailViewExtension,
@@ -15,8 +14,16 @@ import {
} from '../extensions/index';
import { SharedService } from './shared.service';
import { TranslaterService } from './translater.service';
-import { AutoTooltipPlugin, CellMenuPlugin, ContextMenuPlugin, DraggableGroupingPlugin, HeaderButtonPlugin, HeaderMenuPlugin } from '../plugins/index';
import { ColumnPickerControl, GridMenuControl } from '../controls/index';
+import {
+ AutoTooltipPlugin,
+ CellExcelCopyManager,
+ CellMenuPlugin,
+ ContextMenuPlugin,
+ DraggableGroupingPlugin,
+ HeaderButtonPlugin,
+ HeaderMenuPlugin
+} from '../plugins/index';
import { FilterService } from './filter.service';
import { GroupItemMetadataProviderService } from './groupItemMetadataProvider.service';
import { PubSubService } from './pubSub.service';
@@ -31,6 +38,7 @@ interface ExtensionWithColumnIndexPosition {
export class ExtensionService {
protected _cellMenuPlugin?: CellMenuPlugin;
+ protected _cellExcelCopyManagerPlugin?: CellExcelCopyManager;
protected _contextMenuPlugin?: ContextMenuPlugin;
protected _columnPickerControl?: ColumnPickerControl;
protected _draggleGroupingPlugin?: DraggableGroupingPlugin;
@@ -55,7 +63,6 @@ export class ExtensionService {
protected readonly sortService: SortService,
protected readonly treeDataService: TreeDataService,
- protected readonly cellExternalCopyExtension: CellExternalCopyManagerExtension,
protected readonly checkboxSelectorExtension: CheckboxSelectorExtension,
protected readonly rowDetailViewExtension: RowDetailViewExtension,
protected readonly rowMoveManagerExtension: RowMoveManagerExtension,
@@ -152,11 +159,13 @@ export class ExtensionService {
}
// Cell External Copy Manager Plugin (Excel Like)
- if (this.gridOptions.enableExcelCopyBuffer && this.cellExternalCopyExtension && this.cellExternalCopyExtension.register) {
- const instance = this.cellExternalCopyExtension.register();
- if (instance) {
- this._extensionList[ExtensionName.cellExternalCopyManager] = { name: ExtensionName.cellExternalCopyManager, class: this.cellExternalCopyExtension, instance };
+ if (this.gridOptions.enableExcelCopyBuffer) {
+ this._cellExcelCopyManagerPlugin = new CellExcelCopyManager();
+ this._cellExcelCopyManagerPlugin.init(this.sharedService.slickGrid, this.sharedService.gridOptions.excelCopyBufferOptions);
+ if (this.gridOptions.excelCopyBufferOptions?.onExtensionRegistered) {
+ this.gridOptions.excelCopyBufferOptions.onExtensionRegistered(this._cellExcelCopyManagerPlugin);
}
+ this._extensionList[ExtensionName.cellExternalCopyManager] = { name: ExtensionName.cellExternalCopyManager, class: this._cellExcelCopyManagerPlugin, instance: this._cellExcelCopyManagerPlugin };
}
// (Action) Cell Menu Plugin
diff --git a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts
index 1503a6fe2..2d5c0fadc 100644
--- a/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts
+++ b/packages/vanilla-bundle/src/components/__tests__/slick-vanilla-grid.spec.ts
@@ -33,10 +33,8 @@ import {
ServicePagination,
SharedService,
SlickDataView,
- SlickDraggableGrouping,
SlickEventHandler,
SlickGrid,
- SlickGroupItemMetadataProvider,
SortService,
TreeDataService,
TranslaterService,
@@ -212,12 +210,6 @@ const mockDataView = {
syncGridSelection: jest.fn(),
} as unknown as SlickDataView;
-const mockDraggableGroupingExtension = {
- constructor: jest.fn(),
- init: jest.fn(),
- destroy: jest.fn(),
-} as unknown as SlickDraggableGrouping;
-
const mockEventPubSub = {
notify: jest.fn(),
subscribe: jest.fn(),
@@ -272,16 +264,13 @@ const mockGrid = {
const mockSlickEventHandlerImplementation = jest.fn().mockImplementation(() => mockSlickEventHandler);
const mockDataViewImplementation = jest.fn().mockImplementation(() => mockDataView);
const mockGridImplementation = jest.fn().mockImplementation(() => mockGrid);
-const mockDraggableGroupingImplementation = jest.fn().mockImplementation(() => mockDraggableGroupingExtension);
const template = ``;
describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', () => {
jest.mock('slickgrid/slick.grid', () => mockGridImplementation);
- jest.mock('slickgrid/plugins/slick.draggablegrouping', () => mockDraggableGroupingImplementation);
Slick.Grid = mockGridImplementation;
Slick.EventHandler = slickEventHandler;
Slick.Data = { DataView: mockDataViewImplementation, };
- Slick.DraggableGrouping = mockDraggableGroupingImplementation;
let component: SlickVanillaGridBundle;
let divContainer: HTMLDivElement;
@@ -2141,10 +2130,8 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor', ()
describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor with a Hierarchical Dataset', () => {
jest.mock('slickgrid/slick.grid', () => mockGridImplementation);
- jest.mock('slickgrid/plugins/slick.draggablegrouping', () => mockDraggableGroupingImplementation);
Slick.Grid = mockGridImplementation;
Slick.Data = { DataView: mockDataViewImplementation, };
- Slick.DraggableGrouping = mockDraggableGroupingImplementation;
let component: SlickVanillaGridBundle;
let divContainer: HTMLDivElement;
@@ -2225,11 +2212,9 @@ describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor with
describe('Slick-Vanilla-Grid-Bundle Component instantiated via Constructor with a Slickgrid Container that already exist', () => {
jest.mock('slickgrid/slick.grid', () => mockGridImplementation);
- jest.mock('slickgrid/plugins/slick.draggablegrouping', () => mockDraggableGroupingImplementation);
Slick.Grid = mockGridImplementation;
Slick.EventHandler = mockSlickEventHandlerImplementation;
Slick.Data = { DataView: mockDataViewImplementation, };
- Slick.DraggableGrouping = mockDraggableGroupingImplementation;
let component: SlickVanillaGridBundle;
let divContainer: HTMLDivElement;
diff --git a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts
index d2c316437..8371837c5 100644
--- a/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts
+++ b/packages/vanilla-bundle/src/components/slick-vanilla-grid-bundle.ts
@@ -31,7 +31,6 @@ import {
// extensions
CheckboxSelectorExtension,
- CellExternalCopyManagerExtension,
ExtensionUtility,
RowDetailViewExtension,
RowSelectionExtension,
@@ -350,7 +349,6 @@ export class SlickVanillaGridBundle {
this.paginationService = services?.paginationService ?? new PaginationService(this._eventPubSubService, this.sharedService, this.backendUtilityService);
// extensions
- const cellExternalCopyManagerExtension = new CellExternalCopyManagerExtension(this.extensionUtility, this.sharedService);
const checkboxExtension = new CheckboxSelectorExtension(this.sharedService);
const rowDetailViewExtension = new RowDetailViewExtension();
const rowMoveManagerExtension = new RowMoveManagerExtension(this.sharedService);
@@ -362,7 +360,6 @@ export class SlickVanillaGridBundle {
this._eventPubSubService,
this.sortService,
this.treeDataService,
- cellExternalCopyManagerExtension,
checkboxExtension,
rowDetailViewExtension,
rowMoveManagerExtension,