Skip to content

Commit

Permalink
feat(extension): add latest slickgrid with RowMove improvements
Browse files Browse the repository at this point in the history
- now create column definition just by the enableRowMoveManager flag
- also keep row selection with dataview syncGridSelection
  • Loading branch information
ghiscoding committed Apr 5, 2020
1 parent 0e2ee46 commit c10fffd
Show file tree
Hide file tree
Showing 16 changed files with 402 additions and 97 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ npm run test:watch
- [ ] Resizer
- [ ] Row Detail
- [x] Row Move
- [ ] the Column itself should be created by the extension instead of the user itself
- [ ] column index position (requires SlickGrid [PR #474](https://github.com/6pac/SlickGrid/pull/474))
- [x] the Column itself should be created by the extension instead of the user itself
- [x] column index position (requires SlickGrid [PR #474](https://github.com/6pac/SlickGrid/pull/474))
- [ ] move the methods `onBeforeMoveRow` and `onMoveRows` in the extension
- [x] Row Selection
- [x] Grouping Formatters (12)
- [x] Sorters (5)
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
"packages/*"
],
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^2.25.0",
"@typescript-eslint/parser": "^2.25.0",
"@typescript-eslint/eslint-plugin": "^2.26.0",
"@typescript-eslint/parser": "^2.26.0",
"eslint": "^6.8.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-prefer-arrow": "^1.1.7",
"lerna": "^3.20.2"
},
Expand Down
12 changes: 6 additions & 6 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@
},
"devDependencies": {
"@types/dompurify": "^2.0.1",
"@types/jest": "^25.1.4",
"@types/jest": "^25.2.1",
"@types/jquery": "^3.3.34",
"@types/moment": "^2.13.0",
"@types/node": "^12.12.32",
"@types/node": "^12.12.34",
"autoprefixer": "^9.7.5",
"copyfiles": "^2.2.0",
"cross-env": "^7.0.2",
"jest": "^25.2.4",
"jest-cli": "^25.2.4",
"jest-environment-jsdom": "^25.2.4",
"jest": "^25.2.7",
"jest-cli": "^25.2.7",
"jest-environment-jsdom": "^25.2.6",
"jest-extended": "^0.11.5",
"jest-junit": "^10.0.0",
"jsdom": "^16.2.2",
Expand All @@ -85,7 +85,7 @@
"npm-run-all": "^4.1.5",
"postcss-cli": "^7.1.0",
"rimraf": "^3.0.2",
"ts-jest": "^25.3.0",
"ts-jest": "^25.3.1",
"typescript": "^3.8.3"
},
"engines": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { RowMoveManagerExtension } from '../rowMoveManagerExtension';
import { ExtensionUtility } from '../extensionUtility';
import { SharedService } from '../../services/shared.service';
import { TranslateServiceStub } from '../../../../../test/translateServiceStub';
import { Column } from '../../interfaces/column.interface';

declare const Slick: any;

Expand All @@ -16,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(),
}));
Expand All @@ -39,6 +41,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[]; }) => { },
Expand All @@ -57,8 +62,14 @@ describe('rowMoveManagerExtension', () => {
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);
});
Expand All @@ -67,16 +78,88 @@ describe('rowMoveManagerExtension', () => {
jest.clearAllMocks();
});

it('should create the addon', () => {
extension.create(columnsMock, gridOptionsMock);

expect(mockAddon).toHaveBeenCalledWith({
cancelEditOnDrag: true,
singleRowMove: true,
disableRowSelection: true,
onExtensionRegistered: expect.anything(),
onBeforeMoveRows: expect.anything(),
onMoveRows: expect.anything(),
});
});

it('should add a reserved column for icons in 1st column index', () => {
const instance = extension.create(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 expect the column to be at a different column index position when "columnIndexPosition" is defined', () => {
gridOptionsMock.rowMoveManager.columnIndexPosition = 2;
const instance = extension.create(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.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(),
Expand All @@ -86,7 +169,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();
Expand All @@ -95,21 +179,23 @@ 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', () => {
const handlerSpy = jest.spyOn(extension.eventHandler, 'subscribe');
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);
Expand All @@ -126,7 +212,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);
Expand Down
4 changes: 2 additions & 2 deletions packages/common/src/extensions/checkboxSelectorExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export class CheckboxSelectorExtension implements Extension {
}

/**
* Create the plugin before the Grid creation, else it will behave oddly.
* Mostly because the column definitions might change after the grid creation
* 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) {
Expand Down
35 changes: 33 additions & 2 deletions packages/common/src/extensions/rowMoveManagerExtension.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ExtensionName } from '../enums/index';
import { CellArgs, Extension, SlickEventHandler } from '../interfaces/index';
import { CellArgs, Extension, SlickEventHandler, Column, GridOption } from '../interfaces/index';
import { ExtensionUtility } from './extensionUtility';
import { SharedService } from '../services/shared.service';

Expand Down Expand Up @@ -27,6 +27,38 @@ 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) {
// dynamically import the SlickGrid plugin (addon) with RequireJS
this.extensionUtility.loadExtensionDynamically(ExtensionName.rowMoveManager);
if (!this._addon) {
this._addon = new Slick.RowMoveManager(gridOptions?.rowMoveManager || { cancelEditOnDrag: true });
}
const selectionColumn: Column = this._addon.getColumnDefinition();
if (typeof selectionColumn === 'object') {
selectionColumn.excludeFromExport = true;
selectionColumn.excludeFromColumnPicker = true;
selectionColumn.excludeFromGridMenu = true;
selectionColumn.excludeFromQuery = true;
selectionColumn.excludeFromHeaderMenu = true;

// column index position in the grid
const columnPosition = gridOptions?.rowMoveManager?.columnIndexPosition || 0;
if (columnPosition > 0) {
columnDefinitions.splice(columnPosition, 0, selectionColumn);
} else {
columnDefinitions.unshift(selectionColumn);
}
}
return this._addon;
}
return null;
}

/** Get the instance of the SlickGrid addon (control or plugin). */
getAddonInstance() {
return this._addon;
Expand All @@ -44,7 +76,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
Expand Down
26 changes: 24 additions & 2 deletions packages/common/src/interfaces/rowMoveManager.interface.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
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;

/** A CSS class to be added to the menu item container. */
cssClass?: string;

/** Column definition id(defaults to "_move") */
columnId?: 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;


/** Defaults to 0, the column index position in the grid by default it will show as the first column (index 0) */
columnIndexPosition?: number;

/** 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;
Expand Down
13 changes: 10 additions & 3 deletions packages/common/src/services/__tests__/extension.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,19 +299,26 @@ describe('ExtensionService', () => {
// expect(gridSpy).toHaveBeenCalled();
// expect(extCreateSpy).toHaveBeenCalledWith(columnsMock, gridOptionsMock);
// expect(rowSelectionInstance).not.toBeNull();
// expect(extRegisterSpy).toHaveBeenCalled(); expect(output).toEqual({ name: ExtensionName.rowDetailView, instance: instanceMock, class: extensionStub } as ExtensionModel);
// expect(extRegisterSpy).toHaveBeenCalled();
// expect(output).toEqual({ name: ExtensionName.rowDetailView, 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, instance: instanceMock, class: extensionStub } as ExtensionModel);
});

Expand Down
Loading

0 comments on commit c10fffd

Please sign in to comment.