Skip to content

Commit

Permalink
fix(pagination): should recreate pagination on cursor based changed (#…
Browse files Browse the repository at this point in the history
…1175)

- when changing cursor based by calling  `setCursorBased` we should recreate the pagination component, we can simply add a `onPaginationSetCursorBased` event to subscribe and recreate pagination
  • Loading branch information
ghiscoding authored Nov 3, 2023
1 parent aa1939c commit c7836aa
Show file tree
Hide file tree
Showing 7 changed files with 148 additions and 34 deletions.
17 changes: 11 additions & 6 deletions examples/vite-demo-vanilla-bundle/src/examples/example10.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ <h6 class="title is-6 italic">
onclick.delegate="setSortingDynamically()">
Set Sorting Dynamically
</button>
<button class="button is-small" data-test="reset-presets"
onclick.delegate="resetToOriginalPresets()">
Reset Original Presets
</button>

<br />
<label for="serverdelay" class="ml-4">Server Delay: </label>
<input id="serverdelay" type="number" data-test="server-delay" style="width: 55px"
value.bind="serverWaitDelay"
title="input a fake timer delay to simulate slow server response" />

<div class="row col-md-12">
<span>
Expand All @@ -41,7 +48,7 @@ <h6 class="title is-6 italic">
</button>
</span>

<span style="margin-left: 20px">
<span class="ml-4">
<button class="button is-small" onclick.delegate="switchLanguage()" data-test="language-button">
Switch Language
</button>
Expand All @@ -50,7 +57,7 @@ <h6 class="title is-6 italic">
</span>
</span>

<span style="margin-left: 10px">
<span class="ml-4">
<label>Pagination strategy: </label>
<span data-test="radioStrategy">
<label class="radio-inline control-label" for="offset">
Expand All @@ -67,9 +74,7 @@ <h6 class="title is-6 italic">

</div>

<br />

<div class="columns" style="margin-top: 5px">
<div class="columns mt-2">
<div class="column">
<div class="notification is-info is-light" data-test="alert-graphql-query">
<strong>GraphQL Query:</strong>
Expand Down
62 changes: 51 additions & 11 deletions examples/vite-demo-vanilla-bundle/src/examples/example10.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
OperatorType,
SortDirection,
} from '@slickgrid-universal/common';
import { GraphqlService, GraphqlPaginatedResult, GraphqlServiceApi, } from '@slickgrid-universal/graphql';
import { GraphqlService, GraphqlPaginatedResult, GraphqlServiceApi, GraphqlServiceOption, } from '@slickgrid-universal/graphql';
import { Slicker, SlickVanillaGridBundle } from '@slickgrid-universal/vanilla-bundle';
import moment from 'moment-mini';
import { ExampleGridOptions } from './example-grid-options';
Expand All @@ -22,6 +22,8 @@ import '../material-styles.scss';

const defaultPageSize = 20;
const GRAPHQL_QUERY_DATASET_NAME = 'users';
const FAKE_SERVER_DELAY = 250;
const FAKE_SMALLER_SERVER_DELAY = 50;

export default class Example10 {
private _bindingEventService: BindingEventService;
Expand All @@ -39,6 +41,7 @@ export default class Example10 {
status = '';
statusClass = 'is-success';
translateService: TranslateService;
serverWaitDelay = FAKE_SERVER_DELAY; // server simulation with default of 250ms but 50ms for Cypress tests

constructor() {
this._bindingEventService = new BindingEventService();
Expand Down Expand Up @@ -274,7 +277,7 @@ export default class Example10 {
this.sgb?.paginationService.setCursorPageInfo((mockedResult.data[GRAPHQL_QUERY_DATASET_NAME].pageInfo));
}
resolve(mockedResult);
}, 150);
}, this.serverWaitDelay);
});
}

Expand Down Expand Up @@ -317,18 +320,43 @@ export default class Example10 {
]);
}

