Skip to content

Commit b328d36

Browse files
andrewseguintinayuangao
authored andcommitted
feat(sort): add ability to manage and display sorting (#5307)
* feat(sort): add sortable * checkin * checkin * feat(sort): add sort header * overrides, tests * format demo html * add ngif to screenready label * add new line to scss * fix tests * fix types * fix types * shorten coerce import * comments * comments * rebase * specialize intl to header; make public * remove reverse * button type and onpush * rename sort directions (shorten) * small changes * remove consolelog * screenreader
1 parent 24b8064 commit b328d36

18 files changed

+721
-15
lines changed

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

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,25 @@
3333
(toggleColorColumn)="toggleColorColumn()">
3434
</table-header-demo>
3535

36-
<cdk-table #table [dataSource]="dataSource" [trackBy]="userTrackBy">
36+
<cdk-table #table mdSort
37+
[dataSource]="dataSource"
38+
[trackBy]="userTrackBy">
3739

3840
<!-- Column Definition: ID -->
3941
<ng-container cdkColumnDef="userId">
40-
<cdk-header-cell *cdkHeaderCellDef> ID </cdk-header-cell>
42+
<cdk-header-cell *cdkHeaderCellDef
43+
md-sort-header arrowPosition="before">
44+
ID
45+
</cdk-header-cell>
4146
<cdk-cell *cdkCellDef="let row"> {{row.id}} </cdk-cell>
4247
</ng-container>
4348

4449
<!-- Column Definition: Progress -->
4550
<ng-container cdkColumnDef="progress">
46-
<cdk-header-cell *cdkHeaderCellDef> Progress </cdk-header-cell>
51+
<cdk-header-cell *cdkHeaderCellDef
52+
md-sort-header start="desc">
53+
Progress
54+
</cdk-header-cell>
4755
<cdk-cell *cdkCellDef="let row">
4856
<div class="demo-progress-stat">{{row.progress}}%</div>
4957
<div class="demo-progress-indicator-container">
@@ -57,13 +65,18 @@
5765

5866
<!-- Column Definition: Name -->
5967
<ng-container cdkColumnDef="userName">
60-
<cdk-header-cell *cdkHeaderCellDef> Name </cdk-header-cell>
68+
<cdk-header-cell *cdkHeaderCellDef md-sort-header>
69+
Name
70+
</cdk-header-cell>
6171
<cdk-cell *cdkCellDef="let row"> {{row.name}} </cdk-cell>
6272
</ng-container>
6373

6474
<!-- Column Definition: Color -->
6575
<ng-container cdkColumnDef="color">
66-
<cdk-header-cell *cdkHeaderCellDef>Color</cdk-header-cell>
76+
<cdk-header-cell *cdkHeaderCellDef
77+
md-sort-header disableClear>
78+
Color
79+
</cdk-header-cell>
6780
<cdk-cell *cdkCellDef="let row" [style.color]="row.color"> {{row.color}} </cdk-cell>
6881
</ng-container>
6982

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@
6363
font-size: 12px;
6464
font-weight: bold;
6565
color: rgba(0, 0, 0, 0.54);
66+
67+
&.mat-sort-header-sorted {
68+
color: black;
69+
}
6670
}
6771

