Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sort): add the ability to disable sort toggling #8643

Merged
merged 1 commit into from
Dec 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/demo-app/table/table-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,13 @@ <h3>MatTable With MatTableDataSource Example</h3>

<table-header-demo (shiftColumns)="displayedColumns.push(displayedColumns.shift())"
(toggleColorColumn)="toggleColorColumn()" *ngIf="selection.isEmpty()">

<button mat-menu-item (click)="progressSortingDisabled = !progressSortingDisabled">
<mat-icon>sort</mat-icon>
Toggle Progress Sorting
</button>
</table-header-demo>

<div class="example-header example-selection-header"
*ngIf="!selection.isEmpty()">
{{selection.selected.length}}
Expand Down Expand Up @@ -257,7 +263,10 @@ <h3>MatTable With MatTableDataSource Example</h3>

<!-- Column Definition: Progress -->
<ng-container matColumnDef="progress">
<mat-header-cell *matHeaderCellDef mat-sort-header> Progress </mat-header-cell>
<mat-header-cell
*matHeaderCellDef
[disabled]="progressSortingDisabled"
mat-sort-header> Progress </mat-header-cell>
<mat-cell *matCellDef="let row">
<div class="demo-progress-stat">{{row.progress}}%</div>
<div class="demo-progress-indicator-container">
Expand Down
1 change: 1 addition & 0 deletions src/demo-app/table/table-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class TableDemo {
displayedColumns: UserProperties[] = [];
trackByStrategy: TrackByStrategy = 'reference';
changeReferences = false;
progressSortingDisabled = false;
highlights = new Set<string>();
wasExpanded = new Set<UserData>();

Expand Down
1 change: 1 addition & 0 deletions src/demo-app/table/table-header-demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
<mat-icon>color_lens</mat-icon>
Toggle Color Column
</button>
<ng-content></ng-content>
</mat-menu>
</div>
3 changes: 2 additions & 1 deletion src/lib/sort/sort-header.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<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)">
[attr.aria-label]="_intl.sortButtonLabel(id)"
[attr.disabled]="_isDisabled() || null">
<ng-content></ng-content>
</button>

Expand Down
6 changes: 5 additions & 1 deletion src/lib/sort/sort-header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down
33 changes: 28 additions & 5 deletions src/lib/sort/sort-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)'})),
Expand Down Expand Up @@ -93,7 +103,7 @@ const SORT_ANIMATION_TRANSITION =
])
]
})
export class MatSortHeader implements MatSortable {
export class MatSortHeader extends _MatSortHeaderMixinBase implements MatSortable, CanDisable {
private _rerenderSubscription: Subscription;

/**
Expand All @@ -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() {
Expand All @@ -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;
}
}
13 changes: 9 additions & 4 deletions src/lib/sort/sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
46 changes: 45 additions & 1 deletion src/lib/sort/sort.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -211,11 +248,16 @@ function testSingleColumnSortDirectionSequence(fixture: ComponentFixture<SimpleM
template: `
<div matSort
[matSortActive]="active"
[matSortDisabled]="disableAllSort"
[matSortStart]="start"
[matSortDirection]="direction"
[matSortDisableClear]="disableClear"
(matSortChange)="latestSortEvent = $event">
<div id="defaultSortHeaderA" #defaultSortHeaderA mat-sort-header="defaultSortHeaderA">
<div
id="defaultSortHeaderA"
#defaultSortHeaderA
mat-sort-header="defaultSortHeaderA"
[disabled]="disabledColumnSort">
A
</div>
<div id="defaultSortHeaderB" #defaultSortHeaderB mat-sort-header="defaultSortHeaderB">
Expand All @@ -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;
Expand Down
33 changes: 30 additions & 3 deletions src/lib/sort/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<string, MatSortable>();

/** Used to notify any child components listening to state changes. */
_stateChanges = new Subject<void>();

/** The id of the most recently sorted MatSortable. */
@Input('matSortActive') active: string;

Expand Down Expand Up @@ -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. */
Expand Down