Skip to content

Commit

Permalink
feat(pagination): add new pages templating options
Browse files Browse the repository at this point in the history
  • Loading branch information
ExFlo authored and maxokorokov committed Mar 18, 2021
1 parent 02cb126 commit c605ab7
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,35 @@
</ngb-pagination>
<hr>

<p>A pagination with customized pages:</p>
<ngb-pagination [collectionSize]="70" [(page)]="page" [boundaryLinks]="true">
<ng-template ngbPaginationPages let-page="page" let-pages="pages">
<li class="ngb-custom-pages-item" *ngIf="pages.length > 0">
<div class="form-group d-flex flex-nowrap">
<label
id="paginationInputLabel"
for="paginationInput"
class="col-form-label mr-2 ml-1"
>Pages</label>
<input
type="text"
inputmode="numeric"
pattern="[0-9]*"
#myInputPage
class="form-control custom-pages-input"
id="paginationInput"
[value]="page"
(keyup.enter)="applyPage(myInputPage.value)"
(blur)="applyPage(myInputPage.value)"
(input)="formatInput($any($event).target)"
aria-labelledby="paginationInputLabel paginationDescription"
/>
<span id="paginationDescription" class="col-form-label text-nowrap pl-2 pr-2">
of {{pages.length}}</span>
</div>
</li>
</ng-template>
</ngb-pagination>
<hr>

<pre>Current page: {{page}}</pre>
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {Component} from '@angular/core';

const FILTER_PAG_REGEX = /[^0-9]/g;

