Skip to content

Commit fa07f14

Browse files
author
lharries
committed
feat(paginator): Add functionality to jump to first and last page
Add two buttons which allow you to jump to the first and last page. Hidden by default and can be toggled with showHideFirstButtons Fixes #9278
1 parent c306297 commit fa07f14

File tree

11 files changed

+221
-25
lines changed

11 files changed

+221
-25
lines changed

src/demo-app/table/table-demo.html

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,24 @@ <h3>MatTable Example</h3>
172172
</mat-paginator>
173173
</div>
174174

175+
<h3>MatTable with First/Last Buttons Example</h3>
176+
177+
<div class="demo-table-container demo-mat-table-example mat-elevation-z4">
178+
179+
<table-header-demo>
180+
</table-header-demo>
181+
182+
<h4 class="paginator-output">{{paginatorOutput | json}}</h4>
183+
184+
<mat-paginator #paginator
185+
[length]="_peopleDatabase.data.length"
186+
[pageSize]="10"
187+
[pageSizeOptions]="[5, 10, 25, 100]"
188+
showFirstLastButtons
189+
(page)="handlePaginator($event)">
190+
</mat-paginator>
191+
</div>
192+
175193
<mat-card class="demo-table-card">
176194
<h3> MatTable Using 'When' Rows for Interactive Details</h3>
177195

src/demo-app/table/table-demo.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,7 @@
141141
background: #f5f5f5;
142142
}
143143
}
144+
145+
.paginator-output {
146+
margin-left: 20px;
147+
}

