Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extension): add latest slickgrid with RowMove improvements #321

Merged
merged 3 commits into from
Apr 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { I18N } from 'aurelia-i18n';
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';
Expand All @@ -16,6 +16,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 @@ -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[]; }) => { },
Expand All @@ -50,13 +54,29 @@ describe('rowMoveManagerExtension', () => {
extension = new RowMoveManagerExtension(extensionUtility, sharedService);
});

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);
});
Expand All @@ -65,16 +85,109 @@ 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(),
Expand All @@ -84,7 +197,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 @@ -93,21 +207,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 @@ -124,7 +240,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
1 change: 0 additions & 1 deletion src/aurelia-slickgrid/extensions/rowDetailViewExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,6 @@ export class RowDetailViewExtension implements Extension {
this.sharedService.grid.setSelectionModel(rowSelectionPlugin);
}

// this._extension = this.create(this.sharedService.allColumns, this.sharedService.gridOptions);
this.sharedService.grid.registerPlugin(this._addon);

// hook all events
Expand Down
50 changes: 48 additions & 2 deletions src/aurelia-slickgrid/extensions/rowMoveManagerExtension.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { singleton, inject } from 'aurelia-framework';
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';

Expand Down Expand Up @@ -29,6 +29,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;
Expand All @@ -46,7 +93,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
6 changes: 3 additions & 3 deletions src/aurelia-slickgrid/models/rowDetailView.interface.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
export interface RowDetailView {
/** A CSS class to be added to the row detail */
cssClass?: string;

/** Defaults to true, which will collapse all row detail views when user calls a sort. Unless user implements a sort to deal with padding */
collapseAllOnSort?: boolean;

Expand All @@ -15,6 +12,9 @@ export interface RowDetailView {
*/
columnIndexPosition?: number;

/** A CSS class to be added to the row detail */
cssClass?: string;

/** Extra classes to be added to the expanded Toggle */
expandedClass?: string;

Expand Down
24 changes: 21 additions & 3 deletions src/aurelia-slickgrid/models/rowMoveManager.interface.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
export interface RowMoveManager {
/** 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;

/** defaults to false, option to cancel edit on drag */
cancelEditOnDrag?: boolean;
/** 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?: 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 src/aurelia-slickgrid/services/__tests__/extension.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,19 +313,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);
});

Expand Down
1 change: 0 additions & 1 deletion src/aurelia-slickgrid/services/excelExport.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@ export class ExcelExportService {
/**
* Initialize the Export Service
* @param grid
* @param gridOptions
* @param dataView
*/
init(grid: any, dataView: any): void {
Expand Down
Loading