Skip to content

Commit

Permalink
feat(pagination): add Pagination to local grid
Browse files Browse the repository at this point in the history
  • Loading branch information
ghiscoding-SE committed Jan 17, 2020
1 parent e78b2d9 commit 53aa9dc
Show file tree
Hide file tree
Showing 13 changed files with 217 additions and 81 deletions.
31 changes: 11 additions & 20 deletions src/app/examples/grid-basic.component.html
Original file line number Diff line number Diff line change
@@ -1,25 +1,16 @@
<div class="container-fluid">
<h2>{{title}}</h2>
<div class="subtitle" [innerHTML]="subTitle"></div>
<h2>{{title}}</h2>
<div class="subtitle" [innerHTML]="subTitle"></div>

<h3>Grid 1</h3>
<angular-slickgrid gridId="grid1"
[columnDefinitions]="columnDefinitions1"
[gridOptions]="gridOptions1"
[dataset]="dataset1"
gridHeight="300"
gridWidth="800">
</angular-slickgrid>
<h3>Grid 1</h3>
<angular-slickgrid gridId="grid1" [columnDefinitions]="columnDefinitions1" [gridOptions]="gridOptions1"
[dataset]="dataset1" gridHeight="225" gridWidth="800">
</angular-slickgrid>

<hr/>
<hr />

<h3>Grid 2</h3>
<angular-slickgrid gridId="grid2"
[columnDefinitions]="columnDefinitions2"
[gridOptions]="gridOptions2"
[dataset]="dataset2"
gridHeight="300"
gridWidth="800">
</angular-slickgrid>
<h3>Grid 2 <small>(with local Pagination)</small></h3>
<angular-slickgrid gridId="grid2" [columnDefinitions]="columnDefinitions2" [gridOptions]="gridOptions2"
[dataset]="dataset2" gridHeight="225" gridWidth="800">
</angular-slickgrid>
</div>

22 changes: 17 additions & 5 deletions src/app/examples/grid-basic.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Component, OnInit } from '@angular/core';
import { Column, GridOption } from './../modules/angular-slickgrid';

const NB_ITEMS = 600;

@Component({
templateUrl: './grid-basic.component.html'
})
Expand Down Expand Up @@ -35,18 +37,28 @@ export class GridBasicComponent implements OnInit {
};

// copy the same Grid Options and Column Definitions to 2nd grid
// but also add Pagination in this grid
this.columnDefinitions2 = this.columnDefinitions1;
this.gridOptions2 = this.gridOptions1;
this.gridOptions2 = {
...this.gridOptions1,
...{
enablePagination: true,
pagination: {
pageSizes: [5, 10, 15, 20, 25, 50, 75, 100],
pageSize: 5
},
}
};

// mock some data (different in each dataset)
this.dataset1 = this.mockData();
this.dataset2 = this.mockData();
this.dataset1 = this.mockData(NB_ITEMS);
this.dataset2 = this.mockData(NB_ITEMS);
}

