Skip to content

Commit

Permalink
feat(service): add GridEvent Service to the lib
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding-SE committed Mar 26, 2020
1 parent ba6390a commit 4a4bf6f
Show file tree
Hide file tree
Showing 7 changed files with 365 additions and 7 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ npm run test:watch
- [x] Filter
- [ ] GraphQL (**separate package**)
- [ ] OData (**separate package**)
- [ ] Grid Event
- [x] Grid Event
- [x] Grid Service (helper)
- [ ] Grid State
- [x] Grouping & Col Span
Expand Down
7 changes: 5 additions & 2 deletions packages/common/src/interfaces/column.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,13 @@ export interface Column {
/** Field Name translation key that can be used by the translate Service (i18n) to display the text for each column header title */
nameKey?: string;

/** an event that can be used for triggering an action after a cell change */
/** an event that can be used for executing an action before the cell becomes editable (that event happens before the "onCellChange" event) */
onBeforeEditCell?: (e: KeyboardEvent | MouseEvent, args: OnEventArgs) => void;

/** an event that can be used for executing an action after a cell change */
onCellChange?: (e: KeyboardEvent | MouseEvent, args: OnEventArgs) => void;

/** an event that can be used for triggering an action after a cell click */
/** an event that can be used for executing an action after a cell click */
onCellClick?: (e: KeyboardEvent | MouseEvent, args: OnEventArgs) => void;

/** column output type */
Expand Down
240 changes: 240 additions & 0 deletions packages/common/src/services/__tests__/gridEvent.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
import { GridEventService } from '../gridEvent.service';
import { Column } from '../../interfaces/index';

declare const Slick: any;

const dataViewStub = {
refresh: jest.fn(),
sort: jest.fn(),
reSort: jest.fn(),
};

const gridStub = {
getColumnIndex: jest.fn(),
getColumns: jest.fn(),
getDataItem: jest.fn(),
getOptions: jest.fn(),
setActiveCell: jest.fn(),
setColumns: jest.fn(),
setSortColumns: jest.fn(),
onBeforeEditCell: new Slick.Event(),
onCellChange: new Slick.Event(),
onClick: new Slick.Event(),
};

describe('GridEventService', () => {
let service: GridEventService;

beforeEach(() => {
service = new GridEventService();
});

afterEach(() => {
service.dispose();
jest.clearAllMocks();
});

describe('bindOnBeforeEditCell method', () => {
let mockColumn;
let mockRowData;

beforeEach(() => {
mockColumn = { id: 'firstName', field: 'firstName', onBeforeEditCell: jest.fn() } as Column;
mockRowData = { firstName: 'John', lastName: 'Doe' };
});

it('should not do anything when no arguments are provided to the event', () => {
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);

service.bindOnBeforeEditCell(gridStub, dataViewStub);
gridStub.onBeforeEditCell.notify(undefined, new Slick.EventData(), gridStub);

expect(spyGetCols).not.toHaveBeenCalled();
});

it('should not do anything when "cell" property is missing', () => {
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);

service.bindOnBeforeEditCell(gridStub, dataViewStub);
gridStub.onBeforeEditCell.notify({ cell: undefined, row: undefined }, new Slick.EventData(), gridStub);

expect(spyGetCols).not.toHaveBeenCalled();
});

it('should execute the column "onBeforeEditCell" callback method', () => {
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);
const spyGetData = jest.spyOn(gridStub, 'getDataItem').mockReturnValue(mockRowData);
const spyOnChange = jest.spyOn(mockColumn, 'onBeforeEditCell');

service.bindOnBeforeEditCell(gridStub, dataViewStub);
gridStub.onBeforeEditCell.notify({ cell: 0, row: 0 }, new Slick.EventData(), gridStub);

expect(spyGetCols).toHaveBeenCalled();
expect(spyGetData).toHaveBeenCalled();
expect(spyOnChange).toHaveBeenCalledWith(expect.anything(), {
row: 0,
cell: 0,
dataView: dataViewStub,
grid: gridStub,
columnDef: mockColumn,
dataContext: mockRowData
});
});
});

describe('bindOnCellChange method', () => {
let mockColumn;
let mockRowData;

beforeEach(() => {
mockColumn = { id: 'firstName', field: 'firstName', onCellChange: jest.fn() } as Column;
mockRowData = { firstName: 'John', lastName: 'Doe' };
});

it('should not do anything when no arguments are provided to the event', () => {
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);

service.bindOnCellChange(gridStub, dataViewStub);
gridStub.onCellChange.notify(undefined, new Slick.EventData(), gridStub);

expect(spyGetCols).not.toHaveBeenCalled();
});

it('should not do anything when "cell" property is missing', () => {
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);

service.bindOnCellChange(gridStub, dataViewStub);
gridStub.onCellChange.notify({ cell: undefined, row: undefined }, new Slick.EventData(), gridStub);

expect(spyGetCols).not.toHaveBeenCalled();
});