6872
.cdk-cell {
@@ -73,6 +77,8 @@
7377
/* Column and cell styles */
7478
.cdk-column-userId {
7579
max-width: 32px;
80+
text-align: right;
81+
justify-content: flex-end;
7682
}
7783

7884
.cdk-column-userName {

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {Component, ViewChild} from '@angular/core';
22
import {PeopleDatabase, UserData} from './people-database';
33
import {PersonDataSource} from './person-data-source';
44
import {MdPaginator} from '@angular/material';
5+
import {MdSort} from '@angular/material';
56

67
export type UserProperties = 'userId' | 'userName' | 'progress' | 'color' | undefined;
78

@@ -22,6 +23,8 @@ export class DataTableDemo {
2223

2324
@ViewChild(MdPaginator) _paginator: MdPaginator;
2425

26+
@ViewChild(MdSort) sort: MdSort;
27+
2528
constructor(public _peopleDatabase: PeopleDatabase) { }
2629

2730
ngOnInit() {
@@ -30,7 +33,8 @@ export class DataTableDemo {
3033

3134
connect() {
3235
this.propertiesToDisplay = ['userId', 'userName', 'progress', 'color'];
33-
this.dataSource = new PersonDataSource(this._peopleDatabase, this._paginator);
36+
this.dataSource = new PersonDataSource(this._peopleDatabase,
37+
this._paginator, this.sort);
3438
this._peopleDatabase.initialize();
3539
}
3640

src/demo-app/data-table/person-data-source.ts

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
import {CollectionViewer, DataSource, MdPaginator} from '@angular/material';
1+
import {CollectionViewer, DataSource, MdPaginator, MdSort} from '@angular/material';
22
import {Observable} from 'rxjs/Observable';
33
import {PeopleDatabase, UserData} from './people-database';
44
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
5+
import 'rxjs/add/observable/combineLatest';
6+
import 'rxjs/add/observable/merge';
57
import 'rxjs/add/operator/map';
68
import 'rxjs/add/observable/merge';
79
import 'rxjs/add/observable/combineLatest';
@@ -15,12 +17,15 @@ export class PersonDataSource extends DataSource<any> {
1517
_renderedData: any[] = [];
1618

1719
constructor(private _peopleDatabase: PeopleDatabase,
18-
private _paginator: MdPaginator) {
20+
private _paginator: MdPaginator,
21+
private _sort: MdSort) {
1922
super();
2023

21-
// Subscribe to page changes and database changes by clearing the cached data and
24+
// Subscribe to paging, sorting, and database changes by clearing the cached data and
2225
// determining the updated display data.
23-
Observable.merge(this._paginator.page, this._peopleDatabase.dataChange).subscribe(() => {
26+
Observable.merge(this._paginator.page,
27+
this._peopleDatabase.dataChange,
28+
this._sort.mdSortChange).subscribe(() => {
2429
this._renderedData = [];
2530
this.updateDisplayData();
2631
});
@@ -51,12 +56,35 @@ export class PersonDataSource extends DataSource<any> {
5156
}
5257

5358
updateDisplayData() {
54-
const data = this._peopleDatabase.data.slice();
59+
const data = this.getSortedData();
5560

5661
// Grab the page's slice of data.
5762
const startIndex = this._paginator.pageIndex * this._paginator.pageSize;
5863
const paginatedData = data.splice(startIndex, this._paginator.pageSize);
5964

6065
this._displayData.next(paginatedData);
6166
}
67+
68+
/** Returns a sorted copy of the database data. */
69+
getSortedData(): UserData[] {
70+
const data = this._peopleDatabase.data.slice();
71+
if (!this._sort.active || this._sort.direction == '') { return data; }
72+
73+
return data.sort((a, b) => {
74+
let propertyA: number|string = '';
75+
let propertyB: number|string = '';
76+
77+
switch (this._sort.active) {
78+
case 'userId': [propertyA, propertyB] = [a.id, b.id]; break;
79+
case 'userName': [propertyA, propertyB] = [a.name, b.name]; break;
80+
case 'progress': [propertyA, propertyB] = [a.progress, b.progress]; break;
81+
case 'color': [propertyA, propertyB] = [a.color, b.color]; break;
82+
}
83+
84+
let valueA = isNaN(+propertyA) ? propertyA : +propertyA;
85+
let valueB = isNaN(+propertyB) ? propertyB : +propertyB;
86+
87+
return (valueA < valueB ? -1 : 1) * (this._sort.direction == 'asc' ? 1 : -1);
88+
});
89+
}
6290
}

src/demo-app/demo-app-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ import {
7171
MdSliderModule,
7272
MdSlideToggleModule,
7373
MdSnackBarModule,
74+
MdSortModule,
7475
MdTabsModule,
7576
MdToolbarModule,
7677
MdTooltipModule,
@@ -109,6 +110,7 @@ import {TableHeaderDemo} from './data-table/table-header-demo';
109110
MdSlideToggleModule,
110111
MdSliderModule,
111112
MdSnackBarModule,
113+
MdSortModule,
112114
MdTabsModule,
113115
MdToolbarModule,
114116
MdTooltipModule,

src/lib/core/coordination/unique-selection-dispatcher.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export class UniqueSelectionDispatcher {
3838

3939
/**
4040
* Listen for future changes to item selection.
41-
* @return Function used to unregister listener
41+
* @return Function used to deregister listener
4242
**/
4343
listen(listener: UniqueSelectionDispatcherListener): () => void {
4444
this._listeners.push(listener);

src/lib/core/data-table/data-table.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,14 +213,15 @@ export class CdkTable<T> implements CollectionViewer {
213213
ngAfterViewInit() {
214214
// Find and construct an iterable differ that can be used to find the diff in an array.
215215
this._dataDiffer = this._differs.find([]).create(this._trackByFn);
216-
217-
this._renderHeaderRow();
218216
this._isViewInitialized = true;
219217
}
220218

221219
ngDoCheck() {
222220
if (this._isViewInitialized && this.dataSource && !this._renderChangeSubscription) {
223-
this._observeRenderChanges();
221+
this._renderHeaderRow();
222+
if (this.dataSource && !this._renderChangeSubscription) {
223+
this._observeRenderChanges();
224+
}
224225
}
225226
}
226227

src/lib/module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import {StyleModule} from './core/style/index';
4646
import {MdDatepickerModule} from './datepicker/index';
4747
import {CdkDataTableModule} from './core/data-table/index';
4848
import {MdExpansionModule} from './expansion/index';
49+
import {MdSortModule} from './sort/index';
4950
import {MdPaginatorModule} from './paginator/index';
5051

5152
const MATERIAL_MODULES = [
@@ -73,6 +74,7 @@ const MATERIAL_MODULES = [
7374
MdSliderModule,
7475
MdSlideToggleModule,
7576
MdSnackBarModule,
77+
MdSortModule,
7678
MdTabsModule,
7779
MdToolbarModule,
7880
MdTooltipModule,

src/lib/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export * from './sidenav/index';
3939
export * from './slider/index';
4040
export * from './slide-toggle/index';
4141
export * from './snack-bar/index';
42+
export * from './sort/index';
4243
export * from './tabs/index';
4344
export * from './tabs/tab-nav-bar/index';
4445
export * from './toolbar/index';

src/lib/sort/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {NgModule} from '@angular/core';
10+
import {MdSortHeader} from './sort-header';
11+
import {MdSort} from './sort';
12+
import {MdSortHeaderIntl} from './sort-header-intl';
13+
import {CommonModule} from '@angular/common';
14+
15+
export * from './sort-direction';
16+
export * from './sort-header';
17+
export * from './sort-header-intl';
18+
export * from './sort';
19+
20+
@NgModule({
21+
imports: [CommonModule],
22+
exports: [MdSort, MdSortHeader],
23+
declarations: [MdSort, MdSortHeader],
24+
providers: [MdSortHeaderIntl]
25+
})
26+
export class MdSortModule {}

src/lib/sort/sort-direction.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
export type SortDirection = 'asc' | 'desc' | '';

src/lib/sort/sort-errors.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/** @docs-private */
10+
export function getMdSortDuplicateMdSortableIdError(id: string): Error {
11+
return Error(`Cannot have two MdSortables with the same id (${id}).`);
12+
}
13+
14+
/** @docs-private */
15+
export function getMdSortHeaderNotContainedWithinMdSortError(): Error {
16+
return Error(`MdSortHeader must be placed within a parent element with the MdSort directive.`);
17+
}
18+
19+
/** @docs-private */
20+
export function getMdSortHeaderMissingIdError(): Error {
21+
return Error(`MdSortHeader must be provided with a unique id.`);
22+
}

src/lib/sort/sort-header-intl.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Injectable} from '@angular/core';
10+
import {SortDirection} from './sort-direction';
11+
12+
/**
13+
* To modify the labels and text displayed, create a new instance of MdSortHeaderIntl and
14+
* include it in a custom provider.
15+
*/
16+
@Injectable()
17+
export class MdSortHeaderIntl {
18+
sortButtonLabel = (id: string) => {
19+
return `Change sorting for ${id}`;
20+
}
21+
22+
/** A label to describe the current sort (visible only to screenreaders). */
23+
sortDescriptionLabel = (id: string, direction: SortDirection) => {
24+
return `Sorted by ${id} ${direction == 'asc' ? 'ascending' : 'descending'}`;
25+
}
26+
}

src/lib/sort/sort-header.html

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<div class="mat-sort-header-container"
2+
[class.mat-sort-header-position-before]="arrowPosition == 'before'">
3+
<button class="mat-sort-header-button" type="button"
4+
[attr.aria-label]="_intl.sortButtonLabel(id)">
5+
<ng-content></ng-content>
6+
</button>
7+
8+
<div *ngIf="_isSorted()"
9+
class="mat-sort-header-arrow"
10+
[class.mat-sort-header-asc]="_sort.direction == 'asc'"
11+
[class.mat-sort-header-desc]="_sort.direction == 'desc'">
12+
<div class="mat-sort-header-stem"></div>
13+
<div class="mat-sort-header-pointer-left"></div>
14+
<div class="mat-sort-header-pointer-right"></div>
15+
</div>
16+
</div>
17+
18+
<span class="cdk-visually-hidden" *ngIf="_isSorted()">
19+
{{_intl.sortDescriptionLabel(id, _sort.direction)}}
20+
</span>

0 commit comments

Comments
 (0)