Skip to content

Commit

Permalink
Merge pull request #396 from UiPath/feat/multiselect_grid_filter
Browse files Browse the repository at this point in the history
Feat(grid): multi select filters
  • Loading branch information
CatalinB7 authored Dec 21, 2023
2 parents 3814af0 + 793726d commit 295b94e
Show file tree
Hide file tree
Showing 25 changed files with 625 additions and 348 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# v15.1.0 (2023-12-15)
* **grid** drop unused customMenu directive
* **grid** use suggest for multi filter dropdown
* **suggest** add config input for max selection count

# v15.0.19 (2023-12-20)
* **grid** change visibility-manager isDirty$

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "angular-components",
"version": "15.0.19",
"version": "15.1.0",
"author": {
"name": "UiPath Inc",
"url": "https://uipath.com"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,10 @@ export class UiGridColumnDirective<T> implements OnChanges, OnDestroy {
@Input()
refetch = false;

/**
* Determines the message which appears in the tooltip of an info icon inside the column header.
*
*/
@Input()
description = '';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
import { BehaviorSubject } from 'rxjs';
import { isArray } from 'lodash-es';
import {
BehaviorSubject,
map,
} from 'rxjs';

import {
Directive,
Input,
OnDestroy,
} from '@angular/core';
import { ISuggestValueData } from '@uipath/angular/components/ui-suggest';

import { UiGridFilterDirective } from './ui-grid-filter';

type Arrayify <T> = T extends T ? T[] : never;

export type FilterSingleValue = string | number | boolean;
export type FilterMultiValue = Arrayify<FilterSingleValue>;

/**
* Dropdown option schema.
*
Expand All @@ -18,7 +28,7 @@ export interface IDropdownOption {
* The current dropdown value.
*
*/
value: string | number | boolean;
value: FilterSingleValue | FilterMultiValue;
/**
* The dropdown option label.
*
Expand All @@ -31,6 +41,8 @@ export interface IDropdownOption {
translationKey?: string;
}

export type ISuggestDropdownValueData = ISuggestValueData<IDropdownOption['value']>;

/**
* The dropdown filter definition directive.
*
Expand All @@ -45,7 +57,30 @@ export class UiGridDropdownFilterDirective<T> extends UiGridFilterDirective<T> i
*
*/
@Input()
items?: IDropdownOption[];
set items(value: IDropdownOption[]) {
this._items = value;
this.suggestItems = value.map((item, idx) => ({
id: idx + 1,
text: item.label,
data: item.value,
}));
}
get items() { return this._items!; }

/**
* If true multiple values can be selected in the dropdown filter.
*
*/
@Input()
set multi(value: boolean) {
this._multi = value;
if (value) {
this.selectedFilters$.next([]);
}
}
get multi() {
return this._multi;
}

/**
* If it should display the `All` option.
Expand Down Expand Up @@ -79,6 +114,31 @@ export class UiGridDropdownFilterDirective<T> extends UiGridFilterDirective<T> i
* @ignore
*/
visible$ = new BehaviorSubject(true);
/**
* Current filter value selection.
*
*/
selectedFilters$ = new BehaviorSubject<IDropdownOption['value'] | undefined>(undefined);

/**
* Current filter selection expressed as ISuggestValue.
*
*/
suggestValue$ = this.selectedFilters$.pipe(
map(selection => {
const value = this.suggestItems?.filter(item =>
(isArray(selection) && selection?.some(s => s === item?.data) || selection === item?.data));
return value as ISuggestDropdownValueData[];
}),
);

/**
* Dropdown items expressed as ISuggestDropdownValueData
*/
suggestItems: ISuggestDropdownValueData[] = [];

private _items?: IDropdownOption[];
private _multi = false;

/**
* Updates the dropdown value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ import {
} from 'rxjs/operators';

import { TestBed } from '@angular/core/testing';
import { UiGridFooterDirective } from '@uipath/angular/components/ui-grid';
import {
IDropdownOption,
UiGridFooterDirective,
} from '@uipath/angular/components/ui-grid';
import { ISuggestValue } from '@uipath/angular/components/ui-suggest';

import { UiGridColumnDirective } from '../body/ui-grid-column.directive';
import { IDropdownOption } from '../filters/ui-grid-dropdown-filter.directive';
import { UiGridHeaderDirective } from '../header/ui-grid-header.directive';
import {
FilterManager,
Expand Down Expand Up @@ -200,7 +202,7 @@ describe('Component: UiGrid', () => {
)
.subscribe(filter => {
expect(filter).toBeDefined();
expect(filter!.value).toEqual(columnOptionDefinition.option.value);
expect(filter!.value as IDropdownOption['value']).toEqual(columnOptionDefinition.option.value);
expect(filter!.method).toBe(columnOptionDefinition.column.dropdown!.method!);
});

Expand All @@ -218,7 +220,7 @@ describe('Component: UiGrid', () => {
const [filter] = filters;

expect(filter).toBeDefined();
expect(filter.value).toEqual(columnOptionDefinition.option.value);
expect(filter.value as IDropdownOption['value']).toEqual(columnOptionDefinition.option.value);
expect(filter.method).toBe(columnOptionDefinition.column.dropdown!.method!);
});

Expand All @@ -245,7 +247,7 @@ describe('Component: UiGrid', () => {
const columnOptionDefinition = columnOptionDefinitionList[idx];

expect(filter).toBeDefined();
expect(filter.value).toEqual(columnOptionDefinition.option.value);
expect(filter.value as IDropdownOption['value']).toEqual(columnOptionDefinition.option.value);
expect(filter.method).toBe(columnOptionDefinition.column.dropdown!.method!);
});
});
Expand Down
70 changes: 66 additions & 4 deletions projects/angular/components/ui-grid/src/managers/filter-manager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isArray } from 'lodash-es';
import isEqual from 'lodash-es/isEqual';
import {
BehaviorSubject,
Expand All @@ -12,7 +13,9 @@ import { ISuggestValue } from '@uipath/angular/components/ui-suggest';

import { UiGridColumnDirective } from '../body/ui-grid-column.directive';
import {
FilterMultiValue,
IDropdownOption,
ISuggestDropdownValueData,
UiGridDropdownFilterDirective,
} from '../filters/ui-grid-dropdown-filter.directive';
import { UiGridSearchFilterDirective } from '../filters/ui-grid-search-filter.directive';
Expand Down Expand Up @@ -61,6 +64,23 @@ export class FilterManager<T> {
distinctUntilChanged(),
);

activeFilterValueCount$ = combineLatest([
this.activeCount$,
this.filter$,
]).pipe(
map(([activeCount, filters]) => {
const activeFilterValueCount = activeCount + filters.reduce((acc, filterModel) => {
const filterValue = filterModel?.value;
if (isArray(filterValue) && filterValue.length > 1) {
return acc + filterValue.length - 1;
}
return acc;
}, 0);

return activeFilterValueCount;
}),
);

private _initialFilters: IFilterModel<T>[] | null = null;

constructor(
Expand All @@ -80,11 +100,49 @@ export class FilterManager<T> {
this.filter$.complete();
}

searchableDropdownUpdate = (column?: UiGridColumnDirective<T>, value?: ISuggestValue, selected?: boolean) =>
selectFilters(column: UiGridColumnDirective<T>, selection?: ISuggestDropdownValueData) {
if (selection?.data === undefined) { return this.dropdownUpdate(column, undefined); }

let value = selection.data;
const currentValue = column.dropdown?.selectedFilters$.value;

if (column.dropdown?.multi && isArray(currentValue)) {
const valueAlreadySelected = currentValue.some(v => v === value);
value = (valueAlreadySelected
? (currentValue as []).filter(v => v !== value)
: [...currentValue, value]) as FilterMultiValue;
}

this.dropdownUpdate(column, { value } as IDropdownOption);
}

searchableDropdownUpdate = (column?: UiGridColumnDirective<T>, value?: ISuggestValue | ISuggestValue[], selected?: boolean) => {
if (isArray(value)) {
if (column?.searchableDropdown) {
value.forEach(filterValue => {
column!.searchableDropdown!.updateValue(filterValue, true);
});
this._emitFilterOptions();
}
return;
}

this._updateFilterValue(column, value, selected, this._mapSearchableDropdownItem);
};

dropdownUpdate = (column?: UiGridColumnDirective<T>, value?: IDropdownOption) =>
this._updateFilterValue(column, value, false, this._mapDropdownItem);
dropdownUpdate = (column?: UiGridColumnDirective<T>, dropdownOption?: IDropdownOption) => {
if (column?.dropdown) {
const selectedFilterValue = ((dropdownOption == null || isArray(dropdownOption.value))
? dropdownOption?.value
: [dropdownOption.value]) as IDropdownOption['value'] | undefined;
column.dropdown.selectedFilters$.next(selectedFilterValue);
}

const updateValue = (dropdownOption == null)
? undefined
: dropdownOption;
return this._updateFilterValue(column, updateValue, false, this._mapDropdownItem);
};

searchChange(term: string | undefined, header: UiGridHeaderDirective<T>, footer?: UiGridFooterDirective) {
if (term === header.searchValue) { return; }
Expand Down Expand Up @@ -152,7 +210,7 @@ export class FilterManager<T> {

private _emitFilterOptions = () => {
this.defaultValueDropdownFilters = this._columns
.filter(({ dropdown }) => this._hasFilterValue(dropdown))
.filter(({ dropdown }) => this._hasFilterValue(dropdown) && this._dropdownFilterDirectiveHasValue(dropdown!))
.map(this._mapDropdownItem);

const emptyStateDropdownFilters = this._columns
Expand All @@ -175,6 +233,10 @@ export class FilterManager<T> {
);
};

private _dropdownFilterDirectiveHasValue = (dropdown: UiGridDropdownFilterDirective<T>) =>
dropdown.value?.value !== undefined &&
(!isArray(dropdown.value.value) || dropdown.value.value.length);

private _hasFilterValue = (dropdown?: UiGridSearchFilterDirective<T> | UiGridDropdownFilterDirective<T>) =>
!!dropdown &&
dropdown.value;
Expand Down
Loading

0 comments on commit 295b94e

Please sign in to comment.