From dee9e0488248905a5d2b68b03c52ad5cda92fa50 Mon Sep 17 00:00:00 2001 From: Ghislain Beaulac Date: Fri, 24 May 2019 11:30:45 -0400 Subject: [PATCH] feat(copy): add ExcelCopyBufferOptions to grid option, ref #198 --- src/app/examples/grid-formatter.component.ts | 12 +- .../cellExternalCopyManagerExtension.ts | 117 ++++++++++++------ .../models/excelCopyBufferOption.interface.ts | 57 +++++++++ .../models/gridOption.interface.ts | 4 + .../modules/angular-slickgrid/models/index.ts | 2 + .../models/selectedRange.interface.ts | 25 ++++ 6 files changed, 174 insertions(+), 43 deletions(-) create mode 100644 src/app/modules/angular-slickgrid/models/excelCopyBufferOption.interface.ts create mode 100644 src/app/modules/angular-slickgrid/models/selectedRange.interface.ts diff --git a/src/app/examples/grid-formatter.component.ts b/src/app/examples/grid-formatter.component.ts index 120922987..f130efc21 100644 --- a/src/app/examples/grid-formatter.component.ts +++ b/src/app/examples/grid-formatter.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { Column, FieldType, Formatter, Formatters, GridOption } from './../modules/angular-slickgrid'; +import { Column, FieldType, Formatter, Formatters, GridOption, SelectedRange } from './../modules/angular-slickgrid'; // create my custom Formatter with the Formatter type const myCustomCheckmarkFormatter: Formatter = (row, cell, value, columnDef, dataContext) => { @@ -46,13 +46,21 @@ export class GridFormatterComponent implements OnInit { }, enableAutoResize: true, enableCellNavigation: true, - enableExcelCopyBuffer: true, + // you customize the date separator through "formatterOptions" /* formatterOptions: { dateSeparator: '.' }, */ + + // when using the ExcelCopyBuffer, you can see what the selection range is + enableExcelCopyBuffer: true, + excelCopyBufferOptions: { + onCopyCells: (e, args: { ranges: SelectedRange[] }) => console.log('onCopyCells', args.ranges), + onPasteCells: (e, args: { ranges: SelectedRange[] }) => console.log('onPasteCells', args.ranges), + onCopyCancelled: (e, args: { ranges: SelectedRange[] }) => console.log('onCopyCancelled', args.ranges), + } }; // mock a dataset diff --git a/src/app/modules/angular-slickgrid/extensions/cellExternalCopyManagerExtension.ts b/src/app/modules/angular-slickgrid/extensions/cellExternalCopyManagerExtension.ts index de28b818d..57ea9d24d 100644 --- a/src/app/modules/angular-slickgrid/extensions/cellExternalCopyManagerExtension.ts +++ b/src/app/modules/angular-slickgrid/extensions/cellExternalCopyManagerExtension.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Column, Extension, ExtensionName } from '../models/index'; +import { Column, ExcelCopyBufferOption, Extension, ExtensionName, SelectedRange } from '../models/index'; import { ExtensionUtility } from './extensionUtility'; import { sanitizeHtmlToText } from '../services/utilities'; import { SharedService } from '../services/shared.service'; @@ -10,12 +10,15 @@ declare var $: any; @Injectable() export class CellExternalCopyManagerExtension implements Extension { + private _eventHandler: any = new Slick.EventHandler(); private _extension: any; private _undoRedoBuffer: any; constructor(private extensionUtility: ExtensionUtility, private sharedService: SharedService) { } dispose() { + // unsubscribe all SlickGrid events + this._eventHandler.unsubscribeAll(); if (this._extension && this._extension.destroy) { this._extension.destroy(); } @@ -27,49 +30,34 @@ export class CellExternalCopyManagerExtension implements Extension { this.extensionUtility.loadExtensionDynamically(ExtensionName.cellExternalCopyManager); this.createUndoRedoBuffer(); this.hookUndoShortcutKey(); - let newRowIds = 0; - const pluginOptions = { - clipboardCommandHandler: (editCommand: any) => { - this._undoRedoBuffer.queueAndExecuteCommand.call(this._undoRedoBuffer, editCommand); - }, - 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 isEvaluatingFormatter = (columnDef.exportWithFormatter !== undefined) ? columnDef.exportWithFormatter : (this.sharedService.gridOptions.exportOptions && this.sharedService.gridOptions.exportOptions.exportWithFormatter); - if (columnDef.formatter && isEvaluatingFormatter) { - const formattedOutput = columnDef.formatter(0, 0, item[columnDef.field], columnDef, item, this.sharedService.grid); - if (columnDef.sanitizeDataExport || (this.sharedService.gridOptions.exportOptions && this.sharedService.gridOptions.exportOptions.sanitizeDataExport)) { - let outputString = formattedOutput as string; - if (formattedOutput && typeof formattedOutput === 'object' && formattedOutput.hasOwnProperty('text')) { - outputString = formattedOutput.text; - } - if (outputString === null) { - outputString = ''; - } - return sanitizeHtmlToText(outputString); - } - return formattedOutput; - } - } - // else use the default "dataItemColumnValueExtractor" from the plugin itself - // we can do that by setting back the getter with null - return null; - }, - readOnlyMode: false, - includeHeaderWhenCopying: false, - newRowCreator: (count: number) => { - for (let i = 0; i < count; i++) { - const item = { - id: 'newRow_' + newRowIds++ - }; - this.sharedService.grid.getData().addItem(item); - } - } - }; + + const pluginOptions = { ...this.getDefaultOptions(), ...this.sharedService.gridOptions.excelCopyBufferOptions } as ExcelCopyBufferOption; this.sharedService.grid.setSelectionModel(new Slick.CellSelectionModel()); this._extension = new Slick.CellExternalCopyManager(pluginOptions); this.sharedService.grid.registerPlugin(this._extension); + + // hook to all possible events + if (this.sharedService.grid && this.sharedService.gridOptions.excelCopyBufferOptions) { + if (this.sharedService.gridOptions.excelCopyBufferOptions.onExtensionRegistered) { + this.sharedService.gridOptions.excelCopyBufferOptions.onExtensionRegistered(this._extension); + } + this._eventHandler.subscribe(this._extension.onCopyCells, (e: any, args: { ranges: SelectedRange[] }) => { + if (this.sharedService.gridOptions.excelCopyBufferOptions && typeof this.sharedService.gridOptions.excelCopyBufferOptions.onCopyCells === 'function') { + this.sharedService.gridOptions.excelCopyBufferOptions.onCopyCells(e, args); + } + }); + this._eventHandler.subscribe(this._extension.onCopyCancelled, (e: any, args: { ranges: SelectedRange[] }) => { + if (this.sharedService.gridOptions.excelCopyBufferOptions && typeof this.sharedService.gridOptions.excelCopyBufferOptions.onCopyCancelled === 'function') { + this.sharedService.gridOptions.excelCopyBufferOptions.onCopyCancelled(e, args); + } + }); + this._eventHandler.subscribe(this._extension.onPasteCells, (e: any, args: { ranges: SelectedRange[] }) => { + if (this.sharedService.gridOptions.excelCopyBufferOptions && typeof this.sharedService.gridOptions.excelCopyBufferOptions.onPasteCells === 'function') { + this.sharedService.gridOptions.excelCopyBufferOptions.onPasteCells(e, args); + } + }); + } + return this._extension; } return null; @@ -104,6 +92,53 @@ export class CellExternalCopyManagerExtension implements Extension { }; } + /** + * @return default plugin (addon) options + */ + private getDefaultOptions(): ExcelCopyBufferOption { + let newRowIds = 0; + + return { + clipboardCommandHandler: (editCommand: any) => { + this._undoRedoBuffer.queueAndExecuteCommand.call(this._undoRedoBuffer, editCommand); + }, + 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 isEvaluatingFormatter = (columnDef.exportWithFormatter !== undefined) ? columnDef.exportWithFormatter : (this.sharedService.gridOptions.exportOptions && this.sharedService.gridOptions.exportOptions.exportWithFormatter); + if (columnDef.formatter && isEvaluatingFormatter) { + const formattedOutput = columnDef.formatter(0, 0, item[columnDef.field], columnDef, item, this.sharedService.grid); + if (columnDef.sanitizeDataExport || (this.sharedService.gridOptions.exportOptions && this.sharedService.gridOptions.exportOptions.sanitizeDataExport)) { + let outputString = formattedOutput as string; + if (formattedOutput && typeof formattedOutput === 'object' && formattedOutput.hasOwnProperty('text')) { + outputString = formattedOutput.text; + } + if (outputString === null) { + outputString = ''; + } + return sanitizeHtmlToText(outputString); + } + return formattedOutput; + } + } + // else use the default "dataItemColumnValueExtractor" from the plugin itself + // we can do that by setting back the getter with null + return null; + }, + readOnlyMode: false, + includeHeaderWhenCopying: false, + newRowCreator: (count: number) => { + for (let i = 0; i < count; i++) { + const item = { + id: 'newRow_' + newRowIds++ + }; + this.sharedService.grid.getData().addItem(item); + } + } + }; + } + /** Attach an undo shortcut key hook that will redo/undo the copy buffer */ private hookUndoShortcutKey() { // undo shortcut diff --git a/src/app/modules/angular-slickgrid/models/excelCopyBufferOption.interface.ts b/src/app/modules/angular-slickgrid/models/excelCopyBufferOption.interface.ts new file mode 100644 index 000000000..bb5a4dbf2 --- /dev/null +++ b/src/app/modules/angular-slickgrid/models/excelCopyBufferOption.interface.ts @@ -0,0 +1,57 @@ +import { Column } from './column.interface'; +import { SelectedRange } from './selectedRange.interface'; + +export interface ExcelCopyBufferOption { + /** defaults to "copied", sets the css className used for copied cells. */ + copiedCellStyle?: string; + + /** defaults to "copy-manager", sets the layer key for setting css values of copied cells. */ + copiedCellStyleLayerKey?: string; + + /** option to specify a custom column value extractor function */ + dataItemColumnValueExtractor?: (item: any, columnDef: Column) => any; + + /** option to specify a custom column value setter function */ + dataItemColumnValueSetter?: (item: any, columnDef: Column, value: any) => any; + + /** option to specify a custom handler for paste actions */ + clipboardCommandHandler?: (editCommand: any) => void; + + /** set to true and the plugin will take the name property from each column (which is usually what appears in your header) and put that as the first row of the text that's copied to the clipboard */ + includeHeaderWhenCopying?: boolean; + + /** option to specify a custom DOM element which to will be added the hidden textbox. It's useful if the grid is inside a modal dialog. */ + bodyElement?: HTMLElement; + + /** optional handler to run when copy action initializes */ + onCopyInit?: any; + + /** optional handler to run when copy action is complete */ + onCopySuccess?: any; + + /** function to add rows to table if paste overflows bottom of table, if this function is not provided new rows will be ignored. */ + newRowCreator?: (count: number) => void; + + /** suppresses paste */ + readOnlyMode?: boolean; + + /** option to specify a custom column header value extractor function */ + headerColumnValueExtractor?: (columnDef: Column) => any; + + + // -- + // Events + // ------------ + + /** Fired after extension (plugin) is registered by SlickGrid */ + onExtensionRegistered?: (plugin: any) => void; + + /** Fired when a copy cell is triggered */ + onCopyCells?: (e: Event, args: { ranges: SelectedRange[] }) => void; + + /** Fired when the command to copy the cells is cancelled */ + onCopyCancelled?: (e: Event, args: { ranges: SelectedRange[] }) => void; + + /** Fired when the user paste cells to the grid */ + onPasteCells?: (e: Event, args: { ranges: SelectedRange[] }) => void; +} diff --git a/src/app/modules/angular-slickgrid/models/gridOption.interface.ts b/src/app/modules/angular-slickgrid/models/gridOption.interface.ts index eafda4913..d73451ac5 100644 --- a/src/app/modules/angular-slickgrid/models/gridOption.interface.ts +++ b/src/app/modules/angular-slickgrid/models/gridOption.interface.ts @@ -7,6 +7,7 @@ import { CheckboxSelector, DraggableGrouping, EditCommand, + ExcelCopyBufferOption, ExportOption, FormatterOption, GridMenu, @@ -217,6 +218,9 @@ export interface GridOption { /** Do we want to enable localization translation (i18n)? */ enableTranslate?: boolean; + /** Options for the ExcelCopyBuffer Extension */ + excelCopyBufferOptions?: ExcelCopyBufferOption; + /** Do we want explicit grid initialization? */ explicitInitialization?: boolean; diff --git a/src/app/modules/angular-slickgrid/models/index.ts b/src/app/modules/angular-slickgrid/models/index.ts index c3cc88e44..479a784c4 100644 --- a/src/app/modules/angular-slickgrid/models/index.ts +++ b/src/app/modules/angular-slickgrid/models/index.ts @@ -32,6 +32,7 @@ export * from './editorValidator.interface'; export * from './editorValidatorOutput.interface'; export * from './elementPosition.interface'; export * from './emitterType.enum'; +export * from './excelCopyBufferOption.interface'; export * from './exportOption.interface'; export * from './extension.interface'; export * from './extensionModel.interface'; @@ -85,6 +86,7 @@ export * from './queryArgument.interface'; export * from './rowDetailView.interface'; export * from './rowMoveManager.interface'; export * from './searchTerm.type'; +export * from './selectedRange.interface'; export * from './selectOption.interface'; export * from './slickEvent.interface'; export * from './sortChangedArgs.interface'; diff --git a/src/app/modules/angular-slickgrid/models/selectedRange.interface.ts b/src/app/modules/angular-slickgrid/models/selectedRange.interface.ts new file mode 100644 index 000000000..60597f97d --- /dev/null +++ b/src/app/modules/angular-slickgrid/models/selectedRange.interface.ts @@ -0,0 +1,25 @@ +export interface SelectedRange { + /** Selection start from which cell? */ + fromCell: number; + + /** Selection start from which row? */ + fromRow: number; + + /** Selection goes to which cell? */ + toCell: number; + + /** Selection goes to which row? */ + toRow: number; + + /** Does the selection contain a row & cell number? */ + contains?: (row: number, cell: number) => boolean; + + /** Is it a Single Cell Selection? */ + isSingleCell?: () => boolean; + + /** Is it a Single Row Selection? */ + isSingleRow?: () => boolean; + + /** Output print to string */ + toString?: () => string; +}