Skip to content

Commit

Permalink
feat(sort): add ability to manage and display sorting (#5307)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
andrewseguin authored and tinayuangao committed Jun 27, 2017
1 parent 24b8064 commit b328d36
Show file tree
Hide file tree
Showing 18 changed files with 721 additions and 15 deletions.
23 changes: 18 additions & 5 deletions src/demo-app/data-table/data-table-demo.html
Expand Up @@ -33,17 +33,25 @@
(toggleColorColumn)="toggleColorColumn()">
</table-header-demo>

<cdk-table #table [dataSource]="dataSource" [trackBy]="userTrackBy">
<cdk-table #table mdSort
[dataSource]="dataSource"
[trackBy]="userTrackBy">

<!-- Column Definition: ID -->
<ng-container cdkColumnDef="userId">
<cdk-header-cell *cdkHeaderCellDef> ID </cdk-header-cell>
<cdk-header-cell *cdkHeaderCellDef
md-sort-header arrowPosition="before">
ID
</cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.id}} </cdk-cell>
</ng-container>

<!-- Column Definition: Progress -->
<ng-container cdkColumnDef="progress">
<cdk-header-cell *cdkHeaderCellDef> Progress </cdk-header-cell>
<cdk-header-cell *cdkHeaderCellDef
md-sort-header start="desc">
Progress
</cdk-header-cell>
<cdk-cell *cdkCellDef="let row">
<div class="demo-progress-stat">{{row.progress}}%</div>
<div class="demo-progress-indicator-container">
Expand All @@ -57,13 +65,18 @@

<!-- Column Definition: Name -->
<ng-container cdkColumnDef="userName">
<cdk-header-cell *cdkHeaderCellDef> Name </cdk-header-cell>
<cdk-header-cell *cdkHeaderCellDef md-sort-header>
Name
</cdk-header-cell>
<cdk-cell *cdkCellDef="let row"> {{row.name}} </cdk-cell>
</ng-container>

<!-- Column Definition: Color -->
<ng-container cdkColumnDef="color">
<cdk-header-cell *cdkHeaderCellDef>Color</cdk-header-cell>
<cdk-header-cell *cdkHeaderCellDef
md-sort-header disableClear>
Color
</cdk-header-cell>
<cdk-cell *cdkCellDef="let row" [style.color]="row.color"> {{row.color}} </cdk-cell>
</ng-container>

Expand Down
6 changes: 6 additions & 0 deletions src/demo-app/data-table/data-table-demo.scss
Expand Up @@ -63,6 +63,10 @@
font-size: 12px;
font-weight: bold;
color: rgba(0, 0, 0, 0.54);

&.mat-sort-header-sorted {
color: black;
}
}

