Skip to content

Commit

Permalink
feat(pagination): initial pagination component (#5156)
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewseguin authored and jelbourn committed Jun 23, 2017
1 parent 7b2d4ae commit 85fb00a
Show file tree
Hide file tree
Showing 25 changed files with 854 additions and 36 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/demo-app/data-table/data-table-demo.html
Expand Up @@ -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>
11 changes: 8 additions & 3 deletions src/demo-app/data-table/data-table-demo.ts
@@ -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;

Expand All @@ -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();
}

Expand Down
60 changes: 42 additions & 18 deletions src/demo-app/data-table/person-data-source.ts
@@ -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 changes: 2 additions & 0 deletions src/demo-app/demo-app-module.ts
Expand Up @@ -61,6 +61,7 @@ import {
MdListModule,
MdMenuModule,
MdNativeDateModule,
MdPaginatorModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
Expand Down Expand Up @@ -98,6 +99,7 @@ import {TableHeaderDemo} from './data-table/table-header-demo';
MdListModule,
MdMenuModule,
MdCoreModule,
MdPaginatorModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/core/theming/_all-theme.scss
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/lib/core/typography/_all-typography.scss
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/lib/module.ts
Expand Up @@ -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,
Expand All @@ -62,6 +63,7 @@ const MATERIAL_MODULES = [
MdInputModule,
MdListModule,
MdMenuModule,
MdPaginatorModule,
MdProgressBarModule,
MdProgressSpinnerModule,
MdRadioModule,
Expand Down
42 changes: 42 additions & 0 deletions src/lib/paginator/_paginator-theme.scss
@@ -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 changes: 35 additions & 0 deletions src/lib/paginator/index.ts
@@ -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 changes: 41 additions & 0 deletions src/lib/paginator/paginator-intl.ts
@@ -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 changes: 40 additions & 0 deletions src/lib/paginator/paginator.html
@@ -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>

0 comments on commit 85fb00a

Please sign in to comment.