From 459dcb3186a72d4e3f5d51108b7723698a13fffa Mon Sep 17 00:00:00 2001 From: Jeremy Smartt Date: Thu, 4 May 2017 13:11:08 -0700 Subject: [PATCH] feat(paging): ability to jump to page `n` with page links (closes #496) (#544) * feat: Paging: Jump to page n Data table paging - Capability to jump to page n. A new parameter 'pageLinkCount' on the Paging-Bar Component that is a number and will create the amount of page links that can be clicked on to jump to a certain page. Closes #496 * Fixing cases where there are an even number of pageLinks or the pageLinkCount is 2 or less * Change to have a guid on the paging bar to be able to handle multiple paginators on the same page * Making inputs on unit tests dynamic. First testing initial value, then changing value, and then testing for the new value. * Special case handling for when there are two pageLinks and going all the way to send and back * handling case where pagelinksCount is above max possible pages based on per page setting * handling reponsive pagelinks and updating total of datatable updates pagelinks --- .../components/paging/paging.component.html | 6 +- .../components/paging/paging.component.ts | 4 + .../core/paging/paging-bar.component.html | 7 +- .../core/paging/paging-bar.component.spec.ts | 222 ++++++++++++++++++ .../core/paging/paging-bar.component.ts | 100 +++++++- 5 files changed, 333 insertions(+), 6 deletions(-) create mode 100644 src/platform/core/paging/paging-bar.component.spec.ts diff --git a/src/app/components/components/paging/paging.component.html b/src/app/components/components/paging/paging.component.html index ba0761b0bd..d2a2c2722e 100644 --- a/src/app/components/components/paging/paging.component.html +++ b/src/app/components/components/paging/paging.component.html @@ -15,7 +15,7 @@

Total: {{event?.total || '1345'}}

- Row per page: {{pagingBar.range}} of {{pagingBar.total}} @@ -27,7 +27,7 @@

HTML:

Row per page: { {pagingBar.range} } of { {pagingBar.total} } @@ -101,7 +101,7 @@

Example:

+ pageLinkCount="5" [initialPage]="1" [pageSize]="100" [total]="1345" (change)="change($event)"> Row per page: { {pagingBar.range} } of { {pagingBar.total} }
diff --git a/src/app/components/components/paging/paging.component.ts b/src/app/components/components/paging/paging.component.ts index 5d36929feb..47caf600ec 100644 --- a/src/app/components/components/paging/paging.component.ts +++ b/src/app/components/components/paging/paging.component.ts @@ -35,6 +35,10 @@ export class PagingDemoComponent { description: `Selected page size for the pagination. Defaults to first element of the [pageSizes] array.`, name: 'pageSize?', type: 'number', + }, { + description: `Defines the number of PageLinks to display. PageLinks are used to jump to a specific page, default is 0.`, + name: 'pageLinkCount?', + type: 'number', }, { description: `ets starting page for the paging bar. Defaults to '1'`, name: 'initialPage?', diff --git a/src/platform/core/paging/paging-bar.component.html b/src/platform/core/paging/paging-bar.component.html index 680f365aef..f1e59a98a8 100644 --- a/src/platform/core/paging/paging-bar.component.html +++ b/src/platform/core/paging/paging-bar.component.html @@ -12,16 +12,19 @@
- + + + -
diff --git a/src/platform/core/paging/paging-bar.component.spec.ts b/src/platform/core/paging/paging-bar.component.spec.ts new file mode 100644 index 0000000000..c8233cc832 --- /dev/null +++ b/src/platform/core/paging/paging-bar.component.spec.ts @@ -0,0 +1,222 @@ +import { + TestBed, + inject, + async, + ComponentFixture, +} from '@angular/core/testing'; +import 'hammerjs'; +import { Component } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { TdPagingBarComponent } from './paging-bar.component'; +import { CovalentPagingModule } from './paging.module'; +import { NgModule, DebugElement } from '@angular/core'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; + +describe('Component: TdPagingBarComponent', () => { + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserAnimationsModule, + CovalentPagingModule, + ], + declarations: [ + TestpageSizeAllTextComponent, + TestInitialPageComponent, + TestPageSizesComponent, + TestFirstLastComponent, + TestPageLinkCountComponent, + ], + }); + TestBed.compileComponents(); + })); + + it('should create the component', (done: DoneFn) => { + let fixture: ComponentFixture = TestBed.createComponent(TestpageSizeAllTextComponent); + let component: TestpageSizeAllTextComponent = fixture.debugElement.componentInstance; + + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(component).toBeTruthy(); + done(); + }); + }); + + it('should set pageSizeAllText, pageSizeAll and see it in markup', (done: DoneFn) => { + let fixture: ComponentFixture = TestBed.createComponent(TestpageSizeAllTextComponent); + let component: TestpageSizeAllTextComponent = fixture.debugElement.componentInstance; + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + let pageSizeAllText: string = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.pageSizeAllText; + expect(pageSizeAllText).toBe('SomeOtherText'); + + component.pageSizeAllText = 'aDifferentText'; + fixture.detectChanges(); + fixture.whenStable().then(() => { + pageSizeAllText = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.pageSizeAllText; + expect(pageSizeAllText).toBe('aDifferentText'); + + let pageSizeAll: boolean = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.pageSizeAll; + expect(pageSizeAll).toBe(true); + + component.pageSizeAll = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + pageSizeAll = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.pageSizeAll; + expect(pageSizeAll).toBe(false); + done(); + }); + }); + }); + }); + }); + + it('should set pageSizes and then component instantiate with that pageSize', (done: DoneFn) => { + let fixture: ComponentFixture = TestBed.createComponent(TestPageSizesComponent); + let component: TestPageSizesComponent = fixture.debugElement.componentInstance; + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + let pageSize: number = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.pageSize; + expect(pageSize).toBe(37); + + component.pageSizes = [55, 77]; + fixture.detectChanges(); + fixture.whenStable().then(() => { + pageSize = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.pageSize; + expect(pageSize).toBe(55); + done(); + }); + }); + }); + }); + + it('should set intialPage and then component instantiate with that page', (done: DoneFn) => { + let fixture: ComponentFixture = TestBed.createComponent(TestInitialPageComponent); + let component: TestInitialPageComponent = fixture.debugElement.componentInstance; + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + let page: number = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.page; + expect(page).toBe(3); + done(); + }); + }); + }); + + it('should set firstLast and then see buttons in markup', (done: DoneFn) => { + let fixture: ComponentFixture = TestBed.createComponent(TestFirstLastComponent); + let component: TestFirstLastComponent = fixture.debugElement.componentInstance; + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + let id: string = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.id; + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-first-page'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-last-page'))).toBeTruthy(); + + component.firstLast = false; + fixture.detectChanges(); + fixture.whenStable().then(() => { + id = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.id; + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-first-page'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-last-page'))).toBeFalsy(); + done(); + }); + }); + }); + }); + + it('should set pageLinkCount and then see buttons in markup', (done: DoneFn) => { + let fixture: ComponentFixture = TestBed.createComponent(TestPageLinkCountComponent); + let component: TestPageLinkCountComponent = fixture.debugElement.componentInstance; + + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + let id: string = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.id; + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-0'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-1'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-2'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-3'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-4'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-5'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-6'))).toBeTruthy(); + + component.pageLinkCount = 4; + component.pageSize = 50; + fixture.detectChanges(); + fixture.whenStable().then(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + id = fixture.debugElement.query(By.directive(TdPagingBarComponent)).componentInstance.id; + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-0'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-1'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-2'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-3'))).toBeTruthy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-4'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-5'))).toBeFalsy(); + expect(fixture.debugElement.query(By.css('#td-paging-bar-' + id + '-page-link-6'))).toBeFalsy(); + done(); + }); + }); + }); + }); + }); +}); + +@Component({ + template: ` + `, +}) +class TestpageSizeAllTextComponent { + pageSizeAllText: string = 'SomeOtherText'; + pageSizeAll: boolean = true; +} + +@Component({ + template: ` + `, +}) +class TestPageSizesComponent { + pageSizes: number[] = [37, 48]; +} + +@Component({ + template: ` + `, +}) +class TestInitialPageComponent { + initialPage: number = 3; +} + +@Component({ + template: ` + `, +}) +class TestFirstLastComponent { + firstLast: boolean = true; +} + +@Component({ + template: ` + `, +}) +class TestPageLinkCountComponent { + pageLinkCount: number = 7; + pageSize: number = 100; +} diff --git a/src/platform/core/paging/paging-bar.component.ts b/src/platform/core/paging/paging-bar.component.ts index 66218344ff..ea38e41cc1 100644 --- a/src/platform/core/paging/paging-bar.component.ts +++ b/src/platform/core/paging/paging-bar.component.ts @@ -24,6 +24,13 @@ export class TdPagingBarComponent implements OnInit { private _fromRow: number = 1; private _toRow: number = 1; private _initialized: boolean = false; + private _pageLinks: number[] = []; + private _pageLinkCount: number = 0; + private _id: string; + // special case when 2 pageLinks, detect when hit end of pages so can lead in correct direction + private _hitEnd: boolean = false; + // special case when 2 pageLinks, detect when hit start of pages so can lead in correct direction + private _hitStart: boolean = false; /** * pageSizeAll?: boolean @@ -49,6 +56,19 @@ export class TdPagingBarComponent implements OnInit { */ @Input('initialPage') initialPage: number = 1; + /** + * pageLinkCount?: number + * Amount of page jump to links for the paging bar. Defaults to '0' + */ + @Input('pageLinkCount') + set pageLinkCount(pageLinkCount: number) { + this._pageLinkCount = pageLinkCount; + this._calculatePageLinks(); + } + get pageLinkCount(): number { + return this._pageLinkCount; + } + /** * pageSizes?: number[] * Array that populates page size menu. Defaults to [50, 100, 200, 500, 1000] @@ -91,11 +111,20 @@ export class TdPagingBarComponent implements OnInit { set total(total: number) { this._total = total; this._calculateRows(); + this._calculatePageLinks(); } get total(): number { return this._total; } + /** + * pageLinks: number[] + * Returns the pageLinks in an array + */ + get pageLinks(): number[] { + return this._pageLinks; + } + /** * range: string * Returns the range of the rows. @@ -120,6 +149,14 @@ export class TdPagingBarComponent implements OnInit { return Math.ceil(this._total / this._pageSize); } + /** + * id: string + * Returns the guid id for this paginator + */ + get id(): string { + return this._id; + } + /** * change?: function * Method to be executed when page size changes or any button is clicked in the paging bar. @@ -134,11 +171,14 @@ export class TdPagingBarComponent implements OnInit { return false; } - constructor(@Optional() private _dir: Dir) {} + constructor(@Optional() private _dir: Dir) { + this._id = this.guid(); + } ngOnInit(): void { this._page = this.initialPage; this._calculateRows(); + this._calculatePageLinks(); this._initialized = true; } @@ -201,8 +241,54 @@ export class TdPagingBarComponent implements OnInit { this._toRow = this._total > top ? top : this._total; } + /** + * _calculatePageLinks?: function + * Calculates the page links that should be shown to the user based on the current state of the paginator + */ + private _calculatePageLinks(): void { + // special case when 2 pageLinks, detect when hit end of pages so can lead in correct direction + if (this.isMaxPage()) { + this._hitEnd = true; + this._hitStart = false; + } + // special case when 2 pageLinks, detect when hit start of pages so can lead in correct direction + if (this.isMinPage()) { + this._hitEnd = false; + this._hitStart = true; + } + // If the pageLinkCount goes above max possible pages based on perpage setting then reset it to maxPage + let actualPageLinkCount: number = this.pageLinkCount; + if (this.pageLinkCount > this.maxPage) { + actualPageLinkCount = this.maxPage; + } + // reset the pageLinks array + this._pageLinks = []; + // fill in the array with the pageLinks based on the current selected page + let middlePageLinks: number = Math.floor(actualPageLinkCount / 2); + for (let x: number = 0; x < actualPageLinkCount; x++) { + // don't go past the maxPage in the pageLinks + // have to handle even and odd pageLinkCounts differently so can still lead to the next numbers + if ((actualPageLinkCount % 2 === 0 && (this.page + middlePageLinks > this.maxPage)) || + (actualPageLinkCount % 2 !== 0 && (this.page + middlePageLinks >= this.maxPage))) { + this._pageLinks[x] = this.maxPage - (actualPageLinkCount - (x + 1)); + // if the selected page is after the middle then set that page as middle and get the correct balance on left and right + // special handling when there are only 2 pageLinks to just drop to next if block so can lead to next numbers when moving to right + // when moving to the left then go into this block + } else if ((actualPageLinkCount > 2 || actualPageLinkCount <= 2 && this._hitEnd) && (this.page - middlePageLinks) > 0) { + this._pageLinks[x] = (this.page - middlePageLinks) + x; + // if the selected page is before the middle then set the pages based on the x index leading up to and after selected page + } else if ((this.page - middlePageLinks) <= 0) { + this._pageLinks[x] = x + 1; + // other wise just set the array in order starting from the selected page + } else { + this._pageLinks[x] = this.page + x; + } + } + } + private _handleOnChange(): void { this._calculateRows(); + this._calculatePageLinks(); let event: IPageChangeEvent = { page: this._page, maxPage: this.maxPage, @@ -214,4 +300,16 @@ export class TdPagingBarComponent implements OnInit { this.onChange.emit(event); } + /** + * guid?: function + * Returns RFC4122 random ("version 4") GUIDs + */ + private guid(): string { + return this.s4() + this.s4() + '-' + this.s4() + '-' + this.s4() + '-' + this.s4() + '-' + this.s4() + this.s4() + this.s4(); + } + + private s4(): string { + return Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1); + } + }