it('should execute the column "onCellChange" callback method', () => {
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);
const spyGetData = jest.spyOn(gridStub, 'getDataItem').mockReturnValue(mockRowData);
const spyOnChange = jest.spyOn(mockColumn, 'onCellChange');

service.bindOnCellChange(gridStub, dataViewStub);
gridStub.onCellChange.notify({ cell: 0, row: 0 }, new Slick.EventData(), gridStub);

expect(spyGetCols).toHaveBeenCalled();
expect(spyGetData).toHaveBeenCalled();
expect(spyOnChange).toHaveBeenCalledWith(expect.anything(), {
row: 0,
cell: 0,
dataView: dataViewStub,
grid: gridStub,
columnDef: mockColumn,
dataContext: mockRowData
});
});
});

describe('bindOnClick method', () => {
let mockColumn;
let mockRowData;

beforeEach(() => {
mockColumn = { id: 'firstName', field: 'firstName', onCellClick: jest.fn() } as Column;
mockRowData = { firstName: 'John', lastName: 'Doe' };
});

it('should not do anything when no arguments are provided to the event', () => {
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);

service.bindOnClick(gridStub, dataViewStub);
gridStub.onClick.notify(undefined, new Slick.EventData(), gridStub);

expect(spyGetCols).not.toHaveBeenCalled();
});

it('should not do anything when "cell" property is missing', () => {
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);

service.bindOnClick(gridStub, dataViewStub);
gridStub.onClick.notify({ cell: undefined, row: undefined }, new Slick.EventData(), gridStub);

expect(spyGetCols).not.toHaveBeenCalled();
});

it('should execute the column "onCellClick" callback method', () => {
gridStub.getOptions = undefined;
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);
const spyGetData = jest.spyOn(gridStub, 'getDataItem').mockReturnValue(mockRowData);
const spyOnChange = jest.spyOn(mockColumn, 'onCellClick');

service.bindOnClick(gridStub, dataViewStub);
gridStub.onClick.notify({ cell: 0, row: 0 }, new Slick.EventData(), gridStub);

expect(spyGetCols).toHaveBeenCalled();
expect(spyGetData).toHaveBeenCalled();
expect(spyOnChange).toHaveBeenCalledWith(expect.anything(), {
row: 0,
cell: 0,
dataView: dataViewStub,
grid: gridStub,
columnDef: mockColumn,
dataContext: mockRowData
});
});

it('should execute the column "onCellClick" callback method and "setActiveCell" cell navigation is enabled but column is not editable', () => {
gridStub.getOptions = jest.fn();
jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableCellNavigation: true, editable: false });
const spyActive = jest.spyOn(gridStub, 'setActiveCell');
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);
const spyGetData = jest.spyOn(gridStub, 'getDataItem').mockReturnValue(mockRowData);
const spyOnChange = jest.spyOn(mockColumn, 'onCellClick');

service.bindOnClick(gridStub, dataViewStub);
gridStub.onClick.notify({ cell: 0, row: 0 }, new Slick.EventData(), gridStub);

expect(spyActive).toHaveBeenCalled();
expect(spyGetCols).toHaveBeenCalled();
expect(spyGetData).toHaveBeenCalled();
expect(spyOnChange).toHaveBeenCalledWith(expect.anything(), {
row: 0,
cell: 0,
dataView: dataViewStub,
grid: gridStub,
columnDef: mockColumn,
dataContext: mockRowData
});
});

it('should execute the column "onCellClick" callback method and "setActiveCell" when cell is editable and autoCommitEdit', () => {
gridStub.getOptions = jest.fn();
jest.spyOn(gridStub, 'getOptions').mockReturnValue({ enableCellNavigation: true, editable: true, autoCommitEdit: true });
const spyActive = jest.spyOn(gridStub, 'setActiveCell');
const spyGetCols = jest.spyOn(gridStub, 'getColumns').mockReturnValue([mockColumn]);
const spyGetData = jest.spyOn(gridStub, 'getDataItem').mockReturnValue(mockRowData);
const spyOnChange = jest.spyOn(mockColumn, 'onCellClick');

service.bindOnClick(gridStub, dataViewStub);
gridStub.onClick.notify({ cell: 0, row: 0 }, new Slick.EventData(), gridStub);

expect(spyActive).toHaveBeenCalled();
expect(spyGetCols).toHaveBeenCalled();
expect(spyGetData).toHaveBeenCalled();
expect(spyOnChange).toHaveBeenCalledWith(expect.anything(), {
row: 0,
cell: 0,
dataView: dataViewStub,
grid: gridStub,
columnDef: mockColumn,
dataContext: mockRowData
});
});
});

describe('dispose method', () => {
it('should unsubscribe all event from the event handler', () => {
const eventHandler = service.eventHandler;
const spy = jest.spyOn(eventHandler, 'unsubscribeAll');

service.dispose();

expect(spy).toHaveBeenCalled();
});
});
});
2 changes: 1 addition & 1 deletion packages/common/src/services/grid.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ export class GridService {
}

