From 93267b625a66c5a4a9c7d10b30106455e11ad1d3 Mon Sep 17 00:00:00 2001 From: Peter Lauck Date: Wed, 25 Oct 2023 22:56:38 +0000 Subject: [PATCH] feat(design,design-land): add link support to paginator --- .../app/paginator/paginator.component.html | 6 + .../design/paginator/examples/src/examples.ts | 2 + .../link-paginator.component.html | 1 + .../link-paginator.component.ts | 32 +++++ .../link-paginator/link-paginator.module.ts | 19 +++ .../paginator/examples/src/public_api.ts | 1 + .../paginator/paginator.component.html | 106 +++++++++++++---- .../paginator/paginator.component.spec.ts | 110 +++++++++++++++++- .../paginator/paginator.component.ts | 91 +++++++++------ .../molecules/paginator/paginator.module.ts | 2 + 10 files changed, 311 insertions(+), 59 deletions(-) create mode 100644 libs/design/paginator/examples/src/link-paginator/link-paginator.component.html create mode 100644 libs/design/paginator/examples/src/link-paginator/link-paginator.component.ts create mode 100644 libs/design/paginator/examples/src/link-paginator/link-paginator.module.ts diff --git a/apps/design-land/src/app/paginator/paginator.component.html b/apps/design-land/src/app/paginator/paginator.component.html index 423d181b75..e8b7233143 100644 --- a/apps/design-land/src/app/paginator/paginator.component.html +++ b/apps/design-land/src/app/paginator/paginator.component.html @@ -11,3 +11,9 @@

Usage

+ +

Link Mode

+

The paginator can be used in link mode which replaces the buttons with anchors. In link mode, pass the URL of the current collection page and optionally the name of the query param that will set the current page.

