From 0c030321314ea35af523d7a88f4eb3c4d2086134 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Mon, 27 Nov 2017 21:03:25 +0100 Subject: [PATCH] feat(sort): add the ability to disable sort toggling Adds the ability for a `mat-sort-header` or `mat-sort` instance to be disabled, preventing the user from changing the sorting direction. Fixes #8622. --- src/demo-app/table/table-demo.html | 11 +++++- src/demo-app/table/table-demo.ts | 1 + src/demo-app/table/table-header-demo.html | 1 + src/lib/sort/sort-header.html | 3 +- src/lib/sort/sort-header.scss | 6 ++- src/lib/sort/sort-header.ts | 33 +++++++++++++--- src/lib/sort/sort.md | 13 +++++-- src/lib/sort/sort.spec.ts | 46 ++++++++++++++++++++++- src/lib/sort/sort.ts | 33 ++++++++++++++-- 9 files changed, 131 insertions(+), 16 deletions(-) diff --git a/src/demo-app/table/table-demo.html b/src/demo-app/table/table-demo.html index 2b5cf19f3eb8..f25906d05ca4 100644 --- a/src/demo-app/table/table-demo.html +++ b/src/demo-app/table/table-demo.html @@ -221,7 +221,13 @@

MatTable With MatTableDataSource Example

+ + +
{{selection.selected.length}} @@ -257,7 +263,10 @@

MatTable With MatTableDataSource Example