mockData() {
mockData(count: number) {
// mock a dataset
const mockDataset = [];
for (let i = 0; i < 1000; i++) {
for (let i = 0; i < count; i++) {
const randomYear = 2000 + Math.floor(Math.random() * 10);
const randomMonth = Math.floor(Math.random() * 11);
const randomDay = Math.floor((Math.random() * 29));
Expand Down
18 changes: 12 additions & 6 deletions src/app/examples/grid-state.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,20 +150,25 @@ export class GridStateComponent implements OnInit {
gridMenu: {
hideForceFitButton: true
},
enablePagination: true,
pagination: {
pageSizes: [5, 10, 15, 20, 25, 30, 40, 50, 75, 100],
pageSize: 25
},
};

// reload the Grid State with the grid options presets
if (gridStatePresets) {
this.gridOptions.presets = gridStatePresets;
}

this.getData();
this.dataset = this.getData(NB_ITEMS);
}

getData() {
getData(count: number) {
// mock a dataset
this.dataset = [];
for (let i = 0; i < NB_ITEMS; i++) {
const tmpData = [];
for (let i = 0; i < count; i++) {
const randomDuration = Math.round(Math.random() * 100);
const randomYear = randomBetween(2000, 2025);
const randomYearShort = randomBetween(10, 25);
Expand All @@ -174,7 +179,7 @@ export class GridStateComponent implements OnInit {
const randomHour = randomBetween(10, 23);
const randomTime = randomBetween(10, 59);

this.dataset[i] = {
tmpData[i] = {
id: i,
title: 'Task ' + i,
etc: 'Something hidden ' + i,
Expand All @@ -188,6 +193,7 @@ export class GridStateComponent implements OnInit {
completed: (i % 3 === 0)
};
}
return tmpData;
}

/** Dispatched event of a Grid State Changed event */
Expand Down Expand Up @@ -231,6 +237,6 @@ export class GridStateComponent implements OnInit {
{ columnId: 'duration', direction: 'DESC' },
{ columnId: 'complete', direction: 'ASC' }
],
};
} as GridState;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import {
ExportService,
ExtensionService,
FilterService,
GraphqlPaginatedResult,
GraphqlService,
GridService,
GridEventService,
Expand All @@ -21,7 +20,7 @@ import {
SharedService,
SortService,
} from '../../services';
import { Column, CurrentFilter, CurrentSorter, GridOption, GridState, GridStateChange, GridStateType, Pagination, GraphqlServiceApi, GraphqlServiceOption } from '../../models';
import { Column, CurrentFilter, CurrentSorter, GraphqlPaginatedResult, GraphqlServiceApi, GraphqlServiceOption, GridOption, GridState, GridStateChange, GridStateType, Pagination } from '../../models';
import { Filters } from '../../filters';
import { Editors } from '../../editors';
import * as utilities from '../../services/backend-utilities';
Expand Down Expand Up @@ -579,6 +578,28 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () =

expect(spy).toHaveBeenCalledWith();
});

it('should refresh a local grid and change pagination options pagination when a preset for it is defined in grid options', (done) => {
const expectedPageNumber = 3;
const refreshSpy = jest.spyOn(component, 'refreshGridData');

const mockData = [{ firstName: 'John', lastName: 'Doe' }, { firstName: 'Jane', lastName: 'Smith' }];
component.gridOptions = {
enablePagination: true,
presets: { pagination: { pageSize: 10, pageNumber: expectedPageNumber } }
};
component.paginationOptions = { pageSize: 10, pageNumber: 2, pageSizes: [10, 25, 50], totalItems: 100 };

component.ngAfterViewInit();
component.dataset = mockData;

setTimeout(() => {
expect(component.paginationOptions.pageSize).toBe(10);
expect(component.paginationOptions.pageNumber).toBe(expectedPageNumber);
expect(refreshSpy).toHaveBeenCalledWith(mockData);
done();
});
});
});

describe('Backend Service API', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,7 +302,10 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
if (this.gridOptions.enableRowSelection || this.gridOptions.enableCheckboxSelector) {
this.gridService.setSelectedRows([]);
}

if (this.sharedService) {
const { pageNumber, pageSize } = pagination;
this.sharedService.currentPagination = { pageNumber, pageSize };
}
this.gridStateService.onGridStateChanged.next({
change: { newValues: pagination, type: GridStateType.pagination },
gridState: this.gridStateService.getCurrentGridState()
Expand All @@ -314,6 +317,15 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
* @param dataset
*/
refreshGridData(dataset: any[], totalCount?: number) {
// local grid
if (this.gridOptions && this.gridOptions.enablePagination && !this.gridOptions.backendServiceApi) {
this.totalItems = dataset.length;
setTimeout(() => {
this.showPagination = true;
this.setPaginationOptionsWhenPresetDefined();
});
}

if (Array.isArray(dataset) && this.grid && this.dataView && typeof this.dataView.setItems === 'function') {
this.dataView.setItems(dataset, this.gridOptions.datasetIdPropertyName);
if (!this.gridOptions.backendServiceApi) {
Expand All @@ -326,13 +338,10 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
}

// display the Pagination component only after calling this refresh data first, we call it here so that if we preset pagination page number it will be shown correctly
this.showPagination = ((this.gridOptions && this.gridOptions.enablePagination && (this.gridOptions.backendServiceApi && this.gridOptions.enablePagination === undefined)) ? true : this.gridOptions.enablePagination) || false;
this.showPagination = (this.gridOptions && (this.gridOptions.enablePagination || (this.gridOptions.backendServiceApi && this.gridOptions.enablePagination === undefined))) ? true : false;

if (this.gridOptions && this.gridOptions.backendServiceApi && this.gridOptions.pagination) {
if (this.gridOptions.presets && this.gridOptions.presets.pagination && this.gridOptions.pagination) {
this.paginationOptions.pageSize = this.gridOptions.presets.pagination.pageSize;
this.paginationOptions.pageNumber = this.gridOptions.presets.pagination.pageNumber;
}
this.setPaginationOptionsWhenPresetDefined();

// when we have a totalCount use it, else we'll take it from the pagination object
// only update the total items if it's different to avoid refreshing the UI
Expand All @@ -353,6 +362,13 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
}
}

setPaginationOptionsWhenPresetDefined() {
if (this.gridOptions.presets && this.gridOptions.presets.pagination && this.gridOptions.pagination) {
this.paginationOptions.pageSize = this.gridOptions.presets.pagination.pageSize;
this.paginationOptions.pageNumber = this.gridOptions.presets.pagination.pageNumber;
}
}

/**
* Dynamically change or update the column definitions list.
* We will re-render the grid so that the new header and data shows up correctly.
Expand Down Expand Up @@ -775,7 +791,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
gridOptions.enablePagination = ((gridOptions.backendServiceApi && gridOptions.enablePagination === undefined) ? true : gridOptions.enablePagination) || false;

// use jquery extend to deep merge & copy to avoid immutable properties being changed in GlobalGridOptions after a route change
const options = $.extend(true, {}, GlobalGridOptions, this.forRootConfig, gridOptions);
const options = $.extend(true, {}, GlobalGridOptions, this.forRootConfig, gridOptions) as GridOption;

// using jQuery extend to do a deep clone has an unwanted side on objects and pageSizes but ES6 spread has other worst side effects
// so we will just overwrite the pageSizes when needed, this is the only one causing issues so far.
Expand All @@ -791,6 +807,13 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn
if (options.enableFiltering && !options.showHeaderRow) {
options.showHeaderRow = options.enableFiltering;
}

// when we use Pagination on Local Grid, it doesn't seem to work without enableFiltering
// so we'll enable the filtering but we'll keep the header row hidden
if (!options.enableFiltering && options.enablePagination && !options.backendServiceApi) {
options.enableFiltering = true;
options.showHeaderRow = false;
}
return options;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class SlickPaginationComponent implements AfterViewInit, OnDestroy {
throw new Error('[Angular-Slickgrid] requires "ngx-translate" to be installed and configured when the grid option "enableTranslate" is enabled.');
}
// Angular throws the infamous "ExpressionChangedAfterItHasBeenCheckedError"
// none of the code refactoring worked to go over the error expect adding a delay, so we'll keep that for now
// none of the code refactoring worked to go over the error except adding a delay, so we'll keep that for now
setTimeout(() => this.paginationService.init(this.grid, this.dataView, this.options, this.backendServiceApi));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,21 @@ describe('GridStateService', () => {
expect(output).toBeNull();
});

it('should call "getCurrentPagination" and return Pagination when using a Local Grid', () => {
const gridOptionsMock = { enablePagination: true } as GridOption;
const paginationMock = { pageNumber: 2, pageSize: 50 } as CurrentPagination;
const gridSpy = jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock);
const sharedSpy = jest.spyOn(SharedService.prototype, 'currentPagination', 'get').mockReturnValue(paginationMock);

const output = service.getCurrentPagination();

expect(gridSpy).toHaveBeenCalled();
expect(sharedSpy).toHaveBeenCalled();
expect(output).toBe(paginationMock);
});

it('should call "getCurrentPagination" and return Pagination when a BackendService is used', () => {
const gridOptionsMock = { backendServiceApi: { service: backendServiceStub } } as GridOption;
const gridOptionsMock = { backendServiceApi: { service: backendServiceStub }, enablePagination: true } as GridOption;
const paginationMock = { pageNumber: 2, pageSize: 50 } as CurrentPagination;
const gridSpy = jest.spyOn(gridStub, 'getOptions').mockReturnValue(gridOptionsMock);
const backendSpy = jest.spyOn(backendServiceStub, 'getCurrentPagination').mockReturnValue(paginationMock);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Subject, of, throwError } from 'rxjs';

import { PaginationService } from './../pagination.service';
import { SharedService } from '../shared.service';
import { FilterService, GridService } from '../index';
import { Column, GridOption, CurrentFilter } from '../../models';
import * as utilities from '../backend-utilities';
Expand Down Expand Up @@ -69,9 +70,11 @@ const gridServiceStub = {

describe('PaginationService', () => {
let service: PaginationService;
let sharedService: SharedService;

beforeEach(() => {
service = new PaginationService(filterServiceStub, gridServiceStub);
sharedService = new SharedService();
service = new PaginationService(filterServiceStub, gridServiceStub, sharedService);
});

afterEach(() => {
Expand Down Expand Up @@ -452,9 +455,10 @@ describe('PaginationService', () => {
};
});

it('should throw an error when no backendServiceApi is provided', (done) => {
it('should throw an error when backendServiceApi is defined without a "process" method', (done) => {
try {
mockGridOption.backendServiceApi = null;
// @ts-ignore
mockGridOption.backendServiceApi = {};
service.init(gridStub, dataviewStub, mockGridOption.pagination, mockGridOption.backendServiceApi);
service.refreshPagination();
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SharedService } from '..';
import { Column, GridOption } from '../../models';
import { Column, CurrentPagination, GridOption } from '../../models';

jest.mock('flatpickr', () => { });

Expand Down Expand Up @@ -66,6 +66,29 @@ describe('Shared Service', () => {
expect(columns).toEqual(mockColumns);
});

it('should call "currentPagination" GETTER and return the currentPagination object', () => {
const expectedResult = { pageNumber: 2, pageSize: 10 } as CurrentPagination;
const spy = jest.spyOn(service, 'currentPagination', 'get').mockReturnValue(expectedResult);

const output = service.currentPagination;

expect(spy).toHaveBeenCalled();
expect(output).toEqual(expectedResult);
});

it('should call "currentPagination" SETTER and expect GETTER to return the same', () => {
const expectedResult = { pageNumber: 2, pageSize: 10 } as CurrentPagination;
const getSpy = jest.spyOn(service, 'currentPagination', 'get');
const setSpy = jest.spyOn(service, 'currentPagination', 'set');

service.currentPagination = expectedResult;
const output = service.currentPagination;

expect(getSpy).toHaveBeenCalled();
expect(setSpy).toHaveBeenCalled();
expect(output).toEqual(expectedResult);
});

it('should call "dataView" GETTER and return a dataView', () => {
const spy = jest.spyOn(service, 'dataView', 'get').mockReturnValue(dataviewStub);

Expand Down
14 changes: 8 additions & 6 deletions src/app/modules/angular-slickgrid/services/gridState.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,13 +177,15 @@ export class GridStateService {
* @return current pagination state
*/
getCurrentPagination(): CurrentPagination | null {
if (this._gridOptions && this._gridOptions.backendServiceApi) {
const backendService = this._gridOptions.backendServiceApi.service;
if (backendService && backendService.getCurrentPagination) {
return backendService.getCurrentPagination();
if (this._gridOptions.enablePagination) {
if (this._gridOptions && this._gridOptions.backendServiceApi) {
const backendService = this._gridOptions.backendServiceApi.service;
if (backendService && backendService.getCurrentPagination) {
return backendService.getCurrentPagination();
}
} else {
return this.sharedService.currentPagination;
}
} else {
// TODO implement this whenever local pagination gets implemented
}
return null;
}
Expand Down
Loading

0 comments on commit 53aa9dc

Please sign in to comment.