From 12ec1fb4914815c569077282f5d343177b47cb00 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 19 Jun 2017 14:57:02 -0700 Subject: [PATCH 01/22] feat(sort): add sortable --- src/demo-app/data-table/data-table-demo.html | 10 ++-- src/demo-app/data-table/data-table-demo.ts | 6 +- src/demo-app/data-table/person-data-source.ts | 9 ++- src/demo-app/demo-app-module.ts | 2 + src/lib/module.ts | 2 + src/lib/public_api.ts | 1 + src/lib/sort/index.ts | 21 +++++++ src/lib/sort/sort-header.html | 7 +++ src/lib/sort/sort-header.scss | 38 +++++++++++++ src/lib/sort/sort-header.ts | 30 ++++++++++ src/lib/sort/sort.ts | 55 +++++++++++++++++++ 11 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 src/lib/sort/index.ts create mode 100644 src/lib/sort/sort-header.html create mode 100644 src/lib/sort/sort-header.scss create mode 100644 src/lib/sort/sort-header.ts create mode 100644 src/lib/sort/sort.ts diff --git a/src/demo-app/data-table/data-table-demo.html b/src/demo-app/data-table/data-table-demo.html index 728a2ae7b597..b8648fe01534 100644 --- a/src/demo-app/data-table/data-table-demo.html +++ b/src/demo-app/data-table/data-table-demo.html @@ -33,17 +33,19 @@ (toggleColorColumn)="toggleColorColumn()"> - + - ID + ID {{row.id}} - Progress + Progress
{{row.progress}}%
@@ -57,7 +59,7 @@ - Name + Name {{row.name}} diff --git a/src/demo-app/data-table/data-table-demo.ts b/src/demo-app/data-table/data-table-demo.ts index af13af2199a0..098c5b48b35d 100644 --- a/src/demo-app/data-table/data-table-demo.ts +++ b/src/demo-app/data-table/data-table-demo.ts @@ -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; @@ -22,6 +23,8 @@ export class DataTableDemo { @ViewChild(MdPaginator) _paginator: MdPaginator; + @ViewChild(MdSort) sort: MdSort; + constructor(public _peopleDatabase: PeopleDatabase) { } ngOnInit() { @@ -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(); } diff --git a/src/demo-app/data-table/person-data-source.ts b/src/demo-app/data-table/person-data-source.ts index bad0e0ff259c..49e1a10765d4 100644 --- a/src/demo-app/data-table/person-data-source.ts +++ b/src/demo-app/data-table/person-data-source.ts @@ -1,4 +1,4 @@ -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'; @@ -12,12 +12,15 @@ export class PersonDataSource extends DataSource { _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 // determining the updated display data. - Observable.merge(this._paginator.page, this._peopleDatabase.dataChange).subscribe(() => { + Observable.merge(this._paginator.page, + this._peopleDatabase.dataChange, + this._sort.sortChange).subscribe(() => { this._renderedData = []; this.updateDisplayData(); }); diff --git a/src/demo-app/demo-app-module.ts b/src/demo-app/demo-app-module.ts index 9b8d42650299..dc3557f834a1 100644 --- a/src/demo-app/demo-app-module.ts +++ b/src/demo-app/demo-app-module.ts @@ -71,6 +71,7 @@ import { MdSliderModule, MdSlideToggleModule, MdSnackBarModule, + MdSortModule, MdTabsModule, MdToolbarModule, MdTooltipModule, @@ -109,6 +110,7 @@ import {TableHeaderDemo} from './data-table/table-header-demo'; MdSlideToggleModule, MdSliderModule, MdSnackBarModule, + MdSortModule, MdTabsModule, MdToolbarModule, MdTooltipModule, diff --git a/src/lib/module.ts b/src/lib/module.ts index af6cb9ad1f52..f64de2a236c0 100644 --- a/src/lib/module.ts +++ b/src/lib/module.ts @@ -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 = [ @@ -73,6 +74,7 @@ const MATERIAL_MODULES = [ MdSliderModule, MdSlideToggleModule, MdSnackBarModule, + MdSortModule, MdTabsModule, MdToolbarModule, MdTooltipModule, diff --git a/src/lib/public_api.ts b/src/lib/public_api.ts index bf44dbe51ea0..19d003d3d1c6 100644 --- a/src/lib/public_api.ts +++ b/src/lib/public_api.ts @@ -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'; diff --git a/src/lib/sort/index.ts b/src/lib/sort/index.ts new file mode 100644 index 000000000000..0dfe6b612839 --- /dev/null +++ b/src/lib/sort/index.ts @@ -0,0 +1,21 @@ +/** + * @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'; + +export * from './sort-header'; +export * from './sort'; + +@NgModule({ + exports: [MdSort, MdSortHeader], + declarations: [MdSort, MdSortHeader], +}) +export class MdSortModule {} + diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html new file mode 100644 index 000000000000..4452e0ac50ee --- /dev/null +++ b/src/lib/sort/sort-header.html @@ -0,0 +1,7 @@ + + +
+
+
+
+
diff --git a/src/lib/sort/sort-header.scss b/src/lib/sort/sort-header.scss new file mode 100644 index 000000000000..2dad730a85bb --- /dev/null +++ b/src/lib/sort/sort-header.scss @@ -0,0 +1,38 @@ +:host { + display: flex; + align-items: baseline; +} + +.mat-sort-header-arrow { + height: 12px; + width: 12px; + position: relative; + transform: rotate(45deg); + margin-left: 4px; +} + +.mat-sort-header-stem { + background: black; + transform: rotate(135deg); + height: 12px; + width: 2px; + margin: auto; +} + +.mat-sort-header-pointer-left { + background: black; + width: 2px; + height: 8px; + position: absolute; + bottom: 0; + right: 0; +} + +.mat-sort-header-pointer-right { + background: black; + width: 8px; + height: 2px; + position: absolute; + bottom: 0; + right: 0; +} \ No newline at end of file diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts new file mode 100644 index 000000000000..fc8a8ddf62b1 --- /dev/null +++ b/src/lib/sort/sort-header.ts @@ -0,0 +1,30 @@ +/** + * @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 {Component} from '@angular/core'; +import {MdSort, MdSortable} from './sort'; + +@Component({ + moduleId: module.id, + selector: '[mdSortHeader], [matSortHeader]', + templateUrl: 'sort-header.html', + styleUrls: ['sort-header.css'], + host: { + '(click)': '_sort.sort(this)', + '[class.sorted]': '_sort.isSorted(this)', + } +}) +export class MdSortHeader implements MdSortable { + constructor(private _sort: MdSort) { + _sort.register(this); + } + + ngOnDestroy() { + this._sort.unregister(this); + } +} diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts new file mode 100644 index 000000000000..87f525ba06ab --- /dev/null +++ b/src/lib/sort/sort.ts @@ -0,0 +1,55 @@ +/** + * @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 {Directive, EventEmitter, Input, Output} from '@angular/core'; + +export interface MdSortable { } + +export interface Sort { + direction: string; + sortable: MdSortable; +} + +@Directive({ + selector: '[mdSort], [matSort]', +}) +export class MdSort { + sortables: MdSortable[] = []; + + active: MdSortable; + + @Input('mdSortOrder') order: string[] = ['ascending', 'descending']; + + @Input('mdSortDirection') direction: string = ''; + + @Output() sortChange = new EventEmitter(); + + register(sortable: MdSortable) { + this.sortables.push(sortable); + } + + unregister(sortable: MdSortable) { + this.sortables = this.sortables.filter(s => s != sortable); + } + + isSorted(sortable: MdSortable) { + return this.active == sortable; + } + + sort(sortable: MdSortable) { + if (this.active != sortable) { + this.direction = this.order[0]; + this.active = sortable; + } else { + let nextDirectionIndex = this.order.indexOf(this.direction) + 1; + this.direction = this.order[nextDirectionIndex < this.order.length ? nextDirectionIndex : 0]; + } + + this.sortChange.next({direction: this.direction, sortable}); + } +} From 4eea9a6b853065a2a0de3f261d9a95df8ac42a13 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 19 Jun 2017 16:36:15 -0700 Subject: [PATCH 02/22] checkin --- src/demo-app/data-table/data-table-demo.html | 6 ++--- src/demo-app/data-table/data-table-demo.scss | 5 ++++ src/demo-app/data-table/person-data-source.ts | 24 ++++++++++++++++++- src/lib/sort/index.ts | 2 ++ src/lib/sort/sort-header.html | 6 +++-- src/lib/sort/sort-header.scss | 15 ++++++++---- src/lib/sort/sort-header.ts | 8 ++++--- src/lib/sort/sort.ts | 10 ++++---- 8 files changed, 58 insertions(+), 18 deletions(-) diff --git a/src/demo-app/data-table/data-table-demo.html b/src/demo-app/data-table/data-table-demo.html index b8648fe01534..7fdaa7d27e60 100644 --- a/src/demo-app/data-table/data-table-demo.html +++ b/src/demo-app/data-table/data-table-demo.html @@ -39,13 +39,13 @@ - ID + ID {{row.id}} - Progress + Progress
{{row.progress}}%
@@ -59,7 +59,7 @@ - Name + Name {{row.name}} diff --git a/src/demo-app/data-table/data-table-demo.scss b/src/demo-app/data-table/data-table-demo.scss index a6ebd7848764..e3dcb4f6174c 100644 --- a/src/demo-app/data-table/data-table-demo.scss +++ b/src/demo-app/data-table/data-table-demo.scss @@ -63,6 +63,11 @@ font-size: 12px; font-weight: bold; color: rgba(0, 0, 0, 0.54); + user-select: none; + + &.mat-sort-header-sorted { + color: black; + } } .cdk-cell { diff --git a/src/demo-app/data-table/person-data-source.ts b/src/demo-app/data-table/person-data-source.ts index 49e1a10765d4..b5ce5c1c8535 100644 --- a/src/demo-app/data-table/person-data-source.ts +++ b/src/demo-app/data-table/person-data-source.ts @@ -51,7 +51,7 @@ export class PersonDataSource extends DataSource { } 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; @@ -59,4 +59,26 @@ export class PersonDataSource extends DataSource { this._displayData.next(paginatedData); } + + /** Returns a sorted copy of the database data. */ + getSortedData(): UserData[] { + const data = this._peopleDatabase.data.slice(); + if (!this._sort.active) { return data; } + + return data.sort((a, b) => { + let propertyA, propertyB; + + switch (this._sort.active.id) { + 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 == 'ascending' ? 1 : -1); + }); + } } diff --git a/src/lib/sort/index.ts b/src/lib/sort/index.ts index 0dfe6b612839..6d73d4c5fa46 100644 --- a/src/lib/sort/index.ts +++ b/src/lib/sort/index.ts @@ -9,11 +9,13 @@ import {NgModule} from '@angular/core'; import {MdSortHeader} from './sort-header'; import {MdSort} from './sort'; +import {CommonModule} from '@angular/common'; export * from './sort-header'; export * from './sort'; @NgModule({ + imports: [CommonModule], exports: [MdSort, MdSortHeader], declarations: [MdSort, MdSortHeader], }) diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html index 4452e0ac50ee..4063efacd73a 100644 --- a/src/lib/sort/sort-header.html +++ b/src/lib/sort/sort-header.html @@ -1,7 +1,9 @@ -
+
-
+
\ No newline at end of file diff --git a/src/lib/sort/sort-header.scss b/src/lib/sort/sort-header.scss index 2dad730a85bb..02a94f3bceb2 100644 --- a/src/lib/sort/sort-header.scss +++ b/src/lib/sort/sort-header.scss @@ -1,20 +1,25 @@ :host { display: flex; - align-items: baseline; + cursor: pointer; } .mat-sort-header-arrow { - height: 12px; - width: 12px; + height: 10px; + width: 10px; position: relative; transform: rotate(45deg); - margin-left: 4px; + margin-left: 6px; +} + +.mat-sort-header-descending { + transform: rotate(225deg); + top: 2px; } .mat-sort-header-stem { background: black; transform: rotate(135deg); - height: 12px; + height: 10px; width: 2px; margin: auto; } diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts index fc8a8ddf62b1..67a6224b3a79 100644 --- a/src/lib/sort/sort-header.ts +++ b/src/lib/sort/sort-header.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component} from '@angular/core'; +import {Component, Input} from '@angular/core'; import {MdSort, MdSortable} from './sort'; @Component({ @@ -16,11 +16,13 @@ import {MdSort, MdSortable} from './sort'; styleUrls: ['sort-header.css'], host: { '(click)': '_sort.sort(this)', - '[class.sorted]': '_sort.isSorted(this)', + '[class.mat-sort-header-sorted]': '_sort.isSorted(this)' } }) export class MdSortHeader implements MdSortable { - constructor(private _sort: MdSort) { + @Input('mdSortHeader') id: string; + + constructor(public _sort: MdSort) { _sort.register(this); } diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index 87f525ba06ab..d62f07faf060 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -8,7 +8,9 @@ import {Directive, EventEmitter, Input, Output} from '@angular/core'; -export interface MdSortable { } +export interface MdSortable { + id: string; +} export interface Sort { direction: string; @@ -19,7 +21,7 @@ export interface Sort { selector: '[mdSort], [matSort]', }) export class MdSort { - sortables: MdSortable[] = []; + sortables = new Map(); active: MdSortable; @@ -30,11 +32,11 @@ export class MdSort { @Output() sortChange = new EventEmitter(); register(sortable: MdSortable) { - this.sortables.push(sortable); + this.sortables.set(sortable.id, sortable); } unregister(sortable: MdSortable) { - this.sortables = this.sortables.filter(s => s != sortable); + this.sortables.delete(sortable.id); } isSorted(sortable: MdSortable) { From c5244b39994d1650f4608e43d0b518c2a9d66085 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 19 Jun 2017 17:14:15 -0700 Subject: [PATCH 03/22] checkin --- src/lib/sort/sort.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index d62f07faf060..8f4a72835766 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -36,6 +36,10 @@ export class MdSort { } unregister(sortable: MdSortable) { + if (this.active === sortable) { + this.active = null; + } + this.sortables.delete(sortable.id); } From af0df286d5205fd83b30ff5092c25ad06f34e0e2 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Wed, 21 Jun 2017 15:48:13 -0700 Subject: [PATCH 04/22] feat(sort): add sort header --- src/demo-app/data-table/data-table-demo.html | 6 +- src/demo-app/data-table/person-data-source.ts | 10 +- src/lib/sort/sort-header.html | 7 +- src/lib/sort/sort-header.scss | 20 +- src/lib/sort/sort-header.ts | 21 +- src/lib/sort/sort.spec.ts | 215 ++++++++++++++++++ src/lib/sort/sort.ts | 50 ++-- 7 files changed, 299 insertions(+), 30 deletions(-) create mode 100644 src/lib/sort/sort.spec.ts diff --git a/src/demo-app/data-table/data-table-demo.html b/src/demo-app/data-table/data-table-demo.html index 7fdaa7d27e60..ba04d8ca410f 100644 --- a/src/demo-app/data-table/data-table-demo.html +++ b/src/demo-app/data-table/data-table-demo.html @@ -39,13 +39,13 @@ - ID + ID {{row.id}} - Progress + Progress
{{row.progress}}%
@@ -59,7 +59,7 @@ - Name + Name {{row.name}} diff --git a/src/demo-app/data-table/person-data-source.ts b/src/demo-app/data-table/person-data-source.ts index b5ce5c1c8535..ab6c0ebc5018 100644 --- a/src/demo-app/data-table/person-data-source.ts +++ b/src/demo-app/data-table/person-data-source.ts @@ -16,7 +16,7 @@ export class PersonDataSource extends DataSource { 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, @@ -63,14 +63,14 @@ export class PersonDataSource extends DataSource { /** Returns a sorted copy of the database data. */ getSortedData(): UserData[] { const data = this._peopleDatabase.data.slice(); - if (!this._sort.active) { return data; } + if (!this._sort.active || this._sort.direction == '') { return data; } return data.sort((a, b) => { let propertyA, propertyB; - switch (this._sort.active.id) { - case 'userId': [propertyA, propertyB] = [a.id, b.id]; break; - case 'userName': [propertyA, propertyB] = [a.name, b.name]; break; + switch (this._sort.active) { + case 'id': [propertyA, propertyB] = [a.id, b.id]; break; + case 'name': [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; } diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html index 4063efacd73a..5dc5440d3197 100644 --- a/src/lib/sort/sort-header.html +++ b/src/lib/sort/sort-header.html @@ -1,8 +1,11 @@ - +
+ *ngIf="_sort.isSorted(this)">
diff --git a/src/lib/sort/sort-header.scss b/src/lib/sort/sort-header.scss index 02a94f3bceb2..dd07982f3aab 100644 --- a/src/lib/sort/sort-header.scss +++ b/src/lib/sort/sort-header.scss @@ -3,15 +3,33 @@ cursor: pointer; } +.mat-sort-header-button { + border: none; + background: 0 0; + display: flex; + align-items: center; + padding: 0; + cursor: pointer; + outline: 0; + font: inherit; + color: currentColor; +} + .mat-sort-header-arrow { + display: none; height: 10px; width: 10px; position: relative; - transform: rotate(45deg); margin-left: 6px; } +.mat-sort-header-ascending { + display: block; + transform: rotate(45deg); +} + .mat-sort-header-descending { + display: block; transform: rotate(225deg); top: 2px; } diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts index 67a6224b3a79..f6771974853f 100644 --- a/src/lib/sort/sort-header.ts +++ b/src/lib/sort/sort-header.ts @@ -6,12 +6,13 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, Input} from '@angular/core'; +import {Component, Input, Optional} from '@angular/core'; import {MdSort, MdSortable} from './sort'; +import {CdkColumnDef} from '../core/data-table/cell'; @Component({ moduleId: module.id, - selector: '[mdSortHeader], [matSortHeader]', + selector: '[md-sort-header], [mat-sort-header]', templateUrl: 'sort-header.html', styleUrls: ['sort-header.css'], host: { @@ -20,10 +21,20 @@ import {MdSort, MdSortable} from './sort'; } }) export class MdSortHeader implements MdSortable { - @Input('mdSortHeader') id: string; + @Input('md-sort-header') id: string; - constructor(public _sort: MdSort) { - _sort.register(this); + @Input('mat-sort-header') + get _id() { return this.id; } + set _id(v: string) { this.id = v; } + + constructor(public _sort: MdSort, + @Optional() public _cdkColumnDef: CdkColumnDef) { } + + ngOnInit() { + if (!this.id && this._cdkColumnDef) { + this.id = this._cdkColumnDef.name; + } + this._sort.register(this); } ngOnDestroy() { diff --git a/src/lib/sort/sort.spec.ts b/src/lib/sort/sort.spec.ts new file mode 100644 index 000000000000..7bf697f419b6 --- /dev/null +++ b/src/lib/sort/sort.spec.ts @@ -0,0 +1,215 @@ +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; +import {Component, ElementRef, ViewChild} from '@angular/core'; +import {MdSort, MdSortHeader, Sort, SortDirection, MdSortModule} from './index'; +import {CdkDataTableModule, DataSource, CollectionViewer} from '../core/data-table/index'; +import {Observable} from 'rxjs/Observable'; +import {dispatchMouseEvent} from '../core/testing/dispatch-events'; + +fdescribe('MdSort', () => { + let fixture: ComponentFixture; + + let component: SimpleMdSortApp; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MdSortModule, CdkDataTableModule], + declarations: [SimpleMdSortApp, CdkTableMdSortApp], + }).compileComponents(); + + fixture = TestBed.createComponent(SimpleMdSortApp); + + component = fixture.componentInstance; + + fixture.detectChanges(); + })); + + it('should have the sort headers register and unregister themselves', () => { + const sortables = component.mdSort.sortables; + expect(sortables.size).toBe(3); + expect(sortables.get('a')).toBe(component.mdSortHeaderA); + expect(sortables.get('b')).toBe(component.mdSortHeaderB); + expect(sortables.get('c')).toBe(component.mdSortHeaderC); + + fixture.destroy(); + expect(sortables.size).toBe(0); + expect(sortables.has('a')).toBeFalsy(); + expect(sortables.has('b')).toBeFalsy(); + expect(sortables.has('c')).toBeFalsy(); + }); + + it('should use the column definition if used within a cdk table', () => { + let cdkTableMdSortAppFixture = TestBed.createComponent(CdkTableMdSortApp); + + let cdkTableMdSortAppComponent = cdkTableMdSortAppFixture.componentInstance; + + cdkTableMdSortAppFixture.detectChanges(); + cdkTableMdSortAppFixture.detectChanges(); + + const sortables = cdkTableMdSortAppComponent.mdSort.sortables; + expect(sortables.size).toBe(3); + expect(sortables.has('column_a')).toBe(true); + expect(sortables.has('column_b')).toBe(true); + expect(sortables.has('column_c')).toBe(true); + }); + + it('should be able to cycle from asc -> desc from any start point', () => { + component.disableClear = true; + + component.start = 'ascending'; + testSingleColumnSortDirectionSequence(fixture, ['ascending', 'descending']); + + // Reverse directions + component.reverseOrder = true; + + component.start = 'descending'; + testSingleColumnSortDirectionSequence(fixture, ['descending', 'ascending']); + }); + + it('should be able to cycle between asc, desc, and [none] from any start point', () => { + component.start = 'ascending'; + testSingleColumnSortDirectionSequence(fixture, ['ascending', 'descending', '']); + + component.start = 'descending'; + testSingleColumnSortDirectionSequence(fixture, ['descending', '', 'ascending']); + + component.start = ''; + testSingleColumnSortDirectionSequence(fixture, ['', 'ascending', 'descending']); + + // Reverse directions + component.reverseOrder = true; + + component.start = 'descending'; + testSingleColumnSortDirectionSequence(fixture, ['descending', 'ascending', '']); + + component.start = 'ascending'; + testSingleColumnSortDirectionSequence(fixture, ['ascending', '', 'descending']); + + component.start = ''; + testSingleColumnSortDirectionSequence(fixture, ['', 'descending', 'ascending']); + }); + + it('should reset sort direction when a different column is sorted', () => { + component.sort('a'); + expect(component.mdSort.active).toBe('a'); + expect(component.mdSort.direction).toBe('ascending'); + + component.sort('a'); + expect(component.mdSort.active).toBe('a'); + expect(component.mdSort.direction).toBe('descending'); + + component.sort('b'); + expect(component.mdSort.active).toBe('b'); + expect(component.mdSort.direction).toBe('ascending'); + }); +}); + +/** + * Performs a sequence of sorting on a single column to see if the sort directions are + * consistent with expectations. Detects any changes in the fixture to reflect any changes in + * the inputs and resets the MdSort to remove any side effects from previous tests. + */ +function testSingleColumnSortDirectionSequence(fixture: ComponentFixture, + expectedSequence: SortDirection[]) { + // Detect any changes that were made in preparation for this sort sequence + fixture.detectChanges(); + + // Reset the md sort to make sure there are no side affects from previous tests + const component = fixture.componentInstance; + component.mdSort.active = null; + component.mdSort.direction = ''; + + // Run through the sequence to confirm the order + let actualSequence = expectedSequence.map(() => { + component.sort('a'); + + // Check that the sort event's active sort is consistent with the MdSort + expect(component.mdSort.active).toBe('a'); + expect(component.latestSortEvent.active).toBe('a'); + + // Check that the sort event's direction is consistent with the MdSort + expect(component.mdSort.direction).toBe(component.latestSortEvent.direction); + return component.mdSort.direction; + }); + expect(actualSequence).toEqual(expectedSequence); + + // Expect that performing one more sort will loop it back to the beginning. + component.sort('a'); + expect(component.mdSort.direction).toBe(expectedSequence[0]); +} + +@Component({ + template: ` +
+
A
+
B
+
C
+
+ ` +}) +class SimpleMdSortApp { + latestSortEvent: Sort; + + active: string; + start: SortDirection = 'ascending'; + direction: SortDirection = ''; + disableClear: boolean; + reverseOrder: boolean; + + @ViewChild(MdSort) mdSort: MdSort; + @ViewChild('sortHeaderA') mdSortHeaderA: MdSortHeader; + @ViewChild('sortHeaderB') mdSortHeaderB: MdSortHeader; + @ViewChild('sortHeaderC') mdSortHeaderC: MdSortHeader; + + constructor (public elementRef: ElementRef) { } + + sort(id: string) { + const sortElement = this.elementRef.nativeElement.querySelector(`#${id}`); + dispatchMouseEvent(sortElement, 'click'); + } +} + + +class FakeDataSource extends DataSource { + connect(collectionViewer: CollectionViewer): Observable { + return collectionViewer.viewChange.map(() => []); + } +} + +@Component({ + template: ` + + + Column A + {{row.a}} + + + + Column B + {{row.b}} + + + + Column C + {{row.c}} + + + + + + ` +}) +class CdkTableMdSortApp { + @ViewChild(MdSort) mdSort: MdSort; + @ViewChild('sortHeaderA') mdSortHeaderA: MdSortHeader; + @ViewChild('sortHeaderB') mdSortHeaderB: MdSortHeader; + @ViewChild('sortHeaderC') mdSortHeaderC: MdSortHeader; + + dataSource = new FakeDataSource(); + columnsToRender = ['column_a', 'column_b', 'column_c']; +} diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index 8f4a72835766..f8be33874ceb 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -13,30 +13,36 @@ export interface MdSortable { } export interface Sort { - direction: string; - sortable: MdSortable; + active: string; + direction: SortDirection; } +export type SortDirection = 'ascending' | 'descending' | ''; + @Directive({ selector: '[mdSort], [matSort]', }) export class MdSort { sortables = new Map(); - active: MdSortable; + @Input('mdSortActive') active: string; + + @Input('mdSortStart') sortStart: SortDirection = 'ascending'; - @Input('mdSortOrder') order: string[] = ['ascending', 'descending']; + @Input('mdSortDirection') direction: SortDirection = ''; - @Input('mdSortDirection') direction: string = ''; + @Input('mdSortDisableClear') disableClear: boolean; - @Output() sortChange = new EventEmitter(); + @Input('mdSortReverseOrder') reverseOrder: boolean; + + @Output() mdSortChange = new EventEmitter(); register(sortable: MdSortable) { this.sortables.set(sortable.id, sortable); } unregister(sortable: MdSortable) { - if (this.active === sortable) { + if (this.active === sortable.id) { this.active = null; } @@ -44,18 +50,34 @@ export class MdSort { } isSorted(sortable: MdSortable) { - return this.active == sortable; + return this.active == sortable.id && this.direction != ''; } sort(sortable: MdSortable) { - if (this.active != sortable) { - this.direction = this.order[0]; - this.active = sortable; + if (this.active != sortable.id) { + this.direction = this.sortStart; + this.active = sortable.id; } else { - let nextDirectionIndex = this.order.indexOf(this.direction) + 1; - this.direction = this.order[nextDirectionIndex < this.order.length ? nextDirectionIndex : 0]; + let sortDirectionCycle = this._getSortDirectionCycle(); + + // Take the next direction in the cycle + let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1; + if (nextDirectionIndex >= sortDirectionCycle.length) { nextDirectionIndex = 0; } + + this.direction = sortDirectionCycle[nextDirectionIndex]; } - this.sortChange.next({direction: this.direction, sortable}); + this.mdSortChange.next({ + active: this.active, + direction: this.direction + }); + } + + _getSortDirectionCycle(): SortDirection[] { + let sortOrder: SortDirection[] = ['ascending', 'descending']; + if (this.reverseOrder) { sortOrder.reverse(); } + if (!this.disableClear) { sortOrder.push(''); } + + return sortOrder; } } From 29ef808251cf3c0cc3ba9ffb0b81b036f9f9afb6 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 22 Jun 2017 11:59:30 -0700 Subject: [PATCH 05/22] overrides, tests --- src/demo-app/data-table/data-table-demo.html | 17 ++- src/demo-app/data-table/data-table-demo.scss | 3 +- src/demo-app/data-table/person-data-source.ts | 4 +- src/lib/sort/index.ts | 5 +- src/lib/sort/sort-direction.ts | 9 ++ src/lib/sort/sort-errors.ts | 22 +++ src/lib/sort/sort-header.html | 30 ++-- src/lib/sort/sort-header.scss | 38 +++-- src/lib/sort/sort-header.ts | 52 ++++++- src/lib/sort/sort-intl.ts | 26 ++++ src/lib/sort/sort.spec.ts | 131 ++++++++++++++---- src/lib/sort/sort.ts | 123 +++++++++++----- 12 files changed, 366 insertions(+), 94 deletions(-) create mode 100644 src/lib/sort/sort-direction.ts create mode 100644 src/lib/sort/sort-errors.ts create mode 100644 src/lib/sort/sort-intl.ts diff --git a/src/demo-app/data-table/data-table-demo.html b/src/demo-app/data-table/data-table-demo.html index ba04d8ca410f..20f9937899b9 100644 --- a/src/demo-app/data-table/data-table-demo.html +++ b/src/demo-app/data-table/data-table-demo.html @@ -39,13 +39,16 @@ - ID + ID {{row.id}} - Progress + + Progress +
{{row.progress}}%
@@ -59,13 +62,19 @@ - Name + + Name + {{row.name}} - Color + + Color + {{row.color}} diff --git a/src/demo-app/data-table/data-table-demo.scss b/src/demo-app/data-table/data-table-demo.scss index e3dcb4f6174c..a9abc92603d5 100644 --- a/src/demo-app/data-table/data-table-demo.scss +++ b/src/demo-app/data-table/data-table-demo.scss @@ -63,7 +63,6 @@ font-size: 12px; font-weight: bold; color: rgba(0, 0, 0, 0.54); - user-select: none; &.mat-sort-header-sorted { color: black; @@ -78,6 +77,8 @@ /* Column and cell styles */ .cdk-column-userId { max-width: 32px; + text-align: right; + justify-content: flex-end; } .cdk-column-userName { diff --git a/src/demo-app/data-table/person-data-source.ts b/src/demo-app/data-table/person-data-source.ts index ab6c0ebc5018..32d93aa80320 100644 --- a/src/demo-app/data-table/person-data-source.ts +++ b/src/demo-app/data-table/person-data-source.ts @@ -69,8 +69,8 @@ export class PersonDataSource extends DataSource { let propertyA, propertyB; switch (this._sort.active) { - case 'id': [propertyA, propertyB] = [a.id, b.id]; break; - case 'name': [propertyA, propertyB] = [a.name, b.name]; break; + 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; } diff --git a/src/lib/sort/index.ts b/src/lib/sort/index.ts index 6d73d4c5fa46..135ed013cd88 100644 --- a/src/lib/sort/index.ts +++ b/src/lib/sort/index.ts @@ -9,15 +9,18 @@ import {NgModule} from '@angular/core'; import {MdSortHeader} from './sort-header'; import {MdSort} from './sort'; +import {MdSortIntl} from './sort-intl'; import {CommonModule} from '@angular/common'; +export * from './sort-direction'; export * from './sort-header'; +export * from './sort-intl'; export * from './sort'; @NgModule({ imports: [CommonModule], exports: [MdSort, MdSortHeader], declarations: [MdSort, MdSortHeader], + providers: [MdSortIntl] }) export class MdSortModule {} - diff --git a/src/lib/sort/sort-direction.ts b/src/lib/sort/sort-direction.ts new file mode 100644 index 000000000000..679ebec3e08d --- /dev/null +++ b/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 = 'ascending' | 'descending' | ''; diff --git a/src/lib/sort/sort-errors.ts b/src/lib/sort/sort-errors.ts new file mode 100644 index 000000000000..f9eb38da6204 --- /dev/null +++ b/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.`); +} diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html index 5dc5440d3197..3fc17683bc11 100644 --- a/src/lib/sort/sort-header.html +++ b/src/lib/sort/sort-header.html @@ -1,12 +1,20 @@ - +
+ -
-
-
-
-
\ No newline at end of file +
+
+
+
+
+
+ + + {{_intl.sortDescriptionLabel(id, _sort.direction)}} + diff --git a/src/lib/sort/sort-header.scss b/src/lib/sort/sort-header.scss index dd07982f3aab..21d42832a4ae 100644 --- a/src/lib/sort/sort-header.scss +++ b/src/lib/sort/sort-header.scss @@ -1,8 +1,20 @@ +$mat-sort-header-arrow-margin: 6px; +$mat-sort-header-arrow-container-size: 10px; +$mat-sort-header-arrow-pointer-length: 8px; +$mat-sort-header-arrow-thickness: 2px; + :host { - display: flex; cursor: pointer; } +.mat-sort-header-container { + display: flex; +} + +.mat-sort-header-position-before { + flex-direction: row-reverse; +} + .mat-sort-header-button { border: none; background: 0 0; @@ -17,10 +29,14 @@ .mat-sort-header-arrow { display: none; - height: 10px; - width: 10px; + height: $mat-sort-header-arrow-container-size; + width: $mat-sort-header-arrow-container-size; position: relative; - margin-left: 6px; + margin: 0 0 0 $mat-sort-header-arrow-margin; + + .mat-sort-header-position-before & { + margin: 0 $mat-sort-header-arrow-margin 0 0; + } } .mat-sort-header-ascending { @@ -31,21 +47,21 @@ .mat-sort-header-descending { display: block; transform: rotate(225deg); - top: 2px; + top: $mat-sort-header-arrow-thickness; } .mat-sort-header-stem { background: black; transform: rotate(135deg); - height: 10px; - width: 2px; + height: $mat-sort-header-arrow-container-size; + width: $mat-sort-header-arrow-thickness; margin: auto; } .mat-sort-header-pointer-left { background: black; - width: 2px; - height: 8px; + width: $mat-sort-header-arrow-thickness; + height: $mat-sort-header-arrow-pointer-length; position: absolute; bottom: 0; right: 0; @@ -53,8 +69,8 @@ .mat-sort-header-pointer-right { background: black; - width: 8px; - height: 2px; + width: $mat-sort-header-arrow-pointer-length; + height: $mat-sort-header-arrow-thickness; position: absolute; bottom: 0; right: 0; diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts index f6771974853f..d9485e2b4de1 100644 --- a/src/lib/sort/sort-header.ts +++ b/src/lib/sort/sort-header.ts @@ -8,8 +8,21 @@ import {Component, Input, Optional} from '@angular/core'; import {MdSort, MdSortable} from './sort'; +import {MdSortIntl} from './sort-intl'; import {CdkColumnDef} from '../core/data-table/cell'; +import {SortDirection} from './sort-direction'; +import {coerceBooleanProperty} from '../core/coercion/boolean-property'; +import {getMdSortHeaderNotContainedWithinMdSortError} from './sort-errors'; +/** + * Applies sorting behavior (click to change sort) and styles to an element, including an + * arrow to display the current sort direction. + * + * Must be provided with an id and contained within a parent MdSort directive. + * + * If used on header cells in a CdkTable, it will automatically default its id from its containing + * column definition. + */ @Component({ moduleId: module.id, selector: '[md-sort-header], [mat-sort-header]', @@ -17,27 +30,60 @@ import {CdkColumnDef} from '../core/data-table/cell'; styleUrls: ['sort-header.css'], host: { '(click)': '_sort.sort(this)', - '[class.mat-sort-header-sorted]': '_sort.isSorted(this)' + '[class.mat-sort-header-sorted]': '_isSorted()', } }) export class MdSortHeader implements MdSortable { + /** + * ID of this sort header. If used within the context of a CdkColumnDef, this will default to + * the column's name. + */ @Input('md-sort-header') id: string; + /** Sets the position of the arrow that displays when sorted. */ + @Input() arrowPosition: 'before' | 'after' = 'after'; + + /** Overrides the sort start value of the containing MdSort for this MdSortable. */ + @Input('start') start: SortDirection; + + /** Overrides the disable clear value of the containing MdSort for this MdSortable. */ + @Input() + get disableClear() { return this._disableClear; } + set disableClear(v) { this._disableClear = coerceBooleanProperty(v); } + private _disableClear: boolean; + + /** Overrides the reverse order value of the containing MdSort for this MdSortable. */ + @Input() + get reverseOrder() { return this._reverseOrder; } + set reverseOrder(v) { this._reverseOrder = coerceBooleanProperty(v); } + private _reverseOrder: boolean; + @Input('mat-sort-header') get _id() { return this.id; } set _id(v: string) { this.id = v; } - constructor(public _sort: MdSort, - @Optional() public _cdkColumnDef: CdkColumnDef) { } + constructor(@Optional() public _sort: MdSort, + public _intl: MdSortIntl, + @Optional() public _cdkColumnDef: CdkColumnDef) { + if (!_sort) { + throw getMdSortHeaderNotContainedWithinMdSortError(); + } + } ngOnInit() { if (!this.id && this._cdkColumnDef) { this.id = this._cdkColumnDef.name; } + this._sort.register(this); } ngOnDestroy() { this._sort.unregister(this); } + + /** Whether this MdSortHeader is currently sorted in either ascending or descending order. */ + _isSorted() { + return this._sort.active == this.id && this._sort.direction != ''; + } } diff --git a/src/lib/sort/sort-intl.ts b/src/lib/sort/sort-intl.ts new file mode 100644 index 000000000000..fefdf061da2d --- /dev/null +++ b/src/lib/sort/sort-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 MdSortIntl and + * include it in a custom provider. + */ +@Injectable() +export class MdSortIntl { + 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}`; + } +} diff --git a/src/lib/sort/sort.spec.ts b/src/lib/sort/sort.spec.ts index 7bf697f419b6..7be4b69669fc 100644 --- a/src/lib/sort/sort.spec.ts +++ b/src/lib/sort/sort.spec.ts @@ -4,8 +4,14 @@ import {MdSort, MdSortHeader, Sort, SortDirection, MdSortModule} from './index'; import {CdkDataTableModule, DataSource, CollectionViewer} from '../core/data-table/index'; import {Observable} from 'rxjs/Observable'; import {dispatchMouseEvent} from '../core/testing/dispatch-events'; - -fdescribe('MdSort', () => { +import { + getMdSortDuplicateMdSortableIdError, + getMdSortHeaderMissingIdError, + getMdSortHeaderNotContainedWithinMdSortError +} from './sort-errors'; +import {wrappedErrorMessage} from '../core/testing/wrapped-error-message'; + +describe('MdSort', () => { let fixture: ComponentFixture; let component: SimpleMdSortApp; @@ -13,7 +19,13 @@ fdescribe('MdSort', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [MdSortModule, CdkDataTableModule], - declarations: [SimpleMdSortApp, CdkTableMdSortApp], + declarations: [ + SimpleMdSortApp, + CdkTableMdSortApp, + MdSortHeaderMissingMdSortApp, + MdSortDuplicateMdSortableIdsApp, + MdSortableMissingIdApp + ], }).compileComponents(); fixture = TestBed.createComponent(SimpleMdSortApp); @@ -25,16 +37,12 @@ fdescribe('MdSort', () => { it('should have the sort headers register and unregister themselves', () => { const sortables = component.mdSort.sortables; - expect(sortables.size).toBe(3); - expect(sortables.get('a')).toBe(component.mdSortHeaderA); - expect(sortables.get('b')).toBe(component.mdSortHeaderB); - expect(sortables.get('c')).toBe(component.mdSortHeaderC); + expect(sortables.size).toBe(5); + expect(sortables.get('defaultSortHeaderA')).toBe(component.mdSortHeaderDefaultA); + expect(sortables.get('defaultSortHeaderB')).toBe(component.mdSortHeaderDefaultB); fixture.destroy(); expect(sortables.size).toBe(0); - expect(sortables.has('a')).toBeFalsy(); - expect(sortables.has('b')).toBeFalsy(); - expect(sortables.has('c')).toBeFalsy(); }); it('should use the column definition if used within a cdk table', () => { @@ -89,18 +97,55 @@ fdescribe('MdSort', () => { }); it('should reset sort direction when a different column is sorted', () => { - component.sort('a'); - expect(component.mdSort.active).toBe('a'); + component.sort('defaultSortHeaderA'); + expect(component.mdSort.active).toBe('defaultSortHeaderA'); expect(component.mdSort.direction).toBe('ascending'); - component.sort('a'); - expect(component.mdSort.active).toBe('a'); + component.sort('defaultSortHeaderA'); + expect(component.mdSort.active).toBe('defaultSortHeaderA'); expect(component.mdSort.direction).toBe('descending'); - component.sort('b'); - expect(component.mdSort.active).toBe('b'); + component.sort('defaultSortHeaderB'); + expect(component.mdSort.active).toBe('defaultSortHeaderB'); expect(component.mdSort.direction).toBe('ascending'); }); + + it('should throw an error if an MdSortable is not contained within an MdSort directive', () => { + expect(() => TestBed.createComponent(MdSortHeaderMissingMdSortApp).detectChanges()) + .toThrowError(wrappedErrorMessage(getMdSortHeaderNotContainedWithinMdSortError())); + }); + + it('should throw an error if two MdSortables have the same id', () => { + expect(() => TestBed.createComponent(MdSortDuplicateMdSortableIdsApp).detectChanges()) + .toThrowError(wrappedErrorMessage(getMdSortDuplicateMdSortableIdError('duplicateId'))); + }); + + it('should throw an error if an MdSortable is missing an id', () => { + expect(() => TestBed.createComponent(MdSortableMissingIdApp).detectChanges()) + .toThrowError(wrappedErrorMessage(getMdSortHeaderMissingIdError())); + }); + + it('should allow let MdSortable override the default sort parameters', () => { + testSingleColumnSortDirectionSequence( + fixture, ['ascending', 'descending', '']); + + testSingleColumnSortDirectionSequence( + fixture, ['descending', '', 'ascending'], + 'overrideStart'); + + testSingleColumnSortDirectionSequence( + fixture, ['ascending', 'descending'], + 'overrideDisableClear'); + + testSingleColumnSortDirectionSequence( + fixture, ['descending', 'ascending', ''], + 'overrideReverseOrder'); + }); + + it('should apply the aria-labels to the button', () => { + const button = fixture.nativeElement.querySelector('#defaultSortHeaderA button'); + expect(button.getAttribute('aria-label')).toBe('Change sorting for defaultSortHeaderA'); + }); }); /** @@ -109,7 +154,8 @@ fdescribe('MdSort', () => { * the inputs and resets the MdSort to remove any side effects from previous tests. */ function testSingleColumnSortDirectionSequence(fixture: ComponentFixture, - expectedSequence: SortDirection[]) { + expectedSequence: SortDirection[], + id: string = 'defaultSortHeaderA') { // Detect any changes that were made in preparation for this sort sequence fixture.detectChanges(); @@ -120,11 +166,11 @@ function testSingleColumnSortDirectionSequence(fixture: ComponentFixture { - component.sort('a'); + component.sort(id); // Check that the sort event's active sort is consistent with the MdSort - expect(component.mdSort.active).toBe('a'); - expect(component.latestSortEvent.active).toBe('a'); + expect(component.mdSort.active).toBe(id); + expect(component.latestSortEvent.active).toBe(id); // Check that the sort event's direction is consistent with the MdSort expect(component.mdSort.direction).toBe(component.latestSortEvent.direction); @@ -133,7 +179,7 @@ function testSingleColumnSortDirectionSequence(fixture: ComponentFixture -
A
-
B
-
C
+
A
+
B
+
D
+
E
+
F
` }) @@ -162,9 +210,8 @@ class SimpleMdSortApp { reverseOrder: boolean; @ViewChild(MdSort) mdSort: MdSort; - @ViewChild('sortHeaderA') mdSortHeaderA: MdSortHeader; - @ViewChild('sortHeaderB') mdSortHeaderB: MdSortHeader; - @ViewChild('sortHeaderC') mdSortHeaderC: MdSortHeader; + @ViewChild('defaultSortHeaderA') mdSortHeaderDefaultA: MdSortHeader; + @ViewChild('defaultSortHeaderB') mdSortHeaderDefaultB: MdSortHeader; constructor (public elementRef: ElementRef) { } @@ -206,10 +253,34 @@ class FakeDataSource extends DataSource { }) class CdkTableMdSortApp { @ViewChild(MdSort) mdSort: MdSort; - @ViewChild('sortHeaderA') mdSortHeaderA: MdSortHeader; - @ViewChild('sortHeaderB') mdSortHeaderB: MdSortHeader; - @ViewChild('sortHeaderC') mdSortHeaderC: MdSortHeader; dataSource = new FakeDataSource(); columnsToRender = ['column_a', 'column_b', 'column_c']; } + + +@Component({ + template: `
A
` +}) +class MdSortHeaderMissingMdSortApp { } + + +@Component({ + template: ` +
+
A
+
A
+
+ ` +}) +class MdSortDuplicateMdSortableIdsApp { } + + +@Component({ + template: ` +
+
A
+
+ ` +}) +class MdSortableMissingIdApp { } diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index f8be33874ceb..8d3312a82476 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -7,9 +7,15 @@ */ import {Directive, EventEmitter, Input, Output} from '@angular/core'; +import {SortDirection} from './sort-direction'; +import {coerceBooleanProperty} from '../core/coercion/boolean-property'; +import {getMdSortDuplicateMdSortableIdError, getMdSortHeaderMissingIdError} from './sort-errors'; export interface MdSortable { id: string; + start: SortDirection; + disableClear: boolean; + reverseOrder: boolean; } export interface Sort { @@ -17,67 +23,122 @@ export interface Sort { direction: SortDirection; } -export type SortDirection = 'ascending' | 'descending' | ''; - +/** Container for MdSortables to manage the sort state and provide default sort parameters. */ @Directive({ selector: '[mdSort], [matSort]', }) export class MdSort { + /** Collection of all registered sortables that this directive manages. */ sortables = new Map(); + /** The id of the most recently sorted MdSortable. */ @Input('mdSortActive') active: string; - @Input('mdSortStart') sortStart: SortDirection = 'ascending'; + /** + * The direction to set when an MdSortable is initially sorted. + * May be overriden by the MdSortable's sort start. + */ + @Input('mdSortStart') start: SortDirection = 'ascending'; + /** The sort direction of the currently active MdSortable. */ @Input('mdSortDirection') direction: SortDirection = ''; - @Input('mdSortDisableClear') disableClear: boolean; - - @Input('mdSortReverseOrder') reverseOrder: boolean; - + /** + * Whether to disable the user from clearing the sort by finishing the sort direction cycle. + * May be overriden by the MdSortable's disable clear input. + */ + @Input('mdSortDisableClear') + get disableClear() { return this._disableClear; } + set disableClear(v) { this._disableClear = coerceBooleanProperty(v); } + private _disableClear: boolean; + + /** + * Whether to reverse the default sort direction order cycle. + * May be overriden by the MdSortable's reverse order input. + */ + @Input('mdSortReverseOrder') + get reverseOrder() { return this._reverseOrder; } + set reverseOrder(v) { this._reverseOrder = coerceBooleanProperty(v); } + private _reverseOrder: boolean; + + /** Event emitted when the user changes either the active sort or sort direction. */ @Output() mdSortChange = new EventEmitter(); + /** + * Register function to be used by the contained MdSortables. Adds the MdSortable to the + * collection of MdSortables. + * @docs-private + */ register(sortable: MdSortable) { + if (!sortable.id) { + throw getMdSortHeaderMissingIdError(); + } + + if (this.sortables.has(sortable.id)) { + throw getMdSortDuplicateMdSortableIdError(sortable.id); + } this.sortables.set(sortable.id, sortable); } + /** + * Unregister function to be used by the contained MdSortables. Removes the MdSortable from the + * collection of contained MdSortables. + * @docs-private + */ unregister(sortable: MdSortable) { - if (this.active === sortable.id) { - this.active = null; - } - this.sortables.delete(sortable.id); } - isSorted(sortable: MdSortable) { - return this.active == sortable.id && this.direction != ''; - } - + /** Sets the active sort id and determines the new sort direction. */ sort(sortable: MdSortable) { if (this.active != sortable.id) { - this.direction = this.sortStart; this.active = sortable.id; + this.direction = this._getInitialDirection(); } else { - let sortDirectionCycle = this._getSortDirectionCycle(); + this.direction = this._getNextSortDirection(); + } + + this.mdSortChange.next({active: this.active, direction: this.direction}); + } - // Take the next direction in the cycle - let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1; - if (nextDirectionIndex >= sortDirectionCycle.length) { nextDirectionIndex = 0; } + /** Returns the first sort direction to be used for the active sortable. */ + _getInitialDirection() { + const sortable = this.sortables.get(this.active); + if (sortable.start) { return sortable.start; } - this.direction = sortDirectionCycle[nextDirectionIndex]; + // If the sortable is reversed, the start direction should be opposite of the MdSort start. + if (sortable.reverseOrder) { + return this.start == 'ascending' ? 'descending' : 'ascending'; } - this.mdSortChange.next({ - active: this.active, - direction: this.direction - }); + return this.start; } - _getSortDirectionCycle(): SortDirection[] { - let sortOrder: SortDirection[] = ['ascending', 'descending']; - if (this.reverseOrder) { sortOrder.reverse(); } - if (!this.disableClear) { sortOrder.push(''); } - - return sortOrder; + /** Returns the next sort direction of the active sortable, checking for potential overrides. */ + _getNextSortDirection(): SortDirection { + const sortable = this.sortables.get(this.active); + + // Get the sort direction cycle with the potential sortable overrides. + const reverseOrder = sortable.reverseOrder != undefined ? + sortable.reverseOrder : + this.reverseOrder; + const disableClear = sortable.disableClear != undefined ? + sortable.disableClear : + this.disableClear; + let sortDirectionCycle = getSortDirectionCycle(reverseOrder, disableClear); + + // Get and return the next direction in the cycle + let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1; + if (nextDirectionIndex >= sortDirectionCycle.length) { nextDirectionIndex = 0; } + return sortDirectionCycle[nextDirectionIndex]; } } + +/** Returns the sort direction cycle to use given the provided parameters of order and clear. */ +function getSortDirectionCycle(reverseOrder: boolean, disableClear: boolean): SortDirection[] { + let sortOrder: SortDirection[] = ['ascending', 'descending']; + if (reverseOrder) { sortOrder.reverse(); } + if (!disableClear) { sortOrder.push(''); } + + return sortOrder; +} From 45a06af962c9248183f435c6ee4d117a4f536059 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 22 Jun 2017 12:09:08 -0700 Subject: [PATCH 06/22] format demo html --- src/demo-app/data-table/data-table-demo.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/demo-app/data-table/data-table-demo.html b/src/demo-app/data-table/data-table-demo.html index 20f9937899b9..9f421aae6d5f 100644 --- a/src/demo-app/data-table/data-table-demo.html +++ b/src/demo-app/data-table/data-table-demo.html @@ -39,7 +39,10 @@ - ID + + ID + {{row.id}} From 1644763f3fa5548900b2a6e9e19f2333e9fd7788 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 22 Jun 2017 12:10:58 -0700 Subject: [PATCH 07/22] add ngif to screenready label --- src/lib/sort/sort-header.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html index 3fc17683bc11..74f6246bc809 100644 --- a/src/lib/sort/sort-header.html +++ b/src/lib/sort/sort-header.html @@ -15,6 +15,6 @@
- + {{_intl.sortDescriptionLabel(id, _sort.direction)}} From 06adfc7c5319bf6062b9af63cce301ce970d1581 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 22 Jun 2017 12:11:23 -0700 Subject: [PATCH 08/22] add new line to scss --- src/lib/sort/sort-header.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sort/sort-header.scss b/src/lib/sort/sort-header.scss index 21d42832a4ae..bbbb9bdf498b 100644 --- a/src/lib/sort/sort-header.scss +++ b/src/lib/sort/sort-header.scss @@ -74,4 +74,4 @@ $mat-sort-header-arrow-thickness: 2px; position: absolute; bottom: 0; right: 0; -} \ No newline at end of file +} From 9cee02b78acf57010ccb4a178412809da3f4a227 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 22 Jun 2017 14:09:43 -0700 Subject: [PATCH 09/22] fix tests --- src/lib/sort/sort.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index 8d3312a82476..24e3205302dd 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -104,6 +104,8 @@ export class MdSort { /** Returns the first sort direction to be used for the active sortable. */ _getInitialDirection() { const sortable = this.sortables.get(this.active); + if (!sortable) { return ''; } + if (sortable.start) { return sortable.start; } // If the sortable is reversed, the start direction should be opposite of the MdSort start. @@ -117,6 +119,7 @@ export class MdSort { /** Returns the next sort direction of the active sortable, checking for potential overrides. */ _getNextSortDirection(): SortDirection { const sortable = this.sortables.get(this.active); + if (!sortable) { return ''; } // Get the sort direction cycle with the potential sortable overrides. const reverseOrder = sortable.reverseOrder != undefined ? From efe57aad3832f2591d9775504eda48900b316a72 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 22 Jun 2017 15:19:52 -0700 Subject: [PATCH 10/22] fix types --- src/demo-app/data-table/person-data-source.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/demo-app/data-table/person-data-source.ts b/src/demo-app/data-table/person-data-source.ts index 32d93aa80320..c0a68a0d52f8 100644 --- a/src/demo-app/data-table/person-data-source.ts +++ b/src/demo-app/data-table/person-data-source.ts @@ -66,7 +66,8 @@ export class PersonDataSource extends DataSource { if (!this._sort.active || this._sort.direction == '') { return data; } return data.sort((a, b) => { - let propertyA, propertyB; + let propertyA: number|string = ''; + let propertyB: number|string = ''; switch (this._sort.active) { case 'userId': [propertyA, propertyB] = [a.id, b.id]; break; From d3ff9f8c5e8e762810bfccb7dbb93242fa3c907e Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 22 Jun 2017 15:25:07 -0700 Subject: [PATCH 11/22] fix types --- src/lib/sort/sort.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sort/sort.spec.ts b/src/lib/sort/sort.spec.ts index 7be4b69669fc..6312cd114192 100644 --- a/src/lib/sort/sort.spec.ts +++ b/src/lib/sort/sort.spec.ts @@ -161,7 +161,7 @@ function testSingleColumnSortDirectionSequence(fixture: ComponentFixture Date: Thu, 22 Jun 2017 16:09:16 -0700 Subject: [PATCH 12/22] shorten coerce import --- src/lib/sort/sort-header.ts | 2 +- src/lib/sort/sort.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts index d9485e2b4de1..f108094679bc 100644 --- a/src/lib/sort/sort-header.ts +++ b/src/lib/sort/sort-header.ts @@ -11,7 +11,7 @@ import {MdSort, MdSortable} from './sort'; import {MdSortIntl} from './sort-intl'; import {CdkColumnDef} from '../core/data-table/cell'; import {SortDirection} from './sort-direction'; -import {coerceBooleanProperty} from '../core/coercion/boolean-property'; +import {coerceBooleanProperty} from '../core'; import {getMdSortHeaderNotContainedWithinMdSortError} from './sort-errors'; /** diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index 24e3205302dd..e1ed45a1cfdd 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -8,7 +8,7 @@ import {Directive, EventEmitter, Input, Output} from '@angular/core'; import {SortDirection} from './sort-direction'; -import {coerceBooleanProperty} from '../core/coercion/boolean-property'; +import {coerceBooleanProperty} from '../core'; import {getMdSortDuplicateMdSortableIdError, getMdSortHeaderMissingIdError} from './sort-errors'; export interface MdSortable { From 1684996dea5b5d9c8ea515a5700cafe7583a3d24 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Fri, 23 Jun 2017 11:14:34 -0700 Subject: [PATCH 13/22] comments --- src/lib/sort/sort-header.html | 2 +- src/lib/sort/sort-header.scss | 11 ++++------- src/lib/sort/sort-header.ts | 18 +++++++++++++----- src/lib/sort/sort.ts | 2 -- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html index 74f6246bc809..df186b6b5c0e 100644 --- a/src/lib/sort/sort-header.html +++ b/src/lib/sort/sort-header.html @@ -1,4 +1,4 @@ -
` }) @@ -208,7 +185,6 @@ class SimpleMdSortApp { start: SortDirection = 'ascending'; direction: SortDirection = ''; disableClear: boolean; - reverseOrder: boolean; @ViewChild(MdSort) mdSort: MdSort; @ViewChild('defaultSortHeaderA') mdSortHeaderDefaultA: MdSortHeader; diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index d3e95f79e966..1699e53c12ce 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -13,9 +13,8 @@ import {getMdSortDuplicateMdSortableIdError, getMdSortHeaderMissingIdError} from export interface MdSortable { id: string; - start: SortDirection; + start: 'ascending' | 'descending'; disableClear: boolean; - reverseOrder: boolean; } export interface Sort { @@ -38,7 +37,7 @@ export class MdSort { * The direction to set when an MdSortable is initially sorted. * May be overriden by the MdSortable's sort start. */ - @Input('mdSortStart') start: SortDirection = 'ascending'; + @Input('mdSortStart') start: 'ascending' | 'descending' = 'ascending'; /** The sort direction of the currently active MdSortable. */ @Input('mdSortDirection') direction: SortDirection = ''; @@ -52,15 +51,6 @@ export class MdSort { set disableClear(v) { this._disableClear = coerceBooleanProperty(v); } private _disableClear: boolean; - /** - * Whether to reverse the default sort direction order cycle. - * May be overriden by the MdSortable's reverse order input. - */ - @Input('mdSortReverseOrder') - get reverseOrder() { return this._reverseOrder; } - set reverseOrder(v) { this._reverseOrder = coerceBooleanProperty(v); } - private _reverseOrder: boolean; - /** Event emitted when the user changes either the active sort or sort direction. */ @Output() mdSortChange = new EventEmitter(); @@ -91,7 +81,7 @@ export class MdSort { sort(sortable: MdSortable) { if (this.active != sortable.id) { this.active = sortable.id; - this.direction = this._getInitialDirection(); + this.direction = sortable.start ? sortable.start : this.start; } else { this.direction = this._getNextSortDirection(); } @@ -99,34 +89,16 @@ export class MdSort { this.mdSortChange.next({active: this.active, direction: this.direction}); } - /** Returns the first sort direction to be used for the active sortable. */ - _getInitialDirection() { - const sortable = this.sortables.get(this.active); - if (!sortable) { return ''; } - - if (sortable.start) { return sortable.start; } - - // If the sortable is reversed, the start direction should be opposite of the MdSort start. - if (sortable.reverseOrder) { - return this.start == 'ascending' ? 'descending' : 'ascending'; - } - - return this.start; - } - /** Returns the next sort direction of the active sortable, checking for potential overrides. */ _getNextSortDirection(): SortDirection { const sortable = this.sortables.get(this.active); if (!sortable) { return ''; } // Get the sort direction cycle with the potential sortable overrides. - const reverseOrder = sortable.reverseOrder != undefined ? - sortable.reverseOrder : - this.reverseOrder; const disableClear = sortable.disableClear != undefined ? sortable.disableClear : this.disableClear; - let sortDirectionCycle = getSortDirectionCycle(reverseOrder, disableClear); + let sortDirectionCycle = getSortDirectionCycle(sortable.start || this.start, disableClear); // Get and return the next direction in the cycle let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1; @@ -136,9 +108,10 @@ export class MdSort { } /** Returns the sort direction cycle to use given the provided parameters of order and clear. */ -function getSortDirectionCycle(reverseOrder: boolean, disableClear: boolean): SortDirection[] { +function getSortDirectionCycle(start: 'ascending' | 'descending', + disableClear: boolean): SortDirection[] { let sortOrder: SortDirection[] = ['ascending', 'descending']; - if (reverseOrder) { sortOrder.reverse(); } + if (start == 'descending') { sortOrder.reverse(); } if (!disableClear) { sortOrder.push(''); } return sortOrder; From 25b4fd6e42cb12846bf156e2e7038b7c882e93c8 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Fri, 23 Jun 2017 16:26:28 -0700 Subject: [PATCH 18/22] button type and onpush --- src/lib/sort/sort-header.html | 4 ++-- src/lib/sort/sort-header.ts | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html index df186b6b5c0e..32318115c491 100644 --- a/src/lib/sort/sort-header.html +++ b/src/lib/sort/sort-header.html @@ -1,6 +1,6 @@ -
- diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts index 0776b671b944..e128618aeebd 100644 --- a/src/lib/sort/sort-header.ts +++ b/src/lib/sort/sort-header.ts @@ -6,7 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ -import {ChangeDetectorRef, Component, Input, Optional} from '@angular/core'; +import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, + Optional, ViewEncapsulation +} from '@angular/core'; import {MdSort, MdSortable} from './sort'; import {MdSortHeaderIntl} from './sort-header-intl'; import {CdkColumnDef} from '../core/data-table/cell'; @@ -31,6 +34,8 @@ import {getMdSortHeaderNotContainedWithinMdSortError} from './sort-errors'; '(click)': '_sort.sort(this)', '[class.mat-sort-header-sorted]': '_isSorted()', }, + encapsulation: ViewEncapsulation.None, + changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdSortHeader implements MdSortable { /** From ec18696a6d49995c6ebfa7d821c5feeb3dd725b8 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 26 Jun 2017 10:35:38 -0700 Subject: [PATCH 19/22] rename sort directions (shorten) --- src/lib/sort/sort-direction.ts | 2 +- src/lib/sort/sort-header.html | 4 ++-- src/lib/sort/sort-header.scss | 4 ++-- src/lib/sort/sort-header.ts | 2 +- src/lib/sort/sort.spec.ts | 32 ++++++++++++++++---------------- src/lib/sort/sort.ts | 10 +++++----- 6 files changed, 27 insertions(+), 27 deletions(-) diff --git a/src/lib/sort/sort-direction.ts b/src/lib/sort/sort-direction.ts index 679ebec3e08d..9a1f2657efa6 100644 --- a/src/lib/sort/sort-direction.ts +++ b/src/lib/sort/sort-direction.ts @@ -6,4 +6,4 @@ * found in the LICENSE file at https://angular.io/license */ -export type SortDirection = 'ascending' | 'descending' | ''; +export type SortDirection = 'asc' | 'desc' | ''; diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html index 32318115c491..d70be440d20d 100644 --- a/src/lib/sort/sort-header.html +++ b/src/lib/sort/sort-header.html @@ -7,8 +7,8 @@
+ [class.mat-sort-header-asc]="_sort.direction == 'asc'" + [class.mat-sort-header-desc]="_sort.direction == 'desc'">
diff --git a/src/lib/sort/sort-header.scss b/src/lib/sort/sort-header.scss index 390b9a5203b0..64c3dbf67d28 100644 --- a/src/lib/sort/sort-header.scss +++ b/src/lib/sort/sort-header.scss @@ -36,12 +36,12 @@ $mat-sort-header-arrow-thickness: 2px; } } -.mat-sort-header-ascending { +.mat-sort-header-asc { display: block; transform: rotate(45deg); } -.mat-sort-header-descending { +.mat-sort-header-desc { display: block; transform: rotate(225deg); top: $mat-sort-header-arrow-thickness; diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts index e128618aeebd..2aab4470d1d3 100644 --- a/src/lib/sort/sort-header.ts +++ b/src/lib/sort/sort-header.ts @@ -48,7 +48,7 @@ export class MdSortHeader implements MdSortable { @Input() arrowPosition: 'before' | 'after' = 'after'; /** Overrides the sort start value of the containing MdSort for this MdSortable. */ - @Input('start') start: 'ascending' | 'descending'; + @Input('start') start: 'asc' | 'desc'; /** Overrides the disable clear value of the containing MdSort for this MdSortable. */ @Input() diff --git a/src/lib/sort/sort.spec.ts b/src/lib/sort/sort.spec.ts index 7dc2c9d0d660..40aa96439a21 100644 --- a/src/lib/sort/sort.spec.ts +++ b/src/lib/sort/sort.spec.ts @@ -64,36 +64,36 @@ describe('MdSort', () => { it('should be able to cycle from asc -> desc from either start point', () => { component.disableClear = true; - component.start = 'ascending'; - testSingleColumnSortDirectionSequence(fixture, ['ascending', 'descending']); + component.start = 'asc'; + testSingleColumnSortDirectionSequence(fixture, ['asc', 'desc']); // Reverse directions - component.start = 'descending'; - testSingleColumnSortDirectionSequence(fixture, ['descending', 'ascending']); + component.start = 'desc'; + testSingleColumnSortDirectionSequence(fixture, ['desc', 'asc']); }); it('should be able to cycle asc -> desc -> [none]', () => { - component.start = 'ascending'; - testSingleColumnSortDirectionSequence(fixture, ['ascending', 'descending', '']); + component.start = 'asc'; + testSingleColumnSortDirectionSequence(fixture, ['asc', 'desc', '']); }); it('should be able to cycle desc -> asc -> [none]', () => { - component.start = 'descending'; - testSingleColumnSortDirectionSequence(fixture, ['descending', 'ascending', '']); + component.start = 'desc'; + testSingleColumnSortDirectionSequence(fixture, ['desc', 'asc', '']); }); it('should reset sort direction when a different column is sorted', () => { component.sort('defaultSortHeaderA'); expect(component.mdSort.active).toBe('defaultSortHeaderA'); - expect(component.mdSort.direction).toBe('ascending'); + expect(component.mdSort.direction).toBe('asc'); component.sort('defaultSortHeaderA'); expect(component.mdSort.active).toBe('defaultSortHeaderA'); - expect(component.mdSort.direction).toBe('descending'); + expect(component.mdSort.direction).toBe('desc'); component.sort('defaultSortHeaderB'); expect(component.mdSort.active).toBe('defaultSortHeaderB'); - expect(component.mdSort.direction).toBe('ascending'); + expect(component.mdSort.direction).toBe('asc'); }); it('should throw an error if an MdSortable is not contained within an MdSort directive', () => { @@ -113,13 +113,13 @@ describe('MdSort', () => { it('should allow let MdSortable override the default sort parameters', () => { testSingleColumnSortDirectionSequence( - fixture, ['ascending', 'descending', '']); + fixture, ['asc', 'desc', '']); testSingleColumnSortDirectionSequence( - fixture, ['descending', 'ascending', ''], 'overrideStart'); + fixture, ['desc', 'asc', ''], 'overrideStart'); testSingleColumnSortDirectionSequence( - fixture, ['ascending', 'descending'], 'overrideDisableClear'); + fixture, ['asc', 'desc'], 'overrideDisableClear'); }); it('should apply the aria-labels to the button', () => { @@ -173,7 +173,7 @@ function testSingleColumnSortDirectionSequence(fixture: ComponentFixture
A
B
-
D
+
D
E
` @@ -182,7 +182,7 @@ class SimpleMdSortApp { latestSortEvent: Sort; active: string; - start: SortDirection = 'ascending'; + start: SortDirection = 'asc'; direction: SortDirection = ''; disableClear: boolean; diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index 1699e53c12ce..aea94f7008a6 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -13,7 +13,7 @@ import {getMdSortDuplicateMdSortableIdError, getMdSortHeaderMissingIdError} from export interface MdSortable { id: string; - start: 'ascending' | 'descending'; + start: 'asc' | 'desc'; disableClear: boolean; } @@ -37,7 +37,7 @@ export class MdSort { * The direction to set when an MdSortable is initially sorted. * May be overriden by the MdSortable's sort start. */ - @Input('mdSortStart') start: 'ascending' | 'descending' = 'ascending'; + @Input('mdSortStart') start: 'asc' | 'desc' = 'desc'; /** The sort direction of the currently active MdSortable. */ @Input('mdSortDirection') direction: SortDirection = ''; @@ -108,10 +108,10 @@ export class MdSort { } /** Returns the sort direction cycle to use given the provided parameters of order and clear. */ -function getSortDirectionCycle(start: 'ascending' | 'descending', +function getSortDirectionCycle(start: 'asc' | 'desc', disableClear: boolean): SortDirection[] { - let sortOrder: SortDirection[] = ['ascending', 'descending']; - if (start == 'descending') { sortOrder.reverse(); } + let sortOrder: SortDirection[] = ['asc', 'desc']; + if (start == 'desc') { sortOrder.reverse(); } if (!disableClear) { sortOrder.push(''); } return sortOrder; From cdeba41db50e5a1b8991472a106f6c6733badc74 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 26 Jun 2017 11:19:58 -0700 Subject: [PATCH 20/22] small changes --- src/demo-app/data-table/data-table-demo.html | 2 +- src/demo-app/data-table/person-data-source.ts | 2 +- .../coordination/unique-selection-dispatcher.ts | 2 +- src/lib/sort/sort-header.ts | 8 ++++++-- src/lib/sort/sort.spec.ts | 8 ++++---- src/lib/sort/sort.ts | 14 ++++++-------- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/src/demo-app/data-table/data-table-demo.html b/src/demo-app/data-table/data-table-demo.html index cebf2b3a5265..36a38c8e5cd9 100644 --- a/src/demo-app/data-table/data-table-demo.html +++ b/src/demo-app/data-table/data-table-demo.html @@ -49,7 +49,7 @@ + md-sort-header start="desc"> Progress diff --git a/src/demo-app/data-table/person-data-source.ts b/src/demo-app/data-table/person-data-source.ts index 7c48ff5802b0..1fde33c2f2d8 100644 --- a/src/demo-app/data-table/person-data-source.ts +++ b/src/demo-app/data-table/person-data-source.ts @@ -81,7 +81,7 @@ export class PersonDataSource extends DataSource { let valueA = isNaN(+propertyA) ? propertyA : +propertyA; let valueB = isNaN(+propertyB) ? propertyB : +propertyB; - return (valueA < valueB ? -1 : 1) * (this._sort.direction == 'ascending' ? 1 : -1); + return (valueA < valueB ? -1 : 1) * (this._sort.direction == 'asc' ? 1 : -1); }); } } diff --git a/src/lib/core/coordination/unique-selection-dispatcher.ts b/src/lib/core/coordination/unique-selection-dispatcher.ts index 6f96f5713f74..0f6ed31d1594 100644 --- a/src/lib/core/coordination/unique-selection-dispatcher.ts +++ b/src/lib/core/coordination/unique-selection-dispatcher.ts @@ -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); diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts index 2aab4470d1d3..c0532587c286 100644 --- a/src/lib/sort/sort-header.ts +++ b/src/lib/sort/sort-header.ts @@ -15,6 +15,7 @@ import {MdSortHeaderIntl} from './sort-header-intl'; import {CdkColumnDef} from '../core/data-table/cell'; import {coerceBooleanProperty} from '../core'; import {getMdSortHeaderNotContainedWithinMdSortError} from './sort-errors'; +import {Subscription} from 'rxjs/Subscription'; /** * Applies sorting behavior (click to change sort) and styles to an element, including an @@ -38,6 +39,8 @@ import {getMdSortHeaderNotContainedWithinMdSortError} from './sort-errors'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class MdSortHeader implements MdSortable { + sortSubscription: Subscription; + /** * ID of this sort header. If used within the context of a CdkColumnDef, this will default to * the column's name. @@ -68,7 +71,7 @@ export class MdSortHeader implements MdSortable { throw getMdSortHeaderNotContainedWithinMdSortError(); } - _sort.mdSortChange.subscribe(() => _changeDetectorRef.markForCheck()); + this.sortSubscription = _sort.mdSortChange.subscribe(() => _changeDetectorRef.markForCheck()); } ngOnInit() { @@ -80,7 +83,8 @@ export class MdSortHeader implements MdSortable { } ngOnDestroy() { - this._sort.unregister(this); + this._sort.deregister(this); + this.sortSubscription.unsubscribe(); } /** Whether this MdSortHeader is currently sorted in either ascending or descending order. */ diff --git a/src/lib/sort/sort.spec.ts b/src/lib/sort/sort.spec.ts index 40aa96439a21..dadc2fe7422b 100644 --- a/src/lib/sort/sort.spec.ts +++ b/src/lib/sort/sort.spec.ts @@ -28,15 +28,15 @@ describe('MdSort', () => { MdSortableMissingIdApp ], }).compileComponents(); + })); + beforeEach(() => { fixture = TestBed.createComponent(SimpleMdSortApp); - component = fixture.componentInstance; - fixture.detectChanges(); - })); + }); - it('should have the sort headers register and unregister themselves', () => { + it('should have the sort headers register and deregister themselves', () => { const sortables = component.mdSort.sortables; expect(sortables.size).toBe(4); expect(sortables.get('defaultSortHeaderA')).toBe(component.mdSortHeaderDefaultA); diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index aea94f7008a6..51bc8bcce0d3 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -37,7 +37,7 @@ export class MdSort { * The direction to set when an MdSortable is initially sorted. * May be overriden by the MdSortable's sort start. */ - @Input('mdSortStart') start: 'asc' | 'desc' = 'desc'; + @Input('mdSortStart') start: 'asc' | 'desc' = 'asc'; /** The sort direction of the currently active MdSortable. */ @Input('mdSortDirection') direction: SortDirection = ''; @@ -73,7 +73,7 @@ export class MdSort { * Unregister function to be used by the contained MdSortables. Removes the MdSortable from the * collection of contained MdSortables. */ - unregister(sortable: MdSortable) { + deregister(sortable: MdSortable) { this.sortables.delete(sortable.id); } @@ -83,22 +83,20 @@ export class MdSort { this.active = sortable.id; this.direction = sortable.start ? sortable.start : this.start; } else { - this.direction = this._getNextSortDirection(); + this.direction = this.getNextSortDirection(sortable); } this.mdSortChange.next({active: this.active, direction: this.direction}); } /** Returns the next sort direction of the active sortable, checking for potential overrides. */ - _getNextSortDirection(): SortDirection { - const sortable = this.sortables.get(this.active); + getNextSortDirection(sortable: MdSortable): SortDirection { if (!sortable) { return ''; } // Get the sort direction cycle with the potential sortable overrides. - const disableClear = sortable.disableClear != undefined ? - sortable.disableClear : - this.disableClear; + const disableClear = sortable.disableClear != null ? sortable.disableClear : this.disableClear; let sortDirectionCycle = getSortDirectionCycle(sortable.start || this.start, disableClear); + console.log(sortDirectionCycle) // Get and return the next direction in the cycle let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1; From eb3556e2ba2e60f75d5b8ed1e2bbd1767e33d6a4 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 26 Jun 2017 11:22:04 -0700 Subject: [PATCH 21/22] remove consolelog --- src/lib/sort/sort.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index 51bc8bcce0d3..c28841f2b596 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -96,7 +96,6 @@ export class MdSort { // Get the sort direction cycle with the potential sortable overrides. const disableClear = sortable.disableClear != null ? sortable.disableClear : this.disableClear; let sortDirectionCycle = getSortDirectionCycle(sortable.start || this.start, disableClear); - console.log(sortDirectionCycle) // Get and return the next direction in the cycle let nextDirectionIndex = sortDirectionCycle.indexOf(this.direction) + 1; From ea51206381891eabf6ba188f33d98e057dc4b951 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Mon, 26 Jun 2017 15:55:08 -0700 Subject: [PATCH 22/22] screenreader --- src/lib/sort/sort-header-intl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sort/sort-header-intl.ts b/src/lib/sort/sort-header-intl.ts index 97878bfc0bdc..a15734ecb94b 100644 --- a/src/lib/sort/sort-header-intl.ts +++ b/src/lib/sort/sort-header-intl.ts @@ -21,6 +21,6 @@ export class MdSortHeaderIntl { /** A label to describe the current sort (visible only to screenreaders). */ sortDescriptionLabel = (id: string, direction: SortDirection) => { - return `Sorted by ${id} ${direction}`; + return `Sorted by ${id} ${direction == 'asc' ? 'ascending' : 'descending'}`; } }