From 7907cb8c89de067b0a0cb009f7aa110a417b14e7 Mon Sep 17 00:00:00 2001 From: ghiscoding Date: Wed, 2 Dec 2020 11:47:40 -0500 Subject: [PATCH] fix(core): showing/hiding column shouldn't affect its freezing position - when calling "hide column" we need to readjust the freezingColumn by (-1) when on left container - when showing/hiding a column from ColumnPicker/GridMenu, we need to check if we need to also readjust when column is on the left container --- package.json | 4 +- .../angular-slickgrid-constructor.spec.ts | 10 ++ .../components/angular-slickgrid.component.ts | 6 + .../__tests__/columnPickerExtension.spec.ts | 32 ++++- .../__tests__/extensionUtility.spec.ts | 74 ++++++++++- .../__tests__/gridMenuExtension.spec.ts | 22 +++- .../__tests__/headerMenuExtension.spec.ts | 30 +++++ .../extensions/columnPickerExtension.ts | 11 +- .../extensions/extensionUtility.ts | 38 +++++- .../extensions/gridMenuExtension.ts | 9 +- .../extensions/headerMenuExtension.ts | 9 ++ .../services/__tests__/shared.service.spec.ts | 10 ++ .../services/shared.service.ts | 10 ++ test/cypress/integration/example20.spec.js | 124 ++++++++++++++++++ 14 files changed, 374 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index befdd4d7b..ba4fdab11 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "lodash.isequal": "^4.5.0", "moment-mini": "^2.24.0", "rxjs": "^6.3.3", - "slickgrid": "^2.4.31", + "slickgrid": "^2.4.32", "text-encoding-utf-8": "^1.0.2" }, "peerDependencies": { @@ -178,4 +178,4 @@ "yargs": "^15.4.1", "zone.js": "~0.9.1" } -} +} \ No newline at end of file diff --git a/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid-constructor.spec.ts b/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid-constructor.spec.ts index bea28bd56..c61d385bf 100644 --- a/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid-constructor.spec.ts +++ b/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid-constructor.spec.ts @@ -346,6 +346,16 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () = expect(loadSpy).toHaveBeenCalled(); }); + it('should keep frozen column index reference (via frozenVisibleColumnId) when grid is a frozen grid', () => { + const sharedFrozenIndexSpy = jest.spyOn(SharedService.prototype, 'frozenVisibleColumnId', 'set'); + component.gridOptions.frozenColumn = 0; + + component.ngOnInit(); + component.ngAfterViewInit(); + + expect(sharedFrozenIndexSpy).toHaveBeenCalledWith('name'); + }); + describe('initialization method', () => { describe('columns definitions changed', () => { it('should expect "translateColumnHeaders" being called when "enableTranslate" is set', () => { diff --git a/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts b/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts index 5034af0e6..34454ad4f 100644 --- a/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts +++ b/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts @@ -842,6 +842,12 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn // emit the Grid & DataView object to make them available in parent component this.onGridCreated.emit(this.grid); + // when it's a frozen grid, we need to keep the frozen column id for reference if we ever show/hide column from ColumnPicker/GridMenu afterward + const frozenColumnIndex = this.gridOptions.frozenColumn !== undefined ? this.gridOptions.frozenColumn : -1; + if (frozenColumnIndex >= 0 && frozenColumnIndex <= this._columnDefinitions.length) { + this.sharedService.frozenVisibleColumnId = this._columnDefinitions[frozenColumnIndex].id || ''; + } + // initialize the SlickGrid grid this.grid.init(); diff --git a/src/app/modules/angular-slickgrid/extensions/__tests__/columnPickerExtension.spec.ts b/src/app/modules/angular-slickgrid/extensions/__tests__/columnPickerExtension.spec.ts index cb966b2e0..346c8f0c0 100644 --- a/src/app/modules/angular-slickgrid/extensions/__tests__/columnPickerExtension.spec.ts +++ b/src/app/modules/angular-slickgrid/extensions/__tests__/columnPickerExtension.spec.ts @@ -11,6 +11,8 @@ declare const Slick: any; const gridStub = { getOptions: jest.fn(), registerPlugin: jest.fn(), + setColumns: jest.fn(), + setOptions: jest.fn(), }; const mockAddon = jest.fn().mockImplementation(() => ({ @@ -80,41 +82,59 @@ describe('columnPickerExtension', () => { expect(mockAddon).toHaveBeenCalledWith(columnsMock, gridStub, gridOptionsMock); }); - it('should call internal event handler subscribe and expect the "onColumnSpy" option to be called when addon notify is called', () => { + it('should call internal event handler subscribe and expect the "onColumnsChanged" grid option to be called when addon notify is called', () => { const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.columnPicker, 'onColumnsChanged'); const visibleColsSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set'); + const readjustSpy = jest.spyOn(extensionUtility, 'readjustFrozenColumnIndexWhenNeeded'); const instance = extension.register(); - instance.onColumnsChanged.notify({ columns: columnsMock.slice(0, 1), grid: gridStub }, new Slick.EventData(), gridStub); + instance.onColumnsChanged.notify({ columnId: 'field1', showing: false, columns: columnsMock.slice(0, 1), grid: gridStub }, new Slick.EventData(), gridStub); + expect(readjustSpy).not.toHaveBeenCalled(); expect(handlerSpy).toHaveBeenCalledTimes(1); expect(handlerSpy).toHaveBeenCalledWith( { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, expect.anything() ); - expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), { columns: columnsMock.slice(0, 1), grid: gridStub }); + expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), { columnId: 'field1', showing: false, columns: columnsMock.slice(0, 1), grid: gridStub }); expect(visibleColsSpy).not.toHaveBeenCalled(); }); - it(`should call internal event handler subscribe and expect the "onColumnSpy" option to be called when addon notify is called + it(`should call internal event handler subscribe and expect the "onColumnsChanged" grid option to be called when addon notify is called and it should override "visibleColumns" when array passed as arguments is bigger than previous visible columns`, () => { const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.columnPicker, 'onColumnsChanged'); const visibleColsSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set'); const instance = extension.register(); - instance.onColumnsChanged.notify({ columns: columnsMock, grid: gridStub }, new Slick.EventData(), gridStub); + instance.onColumnsChanged.notify({ columnId: 'field1', showing: true, columns: columnsMock, grid: gridStub }, new Slick.EventData(), gridStub); expect(handlerSpy).toHaveBeenCalledTimes(1); expect(handlerSpy).toHaveBeenCalledWith( { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, expect.anything() ); - expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), { columns: columnsMock, grid: gridStub }); + expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), { columnId: 'field1', showing: true, columns: columnsMock, grid: gridStub }); expect(visibleColsSpy).toHaveBeenCalledWith(columnsMock); }); + it('should call internal "onColumnsChanged" event and expect "readjustFrozenColumnIndexWhenNeeded" method to be called when the grid is detected to be a frozen grid', () => { + gridOptionsMock.frozenColumn = 0; + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const readjustSpy = jest.spyOn(extensionUtility, 'readjustFrozenColumnIndexWhenNeeded'); + + const instance = extension.register(); + instance.onColumnsChanged.notify({ columnId: 'field1', showing: false, allColumns: columnsMock, columns: columnsMock.slice(0, 1), grid: gridStub }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(1); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(readjustSpy).toHaveBeenCalledWith('field1', 0, false, columnsMock, columnsMock.slice(0, 1)); + }); + it('should dispose of the addon', () => { const instance = extension.register(); const destroySpy = jest.spyOn(instance, 'destroy'); diff --git a/src/app/modules/angular-slickgrid/extensions/__tests__/extensionUtility.spec.ts b/src/app/modules/angular-slickgrid/extensions/__tests__/extensionUtility.spec.ts index 85af318f7..c31c46d21 100644 --- a/src/app/modules/angular-slickgrid/extensions/__tests__/extensionUtility.spec.ts +++ b/src/app/modules/angular-slickgrid/extensions/__tests__/extensionUtility.spec.ts @@ -2,11 +2,18 @@ import { TestBed } from '@angular/core/testing'; import { TranslateService, TranslateModule } from '@ngx-translate/core'; import { ExtensionUtility } from '../extensionUtility'; -import { ExtensionName, GridOption } from '../../models'; +import { Column, ExtensionName, GridOption, SlickGrid } from '../../models'; import { SharedService } from '../../services/shared.service'; declare let Slick: any; +const gridStub = { + getOptions: jest.fn(), + setColumns: jest.fn(), + setOptions: jest.fn(), + registerPlugin: jest.fn(), +} as unknown as SlickGrid; + const mockAddon = jest.fn().mockImplementation(() => ({ init: jest.fn(), destroy: jest.fn() @@ -227,6 +234,71 @@ describe('ExtensionUtility', () => { expect(output).toBe('Commandes'); }); }); + + describe('readjustFrozenColumnIndexWhenNeeded method', () => { + let gridOptionsMock: GridOption; + + beforeEach(() => { + gridOptionsMock = { frozenColumn: 1 } as GridOption; + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + jest.spyOn(SharedService.prototype, 'frozenVisibleColumnId', 'get').mockReturnValue('field2'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should increase "frozenColumn" from 0 to 1 when showing a column that was previously hidden and its index is lower or equal to provided argument (2nd arg, frozenColumnIndex)', () => { + const allColumns = [{ id: 'field1' }, { id: 'field2' }, { id: 'field3' }] as Column[]; + const visibleColumns = [{ id: 'field1' }, { id: 'field2' }] as Column[]; + const setOptionSpy = jest.spyOn(SharedService.prototype.grid, 'setOptions'); + + utility.readjustFrozenColumnIndexWhenNeeded('field1', 0, true, allColumns, visibleColumns); + + expect(setOptionSpy).toHaveBeenCalledWith({ frozenColumn: 1 }); + }); + + it('should keep "frozenColumn" at 0 when showing a column that was previously hidden and its index is greater than provided argument (2nd arg, frozenColumnIndex)', () => { + const allColumns = [{ id: 'field1' }, { id: 'field2' }, { id: 'field3' }] as Column[]; + const visibleColumns = [{ id: 'field1' }, { id: 'field2' }, { id: 'field3' }] as Column[]; + const setOptionSpy = jest.spyOn(SharedService.prototype.grid, 'setOptions'); + + utility.readjustFrozenColumnIndexWhenNeeded('field3', 0, true, allColumns, visibleColumns); + + expect(setOptionSpy).not.toHaveBeenCalled(); + }); + + it('should decrease "frozenColumn" from 1 to 0 when hiding a column that was previously shown and its index is lower or equal to provided argument (2nd arg, frozenColumnIndex)', () => { + const allColumns = [{ id: 'field1' }, { id: 'field2' }, { id: 'field3' }] as Column[]; + const visibleColumns = [{ id: 'field1' }, { id: 'field2' }] as Column[]; + const setOptionSpy = jest.spyOn(SharedService.prototype.grid, 'setOptions'); + + utility.readjustFrozenColumnIndexWhenNeeded('field1', 1, false, allColumns, visibleColumns); + + expect(setOptionSpy).toHaveBeenCalledWith({ frozenColumn: 0 }); + }); + + it('should keep "frozenColumn" at 1 when hiding a column that was previously hidden and its index is greater than provided argument (2nd arg, frozenColumnIndex)', () => { + const allColumns = [{ id: 'field1' }, { id: 'field2' }, { id: 'field3' }] as Column[]; + const visibleColumns = [{ id: 'field1' }, { id: 'field2' }] as Column[]; + const setOptionSpy = jest.spyOn(SharedService.prototype.grid, 'setOptions'); + + utility.readjustFrozenColumnIndexWhenNeeded('field3', 1, false, allColumns, visibleColumns); + + expect(setOptionSpy).not.toHaveBeenCalled(); + }); + + it('should not change "frozenColumn" when showing a column that was not found in the visibleColumns columns array', () => { + const allColumns = [{ id: 'field1' }, { id: 'field2' }, { id: 'field3' }] as Column[]; + const visibleColumns = [{ id: 'field1' }, { field: 'field2' }] as Column[]; + const setOptionSpy = jest.spyOn(SharedService.prototype.grid, 'setOptions'); + + utility.readjustFrozenColumnIndexWhenNeeded('fiel3', 0, true, allColumns, visibleColumns); + + expect(setOptionSpy).not.toHaveBeenCalled(); + }); + }); }); describe('without ngx-translate', () => { diff --git a/src/app/modules/angular-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts b/src/app/modules/angular-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts index 945f919bb..ef74bfcf4 100644 --- a/src/app/modules/angular-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts +++ b/src/app/modules/angular-slickgrid/extensions/__tests__/gridMenuExtension.spec.ts @@ -195,16 +195,18 @@ describe('gridMenuExtension', () => { const onCloseSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onMenuClose'); const onCommandSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onCommand'); const visibleColsSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set'); + const readjustSpy = jest.spyOn(extensionUtility, 'readjustFrozenColumnIndexWhenNeeded'); const instance = extension.register(); - instance.onColumnsChanged.notify({ columns: columnsMock.slice(0, 1), grid: gridStub }, new Slick.EventData(), gridStub); + instance.onColumnsChanged.notify({ columnId: 'field1', showing: false, columns: columnsMock.slice(0, 1), grid: gridStub }, new Slick.EventData(), gridStub); + expect(readjustSpy).not.toHaveBeenCalled(); expect(handlerSpy).toHaveBeenCalledTimes(5); expect(handlerSpy).toHaveBeenCalledWith( { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, expect.anything() ); - expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), { columns: columnsMock.slice(0, 1), grid: gridStub }); + expect(onColumnSpy).toHaveBeenCalledWith(expect.anything(), { columnId: 'field1', showing: false, columns: columnsMock.slice(0, 1), grid: gridStub }); expect(onAfterSpy).not.toHaveBeenCalled(); expect(onBeforeSpy).not.toHaveBeenCalled(); expect(onCloseSpy).not.toHaveBeenCalled(); @@ -238,6 +240,22 @@ describe('gridMenuExtension', () => { expect(visibleColsSpy).toHaveBeenCalledWith(columnsMock); }); + it('should call internal "onColumnsChanged" event and expect "readjustFrozenColumnIndexWhenNeeded" method to be called when the grid is detected to be a frozen grid', () => { + gridOptionsMock.frozenColumn = 0; + const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); + const readjustSpy = jest.spyOn(extensionUtility, 'readjustFrozenColumnIndexWhenNeeded'); + + const instance = extension.register(); + instance.onColumnsChanged.notify({ columnId: 'field1', showing: false, allColumns: columnsMock, columns: columnsMock.slice(0, 1), grid: gridStub }, new Slick.EventData(), gridStub); + + expect(handlerSpy).toHaveBeenCalledTimes(5); + expect(handlerSpy).toHaveBeenCalledWith( + { notify: expect.anything(), subscribe: expect.anything(), unsubscribe: expect.anything(), }, + expect.anything() + ); + expect(readjustSpy).toHaveBeenCalledWith('field1', 0, false, columnsMock, columnsMock.slice(0, 1)); + }); + it('should call internal event handler subscribe and expect the "onBeforeMenuShow" option to be called when addon notify is called', () => { const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe'); const onColumnSpy = jest.spyOn(SharedService.prototype.gridOptions.gridMenu, 'onColumnsChanged'); diff --git a/src/app/modules/angular-slickgrid/extensions/__tests__/headerMenuExtension.spec.ts b/src/app/modules/angular-slickgrid/extensions/__tests__/headerMenuExtension.spec.ts index 1e33e54a7..2d31dfcbc 100644 --- a/src/app/modules/angular-slickgrid/extensions/__tests__/headerMenuExtension.spec.ts +++ b/src/app/modules/angular-slickgrid/extensions/__tests__/headerMenuExtension.spec.ts @@ -341,6 +341,7 @@ describe('headerMenuExtension', () => { jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(1); jest.spyOn(gridStub, 'getColumns').mockReturnValue(columnsMock); const setColumnsSpy = jest.spyOn(gridStub, 'setColumns'); + const setOptionSpy = jest.spyOn(gridStub, 'setOptions'); const visibleSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set'); const updatedColumnsMock = [{ id: 'field1', field: 'field1', nameKey: 'TITLE', width: 100, @@ -357,6 +358,35 @@ describe('headerMenuExtension', () => { extension.hideColumn(columnsMock[1]); + expect(setOptionSpy).not.toHaveBeenCalled(); + expect(visibleSpy).toHaveBeenCalledWith(updatedColumnsMock); + expect(setColumnsSpy).toHaveBeenCalledWith(updatedColumnsMock); + }); + + it('should call hideColumn and expect "setOptions" to be called with new "frozenColumn" index when the grid is detected to be a frozen grid', () => { + gridOptionsMock.frozenColumn = 1; + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(gridStub, 'getColumnIndex').mockReturnValue(1); + jest.spyOn(gridStub, 'getColumns').mockReturnValue(columnsMock); + const setColumnsSpy = jest.spyOn(gridStub, 'setColumns'); + const setOptionSpy = jest.spyOn(gridStub, 'setOptions'); + const visibleSpy = jest.spyOn(SharedService.prototype, 'visibleColumns', 'set'); + const updatedColumnsMock = [{ + id: 'field1', field: 'field1', nameKey: 'TITLE', width: 100, + header: { + menu: { + items: [ + { iconCssClass: 'fa fa-thumb-tack', title: 'Geler les colonnes', command: 'freeze-columns', positionOrder: 48 }, + { divider: true, command: '', positionOrder: 49 }, + { command: 'hide', iconCssClass: 'fa fa-times', positionOrder: 55, title: 'Cacher la colonne' } + ] + } + } + }] as Column[]; + + extension.hideColumn(columnsMock[1]); + + expect(setOptionSpy).toHaveBeenCalledWith({ frozenColumn: 0 }); expect(visibleSpy).toHaveBeenCalledWith(updatedColumnsMock); expect(setColumnsSpy).toHaveBeenCalledWith(updatedColumnsMock); }); diff --git a/src/app/modules/angular-slickgrid/extensions/columnPickerExtension.ts b/src/app/modules/angular-slickgrid/extensions/columnPickerExtension.ts index 4eb600cae..dd2533b39 100644 --- a/src/app/modules/angular-slickgrid/extensions/columnPickerExtension.ts +++ b/src/app/modules/angular-slickgrid/extensions/columnPickerExtension.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { ColumnPicker, Extension, ExtensionName, SlickEventHandler } from '../models/index'; +import { Column, ColumnPicker, Extension, ExtensionName, SlickEventHandler } from '../models/index'; import { ExtensionUtility } from './extensionUtility'; import { SharedService } from '../services/shared.service'; @@ -56,13 +56,20 @@ export class ColumnPickerExtension implements Extension { if (this._columnPicker.onExtensionRegistered) { this._columnPicker.onExtensionRegistered(this._addon); } - this._eventHandler.subscribe(this._addon.onColumnsChanged, (e: any, args: { columns: any, grid: any }) => { + this._eventHandler.subscribe(this._addon.onColumnsChanged, (e: any, args: { columnId: string; showing: boolean; columns: Column[]; allColumns: Column[]; grid: any; }) => { if (this._columnPicker && typeof this._columnPicker.onColumnsChanged === 'function') { this._columnPicker.onColumnsChanged(e, args); } if (args && Array.isArray(args.columns) && args.columns.length !== this.sharedService.visibleColumns.length) { this.sharedService.visibleColumns = args.columns; } + // if we're using frozen columns, we need to readjust pinning when the new hidden column becomes visible again on the left pinning container + // we need to readjust frozenColumn index because SlickGrid freezes by index and has no knowledge of the columns themselves + const frozenColumnIndex = this.sharedService.gridOptions.frozenColumn !== undefined ? this.sharedService.gridOptions.frozenColumn : -1; + if (frozenColumnIndex >= 0) { + const { showing: isColumnShown, columnId, allColumns, columns: visibleColumns } = args; + this.extensionUtility.readjustFrozenColumnIndexWhenNeeded(columnId, frozenColumnIndex, isColumnShown, allColumns, visibleColumns); + } }); } return this._addon; diff --git a/src/app/modules/angular-slickgrid/extensions/extensionUtility.ts b/src/app/modules/angular-slickgrid/extensions/extensionUtility.ts index 3c0a28eb4..12ce5aa6e 100644 --- a/src/app/modules/angular-slickgrid/extensions/extensionUtility.ts +++ b/src/app/modules/angular-slickgrid/extensions/extensionUtility.ts @@ -2,7 +2,7 @@ import { Injectable, Optional } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { Constants } from '../constants'; -import { ExtensionName } from '../models/index'; +import { Column, ExtensionName } from '../models/index'; import { SharedService } from '../services/shared.service'; import { getTranslationPrefix } from '../services/utilities'; @@ -129,6 +129,42 @@ export class ExtensionUtility { } } + /** + * When using ColumnPicker/GridMenu to show/hide a column, we potentially need to readjust the grid option "frozenColumn" index. + * That is because SlickGrid freezes by column index and it has no knowledge of the columns themselves and won't change the index, we need to do that ourselves whenever necessary. + * Note: we call this method right after the visibleColumns array got updated, it won't work properly if we call it before the setting the visibleColumns. + * @param {String} pickerColumnId - what is the column id triggered by the picker + * @param {Number} frozenColumnIndex - current frozenColumn index + * @param {Boolean} showingColumn - is the column being shown or hidden? + * @param {Array} allColumns - all columns (including hidden ones) + * @param {Array} visibleColumns - only visible columns (excluding hidden ones) + */ + readjustFrozenColumnIndexWhenNeeded(pickerColumnId: string | number, frozenColumnIndex: number, showingColumn: boolean, allColumns: Column[], visibleColumns: Column[]) { + if (frozenColumnIndex >= 0 && pickerColumnId) { + // calculate a possible frozenColumn index variance + let frozenColIndexVariance = 0; + if (showingColumn) { + const definedFrozenColumnIndex = visibleColumns.findIndex(col => col.id === this.sharedService.frozenVisibleColumnId); + const columnIndex = visibleColumns.findIndex(col => col.id === pickerColumnId); + frozenColIndexVariance = (columnIndex >= 0 && (frozenColumnIndex >= columnIndex || definedFrozenColumnIndex === columnIndex)) ? 1 : 0; + } else { + const columnIndex = allColumns.findIndex(col => col.id === pickerColumnId); + frozenColIndexVariance = (columnIndex >= 0 && frozenColumnIndex >= columnIndex) ? -1 : 0; + } + // if we have a variance different than 0 then apply it + const newFrozenColIndex = frozenColumnIndex + frozenColIndexVariance; + if (frozenColIndexVariance !== 0) { + this.sharedService.grid.setOptions({ frozenColumn: newFrozenColIndex }); + } + + // to freeze columns, we need to take only the visible columns and we also need to use setColumns() when some of them are hidden + // to make sure that we only use the visible columns, not doing this would show back some of the hidden columns + if (Array.isArray(visibleColumns) && Array.isArray(allColumns) && visibleColumns.length !== allColumns.length) { + this.sharedService.grid.setColumns(visibleColumns); + } + } + } + /** * Sort items (by pointers) in an array by a property name * @params items array diff --git a/src/app/modules/angular-slickgrid/extensions/gridMenuExtension.ts b/src/app/modules/angular-slickgrid/extensions/gridMenuExtension.ts index 9a8eeddf2..d29bfc723 100644 --- a/src/app/modules/angular-slickgrid/extensions/gridMenuExtension.ts +++ b/src/app/modules/angular-slickgrid/extensions/gridMenuExtension.ts @@ -113,7 +113,7 @@ export class GridMenuExtension implements Extension { this._gridMenuOptions.onAfterMenuShow(e, args); }); } - this._eventHandler.subscribe(this._addon.onColumnsChanged, (e: any, args: { grid: any; allColumns: Column[]; columns: Column[]; }) => { + this._eventHandler.subscribe(this._addon.onColumnsChanged, (e: any, args: { columnId: string; showing: boolean; columns: Column[]; allColumns: Column[]; grid: any; }) => { this._areVisibleColumnDifferent = true; if (this._gridMenuOptions && typeof this._gridMenuOptions.onColumnsChanged === 'function') { this._gridMenuOptions.onColumnsChanged(e, args); @@ -121,6 +121,13 @@ export class GridMenuExtension implements Extension { if (args && Array.isArray(args.columns) && args.columns.length > this.sharedService.visibleColumns.length) { this.sharedService.visibleColumns = args.columns; } + // if we're using frozen columns, we need to readjust pinning when the new hidden column becomes visible again on the left pinning container + // we need to readjust frozenColumn index because SlickGrid freezes by index and has no knowledge of the columns themselves + const frozenColumnIndex = this.sharedService.gridOptions.frozenColumn !== undefined ? this.sharedService.gridOptions.frozenColumn : -1; + if (frozenColumnIndex >= 0) { + const { showing: isColumnShown, columnId, allColumns, columns: visibleColumns } = args; + this.extensionUtility.readjustFrozenColumnIndexWhenNeeded(columnId, frozenColumnIndex, isColumnShown, allColumns, visibleColumns); + } }); this._eventHandler.subscribe(this._addon.onCommand, (e: any, args: MenuCommandItemCallbackArgs) => { this.executeGridMenuInternalCustomCommands(e, args); diff --git a/src/app/modules/angular-slickgrid/extensions/headerMenuExtension.ts b/src/app/modules/angular-slickgrid/extensions/headerMenuExtension.ts index 279fd2421..63568f5cb 100644 --- a/src/app/modules/angular-slickgrid/extensions/headerMenuExtension.ts +++ b/src/app/modules/angular-slickgrid/extensions/headerMenuExtension.ts @@ -221,6 +221,15 @@ export class HeaderMenuExtension implements Extension { if (this.sharedService.grid && this.sharedService.grid.getColumns && this.sharedService.grid.setColumns && this.sharedService.grid.getColumnIndex) { const columnIndex = this.sharedService.grid.getColumnIndex(column.id); const currentColumns = this.sharedService.grid.getColumns() as Column[]; + + // if we're using frozen columns, we need to readjust pinning when the new hidden column is on the left pinning container + // we need to do this because SlickGrid freezes by index and has no knowledge of the columns themselves + const frozenColumnIndex = this.sharedService.gridOptions.frozenColumn || -1; + if (frozenColumnIndex >= 0 && frozenColumnIndex >= columnIndex) { + this.sharedService.grid.setOptions({ frozenColumn: frozenColumnIndex - 1 }); + } + + // then proceed with hiding the column in SlickGrid & trigger an event when done const visibleColumns = arrayRemoveItemByIndex(currentColumns, columnIndex); this.sharedService.visibleColumns = visibleColumns; this.sharedService.grid.setColumns(visibleColumns); diff --git a/src/app/modules/angular-slickgrid/services/__tests__/shared.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/shared.service.spec.ts index 78f80e78c..533d3968a 100644 --- a/src/app/modules/angular-slickgrid/services/__tests__/shared.service.spec.ts +++ b/src/app/modules/angular-slickgrid/services/__tests__/shared.service.spec.ts @@ -197,6 +197,16 @@ describe('Shared Service', () => { expect(output).toEqual(mockColumns); }); + it('should call "frozenVisibleColumnId" GETTER and expect a boolean value to be returned', () => { + const columnId = service.frozenVisibleColumnId; + expect(columnId).toEqual(undefined); + }); + + it('should call "frozenVisibleColumnId" GETTER and SETTER expect same value to be returned', () => { + service.frozenVisibleColumnId = 'field1'; + expect(service.frozenVisibleColumnId).toEqual('field1'); + }); + it('should call "visibleColumns" GETTER and return all columns', () => { const spy = jest.spyOn(service, 'visibleColumns', 'get').mockReturnValue(mockColumns); diff --git a/src/app/modules/angular-slickgrid/services/shared.service.ts b/src/app/modules/angular-slickgrid/services/shared.service.ts index 3925ff912..aac953cde 100644 --- a/src/app/modules/angular-slickgrid/services/shared.service.ts +++ b/src/app/modules/angular-slickgrid/services/shared.service.ts @@ -12,6 +12,7 @@ export class SharedService { private _hideHeaderRowAfterPageLoad = false; private _visibleColumns: Column[]; private _hierarchicalDataset: any[] | null; + private _frozenVisibleColumnId: string | number; onHeaderMenuHideColumns = new Subject(); // -- @@ -50,6 +51,15 @@ export class SharedService { this._dataView = dataView; } + /** Setter to keep the frozen column id for reference if we ever show/hide column from ColumnPicker/GridMenu afterward */ + get frozenVisibleColumnId(): string | number { + return this._frozenVisibleColumnId; + } + /** Getter to keep the frozen column id for reference if we ever show/hide column from ColumnPicker/GridMenu afterward */ + set frozenVisibleColumnId(columnId: string | number) { + this._frozenVisibleColumnId = columnId; + } + /** Getter for SlickGrid Grid object */ get grid(): any { return this._grid; diff --git a/test/cypress/integration/example20.spec.js b/test/cypress/integration/example20.spec.js index 276b0a3d4..61c9b90d2 100644 --- a/test/cypress/integration/example20.spec.js +++ b/test/cypress/integration/example20.spec.js @@ -40,6 +40,130 @@ describe('Example 20 - Frozen Grid', () => { cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); }); + it('should hide "Title" column from Grid Menu and expect last frozen column to be "% Complete"', () => { + const newColumnList = ['#', '% Complete', 'Start', 'Finish', 'Cost | Duration', 'Effort Driven', 'Title 1', 'Title 2', 'Title 3', 'Title 4']; + + cy.get('#grid20') + .find('button.slick-gridmenu-button') + .click({ force: true }); + + cy.get('#grid20') + .get('.slick-gridmenu:visible') + .find('.slick-gridmenu-list') + .children('li:visible:nth(1)') + .children('label') + .should('contain', 'Title') + .click({ force: true }); + + cy.get('#grid20') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(newColumnList[index])); + + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 2 * 2); + cy.get('.grid-canvas-right > [style="top:0px"]').children().should('have.length', 8 * 2); + + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(0)').should('contain', ''); + + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + }); + + it('should show again "Title" column from Grid Menu and expect last frozen column to still be "% Complete"', () => { + cy.get('#grid20') + .get('.slick-gridmenu:visible') + .find('.slick-gridmenu-list') + .children('li:visible:nth(1)') + .children('label') + .should('contain', 'Title') + .click({ force: true }); + + cy.get('#grid20') + .get('.slick-gridmenu:visible') + .find('span.close') + .click({ force: true }); + + cy.get('#grid20') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 3 * 2); + cy.get('.grid-canvas-right > [style="top:0px"]').children().should('have.length', 8 * 2); + + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + }); + + it('should hide "Title" column from Header Menu and expect last frozen column to be "% Complete"', () => { + const newColumnList = ['#', '% Complete', 'Start', 'Finish', 'Cost | Duration', 'Effort Driven', 'Title 1', 'Title 2', 'Title 3', 'Title 4']; + + cy.get('#grid20') + .find('.slick-header-column:nth(1)') + .trigger('mouseover') + .children('.slick-header-menubutton') + .should('be.hidden') + .invoke('show') + .click(); + + cy.get('.slick-header-menu') + .should('be.visible') + .children('.slick-header-menuitem:nth-child(7)') + .children('.slick-header-menucontent') + .should('contain', 'Hide Column') + .click(); + + cy.get('#grid20') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(newColumnList[index])); + + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 2 * 2); + cy.get('.grid-canvas-right > [style="top:0px"]').children().should('have.length', 8 * 2); + + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(0)').should('contain', ''); + + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + }); + + it('should show again "Title" column from Column Picker and expect last frozen column to still be "% Complete"', () => { + cy.get('#grid20') + .find('.slick-header-column:nth(5)') + .trigger('mouseover') + .trigger('contextmenu') + .invoke('show'); + + cy.get('.slick-columnpicker') + .find('.slick-columnpicker-list') + .children('li:nth-child(2)') + .children('label') + .should('contain', 'Title') + .click(); + + cy.get('.slick-columnpicker:visible') + .find('span.close') + .trigger('click') + .click(); + + cy.get('#grid20') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + + cy.get('.grid-canvas-left > [style="top:0px"]').children().should('have.length', 3 * 2); + cy.get('.grid-canvas-right > [style="top:0px"]').children().should('have.length', 8 * 2); + + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(0)').should('contain', ''); + cy.get('.grid-canvas-left > [style="top:0px"] > .slick-cell:nth(1)').should('contain', 'Task 0'); + + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(0)').should('contain', '2009-01-01'); + cy.get('.grid-canvas-right > [style="top:0px"] > .slick-cell:nth(1)').should('contain', '2009-05-05'); + }); + it('should click on the "Remove Frozen Columns" button to switch to a regular grid without frozen columns and expect 7 columns on the left container', () => { cy.get('[data-test=remove-frozen-column-button]') .click({ force: true });