blob: 1d1267705add93d8fb1cdcc931256519a43c58c1 [file] [log] [blame]
/**
* 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);
}
}