Skip to content

Commit

Permalink
feat(filter-field): Add partial option.
Browse files Browse the repository at this point in the history
  • Loading branch information
christian-fischer committed May 25, 2020
1 parent 1bd35b0 commit 89e9941
Show file tree
Hide file tree
Showing 18 changed files with 306 additions and 10 deletions.
1 change: 1 addition & 0 deletions apps/demos/src/barista-examples.a11y.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ const BLACKLIST: string[] = [
'filter-field-default-example',
'filter-field-disabled-example',
'filter-field-distinct-example',
'filter-field-partial-example',
'filter-field-programmatic-filters-example',
'filter-field-readonly-non-editable-tags-example',
'filter-field-unique-example',
Expand Down
1 change: 1 addition & 0 deletions apps/dev/src/filter-field/filter-field-demo.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[disabled]="_disabled"
label="Filter by"
clearAllLabel="Clear all"
(inputChange)="inputChange($event)"
(filterChanges)="filterChanges($event)"
(currentFilterChanges)="currentFilterChanges($event)"
></dt-filter-field>
Expand Down
29 changes: 28 additions & 1 deletion apps/dev/src/filter-field/filter-field-demo.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ import {
import { COMPLEX_DATA } from './data';
import { KUBERNETES_DATA } from './kubernetes-data';
import { MULTIDIMENSIONAL_ANALYSIS } from './multidimensional-analysis';
import { TEST_DATA, TEST_DATA_ASYNC, TEST_DATA_ASYNC_2 } from './testdata';
import {
TEST_DATA,
TEST_DATA_ASYNC,
TEST_DATA_ASYNC_2,
TEST_DATA_PARTIAL,
TEST_DATA_PARTIAL_2,
} from './testdata';

// tslint:disable:no-any

Expand Down Expand Up @@ -130,6 +136,7 @@ export class FilterFieldDemo implements AfterViewInit, OnDestroy {
private _activeDataSourceName = 'TEST_DATA';
private _tagChangesSub = Subscription.EMPTY;
private _timerHandle: number;
private _partialFilter: boolean;
_firstTag: DtFilterFieldTag;

_dataSource = new DtFilterFieldDefaultDataSource(TEST_DATA);
Expand Down Expand Up @@ -168,6 +175,26 @@ export class FilterFieldDemo implements AfterViewInit, OnDestroy {
this._timerHandle = setTimeout(() => {
this._dataSource.data = TEST_DATA_ASYNC_2;
}, 2000);
} else if (event.currentFilter[0] === TEST_DATA.autocomplete[4]) {
this._partialFilter = true;
// Simulate async data loading
this._timerHandle = setTimeout(() => {
this._dataSource.data = TEST_DATA_PARTIAL;
}, 2000);
}
}

inputChange(inputText: string): void {
// Cancel current timer if running
clearTimeout(this._timerHandle);

if (this._partialFilter && inputText.length > 0) {
// Explicitly set loading state, as the automatic loading state only works with async, not partial
this.filterField.loading = true;
// Simulate partial data loading where we would pass inputText along
this._timerHandle = setTimeout(() => {
this._dataSource.data = TEST_DATA_PARTIAL_2;
}, 1000);
}
}

Expand Down
27 changes: 27 additions & 0 deletions apps/dev/src/filter-field/testdata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export const TEST_DATA = {
distinct: true,
autocomplete: [],
},
{
name: 'CH (async, partial)',
async: true,
autocomplete: [],
},
{
name: 'Different Country',
suggestions: [{ name: 'IT' }, { name: 'ES' }, { name: 'UK' }],
Expand Down Expand Up @@ -105,3 +110,25 @@ export const TEST_DATA_ASYNC_2 = {
},
],
};

export const TEST_DATA_PARTIAL = {
name: 'CH (async, partial)',
autocomplete: [
{ name: 'Zürich' },
{ name: 'Genf' },
{ name: 'Basel' },
{ name: 'Bern' },
],
partial: true,
};

export const TEST_DATA_PARTIAL_2 = {
name: 'CH (async, partial)',
autocomplete: [
{ name: 'Zug' },
{ name: 'Schaffhausen' },
{ name: 'Luzern' },
{ name: 'St. Gallen' },
],
partial: true,
};
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export class DtQuickFilterDefaultDataSource<
[],
!!data.distinct,
!!data.async,
!!data.partial,
);
def.autocomplete!.optionsOrGroups = this.transformList(
data.autocomplete,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import { buildData } from './reducer';

test('should build the data with an object', () => {
const data = { name: 'Linz' };
const autocomplete = dtAutocompleteDef(data, null, [], false, false);
const autocomplete = dtAutocompleteDef(data, null, [], false, false, false);
expect(buildData(autocomplete)).toMatchObject([data]);
});

test('should build the data with undefined', () => {
const data = undefined;
const autocomplete = dtAutocompleteDef(data, null, [], false, false);
const autocomplete = dtAutocompleteDef(data, null, [], false, false, false);

expect(buildData(autocomplete)).toMatchObject([data]);
});
10 changes: 10 additions & 0 deletions libs/barista-components/filter-field/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ data and apply it to the data source.

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

## Server side filtered partial options

When loading data asynchronously, there might be the need to filter the options
on the server side, as there simply are too many options to return at once. You
can do this by initially setting the `async: true` property. For every response
you get with the server side filtered data, you need to set the `partial: true`
property, when replacing the datasource with the partially loaded data.

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

## Unique free-text or range options

It is possible to set a free-text or range option to be unique. So it can only
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export interface DtFilterFieldDefaultDataSourceAutocomplete {
>;
distinct?: boolean;
async?: boolean;
partial?: boolean;
}

/** Shape of an object to be usable as a free text variant */
Expand Down Expand Up @@ -238,6 +239,7 @@ export class DtFilterFieldDefaultDataSource
[],
!!data.distinct,
!!data.async,
!!data.partial,
);
def.autocomplete!.optionsOrGroups = this.transformList(
data.autocomplete,
Expand Down
25 changes: 22 additions & 3 deletions libs/barista-components/filter-field/src/filter-field-util.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ describe('DtFilterField Util', () => {
[optionDef],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
expect(
Expand All @@ -99,6 +100,7 @@ describe('DtFilterField Util', () => {
[optionDefInGroup],
false,
false,
false,
);
optionDefInGroup.option!.parentAutocomplete = autocompleteDef;
groupDef.group!.parentAutocomplete = autocompleteDef;
Expand All @@ -113,6 +115,7 @@ describe('DtFilterField Util', () => {
[optionDef, optionDefInGroup],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
optionDefInGroup.option!.parentAutocomplete = autocompleteDef;
Expand All @@ -128,6 +131,7 @@ describe('DtFilterField Util', () => {
[optionDef, optionDefInGroup],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
optionDefInGroup.option!.parentAutocomplete = autocompleteDef;
Expand All @@ -143,6 +147,7 @@ describe('DtFilterField Util', () => {
[optionDef],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
expect(
Expand All @@ -156,6 +161,7 @@ describe('DtFilterField Util', () => {
[optionDefInGroup],
false,
false,
false,
);
optionDefInGroup.option!.parentAutocomplete = autocompleteDef;
groupDef.group!.parentAutocomplete = autocompleteDef;
Expand All @@ -170,6 +176,7 @@ describe('DtFilterField Util', () => {
[optionDef, optionDefInGroup],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
optionDefInGroup.option!.parentAutocomplete = autocompleteDef;
Expand Down Expand Up @@ -382,6 +389,7 @@ describe('DtFilterField Util', () => {
[optionDef],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
expect(findDefForSource(optionSource, autocompleteDef)).toBe(optionDef);
Expand Down Expand Up @@ -416,6 +424,7 @@ describe('DtFilterField Util', () => {
[groupDef],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
optionDef.option!.parentGroup = groupDef;
Expand All @@ -441,6 +450,7 @@ describe('DtFilterField Util', () => {
[optionDef],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
expect(findDefForSource(optionSource, autocompleteDef)).toBe(optionDef);
Expand All @@ -463,6 +473,7 @@ describe('DtFilterField Util', () => {
[optionDef],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
expect(findDefForSource({}, autocompleteDef)).toBe(null);
Expand All @@ -488,6 +499,7 @@ describe('DtFilterField Util', () => {
[optionDef],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;

Expand Down Expand Up @@ -528,6 +540,7 @@ describe('DtFilterField Util', () => {
[groupDef],
false,
false,
false,
);
optionDef.option!.parentAutocomplete = autocompleteDef;
optionDef.option!.parentGroup = groupDef;
Expand Down Expand Up @@ -574,6 +587,7 @@ describe('DtFilterField Util', () => {
[innerOptionDef],
false,
false,
false,
);
innerOptionDef.option!.parentAutocomplete = outerOptionAutocompleteDef;
const rootAutocompleteDef = dtAutocompleteDef(
Expand All @@ -582,6 +596,7 @@ describe('DtFilterField Util', () => {
[outerOptionAutocompleteDef],
false,
false,
false,
);
outerOptionAutocompleteDef.option!.parentAutocomplete = rootAutocompleteDef;

Expand Down Expand Up @@ -627,6 +642,7 @@ describe('DtFilterField Util', () => {
[freeTextDef],
false,
false,
false,
);
freeTextDef.option!.parentAutocomplete = autocompleteDef;

Expand Down Expand Up @@ -668,6 +684,7 @@ describe('DtFilterField Util', () => {
[groupDef],
false,
false,
false,
);
freeTextDef.option!.parentGroup = groupDef;
freeTextDef.option!.parentAutocomplete = autocompleteDef;
Expand Down Expand Up @@ -779,6 +796,7 @@ describe('DtFilterField Util', () => {
[],
false,
false,
false,
) as any;
expect(optionSelectedPredicate(optionDef, selectedIds, false)).toBe(
true,
Expand Down Expand Up @@ -806,6 +824,7 @@ describe('DtFilterField Util', () => {
[],
false,
false,
false,
) as any;
expect(optionSelectedPredicate(optionDef, selectedIds, true)).toBe(
false,
Expand Down Expand Up @@ -945,21 +964,21 @@ describe('DtFilterField Util', () => {

describe('defDistinctPredicate', () => {
it('should return true if an autocomplete is async and also an option; it is not selected and not distinct', () => {
let def = dtAutocompleteDef({}, null, [], false, true);
let def = dtAutocompleteDef({}, null, [], false, true, false);
def = dtOptionDef({}, null, 'foo', 'id0', def, null) as any;
const ids = new Set(['id1']);
expect(defDistinctPredicate(def, ids, false)).toBe(true);
});

it('should return true if an autocomplete is async and also an option; it is selected but not distinct', () => {
let def = dtAutocompleteDef({}, null, [], false, true);
let def = dtAutocompleteDef({}, null, [], false, true, false);
def = dtOptionDef({}, def, 'foo', 'id0', def, null) as any;
const ids = new Set(['id0', 'id1']);
expect(defDistinctPredicate(def, ids, false)).toBe(true);
});

it('should return false if an autocomplete is async and also an option; it is selected and distinct', () => {
let def = dtAutocompleteDef({}, null, [], true, true);
let def = dtAutocompleteDef({}, null, [], true, true, false);
def = dtOptionDef({}, null, 'foo', 'id0', def, null) as any;
const ids = new Set(['id0', 'id1']);
expect(defDistinctPredicate(def, ids, false)).toBe(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
isDtRangeDef,
_isDtRangeValue,
isDtRenderType,
isPartialDtAutocompleteDef,
DtOptionDef,
} from './types';

Expand All @@ -63,6 +64,7 @@ export function filterAutocompleteDef(
optionsOrGroups,
def.autocomplete!.distinct,
def.autocomplete!.async,
def.autocomplete!.partial,
)
: null;
}
Expand Down Expand Up @@ -359,7 +361,7 @@ export function findFilterValuesForSources<T>(
if (isLastSource) {
return null;
}
if (isAsyncDtAutocompleteDef(def)) {
if (isAsyncDtAutocompleteDef(def) || isPartialDtAutocompleteDef(def)) {
const asyncDef = asyncDefs.get(def);
if (asyncDef) {
parentDef = asyncDef;
Expand Down
Loading

0 comments on commit 89e9941

Please sign in to comment.