From 4f4b2316c668f23460212de5b7480d7f7f72dce4 Mon Sep 17 00:00:00 2001 From: Ghislain B Date: Tue, 7 Apr 2020 10:17:56 -0400 Subject: [PATCH] feat(extension): add latest slickgrid with RowMove improvements (#428) - now create column definition just by the enableRowMoveManager flag - also keep row selection with dataview syncGridSelection - latest slickgrid version also brought the usabilityOverride callback method that was added in RowMoveManager plugin - possible ref #256 Co-authored-by: Ghislain Beaulac --- src/app/examples/grid-rowmove.component.html | 17 +-- src/app/examples/grid-rowmove.component.ts | 61 +++++--- .../__tests__/rowMoveManagerExtension.spec.ts | 134 ++++++++++++++++-- .../extensions/rowMoveManagerExtension.ts | 50 ++++++- .../models/rowMoveManager.interface.ts | 29 +++- .../__tests__/extension.service.spec.ts | 13 +- .../services/excelExport.service.ts | 1 - .../services/extension.service.ts | 25 ++-- test/cypress/integration/example17.spec.js | 90 ++++++++++++ test/cypress/package.json | 2 +- 10 files changed, 363 insertions(+), 59 deletions(-) create mode 100644 test/cypress/integration/example17.spec.js diff --git a/src/app/examples/grid-rowmove.component.html b/src/app/examples/grid-rowmove.component.html index b00c32587..ed5df7d9e 100644 --- a/src/app/examples/grid-rowmove.component.html +++ b/src/app/examples/grid-rowmove.component.html @@ -1,13 +1,10 @@
-

{{title}}

-
+

{{title}}

+
-
- - -
+
+ + +
diff --git a/src/app/examples/grid-rowmove.component.ts b/src/app/examples/grid-rowmove.component.ts index cf76c0fc7..0cafac2ee 100644 --- a/src/app/examples/grid-rowmove.component.ts +++ b/src/app/examples/grid-rowmove.component.ts @@ -1,16 +1,25 @@ import { Component, OnInit } from '@angular/core'; -import { AngularGridInstance, Column, Formatters, GridOption } from './../modules/angular-slickgrid'; +import { AngularGridInstance, Column, ExtensionName, Formatters, GridOption } from './../modules/angular-slickgrid'; import { TranslateService } from '@ngx-translate/core'; @Component({ templateUrl: './grid-rowmove.component.html' }) export class GridRowMoveComponent implements OnInit { - title = 'Example 17: Row Move Plugin'; + title = 'Example 17: Row Move & Checkbox Selector'; subTitle = `This example demonstrates using the Slick.Plugins.RowMoveManager plugin to easily move a row in the grid.
`; @@ -28,18 +37,12 @@ export class GridRowMoveComponent implements OnInit { this.angularGrid = angularGrid; } + get rowMoveInstance(): any { + return this.angularGrid && this.angularGrid.extensionService.getSlickgridAddonInstance(ExtensionName.rowMoveManager) || {}; + } + ngOnInit(): void { this.columnDefinitions = [ - { - id: '#', field: '', name: '', width: 40, - behavior: 'selectAndMove', - selectable: false, resizable: false, - cssClass: 'cell-reorder dnd', - excludeFromExport: true, - excludeFromColumnPicker: true, - excludeFromHeaderMenu: true, - excludeFromGridMenu: true - }, { id: 'title', name: 'Title', field: 'title' }, { id: 'duration', name: 'Duration', field: 'duration', sortable: true }, { id: '%', name: '% Complete', field: 'percentComplete', sortable: true }, @@ -55,13 +58,31 @@ export class GridRowMoveComponent implements OnInit { sidePadding: 10 }, enableCellNavigation: true, - enableRowMoveManager: true, - gridMenu: { - iconCssClass: 'fa fa-ellipsis-v', + enableCheckboxSelector: true, + enableRowSelection: true, + rowSelectionOptions: { + // True (Single Selection), False (Multiple Selections) + selectActiveRow: false + }, + dataView: { + syncGridSelection: true, // enable this flag so that the row selection follows the row even if we move it to another position }, + enableRowMoveManager: true, rowMoveManager: { + // when using Row Move + Row Selection, you want to enable the following 2 flags so it doesn't cancel row selection + singleRowMove: true, + disableRowSelection: true, + cancelEditOnDrag: true, onBeforeMoveRows: (e, args) => this.onBeforeMoveRow(e, args), onMoveRows: (e, args) => this.onMoveRows(e, args), + + // you can change the move icon position of any extension (RowMove, RowDetail or RowSelector icon) + // note that you might have to play with the position when using multiple extension + // since it really depends on which extension get created first to know what their real position are + // columnIndexPosition: 1, + + // you can also override the usability of the rows, for example make every 2nd row the only moveable rows, + // usabilityOverride: (row, dataContext, grid) => dataContext.id % 2 === 1 }, enableTranslate: true, i18n: this.translate @@ -105,13 +126,10 @@ export class GridRowMoveComponent implements OnInit { const left = this.dataset.slice(0, insertBefore); const right = this.dataset.slice(insertBefore, this.dataset.length); rows.sort((a, b) => a - b); - for (let i = 0; i < rows.length; i++) { extractedRows.push(this.dataset[rows[i]]); } - rows.reverse(); - for (let i = 0; i < rows.length; i++) { const row = rows[i]; if (row < insertBefore) { @@ -120,14 +138,13 @@ export class GridRowMoveComponent implements OnInit { right.splice(row - insertBefore, 1); } } - const updatedDataset = left.concat(extractedRows.concat(right)); + const tmpDataset = left.concat(extractedRows.concat(right)); const selectedRows = []; for (let i = 0; i < rows.length; i++) { selectedRows.push(left.length + i); } + this.angularGrid.slickGrid.resetActiveCell(); - this.angularGrid.slickGrid.setData(updatedDataset); - this.angularGrid.slickGrid.setSelectedRows(selectedRows); - this.angularGrid.slickGrid.render(); + this.dataset = tmpDataset; } } diff --git a/src/app/modules/angular-slickgrid/extensions/__tests__/rowMoveManagerExtension.spec.ts b/src/app/modules/angular-slickgrid/extensions/__tests__/rowMoveManagerExtension.spec.ts index 13f839492..47772b2ca 100644 --- a/src/app/modules/angular-slickgrid/extensions/__tests__/rowMoveManagerExtension.spec.ts +++ b/src/app/modules/angular-slickgrid/extensions/__tests__/rowMoveManagerExtension.spec.ts @@ -1,6 +1,6 @@ import { TestBed } from '@angular/core/testing'; import { TranslateService, TranslateModule } from '@ngx-translate/core'; -import { GridOption } from '../../models/gridOption.interface'; +import { Column, GridOption } from '../../models'; import { RowMoveManagerExtension } from '../rowMoveManagerExtension'; import { ExtensionUtility } from '../extensionUtility'; import { SharedService } from '../../services/shared.service'; @@ -17,6 +17,7 @@ const gridStub = { const mockAddon = jest.fn().mockImplementation(() => ({ init: jest.fn(), destroy: jest.fn(), + getColumnDefinition: jest.fn(), onBeforeMoveRows: new Slick.Event(), onMoveRows: new Slick.Event(), })); @@ -38,6 +39,9 @@ describe('rowMoveManagerExtension', () => { const gridOptionsMock = { enableRowMoveManager: true, rowMoveManager: { + cancelEditOnDrag: true, + singleRowMove: true, + disableRowSelection: true, onExtensionRegistered: jest.fn(), onBeforeMoveRows: (e, args: { insertBefore: number; rows: number[]; }) => { }, onMoveRows: (e, args: { insertBefore: number; rows: number[]; }) => { }, @@ -53,13 +57,29 @@ describe('rowMoveManagerExtension', () => { translate = TestBed.get(TranslateService); }); - it('should return null when either the grid object or the grid options is missing', () => { + it('should return null after calling "create" method when either the column definitions or the grid options is missing', () => { + const output = extension.create([] as Column[], null); + expect(output).toBeNull(); + }); + + it('should return null after calling "loadAddonWhenNotExists" method when either the column definitions or the grid options is missing', () => { + const output = extension.loadAddonWhenNotExists([] as Column[], null); + expect(output).toBeNull(); + }); + + it('should return null after calling "register" method when either the grid object or the grid options is missing', () => { const output = extension.register(); expect(output).toBeNull(); }); - describe('registered addon', () => { + describe('create method', () => { + let columnsMock: Column[]; + beforeEach(() => { + columnsMock = [ + { id: 'field1', field: 'field1', width: 100, cssClass: 'red' }, + { id: 'field2', field: 'field2', width: 50 } + ]; jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); }); @@ -68,16 +88,108 @@ describe('rowMoveManagerExtension', () => { jest.clearAllMocks(); }); + it('should add a reserved column for icons in 1st column index', () => { + const instance = extension.loadAddonWhenNotExists(columnsMock, gridOptionsMock); + const spy = jest.spyOn(instance, 'getColumnDefinition').mockReturnValue({ id: '_move', field: 'move' }); + extension.create(columnsMock, gridOptionsMock); + + expect(spy).toHaveBeenCalled(); + expect(columnsMock).toEqual([ + { + excludeFromColumnPicker: true, + excludeFromExport: true, + excludeFromGridMenu: true, + excludeFromHeaderMenu: true, + excludeFromQuery: true, + field: 'move', + id: '_move' + }, + { id: 'field1', field: 'field1', width: 100, cssClass: 'red' }, + { id: 'field2', field: 'field2', width: 50 }, + ]); + }); + + it('should NOT add the move icon column if it already exist in the column definitions', () => { + columnsMock = [{ + id: '_move', name: '', field: 'move', width: 40, + behavior: 'selectAndMove', selectable: false, resizable: false, cssClass: '', + formatter: (row, cell, value, columnDef, dataContext, grid) => ({ addClasses: 'cell-reorder dnd' }) + }, ...columnsMock] as Column[]; + const instance = extension.loadAddonWhenNotExists(columnsMock, gridOptionsMock); + const spy = jest.spyOn(instance, 'getColumnDefinition').mockReturnValue({ id: '_move', field: 'move' }); + extension.create(columnsMock, gridOptionsMock); + + expect(spy).toHaveBeenCalled(); + expect(columnsMock).toEqual([ + { + behavior: 'selectAndMove', + cssClass: '', + field: 'move', + formatter: expect.anything(), + id: '_move', + name: '', + resizable: false, + selectable: false, + width: 40, + excludeFromColumnPicker: true, + excludeFromExport: true, + excludeFromGridMenu: true, + excludeFromHeaderMenu: true, + excludeFromQuery: true, + }, + { id: 'field1', field: 'field1', width: 100, cssClass: 'red' }, + { id: 'field2', field: 'field2', width: 50 }, + ]); + }); + + it('should expect the column to be at a different column index position when "columnIndexPosition" is defined', () => { + gridOptionsMock.rowMoveManager.columnIndexPosition = 2; + const instance = extension.loadAddonWhenNotExists(columnsMock, gridOptionsMock); + const spy = jest.spyOn(instance, 'getColumnDefinition').mockReturnValue({ id: '_move', field: 'move' }); + extension.create(columnsMock, gridOptionsMock); + + expect(spy).toHaveBeenCalled(); + expect(columnsMock).toEqual([ + { id: 'field1', field: 'field1', width: 100, cssClass: 'red' }, + { id: 'field2', field: 'field2', width: 50 }, + { + excludeFromColumnPicker: true, + excludeFromExport: true, + excludeFromGridMenu: true, + excludeFromHeaderMenu: true, + excludeFromQuery: true, + field: 'move', + id: '_move' + }, + ]); + }); + }); + + describe('registered addon', () => { + let columnsMock: Column[]; + + beforeEach(() => { + columnsMock = [{ id: 'field1', field: 'field1', width: 100, cssClass: 'red' }]; + jest.spyOn(SharedService.prototype, 'grid', 'get').mockReturnValue(gridStub); + jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + jest.clearAllMocks(); + }); it('should register the addon', () => { const onRegisteredSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onExtensionRegistered'); const pluginSpy = jest.spyOn(SharedService.prototype.grid, 'registerPlugin'); - const instance = extension.register(); + const instance = extension.loadAddonWhenNotExists(columnsMock, gridOptionsMock); + extension.create(columnsMock, gridOptionsMock); + extension.register(); const addonInstance = extension.getAddonInstance(); expect(instance).toBeTruthy(); expect(instance).toEqual(addonInstance); expect(mockAddon).toHaveBeenCalledWith({ + cancelEditOnDrag: true, + disableRowSelection: true, + singleRowMove: true, + columnIndexPosition: 2, onExtensionRegistered: expect.anything(), onBeforeMoveRows: expect.anything(), onMoveRows: expect.anything(), @@ -87,7 +199,8 @@ describe('rowMoveManagerExtension', () => { }); it('should dispose of the addon', () => { - const instance = extension.register(); + const instance = extension.create(columnsMock, gridOptionsMock); + extension.register(); const destroySpy = jest.spyOn(instance, 'destroy'); extension.dispose(); @@ -96,13 +209,14 @@ describe('rowMoveManagerExtension', () => { }); it('should provide addon options and expect them to be called in the addon constructor', () => { - const optionMock = { cancelEditOnDrag: true }; + const optionMock = { cancelEditOnDrag: true, singleRowMove: true, disableRowSelection: true }; const addonOptions = { ...gridOptionsMock, rowMoveManager: optionMock }; jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(addonOptions); + const instance = extension.create(columnsMock, gridOptionsMock); extension.register(); - expect(mockAddon).toHaveBeenCalledWith(optionMock); + expect(mockAddon).toHaveBeenCalledWith(gridOptionsMock.rowMoveManager); }); it('should call internal event handler subscribe and expect the "onBeforeMoveRows" option to be called when addon notify is called', () => { @@ -110,7 +224,8 @@ describe('rowMoveManagerExtension', () => { const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onBeforeMoveRows'); const onMoveSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onMoveRows'); - const instance = extension.register(); + const instance = extension.create(columnsMock, gridOptionsMock); + extension.register(); instance.onBeforeMoveRows.notify({ insertBefore: 3, rows: [1] }, new Slick.EventData(), gridStub); expect(handlerSpy).toHaveBeenCalledTimes(2); @@ -127,7 +242,8 @@ describe('rowMoveManagerExtension', () => { const onBeforeSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onBeforeMoveRows'); const onMoveSpy = jest.spyOn(SharedService.prototype.gridOptions.rowMoveManager, 'onMoveRows'); - const instance = extension.register(); + const instance = extension.create(columnsMock, gridOptionsMock); + extension.register(); instance.onMoveRows.notify({ insertBefore: 3, rows: [1] }, new Slick.EventData(), gridStub); expect(handlerSpy).toHaveBeenCalledTimes(2); diff --git a/src/app/modules/angular-slickgrid/extensions/rowMoveManagerExtension.ts b/src/app/modules/angular-slickgrid/extensions/rowMoveManagerExtension.ts index d71b09e12..56b3437f4 100644 --- a/src/app/modules/angular-slickgrid/extensions/rowMoveManagerExtension.ts +++ b/src/app/modules/angular-slickgrid/extensions/rowMoveManagerExtension.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { CellArgs, Extension, ExtensionName, SlickEventHandler } from '../models/index'; +import { CellArgs, Column, Extension, ExtensionName, GridOption, SlickEventHandler } from '../models/index'; import { ExtensionUtility } from './extensionUtility'; import { SharedService } from '../services/shared.service'; @@ -28,6 +28,53 @@ export class RowMoveManagerExtension implements Extension { } } + /** + * Create the plugin before the Grid creation to avoid having odd behaviors. + * Mostly because the column definitions might change after the grid creation, so we want to make sure to add it before then + */ + create(columnDefinitions: Column[], gridOptions: GridOption) { + if (Array.isArray(columnDefinitions) && gridOptions) { + this._addon = this.loadAddonWhenNotExists(columnDefinitions, gridOptions); + const newRowMoveColumn: Column = this._addon.getColumnDefinition(); + const rowMoveColDef = Array.isArray(columnDefinitions) && columnDefinitions.find((col: Column) => col && col.behavior === 'selectAndMove'); + const finalRowMoveColumn = rowMoveColDef ? rowMoveColDef : newRowMoveColumn; + + // set some exclusion properties since we don't want this column to be part of the export neither the list of column in the pickers + if (typeof finalRowMoveColumn === 'object') { + finalRowMoveColumn.excludeFromExport = true; + finalRowMoveColumn.excludeFromColumnPicker = true; + finalRowMoveColumn.excludeFromGridMenu = true; + finalRowMoveColumn.excludeFromQuery = true; + finalRowMoveColumn.excludeFromHeaderMenu = true; + } + + // only add the new column if it doesn't already exist + if (!rowMoveColDef) { + // column index position in the grid + const columnPosition = gridOptions && gridOptions.rowMoveManager && gridOptions.rowMoveManager.columnIndexPosition || 0; + if (columnPosition > 0) { + columnDefinitions.splice(columnPosition, 0, finalRowMoveColumn); + } else { + columnDefinitions.unshift(finalRowMoveColumn); + } + } + return this._addon; + } + return null; + } + + loadAddonWhenNotExists(columnDefinitions: Column[], gridOptions: GridOption): any { + if (Array.isArray(columnDefinitions) && gridOptions) { + // dynamically import the SlickGrid plugin (addon) with RequireJS + this.extensionUtility.loadExtensionDynamically(ExtensionName.rowMoveManager); + if (!this._addon) { + this._addon = new Slick.RowMoveManager(gridOptions && gridOptions.rowMoveManager || { cancelEditOnDrag: true }); + } + return this._addon; + } + return null; + } + /** Get the instance of the SlickGrid addon (control or plugin). */ getAddonInstance() { return this._addon; @@ -45,7 +92,6 @@ export class RowMoveManagerExtension implements Extension { this.sharedService.grid.setSelectionModel(rowSelectionPlugin); } - this._addon = new Slick.RowMoveManager(this.sharedService.gridOptions.rowMoveManager || { cancelEditOnDrag: true }); this.sharedService.grid.registerPlugin(this._addon); // hook all events diff --git a/src/app/modules/angular-slickgrid/models/rowMoveManager.interface.ts b/src/app/modules/angular-slickgrid/models/rowMoveManager.interface.ts index d19fb836d..8be13aded 100644 --- a/src/app/modules/angular-slickgrid/models/rowMoveManager.interface.ts +++ b/src/app/modules/angular-slickgrid/models/rowMoveManager.interface.ts @@ -1,9 +1,34 @@ export interface RowMoveManager { - /** defaults to false, option to cancel edit on drag */ + /** Defaults to false, option to cancel editing while dragging a row */ cancelEditOnDrag?: boolean; + /** Column definition id(defaults to "_move") */ + columnId?: string; + + /** + * Defaults to 0, the column index position in the grid by default it will show as the first column (index 0). + * Also note that the index position might vary if you use other extensions, after each extension is created, + * it will add an offset to take into consideration (1.CheckboxSelector, 2.RowDetail, 3.RowMove) + */ + columnIndexPosition?: number; + + /** A CSS class to be added to the menu item container. */ + cssClass?: string; + + /** Defaults to False, do we want to disable the row selection? */ + disableRowSelection?: boolean; + + /** Defaults to False, do we want a single row move? Setting this to false means that 1 or more rows can be selected to move together. */ + singleRowMove?: boolean; + + /** Width of the column */ + width?: string; + + /** Override the logic for showing (or not) the move icon (use case example: only every 2nd row is moveable) */ + usabilityOverride?: (row: number, dataContext: any, grid: any) => boolean; + // -- - // Events + // SlickGrid Events /** Fired after extension (plugin) is registered by SlickGrid */ onExtensionRegistered?: (plugin: any) => void; diff --git a/src/app/modules/angular-slickgrid/services/__tests__/extension.service.spec.ts b/src/app/modules/angular-slickgrid/services/__tests__/extension.service.spec.ts index f570a1436..c1c4c66de 100644 --- a/src/app/modules/angular-slickgrid/services/__tests__/extension.service.spec.ts +++ b/src/app/modules/angular-slickgrid/services/__tests__/extension.service.spec.ts @@ -307,19 +307,26 @@ describe('ExtensionService', () => { expect(gridSpy).toHaveBeenCalled(); expect(extCreateSpy).toHaveBeenCalledWith(columnsMock, gridOptionsMock); expect(rowSelectionInstance).not.toBeNull(); - expect(extRegisterSpy).toHaveBeenCalled(); expect(output).toEqual({ name: ExtensionName.rowDetailView, addon: instanceMock, instance: instanceMock, class: extensionStub } as ExtensionModel); + expect(extRegisterSpy).toHaveBeenCalled(); + expect(output).toEqual({ name: ExtensionName.rowDetailView, addon: instanceMock, instance: instanceMock, class: extensionStub } as ExtensionModel); }); it('should register the RowMoveManager addon when "enableRowMoveManager" is set in the grid options', () => { + const columnsMock = [{ id: 'field1', field: 'field1', width: 100, cssClass: 'red' }] as Column[]; const gridOptionsMock = { enableRowMoveManager: true } as GridOption; - const extSpy = jest.spyOn(extensionStub, 'register').mockReturnValue(instanceMock); + const extCreateSpy = jest.spyOn(extensionStub, 'create').mockReturnValue(instanceMock); + const extRegisterSpy = jest.spyOn(extensionStub, 'register'); const gridSpy = jest.spyOn(SharedService.prototype, 'gridOptions', 'get').mockReturnValue(gridOptionsMock); + service.createExtensionsBeforeGridCreation(columnsMock, gridOptionsMock); service.bindDifferentExtensions(); + const rowSelectionInstance = service.getExtensionByName(ExtensionName.rowSelection); const output = service.getExtensionByName(ExtensionName.rowMoveManager); expect(gridSpy).toHaveBeenCalled(); - expect(extSpy).toHaveBeenCalled(); + expect(extCreateSpy).toHaveBeenCalledWith(columnsMock, gridOptionsMock); + expect(rowSelectionInstance).not.toBeNull(); + expect(extRegisterSpy).toHaveBeenCalled(); expect(output).toEqual({ name: ExtensionName.rowMoveManager, addon: instanceMock, instance: instanceMock, class: extensionStub } as ExtensionModel); }); diff --git a/src/app/modules/angular-slickgrid/services/excelExport.service.ts b/src/app/modules/angular-slickgrid/services/excelExport.service.ts index d653d3bba..2932c125a 100644 --- a/src/app/modules/angular-slickgrid/services/excelExport.service.ts +++ b/src/app/modules/angular-slickgrid/services/excelExport.service.ts @@ -57,7 +57,6 @@ export class ExcelExportService { /** * Initialize the Export Service * @param grid - * @param gridOptions * @param dataView */ init(grid: any, dataView: any): void { diff --git a/src/app/modules/angular-slickgrid/services/extension.service.ts b/src/app/modules/angular-slickgrid/services/extension.service.ts index 7361dd253..a95f53ef8 100644 --- a/src/app/modules/angular-slickgrid/services/extension.service.ts +++ b/src/app/modules/angular-slickgrid/services/extension.service.ts @@ -147,8 +147,8 @@ export class ExtensionService { } // Row Selection Plugin - // this extension should be registered BEFORE the Checkbox Selector & Row Detail since it can be use by these 2 plugins - if (!this.getExtensionByName(ExtensionName.rowSelection) && (this.sharedService.gridOptions.enableRowSelection || this.sharedService.gridOptions.enableCheckboxSelector || this.sharedService.gridOptions.enableRowDetailView)) { + // this extension should be registered BEFORE the CheckboxSelector, RowDetail or RowMoveManager since it can be use by these 2 plugins + if (!this.getExtensionByName(ExtensionName.rowSelection) && (this.sharedService.gridOptions.enableRowSelection || this.sharedService.gridOptions.enableCheckboxSelector || this.sharedService.gridOptions.enableRowDetailView || this.sharedService.gridOptions.enableRowMoveManager)) { if (this.rowSelectionExtension && this.rowSelectionExtension.register) { const instance = this.rowSelectionExtension.register(); this._extensionList.push({ name: ExtensionName.rowSelection, class: this.rowSelectionExtension, addon: instance, instance }); @@ -235,11 +235,12 @@ export class ExtensionService { } // Row Move Manager Plugin - if (this.sharedService.gridOptions.enableRowMoveManager) { - if (this.rowMoveManagerExtension && this.rowMoveManagerExtension.register) { - const instance = this.rowMoveManagerExtension.register(); - this._extensionList.push({ name: ExtensionName.rowMoveManager, class: this.rowMoveManagerExtension, addon: instance, instance }); - } + if (this.sharedService.gridOptions.enableRowMoveManager && this.rowMoveManagerExtension && this.rowMoveManagerExtension.register) { + const rowSelectionExtension = this.getExtensionByName(ExtensionName.rowSelection); + this.rowMoveManagerExtension.register(rowSelectionExtension); + const createdExtension = this.getCreatedExtensionByName(ExtensionName.rowMoveManager); // get the instance from when it was really created earlier + const instance = createdExtension && createdExtension.instance; + this._extensionList.push({ name: ExtensionName.rowMoveManager, class: this.rowMoveManagerExtension, addon: instance, instance }); } // manually register other plugins @@ -260,8 +261,8 @@ export class ExtensionService { } /** - * Bind/Create certain plugins before the Grid creation, else they might behave oddly. - * Mostly because the column definitions might change after the grid creation + * Bind/Create certain plugins before the Grid creation to avoid having odd behaviors. + * Mostly because the column definitions might change after the grid creation, so we want to make sure to add it before then * @param columnDefinitions * @param options */ @@ -272,6 +273,12 @@ export class ExtensionService { this._extensionCreatedList.push({ name: ExtensionName.checkboxSelector, instance: checkboxInstance }); } } + if (options.enableRowMoveManager) { + if (!this.getCreatedExtensionByName(ExtensionName.rowMoveManager)) { + const rowMoveInstance = this.rowMoveManagerExtension.create(columnDefinitions, options); + this._extensionCreatedList.push({ name: ExtensionName.rowMoveManager, instance: rowMoveInstance }); + } + } if (options.enableRowDetailView) { if (!this.getCreatedExtensionByName(ExtensionName.rowDetailView)) { const rowDetailInstance = this.rowDetailViewExtension.create(columnDefinitions, options); diff --git a/test/cypress/integration/example17.spec.js b/test/cypress/integration/example17.spec.js new file mode 100644 index 000000000..70b73e082 --- /dev/null +++ b/test/cypress/integration/example17.spec.js @@ -0,0 +1,90 @@ +/// + +describe('Example 17 - Row Move & Checkbox Selector Selector Plugins', () => { + const fullTitles = ['', '', 'Title', 'Duration', '% Complete', 'Start', 'Finish', 'Completed']; + + beforeEach(() => { + // create a console.log spy for later use + cy.window().then((win) => { + cy.spy(win.console, "log"); + }); + }); + + it('should display Example title', () => { + cy.visit(`${Cypress.config('baseExampleUrl')}/rowmove`); + cy.get('h2').should('contain', 'Example 17: Row Move & Checkbox Selector'); + }); + + + it('should have exact Column Titles in the grid', () => { + cy.get('#grid17') + .find('.slick-header-columns') + .children() + .each(($child, index) => expect($child.text()).to.eq(fullTitles[index])); + }); + + it('should drag opened Row Detail to another position in the grid', () => { + cy.get('[style="top:35px"] > .slick-cell.cell-reorder').as('moveIconTask1'); + cy.get('[style="top:105px"] > .slick-cell.cell-reorder').as('moveIconTask3'); + + cy.get('@moveIconTask3').should('have.length', 1); + + cy.get('@moveIconTask3') + .trigger('mousedown', { button: 0, force: true }) + .trigger('mousemove', 'bottomRight'); + + cy.get('@moveIconTask1') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { force: true }); + + cy.get('input[type="checkbox"]:checked') + .should('have.length', 0); + }); + + it('should expect row to be moved to another row index', () => { + cy.get('.slick-viewport-top.slick-viewport-left') + .scrollTo('top'); + + cy.get('[style="top:0px"] > .slick-cell:nth(2)').should('contain', 'Task 0'); + cy.get('[style="top:35px"] > .slick-cell:nth(2)').should('contain', 'Task 1'); + cy.get('[style="top:70px"] > .slick-cell:nth(2)').should('contain', 'Task 3'); + cy.get('[style="top:105px"] > .slick-cell:nth(2)').should('contain', 'Task 2'); + cy.get('[style="top:140px"] > .slick-cell:nth(2)').should('contain', 'Task 4'); + + cy.get('input[type="checkbox"]:checked') + .should('have.length', 0); + }); + + it('should select 2 rows (Task 3,4), then move row and expect the 2 rows to still be selected without any others', () => { + cy.get('[style="top:70px"] > .slick-cell:nth(1)').click(); + cy.get('[style="top:140px"] > .slick-cell:nth(1)').click(); + + cy.get('[style="top:70px"] > .slick-cell.cell-reorder').as('moveIconTask3'); + cy.get('[style="top:175px"] > .slick-cell.cell-reorder').as('moveIconTask5'); + + cy.get('@moveIconTask3').should('have.length', 1); + + cy.get('@moveIconTask3') + .trigger('mousedown', { button: 0, force: true }) + .trigger('mousemove', 'bottomRight'); + + cy.get('@moveIconTask5') + .trigger('mousemove', 'bottomRight') + .trigger('mouseup', 'bottomRight', { force: true }); + + cy.get('.slick-viewport-top.slick-viewport-left') + .scrollTo('top'); + + cy.get('[style="top:0px"] > .slick-cell:nth(2)').should('contain', 'Task 0'); + cy.get('[style="top:35px"] > .slick-cell:nth(2)').should('contain', 'Task 1'); + cy.get('[style="top:70px"] > .slick-cell:nth(2)').should('contain', 'Task 2'); + cy.get('[style="top:105px"] > .slick-cell:nth(2)').should('contain', 'Task 4'); + cy.get('[style="top:140px"] > .slick-cell:nth(2)').should('contain', 'Task 5'); + cy.get('[style="top:175px"] > .slick-cell:nth(2)').should('contain', 'Task 3'); + + // Task 4 and 3 should be selected + cy.get('input[type="checkbox"]:checked').should('have.length', 2); + cy.get('[style="top:105px"] > .slick-cell:nth(1) input[type="checkbox"]:checked').should('have.length', 1); + cy.get('[style="top:175px"] > .slick-cell:nth(1) input[type="checkbox"]:checked').should('have.length', 1); + }); +}); diff --git a/test/cypress/package.json b/test/cypress/package.json index baef52649..f50c09066 100644 --- a/test/cypress/package.json +++ b/test/cypress/package.json @@ -11,7 +11,7 @@ "author": "Ghislain B.", "license": "MIT", "devDependencies": { - "cypress": "^4.2.0", + "cypress": "^4.3.0", "mocha": "^5.2.0", "mochawesome": "^3.1.2", "mochawesome-merge": "^1.0.7",