resetToOriginalPresets() {
const presetLowestDay = moment().add(-2, 'days').format('YYYY-MM-DD');
const presetHighestDay = moment().add(20, 'days').format('YYYY-MM-DD');

this.sgb?.filterService.updateFilters([
// you can use OperatorType or type them as string, e.g.: operator: 'EQ'
{ columnId: 'gender', searchTerms: ['male'], operator: OperatorType.equal },
{ columnId: 'name', searchTerms: ['John Doe'], operator: OperatorType.contains },
{ columnId: 'company', searchTerms: ['xyz'], operator: 'IN' },

// use a date range with 2 searchTerms values
{ columnId: 'finish', searchTerms: [presetLowestDay, presetHighestDay], operator: OperatorType.rangeInclusive },
]);
this.sgb?.sortService.updateSorting([
// direction can written as 'asc' (uppercase or lowercase) and/or use the SortDirection type
{ columnId: 'name', direction: 'asc' },
{ columnId: 'company', direction: SortDirection.DESC }
]);
setTimeout(() => {
this.sgb?.paginationService?.changeItemPerPage(20);
this.sgb?.paginationService?.goToPageNumber(2);
});
}

setIsWithCursor(newValue: boolean) {
this.isWithCursor = newValue;
this.resetOptions({ isWithCursor: this.isWithCursor });

// recreate grid and initiialisations
const parent = document.querySelector(`.grid10`)?.parentElement;
this.dispose();
if (parent) {
const newGrid10El = document.createElement('div');
newGrid10El.classList.add('grid10');
parent.appendChild(newGrid10El);
this.attached();
}
// // recreate grid and initiialisations
// const parent = document.querySelector(`.grid10`)?.parentElement;
// this.dispose();
// if (parent) {
// const newGrid10El = document.createElement('div');
// newGrid10El.classList.add('grid10');
// parent.appendChild(newGrid10El);
// this.attached();
// }
}

