Skip to content

Commit c6fdd9e

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 c6fdd9e

File tree

9 files changed

+241
-23
lines changed

9 files changed

+241
-23
lines changed

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,61 @@ <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 (shiftColumns)="displayedColumns.push(displayedColumns.shift())"
180+
(toggleColorColumn)="toggleColorColumn()">
181+
</table-header-demo>
182+
183+
<mat-table [dataSource]="dataSource" [trackBy]="userTrackBy">
184+
185+
<!-- Column Definition: ID -->
186+
<ng-container cdkColumnDef="userId">
187+
<mat-header-cell *matHeaderCellDef> ID </mat-header-cell>
188+
<mat-cell *matCellDef="let row"> {{row.id}} </mat-cell>
189+
</ng-container>
190+
191+
<!-- Column Definition: Progress -->
192+
<ng-container matColumnDef="progress">
193+
<mat-header-cell *matHeaderCellDef> Progress </mat-header-cell>
194+
<mat-cell *matCellDef="let row">
195+
<div class="demo-progress-stat">{{row.progress}}%</div>
196+
<div class="demo-progress-indicator-container">
197+
<div class="demo-progress-indicator"
198+
[style.background]="row.progress > 50 ? 'green' : 'red'"
199+
[style.opacity]="getOpacity(row.progress)"
200+
[style.width.%]="row.progress"></div>
201+
</div>
202+
</mat-cell>
203+
</ng-container>
204+
205+
<!-- Column Definition: Name -->
206+
<ng-container matColumnDef="userName">
207+
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
208+
<mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
209+
</ng-container>
210+
211+
<!-- Column Definition: Color -->
212+
<ng-container matColumnDef="color">
213+
<mat-header-cell *matHeaderCellDef>Color</mat-header-cell>
214+
<mat-cell *matCellDef="let row" [style.color]="row.color"> {{row.color}} </mat-cell>
215+
</ng-container>
216+
217+
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
218+
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
219+
220+
</mat-table>
221+
222+
<mat-paginator #paginator
223+
[length]="_peopleDatabase.data.length"
224+
[pageSize]="10"
225+
[pageSizeOptions]="[5, 10, 25, 100]"
226+
[showFirstLastButtons]="true">
227+
</mat-paginator>
228+
</div>
229+
175230
<mat-card class="demo-table-card">
176231
<h3> MatTable Using 'When' Rows for Interactive Details</h3>
177232

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)