From 382c8311f5df60c65b9b9230a24b33d49f0efa91 Mon Sep 17 00:00:00 2001 From: Ghislain Beaulac Date: Mon, 16 Nov 2020 15:07:10 -0500 Subject: [PATCH] fix(backend): OData/GraphQL pagination should display warning on empty - add a new paginationOptions getter/setter to avoid incomplete gridOptions passed by the OData result callback, merging the gridOptions was incomplete because it was only merging with the local gridOptions instead of the local+global gridOptions - basically we won't be able to see the Empty Dataset Warning with OData unless we modified the code and use the new setter. Keeping the old code will still work but just won't display the warning when dataset is empty --- src/app/examples/grid-odata.component.html | 16 +++++++---- src/app/examples/grid-odata.component.ts | 5 ++-- .../angular-slickgrid-constructor.spec.ts | 24 ++++++++++++++++ .../components/angular-slickgrid.component.ts | 28 ++++++++++++++----- .../services/pagination.service.ts | 12 ++++++-- test/cypress/integration/example05.spec.js | 7 +++++ 6 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/app/examples/grid-odata.component.html b/src/app/examples/grid-odata.component.html index 26f4062df..a9f9454a9 100644 --- a/src/app/examples/grid-odata.component.html +++ b/src/app/examples/grid-odata.component.html @@ -31,16 +31,16 @@

{{title}}

@@ -55,8 +55,12 @@

{{title}}