src/demo-app/table/table-demo.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import {Component, ElementRef, ViewChild} from '@angular/core';
1010
import {PeopleDatabase, UserData} from './people-database';
1111
import {PersonDataSource} from './person-data-source';
12-
import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material';
12+
import {MatPaginator, MatSort, MatTableDataSource, PageEvent} from '@angular/material';
1313
import {DetailRow, PersonDetailDataSource} from './person-detail-data-source';
1414
import {animate, state, style, transition, trigger} from '@angular/animations';
1515
import {SelectionModel} from '@angular/cdk/collections';
@@ -65,6 +65,8 @@ export class TableDemo {
6565
@ViewChild('paginatorForDataSource') paginatorForDataSource: MatPaginator;
6666
@ViewChild('sortForDataSource') sortForDataSource: MatSort;
6767

68+
paginatorOutput: PageEvent;
69+
6870
constructor(public _peopleDatabase: PeopleDatabase) {
6971
this.matTableDataSource.sortingDataAccessor = (data: UserData, property: string) => {
7072
switch (property) {
@@ -183,4 +185,8 @@ export class TableDemo {
183185
toggleHighlight(property: string, enable: boolean) {
184186
enable ? this.highlights.add(property) : this.highlights.delete(property);
185187
}
188+
189+
handlePaginator(pageEvent: PageEvent) {
190+
this.paginatorOutput = pageEvent;
191+
}
186192
}

src/lib/paginator/_paginator-theme.scss

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,22 @@
1616
color: mat-color($foreground, secondary-text);
1717
}
1818

19-
.mat-paginator-increment,
20-
.mat-paginator-decrement {
19+
.mat-paginator-decrement,
20+
.mat-paginator-increment {
2121
border-top: 2px solid mat-color($foreground, 'icon');
2222
border-right: 2px solid mat-color($foreground, 'icon');
2323
}
2424

25+
.mat-paginator-first,
26+
.mat-paginator-last {
27+
border-top: 2px solid mat-color($foreground, 'icon');
28+
}
29+
2530
.mat-icon-button[disabled] {
31+
.mat-paginator-decrement,
2632
.mat-paginator-increment,
27-
.mat-paginator-decrement {
33+
.mat-paginator-first,
34+
.mat-paginator-last {
2835
border-color: mat-color($foreground, 'disabled');
2936
}
3037
}

src/lib/paginator/paginator-intl.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,12 @@ export class MatPaginatorIntl {
3030
/** A label for the button that decrements the current page. */
3131
previousPageLabel: string = 'Previous page';
3232

33+
/** A label for the button that moves to the first page. */
34+
firstPageLabel: string = 'First page';
35+
36+
/** A label for the button that moves to the last page. */
37+
lastPageLabel: string = 'Last page';
38+
3339
/** A label for the range of items within the current page and the length of the whole list. */
3440
getRangeLabel = (page: number, pageSize: number, length: number) => {
3541
if (length == 0 || pageSize == 0) { return `0 of ${length}`; }

src/lib/paginator/paginator.html

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,25 @@
2525
{{_intl.getRangeLabel(pageIndex, pageSize, length)}}
2626
</div>
2727

28+
<button mat-icon-button type="button"
29+
class="mat-paginator-navigation-first"
30+
(click)="firstPage()"
31+
[attr.aria-label]="_intl.firstPageLabel"
32+
[matTooltip]="_intl.firstPageLabel"
33+
[matTooltipPosition]="'above'"
34+
[disabled]="!hasPreviousPage()"
35+
*ngIf="showFirstLastButtons">
36+
<div class="mat-paginator-first"></div>
37+
<div class="mat-paginator-decrement"></div>
38+
</button>
2839
<button mat-icon-button type="button"
2940
class="mat-paginator-navigation-previous"
3041
(click)="previousPage()"
3142
[attr.aria-label]="_intl.previousPageLabel"
3243
[matTooltip]="_intl.previousPageLabel"
3344
[matTooltipPosition]="'above'"
3445
[disabled]="!hasPreviousPage()">
35-
<div class="mat-paginator-increment"></div>
46+
<div class="mat-paginator-decrement"></div>
3647
</button>
3748
<button mat-icon-button type="button"
3849
class="mat-paginator-navigation-next"
@@ -41,7 +52,18 @@
4152
[matTooltip]="_intl.nextPageLabel"
4253
[matTooltipPosition]="'above'"
4354
[disabled]="!hasNextPage()">
44-
<div class="mat-paginator-decrement"></div>
55+
<div class="mat-paginator-increment"></div>
56+
</button>
57+
<button mat-icon-button type="button"
58+
class="mat-paginator-navigation-last"
59+
(click)="lastPage()"
60+
[attr.aria-label]="_intl.lastPageLabel"
61+
[matTooltip]="_intl.lastPageLabel"
62+
[matTooltipPosition]="'above'"
63+
[disabled]="!hasNextPage()"
64+
*ngIf="showFirstLastButtons">
65+
<div class="mat-paginator-increment"></div>
66+
<div class="mat-paginator-last"></div>
4567
</button>
4668
</div>
4769
</div>

src/lib/paginator/paginator.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,4 @@ This will allow you to change the following:
2626
3. The tooltip messages on the navigation buttons.
2727

2828
### Accessibility
29-
The `aria-label`s for next page and previous page buttons can be set in `MatPaginatorIntl`.
29+
The `aria-label`s for next page, previous page, first page and last page buttons can be set in `MatPaginatorIntl`.

src/lib/paginator/paginator.scss

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@ $mat-paginator-button-margin: 8px;
1515
$mat-paginator-button-icon-height: 8px;
1616
$mat-paginator-button-icon-width: 8px;
1717

18-
$mat-paginator-button-decrement-icon-margin: 12px;
19-
$mat-paginator-button-increment-icon-margin: 16px;
18+
$mat-paginator-button-increment-icon-margin: 12px;
19+
$mat-paginator-button-decrement-icon-margin: 16px;
20+
21+
$mat-paginator-button-first-last-icon-width: 14px;
22+
23+
$mat-paginator-button-first-icon-margin: 3px;
24+
$mat-paginator-button-last-icon-margin: 15px;
25+
26+
27+
$mat-paginator-button-first-decrement-icon-margin: 21px;
28+
$mat-paginator-button-last-increment-icon-margin: 9px;
29+
2030

2131
.mat-paginator {
2232
display: block;
@@ -50,43 +60,71 @@ $mat-paginator-button-increment-icon-margin: 16px;
5060
margin: $mat-paginator-range-label-margin;
5161
}
5262

53-
.mat-paginator-increment-button + .mat-paginator-increment-button {
63+
.mat-paginator-decrement-button + .mat-paginator-decrement-button {
5464
margin: 0 0 0 $mat-paginator-button-margin;
5565

5666
[dir='rtl'] & {
5767
margin: 0 $mat-paginator-button-margin 0 0;
5868
}
5969
}
6070

61-
.mat-paginator-increment,
62-
.mat-paginator-decrement {
71+
.mat-paginator-decrement,
72+
.mat-paginator-increment {
6373
width: $mat-paginator-button-icon-width;
6474
height: $mat-paginator-button-icon-height;
6575
}
6676

67-
.mat-paginator-decrement,
68-
[dir='rtl'] .mat-paginator-increment {
69-
transform: rotate(45deg);
70-
}
7177
.mat-paginator-increment,
7278
[dir='rtl'] .mat-paginator-decrement {
79+
transform: rotate(45deg);
80+
}
81+
.mat-paginator-decrement,
82+
[dir='rtl'] .mat-paginator-increment {
7383
transform: rotate(225deg);
7484
}
7585

86+
.mat-paginator-increment {
87+
margin-left: $mat-paginator-button-increment-icon-margin;
88+
[dir='rtl'] & {
89+
margin-right: $mat-paginator-button-increment-icon-margin;
90+
}
91+
}
92+
7693
.mat-paginator-decrement {
7794
margin-left: $mat-paginator-button-decrement-icon-margin;
7895
[dir='rtl'] & {
7996
margin-right: $mat-paginator-button-decrement-icon-margin;
8097
}
8198
}
8299

83-
.mat-paginator-increment {
84-
margin-left: $mat-paginator-button-increment-icon-margin;
85-
[dir='rtl'] & {
86-
margin-right: $mat-paginator-button-increment-icon-margin;
100+
.mat-paginator-first {
101+
transform: rotate(90deg);
102+
width: $mat-paginator-button-first-last-icon-width;
103+
height: $mat-paginator-button-icon-height;
104+
float:left;
105+
margin-left: $mat-paginator-button-first-icon-margin;
106+
}
107+
108+
.mat-paginator-navigation-first {
109+
.mat-paginator-decrement {
110+
margin-left: $mat-paginator-button-first-decrement-icon-margin;
87111
}
88112
}
89113

114+
.mat-paginator-navigation-last {
115+
.mat-paginator-increment {
116+
float: left;
117+
margin-left: $mat-paginator-button-last-increment-icon-margin;
118+
}
119+
}
120+
121+
.mat-paginator-last {
122+
transform: rotate(90deg);
123+
width: $mat-paginator-button-first-last-icon-width;
124+
height: $mat-paginator-button-icon-height;
125+
margin-left: $mat-paginator-button-last-icon-margin;
126+
}
127+
90128
.mat-paginator-range-actions {
91129
display: flex;
92130
align-items: center;

src/lib/paginator/paginator.spec.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ describe('MatPaginator', () => {
104104
}));
105105
});
106106

107-
describe('when navigating with the navigation buttons', () => {
107+
describe('when navigating with the next and previous buttons', () => {
108108
it('should be able to go to the next page', () => {
109109
expect(paginator.pageIndex).toBe(0);
110110

@@ -125,6 +125,58 @@ describe('MatPaginator', () => {
125125
expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(0);
126126
});
127127

128+
});
129+
130+
it('should be able to show the first/last buttons', () => {
131+
expect(getFirstButton(fixture))
132+
.toBeNull('Expected first button to not exist.');
133+
134+
expect(getLastButton(fixture))
135+
.toBeNull('Expected last button to not exist.');
136+
137+
fixture.componentInstance.showFirstLastButtons = true;
138+
fixture.detectChanges();
139+
140+
expect(getFirstButton(fixture))
141+
.toBeTruthy('Expected first button to be rendered.');
142+
143+
expect(getLastButton(fixture))
144+
.toBeTruthy('Expected last button to be rendered.');
145+
146+
});
147+
148+
describe('when showing the first and last button', () => {
149+
150+
beforeEach(() => {
151+
component.showFirstLastButtons = true;
152+
fixture.detectChanges();
153+
});
154+
155+
it('should show right aria-labels for first/last buttons', () => {
156+
expect(getFirstButton(fixture).getAttribute('aria-label')).toBe('First page');
157+
expect(getLastButton(fixture).getAttribute('aria-label')).toBe('Last page');
158+
});
159+
160+
it('should be able to go to the last page via the last page button', () => {
161+
expect(paginator.pageIndex).toBe(0);
162+
163+
dispatchMouseEvent(getLastButton(fixture), 'click');
164+
165+
expect(paginator.pageIndex).toBe(9);
166+
expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(9);
167+
});
168+
169+
it('should be able to go to the first page via the first page button', () => {
170+
paginator.pageIndex = 3;
171+
fixture.detectChanges();
172+
expect(paginator.pageIndex).toBe(3);
173+
174+
dispatchMouseEvent(getFirstButton(fixture), 'click');
175+
176+
expect(paginator.pageIndex).toBe(0);
177+
expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(0);
178+
});
179+
128180
it('should disable navigating to the next page if at last page', () => {
129181
component.goToLastPage();
130182
fixture.detectChanges();
@@ -148,6 +200,7 @@ describe('MatPaginator', () => {
148200
expect(component.latestPageEvent).toBe(null);
149201
expect(paginator.pageIndex).toBe(0);
150202
});
203+
151204
});
152205

153206
it('should mark for check when inputs are changed directly', () => {
@@ -253,7 +306,7 @@ describe('MatPaginator', () => {
253306
expect(fixture.nativeElement.querySelector('.mat-select')).toBeNull();
254307
});
255308

256-
it('should handle the number inputs being passed in as strings', () => {
309+
it('should handle the number inputs being passed in as strings', () => {
257310
const withStringFixture = TestBed.createComponent(MatPaginatorWithStringValues);
258311
const withStringPaginator = withStringFixture.componentInstance.paginator;
259312

@@ -277,6 +330,7 @@ describe('MatPaginator', () => {
277330
expect(element.querySelector('.mat-paginator-page-size'))
278331
.toBeNull('Expected select to be removed.');
279332
});
333+
280334
});
281335

282336
function getPreviousButton(fixture: ComponentFixture<any>) {
@@ -287,12 +341,21 @@ function getNextButton(fixture: ComponentFixture<any>) {
287341
return fixture.nativeElement.querySelector('.mat-paginator-navigation-next');
288342
}
289343

344+
function getFirstButton(fixture: ComponentFixture<any>) {
345+
return fixture.nativeElement.querySelector('.mat-paginator-navigation-first');
346+
}
347+
348+
function getLastButton(fixture: ComponentFixture<any>) {
349+
return fixture.nativeElement.querySelector('.mat-paginator-navigation-last');
350+
}
351+
290352
@Component({
291353
template: `
292354
<mat-paginator [pageIndex]="pageIndex"
293355
[pageSize]="pageSize"
294356
[pageSizeOptions]="pageSizeOptions"
295357
[hidePageSize]="hidePageSize"
358+
[showFirstLastButtons]="showFirstLastButtons"
296359
[length]="length"
297360
(page)="latestPageEvent = $event">
298361
</mat-paginator>
@@ -303,6 +366,7 @@ class MatPaginatorApp {
303366
pageSize = 10;
304367
pageSizeOptions = [5, 10, 25, 100];
305368
hidePageSize = false;
369+
showFirstLastButtons = false;
306370
length = 100;
307371

308372
latestPageEvent: PageEvent | null;

0 commit comments

Comments
 (0)