.cdk-cell {
Expand All @@ -73,6 +77,8 @@
/* Column and cell styles */
.cdk-column-userId {
max-width: 32px;
text-align: right;
justify-content: flex-end;
}

.cdk-column-userName {
Expand Down
6 changes: 5 additions & 1 deletion src/demo-app/data-table/data-table-demo.ts
Expand Up @@ -2,6 +2,7 @@ import {Component, ViewChild} from '@angular/core';
import {PeopleDatabase, UserData} from './people-database';
import {PersonDataSource} from './person-data-source';
import {MdPaginator} from '@angular/material';
import {MdSort} from '@angular/material';

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

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

@ViewChild(MdPaginator) _paginator: MdPaginator;

@ViewChild(MdSort) sort: MdSort;

constructor(public _peopleDatabase: PeopleDatabase) { }

ngOnInit() {
Expand All @@ -30,7 +33,8 @@ export class DataTableDemo {

connect() {
this.propertiesToDisplay = ['userId', 'userName', 'progress', 'color'];
this.dataSource = new PersonDataSource(this._peopleDatabase, this._paginator);
this.dataSource = new PersonDataSource(this._peopleDatabase,
this._paginator, this.sort);
this._peopleDatabase.initialize();
}

Expand Down
38 changes: 33 additions & 5 deletions src/demo-app/data-table/person-data-source.ts
@@ -1,7 +1,9 @@
import {CollectionViewer, DataSource, MdPaginator} from '@angular/material';
import {CollectionViewer, DataSource, MdPaginator, MdSort} from '@angular/material';
import {Observable} from 'rxjs/Observable';
import {PeopleDatabase, UserData} from './people-database';
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
import 'rxjs/add/observable/combineLatest';
import 'rxjs/add/observable/merge';
import 'rxjs/add/operator/map';
import 'rxjs/add/observable/merge';
import 'rxjs/add/observable/combineLatest';
Expand All @@ -15,12 +17,15 @@ export class PersonDataSource extends DataSource<any> {
_renderedData: any[] = [];

constructor(private _peopleDatabase: PeopleDatabase,
private _paginator: MdPaginator) {
private _paginator: MdPaginator,
private _sort: MdSort) {
super();

// Subscribe to page changes and database changes by clearing the cached data and
// Subscribe to paging, sorting, and database changes by clearing the cached data and
// determining the updated display data.
Observable.merge(this._paginator.page, this._peopleDatabase.dataChange).subscribe(() => {
Observable.merge(this._paginator.page,
this._peopleDatabase.dataChange,
this._sort.mdSortChange).subscribe(() => {
this._renderedData = [];
this.updateDisplayData();
});
Expand Down Expand Up @@ -51,12 +56,35 @@ export class PersonDataSource extends DataSource<any> {
}

updateDisplayData() {
const data = this._peopleDatabase.data.slice();
const data = this.getSortedData();

// 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);
}

/** Returns a sorted copy of the database data. */
getSortedData(): UserData[] {
const data = this._peopleDatabase.data.slice();
if (!this._sort.active || this._sort.direction == '') { return data; }

return data.sort((a, b) => {
let propertyA: number|string = '';
let propertyB: number|string = '';

switch (this._sort.active) {
case 'userId': [propertyA, propertyB] = [a.id, b.id]; break;
case 'userName': [propertyA, propertyB] = [a.name, b.name]; break;
case 'progress': [propertyA, propertyB] = [a.progress, b.progress]; break;
case 'color': [propertyA, propertyB] = [a.color, b.color]; break;
}

let valueA = isNaN(+propertyA) ? propertyA : +propertyA;
let valueB = isNaN(+propertyB) ? propertyB : +propertyB;

return (valueA < valueB ? -1 : 1) * (this._sort.direction == 'asc' ? 1 : -1);
});
}
}
2 changes: 2 additions & 0 deletions src/demo-app/demo-app-module.ts
Expand Up @@ -71,6 +71,7 @@ import {
MdSliderModule,
MdSlideToggleModule,
MdSnackBarModule,
MdSortModule,
MdTabsModule,
MdToolbarModule,
MdTooltipModule,
Expand Down Expand Up @@ -109,6 +110,7 @@ import {TableHeaderDemo} from './data-table/table-header-demo';
MdSlideToggleModule,
MdSliderModule,
MdSnackBarModule,
MdSortModule,
MdTabsModule,
MdToolbarModule,
MdTooltipModule,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/core/coordination/unique-selection-dispatcher.ts
Expand Up @@ -38,7 +38,7 @@ export class UniqueSelectionDispatcher {

/**
* Listen for future changes to item selection.
* @return Function used to unregister listener
* @return Function used to deregister listener
**/
listen(listener: UniqueSelectionDispatcherListener): () => void {
this._listeners.push(listener);
Expand Down
7 changes: 4 additions & 3 deletions src/lib/core/data-table/data-table.ts
Expand Up @@ -213,14 +213,15 @@ export class CdkTable<T> implements CollectionViewer {
ngAfterViewInit() {
// Find and construct an iterable differ that can be used to find the diff in an array.
this._dataDiffer = this._differs.find([]).create(this._trackByFn);

this._renderHeaderRow();
this._isViewInitialized = true;
}

ngDoCheck() {
if (this._isViewInitialized && this.dataSource && !this._renderChangeSubscription) {
this._observeRenderChanges();
this._renderHeaderRow();
if (this.dataSource && !this._renderChangeSubscription) {
this._observeRenderChanges();
}
}
}

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 {MdSortModule} from './sort/index';
import {MdPaginatorModule} from './paginator/index';

const MATERIAL_MODULES = [
Expand Down Expand Up @@ -73,6 +74,7 @@ const MATERIAL_MODULES = [
MdSliderModule,
MdSlideToggleModule,
MdSnackBarModule,
MdSortModule,
MdTabsModule,
MdToolbarModule,
MdTooltipModule,
Expand Down
1 change: 1 addition & 0 deletions src/lib/public_api.ts
Expand Up @@ -39,6 +39,7 @@ export * from './sidenav/index';
export * from './slider/index';
export * from './slide-toggle/index';
export * from './snack-bar/index';
export * from './sort/index';
export * from './tabs/index';
export * from './tabs/tab-nav-bar/index';
export * from './toolbar/index';
Expand Down
26 changes: 26 additions & 0 deletions src/lib/sort/index.ts
@@ -0,0 +1,26 @@
/**
* @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 {MdSortHeader} from './sort-header';
import {MdSort} from './sort';
import {MdSortHeaderIntl} from './sort-header-intl';
import {CommonModule} from '@angular/common';

export * from './sort-direction';
export * from './sort-header';
export * from './sort-header-intl';
export * from './sort';

@NgModule({
imports: [CommonModule],
exports: [MdSort, MdSortHeader],
declarations: [MdSort, MdSortHeader],
providers: [MdSortHeaderIntl]
})
export class MdSortModule {}
9 changes: 9 additions & 0 deletions src/lib/sort/sort-direction.ts
@@ -0,0 +1,9 @@
/**
* @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
*/

export type SortDirection = 'asc' | 'desc' | '';
22 changes: 22 additions & 0 deletions src/lib/sort/sort-errors.ts
@@ -0,0 +1,22 @@
/**
* @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
*/

/** @docs-private */
export function getMdSortDuplicateMdSortableIdError(id: string): Error {
return Error(`Cannot have two MdSortables with the same id (${id}).`);
}

/** @docs-private */
export function getMdSortHeaderNotContainedWithinMdSortError(): Error {
return Error(`MdSortHeader must be placed within a parent element with the MdSort directive.`);
}

/** @docs-private */
export function getMdSortHeaderMissingIdError(): Error {
return Error(`MdSortHeader must be provided with a unique id.`);
}
26 changes: 26 additions & 0 deletions src/lib/sort/sort-header-intl.ts
@@ -0,0 +1,26 @@
/**
* @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';
import {SortDirection} from './sort-direction';

/**
* To modify the labels and text displayed, create a new instance of MdSortHeaderIntl and
* include it in a custom provider.
*/
@Injectable()
export class MdSortHeaderIntl {
sortButtonLabel = (id: string) => {
return `Change sorting for ${id}`;
}

/** A label to describe the current sort (visible only to screenreaders). */
sortDescriptionLabel = (id: string, direction: SortDirection) => {
return `Sorted by ${id} ${direction == 'asc' ? 'ascending' : 'descending'}`;
}
}
20 changes: 20 additions & 0 deletions src/lib/sort/sort-header.html
@@ -0,0 +1,20 @@
<div class="mat-sort-header-container"
[class.mat-sort-header-position-before]="arrowPosition == 'before'">
<button class="mat-sort-header-button" type="button"
[attr.aria-label]="_intl.sortButtonLabel(id)">
<ng-content></ng-content>
</button>

<div *ngIf="_isSorted()"
class="mat-sort-header-arrow"
[class.mat-sort-header-asc]="_sort.direction == 'asc'"
[class.mat-sort-header-desc]="_sort.direction == 'desc'">
<div class="mat-sort-header-stem"></div>
<div class="mat-sort-header-pointer-left"></div>
<div class="mat-sort-header-pointer-right"></div>
</div>
</div>

<span class="cdk-visually-hidden" *ngIf="_isSorted()">
{{_intl.sortDescriptionLabel(id, _sort.direction)}}
</span>

0 comments on commit b328d36

Please sign in to comment.