|
|
|
@ -30,6 +30,7 @@ import { coerceArray, coerceBoolean } from '@shared/decorators/coercion'; |
|
|
|
import { Observable, of } from 'rxjs'; |
|
|
|
import { filter, mergeMap, share, tap } from 'rxjs/operators'; |
|
|
|
import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; |
|
|
|
import { isDefined, isUndefined } from '@core/utils'; |
|
|
|
|
|
|
|
export interface StringItemsOption { |
|
|
|
name: string; |
|
|
|
@ -116,6 +117,13 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { |
|
|
|
@coerceArray() |
|
|
|
predefinedValues: StringItemsOption[]; |
|
|
|
|
|
|
|
@Input() |
|
|
|
fetchOptionsFn: (searchText?: string) => Observable<Array<StringItemsOption>>; |
|
|
|
|
|
|
|
@Input() |
|
|
|
@coerceBoolean() |
|
|
|
allowUserValue = false; |
|
|
|
|
|
|
|
get itemsControl(): AbstractControl { |
|
|
|
return this.stringItemsForm.get('items'); |
|
|
|
} |
|
|
|
@ -124,7 +132,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { |
|
|
|
return this.stringItemsForm.get('item'); |
|
|
|
} |
|
|
|
|
|
|
|
onTouched = () => {}; |
|
|
|
private onTouched = () => {}; |
|
|
|
private propagateChange: (value: any) => void = () => {}; |
|
|
|
private dirty = false; |
|
|
|
|
|
|
|
@ -136,7 +144,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { |
|
|
|
} |
|
|
|
|
|
|
|
ngOnInit() { |
|
|
|
if (this.predefinedValues) { |
|
|
|
if (this.predefinedValues || isDefined(this.fetchOptionsFn)) { |
|
|
|
this.filteredValues = this.itemControl.valueChanges |
|
|
|
.pipe( |
|
|
|
tap((value) => { |
|
|
|
@ -147,7 +155,8 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { |
|
|
|
} |
|
|
|
}), |
|
|
|
filter((value) => typeof value === 'string'), |
|
|
|
mergeMap(name => this.fetchValues(name)), |
|
|
|
tap(name => this.searchText = name), |
|
|
|
mergeMap(name => this.fetchOptionsFn ? this.fetchOptionsFn(name) : this.fetchValues(name)), |
|
|
|
share() |
|
|
|
); |
|
|
|
} |
|
|
|
@ -180,7 +189,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { |
|
|
|
if (value != null && value.length > 0) { |
|
|
|
this.modelValue = [...value]; |
|
|
|
this.itemList = []; |
|
|
|
if (this.predefinedValues) { |
|
|
|
if (this.predefinedValues && !this.allowUserValue) { |
|
|
|
value.forEach(item => { |
|
|
|
const findItem = this.predefinedValues.find(option => option.value === item); |
|
|
|
if (findItem) { |
|
|
|
@ -199,19 +208,16 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { |
|
|
|
this.dirty = true; |
|
|
|
} |
|
|
|
|
|
|
|
addItem(event: MatChipInputEvent): void { |
|
|
|
const item = event.value?.trim() ?? ''; |
|
|
|
if (item) { |
|
|
|
if (this.predefinedValues) { |
|
|
|
const findItems = this.predefinedValues |
|
|
|
.filter(value => value.name.toLowerCase().includes(item.toLowerCase())); |
|
|
|
if (findItems.length === 1) { |
|
|
|
this.add(findItems[0]); |
|
|
|
} |
|
|
|
} else { |
|
|
|
this.add({value: item, name: item}); |
|
|
|
} |
|
|
|
addOnBlur(event: FocusEvent) { |
|
|
|
const target: HTMLElement = event.relatedTarget as HTMLElement; |
|
|
|
if (target && target.tagName !== 'MAT-OPTION') { |
|
|
|
this.addItem(this.stringItemInput.nativeElement.value ?? '') |
|
|
|
} |
|
|
|
this.onTouched(); |
|
|
|
} |
|
|
|
|
|
|
|
addOnEnd(event: MatChipInputEvent): void { |
|
|
|
this.addItem(event.value ?? '') |
|
|
|
} |
|
|
|
|
|
|
|
removeItems(item: StringItemsOption) { |
|
|
|
@ -239,6 +245,27 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { |
|
|
|
return values ? values.name : undefined; |
|
|
|
} |
|
|
|
|
|
|
|
private addItem(searchText: string) { |
|
|
|
searchText = searchText.trim(); |
|
|
|
if (searchText) { |
|
|
|
if (this.allowUserValue || !this.predefinedValues && isUndefined(this.fetchOptionsFn)) { |
|
|
|
this.add({value: searchText, name: searchText}); |
|
|
|
} else if (this.predefinedValues) { |
|
|
|
const findItems = this.predefinedValues |
|
|
|
.filter(value => value.name.toLowerCase().includes(searchText.toLowerCase())); |
|
|
|
if (findItems.length === 1) { |
|
|
|
this.add(findItems[0]); |
|
|
|
} |
|
|
|
} else if (isDefined(this.fetchOptionsFn)) { |
|
|
|
this.fetchOptionsFn(searchText).subscribe((findItems) => { |
|
|
|
if (findItems.length === 1) { |
|
|
|
this.add(findItems[0]); |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
private add(item: StringItemsOption) { |
|
|
|
if (!this.modelValue || this.modelValue.indexOf(item.value) === -1) { |
|
|
|
if (!this.modelValue) { |
|
|
|
@ -256,7 +283,6 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit { |
|
|
|
if (!this.predefinedValues?.length) { |
|
|
|
return of([]); |
|
|
|
} |
|
|
|
this.searchText = searchText; |
|
|
|
let result = this.predefinedValues; |
|
|
|
if (searchText && searchText.length) { |
|
|
|
result = this.predefinedValues.filter(option => option.name.toLowerCase().includes(searchText.toLowerCase())); |
|
|
|
|