async switchLanguage() {
Expand All @@ -337,4 +365,16 @@ export default class Example10 {
this.selectedLanguage = nextLanguage;
this.selectedLanguageFile = `${this.selectedLanguage}.json`;
}

testWithSmallerWaitTime() {
this.serverWaitDelay = FAKE_SMALLER_SERVER_DELAY;
}

private resetOptions(options: Partial<GraphqlServiceOption>) {
const graphqlService = this.gridOptions.backendServiceApi!.service as GraphqlService;
this.sgb?.paginationService!.setCursorBased(options.isWithCursor!);
this.sgb?.paginationService?.goToFirstPage();
graphqlService.updateOptions(options);
this.gridOptions = { ...this.gridOptions };
}
}
26 changes: 23 additions & 3 deletions packages/common/src/services/__tests__/pagination.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ describe('PaginationService', () => {

it('should execute "process" method when defined as an Observable', (done) => {
const postSpy = jest.fn();
mockGridOption.backendServiceApi.process = postSpy;
mockGridOption.backendServiceApi!.process = postSpy;
const backendExecuteSpy = jest.spyOn(backendUtilityServiceStub, 'executeBackendProcessesCallback');
jest.spyOn(mockBackendService, 'processOnPaginationChanged').mockReturnValue('backend query');
const now = new Date();
Expand Down Expand Up @@ -744,18 +744,38 @@ describe('PaginationService', () => {
});

it('should call "goToPageNumber" when page size is different', () => {
const changeItemSpy = jest.spyOn(service, 'goToPageNumber');
const gotoPageSpy = jest.spyOn(service, 'goToPageNumber');
const refreshSpy = jest.spyOn(service, 'refreshPagination');

service.init(gridStub, mockGridOption.pagination as Pagination, mockGridOption.backendServiceApi);
service.goToPageNumber(100, null, false); // change without triggering event to simulate a change
service.resetToPreviousPagination();

expect(changeItemSpy).toHaveBeenCalled();
expect(gotoPageSpy).toHaveBeenCalled();
expect(refreshSpy).toHaveBeenCalled();
});
});

describe('setCursorBased method', () => {
it('should call the method and expect "onPaginationSetCursorBased" to be triggered', () => {
const setCursorSpy = jest.spyOn(service, 'setCursorPageInfo');
const gotoFirstSpy = jest.spyOn(service, 'goToFirstPage');
const pubSubSpy = jest.spyOn(mockPubSub, 'publish');

service.init(gridStub, mockGridOption.pagination as Pagination, mockGridOption.backendServiceApi);

service.setCursorBased(false);
expect(setCursorSpy).toHaveBeenCalledTimes(0);
expect(gotoFirstSpy).toHaveBeenCalledTimes(1);
expect(pubSubSpy).toHaveBeenCalledWith('onPaginationSetCursorBased', { isCursorBased: false });

service.setCursorBased(true);
expect(setCursorSpy).toHaveBeenCalledTimes(1);
expect(gotoFirstSpy).toHaveBeenCalledTimes(2);
expect(pubSubSpy).toHaveBeenCalledWith('onPaginationSetCursorBased', { isCursorBased: true });
});
});

// processOnItemAddedOrRemoved is private but we can spy on recalculateFromToIndexes
describe('processOnItemAddedOrRemoved private method', () => {
afterEach(() => {
Expand Down
9 changes: 7 additions & 2 deletions packages/common/src/services/pagination.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,8 +495,13 @@ export class PaginationService {
}
}

setCursorBased(isWithCursor: boolean) {
this._isCursorBased = isWithCursor;
setCursorBased(isCursorBased: boolean) {
this._isCursorBased = isCursorBased;
if (isCursorBased) {
this.setCursorPageInfo({ startCursor: '', endCursor: '', hasNextPage: false, hasPreviousPage: false }); // reset cursor
}
this.goToFirstPage();
this.pubSubService.publish(`onPaginationSetCursorBased`, { isCursorBased });
}

setCursorPageInfo(pageInfo: CursorPageInfo) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,6 @@ describe('Slick-Pagination Component', () => {

input.value = `${newPageNumber}`;
input.dispatchEvent(mockEvent);
component.pageNumber = newPageNumber;
expect(spy).toHaveBeenCalledWith(newPageNumber);
}
});
Expand Down Expand Up @@ -229,10 +228,9 @@ describe('Slick-Pagination Component', () => {
expect(spy).toHaveBeenCalledWith(newItemsPerPage);
});

it(`should trigger "onPaginationRefreshed" and expect page from/to being displayed when total items is over 0 and also expect first/prev buttons to be disabled when on page 1`, () => {
test(`when "onPaginationRefreshed" event is triggered then expect page from/to being displayed when total items is over 0 and also expect first/prev buttons to be disabled when on page 1`, () => {
mockFullPagination.pageNumber = 1;
mockFullPagination.totalItems = 100;
component.pageNumber = 1;
eventPubSubService.publish('onPaginationRefreshed', mockFullPagination);
const pageFromToElm = document.querySelector('span.page-info-from-to') as HTMLSpanElement;

Expand All @@ -243,11 +241,10 @@ describe('Slick-Pagination Component', () => {
expect(pageFromToElm.style.display).toBe('');
});

it(`should trigger "onPaginationRefreshed" and expect page from/to being displayed when total items is over 0 and also expect last/next buttons to be disabled when on last page`, () => {
test(`when "onPaginationRefreshed" event is triggered then expect page from/to being displayed when total items is over 0 and also expect last/next buttons to be disabled when on last page`, () => {
mockFullPagination.pageNumber = 10;
mockFullPagination.pageCount = 10;
mockFullPagination.totalItems = 100;
component.pageNumber = 10;
eventPubSubService.publish('onPaginationRefreshed', mockFullPagination);
const pageFromToElm = document.querySelector('span.page-info-from-to') as HTMLSpanElement;

Expand All @@ -258,10 +255,9 @@ describe('Slick-Pagination Component', () => {
expect(pageFromToElm.style.display).toBe('');
});

it(`should trigger "onPaginationRefreshed" and expect page from/to NOT being displayed when total items is 0 and also expect all page buttons to be disabled`, () => {
test(`when "onPaginationRefreshed" event is triggered then expect page from/to NOT being displayed when total items is 0 and also expect all page buttons to be disabled`, () => {
mockFullPagination.pageNumber = 0;
mockFullPagination.totalItems = 0;
component.pageNumber = 0;
eventPubSubService.publish('onPaginationRefreshed', mockFullPagination);
const pageFromToElm = document.querySelector('span.page-info-from-to') as HTMLSpanElement;

Expand All @@ -271,6 +267,28 @@ describe('Slick-Pagination Component', () => {
expect(component.nextButtonClasses).toBe('page-item seek-next disabled');
expect(pageFromToElm.style.display).toBe('none');
});

test(`when "onPaginationSetCursorBased" event is triggered then expect pagination to be recreated`, () => {
const disposeSpy = jest.spyOn(component, 'dispose');
const renderPagSpy = jest.spyOn(component, 'renderPagination');

mockFullPagination.pageNumber = 1;
mockFullPagination.pageCount = 10;
mockFullPagination.totalItems = 100;
paginationServiceStub.isCursorBased = true;
eventPubSubService.publish('onPaginationSetCursorBased', { isCursorBased: true });
const pageFromToElm = document.querySelector('span.page-info-from-to') as HTMLSpanElement;
const pageNbSpan = document.querySelector('span[data-test=page-number-label]') as HTMLSpanElement;

expect(disposeSpy).toHaveBeenCalledTimes(1);
expect(renderPagSpy).toHaveBeenCalledTimes(1);
expect(component.firstButtonClasses).toBe('page-item seek-first disabled');
expect(component.prevButtonClasses).toBe('page-item seek-prev disabled');
expect(component.lastButtonClasses).toBe('page-item seek-end');
expect(component.nextButtonClasses).toBe('page-item seek-next');
expect(pageFromToElm.style.display).toBe('');
expect(pageNbSpan.textContent).toBe('1');
});
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export class SlickPaginationComponent {
protected _bindingHelper: BindingHelper;
protected _paginationElement!: HTMLDivElement;
protected _enableTranslate = false;
protected _gridParentContainerElm?: HTMLElement;
protected _subscriptions: Subscription[] = [];
currentPagination: ServicePagination;
firstButtonClasses = '';
Expand Down Expand Up @@ -63,6 +64,10 @@ export class SlickPaginationComponent {
if (pageFromToElm?.style) {
pageFromToElm.style.display = (this.currentPagination.totalItems === 0) ? 'none' : '';
}
}),
this.pubSubService.subscribe('onPaginationSetCursorBased', () => {
this.dispose(); // recreate pagination component, probably only used for GraphQL E2E tests
this.renderPagination(this._gridParentContainerElm!);
})
);
}
Expand Down Expand Up @@ -93,9 +98,6 @@ export class SlickPaginationComponent {
get pageNumber(): number {
return this.paginationService.pageNumber;
}
set pageNumber(_page: number) {
// the setter has to be declared but we won't use it, instead we will use the "changeToCurrentPage()" to only update the value after ENTER keydown event
}

get grid(): SlickGrid {
return this.sharedService.slickGrid;
Expand Down Expand Up @@ -135,6 +137,7 @@ export class SlickPaginationComponent {
}

renderPagination(gridParentContainerElm: HTMLElement) {
this._gridParentContainerElm = gridParentContainerElm;
const paginationElm = this.createPaginationContainer();
const divNavContainerElm = createDomElement('div', { className: 'slick-pagination-nav' });
const leftNavigationElm = this.createPageNavigation('Page navigation', [
Expand Down
Loading

0 comments on commit c7836aa

Please sign in to comment.