Skip to content

Commit

Permalink
feat(selection): preserve row selection & add it to Grid State & Pres…
Browse files Browse the repository at this point in the history
…ets (#388)

* feat(selection): preserve row selection & add it to Grid State & Presets
- closes #295
- with the "syncGridSelection" enabled in the DataView we can now add the Row Selection to the Grid State & Presets (for that the flags "enableCheckboxSelector" or "enableRowSelection" must be enabled)

* (odata): fix TS typing warning in a Jest unit test
  • Loading branch information
ghiscoding committed Jan 31, 2020
1 parent 6031364 commit a50d489
Show file tree
Hide file tree
Showing 26 changed files with 1,248 additions and 108 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,12 @@
"excel-builder-webpacker": "^1.0.4",
"flatpickr": ">=4.5.0",
"font-awesome": "^4.7.0",
"jquery": ">=3.4.1",
"jquery": "^3.4.1",
"jquery-ui-dist": "^1.12.1",
"lodash.isequal": "^4.5.0",
"moment-mini": "^2.22.1",
"rxjs": "^6.3.3",
"slickgrid": "^2.4.17",
"slickgrid": "^2.4.18",
"text-encoding-utf-8": "^1.0.2",
"tslib": "^1.10.0",
"vinyl-paths": "^2.1.0"
Expand Down
26 changes: 16 additions & 10 deletions src/app/examples/grid-rowselection.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ <h2>{{title}}</h2>
<div class="subtitle" [innerHTML]="subTitle"></div>

<div class="row">
<div class="col-sm-1">
<div class="col-sm-4" style="max-width: 170px;">
Pagination
<button class="btn btn-default btn-xs" data-test="goto-first-page" (click)="goToGrid1FirstPage()">
<i class="fa fa-caret-left fa-lg"></i>
</button>
Expand All @@ -21,21 +22,26 @@ <h2>{{title}}</h2>

<angular-slickgrid gridId="grid1" gridHeight="225" gridWidth="800" [columnDefinitions]="columnDefinitions1"
[gridOptions]="gridOptions1" [dataset]="dataset1" (onAngularGridCreated)="angularGridReady1($event)"
(onGridStateChanged)="grid1StateChanged($event)"
(sgOnSelectedRowsChanged)="handleSelectedRowsChanged1($event.detail.eventData, $event.detail.args)">
</angular-slickgrid>

<hr>

<div class="row">
<div class="col-sm-1">
<button class="btn btn-default btn-xs" data-test="goto-first-page" (click)="goToGrid2FirstPage()">
<i class="fa fa-caret-left fa-lg"></i>
</button>
<button class="btn btn-default btn-xs" data-test="goto-last-page" (click)="goToGrid2LastPage()">
<i class="fa fa-caret-right fa-lg"></i>
</button>
<div class="col-sm-4 col-md-3" style="max-width: 170px;">
Pagination: <input type="checkbox" (change)="togglePaginationGrid2()" [checked]="isGrid2WithPagination"
data-test="toggle-pagination-grid2" />
<span style="margin-left: 5px" *ngIf="isGrid2WithPagination">
<button class="btn btn-default btn-xs" data-test="goto-first-page" (click)="goToGrid2FirstPage()">
<i class="fa fa-caret-left fa-lg"></i>
</button>
<button class="btn btn-default btn-xs" data-test="goto-last-page" (click)="goToGrid2LastPage()">
<i class="fa fa-caret-right fa-lg"></i>
</button>
</span>
</div>
<div class="col-sm-8">
<div class="col-sm-7">
<div class="alert alert-success">
<strong>(multi-select) Selected Row(s):</strong>
<span [innerHTML]="selectedTitles" data-test="grid2-selections"></span>
Expand All @@ -45,6 +51,6 @@ <h2>{{title}}</h2>

<angular-slickgrid gridId="grid2" gridHeight="255" gridWidth="800" [columnDefinitions]="columnDefinitions2"
[gridOptions]="gridOptions2" [dataset]="dataset2" (onAngularGridCreated)="angularGridReady2($event)"
(sgOnSelectedRowsChanged)="handleSelectedRowsChanged2($event.detail.eventData, $event.detail.args)">
(onGridStateChanged)="grid2StateChanged($event)">
</angular-slickgrid>
</div>
65 changes: 52 additions & 13 deletions src/app/examples/grid-rowselection.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Component, Injectable, OnInit } from '@angular/core';
import { ChangeDetectorRef, Component, Injectable, OnInit } from '@angular/core';
import {
AngularGridInstance,
Column,
FieldType,
Filters,
Formatters,
GridOption
GridOption,
GridStateChange
} from './../modules/angular-slickgrid';

