mirror of
https://github.com/bitwarden/browser
synced 2025-12-12 14:23:32 +00:00
[CL-314] improve TableDataSource filtering (#9708)
This commit is contained in:
@@ -10,12 +10,14 @@ export type Sort = {
|
|||||||
fn?: SortFn;
|
fn?: SortFn;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FilterFn<T> = (data: T) => boolean;
|
||||||
|
|
||||||
// Loosely based on CDK TableDataSource
|
// Loosely based on CDK TableDataSource
|
||||||
// https://github.com/angular/components/blob/main/src/material/table/table-data-source.ts
|
// https://github.com/angular/components/blob/main/src/material/table/table-data-source.ts
|
||||||
export class TableDataSource<T> extends DataSource<T> {
|
export class TableDataSource<T> extends DataSource<T> {
|
||||||
private readonly _data: BehaviorSubject<T[]>;
|
private readonly _data: BehaviorSubject<T[]>;
|
||||||
private readonly _sort: BehaviorSubject<Sort>;
|
private readonly _sort: BehaviorSubject<Sort>;
|
||||||
private readonly _filter = new BehaviorSubject<string>("");
|
private readonly _filter = new BehaviorSubject<string | FilterFn<T>>(null);
|
||||||
private readonly _renderData = new BehaviorSubject<T[]>([]);
|
private readonly _renderData = new BehaviorSubject<T[]>([]);
|
||||||
private _renderChangesSubscription: Subscription | null = null;
|
private _renderChangesSubscription: Subscription | null = null;
|
||||||
|
|
||||||
@@ -55,11 +57,15 @@ export class TableDataSource<T> extends DataSource<T> {
|
|||||||
return this._sort.value;
|
return this._sort.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter to apply to the `data`.
|
||||||
|
*
|
||||||
|
* If a string is provided, it will be converted to a filter using {@link simpleStringFilter}
|
||||||
|
**/
|
||||||
get filter() {
|
get filter() {
|
||||||
return this._filter.value;
|
return this._filter.value;
|
||||||
}
|
}
|
||||||
|
set filter(filter: string | FilterFn<T>) {
|
||||||
set filter(filter: string) {
|
|
||||||
this._filter.next(filter);
|
this._filter.next(filter);
|
||||||
// Normally the `filteredData` is updated by the re-render
|
// Normally the `filteredData` is updated by the re-render
|
||||||
// subscription, but that won't happen if it's inactive.
|
// subscription, but that won't happen if it's inactive.
|
||||||
@@ -95,10 +101,11 @@ export class TableDataSource<T> extends DataSource<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private filterData(data: T[]): T[] {
|
private filterData(data: T[]): T[] {
|
||||||
this.filteredData =
|
const filter =
|
||||||
this.filter == null || this.filter === ""
|
typeof this.filter === "string"
|
||||||
? data
|
? TableDataSource.simpleStringFilter(this.filter)
|
||||||
: data.filter((obj) => this.filterPredicate(obj, this.filter));
|
: this.filter;
|
||||||
|
this.filteredData = this.filter == null ? data : data.filter((obj) => filter(obj));
|
||||||
|
|
||||||
return this.filteredData;
|
return this.filteredData;
|
||||||
}
|
}
|
||||||
@@ -207,36 +214,39 @@ export class TableDataSource<T> extends DataSource<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copied from https://github.com/angular/components/blob/main/src/material/table/table-data-source.ts
|
* Modified from https://github.com/angular/components/blob/main/src/material/table/table-data-source.ts
|
||||||
* License: MIT
|
* License: MIT
|
||||||
* Copyright (c) 2022 Google LLC.
|
* Copyright (c) 2022 Google LLC.
|
||||||
*
|
*
|
||||||
* Checks if a data object matches the data source's filter string. By default, each data object
|
* @param filter the string to search for
|
||||||
|
* @returns a function that checks if a data object matches the provided `filter` string. Each data object
|
||||||
* is converted to a string of its properties and returns true if the filter has
|
* is converted to a string of its properties and returns true if the filter has
|
||||||
* at least one occurrence in that string. By default, the filter string has its whitespace
|
* at least one occurrence in that string. The filter string has its whitespace
|
||||||
* trimmed and the match is case-insensitive. May be overridden for a custom implementation of
|
* trimmed and the match is case-insensitive.
|
||||||
* filter matching.
|
|
||||||
* @param data Data object used to check against the filter.
|
|
||||||
* @param filter Filter string that has been set on the data source.
|
|
||||||
* @returns Whether the filter matches against the data
|
|
||||||
*/
|
*/
|
||||||
protected filterPredicate(data: T, filter: string): boolean {
|
static readonly simpleStringFilter = <T>(filter: string): FilterFn<T> => {
|
||||||
// Transform the data into a lowercase string of all property values.
|
return (data: T): boolean => {
|
||||||
const dataStr = Object.keys(data as unknown as Record<string, any>)
|
if (!filter) {
|
||||||
.reduce((currentTerm: string, key: string) => {
|
return true;
|
||||||
// Use an obscure Unicode character to delimit the words in the concatenated string.
|
}
|
||||||
// This avoids matches where the values of two columns combined will match the user's query
|
|
||||||
// (e.g. `Flute` and `Stop` will match `Test`). The character is intended to be something
|
|
||||||
// that has a very low chance of being typed in by somebody in a text field. This one in
|
|
||||||
// particular is "White up-pointing triangle with dot" from
|
|
||||||
// https://en.wikipedia.org/wiki/List_of_Unicode_characters
|
|
||||||
return currentTerm + (data as unknown as Record<string, any>)[key] + "◬";
|
|
||||||
}, "")
|
|
||||||
.toLowerCase();
|
|
||||||
|
|
||||||
// Transform the filter by converting it to lowercase and removing whitespace.
|
// Transform the data into a lowercase string of all property values.
|
||||||
const transformedFilter = filter.trim().toLowerCase();
|
const dataStr = Object.keys(data as unknown as Record<string, any>)
|
||||||
|
.reduce((currentTerm: string, key: string) => {
|
||||||
|
// Use an obscure Unicode character to delimit the words in the concatenated string.
|
||||||
|
// This avoids matches where the values of two columns combined will match the user's query
|
||||||
|
// (e.g. `Flute` and `Stop` will match `Test`). The character is intended to be something
|
||||||
|
// that has a very low chance of being typed in by somebody in a text field. This one in
|
||||||
|
// particular is "White up-pointing triangle with dot" from
|
||||||
|
// https://en.wikipedia.org/wiki/List_of_Unicode_characters
|
||||||
|
return currentTerm + (data as unknown as Record<string, any>)[key] + "◬";
|
||||||
|
}, "")
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
return dataStr.indexOf(transformedFilter) != -1;
|
// Transform the filter by converting it to lowercase and removing whitespace.
|
||||||
}
|
const transformedFilter = filter.trim().toLowerCase();
|
||||||
|
|
||||||
|
return dataStr.indexOf(transformedFilter) != -1;
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,11 +121,20 @@ const sortFn = (a: T, b: T) => (a.id > b.id ? 1 : -1);
|
|||||||
|
|
||||||
### Filtering
|
### Filtering
|
||||||
|
|
||||||
The `TableDataSource` supports a rudimentary filtering capability most commonly used to implement a
|
Filtering is supported by passing a filter predicate to `filter`.
|
||||||
search function. It works by converting each entry into a string of it's properties. The string is
|
|
||||||
then compared against the filter value using a simple `indexOf`check.
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
|
dataSource.filter = (data) => data.orgType === "family";
|
||||||
|
```
|
||||||
|
|
||||||
|
Rudimentary string filtering is supported out of the box with `TableDataSource.simpleStringFilter`.
|
||||||
|
It works by converting each entry into a string of it's properties. The provided string is then
|
||||||
|
compared against the filter value using a simple `indexOf` check. For convienence, you can also just
|
||||||
|
pass a string directly.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
dataSource.filter = TableDataSource.simpleStringFilter("search value");
|
||||||
|
// or
|
||||||
dataSource.filter = "search value";
|
dataSource.filter = "search value";
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user