diff --git a/src/index.ts b/src/index.ts index 6c4761a..9e12573 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,4 +9,5 @@ export * from './lib/utils/types/tooltip-positions.type'; export * from './lib/filtering/filter.service'; export * from './lib/filtering/filter-utils'; export * from './lib/filtering/models/filter-group.model'; +export * from './lib/filtering/models/nested-filter.model'; export * from './lib/filtering/models/filter.model'; diff --git a/src/lib/filtering/filter-utils.ts b/src/lib/filtering/filter-utils.ts index 9693860..90aeddc 100644 --- a/src/lib/filtering/filter-utils.ts +++ b/src/lib/filtering/filter-utils.ts @@ -1,7 +1,8 @@ -import { FilterModel } from './models/filter.model'; +import { NestedFilter } from './models/nested-filter.model'; import { FilterGroup } from './models/filter-group.model'; +import { Filter } from './models/filter.model'; -export function processFilters(oldFilters: FilterModel[], newFilters: FilterModel[]) { +export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFilter[]) { copySettings(oldFilters, newFilters); if (newFilters) { newFilters.forEach(filter => { @@ -11,33 +12,33 @@ export function processFilters(oldFilters: FilterModel[], newFilters: FilterMode return newFilters; } -function copySettings(oldFilters: FilterModel[], newFilters: FilterModel[]) { +function copySettings(oldFilters: NestedFilter[], newFilters: NestedFilter[]) { if (oldFilters && newFilters) { for (const oldFilter of oldFilters) { const newFilter = newFilters.find(f => f.key === oldFilter.key); if (newFilter) { newFilter.checked = oldFilter.checked; newFilter.indeterminate = oldFilter.indeterminate; - if (oldFilter.filters && newFilter.filters) copySettings(oldFilter.filters, newFilter.filters); + if (oldFilter.children && newFilter.children) copySettings(oldFilter.children, newFilter.children); } } } } -export function handleCheckedValue(filter: FilterModel) { - if (filter.filters && filter.filters.length) { - filter.checked = filter.filters.reduce((acc, next) => acc && !!next.checked, true); +export function handleCheckedValue(filter: NestedFilter) { + if (filter.children && filter.children.length) { + filter.checked = filter.children.reduce((acc, next) => acc && !!next.checked, true); if (filter.checked) { filter.indeterminate = false; } else { - filter.indeterminate = filter.filters.reduce((acc, next) => acc || !!next.checked, false); + filter.indeterminate = filter.children.reduce((acc, next) => acc || !!next.checked, false); } } else { filter.indeterminate = false; } } -export function checkFilter(entity: any, filters: FilterModel[], validate: Function, validateArgs: any = [], matchAll: boolean = false) { +export function checkFilter(entity: any, filters: NestedFilter[], validate: Function, validateArgs: any = [], matchAll: boolean = false) { const hasChecked = filters.find(f => f.checked); if (validateArgs) { @@ -65,7 +66,7 @@ export function checkFilter(entity: any, filters: FilterModel[], validate: Funct return filterMatched; } -export const keyChecker = (key: string) => (entity: any, filter: FilterModel) => entity[key] === filter.key; +export const keyChecker = (key: string) => (entity: any, filter: NestedFilter) => entity[key] === filter.key; export function getFilteredEntities(entities: T[], filters: FilterGroup[]) { const filteredEntities: T[] = []; @@ -80,3 +81,11 @@ export function getFilteredEntities(entities: T[], filters: FilterGroup[]) { } return filteredEntities; } + +export function flatChildren(filters: NestedFilter[]): Filter[] { + return filters.reduce((acc: Filter[], f) => [...acc, ...(f?.children ?? [])], []); +} + +export function toFlatFilters(groups: FilterGroup[]): Filter[] { + return groups.reduce((acc: Filter[], f) => [...acc, ...f.filters, ...flatChildren(f.filters)], []); +} diff --git a/src/lib/filtering/filter.service.ts b/src/lib/filtering/filter.service.ts index 1ebff7d..5c90d8f 100644 --- a/src/lib/filtering/filter.service.ts +++ b/src/lib/filtering/filter.service.ts @@ -1,9 +1,9 @@ import { Injectable } from '@angular/core'; -import { processFilters } from './filter-utils'; +import { processFilters, toFlatFilters } from './filter-utils'; import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators'; import { FilterGroup } from './models/filter-group.model'; -import { FilterModel } from './models/filter.model'; +import { NestedFilter } from './models/nested-filter.model'; @Injectable() export class FilterService { @@ -21,7 +21,7 @@ export class FilterService { private get _showResetFilters$(): Observable { return this.filterGroups$.pipe( - map(all => this._toFlatFilters(all)), + map(toFlatFilters), map(f => !!f.find(el => el.checked)), distinctUntilChanged() ); @@ -36,7 +36,7 @@ export class FilterService { if (!filters) return console.error(`Cannot find filter group "${filterGroupSlug}"`); let found = filters.find(f => f.key === key); - if (!found) found = filters.map(f => f.filters?.find(ff => ff.key === key))[0]; + if (!found) found = filters.map(f => f.children?.find(ff => ff.key === key))[0]; if (!found) return console.error(`Cannot find filter with key "${key}" in group "${filterGroupSlug}"`); found.checked = !found.checked; @@ -56,7 +56,7 @@ export class FilterService { return this.filterGroups.find(group => group.slug === slug); } - getFilterModels$(filterGroupSlug: string): Observable { + getFilterModels$(filterGroupSlug: string): Observable { return this.getFilterGroup$(filterGroupSlug).pipe(map(f => f?.filters)); } @@ -65,24 +65,14 @@ export class FilterService { } reset(): void { - this.filterGroups.forEach(item => { - item.filters.forEach(child => { - child.checked = false; - child.indeterminate = false; - child.filters?.forEach(f => { - f.checked = false; - f.indeterminate = false; - }); + this.filterGroups.forEach(group => { + group.filters.forEach(filter => { + filter.checked = false; + filter.indeterminate = false; + filter.children?.forEach(f => (f.checked = false)); }); }); this.refresh(); } - - private _toFlatFilters(entities: FilterGroup[]): FilterModel[] { - const flatChildren = (filters: FilterModel[]) => - (filters ?? []).reduce((acc: FilterModel[], f) => [...acc, ...(f?.filters ?? [])], []); - - return entities.reduce((acc: FilterModel[], f) => [...acc, ...f.filters, ...flatChildren(f.filters)], []); - } } diff --git a/src/lib/filtering/models/filter-group.model.ts b/src/lib/filtering/models/filter-group.model.ts index 7309ac4..ab1e50e 100644 --- a/src/lib/filtering/models/filter-group.model.ts +++ b/src/lib/filtering/models/filter-group.model.ts @@ -1,8 +1,8 @@ -import { FilterModel } from './filter.model'; +import { NestedFilter } from './nested-filter.model'; import { TemplateRef } from '@angular/core'; export interface FilterGroup { - filters: FilterModel[]; + filters: NestedFilter[]; readonly slug: string; readonly label?: string; readonly icon?: string; diff --git a/src/lib/filtering/models/filter.model.ts b/src/lib/filtering/models/filter.model.ts index f015950..a7a6dc6 100644 --- a/src/lib/filtering/models/filter.model.ts +++ b/src/lib/filtering/models/filter.model.ts @@ -1,13 +1,10 @@ -export interface FilterModel { +export interface Filter { readonly key: string; checked?: boolean; - indeterminate?: boolean; - expanded?: boolean; matches?: number; - readonly label?: string; + readonly label: string; readonly icon?: string; readonly topLevelFilter?: boolean; - readonly filters?: FilterModel[]; readonly checker?: (obj?: unknown) => boolean; readonly required?: boolean; } diff --git a/src/lib/filtering/models/nested-filter.model.ts b/src/lib/filtering/models/nested-filter.model.ts new file mode 100644 index 0000000..d553c17 --- /dev/null +++ b/src/lib/filtering/models/nested-filter.model.ts @@ -0,0 +1,7 @@ +import { Filter } from './filter.model'; + +export interface NestedFilter extends Filter { + expanded?: boolean; + indeterminate?: boolean; + readonly children?: Filter[]; +} diff --git a/tsconfig.lib.json b/tsconfig.lib.json index bbcc12b..7136ec9 100644 --- a/tsconfig.lib.json +++ b/tsconfig.lib.json @@ -6,6 +6,7 @@ "declaration": true, "declarationMap": true, "inlineSources": true, + "strict": true, "types": [], "lib": ["dom", "es2018"] },