// when user has row selection enabled, we should clear any selection to avoid confusion after a delete
const isSyncGridSelectionEnabled = /*this.gridStateService && this.gridStateService.needToPreserveRowSelection() ||*/ false;
const isSyncGridSelectionEnabled = /* this.gridStateService && this.gridStateService.needToPreserveRowSelection() || */ false;
if (!isSyncGridSelectionEnabled && this._grid && this._gridOptions && (this._gridOptions.enableCheckboxSelector || this._gridOptions.enableRowSelection)) {
this.setSelectedRows([]);
}
Expand Down
107 changes: 107 additions & 0 deletions packages/common/src/services/gridEvent.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { CellArgs, Column, GridOption, OnEventArgs, SlickEventHandler } from './../interfaces/index';

// using external non-typed js libraries
declare const Slick: any;

export class GridEventService {
private _eventHandler: SlickEventHandler;

get eventHandler(): SlickEventHandler {
return this._eventHandler;
}

constructor() {
this._eventHandler = new Slick.EventHandler();
}

/* OnCellChange Event */
bindOnBeforeEditCell(grid: any, dataView: any) {
// subscribe to this Slickgrid event of onBeforeEditCell
this._eventHandler.subscribe(grid.onBeforeEditCell, (e: KeyboardEvent | MouseEvent, args: CellArgs) => {
if (!e || !args || !grid || args.cell === undefined || !grid.getColumns || !grid.getDataItem) {
return;
}
const column: Column = grid.getColumns()[args.cell];

// if the column definition has a onBeforeEditCell property (a callback function), then run it
if (typeof column.onBeforeEditCell === 'function') {
// add to the output gridOptions & dataView since we'll need them inside the AJAX column.onBeforeEditCell
const returnedArgs: OnEventArgs = {
row: args.row,
cell: args.cell,
dataView,
grid,
columnDef: column,
dataContext: grid.getDataItem(args.row)
};

// finally call up the Slick.column.onBeforeEditCells.... function
column.onBeforeEditCell(e, returnedArgs);
}
});
}

/* OnCellChange Event */
bindOnCellChange(grid: any, dataView: any) {
// subscribe to this Slickgrid event of onCellChange
this._eventHandler.subscribe(grid.onCellChange, (e: KeyboardEvent | MouseEvent, args: CellArgs) => {
if (!e || !args || !grid || args.cell === undefined || !grid.getColumns || !grid.getDataItem) {
return;
}
const column: Column = grid.getColumns()[args.cell];

// if the column definition has a onCellChange property (a callback function), then run it
if (typeof column.onCellChange === 'function') {
// add to the output gridOptions & dataView since we'll need them inside the AJAX column.onCellChange
const returnedArgs: OnEventArgs = {
row: args.row,
cell: args.cell,
dataView,
grid,
columnDef: column,
dataContext: grid.getDataItem(args.row)
};

// finally call up the Slick.column.onCellChanges.... function
column.onCellChange(e, returnedArgs);
}
});
}

/* OnClick Event */
bindOnClick(grid: any, dataView: any) {
this._eventHandler.subscribe(grid.onClick, (e: KeyboardEvent | MouseEvent, args: CellArgs) => {
if (!e || !args || !grid || args.cell === undefined || !grid.getColumns || !grid.getDataItem) {
return;
}
const column: Column = grid && grid.getColumns && grid.getColumns()[args.cell];
const gridOptions: GridOption = grid && grid.getOptions && grid.getOptions() || {};

// only when using autoCommitEdit, we will make the cell active (in focus) when clicked
// setting the cell as active as a side effect and if autoCommitEdit is set to false then the Editors won't save correctly
if (gridOptions.enableCellNavigation && (!gridOptions.editable || (gridOptions.editable && gridOptions.autoCommitEdit))) {
grid.setActiveCell(args.row, args.cell);
}

// if the column definition has a onCellClick property (a callback function), then run it
if (typeof column.onCellClick === 'function') {
// add to the output gridOptions & dataView since we'll need them inside the AJAX column.onClick
const returnedArgs: OnEventArgs = {
row: args.row,
cell: args.cell,
dataView,
grid,
columnDef: column,
dataContext: grid.getDataItem(args.row)
};

// finally call up the Slick.column.onCellClick.... function
column.onCellClick(e, returnedArgs);
}
});
}

dispose() {
this._eventHandler.unsubscribeAll();
}
}
4 changes: 2 additions & 2 deletions packages/common/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ export * from './export-utilities';
export * from './extension.service';
export * from './filter.service';
export * from './grid.service';
export * from './gridEvent.service';
export * from './groupingAndColspan.service';
export * from './pubSub.service';
export * from './shared.service';
export * from './sort.service';
export * from './translater.service';
export * from './utilities';
export * from '../filters/filterFactory';
export * from './translater.service';
Loading

0 comments on commit 4a4bf6f

Please sign in to comment.