feat(pagination): initial pagination component (#5156)

This commit is contained in:
Andrew Seguin
2017-06-23 11:51:39 -07:00
committed by Jeremy Elbourn
parent 7b2d4ae601
commit 85fb00a151
25 changed files with 854 additions and 36 deletions
+4 -4
View File
@@ -4169,7 +4169,7 @@
"glob": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=",
"dev": true
},
"glob-base": {
@@ -8070,7 +8070,7 @@
"npmlog": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.0.tgz",
"integrity": "sha512-ocolIkZYZt8UveuiDS0yAkkIjid1o7lPG8cYm05yNYzBn8ykQtaiPMEGp8fY9tKdDgm8okpdKzkvu1y9hUYugA==",
"integrity": "sha1-3Fm+6F9k8A7UJO+yrweD3yXRwLU=",
"dev": true
},
"null-check": {
@@ -11254,7 +11254,7 @@
"wide-align": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.2.tgz",
"integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==",
"integrity": "sha1-Vx4PGwYEY268DfwhsDObvjE0FxA=",
"dev": true
},
"widest-line": {
@@ -11316,7 +11316,7 @@
"write-file-atomic": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.1.0.tgz",
"integrity": "sha512-0TZ20a+xcIl4u0+Mj5xDH2yOWdmQiXlKf9Hm+TgDXjTMsEYb+gDrmb8e8UNAzMCitX8NBqG4Z/FUQIyzv/R1JQ==",
"integrity": "sha1-F2n0tVHu3OQZ8FBd6uLiZ2NULTc=",
"dev": true
},
"write-file-stdout": {
@@ -78,4 +78,11 @@
}">
</cdk-row>
</cdk-table>
<md-paginator #paginator
[length]="_peopleDatabase.data.length"
[pageIndex]="0"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 25, 100]">
</md-paginator>
</div>
+8 -3
View File
@@ -1,6 +1,7 @@
import {Component} from '@angular/core';
import {Component, ViewChild} from '@angular/core';
import {PeopleDatabase, UserData} from './people-database';
import {PersonDataSource} from './person-data-source';
import {MdPaginator} from '@angular/material';
export type UserProperties = 'userId' | 'userName' | 'progress' | 'color' | undefined;
@@ -19,13 +20,17 @@ export class DataTableDemo {
changeReferences = false;
highlights = new Set<string>();
constructor(public _peopleDatabase: PeopleDatabase) {
@ViewChild(MdPaginator) _paginator: MdPaginator;
constructor(public _peopleDatabase: PeopleDatabase) { }
ngOnInit() {
this.connect();
}
connect() {
this.propertiesToDisplay = ['userId', 'userName', 'progress', 'color'];
this.dataSource = new PersonDataSource(this._peopleDatabase);
this.dataSource = new PersonDataSource(this._peopleDatabase, this._paginator);
this._peopleDatabase.initialize();
}
+42 -18
View File
@@ -1,34 +1,58 @@
import {CollectionViewer, DataSource} from '@angular/material';
import {CollectionViewer, DataSource, MdPaginator} from '@angular/material';
import {Observable} from 'rxjs/Observable';
import {PeopleDatabase, UserData} from './people-database';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
export class PersonDataSource extends DataSource<any> {
/** Data that should be displayed by the table. */
_displayData = new BehaviorSubject<UserData[]>([]);
/** Cached data provided by the display data. */
_renderedData: any[] = [];
constructor(private _peopleDatabase: PeopleDatabase) {
constructor(private _peopleDatabase: PeopleDatabase,
private _paginator: MdPaginator) {
super();
// Subscribe to page changes and database changes by clearing the cached data and
// determining the updated display data.
Observable.merge(this._paginator.page, this._peopleDatabase.dataChange).subscribe(() => {
this._renderedData = [];
this.updateDisplayData();
});
}
connect(collectionViewer: CollectionViewer): Observable<UserData[]> {
const changeStreams = Observable.combineLatest(
collectionViewer.viewChange,
this._peopleDatabase.dataChange);
return changeStreams.map((result: any[]) => {
const view: {start: number, end: number} = result[0];
this.updateDisplayData();
// Set the rendered rows length to the virtual page size. Fill in the data provided
// from the index start until the end index or pagination size, whichever is smaller.
this._renderedData.length = this._peopleDatabase.data.length;
const streams = [collectionViewer.viewChange, this._displayData];
return Observable.combineLatest(streams)
.map((results: [{start: number, end: number}, UserData[]]) => {
const [view, data] = results;
const buffer = 20;
let rangeStart = Math.max(0, view.start - buffer);
let rangeEnd = Math.min(this._peopleDatabase.data.length, view.end + buffer);
// Set the rendered rows length to the virtual page size. Fill in the data provided
// from the index start until the end index or pagination size, whichever is smaller.
this._renderedData.length = data.length;
for (let i = rangeStart; i < rangeEnd; i++) {
this._renderedData[i] = this._peopleDatabase.data[i];
}
const buffer = 20;
let rangeStart = Math.max(0, view.start - buffer);
let rangeEnd = Math.min(data.length, view.end + buffer);
return this._renderedData;
});
for (let i = rangeStart; i < rangeEnd; i++) {
this._renderedData[i] = data[i];
}
return this._renderedData;
});
}
updateDisplayData() {
const data = this._peopleDatabase.data.slice();
// Grab the page's slice of data.
const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
const paginatedData = data.splice(startIndex, this._paginator.pageSize);
this._displayData.next(paginatedData);
}
}
+2
View File
@@ -61,6 +61,7 @@ import {
MdListModule,
MdMenuModule,
MdNativeDateModule,
MdPaginatorModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
@@ -98,6 +99,7 @@ import {TableHeaderDemo} from './data-table/table-header-demo';
MdListModule,
MdMenuModule,
MdCoreModule,
MdPaginatorModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
+2
View File
@@ -14,6 +14,7 @@
@import '../../input/input-theme';
@import '../../list/list-theme';
@import '../../menu/menu-theme';
@import '../../paginator/paginator-theme';
@import '../../progress-bar/progress-bar-theme';
@import '../../progress-spinner/progress-spinner-theme';
@import '../../radio/radio-theme';
@@ -44,6 +45,7 @@
@include mat-input-theme($theme);
@include mat-list-theme($theme);
@include mat-menu-theme($theme);
@include mat-paginator-theme($theme);
@include mat-progress-bar-theme($theme);
@include mat-progress-spinner-theme($theme);
@include mat-radio-theme($theme);
@@ -12,6 +12,7 @@
@import '../../input/input-theme';
@import '../../list/list-theme';
@import '../../menu/menu-theme';
@import '../../paginator/paginator-theme';
@import '../../progress-bar/progress-bar-theme';
@import '../../progress-spinner/progress-spinner-theme';
@import '../../radio/radio-theme';
@@ -42,6 +43,7 @@
@include mat-icon-typography($config);
@include mat-input-typography($config);
@include mat-menu-typography($config);
@include mat-paginator-typography($config);
@include mat-progress-bar-typography($config);
@include mat-progress-spinner-typography($config);
@include mat-radio-typography($config);
+2
View File
@@ -46,6 +46,7 @@ import {StyleModule} from './core/style/index';
import {MdDatepickerModule} from './datepicker/index';
import {CdkDataTableModule} from './core/data-table/index';
import {MdExpansionModule} from './expansion/index';
import {MdPaginatorModule} from './paginator/index';
const MATERIAL_MODULES = [
MdAutocompleteModule,
@@ -62,6 +63,7 @@ const MATERIAL_MODULES = [
MdInputModule,
MdListModule,
MdMenuModule,
MdPaginatorModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
+42
View File
@@ -0,0 +1,42 @@
@import '../core/theming/palette';
@import '../core/theming/theming';
@import '../core/typography/typography-utils';
@mixin mat-paginator-theme($theme) {
$foreground: map-get($theme, foreground);
.mat-paginator,
.mat-paginator-page-size .mat-select-trigger {
color: mat-color($foreground, secondary-text);
}
.mat-paginator-increment,
.mat-paginator-decrement {
border-top: 2px solid mat-color($foreground, 'icon');
border-right: 2px solid mat-color($foreground, 'icon');
}
.mat-icon-button[disabled] {
.mat-paginator-increment,
.mat-paginator-decrement {
border-color: mat-color($foreground, 'disabled');
}
}
}
@mixin mat-paginator-typography($config) {
.mat-paginator {
font: {
family: mat-font-family($config);
size: mat-font-size($config, caption);
}
}
.mat-paginator-page-size .mat-select-trigger {
font: {
family: mat-font-family($config);
size: mat-font-size($config, caption);
}
}
}
+35
View File
@@ -0,0 +1,35 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {FormsModule} from '@angular/forms';
import {MdCommonModule, OverlayModule} from '../core';
import {MdButtonModule} from '../button/index';
import {MdSelectModule} from '../select/index';
import {MdPaginator} from './paginator';
import {MdPaginatorIntl} from './paginator-intl';
import {MdTooltipModule} from '../tooltip/index';
@NgModule({
imports: [
CommonModule,
FormsModule,
MdButtonModule,
MdSelectModule,
MdTooltipModule,
],
exports: [MdPaginator],
declarations: [MdPaginator],
providers: [MdPaginatorIntl],
})
export class MdPaginatorModule {}
export * from './paginator';
+41
View File
@@ -0,0 +1,41 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {Injectable} from '@angular/core';
/**
* To modify the labels and text displayed, create a new instance of MdPaginatorIntl and
* include it in a custom provider
*/
@Injectable()
export class MdPaginatorIntl {
/** A label for the page size selector. */
itemsPerPageLabel = 'Items per page:';
/** A label for the button that increments the current page. */
nextPageLabel = 'Next page';
/** A label for the button that decrements the current page. */
previousPageLabel = 'Previous page';
/** A label for the range of items within the current page and the length of the whole list. */
getRangeLabel = (page: number, pageSize: number, length: number) => {
if (length == 0 || pageSize == 0) { return `0 of ${length}`; }
length = Math.max(length, 0);
const startIndex = page * pageSize;
// If the start index exceeds the list length, do not try and fix the end index to the end.
const endIndex = startIndex < length ?
Math.min(startIndex + pageSize, length) :
startIndex + pageSize;
return `${startIndex + 1} - ${endIndex} of ${length}`;
}
}
+40
View File
@@ -0,0 +1,40 @@
<div class="mat-paginator-page-size">
<div class="mat-paginator-page-size-label">
{{_intl.itemsPerPageLabel}}
</div>
<md-select *ngIf="_displayedPageSizeOptions.length > 1"
class="mat-paginator-page-size-select"
[ngModel]="pageSize"
[aria-label]="_intl.itemsPerPageLabel"
(change)="_changePageSize($event.value)">
<md-option *ngFor="let pageSizeOption of _displayedPageSizeOptions" [value]="pageSizeOption">
{{pageSizeOption}}
</md-option>
</md-select>
<div *ngIf="_displayedPageSizeOptions.length <= 1">{{pageSize}}</div>
</div>
<div class="mat-paginator-range-label">
{{_intl.getRangeLabel(pageIndex, pageSize, length)}}
</div>
<button md-icon-button
class="mat-paginator-navigation-previous"
(click)="previousPage()"
[attr.aria-label]="_intl.previousPageLabel"
[mdTooltip]="_intl.previousPageLabel"
[mdTooltipPosition]="'above'"
[disabled]="!hasPreviousPage()">
<div class="mat-paginator-increment"></div>
</button>
<button md-icon-button
class="mat-paginator-navigation-next"
(click)="nextPage()"
[attr.aria-label]="_intl.nextPageLabel"
[mdTooltip]="_intl.nextPageLabel"
[mdTooltipPosition]="'above'"
[disabled]="!hasNextPage()">
<div class="mat-paginator-decrement"></div>
</button>
+26
View File
@@ -0,0 +1,26 @@
`<md-paginator>` is a navigation for pages information, typically used with a data-table.
<!-- example(pagination-overview) -->
### Basic use
Each paginator instance requires:
* The number of items per page (default set to 50)
* The total number of items being paged
The current page index defaults to 0, but can be explicitly set via pageIndex.
When the user interacts with the paginator, a `PageEvent` will be fired that can be used to update
any associated data view.
### Page size options
The paginator displays a dropdown of page sizes for the user to choose from. The options for this
dropdown can be set via `pageSizeOptions`
The current pageSize will always appear in the dropdown, even if it is not included in pageSizeOptions.
### Internationalization
The labels for the paginator can be customized by providing your own instance of `MdPaginatorIntl`.
This will allow you to change the following:
1. The label for the length of each page.
2. The range text displayed to the user.
3. The tooltip messages on the navigation buttons.
+81
View File
@@ -0,0 +1,81 @@
$mat-paginator-height: 56px;
$mat-paginator-padding: 0 8px;
$mat-paginator-items-per-page-label-margin: 0 4px;
$mat-paginator-selector-margin: 0 4px;
$mat-paginator-selector-trigger-min-width: 56px;
$mat-paginator-range-label-margin: 0 32px;
$mat-paginator-button-margin: 8px;
$mat-paginator-button-icon-height: 8px;
$mat-paginator-button-icon-width: 8px;
$mat-paginator-button-decrement-icon-margin: 12px;
$mat-paginator-button-increment-icon-margin: 16px;
.mat-paginator {
display: flex;
align-items: center;
justify-content: flex-end;
min-height: $mat-paginator-height;
padding: $mat-paginator-padding;
}
.mat-paginator-page-size {
display: flex;
align-items: center;
}
.mat-paginator-page-size-label {
margin: $mat-paginator-items-per-page-label-margin;
}
.mat-paginator-page-size-select {
margin: $mat-paginator-selector-margin;
.mat-select-trigger {
min-width: $mat-paginator-selector-trigger-min-width;
}
}
.mat-paginator-range-label {
margin: $mat-paginator-range-label-margin;
}
.mat-paginator-increment-button + .mat-paginator-increment-button {
margin: 0 0 0 $mat-paginator-button-margin;
[dir='rtl'] & {
margin: 0 $mat-paginator-button-margin 0 0;
}
}
.mat-paginator-increment,
.mat-paginator-decrement {
width: $mat-paginator-button-icon-width;
height: $mat-paginator-button-icon-height;
}
.mat-paginator-decrement,
[dir='rtl'] .mat-paginator-increment {
transform: rotate(45deg);
}
.mat-paginator-increment,
[dir='rtl'] .mat-paginator-decrement {
transform: rotate(225deg);
}
.mat-paginator-decrement {
margin-left: $mat-paginator-button-decrement-icon-margin;
[dir='rtl'] & {
margin-right: $mat-paginator-button-decrement-icon-margin;
}
}
.mat-paginator-increment {
margin-left: $mat-paginator-button-increment-icon-margin;
[dir='rtl'] & {
margin-right: $mat-paginator-button-increment-icon-margin;
}
}
+279
View File
@@ -0,0 +1,279 @@
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {MdPaginatorModule} from './index';
import {MdPaginator, PageEvent} from './paginator';
import {Component, ElementRef, ViewChild} from '@angular/core';
import {MdPaginatorIntl} from './paginator-intl';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {customMatchers} from '../core/testing/jasmine-matchers';
import {dispatchMouseEvent} from '../core/testing/dispatch-events';
describe('MdPaginator', () => {
let fixture: ComponentFixture<MdPaginatorApp>;
let component: MdPaginatorApp;
let paginator: MdPaginator;
beforeEach(async(() => {
jasmine.addMatchers(customMatchers);
TestBed.configureTestingModule({
imports: [
MdPaginatorModule,
NoopAnimationsModule,
],
declarations: [
MdPaginatorApp,
MdPaginatorWithoutPageSizeApp,
MdPaginatorWithoutOptionsApp,
],
providers: [MdPaginatorIntl]
}).compileComponents();
fixture = TestBed.createComponent(MdPaginatorApp);
component = fixture.componentInstance;
paginator = component.mdPaginator;
fixture.detectChanges();
}));
describe('with the default internationalization provider', () => {
it('should show the right range text', () => {
const rangeElement = fixture.nativeElement.querySelector('.mat-paginator-range-label');
// View second page of list of 100, each page contains 10 items.
component.length = 100;
component.pageSize = 10;
component.pageIndex = 1;
fixture.detectChanges();
expect(rangeElement.innerText).toBe('11 - 20 of 100');
// View third page of list of 200, each page contains 20 items.
component.length = 200;
component.pageSize = 20;
component.pageIndex = 2;
fixture.detectChanges();
expect(rangeElement.innerText).toBe('41 - 60 of 200');
// View first page of list of 0, each page contains 5 items.
component.length = 0;
component.pageSize = 5;
component.pageIndex = 2;
fixture.detectChanges();
expect(rangeElement.innerText).toBe('0 of 0');
// View third page of list of 12, each page contains 5 items.
component.length = 12;
component.pageSize = 5;
component.pageIndex = 2;
fixture.detectChanges();
expect(rangeElement.innerText).toBe('11 - 12 of 12');
// View third page of list of 10, each page contains 5 items.
component.length = 10;
component.pageSize = 5;
component.pageIndex = 2;
fixture.detectChanges();
expect(rangeElement.innerText).toBe('11 - 15 of 10');
// View third page of list of -5, each page contains 5 items.
component.length = -5;
component.pageSize = 5;
component.pageIndex = 2;
fixture.detectChanges();
expect(rangeElement.innerText).toBe('11 - 15 of 0');
});
it('should show right aria-labels for select and buttons', () => {
const select = fixture.nativeElement.querySelector('.mat-select');
expect(select.getAttribute('aria-label')).toBe('Items per page:');
const prevButton = fixture.nativeElement.querySelector('.mat-paginator-navigation-previous');
expect(prevButton.getAttribute('aria-label')).toBe('Previous page');
const nextButton = fixture.nativeElement.querySelector('.mat-paginator-navigation-next');
expect(nextButton.getAttribute('aria-label')).toBe('Next page');
});
});
describe('when navigating with the navigation buttons', () => {
it('should be able to go to the next page', () => {
expect(paginator.pageIndex).toBe(0);
component.clickNextButton();
expect(paginator.pageIndex).toBe(1);
expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(1);
});
it('should be able to go to the previous page', () => {
paginator.pageIndex = 1;
fixture.detectChanges();
expect(paginator.pageIndex).toBe(1);
component.clickPreviousButton();
expect(paginator.pageIndex).toBe(0);
expect(component.latestPageEvent ? component.latestPageEvent.pageIndex : null).toBe(0);
});
it('should disable navigating to the next page if at first page', () => {
component.goToLastPage();
fixture.detectChanges();
expect(paginator.pageIndex).toBe(10);
expect(paginator.hasNextPage()).toBe(false);
component.latestPageEvent = null;
component.clickNextButton();
expect(component.latestPageEvent).toBe(null);
expect(paginator.pageIndex).toBe(10);
});
it('should disable navigating to the previous page if at first page', () => {
expect(paginator.pageIndex).toBe(0);
expect(paginator.hasPreviousPage()).toBe(false);
component.latestPageEvent = null;
component.clickPreviousButton();
expect(component.latestPageEvent).toBe(null);
expect(paginator.pageIndex).toBe(0);
});
});
it('should default the page size options to the page size if no options provided', () => {
const withoutOptionsAppFixture = TestBed.createComponent(MdPaginatorWithoutOptionsApp);
withoutOptionsAppFixture.detectChanges();
expect(withoutOptionsAppFixture.componentInstance.mdPaginator._displayedPageSizeOptions)
.toEqual([10]);
});
it('should show a sorted list of page size options including the current page size', () => {
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]);
component.pageSize = 30;
fixture.detectChanges();
expect(paginator.pageSizeOptions).toEqual([5, 10, 25, 100]);
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 30, 100]);
component.pageSizeOptions = [100, 25, 10, 5];
fixture.detectChanges();
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 30, 100]);
});
it('should be able to change the page size while keeping the first item present', () => {
// Start on the third page of a list of 100 with a page size of 10.
component.pageIndex = 4;
component.pageSize = 10;
component.length = 100;
fixture.detectChanges();
// The first item of the page should be item with index 40
let firstPageItemIndex: number | null = paginator.pageIndex * paginator.pageSize;
expect(firstPageItemIndex).toBe(40);
// The first item on the page is now 25. Change the page size to 25 so that we should now be
// on the second page where the top item is index 25.
paginator._changePageSize(25);
let paginationEvent = component.latestPageEvent;
firstPageItemIndex = paginationEvent ?
paginationEvent.pageIndex * paginationEvent.pageSize : null;
expect(firstPageItemIndex).toBe(25);
expect(paginationEvent ? paginationEvent.pageIndex : null).toBe(1);
// The first item on the page is still 25. Change the page size to 8 so that we should now be
// on the fourth page where the top item is index 24.
paginator._changePageSize(8);
paginationEvent = component.latestPageEvent;
firstPageItemIndex = paginationEvent ?
paginationEvent.pageIndex * paginationEvent.pageSize : null;
expect(firstPageItemIndex).toBe(24);
expect(paginationEvent ? paginationEvent.pageIndex : null).toBe(3);
// The first item on the page is 24. Change the page size to 16 so that we should now be
// on the first page where the top item is index 0.
paginator._changePageSize(25);
paginationEvent = component.latestPageEvent;
firstPageItemIndex = paginationEvent ?
paginationEvent.pageIndex * paginationEvent.pageSize : null;
expect(firstPageItemIndex).toBe(0);
expect(paginationEvent ? paginationEvent.pageIndex : null).toBe(0);
});
it('should show a select only if there are multiple options', () => {
expect(paginator._displayedPageSizeOptions).toEqual([5, 10, 25, 100]);
expect(fixture.nativeElement.querySelector('.mat-select')).not.toBeNull();
// Remove options so that the paginator only uses the current page size (10) as an option.
// Should no longer show the select component since there is only one option.
component.pageSizeOptions = [];
fixture.detectChanges();
expect(fixture.nativeElement.querySelector('.mat-select')).toBeNull();
});
});
@Component({
template: `
<md-paginator [pageIndex]="pageIndex"
[pageSize]="pageSize"
[pageSizeOptions]="pageSizeOptions"
[length]="length"
(page)="latestPageEvent = $event">
</md-paginator>
`,
})
class MdPaginatorApp {
pageIndex = 0;
pageSize = 10;
pageSizeOptions = [5, 10, 25, 100];
length = 100;
latestPageEvent: PageEvent | null;
@ViewChild(MdPaginator) mdPaginator: MdPaginator;
constructor(private _elementRef: ElementRef) { }
clickPreviousButton() {
const previousButton =
this._elementRef.nativeElement.querySelector('.mat-paginator-navigation-previous');
dispatchMouseEvent(previousButton, 'click');
}
clickNextButton() {
const nextButton =
this._elementRef.nativeElement.querySelector('.mat-paginator-navigation-next');
dispatchMouseEvent(nextButton, 'click'); }
goToLastPage() {
this.pageIndex = Math.ceil(this.length / this.pageSize);
}
}
@Component({
template: `
<md-paginator [length]="100"></md-paginator>
`,
})
class MdPaginatorWithoutPageSizeOrOptionsApp {
@ViewChild(MdPaginator) mdPaginator: MdPaginator;
}
@Component({
template: `
<md-paginator [pageSizeOptions]="[10, 20, 30]"></md-paginator>
`,
})
class MdPaginatorWithoutPageSizeApp {
@ViewChild(MdPaginator) mdPaginator: MdPaginator;
}
@Component({
template: `
<md-paginator [pageSize]="10"></md-paginator>
`,
})
class MdPaginatorWithoutOptionsApp {
@ViewChild(MdPaginator) mdPaginator: MdPaginator;
}
+157
View File
@@ -0,0 +1,157 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnInit,
Output,
ViewEncapsulation
} from '@angular/core';
import {MdPaginatorIntl} from './paginator-intl';
import {MATERIAL_COMPATIBILITY_MODE} from '../core';
/**
* Change event object that is emitted when the user selects a
* different page size or navigates to another page.
*/
export class PageEvent {
pageIndex: number;
pageSize: number;
length: number;
}
/**
* Component to provide navigation between paged information. Displays the size of the current
* page, user-selectable options to change that size, what items are being shown, and
* navigational button to go to the previous or next page.
*/
@Component({
moduleId: module.id,
selector: 'md-paginator, mat-paginator',
templateUrl: 'paginator.html',
styleUrls: ['paginator.css'],
host: {
'class': 'mat-paginator',
},
providers: [
{provide: MATERIAL_COMPATIBILITY_MODE, useValue: false}
],
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
export class MdPaginator implements OnInit {
private _initialized: boolean;
/** The zero-based page index of the displayed list of items. Defaulted to 0. */
@Input() pageIndex: number = 0;
/** The length of the total number of items that are being paginated. Defaulted to 0. */
@Input() length: number = 0;
/** Number of items to display on a page. By default set to 50. */
@Input()
get pageSize(): number { return this._pageSize; }
set pageSize(pageSize: number) {
this._pageSize = pageSize;
this._updateDisplayedPageSizeOptions();
}
private _pageSize: number = 50;
/** The set of provided page size options to display to the user. */
@Input()
get pageSizeOptions(): number[] { return this._pageSizeOptions; }
set pageSizeOptions(pageSizeOptions: number[]) {
this._pageSizeOptions = pageSizeOptions;
this._updateDisplayedPageSizeOptions();
}
private _pageSizeOptions: number[] = [];
/** Event emitted when the paginator changes the page size or page index. */
@Output() page = new EventEmitter<PageEvent>();
/** Displayed set of page size options. Will be sorted and include current page size. */
_displayedPageSizeOptions: number[];
constructor(public _intl: MdPaginatorIntl) { }
ngOnInit() {
this._initialized = true;
this._updateDisplayedPageSizeOptions();
}
/** Increments the page index to the next page index if a next page exists. */
nextPage() {
if (!this.hasNextPage()) { return; }
this.pageIndex++;
this._emitPageEvent();
}
/** Decrements the page index to the previous page index if a next page exists. */
previousPage() {
if (!this.hasPreviousPage()) { return; }
this.pageIndex--;
this._emitPageEvent();
}
/** Returns true if the user can go to the next page. */
hasPreviousPage() {
return this.pageIndex >= 1 && this.pageSize != 0;
}
/** Returns true if the user can go to the next page. */
hasNextPage() {
const numberOfPages = Math.ceil(this.length / this.pageSize) - 1;
return this.pageIndex < numberOfPages && this.pageSize != 0;
}
/**
* Changes the page size so that the first item displayed on the page will still be
* displayed using the new page size.
*
* For example, if the page size is 10 and on the second page (items indexed 10-19) then
* switching so that the page size is 5 will set the third page as the current page so
* that the 10th item will still be displayed.
*/
_changePageSize(pageSize: number) {
// Current page needs to be updated to reflect the new page size. Navigate to the page
// containing the previous page's first item.
const startIndex = this.pageIndex * this.pageSize;
this.pageIndex = Math.floor(startIndex / pageSize) || 0;
this.pageSize = pageSize;
this._emitPageEvent();
}
/**
* Updates the list of page size options to display to the user. Includes making sure that
* the page size is an option and that the list is sorted.
*/
private _updateDisplayedPageSizeOptions() {
if (!this._initialized) { return; }
this._displayedPageSizeOptions = this.pageSizeOptions.slice();
if (this._displayedPageSizeOptions.indexOf(this.pageSize) == -1) {
this._displayedPageSizeOptions.push(this.pageSize);
}
// Sort the numbers using a number-specific sort function.
this._displayedPageSizeOptions.sort((a, b) => a - b);
}
/** Emits an event notifying that a change of the paginator's properties has been triggered. */
private _emitPageEvent() {
this.page.next({
pageIndex: this.pageIndex,
pageSize: this.pageSize,
length: this.length
});
}
}
+1
View File
@@ -30,6 +30,7 @@ export * from './icon/index';
export * from './input/index';
export * from './list/index';
export * from './menu/index';
export * from './paginator/index';
export * from './progress-bar/index';
export * from './progress-spinner/index';
export * from './radio/index';
+20 -11
View File
@@ -15,8 +15,8 @@ import {
ProgressBarConfigurableExample
} from './progress-bar-configurable/progress-bar-configurable-example';
import {
DialogOverviewExampleDialog,
DialogOverviewExample
DialogOverviewExample,
DialogOverviewExampleDialog
} from './dialog-overview/dialog-overview-example';
import {RadioNgModelExample} from './radio-ng-model/radio-ng-model-example';
import {CardFancyExample} from './card-fancy/card-fancy-example';
@@ -36,12 +36,12 @@ import {
import {ListSectionsExample} from './list-sections/list-sections-example';
import {SnackBarOverviewExample} from './snack-bar-overview/snack-bar-overview-example';
import {
DialogResultExampleDialog,
DialogResultExample
DialogResultExample,
DialogResultExampleDialog
} from './dialog-result/dialog-result-example';
import {
DialogElementsExampleDialog,
DialogElementsExample
DialogElementsExample,
DialogElementsExampleDialog
} from './dialog-elements/dialog-elements-example';
import {TooltipOverviewExample} from './tooltip-overview/tooltip-overview-example';
import {ButtonToggleOverviewExample} from './button-toggle-overview/button-toggle-overview-example';
@@ -68,20 +68,22 @@ import {SelectOverviewExample} from './select-overview/select-overview-example';
import {ChipsOverviewExample} from './chips-overview/chips-overview-example';
import {ChipsStackedExample} from './chips-stacked/chips-stacked-example';
import {SelectFormExample} from './select-form/select-form-example';
import {PaginatorOverviewExample} from './paginator-overview/paginator-overview-example';
import {DatepickerOverviewExample} from './datepicker-overview/datepicker-overview-example';
import {
PaginatorConfigurableExample
} from './paginator-configurable/paginator-configurable-example';
import {InputOverviewExample} from './input-overview/input-overview-example';
import {InputErrorsExample} from './input-errors/input-errors-example';
import {InputFormExample} from './input-form/input-form-example';
import {InputPrefixSuffixExample} from './input-prefix-suffix/input-prefix-suffix-example';
import {InputHintExample} from './input-hint/input-hint-example';
import {
MdAutocompleteModule, MdButtonModule, MdButtonToggleModule, MdCardModule, MdCheckboxModule,
MdChipsModule, MdDatepickerModule, MdDialogModule, MdGridListModule, MdIconModule, MdInputModule,
MdListModule, MdMenuModule, MdProgressBarModule, MdProgressSpinnerModule, MdRadioModule,
MdSelectModule, MdSidenavModule, MdSliderModule, MdSlideToggleModule, MdSnackBarModule,
MdTabsModule,
MdToolbarModule, MdTooltipModule
MdListModule, MdMenuModule, MdPaginatorModule, MdProgressBarModule, MdProgressSpinnerModule,
MdRadioModule, MdSelectModule, MdSidenavModule, MdSliderModule, MdSlideToggleModule,
MdSnackBarModule, MdTabsModule, MdToolbarModule, MdTooltipModule
} from '@angular/material';
export interface LiveExample {
@@ -143,6 +145,11 @@ export const EXAMPLE_COMPONENTS = {
'list-sections': {title: 'List with sections', component: ListSectionsExample},
'menu-icons': {title: 'Menu with icons', component: MenuIconsExample},
'menu-overview': {title: 'Basic menu', component: MenuOverviewExample},
'paginator-overview': {title: 'Paginator', component: PaginatorOverviewExample},
'paginator-configurable': {
title: 'Configurable paginator',
component: PaginatorConfigurableExample
},
'progress-bar-configurable': {
title: 'Configurable progress-bar',
component: ProgressBarConfigurableExample
@@ -201,6 +208,7 @@ export const EXAMPLE_COMPONENTS = {
MdInputModule,
MdListModule,
MdMenuModule,
MdPaginatorModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
@@ -252,6 +260,7 @@ export const EXAMPLE_LIST = [
ListSectionsExample,
MenuIconsExample,
MenuOverviewExample,
PaginatorOverviewExample,
ProgressBarConfigurableExample,
ProgressBarOverviewExample,
ProgressSpinnerConfigurableExample,
@@ -0,0 +1 @@
/** No CSS for this example */
@@ -0,0 +1,28 @@
<md-input-container>
List length:
<input mdInput [(ngModel)]="length">
</md-input-container>
<md-input-container>
Page size:
<input mdInput [(ngModel)]="pageSize">
</md-input-container>
<md-input-container>
Page size options:
<input mdInput
[ngModel]="pageSizeOptions"
(ngModelChange)="setPageSizeOptions($event)">
</md-input-container>
<md-paginator [length]="length"
[pageSize]="pageSize"
[pageSizeOptions]="pageSizeOptions"
(page)="pageEvent = $event">
</md-paginator>
<div *ngIf="pageEvent">
<h2>Page Change Event Properties</h2>
<div>List length: {{pageEvent.length}}</div>
<div>Page size: {{pageEvent.pageSize}}</div>
<div>Page index: {{pageEvent.pageIndex}}</div>
</div>
@@ -0,0 +1,20 @@
import {Component} from '@angular/core';
import {PageEvent} from '@angular/material';
@Component({
selector: 'paginator-configurable-example',
templateUrl: 'paginator-configurable-example.html',
})
export class PaginatorConfigurableExample {
// MdPaginator Inputs
length = 100;
pageSize = 10;
pageSizeOptions = [5, 10, 25, 100];
// MdPaginator Output
pageEvent: PageEvent;
setPageSizeOptions(setPageSizeOptionsInput: string) {
this.pageSizeOptions = setPageSizeOptionsInput.split(',').map(str => +str);
}
}
@@ -0,0 +1 @@
/** No CSS for this example */
@@ -0,0 +1,4 @@
<md-paginator [length]="100"
[pageSize]="10"
[pageSizeOptions]="[5, 10, 25, 100]" ]>
</md-paginator>
@@ -0,0 +1,8 @@
import {Component} from '@angular/core';
@Component({
selector: 'paginator-overview-example',
templateUrl: 'paginator-overview-example.html',
})
export class PaginatorOverviewExample {}
+1
View File
@@ -97,6 +97,7 @@ let apiDocsPackage = new DgeniPackage('material2-api-docs', dgeniPackageDeps)
'input/index.ts',
'list/index.ts',
'menu/index.ts',
'paginator/index.ts',
'progress-bar/index.ts',
'progress-spinner/index.ts',
'radio/index.ts',