move filters to classes, remove _primaryKey from listing component
This commit is contained in:
parent
93b5ea7f89
commit
8d001e95b5
@ -1,15 +1,15 @@
|
|||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
import { NestedFilter } from './models/nested-filter.model';
|
import { INestedFilter } from "./models/nested-filter.model";
|
||||||
import { FilterGroup } from './models/filter-group.model';
|
import { IFilterGroup } from "./models/filter-group.model";
|
||||||
import { Filter } from './models/filter.model';
|
import { IFilter } from "./models/filter.model";
|
||||||
|
|
||||||
function copySettings(oldFilters: NestedFilter[], newFilters: NestedFilter[]) {
|
function copySettings(oldFilters: INestedFilter[], newFilters: INestedFilter[]) {
|
||||||
if (!oldFilters || !newFilters) {
|
if (!oldFilters || !newFilters) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
oldFilters.forEach(filter => {
|
oldFilters.forEach(filter => {
|
||||||
const newFilter = newFilters.find(f => f.key === filter.key);
|
const newFilter = newFilters.find(f => f.id === filter.id);
|
||||||
if (newFilter) {
|
if (newFilter) {
|
||||||
newFilter.checked = filter.checked;
|
newFilter.checked = filter.checked;
|
||||||
newFilter.indeterminate = filter.indeterminate;
|
newFilter.indeterminate = filter.indeterminate;
|
||||||
@ -20,7 +20,7 @@ function copySettings(oldFilters: NestedFilter[], newFilters: NestedFilter[]) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleCheckedValue(filter: NestedFilter): void {
|
export function handleCheckedValue(filter: INestedFilter): void {
|
||||||
if (filter.children && filter.children.length) {
|
if (filter.children && filter.children.length) {
|
||||||
filter.checked = filter.children.reduce<boolean>((acc, next) => acc && !!next.checked, true);
|
filter.checked = filter.children.reduce<boolean>((acc, next) => acc && !!next.checked, true);
|
||||||
if (filter.checked) {
|
if (filter.checked) {
|
||||||
@ -33,7 +33,7 @@ export function handleCheckedValue(filter: NestedFilter): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFilter[]): NestedFilter[] {
|
export function processFilters(oldFilters: INestedFilter[], newFilters: INestedFilter[]): INestedFilter[] {
|
||||||
copySettings(oldFilters, newFilters);
|
copySettings(oldFilters, newFilters);
|
||||||
if (newFilters) {
|
if (newFilters) {
|
||||||
newFilters.forEach(filter => {
|
newFilters.forEach(filter => {
|
||||||
@ -45,7 +45,7 @@ export function processFilters(oldFilters: NestedFilter[], newFilters: NestedFil
|
|||||||
|
|
||||||
export function checkFilter(
|
export function checkFilter(
|
||||||
entity: unknown,
|
entity: unknown,
|
||||||
filters: NestedFilter[],
|
filters: INestedFilter[],
|
||||||
validate?: (...args: unknown[]) => boolean,
|
validate?: (...args: unknown[]) => boolean,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
validateArgs: any = [],
|
validateArgs: any = [],
|
||||||
@ -72,9 +72,9 @@ export function checkFilter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||||
export const keyChecker = (key: string) => (entity: Record<string, string>, filter: NestedFilter) => entity[key] === filter.key;
|
export const keyChecker = (key: string) => (entity: Record<string, string>, filter: INestedFilter) => entity[key] === filter.id;
|
||||||
|
|
||||||
export function getFilteredEntities<T>(entities: T[], filters: FilterGroup[]): T[] {
|
export function getFilteredEntities<T>(entities: T[], filters: IFilterGroup[]): T[] {
|
||||||
const filteredEntities: T[] = [];
|
const filteredEntities: T[] = [];
|
||||||
entities.forEach(entity => {
|
entities.forEach(entity => {
|
||||||
let add = true;
|
let add = true;
|
||||||
@ -88,10 +88,10 @@ export function getFilteredEntities<T>(entities: T[], filters: FilterGroup[]): T
|
|||||||
return filteredEntities;
|
return filteredEntities;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function flatChildren(filters: NestedFilter[]): Filter[] {
|
export function flatChildren(filters: INestedFilter[]): IFilter[] {
|
||||||
return filters.reduce((acc: Filter[], f) => [...acc, ...(f?.children ?? [])], []);
|
return filters.reduce((acc: IFilter[], f) => [...acc, ...(f?.children ?? [])], []);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toFlatFilters(groups: FilterGroup[]): Filter[] {
|
export function toFlatFilters(groups: IFilterGroup[]): IFilter[] {
|
||||||
return groups.reduce((acc: Filter[], f) => [...acc, ...f.filters, ...flatChildren(f.filters)], []);
|
return groups.reduce((acc: IFilter[], f) => [...acc, ...f.filters, ...flatChildren(f.filters)], []);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,15 +2,15 @@ import { Injectable } from '@angular/core';
|
|||||||
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
import { BehaviorSubject, Observable, Subject } from 'rxjs';
|
||||||
import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
|
import { distinctUntilChanged, map, startWith, switchMap } from 'rxjs/operators';
|
||||||
import { processFilters, toFlatFilters } from './filter-utils';
|
import { processFilters, toFlatFilters } from './filter-utils';
|
||||||
import { FilterGroup } from './models/filter-group.model';
|
import { IFilterGroup } from './models/filter-group.model';
|
||||||
import { NestedFilter } from './models/nested-filter.model';
|
import { INestedFilter } from './models/nested-filter.model';
|
||||||
import { get } from '../utils';
|
import { get } from '../utils';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FilterService {
|
export class FilterService {
|
||||||
readonly showResetFilters$: Observable<boolean>;
|
readonly showResetFilters$: Observable<boolean>;
|
||||||
readonly filterGroups$: Observable<FilterGroup[]>;
|
readonly filterGroups$: Observable<IFilterGroup[]>;
|
||||||
private readonly _filterGroups$ = new BehaviorSubject<FilterGroup[]>([]);
|
private readonly _filterGroups$ = new BehaviorSubject<IFilterGroup[]>([]);
|
||||||
private readonly _refresh$ = new Subject();
|
private readonly _refresh$ = new Subject();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -22,7 +22,7 @@ export class FilterService {
|
|||||||
this.showResetFilters$ = this._showResetFilters$;
|
this.showResetFilters$ = this._showResetFilters$;
|
||||||
}
|
}
|
||||||
|
|
||||||
get filterGroups(): FilterGroup[] {
|
get filterGroups(): IFilterGroup[] {
|
||||||
return Object.values(this._filterGroups$.getValue());
|
return Object.values(this._filterGroups$.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +44,9 @@ export class FilterService {
|
|||||||
return console.error(`Cannot find filter group "${filterGroupSlug}"`);
|
return console.error(`Cannot find filter group "${filterGroupSlug}"`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let found = filters.find(f => f.key === key);
|
let found = filters.find(f => f.id === key);
|
||||||
if (!found) {
|
if (!found) {
|
||||||
[found] = filters.map(f => f.children?.find(ff => ff.key === key));
|
[found] = filters.map(f => f.children?.find(ff => ff.id === key));
|
||||||
}
|
}
|
||||||
if (!found) {
|
if (!found) {
|
||||||
return console.error(`Cannot find filter with key "${key}" in group "${filterGroupSlug}"`);
|
return console.error(`Cannot find filter with key "${key}" in group "${filterGroupSlug}"`);
|
||||||
@ -59,7 +59,7 @@ export class FilterService {
|
|||||||
this.refresh();
|
this.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
addFilterGroup(value: FilterGroup): void {
|
addFilterGroup(value: IFilterGroup): void {
|
||||||
const oldFilters = this.getGroup(value.slug)?.filters;
|
const oldFilters = this.getGroup(value.slug)?.filters;
|
||||||
if (!oldFilters) {
|
if (!oldFilters) {
|
||||||
return this._filterGroups$.next([...this.filterGroups, value]);
|
return this._filterGroups$.next([...this.filterGroups, value]);
|
||||||
@ -69,15 +69,15 @@ export class FilterService {
|
|||||||
this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== newGroup.slug), newGroup]);
|
this._filterGroups$.next([...this.filterGroups.filter(f => f.slug !== newGroup.slug), newGroup]);
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroup(slug: string): FilterGroup | undefined {
|
getGroup(slug: string): IFilterGroup | undefined {
|
||||||
return this.filterGroups.find(group => group.slug === slug);
|
return this.filterGroups.find(group => group.slug === slug);
|
||||||
}
|
}
|
||||||
|
|
||||||
getFilterModels$(filterGroupSlug: string): Observable<NestedFilter[] | undefined> {
|
getFilterModels$(filterGroupSlug: string): Observable<INestedFilter[] | undefined> {
|
||||||
return this.getGroup$(filterGroupSlug).pipe(map(f => f?.filters));
|
return this.getGroup$(filterGroupSlug).pipe(map(f => f?.filters));
|
||||||
}
|
}
|
||||||
|
|
||||||
getGroup$(slug: string): Observable<FilterGroup | undefined> {
|
getGroup$(slug: string): Observable<IFilterGroup | undefined> {
|
||||||
return this.filterGroups$.pipe(get(group => group.slug === slug));
|
return this.filterGroups$.pipe(get(group => group.slug === slug));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,10 @@ export * from './filters.module';
|
|||||||
export * from './filter-utils';
|
export * from './filter-utils';
|
||||||
export * from './filter.service';
|
export * from './filter.service';
|
||||||
|
|
||||||
|
export * from './models/filter';
|
||||||
export * from './models/filter.model';
|
export * from './models/filter.model';
|
||||||
export * from './models/filter-group.model';
|
export * from './models/filter-group.model';
|
||||||
|
export * from './models/nested-filter';
|
||||||
export * from './models/nested-filter.model';
|
export * from './models/nested-filter.model';
|
||||||
|
|
||||||
export * from './popup-filter/popup-filter.component';
|
export * from './popup-filter/popup-filter.component';
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { TemplateRef } from '@angular/core';
|
import { TemplateRef } from '@angular/core';
|
||||||
import { NestedFilter } from './nested-filter.model';
|
import { INestedFilter } from './nested-filter.model';
|
||||||
|
|
||||||
export interface FilterGroup {
|
export interface IFilterGroup {
|
||||||
filters: NestedFilter[];
|
filters: INestedFilter[];
|
||||||
readonly slug: string;
|
readonly slug: string;
|
||||||
readonly label?: string;
|
readonly label?: string;
|
||||||
readonly icon?: string;
|
readonly icon?: string;
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export interface Filter {
|
import { IListable } from '../../listing';
|
||||||
readonly key: string;
|
|
||||||
|
export interface IFilter extends IListable {
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
matches?: number;
|
matches?: number;
|
||||||
readonly label: string;
|
readonly label: string;
|
||||||
|
|||||||
27
src/lib/filtering/models/filter.ts
Normal file
27
src/lib/filtering/models/filter.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { IFilter } from './filter.model';
|
||||||
|
|
||||||
|
export class Filter implements IFilter {
|
||||||
|
readonly id: string;
|
||||||
|
readonly label: string;
|
||||||
|
checked: boolean;
|
||||||
|
readonly required: boolean;
|
||||||
|
readonly topLevelFilter: boolean;
|
||||||
|
matches?: number;
|
||||||
|
readonly icon?: string;
|
||||||
|
readonly checker?: (obj?: unknown) => boolean;
|
||||||
|
|
||||||
|
constructor(filter: IFilter) {
|
||||||
|
this.id = filter.id;
|
||||||
|
this.label = filter.label;
|
||||||
|
this.checked = !!filter.checked;
|
||||||
|
this.matches = filter.matches;
|
||||||
|
this.icon = filter.icon;
|
||||||
|
this.topLevelFilter = !!filter.topLevelFilter;
|
||||||
|
this.checker = filter.checker;
|
||||||
|
this.required = !!filter.required;
|
||||||
|
}
|
||||||
|
|
||||||
|
get searchKey(): string {
|
||||||
|
return this.label;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import { Filter } from './filter.model';
|
import { IFilter } from './filter.model';
|
||||||
|
|
||||||
export interface NestedFilter extends Filter {
|
export interface INestedFilter extends IFilter {
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
indeterminate?: boolean;
|
indeterminate?: boolean;
|
||||||
readonly children?: Filter[];
|
readonly children?: IFilter[];
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/lib/filtering/models/nested-filter.ts
Normal file
15
src/lib/filtering/models/nested-filter.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import { Filter } from './filter';
|
||||||
|
import { IFilter, INestedFilter } from '@iqser/common-ui';
|
||||||
|
|
||||||
|
export class NestedFilter extends Filter implements INestedFilter {
|
||||||
|
expanded: boolean;
|
||||||
|
indeterminate: boolean;
|
||||||
|
readonly children?: IFilter[];
|
||||||
|
|
||||||
|
constructor(nestedFilter: INestedFilter) {
|
||||||
|
super(nestedFilter);
|
||||||
|
this.expanded = !!nestedFilter.expanded;
|
||||||
|
this.indeterminate = !!nestedFilter.indeterminate;
|
||||||
|
this.children = nestedFilter.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,14 +5,14 @@ import { delay, distinctUntilChanged, map, shareReplay } from 'rxjs/operators';
|
|||||||
import { any } from '../../utils';
|
import { any } from '../../utils';
|
||||||
import { handleCheckedValue } from '../filter-utils';
|
import { handleCheckedValue } from '../filter-utils';
|
||||||
import { FilterService } from '../filter.service';
|
import { FilterService } from '../filter.service';
|
||||||
import { FilterGroup } from '../models/filter-group.model';
|
import { IFilterGroup } from '../models/filter-group.model';
|
||||||
import { NestedFilter } from '../models/nested-filter.model';
|
import { INestedFilter } from '../models/nested-filter.model';
|
||||||
import { SearchService } from '../../search';
|
import { SearchService } from '../../search';
|
||||||
import { Filter } from '..';
|
import { IFilter } from '..';
|
||||||
|
|
||||||
const areExpandable = (nestedFilter: NestedFilter) => !!nestedFilter?.children?.length;
|
const areExpandable = (nestedFilter: INestedFilter) => !!nestedFilter?.children?.length;
|
||||||
const atLeastOneIsExpandable = pipe(
|
const atLeastOneIsExpandable = pipe(
|
||||||
map<FilterGroup | undefined, boolean>(group => !!group?.filters.some(areExpandable)),
|
map<IFilterGroup | undefined, boolean>(group => !!group?.filters.some(areExpandable)),
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
shareReplay()
|
shareReplay()
|
||||||
);
|
);
|
||||||
@ -44,13 +44,11 @@ export class PopupFilterComponent implements OnInit {
|
|||||||
readonly expanded = new BehaviorSubject<boolean>(false);
|
readonly expanded = new BehaviorSubject<boolean>(false);
|
||||||
readonly expanded$ = this.expanded.asObservable().pipe(delay(200));
|
readonly expanded$ = this.expanded.asObservable().pipe(delay(200));
|
||||||
|
|
||||||
primaryFilterGroup$!: Observable<FilterGroup | undefined>;
|
primaryFilterGroup$!: Observable<IFilterGroup | undefined>;
|
||||||
secondaryFilterGroup$!: Observable<FilterGroup | undefined>;
|
secondaryFilterGroup$!: Observable<IFilterGroup | undefined>;
|
||||||
primaryFilters$!: Observable<Filter[] | undefined>;
|
primaryFilters$!: Observable<IFilter[] | undefined>;
|
||||||
|
|
||||||
constructor(readonly filterService: FilterService, readonly searchService: SearchService<Filter>) {
|
constructor(readonly filterService: FilterService, readonly searchService: SearchService<IFilter>) {}
|
||||||
this.searchService.setSearchKey('label');
|
|
||||||
}
|
|
||||||
|
|
||||||
private get _hasActiveFilters$() {
|
private get _hasActiveFilters$() {
|
||||||
return combineLatest([this.primaryFilterGroup$, this.secondaryFilterGroup$]).pipe(
|
return combineLatest([this.primaryFilterGroup$, this.secondaryFilterGroup$]).pipe(
|
||||||
@ -70,7 +68,7 @@ export class PopupFilterComponent implements OnInit {
|
|||||||
this.atLeastOneSecondaryFilterIsExpandable$ = atLeastOneIsExpandable(this.secondaryFilterGroup$);
|
this.atLeastOneSecondaryFilterIsExpandable$ = atLeastOneIsExpandable(this.secondaryFilterGroup$);
|
||||||
}
|
}
|
||||||
|
|
||||||
filterCheckboxClicked($event: MouseEvent, nestedFilter: NestedFilter, parent?: NestedFilter): void {
|
filterCheckboxClicked($event: MouseEvent, nestedFilter: INestedFilter, parent?: INestedFilter): void {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
|
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
@ -86,7 +84,7 @@ export class PopupFilterComponent implements OnInit {
|
|||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
nestedFilter.indeterminate = false;
|
nestedFilter.indeterminate = false;
|
||||||
// eslint-disable-next-line no-return-assign,no-param-reassign
|
// eslint-disable-next-line no-return-assign,no-param-reassign
|
||||||
nestedFilter.children?.forEach(f => (f.checked = nestedFilter.checked));
|
nestedFilter.children?.forEach(f => (f.checked = !!nestedFilter.checked));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.filterService.refresh();
|
this.filterService.refresh();
|
||||||
@ -103,7 +101,7 @@ export class PopupFilterComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleFilterExpanded($event: MouseEvent, nestedFilter: NestedFilter): void {
|
toggleFilterExpanded($event: MouseEvent, nestedFilter: INestedFilter): void {
|
||||||
$event.stopPropagation();
|
$event.stopPropagation();
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
nestedFilter.expanded = !nestedFilter.expanded;
|
nestedFilter.expanded = !nestedFilter.expanded;
|
||||||
@ -123,7 +121,7 @@ export class PopupFilterComponent implements OnInit {
|
|||||||
this.filterService.refresh();
|
this.filterService.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get _primaryFilters$(): Observable<Filter[]> {
|
private get _primaryFilters$(): Observable<IFilter[]> {
|
||||||
return combineLatest([this.primaryFilterGroup$, this.searchService.valueChanges$]).pipe(
|
return combineLatest([this.primaryFilterGroup$, this.searchService.valueChanges$]).pipe(
|
||||||
map(([group]) => this.searchService.searchIn(group?.filters ?? []))
|
map(([group]) => this.searchService.searchIn(group?.filters ?? []))
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<div
|
<div
|
||||||
(click)="filterService.toggleFilter('quickFilters', filter.key)"
|
(click)="filterService.toggleFilter('quickFilters', filter.id)"
|
||||||
*ngFor="let filter of quickFilters$ | async"
|
*ngFor="let filter of quickFilters$ | async"
|
||||||
[class.active]="filter.checked"
|
[class.active]="filter.checked"
|
||||||
class="quick-filter"
|
class="quick-filter"
|
||||||
|
|||||||
@ -2,8 +2,8 @@ import { Directive, Injector, OnDestroy } from '@angular/core';
|
|||||||
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
|
||||||
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
import { distinctUntilChanged, map, switchMap } from 'rxjs/operators';
|
||||||
import { FilterService } from '../filtering';
|
import { FilterService } from '../filtering';
|
||||||
import { SortingOrders, SortingService } from '../sorting';
|
import { SortingService } from '../sorting';
|
||||||
import { AutoUnsubscribe, Bind, KeysOf } from '../utils';
|
import { AutoUnsubscribe, KeysOf } from '../utils';
|
||||||
import { SearchService } from '../search';
|
import { SearchService } from '../search';
|
||||||
import { EntitiesService } from './services';
|
import { EntitiesService } from './services';
|
||||||
import { IListable, TableColumnConfig } from './models';
|
import { IListable, TableColumnConfig } from './models';
|
||||||
@ -40,7 +40,6 @@ export abstract class ListingComponent<T extends IListable> extends AutoUnsubscr
|
|||||||
protected constructor(protected readonly _injector: Injector) {
|
protected constructor(protected readonly _injector: Injector) {
|
||||||
super();
|
super();
|
||||||
this.listingMode$ = this._listingMode$.asObservable();
|
this.listingMode$ = this._listingMode$.asObservable();
|
||||||
setTimeout(() => this.setInitialConfig());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get allEntities(): T[] {
|
get allEntities(): T[] {
|
||||||
@ -71,14 +70,6 @@ export abstract class ListingComponent<T extends IListable> extends AutoUnsubscr
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
setInitialConfig(): void {
|
|
||||||
this.sortingService.setSortingOption({
|
|
||||||
column: this._primaryKey,
|
|
||||||
order: SortingOrders.asc
|
|
||||||
});
|
|
||||||
this.searchService.setSearchKey(this._primaryKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleEntitySelected(event: MouseEvent, entity: T): void {
|
toggleEntitySelected(event: MouseEvent, entity: T): void {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
this.entitiesService.select(entity);
|
this.entitiesService.select(entity);
|
||||||
@ -87,9 +78,4 @@ export abstract class ListingComponent<T extends IListable> extends AutoUnsubscr
|
|||||||
isSelected(entity: T): boolean {
|
isSelected(entity: T): boolean {
|
||||||
return this.entitiesService.isSelected(entity);
|
return this.entitiesService.isSelected(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bind()
|
|
||||||
trackByPrimaryKey(index: number, item: T): unknown {
|
|
||||||
return item.searchKey ?? item[this._primaryKey];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, Optional } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, Optional } from '@angular/core';
|
||||||
import { SortingOrders, SortingService } from '../../sorting';
|
import { SortingOrders, SortingService } from '../../sorting';
|
||||||
import { KeysOf, Required } from '../../utils';
|
import { KeysOf, Required } from '../../utils';
|
||||||
|
import { IListable } from '../models';
|
||||||
|
|
||||||
const ifHasRightIcon = <T>(thisArg: TableColumnNameComponent<T>) => !!thisArg.rightIcon;
|
const ifHasRightIcon = <T extends IListable>(thisArg: TableColumnNameComponent<T>) => !!thisArg.rightIcon;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'iqser-table-column-name',
|
selector: 'iqser-table-column-name',
|
||||||
@ -10,7 +11,7 @@ const ifHasRightIcon = <T>(thisArg: TableColumnNameComponent<T>) => !!thisArg.ri
|
|||||||
styleUrls: ['./table-column-name.component.scss'],
|
styleUrls: ['./table-column-name.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class TableColumnNameComponent<T> {
|
export class TableColumnNameComponent<T extends IListable> {
|
||||||
readonly sortingOrders = SortingOrders;
|
readonly sortingOrders = SortingOrders;
|
||||||
|
|
||||||
@Input() @Required() label!: string;
|
@Input() @Required() label!: string;
|
||||||
|
|||||||
@ -22,13 +22,11 @@
|
|||||||
|
|
||||||
<iqser-empty-state *ngIf="listingComponent.noMatch$ | async" [text]="noMatchText"></iqser-empty-state>
|
<iqser-empty-state *ngIf="listingComponent.noMatch$ | async" [text]="noMatchText"></iqser-empty-state>
|
||||||
|
|
||||||
<cdk-virtual-scroll-viewport [class.no-data]="listingComponent.noContent$ | async"
|
<cdk-virtual-scroll-viewport [class.no-data]="listingComponent.noContent$ | async" [itemSize]="itemSize" iqserHasScrollbar>
|
||||||
[itemSize]="itemSize"
|
|
||||||
iqserHasScrollbar>
|
|
||||||
<div
|
<div
|
||||||
(mouseenter)="itemMouseEnterFn && itemMouseEnterFn(entity)"
|
(mouseenter)="itemMouseEnterFn && itemMouseEnterFn(entity)"
|
||||||
(mouseleave)="itemMouseLeaveFn && itemMouseLeaveFn(entity)"
|
(mouseleave)="itemMouseLeaveFn && itemMouseLeaveFn(entity)"
|
||||||
*cdkVirtualFor="let entity of listingComponent.sortedDisplayedEntities$ | async; trackBy: listingComponent.trackByPrimaryKey"
|
*cdkVirtualFor="let entity of listingComponent.sortedDisplayedEntities$ | async"
|
||||||
[ngClass]="getTableItemClasses(entity)"
|
[ngClass]="getTableItemClasses(entity)"
|
||||||
[routerLink]="routerLinkFn && routerLinkFn(entity)"
|
[routerLink]="routerLinkFn && routerLinkFn(entity)"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { BehaviorSubject } from 'rxjs';
|
import { BehaviorSubject } from 'rxjs';
|
||||||
import { KeysOf } from '../utils';
|
import { KeysOf } from '../utils';
|
||||||
|
import { IListable } from '../listing';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SearchService<T> {
|
export class SearchService<T extends IListable> {
|
||||||
private readonly _query$ = new BehaviorSubject('');
|
private readonly _query$ = new BehaviorSubject('');
|
||||||
readonly valueChanges$ = this._query$.asObservable();
|
readonly valueChanges$ = this._query$.asObservable();
|
||||||
private _searchKey!: KeysOf<T>;
|
|
||||||
|
|
||||||
get searchValue(): string {
|
get searchValue(): string {
|
||||||
return this._query$.getValue();
|
return this._query$.getValue();
|
||||||
@ -17,23 +17,16 @@ export class SearchService<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
searchIn(entities: T[]): T[] {
|
searchIn(entities: T[]): T[] {
|
||||||
if (!this._searchKey) {
|
const searchValue = this.searchValue.toLowerCase();
|
||||||
|
if (!searchValue) {
|
||||||
return entities;
|
return entities;
|
||||||
}
|
}
|
||||||
|
return entities.filter(entity => entity.searchKey?.includes(searchValue));
|
||||||
const searchValue = this.searchValue.toLowerCase();
|
|
||||||
return entities.filter(entity => this._searchField(entity).includes(searchValue));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setSearchKey(value: KeysOf<T>): void {
|
setSearchKey(value: KeysOf<T>): void {}
|
||||||
this._searchKey = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset(): void {
|
reset(): void {
|
||||||
this._query$.next('');
|
this._query$.next('');
|
||||||
}
|
}
|
||||||
|
|
||||||
private _searchField(entity: T): string {
|
|
||||||
return ((<unknown>entity[this._searchKey]) as string).toString().toLowerCase();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,14 @@ import { BehaviorSubject } from 'rxjs';
|
|||||||
import { SortingOption } from './models/sorting-option.model';
|
import { SortingOption } from './models/sorting-option.model';
|
||||||
import { SortingOrder, SortingOrders } from './models/sorting-order.type';
|
import { SortingOrder, SortingOrders } from './models/sorting-order.type';
|
||||||
import { KeysOf } from '../utils';
|
import { KeysOf } from '../utils';
|
||||||
|
import { IListable } from '../listing';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SortingService<T> {
|
export class SortingService<T extends IListable> {
|
||||||
private readonly _sortingOption$ = new BehaviorSubject<SortingOption<T> | undefined>(undefined);
|
private readonly _sortingOption$ = new BehaviorSubject<SortingOption<T>>({
|
||||||
|
column: 'searchKey',
|
||||||
|
order: SortingOrders.asc
|
||||||
|
});
|
||||||
readonly sortingOption$ = this._sortingOption$.asObservable();
|
readonly sortingOption$ = this._sortingOption$.asObservable();
|
||||||
|
|
||||||
get sortingOption(): SortingOption<T> | undefined {
|
get sortingOption(): SortingOption<T> | undefined {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user