- Progress + Progress
{{row.progress}}%
diff --git a/src/demo-app/table/table-demo.ts b/src/demo-app/table/table-demo.ts index 6fd8d9f70a89..e1295ff04f79 100644 --- a/src/demo-app/table/table-demo.ts +++ b/src/demo-app/table/table-demo.ts @@ -43,6 +43,7 @@ export class TableDemo { displayedColumns: UserProperties[] = []; trackByStrategy: TrackByStrategy = 'reference'; changeReferences = false; + progressSortingDisabled = false; highlights = new Set(); wasExpanded = new Set(); diff --git a/src/demo-app/table/table-header-demo.html b/src/demo-app/table/table-header-demo.html index 9ab9197006e6..7971577033db 100644 --- a/src/demo-app/table/table-header-demo.html +++ b/src/demo-app/table/table-header-demo.html @@ -15,5 +15,6 @@ color_lens Toggle Color Column +
diff --git a/src/lib/sort/sort-header.html b/src/lib/sort/sort-header.html index 4061041b1930..142152a8bb5b 100644 --- a/src/lib/sort/sort-header.html +++ b/src/lib/sort/sort-header.html @@ -1,7 +1,8 @@
diff --git a/src/lib/sort/sort-header.scss b/src/lib/sort/sort-header.scss index 088a5429f68e..da39b1a0c910 100644 --- a/src/lib/sort/sort-header.scss +++ b/src/lib/sort/sort-header.scss @@ -8,6 +8,10 @@ $mat-sort-header-arrow-transition: 225ms cubic-bezier(0.4, 0, 0.2, 1); .mat-sort-header-container { display: flex; cursor: pointer; + + .mat-sort-header-disabled & { + cursor: default; + } } .mat-sort-header-position-before { @@ -20,7 +24,7 @@ $mat-sort-header-arrow-transition: 225ms cubic-bezier(0.4, 0, 0.2, 1); display: flex; align-items: center; padding: 0; - cursor: pointer; + cursor: inherit; outline: 0; font: inherit; color: currentColor; diff --git a/src/lib/sort/sort-header.ts b/src/lib/sort/sort-header.ts index 67e395b3906e..62a7babbdce6 100644 --- a/src/lib/sort/sort-header.ts +++ b/src/lib/sort/sort-header.ts @@ -30,10 +30,18 @@ import {MatSort, MatSortable} from './sort'; import {MatSortHeaderIntl} from './sort-header-intl'; import {getSortHeaderNotContainedWithinSortError} from './sort-errors'; import {AnimationCurves, AnimationDurations} from '@angular/material/core'; +import {CanDisable, mixinDisabled} from '@angular/material/core'; + const SORT_ANIMATION_TRANSITION = AnimationDurations.ENTERING + ' ' + AnimationCurves.STANDARD_CURVE; +// Boilerplate for applying mixins to the sort header. +/** @docs-private */ +export class MatSortHeaderBase {} +export const _MatSortHeaderMixinBase = mixinDisabled(MatSortHeaderBase); + + /** * Applies sorting behavior (click to change sort) and styles to an element, including an * arrow to display the current sort direction. @@ -50,12 +58,14 @@ const SORT_ANIMATION_TRANSITION = templateUrl: 'sort-header.html', styleUrls: ['sort-header.css'], host: { - '(click)': '_sort.sort(this)', + '(click)': '_handleClick()', '[class.mat-sort-header-sorted]': '_isSorted()', + '[class.mat-sort-header-disabled]': '_isDisabled()', }, encapsulation: ViewEncapsulation.None, preserveWhitespaces: false, changeDetection: ChangeDetectionStrategy.OnPush, + inputs: ['disabled'], animations: [ trigger('indicator', [ state('asc', style({transform: 'translateY(0px)'})), @@ -93,7 +103,7 @@ const SORT_ANIMATION_TRANSITION = ]) ] }) -export class MatSortHeader implements MatSortable { +export class MatSortHeader extends _MatSortHeaderMixinBase implements MatSortable, CanDisable { private _rerenderSubscription: Subscription; /** @@ -118,13 +128,15 @@ export class MatSortHeader implements MatSortable { changeDetectorRef: ChangeDetectorRef, @Optional() public _sort: MatSort, @Optional() public _cdkColumnDef: CdkColumnDef) { + + super(); + if (!_sort) { throw getSortHeaderNotContainedWithinSortError(); } - this._rerenderSubscription = merge(_sort.sortChange, _intl.changes).subscribe(() => { - changeDetectorRef.markForCheck(); - }); + this._rerenderSubscription = merge(_sort.sortChange, _sort._stateChanges, _intl.changes) + .subscribe(() => changeDetectorRef.markForCheck()); } ngOnInit() { @@ -140,9 +152,20 @@ export class MatSortHeader implements MatSortable { this._rerenderSubscription.unsubscribe(); } + /** Handles click events on the header. */ + _handleClick() { + if (!this._isDisabled()) { + this._sort.sort(this); + } + } + /** Whether this MatSortHeader is currently sorted in either ascending or descending order. */ _isSorted() { return this._sort.active == this.id && (this._sort.direction === 'asc' || this._sort.direction === 'desc'); } + + _isDisabled() { + return this._sort.disabled || this.disabled; + } } diff --git a/src/lib/sort/sort.md b/src/lib/sort/sort.md index 5b331deeb797..8844a88f13c8 100644 --- a/src/lib/sort/sort.md +++ b/src/lib/sort/sort.md @@ -19,14 +19,19 @@ direction to sort (`asc` or `desc`). By default, a sort header starts its sorting at `asc` and then `desc`. Triggering the sort header after `desc` will remove sorting. -To reverse the sort order for all headers, set the `matSortStart` to `desc` on the `matSort` -directive. To reverse the order only for a specific header, set the `start` input only on the header +To reverse the sort order for all headers, set the `matSortStart` to `desc` on the `matSort` +directive. To reverse the order only for a specific header, set the `start` input only on the header instead. -To prevent the user from clearing the sort sort state from an already sorted column, set -`matSortDisableClear` to `true` on the `matSort` to affect all headers, or set `disableClear` to +To prevent the user from clearing the sort sort state from an already sorted column, set +`matSortDisableClear` to `true` on the `matSort` to affect all headers, or set `disableClear` to `true` on a specific header. +#### Disabling sorting + +If you want to prevent the user from changing the sorting order of any column, you can use the +`matSortDisabled` binding on the `mat-sort`, or the `disabled` on an single `mat-sort-header`. + #### Using sort with the mat-table When used on an `mat-table` header, it is not required to set an `mat-sort-header` id on because diff --git a/src/lib/sort/sort.spec.ts b/src/lib/sort/sort.spec.ts index ca214c4ae735..182c5e6c9dd0 100644 --- a/src/lib/sort/sort.spec.ts +++ b/src/lib/sort/sort.spec.ts @@ -109,6 +109,43 @@ describe('MatSort', () => { testSingleColumnSortDirectionSequence(fixture, ['desc', 'asc', '']); }); + it('should allow for the cycling the sort direction to be disabled per column', () => { + const button = fixture.nativeElement.querySelector('#defaultSortHeaderA button'); + + component.sort('defaultSortHeaderA'); + expect(component.matSort.direction).toBe('asc'); + expect(button.getAttribute('disabled')).toBeFalsy(); + + component.disabledColumnSort = true; + fixture.detectChanges(); + + component.sort('defaultSortHeaderA'); + expect(component.matSort.direction).toBe('asc'); + expect(button.getAttribute('disabled')).toBe('true'); + }); + + it('should allow for the cycling the sort direction to be disabled for all columns', () => { + const button = fixture.nativeElement.querySelector('#defaultSortHeaderA button'); + + component.sort('defaultSortHeaderA'); + expect(component.matSort.active).toBe('defaultSortHeaderA'); + expect(component.matSort.direction).toBe('asc'); + expect(button.getAttribute('disabled')).toBeFalsy(); + + component.disableAllSort = true; + fixture.detectChanges(); + + component.sort('defaultSortHeaderA'); + expect(component.matSort.active).toBe('defaultSortHeaderA'); + expect(component.matSort.direction).toBe('asc'); + expect(button.getAttribute('disabled')).toBe('true'); + + component.sort('defaultSortHeaderB'); + expect(component.matSort.active).toBe('defaultSortHeaderA'); + expect(component.matSort.direction).toBe('asc'); + expect(button.getAttribute('disabled')).toBe('true'); + }); + it('should reset sort direction when a different column is sorted', () => { component.sort('defaultSortHeaderA'); expect(component.matSort.active).toBe('defaultSortHeaderA'); @@ -211,11 +248,16 @@ function testSingleColumnSortDirectionSequence(fixture: ComponentFixture -
+
A
@@ -233,6 +275,8 @@ class SimpleMatSortApp { start: SortDirection = 'asc'; direction: SortDirection = ''; disableClear: boolean; + disabledColumnSort = false; + disableAllSort = false; @ViewChild(MatSort) matSort: MatSort; @ViewChild('defaultSortHeaderA') matSortHeaderDefaultA: MatSortHeader; diff --git a/src/lib/sort/sort.ts b/src/lib/sort/sort.ts index 355df422c7b5..4ad410ff887e 100644 --- a/src/lib/sort/sort.ts +++ b/src/lib/sort/sort.ts @@ -6,14 +6,24 @@ * found in the LICENSE file at https://angular.io/license */ -import {Directive, EventEmitter, Input, isDevMode, Output} from '@angular/core'; +import { + Directive, + EventEmitter, + Input, + isDevMode, + Output, + OnChanges, + OnDestroy, +} from '@angular/core'; import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import {CanDisable, mixinDisabled} from '@angular/material/core'; import {SortDirection} from './sort-direction'; import { getSortInvalidDirectionError, getSortDuplicateSortableIdError, getSortHeaderMissingIdError } from './sort-errors'; +import {Subject} from 'rxjs/Subject'; /** Interface for a directive that holds sorting state consumed by `MatSortHeader`. */ export interface MatSortable { @@ -36,15 +46,24 @@ export interface Sort { direction: SortDirection; } +// Boilerplate for applying mixins to MatSort. +/** @docs-private */ +export class MatSortBase {} +export const _MatSortMixinBase = mixinDisabled(MatSortBase); + /** Container for MatSortables to manage the sort state and provide default sort parameters. */ @Directive({ selector: '[matSort]', - exportAs: 'matSort' + exportAs: 'matSort', + inputs: ['disabled: matSortDisabled'] }) -export class MatSort { +export class MatSort extends _MatSortMixinBase implements CanDisable, OnChanges, OnDestroy { /** Collection of all registered sortables that this directive manages. */ sortables = new Map(); + /** Used to notify any child components listening to state changes. */ + _stateChanges = new Subject(); + /** The id of the most recently sorted MatSortable. */ @Input('matSortActive') active: string; @@ -125,6 +144,14 @@ export class MatSort { if (nextDirectionIndex >= sortDirectionCycle.length) { nextDirectionIndex = 0; } return sortDirectionCycle[nextDirectionIndex]; } + + ngOnChanges() { + this._stateChanges.next(); + } + + ngOnDestroy() { + this._stateChanges.complete(); + } } /** Returns the sort direction cycle to use given the provided parameters of order and clear. */