Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
feat(filter-field): Add input to disable the whole filter field
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikmessner authored and Lukas Holzer committed Feb 12, 2020
1 parent 7b68360 commit d6a6833
Show file tree
Hide file tree
Showing 11 changed files with 289 additions and 14 deletions.
4 changes: 4 additions & 0 deletions apps/dev/src/filter-field/filter-field-demo.component.html
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<dt-filter-field
[loading]="_loading"
[dataSource]="_dataSource"
[disabled]="_disabled"
label="Filter by"
clearAllLabel="Clear all"
(filterChanges)="filterChanges($event)"
Expand Down Expand Up @@ -36,6 +37,9 @@
<button dt-button (click)="_loading = !_loading" variant="secondary">
Toggle loading
</button>
<button dt-button (click)="_disabled = !_disabled" variant="secondary">
Toggle disabled
</button>
<button dt-button (click)="getTagForFilter()">
getTagForFilter
</button>
Expand Down
9 changes: 7 additions & 2 deletions apps/dev/src/filter-field/filter-field-demo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,12 @@ export class FilterFieldDemo implements AfterViewInit, OnDestroy {

private _activeDataSourceName = 'TEST_DATA';
private _tagChangesSub = Subscription.EMPTY;
private _timerHandle: number;
_firstTag: DtFilterFieldTag;

_dataSource = new DtFilterFieldDefaultDataSource<any>(TEST_DATA);
_loading = false;
_disabled = false;

ngAfterViewInit(): void {
this.filterField.currentTags.subscribe(tags => {
Expand All @@ -153,14 +155,17 @@ export class FilterFieldDemo implements AfterViewInit, OnDestroy {
currentFilterChanges(
event: DtFilterFieldCurrentFilterChangeEvent<any>,
): void {
// Cancel current timer if running
clearTimeout(this._timerHandle);

if (event.currentFilter[0] === TEST_DATA.autocomplete[2]) {
// Simulate async data loading
setTimeout(() => {
this._timerHandle = setTimeout(() => {
this._dataSource.data = TEST_DATA_ASYNC;
}, 2000);
} else if (event.currentFilter[0] === TEST_DATA.autocomplete[3]) {
// Simulate async data loading
setTimeout(() => {
this._timerHandle = setTimeout(() => {
this._dataSource.data = TEST_DATA_ASYNC_2;
}, 2000);
}
Expand Down
10 changes: 9 additions & 1 deletion components/filter-field/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class MyModule {}
| `filters` | `any[][]` | | The currently selected filters. This input can also be used to programmatically add filters to the filter-field. |
| `label` | `string` | | The label for the input field. Can be set to something like "Filter by". |
| `loading` | `boolean` | `false` | Whether the filter-field is loading data and should show a loading spinner. |
| `disabled` | `boolean` | `false` | Whether the filter-field is disabled. |
| `aria-label` | `string` | | Sets the value for the Aria-Label attribute. |

## Outputs
Expand Down Expand Up @@ -178,9 +179,16 @@ in the [click dummy](https://invis.io/PCG28RGDUFE).

<ba-live-example name="DtExampleFilterFieldClearall"></ba-live-example>

### Disabled state

By setting the `disabled`-property to true, the whole filter field including all
tags get disabled and therefore cannot be modified by the user.

<ba-live-example name="DtExampleFilterFieldDisabled"></ba-live-example>

### Readonly, non-deletable & non-editable tags

The filter filed creates a `DtFilterFieldTag` for each active filter. You can
The filter field creates a `DtFilterFieldTag` for each active filter. You can
get subscribe to the list of current tags with the `currentTags` observable. By
using the utility method `getTagForFilter` you can find a `DtFilterFieldTag`
instance created for a given filter. After getting the tag instance for your
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class DtFilterFieldTag implements OnDestroy {
/** Whether the tag is disabled. */
// Note: The disabled mixin can not be used here because the CD needs to be triggerd after it has been set
// to reflect the state when programatically setting the property.
@Input()
get disabled(): boolean {
return !this.editable && !this.deletable;
}
Expand Down
1 change: 1 addition & 0 deletions components/filter-field/src/filter-field.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
[dtFilterFieldRangeDisabled]="!(_currentDef && !!_currentDef!.range) || loading"
(keydown)="_handleInputKeyDown($event)"
[value]="_inputValue"
[disabled]="disabled"
/>
<dt-autocomplete
#autocomplete="dtAutocomplete"
Expand Down
11 changes: 9 additions & 2 deletions components/filter-field/src/filter-field.scss
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@ $dt-filter-field-inner-height: 30px;
// of inner interactive elements (like buttons) to be cut off
overflow: visible;

cursor: text;

.dt-focused & {
@include dt-focus-style();
}

&:not(.dt-filter-field-disabled) {
cursor: text;
}

&.dt-filter-field-disabled {
background-color: $gray-100;
color: $disabledcolor;
}
}

.dt-filter-field-body {
Expand Down
116 changes: 116 additions & 0 deletions components/filter-field/src/filter-field.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,122 @@ describe('DtFilterField', () => {
expect(document.activeElement).toBe(input);
});

describe('disabled', () => {
it('should disable the input if filter field is disabled', () => {
// when
filterField.disabled = true;
fixture.detectChanges();

// then
const input = fixture.debugElement.query(
By.css('.dt-filter-field-disabled'),
).nativeElement;
expect(input).toBeTruthy();
});

it('should disable all tags if filter field is disabled', fakeAsync(() => {
// given
fixture.componentInstance.dataSource.data = TEST_DATA_SINGLE_DISTINCT;
fixture.detectChanges();

filterField.focus();
zone.simulateMicrotasksEmpty();
zone.simulateZoneExit();
fixture.detectChanges();

let options = getOptions(overlayContainerElement);

const autOption = options[0];
autOption.click();
zone.simulateMicrotasksEmpty();
fixture.detectChanges();
zone.simulateZoneExit();

options = getOptions(overlayContainerElement);
const viennaOption = options[0];
viennaOption.click();
zone.simulateMicrotasksEmpty();
fixture.detectChanges();

// when
filterField.disabled = true;
fixture.detectChanges();

// then
const subscription = filterField.currentTags.subscribe(tags => {
for (const dtFilterFieldTag of tags) {
expect(dtFilterFieldTag.disabled).toBeTruthy();
}
});
tick();
subscription.unsubscribe();
}));

it('should restore the previous state of tags if filter field gets enabled', fakeAsync(() => {
// given
fixture.componentInstance.dataSource.data = TEST_DATA;
fixture.detectChanges();

// Add filter "AUT - Vienna"
filterField.focus();
zone.simulateMicrotasksEmpty();
zone.simulateZoneExit();
fixture.detectChanges();

let options = getOptions(overlayContainerElement);
const autOption = options[0];
autOption.click();
zone.simulateMicrotasksEmpty();
fixture.detectChanges();
zone.simulateZoneExit();

options = getOptions(overlayContainerElement);
const viennaOption = options[1];
viennaOption.click();
zone.simulateMicrotasksEmpty();
fixture.detectChanges();

// Add filter "USA - Los Angeles"
filterField.focus();
zone.simulateMicrotasksEmpty();
zone.simulateZoneExit();
fixture.detectChanges();

options = getOptions(overlayContainerElement);
const usOption = options[1];
usOption.click();
zone.simulateMicrotasksEmpty();
fixture.detectChanges();
zone.simulateZoneExit();

options = getOptions(overlayContainerElement);
const losAngelesOption = options[0];
losAngelesOption.click();
zone.simulateMicrotasksEmpty();
fixture.detectChanges();

// Disable filter "AUT - Vienna"
const sub1 = filterField.currentTags.subscribe(
tags => (tags[0].disabled = true),
);
tick();
sub1.unsubscribe();

// when
filterField.disabled = true;
filterField.disabled = false;
fixture.detectChanges();

// then
const sub2 = filterField.currentTags.subscribe(tags => {
expect(tags[0].disabled).toBeTruthy();
expect(tags[1].disabled).toBeFalsy();
});
tick();
sub2.unsubscribe();
}));
});

describe('labeling', () => {
it('should create an label with an filter icon', () => {
const label = fixture.debugElement.query(
Expand Down
65 changes: 56 additions & 9 deletions components/filter-field/src/filter-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ import {
ViewEncapsulation,
} from '@angular/core';
import {
fromEvent,
merge,
Observable,
of as observableOf,
ReplaySubject,
Subject,
Subscription,
fromEvent,
merge,
of as observableOf,
} from 'rxjs';
import {
debounceTime,
Expand All @@ -79,6 +79,7 @@ import {
DtAutocompleteTrigger,
} from '@dynatrace/barista-components/autocomplete';
import {
CanDisable,
DT_ERROR_ENTER_ANIMATION,
DT_ERROR_ENTER_DELAYED_ANIMATION,
ErrorStateMatcher,
Expand Down Expand Up @@ -159,8 +160,10 @@ export const DT_FILTER_FIELD_TYPING_DEBOUNCE = 200;
styleUrls: ['filter-field.scss'],
host: {
class: 'dt-filter-field',
'[class.dt-filter-field-disabled]': 'disabled',
'(click)': '_handleHostClick($event)',
},
inputs: ['disabled'],
encapsulation: ViewEncapsulation.Emulated,
preserveWhitespaces: false,
changeDetection: ChangeDetectionStrategy.OnPush,
Expand All @@ -174,7 +177,8 @@ export const DT_FILTER_FIELD_TYPING_DEBOUNCE = 200;
]),
],
})
export class DtFilterField<T> implements AfterViewInit, OnDestroy, OnChanges {
export class DtFilterField<T>
implements CanDisable, AfterViewInit, OnDestroy, OnChanges {
/** Label for the filter field (e.g. "Filter by"). Will be placed next to the filter icon. */
@Input() label = '';

Expand Down Expand Up @@ -234,6 +238,45 @@ export class DtFilterField<T> implements AfterViewInit, OnDestroy, OnChanges {
/** Set the Aria-Label attribute */
@Input('aria-label') ariaLabel = '';

/** Whether the filter field is disabled. */
@Input()
get disabled(): boolean {
return this._disabled;
}
set disabled(value: boolean) {
// tslint:disable: deprecation
const coerced = coerceBooleanProperty(value);
if (coerced !== this._disabled) {
this._disabled = coerced;

if (!this.tags || this.tags.length === 0) {
return;
}

if (this._disabled) {
this.closeFilterPanels();

this.tags.forEach(item => {
this._previousTagDisabledState.set(item, item.disabled);
item.disabled = this._disabled;
});
} else {
this.tags.forEach(
item => (item.disabled = !!this._previousTagDisabledState.get(item)),
);
}

this._changeDetectorRef.markForCheck();
}

// tslint:enable: deprecation
}
private _disabled = false;
private _previousTagDisabledState: Map<DtFilterFieldTag, boolean> = new Map<
DtFilterFieldTag,
boolean
>();

/** Emits an event with the current value of the input field every time the user types. */
@Output() readonly inputChange = new EventEmitter<string>();

Expand Down Expand Up @@ -554,11 +597,7 @@ export class DtFilterField<T> implements AfterViewInit, OnDestroy, OnChanges {
);
}
} else if (keyCode === ESCAPE || (keyCode === UP_ARROW && event.altKey)) {
this._autocompleteTrigger.closePanel();
this._filterfieldRangeTrigger.closePanel();
if (this._editModeStashedValue) {
this._cancelEditMode();
}
this.closeFilterPanels();
} else {
if (this._inputFieldKeyboardLocked) {
return;
Expand Down Expand Up @@ -1173,5 +1212,13 @@ export class DtFilterField<T> implements AfterViewInit, OnDestroy, OnChanges {
});
this._switchToRootDef(false);
}

private closeFilterPanels(): void {
this._autocompleteTrigger.closePanel();
this._filterfieldRangeTrigger.closePanel();
if (this._editModeStashedValue) {
this._cancelEditMode();
}
}
}
// tslint:disable:max-file-line-count
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<dt-filter-field
[dataSource]="_dataSource"
[filters]="_filters"
[disabled]="true"
label="Filter by"
></dt-filter-field>
Loading

0 comments on commit d6a6833

Please sign in to comment.