@Component({
Expand Down Expand Up @@ -35,8 +36,12 @@ export class GridRowSelectionComponent implements OnInit {
dataset2: any[];
gridObj1: any;
gridObj2: any;
isGrid2WithPagination = true;
selectedTitles: any[];
selectedTitle: any;
selectedGrid2IDs: number[];

constructor(private cd: ChangeDetectorRef) { }

ngOnInit(): void {
this.prepareGrid();
Expand Down Expand Up @@ -133,6 +138,10 @@ export class GridRowSelectionComponent implements OnInit {
pageSizes: [5, 10, 15, 20, 25, 50, 75, 100],
pageSize: 5
},
// we can use some Presets, for the example Pagination
presets: {
pagination: { pageNumber: 2, pageSize: 5 },
},
};

this.gridOptions2 = {
Expand All @@ -148,14 +157,26 @@ export class GridRowSelectionComponent implements OnInit {
// True (Single Selection), False (Multiple Selections)
selectActiveRow: false
},
preselectedRows: [0, 2],
enableCheckboxSelector: true,
enableRowSelection: true,
enablePagination: true,
pagination: {
pageSizes: [5, 10, 15, 20, 25, 50, 75, 100],
pageSize: 5
},
// 1. pre-select some grid row indexes (less recommended, better use the Presets, see below)
// preselectedRows: [0, 2],

// 2. or use the Presets to pre-select some rows
presets: {
// you can presets row selection here as well, you can choose 1 of the following 2 ways of setting the selection
// by their index position in the grid (UI) or by the object IDs, the default is "dataContextIds" and if provided it will use it and disregard "gridRowIndexes"
// the RECOMMENDED is to use "dataContextIds" since that will always work even with Pagination, while "gridRowIndexes" is only good for 1 page
rowSelection: {
// gridRowIndexes: [2], // the row position of what you see on the screen (UI)
dataContextIds: [3, 12, 13, 522] // (recommended) select by the your data object IDs
}
},
};

this.dataset1 = this.prepareData(495);
Expand Down Expand Up @@ -201,19 +222,37 @@ export class GridRowSelectionComponent implements OnInit {
this.angularGrid2.paginationService.goToLastPage();
}

handleSelectedRowsChanged1(e, args) {
if (Array.isArray(args.rows)) {
this.selectedTitle = args.rows.map(idx => {
const item = this.gridObj1.getDataItem(idx);
return item.title || '';
});
/** Dispatched event of a Grid State Changed event */
grid1StateChanged(gridStateChanges: GridStateChange) {
console.log('Grid State changed:: ', gridStateChanges);
console.log('Grid State changed:: ', gridStateChanges.change);
}

/** Dispatched event of a Grid State Changed event */
grid2StateChanged(gridStateChanges: GridStateChange) {
console.log('Grid State changed:: ', gridStateChanges);
console.log('Grid State changed:: ', gridStateChanges.change);

if (gridStateChanges.gridState.rowSelection) {
this.selectedGrid2IDs = (gridStateChanges.gridState.rowSelection.dataContextIds || []) as number[];
this.selectedGrid2IDs = this.selectedGrid2IDs.sort((a, b) => a - b); // sort by ID
this.selectedTitles = this.selectedGrid2IDs.map(dataContextId => `Task ${dataContextId}`);
this.cd.detectChanges();
}
}

handleSelectedRowsChanged2(e, args) {
if (Array.isArray(args.rows)) {
this.selectedTitles = args.rows.map(idx => {
const item = this.gridObj2.getDataItem(idx);
// Toggle the Pagination of Grid2
// IMPORTANT, the Pagination MUST BE CREATED on initial page load before you can start toggling it
// Basically you cannot toggle a Pagination that doesn't exist (must created at the time as the grid)
togglePaginationGrid2() {
this.isGrid2WithPagination = !this.isGrid2WithPagination;
this.angularGrid2.paginationService.togglePaginationVisibility(this.isGrid2WithPagination);
}

handleSelectedRowsChanged1(e, args) {
if (Array.isArray(args.rows) && this.gridObj1) {
this.selectedTitle = args.rows.map(idx => {
const item = this.gridObj1.getDataItem(idx);
return item.title || '';
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ import * as utilities from '../../services/backend-utilities';


const mockExecuteBackendProcess = jest.fn();
const mockRefreshBackendDataset = jest.fn();
// @ts-ignore
utilities.executeBackendProcessesCallback = mockExecuteBackendProcess;
// @ts-ignore
utilities.refreshBackendDataset = mockRefreshBackendDataset;

const mockBackendError = jest.fn();
// @ts-ignore
Expand Down Expand Up @@ -112,13 +115,15 @@ const gridStateServiceStub = {
dispose: jest.fn(),
getAssociatedGridColumns: jest.fn(),
getCurrentGridState: jest.fn(),
needToPreserveRowSelection: jest.fn(),
onGridStateChanged: new Subject<GridStateChange>(),
} as unknown as GridStateService;

const paginationServiceStub = {
totalItems: 0,
init: jest.fn(),
dispose: jest.fn(),
onPaginationVisibilityChanged: new Subject<boolean>(),
onPaginationChanged: new Subject<ServicePagination>(),
} as unknown as PaginationService;

Expand Down Expand Up @@ -163,6 +168,8 @@ const mockDataView = {
getItems: jest.fn(),
getItemMetadata: jest.fn(),
getPagingInfo: jest.fn(),
mapIdsToRows: jest.fn(),
mapRowsToIds: jest.fn(),
onRowsChanged: jest.fn(),
onRowCountChanged: jest.fn(),
onSetItemsCalled: jest.fn(),
Expand Down Expand Up @@ -196,6 +203,7 @@ const mockGrid = {
invalidate: jest.fn(),
getActiveCellNode: jest.fn(),
getColumns: jest.fn(),
getSelectionModel: jest.fn(),
getEditorLock: () => mockGetEditorLock,
getOptions: jest.fn(),
getScrollbarDimensions: jest.fn(),
Expand Down Expand Up @@ -286,7 +294,7 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () =
component.gridId = 'grid1';
component.columnDefinitions = [{ id: 'name', field: 'name' }];
component.dataset = [];
component.gridOptions = { enableExcelExport: false } as GridOption;
component.gridOptions = { enableExcelExport: false, dataView: null } as GridOption;
component.gridHeight = 600;
component.gridWidth = 800;
});
Expand Down Expand Up @@ -486,6 +494,7 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () =
});

it('should call the DataView syncGridSelection method with 2nd argument as True when the "dataView" grid option is a boolean and is set to True', () => {
jest.spyOn(mockGrid, 'getSelectionModel').mockReturnValue(true);
const syncSpy = jest.spyOn(mockDataView, 'syncGridSelection');

component.gridOptions = { dataView: { syncGridSelection: true }, enableRowSelection: true } as GridOption;
Expand All @@ -495,6 +504,7 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () =
});

it('should call the DataView syncGridSelection method with 3 arguments when the "dataView" grid option is provided as an object', () => {
jest.spyOn(mockGrid, 'getSelectionModel').mockReturnValue(true);
const syncSpy = jest.spyOn(mockDataView, 'syncGridSelection');

component.gridOptions = {
Expand Down Expand Up @@ -746,6 +756,7 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () =
});

it('should call the "updateSorters" method when filters are defined in the "presets" property', () => {
jest.spyOn(mockGrid, 'getSelectionModel').mockReturnValue(true);
const spy = jest.spyOn(mockGraphqlService, 'updateSorters');
const mockSorters = [{ columnId: 'name', direction: 'asc' }] as CurrentSorter[];

Expand Down Expand Up @@ -1104,9 +1115,10 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () =
});
});

describe('paginationChanged method', () => {
describe('pagination events', () => {
beforeEach(() => {
jest.clearAllMocks();
component.destroy();
});

it('should call trigger a gridStage change event when pagination change is triggered', () => {
Expand Down Expand Up @@ -1208,7 +1220,7 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () =
rightContainerClass: 'col-xs-6 col-sm-7',
});
done();
}, 1);
});
});

it('should NOT have a Custom Footer when "showCustomFooter" is enabled WITH Pagination in use', (done) => {
Expand All @@ -1224,7 +1236,97 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () =
expect(component.columnDefinitions).toEqual(mockColDefs);
expect(component.showCustomFooter).toBeFalse();
done();
}, 1);
});
});
});

describe('loadRowSelectionPresetWhenExists method', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should call the "mapIdsToRows" from the DataView then "setSelectedRows" from the Grid when there are row selection presets with "dataContextIds" array set', (done) => {
const selectedGridRows = [2];
const selectedRowIds = [99];
const mockData = [{ firstName: 'John', lastName: 'Doe' }, { firstName: 'Jane', lastName: 'Smith' }];
const dataviewSpy = jest.spyOn(mockDataView, 'mapIdsToRows').mockReturnValue(selectedGridRows);
const selectRowSpy = jest.spyOn(mockGrid, 'setSelectedRows');
jest.spyOn(mockGrid, 'getSelectionModel').mockReturnValue(true);

component.gridOptions.enableCheckboxSelector = true;
component.gridOptions.presets = { rowSelection: { dataContextIds: selectedRowIds } };
component.ngAfterViewInit();
component.dataset = mockData;

setTimeout(() => {
expect(dataviewSpy).toHaveBeenCalled();
expect(selectRowSpy).toHaveBeenCalledWith(selectedGridRows);
done();
});
});

it('should call the "setSelectedRows" from the Grid when there are row selection presets with "dataContextIds" array set', (done) => {
const selectedGridRows = [22];
const mockData = [{ firstName: 'John', lastName: 'Doe' }, { firstName: 'Jane', lastName: 'Smith' }];
const selectRowSpy = jest.spyOn(mockGrid, 'setSelectedRows');
jest.spyOn(mockGrid, 'getSelectionModel').mockReturnValue(true);

component.gridOptions.enableRowSelection = true;
component.gridOptions.presets = { rowSelection: { gridRowIndexes: selectedGridRows } };
component.dataset = mockData;
component.ngAfterViewInit();

setTimeout(() => {
expect(selectRowSpy).toHaveBeenCalledWith(selectedGridRows);
done();
});
});

it('should NOT call the "setSelectedRows" when the Grid has Local Pagination and there are row selection presets with "dataContextIds" array set', (done) => {
const selectedGridRows = [22];
const mockData = [{ firstName: 'John', lastName: 'Doe' }, { firstName: 'Jane', lastName: 'Smith' }];
const selectRowSpy = jest.spyOn(mockGrid, 'setSelectedRows');
jest.spyOn(mockGrid, 'getSelectionModel').mockReturnValue(true);

component.gridOptions.enableRowSelection = true;
component.gridOptions.enablePagination = true;
component.gridOptions.backendServiceApi = null;
component.gridOptions.presets = { rowSelection: { dataContextIds: selectedGridRows } };
component.dataset = mockData;
component.ngAfterViewInit();

setTimeout(() => {
expect(selectRowSpy).not.toHaveBeenCalled();
done();
}, 2);
});
});

describe('onPaginationVisibilityChanged event', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should change "showPagination" flag when "onPaginationVisibilityChanged" from the Pagination Service is triggered', (done) => {
component.gridOptions.enablePagination = true;
component.gridOptions.backendServiceApi = null;
component.ngAfterViewInit();
paginationServiceStub.onPaginationVisibilityChanged.next({ visible: false });
setTimeout(() => {
expect(component.showPagination).toBeFalsy();
done();
});
});

it('should call the backend service API to refresh the dataset', (done) => {
component.gridOptions.enablePagination = true;
component.ngAfterViewInit();
paginationServiceStub.onPaginationVisibilityChanged.next({ visible: false });
setTimeout(() => {
expect(mockRefreshBackendDataset).toHaveBeenCalled();
expect(component.showPagination).toBeFalsy();
done();
});
});
});
});
Expand Down

0 comments on commit a50d489

Please sign in to comment.