diff --git a/examples/webpack-demo-vanilla-bundle/src/examples/example04.ts b/examples/webpack-demo-vanilla-bundle/src/examples/example04.ts index 3d800e366..d68418e0d 100644 --- a/examples/webpack-demo-vanilla-bundle/src/examples/example04.ts +++ b/examples/webpack-demo-vanilla-bundle/src/examples/example04.ts @@ -342,6 +342,7 @@ export class Example4 { autoResize: { container: '.demo-container', }, + enableAutoTooltip: true, enableAutoSizeColumns: true, enableAutoResize: true, enableCellNavigation: true, diff --git a/packages/common/src/enums/slickPluginList.enum.ts b/packages/common/src/enums/slickPluginList.enum.ts index 0438e7812..950bba67b 100644 --- a/packages/common/src/enums/slickPluginList.enum.ts +++ b/packages/common/src/enums/slickPluginList.enum.ts @@ -1,5 +1,4 @@ import { - SlickAutoTooltips, SlickCellExternalCopyManager, SlickCellMenu, SlickCellRangeDecorator, @@ -16,9 +15,10 @@ import { SlickRowMoveManager, SlickRowSelectionModel, } from '../interfaces/index'; +import { AutoTooltipsPlugin } from '../plugins/index'; export type SlickPluginList = - SlickAutoTooltips | + AutoTooltipsPlugin | SlickCellExternalCopyManager | SlickCellMenu | SlickCellRangeDecorator | diff --git a/packages/common/src/extensions/__tests__/autoTooltipExtension.spec.ts b/packages/common/src/extensions/__tests__/autoTooltipExtension.spec.ts deleted file mode 100644 index 6216e3af4..000000000 --- a/packages/common/src/extensions/__tests__/autoTooltipExtension.spec.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { GridOption, SlickAutoTooltips, SlickGrid, SlickNamespace } from '../../interfaces/index'; -import { AutoTooltipExtension } from '../autoTooltipExtension'; -import { SharedService } from '../../services/shared.service'; - -declare const Slick: SlickNamespace; - -const gridStub = { - getOptions: jest.fn(), - registerPlugin: jest.fn(), -} as unknown as SlickGrid; - -const mockAddon = jest.fn().mockImplementation(() => ({ - init: jest.fn(), - destroy: jest.fn() -})); - -describe('autoTooltipExtension', () => { - jest.mock('slickgrid/plugins/slick.autotooltips', () => mockAddon); - Slick.AutoTooltips = mockAddon; - - let extension: AutoTooltipExtension; - let sharedService: SharedService; - const gridOptionsMock = { enableAutoTooltip: true } as GridOption; - - beforeEach(() => { - sharedService = new SharedService(); - extension = new AutoTooltipExtension(sharedService); - }); - - 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); - }); - - it('should register the addon', () => { - const pluginSpy = jest.spyOn(SharedService.prototype.slickGrid, 'registerPlugin'); - - const instance = extension.register(); - const addonInstance = extension.getAddonInstance(); - - expect(instance).toBeTruthy(); - expect(instance).toEqual(addonInstance); - expect(mockAddon).toHaveBeenCalledWith(undefined); - expect(pluginSpy).toHaveBeenCalledWith(instance); - }); - - it('should dispose of the addon', () => { - const instance = extension.register() as SlickAutoTooltips; - const destroySpy = jest.spyOn(instance, 'destroy'); - - extension.dispose(); - - expect(destroySpy).toHaveBeenCalled(); - }); - - it('should provide addon options and expect them to be called in the addon constructor', () => { - const optionMock = { enableForCells: true, enableForHeaderCells: false, maxToolTipLength: 12 }; - const addonOptions = { ...gridOptionsMock, autoTooltipOptions: optionMock }; - jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(addonOptions); - - extension.register(); - - expect(mockAddon).toHaveBeenCalledWith(optionMock); - }); - }); -}); diff --git a/packages/common/src/extensions/autoTooltipExtension.ts b/packages/common/src/extensions/autoTooltipExtension.ts deleted file mode 100644 index 209ec3113..000000000 --- a/packages/common/src/extensions/autoTooltipExtension.ts +++ /dev/null @@ -1,39 +0,0 @@ -import 'slickgrid/plugins/slick.autotooltips'; - -import { SharedService } from '../services/shared.service'; -import { SlickAutoTooltips, Extension, SlickNamespace } from '../interfaces/index'; - -// using external SlickGrid JS libraries -declare const Slick: SlickNamespace; - -export class AutoTooltipExtension implements Extension { - private _addon: SlickAutoTooltips | null = null; - - constructor(private readonly sharedService: SharedService) { } - - dispose() { - if (this._addon && this._addon.destroy) { - this._addon.destroy(); - this._addon = null; - } - } - - /** Get the instance of the SlickGrid addon (control or plugin). */ - getAddonInstance(): SlickAutoTooltips | null { - return this._addon; - } - - /** Register the 3rd party addon (plugin) */ - register(): SlickAutoTooltips | null { - if (this.sharedService && this.sharedService.slickGrid && this.sharedService.gridOptions) { - const options = this.sharedService.gridOptions.autoTooltipOptions; - this._addon = new Slick.AutoTooltips(options); - if (this._addon) { - this.sharedService.slickGrid.registerPlugin(this._addon); - } - - return this._addon; - } - return null; - } -} diff --git a/packages/common/src/extensions/index.ts b/packages/common/src/extensions/index.ts index 3e84f1aad..be7aace10 100644 --- a/packages/common/src/extensions/index.ts +++ b/packages/common/src/extensions/index.ts @@ -1,4 +1,3 @@ -export * from './autoTooltipExtension'; export * from './cellExternalCopyManagerExtension'; export * from './cellMenuExtension'; export * from './checkboxSelectorExtension'; diff --git a/packages/common/src/interfaces/autoTooltipOption.interface.ts b/packages/common/src/interfaces/autoTooltipOption.interface.ts index 53e891ba9..488e7cdc5 100644 --- a/packages/common/src/interfaces/autoTooltipOption.interface.ts +++ b/packages/common/src/interfaces/autoTooltipOption.interface.ts @@ -6,7 +6,7 @@ export interface AutoTooltipOption { enableForHeaderCells: boolean; /** what is the maximum tooltip length in pixels (only type the number) */ - maxToolTipLength: number; + maxToolTipLength?: number; /** Allow preventing custom tooltips from being overwritten by auto tooltips */ replaceExisting?: boolean; diff --git a/packages/common/src/interfaces/index.ts b/packages/common/src/interfaces/index.ts index 16be20a6f..448591066 100644 --- a/packages/common/src/interfaces/index.ts +++ b/packages/common/src/interfaces/index.ts @@ -126,7 +126,6 @@ export * from './searchColumnFilter.interface'; export * from './selectOption.interface'; export * from './servicePagination.interface'; export * from './singleColumnSort.interface'; -export * from './slickAutoTooltips.interface'; export * from './slickCellExternalCopyManager.interface'; export * from './slickCellMenu.interface'; export * from './slickCellRangeDecorator.interface'; diff --git a/packages/common/src/interfaces/slickAutoTooltips.interface.ts b/packages/common/src/interfaces/slickAutoTooltips.interface.ts deleted file mode 100644 index c143dd2e5..000000000 --- a/packages/common/src/interfaces/slickAutoTooltips.interface.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { AutoTooltipOption } from './autoTooltipOption.interface'; -import { SlickGrid } from './slickGrid.interface'; - -export interface SlickAutoTooltips { - pluginName: 'AutoTooltips'; - - /** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */ - constructor: (options: AutoTooltipOption) => void; - - /** Initialize the SlickGrid 3rd party plugin */ - init(grid: SlickGrid): void; - - /** Destroy (dispose) the SlickGrid 3rd party plugin */ - destroy(): void; -} diff --git a/packages/common/src/interfaces/slickNamespace.interface.ts b/packages/common/src/interfaces/slickNamespace.interface.ts index 1ab534c01..1ac5d5d0d 100644 --- a/packages/common/src/interfaces/slickNamespace.interface.ts +++ b/packages/common/src/interfaces/slickNamespace.interface.ts @@ -15,7 +15,7 @@ import { RowDetailViewOption, RowMoveManagerOption, RowSelectionModelOption, - SlickAutoTooltips, + // SlickAutoTooltips, SlickCellExternalCopyManager, SlickCellMenu, SlickCellRangeDecorator, @@ -44,6 +44,7 @@ import { SlickRowSelectionModel, } from './index'; import { CompositeEditorOption } from './compositeEditorOption.interface'; +import { AutoTooltipsPlugin } from '../plugins/index'; /** * Slick Grid class interface of the entire library and it's multiple controls/plugins. @@ -109,7 +110,7 @@ export interface SlickNamespace { // ------------------------------- /** AutoTooltips is a 3rd party plugin (addon) to show/hide tooltips when columns are too narrow to fit content. */ - AutoTooltips: new (options?: AutoTooltipOption) => SlickAutoTooltips; + AutoTooltips: new (options?: AutoTooltipOption) => AutoTooltipsPlugin; /** Cell External Copy Manager is a 3rd party plugin (addon) which is an Excel like copy cell range addon */ CellExternalCopyManager: new (options?: ExcelCopyBufferOption) => SlickCellExternalCopyManager; diff --git a/packages/common/src/plugins/__tests__/autoTooltips.plugin.spec.ts b/packages/common/src/plugins/__tests__/autoTooltips.plugin.spec.ts new file mode 100644 index 000000000..dee647a48 --- /dev/null +++ b/packages/common/src/plugins/__tests__/autoTooltips.plugin.spec.ts @@ -0,0 +1,57 @@ +import { GridOption, SlickGrid, SlickNamespace } from '../../interfaces/index'; +import { SharedService } from '../../services/shared.service'; +import { AutoTooltipsPlugin } from '../autoTooltips.plugin'; + +declare const Slick: SlickNamespace; + +const gridStub = { + getOptions: jest.fn(), + registerPlugin: jest.fn(), + onHeaderMouseEnter: new Slick.Event(), + onMouseEnter: new Slick.Event(), +} as unknown as SlickGrid; + +const mockAddon = jest.fn().mockImplementation(() => ({ + init: jest.fn(), + destroy: jest.fn() +})); + +describe('AutoTooltip Plugin', () => { + jest.mock('slickgrid/plugins/slick.autotooltips', () => mockAddon); + Slick.AutoTooltips = mockAddon; + + let plugin: AutoTooltipsPlugin; + let sharedService: SharedService; + + beforeEach(() => { + sharedService = new SharedService(); + plugin = new AutoTooltipsPlugin(); + }); + + it('should create the plugin', () => { + expect(plugin).toBeTruthy(); + expect(plugin.eventHandler).toBeTruthy(); + }); + + it('should use default options when instantiating the plugin without passing any arguments', () => { + plugin.init(gridStub); + + expect(plugin.options).toEqual({ + enableForCells: true, + enableForHeaderCells: false, + maxToolTipLength: undefined, + replaceExisting: true + }); + }); + + // describe('registered addon', () => { + // beforeEach(() => { + // jest.spyOn(SharedService.prototype, 'slickGrid', 'get').mockReturnValue(gridStub); + // jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + // }); + + // it('should ', () => { + + // }); + // }); +}); diff --git a/packages/common/src/plugins/autoTooltips.plugin.ts b/packages/common/src/plugins/autoTooltips.plugin.ts new file mode 100644 index 000000000..5d4daa9c9 --- /dev/null +++ b/packages/common/src/plugins/autoTooltips.plugin.ts @@ -0,0 +1,99 @@ +import { + AutoTooltipOption, + Column, + GetSlickEventType, + SlickEventHandler, + SlickGrid, + SlickNamespace, +} from '../interfaces/index'; + +// using external SlickGrid JS libraries +declare const Slick: SlickNamespace; + +export class AutoTooltipsPlugin { + private _eventHandler!: SlickEventHandler; + private _grid!: SlickGrid; + private _options?: AutoTooltipOption; + private _defaults = { + enableForCells: true, + enableForHeaderCells: false, + maxToolTipLength: undefined, + replaceExisting: true + } as AutoTooltipOption; + pluginName: 'AutoTooltips' = 'AutoTooltips'; + + /** Constructor of the SlickGrid 3rd party plugin, it can optionally receive options */ + constructor(options?: AutoTooltipOption) { + this._eventHandler = new Slick.EventHandler(); + this._options = options; + } + + get eventHandler(): SlickEventHandler { + return this._eventHandler; + } + + get options(): AutoTooltipOption { + return this._options as AutoTooltipOption; + } + + /** Initialize plugin. */ + init(grid: SlickGrid) { + this._options = { ...this._defaults, ...this._options }; + this._grid = grid; + if (this._options.enableForCells) { + const onMouseEnterHandler = this._grid.onMouseEnter; + (this._eventHandler as SlickEventHandler>).subscribe(onMouseEnterHandler, this.handleMouseEnter.bind(this)); + } + if (this._options.enableForHeaderCells) { + const onHeaderMouseEnterHandler = this._grid.onHeaderMouseEnter; + (this._eventHandler as SlickEventHandler>).subscribe(onHeaderMouseEnterHandler, this.handleHeaderMouseEnter.bind(this)); + } + } + + /** Destroy (dispose) the SlickGrid 3rd party plugin */ + destroy() { + this._eventHandler?.unsubscribeAll(); + } + + // -- + // private functions + // ------------------ + + /** + * Handle mouse entering grid cell to add/remove tooltip. + * @param {Object} event - The event + */ + private handleMouseEnter(event: Event) { + const cell = this._grid.getCellFromEvent(event); + if (cell) { + let node: HTMLElement | null = this._grid.getCellNode(cell.row, cell.cell); + let text; + if (this._options && node && (!node.title || this._options?.replaceExisting)) { + if (node.clientWidth < node.scrollWidth) { + text = node.textContent?.trim() ?? ''; + if (this._options?.maxToolTipLength && text.length > this._options?.maxToolTipLength) { + text = text.substr(0, this._options.maxToolTipLength - 3) + '...'; + } + } else { + text = ''; + } + node.title = text; + } + node = null; + } + } + + /** + * Handle mouse entering header cell to add/remove tooltip. + * @param {Object} event - The event + * @param {Object} args.column - The column definition + */ + private handleHeaderMouseEnter(event: Event, args: { column: Column; }) { + const column = args.column; + let node = (event.target as HTMLDivElement).querySelector('.slick-header-column'); + if (node && !column?.toolTip) { + node.title = (node.clientWidth < node.scrollWidth) ? column.name ?? '' : ''; + } + node = null; + } +} \ No newline at end of file diff --git a/packages/common/src/plugins/index.ts b/packages/common/src/plugins/index.ts new file mode 100644 index 000000000..60ba378d1 --- /dev/null +++ b/packages/common/src/plugins/index.ts @@ -0,0 +1 @@ +export * from './autoTooltips.plugin'; \ No newline at end of file diff --git a/packages/common/src/services/__tests__/extension.service.spec.ts b/packages/common/src/services/__tests__/extension.service.spec.ts index 46238b8f0..5efcdea69 100644 --- a/packages/common/src/services/__tests__/extension.service.spec.ts +++ b/packages/common/src/services/__tests__/extension.service.spec.ts @@ -1,7 +1,6 @@ import { ExtensionName } from '../../enums/index'; import { Column, ExtensionModel, GridOption, SlickGrid, SlickHeaderMenu } from '../../interfaces/index'; import { - AutoTooltipExtension, CellExternalCopyManagerExtension, CellMenuExtension, CheckboxSelectorExtension, @@ -18,6 +17,7 @@ import { } from '../../extensions'; import { ExtensionService, SharedService } from '..'; import { TranslateServiceStub } from '../../../../../test/translateServiceStub'; +import { AutoTooltipsPlugin } from '../../plugins/index'; jest.mock('flatpickr', () => { }); @@ -79,7 +79,6 @@ describe('ExtensionService', () => { service = new ExtensionService( // extensions - extensionStub as unknown as AutoTooltipExtension, extensionStub as unknown as CellExternalCopyManagerExtension, extensionCellMenuStub as unknown as CellMenuExtension, extensionStub as unknown as CheckboxSelectorExtension, @@ -201,15 +200,15 @@ describe('ExtensionService', () => { it('should register the AutoTooltip addon when "enableAutoTooltip" is set in the grid options', () => { const gridOptionsMock = { enableAutoTooltip: true } as GridOption; - const extSpy = jest.spyOn(extensionStub, 'register').mockReturnValue(instanceMock); - const gridSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + const extSpy = jest.spyOn(gridStub, 'registerPlugin'); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); service.bindDifferentExtensions(); const output = service.getExtensionByName(ExtensionName.autoTooltip); - expect(gridSpy).toHaveBeenCalled(); expect(extSpy).toHaveBeenCalled(); - expect(output).toEqual({ name: ExtensionName.autoTooltip, instance: instanceMock as unknown, class: extensionStub } as ExtensionModel); + expect(output).toEqual({ name: ExtensionName.autoTooltip, instance: expect.anything(), class: {} } as ExtensionModel); + expect(output.instance instanceof AutoTooltipsPlugin).toBeTrue(); }); it('should register the ColumnPicker addon when "enableColumnPicker" is set in the grid options', () => { @@ -700,7 +699,6 @@ describe('ExtensionService', () => { translateService = undefined as any; service = new ExtensionService( // extensions - extensionStub as unknown as AutoTooltipExtension, extensionStub as unknown as CellExternalCopyManagerExtension, extensionStub as unknown as CellMenuExtension, extensionStub as unknown as CheckboxSelectorExtension, diff --git a/packages/common/src/services/extension.service.ts b/packages/common/src/services/extension.service.ts index 81e85a958..8fd4101e2 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 { ExtensionList, ExtensionName, SlickControlList, SlickPluginList } from '../enums/index'; import { - AutoTooltipExtension, CellExternalCopyManagerExtension, CellMenuExtension, CheckboxSelectorExtension, @@ -23,6 +22,7 @@ import { } from '../extensions/index'; import { SharedService } from './shared.service'; import { TranslaterService } from './translater.service'; +import { AutoTooltipsPlugin } from '../plugins/index'; export class ExtensionService { private _extensionCreatedList: ExtensionList = {} as ExtensionList; @@ -33,7 +33,6 @@ export class ExtensionService { } constructor( - private readonly autoTooltipExtension: AutoTooltipExtension, private readonly cellExternalCopyExtension: CellExternalCopyManagerExtension, private readonly cellMenuExtension: CellMenuExtension, private readonly checkboxSelectorExtension: CheckboxSelectorExtension, @@ -121,10 +120,11 @@ export class ExtensionService { } // Auto Tooltip Plugin - if (this.sharedService.gridOptions.enableAutoTooltip && this.autoTooltipExtension && this.autoTooltipExtension.register) { - const instance = this.autoTooltipExtension.register(); + if (this.sharedService.gridOptions.enableAutoTooltip) { + const instance = new AutoTooltipsPlugin(); if (instance) { - this._extensionList[ExtensionName.autoTooltip] = { name: ExtensionName.autoTooltip, class: this.autoTooltipExtension, instance }; + this.sharedService.slickGrid.registerPlugin(instance); + this._extensionList[ExtensionName.autoTooltip] = { name: ExtensionName.autoTooltip, class: {}, instance }; } } 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 2b4e39556..f54d1b83c 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 { Subscription, // extensions - AutoTooltipExtension, CheckboxSelectorExtension, CellExternalCopyManagerExtension, CellMenuExtension, @@ -358,7 +357,6 @@ export class SlickVanillaGridBundle { this.paginationService = services?.paginationService ?? new PaginationService(this._eventPubSubService, this.sharedService, this.backendUtilityService); // extensions - const autoTooltipExtension = new AutoTooltipExtension(this.sharedService); const cellExternalCopyManagerExtension = new CellExternalCopyManagerExtension(this.extensionUtility, this.sharedService); const cellMenuExtension = new CellMenuExtension(this.extensionUtility, this.sharedService, this.translaterService); const contextMenuExtension = new ContextMenuExtension(this.extensionUtility, this.sharedService, this.treeDataService, this.translaterService); @@ -374,7 +372,6 @@ export class SlickVanillaGridBundle { const rowSelectionExtension = new RowSelectionExtension(this.sharedService); this.extensionService = services?.extensionService ?? new ExtensionService( - autoTooltipExtension, cellExternalCopyManagerExtension, cellMenuExtension, checkboxExtension,