+ + + diff --git a/libs/design/paginator/examples/src/examples.ts b/libs/design/paginator/examples/src/examples.ts index f99a2f278d..d15468e223 100644 --- a/libs/design/paginator/examples/src/examples.ts +++ b/libs/design/paginator/examples/src/examples.ts @@ -1,5 +1,7 @@ import { BasicPaginatorComponent } from './basic-paginator/basic-paginator.component'; +import { LinkPaginatorComponent } from './link-paginator/link-paginator.component'; export const PAGINATOR_EXAMPLES = [ BasicPaginatorComponent, + LinkPaginatorComponent, ]; diff --git a/libs/design/paginator/examples/src/link-paginator/link-paginator.component.html b/libs/design/paginator/examples/src/link-paginator/link-paginator.component.html new file mode 100644 index 0000000000..73a32d757f --- /dev/null +++ b/libs/design/paginator/examples/src/link-paginator/link-paginator.component.html @@ -0,0 +1 @@ + diff --git a/libs/design/paginator/examples/src/link-paginator/link-paginator.component.ts b/libs/design/paginator/examples/src/link-paginator/link-paginator.component.ts new file mode 100644 index 0000000000..cc79afc56b --- /dev/null +++ b/libs/design/paginator/examples/src/link-paginator/link-paginator.component.ts @@ -0,0 +1,32 @@ +import { + ChangeDetectionStrategy, + Component, +} from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { + Observable, + map, +} from 'rxjs'; + +@Component({ + // eslint-disable-next-line @angular-eslint/component-selector + selector: 'link-paginator', + templateUrl: './link-paginator.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class LinkPaginatorComponent { + numberOfPages = 15; + // TODO: don't hardcode this, pass it in design land + url = '/paginator'; + queryParam = 'currentPage'; + + get currentPage$(): Observable { + return this.route.queryParamMap.pipe( + map((qps) => Number(qps.get(this.queryParam))), + ); + } + + constructor( + private route: ActivatedRoute, + ) {} +} diff --git a/libs/design/paginator/examples/src/link-paginator/link-paginator.module.ts b/libs/design/paginator/examples/src/link-paginator/link-paginator.module.ts new file mode 100644 index 0000000000..891d83bc7e --- /dev/null +++ b/libs/design/paginator/examples/src/link-paginator/link-paginator.module.ts @@ -0,0 +1,19 @@ +import { NgModule } from '@angular/core'; + +import { DaffPaginatorModule } from '@daffodil/design'; + +import { LinkPaginatorComponent } from './link-paginator.component'; + +@NgModule({ + declarations: [ + LinkPaginatorComponent, + ], + exports: [ + LinkPaginatorComponent, + ], + imports: [ + DaffPaginatorModule, + ], + providers: [], +}) +export class LinkPaginatorModule { } diff --git a/libs/design/paginator/examples/src/public_api.ts b/libs/design/paginator/examples/src/public_api.ts index 35d76c6232..a9ebd9634b 100644 --- a/libs/design/paginator/examples/src/public_api.ts +++ b/libs/design/paginator/examples/src/public_api.ts @@ -1,4 +1,5 @@ export { BasicPaginatorComponent } from './basic-paginator/basic-paginator.component'; +export { LinkPaginatorComponent } from './link-paginator/link-paginator.component'; export { PaginatorExamplesModule } from './paginator-examples.module'; export { PAGINATOR_EXAMPLES } from './examples'; diff --git a/libs/design/src/molecules/paginator/paginator.component.html b/libs/design/src/molecules/paginator/paginator.component.html index db1d10b790..1e6ea67a03 100644 --- a/libs/design/src/molecules/paginator/paginator.component.html +++ b/libs/design/src/molecules/paginator/paginator.component.html @@ -1,48 +1,106 @@ - + + + Previous + + + Previous + + - +1 -... +... - + + + {{ pageNumber }} + -... +... - + + + {{ numberOfPages }} + - + + + Next + + + Next + + diff --git a/libs/design/src/molecules/paginator/paginator.component.spec.ts b/libs/design/src/molecules/paginator/paginator.component.spec.ts index e7cfddb87f..aa5b4c2e20 100644 --- a/libs/design/src/molecules/paginator/paginator.component.spec.ts +++ b/libs/design/src/molecules/paginator/paginator.component.spec.ts @@ -1,3 +1,4 @@ +import { Location } from '@angular/common'; import { Component, DebugElement, @@ -6,8 +7,12 @@ import { waitForAsync, ComponentFixture, TestBed, + fakeAsync, + tick, } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; +import { ActivatedRoute } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; import { DaffPaginatorNumberOfPagesErrorMessage, @@ -16,11 +21,27 @@ import { import { DaffPaginatorComponent } from './paginator.component'; import { DaffPaginatorModule } from './paginator.module'; -@Component({ template: '' }) + +@Component({ template: '' }) +class TestComponent {} + +@Component({ template: ` + +` }) class WrapperComponent { numberOfPagesValue = 20; currentPageValue = 2; + linkModeValue = false; + urlValue = ''; + queryParamValue = ''; } describe('DaffPaginatorComponent', () => { @@ -28,11 +49,24 @@ describe('DaffPaginatorComponent', () => { let fixture: ComponentFixture; let de: DebugElement; let component: DaffPaginatorComponent; + let route: ActivatedRoute; + let location: Location; + const testUrl = 'test'; beforeEach(waitForAsync(() => { TestBed.configureTestingModule({ imports: [ DaffPaginatorModule, + RouterTestingModule.withRoutes([ + { + path: '', + component: TestComponent, + }, + { + path: testUrl, + component: TestComponent, + }, + ]), ], declarations: [ WrapperComponent, @@ -42,6 +76,9 @@ describe('DaffPaginatorComponent', () => { })); beforeEach(() => { + route = TestBed.inject(ActivatedRoute); + location = TestBed.inject(Location); + fixture = TestBed.createComponent(WrapperComponent); wrapper = fixture.componentInstance; de = fixture.debugElement.query(By.css('daff-paginator')); @@ -64,6 +101,18 @@ describe('DaffPaginatorComponent', () => { expect(component.currentPage).toEqual(wrapper.currentPageValue); }); + it('should be able to take linkMode as input', () => { + expect(component.linkMode).toEqual(wrapper.linkModeValue); + }); + + it('should be able to take url as input', () => { + expect(component.url).toEqual(wrapper.urlValue); + }); + + it('should be able to take queryParam as input', () => { + expect(component.queryParam).toEqual(wrapper.queryParamValue); + }); + it('should be able to take numberOfPages as input', () => { expect(component.numberOfPages).toEqual(wrapper.numberOfPagesValue); }); @@ -81,6 +130,65 @@ describe('DaffPaginatorComponent', () => { expect(paginatorText.includes(greaterPage)).toBeTruthy(); }); + describe('when the component is in link mode', () => { + beforeEach(() => { + wrapper.linkModeValue = true; + wrapper.urlValue = testUrl; + wrapper.queryParamValue = 'queryParam'; + fixture.detectChanges(); + }); + + it('should render the buttons as anchors', () => { + expect(fixture.debugElement.queryAll(By.css('button')).length).toEqual(0); + expect(fixture.debugElement.queryAll(By.css('a')).length).toBeGreaterThan(0); + }); + + describe('when previous link is clicked', () => { + beforeEach(fakeAsync(() => { + fixture.debugElement.query(By.css('.daff-paginator__previous')).nativeElement.click(); + tick(); + })); + + it('should set the query param value', () => { + expect(Number(route.snapshot.queryParamMap.get(wrapper.queryParamValue))).toEqual(wrapper.currentPageValue - 1); + }); + + it('should navigate to the set url', () => { + expect(location.path()).toContain(wrapper.urlValue); + }); + }); + + describe('when next link is clicked', () => { + beforeEach(fakeAsync(() => { + fixture.debugElement.query(By.css('.daff-paginator__next')).nativeElement.click(); + tick(); + })); + + it('should set the query param value', () => { + expect(Number(route.snapshot.queryParamMap.get(wrapper.queryParamValue))).toEqual(wrapper.currentPageValue + 1); + }); + + it('should navigate to the set url', () => { + expect(location.path()).toContain(wrapper.urlValue); + }); + }); + + describe('when a page number link is clicked', () => { + beforeEach(fakeAsync(() => { + fixture.debugElement.query(By.css('.daff-paginator__page-link[data-page-number="3"]')).nativeElement.click(); + tick(); + })); + + it('should set the query param value', () => { + expect(Number(route.snapshot.queryParamMap.get(wrapper.queryParamValue))).toEqual(3); + }); + + it('should navigate to the set url', () => { + expect(location.path()).toContain(wrapper.urlValue); + }); + }); + }); + describe('when the numberOfPages is less than 2', () => { it('should only render one .daff-paginator__page-link', () => { diff --git a/libs/design/src/molecules/paginator/paginator.component.ts b/libs/design/src/molecules/paginator/paginator.component.ts index 00b769fb2a..cbd96519ef 100644 --- a/libs/design/src/molecules/paginator/paginator.component.ts +++ b/libs/design/src/molecules/paginator/paginator.component.ts @@ -9,6 +9,7 @@ import { ChangeDetectionStrategy, Renderer2, } from '@angular/core'; +import { Params } from '@angular/router'; import { faChevronRight, faChevronLeft, @@ -91,6 +92,22 @@ export class DaffPaginatorComponent extends _daffPaginatorBase implements OnChan */ @Input() currentPage: number; + /** + * Replace the paginator buttons with links. `url` is required if using this mode. + */ + @Input() linkMode = false; + + /** + * The url to which to navigate if the paginator is in link mode. + * This paginator component will set the page query param. + */ + @Input() url?: string; + + /** + * The query param to which the paginator component will set the current page value in link mode. + */ + @Input() queryParam = 'page'; + /** * @docs-private */ @@ -101,6 +118,42 @@ export class DaffPaginatorComponent extends _daffPaginatorBase implements OnChan */ @Output() notifyPageChange: EventEmitter = new EventEmitter(); + /** + * Determines when ellipsis after the first page number should show. + * + * @docs-private + */ + get _showFirstEllipsis(): boolean { + return this.currentPage >= visiblePageRange+2; + } + + /** + * Determines when ellipsis before the final page number should show. + * + * @docs-private + */ + get _showLastEllipsis(): boolean { + return this.currentPage < (this.numberOfPages - visiblePageRange); + } + + /** + * Determines when the Previous button should be disabled. + * + * @docs-private + */ + get _disablePrev(): boolean { + return this.currentPage === 1; + } + + /** + * Determines when the Next button should be disabled. + * + * @docs-private + */ + get _disableNext(): boolean { + return this.currentPage === this.numberOfPages; + } + /** * @docs-private */ @@ -152,24 +205,6 @@ export class DaffPaginatorComponent extends _daffPaginatorBase implements OnChan return page === this.currentPage; } - /** - * Determines when ellipsis after the first page number should show. - * - * @docs-private - */ - _showFirstEllipsis(): boolean { - return this.currentPage >= visiblePageRange+2; - } - - /** - * Determines when ellipsis before the final page number should show. - * - * @docs-private - */ - _showLastEllipsis(): boolean { - return this.currentPage < (this.numberOfPages - visiblePageRange); - } - /** * Determines if the given page number should be shown. The two additional 'or' conditionals are needed * so the paginator retains the same total width at the extreme page numbers (1 and numberOfPages). @@ -183,21 +218,9 @@ export class DaffPaginatorComponent extends _daffPaginatorBase implements OnChan || (this.currentPage > this.numberOfPages - visiblePageRange && pageNumber > this.numberOfPages - 2*visiblePageRange); } - /** - * Determines when the Previous button should be disabled. - * - * @docs-private - */ - _disablePrev(): boolean { - return this.currentPage === 1; - } - - /** - * Determines when the Next button should be disabled. - * - * @docs-private - */ - _disableNext(): boolean { - return this.currentPage === this.numberOfPages; + _buildPageQueryParams(page: number): Params { + return { + [this.queryParam]: page, + }; } } diff --git a/libs/design/src/molecules/paginator/paginator.module.ts b/libs/design/src/molecules/paginator/paginator.module.ts index 7ae2dfd91f..230a50b013 100644 --- a/libs/design/src/molecules/paginator/paginator.module.ts +++ b/libs/design/src/molecules/paginator/paginator.module.ts @@ -1,5 +1,6 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { RouterModule } from '@angular/router'; import { FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { DaffPaginatorComponent } from './paginator.component'; @@ -8,6 +9,7 @@ import { DaffButtonModule } from '../../atoms/button/public_api'; @NgModule({ imports: [ CommonModule, + RouterModule, FontAwesomeModule, DaffButtonModule, ],