From 34766f8c7472a2f66537a52ff69b884f1003a1f0 Mon Sep 17 00:00:00 2001 From: Will Martin Date: Thu, 20 Jun 2024 12:42:14 -0400 Subject: [PATCH] [CL-314] improve `TableDataSource` filtering (#9708) --- .../components/src/table/table-data-source.ts | 74 +++++++++++-------- libs/components/src/table/table.mdx | 15 +++- 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/libs/components/src/table/table-data-source.ts b/libs/components/src/table/table-data-source.ts index 3249a42b55..36f5866763 100644 --- a/libs/components/src/table/table-data-source.ts +++ b/libs/components/src/table/table-data-source.ts @@ -10,12 +10,14 @@ export type Sort = { fn?: SortFn; }; +export type FilterFn = (data: T) => boolean; + // Loosely based on CDK TableDataSource // https://github.com/angular/components/blob/main/src/material/table/table-data-source.ts export class TableDataSource extends DataSource { private readonly _data: BehaviorSubject; private readonly _sort: BehaviorSubject; - private readonly _filter = new BehaviorSubject(""); + private readonly _filter = new BehaviorSubject>(null); private readonly _renderData = new BehaviorSubject([]); private _renderChangesSubscription: Subscription | null = null; @@ -55,11 +57,15 @@ export class TableDataSource extends DataSource { 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() { return this._filter.value; } - - set filter(filter: string) { + set filter(filter: string | FilterFn) { this._filter.next(filter); // Normally the `filteredData` is updated by the re-render // subscription, but that won't happen if it's inactive. @@ -95,10 +101,11 @@ export class TableDataSource extends DataSource { } private filterData(data: T[]): T[] { - this.filteredData = - this.filter == null || this.filter === "" - ? data - : data.filter((obj) => this.filterPredicate(obj, this.filter)); + const filter = + typeof this.filter === "string" + ? TableDataSource.simpleStringFilter(this.filter) + : this.filter; + this.filteredData = this.filter == null ? data : data.filter((obj) => filter(obj)); return this.filteredData; } @@ -207,36 +214,39 @@ export class TableDataSource extends DataSource { } /** - * 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 * 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 - * at least one occurrence in that string. By default, the filter string has its whitespace - * trimmed and the match is case-insensitive. May be overridden for a custom implementation of - * 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 + * at least one occurrence in that string. The filter string has its whitespace + * trimmed and the match is case-insensitive. */ - protected filterPredicate(data: T, filter: string): boolean { - // Transform the data into a lowercase string of all property values. - const dataStr = Object.keys(data as unknown as Record) - .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)[key] + "◬"; - }, "") - .toLowerCase(); + static readonly simpleStringFilter = (filter: string): FilterFn => { + return (data: T): boolean => { + if (!filter) { + return true; + } - // Transform the filter by converting it to lowercase and removing whitespace. - const transformedFilter = filter.trim().toLowerCase(); + // Transform the data into a lowercase string of all property values. + const dataStr = Object.keys(data as unknown as Record) + .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)[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; + }; + }; } diff --git a/libs/components/src/table/table.mdx b/libs/components/src/table/table.mdx index 89a341eeaa..f08691f764 100644 --- a/libs/components/src/table/table.mdx +++ b/libs/components/src/table/table.mdx @@ -121,11 +121,20 @@ const sortFn = (a: T, b: T) => (a.id > b.id ? 1 : -1); ### Filtering -The `TableDataSource` supports a rudimentary filtering capability most commonly used to implement a -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. +Filtering is supported by passing a filter predicate to `filter`. ```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"; ```