| /** | |
| * FilterArray is a class for filtering a live array of rich objects based on a | |
| * case-insensitive substring search. | |
| * | |
| * The array being monitored needs to be passed to updateArray() every time it | |
| * changes. | |
| * | |
| * Each time the substring being filtered for changes, call filterChanged(), and | |
| * pass it in. This is typically done from an event listener on an input field. | |
| * | |
| * Initially, the FilterArray will represent an empty list. If updateArray() is | |
| * called before filterChanged(), it will represent an unfiltered view of the | |
| * list. Then, once filterChanged() is called, filtration will begin. | |
| * | |
| * The matchingValues() function is expected to be used in a lit-html template | |
| * and returns all the matches for the filter. | |
| * | |
| * The string passed into filterChanged is split on spaces and each word is | |
| * searched on, i.e. the searching is an AND of all the words in the search | |
| * string. | |
| */ | |
| export class FilterArray<T> { | |
| private filterWords: string[] = []; | |
| private elements: T[] = []; | |
| /** Lowercase-folded JSON representations of my elements, good for searching */ | |
| private jsonifiedElements: string[] = []; | |
| /** | |
| * Call this every time the array being filtered changes. | |
| * | |
| * @param arr - The array to be filtered | |
| */ | |
| updateArray(arr: T[]): void { | |
| this.elements = arr; | |
| this.jsonifiedElements = arr.map((e) => JSON.stringify(e).toLowerCase()); | |
| } | |
| /** | |
| * Returns the elements of the array passed in via updateArray() that match | |
| * the current filter. | |
| * | |
| * If updateArray() hasn't yet been called, return an empty array. If no | |
| * filter has been set (via connect()), return unfiltered results. | |
| * | |
| * Note that this currently searches each JSONified array element as a single | |
| * string, so JSON delimiters like { and " do get matched, and matches can | |
| * span properties. | |
| */ | |
| matchingValues(): T[] { | |
| const ret: T[] = []; | |
| if (this.filterWords.length === 0) { | |
| return [...this.elements]; | |
| } | |
| this.jsonifiedElements.forEach((v, i) => { | |
| if (this.filterWords.every((filt) => v.includes(filt))) { | |
| ret.push(this.elements[i]); | |
| } | |
| }); | |
| return ret; | |
| } | |
| /** Inform me that the string I'm filtering for has changed. */ | |
| filterChanged(value: string): void { | |
| this.filterWords = value | |
| .toLowerCase() | |
| .split(' ') | |
| .filter((s: string) => s.length > 0); | |
| } | |
| } |