- + diff --git a/src/app/examples/grid-odata.component.ts b/src/app/examples/grid-odata.component.ts index 669bfb51d..612affc72 100644 --- a/src/app/examples/grid-odata.component.ts +++ b/src/app/examples/grid-odata.component.ts @@ -12,6 +12,7 @@ import { OdataOption, OdataServiceApi, OperatorType, + Pagination, } from './../modules/angular-slickgrid'; const defaultPageSize = 20; @@ -43,6 +44,7 @@ export class GridOdataComponent implements OnInit { gridOptions: GridOption; dataset = []; metrics: Metrics; + paginationOptions: Pagination; isCountEnabled = true; odataVersion = 2; @@ -139,11 +141,10 @@ export class GridOdataComponent implements OnInit { if (this.isCountEnabled) { countPropName = (this.odataVersion === 4) ? '@odata.count' : 'odata.count'; } - this.gridOptions.pagination.totalItems = data[countPropName]; + this.paginationOptions = { ...this.gridOptions.pagination, totalItems: data[countPropName] }; if (this.metrics) { this.metrics.totalItemCount = data[countPropName]; } - this.gridOptions = Object.assign({}, this.gridOptions); // once pagination totalItems is filled, we can update the dataset this.dataset = data['items']; diff --git a/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid-constructor.spec.ts b/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid-constructor.spec.ts index 48259aa7d..91544b5d1 100644 --- a/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid-constructor.spec.ts +++ b/src/app/modules/angular-slickgrid/components/__tests__/angular-slickgrid-constructor.spec.ts @@ -126,6 +126,7 @@ const paginationServiceStub = { totalItems: 0, init: jest.fn(), dispose: jest.fn(), + updateTotalItems: jest.fn(), onPaginationVisibilityChanged: new Subject(), onPaginationChanged: new Subject(), } as unknown as PaginationService; @@ -1249,6 +1250,29 @@ describe('Angular-Slickgrid Custom Component instantiated via Constructor', () = component.destroy(); }); + it('should merge paginationOptions when some already exist', () => { + const mockPagination = { pageSize: 2, pageSizes: [] }; + const paginationSrvSpy = jest.spyOn(paginationServiceStub, 'updateTotalItems'); + + component.ngAfterViewInit(); + component.paginationOptions = mockPagination; + + expect(component.paginationOptions).toEqual({ ...mockPagination, totalItems: 0 }); + expect(paginationSrvSpy).toHaveBeenCalledWith(0, true); + }); + + it('should set brand new paginationOptions when none previously exist', () => { + const mockPagination = { pageSize: 2, pageSizes: [], totalItems: 1 }; + const paginationSrvSpy = jest.spyOn(paginationServiceStub, 'updateTotalItems'); + + component.ngAfterViewInit(); + component.paginationOptions = undefined; + component.paginationOptions = mockPagination; + + expect(component.paginationOptions).toEqual(mockPagination); + expect(paginationSrvSpy).toHaveBeenNthCalledWith(2, 1, true); + }); + it('should call trigger a gridStage change event when pagination change is triggered', () => { const mockPagination = { pageNumber: 2, pageSize: 20 } as Pagination; const spy = jest.spyOn(gridStateServiceStub.onGridStateChanged, 'next'); diff --git a/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts b/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts index 9b70d02eb..a82599f46 100644 --- a/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts +++ b/src/app/modules/angular-slickgrid/components/angular-slickgrid.component.ts @@ -132,6 +132,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn private _isDatasetInitialized = false; private _isPaginationInitialized = false; private _isLocalGrid = true; + private _paginationOptions: Pagination | undefined; private slickEmptyWarning: SlickEmptyWarningComponent; dataView: any | null; grid: any | null; @@ -143,7 +144,6 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn customFooterOptions: CustomFooterOption; locales: Locale; metrics: Metrics; - paginationOptions: Pagination; showCustomFooter = false; showPagination = false; totalItems = 0; @@ -168,6 +168,20 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn @Input() gridId: string; @Input() gridOptions: GridOption; + @Input() + get paginationOptions(): Pagination | undefined { + return this._paginationOptions; + } + set paginationOptions(options: Pagination | undefined) { + if (options && this._paginationOptions) { + this._paginationOptions = { ...this._paginationOptions, ...options }; + } else { + this._paginationOptions = options; + } + this.gridOptions.pagination = options; + this.paginationService.updateTotalItems(options && options.totalItems || 0, true); + } + @Input() set gridHeight(height: number) { this._fixedHeight = height; @@ -435,7 +449,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn 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) { - const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this.gridOptions, this.paginationOptions); + const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this.gridOptions, this._paginationOptions); // 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 const totalRecords = totalCount !== undefined ? totalCount : (this.gridOptions && this.gridOptions.pagination && this.gridOptions.pagination.totalItems); @@ -783,7 +797,7 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn // make sure the dataset is initialized (if not it will throw an error that it cannot getLength of null) this._dataset = this._dataset || []; this.gridOptions = this.mergeGridOptions(this.gridOptions); - this.paginationOptions = this.gridOptions.pagination; + this._paginationOptions = this.gridOptions.pagination; this.locales = this.gridOptions && this.gridOptions.locales || Constants.locales; this.backendServiceApi = this.gridOptions && this.gridOptions.backendServiceApi; this.createBackendApiInternalPostProcessCallback(this.gridOptions); @@ -1007,14 +1021,14 @@ export class AngularSlickgridComponent implements AfterViewInit, OnDestroy, OnIn private loadLocalGridPagination() { if (this.gridOptions) { this.totalItems = Array.isArray(this.dataset) ? this.dataset.length : 0; - if (this.paginationOptions && this.dataView && this.dataView.getPagingInfo) { + if (this._paginationOptions && this.dataView && this.dataView.getPagingInfo) { const slickPagingInfo = this.dataView.getPagingInfo() || {}; - if (slickPagingInfo.hasOwnProperty('totalRows') && this.paginationOptions.totalItems !== slickPagingInfo.totalRows) { + if (slickPagingInfo.hasOwnProperty('totalRows') && this._paginationOptions.totalItems !== slickPagingInfo.totalRows) { this.totalItems = slickPagingInfo.totalRows; } } - this.paginationOptions.totalItems = this.totalItems; - const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this.gridOptions, this.paginationOptions); + this._paginationOptions.totalItems = this.totalItems; + const paginationOptions = this.setPaginationOptionsWhenPresetDefined(this.gridOptions, this._paginationOptions); this.initializePaginationService(paginationOptions); } } diff --git a/src/app/modules/angular-slickgrid/services/pagination.service.ts b/src/app/modules/angular-slickgrid/services/pagination.service.ts index ae55d767d..2a4ac25b3 100644 --- a/src/app/modules/angular-slickgrid/services/pagination.service.ts +++ b/src/app/modules/angular-slickgrid/services/pagination.service.ts @@ -95,9 +95,7 @@ export class PaginationService { if (this._isLocalGrid && this.dataView) { this._eventHandler.subscribe(this.dataView.onPagingInfoChanged, (e, pagingInfo) => { if (this._totalItems !== pagingInfo.totalRows) { - this._totalItems = pagingInfo.totalRows; - this._paginationOptions.totalItems = this._totalItems; - this.refreshPagination(false, false); + this.updateTotalItems(pagingInfo.totalRows); } }); dataView.setRefreshHints({ isFilterUnchanged: true }); @@ -367,6 +365,14 @@ export class PaginationService { // private functions // -------------------- + updateTotalItems(totalItems: number, triggerChangedEvent = false) { + this._totalItems = totalItems; + if (this._paginationOptions) { + this._paginationOptions.totalItems = totalItems; + this.refreshPagination(false, triggerChangedEvent); + } + } + /** * When item is added or removed, we will refresh the numbers on the pagination however we won't trigger a backend change * This will have a side effect though, which is that the "To" count won't be matching the "items per page" count, diff --git a/test/cypress/integration/example05.spec.js b/test/cypress/integration/example05.spec.js index e31b50d10..ca030eb7f 100644 --- a/test/cypress/integration/example05.spec.js +++ b/test/cypress/integration/example05.spec.js @@ -516,6 +516,9 @@ describe('Example 5 - OData Grid', () => { // wait for the query to finish cy.get('[data-test=status]').should('contain', 'done'); + + cy.get('.slick-empty-data-warning:visible') + .contains('No data to display.'); }); it('should display page 0 of 0 but hide pagination from/to numbers when filtered data "xy" returns an empty dataset', () => { @@ -554,6 +557,10 @@ describe('Example 5 - OData Grid', () => { // wait for the query to finish cy.get('[data-test=status]').should('contain', 'done'); + cy.get('.slick-empty-data-warning') + .contains('No data to display.') + .should('not.be.visible'); + cy.window().then((win) => { expect(win.console.log).to.have.callCount(2); expect(win.console.log).to.be.calledWith('Client sample, Grid State changed:: ', { newValues: [{ columnId: 'name', searchTerms: ['x'] }], type: 'filter' });