@Component({
selector: 'ngbd-pagination-customization',
templateUrl: './pagination-customization.html'
Expand All @@ -10,4 +12,16 @@ export class NgbdPaginationCustomization {
getPageSymbol(current: number) {
return ['A', 'B', 'C', 'D', 'E', 'F', 'G'][current - 1];
}

// User can apply the logic they want.
applyPage(myInputPageValue: string) {
const value = parseInt(myInputPageValue, 10) || 1;
// You need to apply the value back to the pagination feature
this.page = value;
}

// To allow only number in the input.
formatInput(input: HTMLInputElement) {
input.value = input.value.replace(FILTER_PAG_REGEX, '');
}
}
2 changes: 1 addition & 1 deletion demo/src/app/components/pagination/pagination.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ const DEMOS = {
markup: require('!!raw-loader!./demos/advanced/pagination-advanced.html').default
},
customization: {
title: 'Custom links',
title: 'Custom links and pages',
type: NgbdPaginationCustomization,
code: require('!!raw-loader!./demos/customization/pagination-customization').default,
markup: require('!!raw-loader!./demos/customization/pagination-customization.html').default
Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ export {
NgbPaginationModule,
NgbPaginationNext,
NgbPaginationNumber,
NgbPaginationPrevious
NgbPaginationPrevious,
NgbPaginationPages
} from './pagination/pagination.module';
export {NgbPopover, NgbPopoverConfig, NgbPopoverModule} from './popover/popover.module';
export {NgbProgressbar, NgbProgressbarConfig, NgbProgressbarModule} from './progressbar/progressbar.module';
Expand Down
8 changes: 5 additions & 3 deletions src/pagination/pagination.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
NgbPaginationLast,
NgbPaginationNext,
NgbPaginationNumber,
NgbPaginationPrevious
NgbPaginationPrevious,
NgbPaginationPages
} from './pagination';

export {
Expand All @@ -18,13 +19,14 @@ export {
NgbPaginationLast,
NgbPaginationNext,
NgbPaginationNumber,
NgbPaginationPrevious
NgbPaginationPrevious,
NgbPaginationPages
} from './pagination';
export {NgbPaginationConfig} from './pagination-config';

const DIRECTIVES = [
NgbPagination, NgbPaginationEllipsis, NgbPaginationFirst, NgbPaginationLast, NgbPaginationNext, NgbPaginationNumber,
NgbPaginationPrevious
NgbPaginationPrevious, NgbPaginationPages
];

@NgModule({declarations: DIRECTIVES, exports: DIRECTIVES, imports: [CommonModule]})
Expand Down
102 changes: 96 additions & 6 deletions src/pagination/pagination.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,31 @@ function expectPages(nativeEl: HTMLElement, pagesDef: string[], ellipsis = '...'
for (let i = 0; i < pagesDef.length; i++) {
let pageDef = pagesDef[i];
let classIndicator = pageDef.charAt(0);
let textContent = normalizeText(pages[i].textContent);

if (classIndicator === '+') {
expect(pages[i]).toHaveCssClass('active');
expect(pages[i]).not.toHaveCssClass('disabled');
expect(pages[i].getAttribute('aria-current')).toBe('page');
expect(normalizeText(pages[i].textContent)).toEqual(pageDef.substr(1) + ' (current)');
expect(textContent).toEqual(pageDef.substr(1) + ' (current)');
} else if (classIndicator === '-') {
expect(pages[i]).not.toHaveCssClass('active');
expect(pages[i]).toHaveCssClass('disabled');
expect(pages[i].getAttribute('aria-current')).toBeNull();
expect(normalizeText(pages[i].textContent)).toEqual(pageDef.substr(1));
expect(textContent).toEqual(pageDef.substr(1));
} else if (classIndicator === 'c') { // Custom Pages
const inputPagination = pages[i].querySelector('input');
textContent += ',' + inputPagination ?.value;
expect(textContent).toEqual(pageDef.substr(1));
} else {
expect(pages[i]).not.toHaveCssClass('active');
expect(pages[i]).not.toHaveCssClass('disabled');
expect(pages[i].getAttribute('aria-current')).toBeNull();
expect(normalizeText(pages[i].textContent)).toEqual(pageDef);
expect(textContent).toEqual(pageDef);
}

// ellipsis is always disabled
if (normalizeText(pages[i].textContent) === ellipsis) {
if (textContent === ellipsis) {
expect(pages[i]).not.toHaveCssClass('active');
expect(pages[i]).toHaveCssClass('disabled');
expect(pages[i].getAttribute('aria-current')).toBeNull();
Expand Down Expand Up @@ -187,7 +192,7 @@ describe('ngb-pagination', () => {
expectPages(fixture.nativeElement, ['«', '1', '2', '+3', '-»']);
});

it('should update selected page model on page no click', () => {
it('should update selected page model on page on click', () => {
const html = '<ngb-pagination [collectionSize]="collectionSize" [page]="page"></ngb-pagination>';
const fixture = createTestComponent(html);

Expand All @@ -196,7 +201,7 @@ describe('ngb-pagination', () => {
fixture.detectChanges();
expectPages(fixture.nativeElement, ['«', '1', '+2', '3', '»']);

getLink(fixture.nativeElement, 0).click();
getLink(fixture.nativeElement, 1).click();
fixture.detectChanges();
expectPages(fixture.nativeElement, ['-«', '+1', '2', '3', '»']);

Expand Down Expand Up @@ -718,6 +723,67 @@ describe('ngb-pagination', () => {
});
});

describe('Custom Pages', () => {

beforeEach(
() => { TestBed.configureTestingModule({declarations: [TestPageComponent], imports: [NgbPaginationModule]}); });

it('should render and respond to collectionSize change with customPages', () => {
const fixture = TestBed.createComponent(TestPageComponent);

fixture.componentInstance.collectionSize = 30;
fixture.detectChanges();
expectPages(fixture.nativeElement, ['-«', 'cPagesof 3,1', '»']);

fixture.componentInstance.collectionSize = 40;
fixture.detectChanges();
expectPages(fixture.nativeElement, ['-«', 'cPagesof 4,1', '»']);
});

it('should render and respond to pageSize change with custom Pages', () => {
const fixture = TestBed.createComponent(TestPageComponent);

fixture.componentInstance.collectionSize = 30;
fixture.componentInstance.pageSize = 5;
fixture.detectChanges();
expectPages(fixture.nativeElement, ['-«', 'cPagesof 6,1', '»']);

fixture.componentInstance.pageSize = 10;
fixture.detectChanges();
expectPages(fixture.nativeElement, ['-«', 'cPagesof 3,1', '»']);
});

it('should render and respond to active page change with custom Pages', () => {
const fixture = TestBed.createComponent(TestPageComponent);

fixture.componentInstance.collectionSize = 30;
fixture.componentInstance.page = 2;
fixture.detectChanges();
expectPages(fixture.nativeElement, ['«', 'cPagesof 3,2', '»']);

fixture.componentInstance.page = 3;
fixture.detectChanges();
expectPages(fixture.nativeElement, ['«', 'cPagesof 3,3', '-»']);
});

it('should update selected page model on page on click with custom Pages', () => {
const fixture = TestBed.createComponent(TestPageComponent);

fixture.componentInstance.collectionSize = 30;
fixture.componentInstance.page = 2;
fixture.detectChanges();
expectPages(fixture.nativeElement, ['«', 'cPagesof 3,2', '»']);

getLink(fixture.nativeElement, 0).click();
fixture.detectChanges();
expectPages(fixture.nativeElement, ['-«', 'cPagesof 3,1', '»']);

getLink(fixture.nativeElement, 2).click();
fixture.detectChanges();
expectPages(fixture.nativeElement, ['«', 'cPagesof 3,2', '»']);
});
});

describe('Custom config', () => {
let config: NgbPaginationConfig;

Expand Down Expand Up @@ -784,3 +850,27 @@ class TestComponent {

onPageChange = () => {};
}

@Component({
selector: 'test-page-cmp',
template: `<ngb-pagination [collectionSize]="collectionSize" [page]="page" [pageSize]="pageSize">
<ng-template ngbPaginationPages let-page="page" let-pages="pages">
<li *ngIf="pages.length > 0">
<label>Pages</label>
<input
type="text"
inputmode="numeric"
pattern="[0-9]*"
#myInputPage
[value]="page"
/>
<span>of {{pages.length}}</span>
</li>
</ng-template>
</ngb-pagination>`
})
class TestPageComponent {
pageSize = 10;
collectionSize = 100;
page = 1;
}
57 changes: 45 additions & 12 deletions src/pagination/pagination.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {NgbPaginationConfig} from './pagination-config';
* * `NgbPaginationNext`
* * `NgbPaginationLast`
* * `NgbPaginationEllipsis`
* * `NgbPaginationPages`
*
* link templates in case you want to override one.
*
Expand Down Expand Up @@ -51,6 +52,20 @@ export interface NgbPaginationNumberContext extends NgbPaginationLinkContext {
$implicit: number;
}

/**
* A context for the `NgbPaginationPages` pages template in case you want to override them.
*
* Extends `NgbPaginationLinkContext`.
*
* @since 4.1.0
*/
export interface NgbPaginationPagesContext extends NgbPaginationLinkContext {
/**
* The page number, displayed by the current page link.
*/
$implicit: number;
}

/**
* A directive to match the 'ellipsis' link template
*
Expand Down Expand Up @@ -111,6 +126,16 @@ export class NgbPaginationPrevious {
constructor(public templateRef: TemplateRef<NgbPaginationLinkContext>) {}
}

/**
* A directive to match the 'pages' whole content
*
* @since 8.1.0
*/
@Directive({selector: 'ng-template[ngbPaginationPages]'})
export class NgbPaginationPages {
constructor(public templateRef: TemplateRef<NgbPaginationPagesContext>) {}
}

/**
* A component that displays page numbers and allows to customize them in several ways.
*/
Expand All @@ -128,6 +153,20 @@ export class NgbPaginationPrevious {
{{ page }}
<span *ngIf="page === currentPage" class="sr-only">(current)</span>
</ng-template>
<ng-template #defaultPages let-page="page" let-pages="pages">
<li *ngFor="let pageNumber of pages" class="page-item" [class.active]="pageNumber === page"
[class.disabled]="isEllipsis(pageNumber) || disabled" [attr.aria-current]="(pageNumber === page ? 'page' : null)">
<a *ngIf="isEllipsis(pageNumber)" class="page-link" tabindex="-1" aria-disabled="true">
<ng-template [ngTemplateOutlet]="tplEllipsis?.templateRef || ellipsis"
[ngTemplateOutletContext]="{disabled: true, currentPage: page}"></ng-template>
</a>
<a *ngIf="!isEllipsis(pageNumber)" class="page-link" href (click)="selectPage(pageNumber); $event.preventDefault()"
[attr.tabindex]="disabled ? '-1' : null" [attr.aria-disabled]="disabled ? 'true' : null">
<ng-template [ngTemplateOutlet]="tplNumber?.templateRef || defaultNumber"
[ngTemplateOutletContext]="{disabled: disabled, $implicit: pageNumber, currentPage: page}"></ng-template>
</a>
</li>
</ng-template>
<ul [class]="'pagination' + (size ? ' pagination-' + size : '')">
<li *ngIf="boundaryLinks" class="page-item"
[class.disabled]="previousDisabled()">
Expand All @@ -148,18 +187,11 @@ export class NgbPaginationPrevious {
[ngTemplateOutletContext]="{disabled: previousDisabled()}"></ng-template>
</a>
</li>
<li *ngFor="let pageNumber of pages" class="page-item" [class.active]="pageNumber === page"
[class.disabled]="isEllipsis(pageNumber) || disabled" [attr.aria-current]="(pageNumber === page ? 'page' : null)">
<a *ngIf="isEllipsis(pageNumber)" class="page-link" tabindex="-1" aria-disabled="true">
<ng-template [ngTemplateOutlet]="tplEllipsis?.templateRef || ellipsis"
[ngTemplateOutletContext]="{disabled: true, currentPage: page}"></ng-template>
</a>
<a *ngIf="!isEllipsis(pageNumber)" class="page-link" href (click)="selectPage(pageNumber); $event.preventDefault()"
[attr.tabindex]="disabled ? '-1' : null" [attr.aria-disabled]="disabled ? 'true' : null">
<ng-template [ngTemplateOutlet]="tplNumber?.templateRef || defaultNumber"
[ngTemplateOutletContext]="{disabled: disabled, $implicit: pageNumber, currentPage: page}"></ng-template>
</a>
</li>
<ng-template
[ngTemplateOutlet]="tplPages?.templateRef || defaultPages"
[ngTemplateOutletContext]="{ disabled: false, page: page, pages: pages }"
>
</ng-template>
<li *ngIf="directionLinks" class="page-item" [class.disabled]="nextDisabled()">
<a aria-label="Next" i18n-aria-label="@@ngb.pagination.next-aria" class="page-link" href
(click)="selectPage(page+1); $event.preventDefault()" [attr.tabindex]="nextDisabled() ? '-1' : null"
Expand Down Expand Up @@ -190,6 +222,7 @@ export class NgbPagination implements OnChanges {
@ContentChild(NgbPaginationNext, {static: false}) tplNext: NgbPaginationNext;
@ContentChild(NgbPaginationNumber, {static: false}) tplNumber: NgbPaginationNumber;
@ContentChild(NgbPaginationPrevious, {static: false}) tplPrevious: NgbPaginationPrevious;
@ContentChild(NgbPaginationPages, {static: false}) tplPages: NgbPaginationPages;

/**
* If `true`, pagination links will be disabled.
Expand Down

0 comments on commit c605ab7

Please sign in to comment.