Skip to content

Commit 04cdb13

Browse files
lharriestinayuangao
authored andcommitted
feat(paginator): Add functionality to jump to first and last page (#9603)
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 cde00df commit 04cdb13

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)