Merge pull request #13090 from vvlladd28/improvement/string-item/fetch-option
Add fetch function for string items list; fix double item addition on blur
This commit is contained in:
commit
e27a2121b6
@ -30,7 +30,7 @@
|
|||||||
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
|
<mat-icon matChipRemove *ngIf="!disabled">close</mat-icon>
|
||||||
</mat-chip-row>
|
</mat-chip-row>
|
||||||
<input matInput type="text"
|
<input matInput type="text"
|
||||||
(blur)="onTouched()"
|
(blur)="addOnBlur($event)"
|
||||||
placeholder="{{ placeholder }}"
|
placeholder="{{ placeholder }}"
|
||||||
style="max-width: 300px;min-width: 250px"
|
style="max-width: 300px;min-width: 250px"
|
||||||
#stringItemInput
|
#stringItemInput
|
||||||
@ -40,8 +40,7 @@
|
|||||||
[matChipInputFor]="itemsChipList"
|
[matChipInputFor]="itemsChipList"
|
||||||
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes"
|
||||||
matAutocompleteOrigin
|
matAutocompleteOrigin
|
||||||
matChipInputAddOnBlur
|
(matChipInputTokenEnd)="addOnEnd($event)"
|
||||||
(matChipInputTokenEnd)="addItem($event)"
|
|
||||||
[matAutocompleteConnectedTo]="origin"
|
[matAutocompleteConnectedTo]="origin"
|
||||||
[matAutocomplete]="stringItemAutocomplete"
|
[matAutocomplete]="stringItemAutocomplete"
|
||||||
[matAutocompleteDisabled]="!predefinedValues?.length">
|
[matAutocompleteDisabled]="!predefinedValues?.length">
|
||||||
@ -49,12 +48,11 @@
|
|||||||
<mat-autocomplete #stringItemAutocomplete="matAutocomplete"
|
<mat-autocomplete #stringItemAutocomplete="matAutocomplete"
|
||||||
[displayWith]="displayValueFn"
|
[displayWith]="displayValueFn"
|
||||||
class="tb-autocomplete">
|
class="tb-autocomplete">
|
||||||
<mat-option *ngFor="let value of filteredValues | async" [value]="value">
|
@for (value of filteredValues | async; track value.value) {
|
||||||
<span [innerHTML]="value.name | highlight:searchText"></span>
|
<mat-option [value]="value"><span [innerHTML]="value.name | highlight:searchText"></span></mat-option>
|
||||||
</mat-option>
|
} @empty {
|
||||||
<mat-option *ngIf="!(filteredValues | async)?.length" [value]="null">
|
<mat-option [value]="null">{{ 'common.not-found' | translate }}</mat-option>
|
||||||
{{ 'common.not-found' | translate }}
|
}
|
||||||
</mat-option>
|
|
||||||
</mat-autocomplete>
|
</mat-autocomplete>
|
||||||
<mat-hint [hidden]="!hint">
|
<mat-hint [hidden]="!hint">
|
||||||
{{ hint }}
|
{{ hint }}
|
||||||
|
|||||||
@ -30,6 +30,7 @@ import { coerceArray, coerceBoolean } from '@shared/decorators/coercion';
|
|||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { filter, mergeMap, share, tap } from 'rxjs/operators';
|
import { filter, mergeMap, share, tap } from 'rxjs/operators';
|
||||||
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
|
import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
|
||||||
|
import { isDefined, isUndefined } from '@core/utils';
|
||||||
|
|
||||||
export interface StringItemsOption {
|
export interface StringItemsOption {
|
||||||
name: string;
|
name: string;
|
||||||
@ -116,6 +117,13 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
|
|||||||
@coerceArray()
|
@coerceArray()
|
||||||
predefinedValues: StringItemsOption[];
|
predefinedValues: StringItemsOption[];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
fetchOptionsFn: (searchText?: string) => Observable<Array<StringItemsOption>>;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
@coerceBoolean()
|
||||||
|
allowUserValue = false;
|
||||||
|
|
||||||
get itemsControl(): AbstractControl {
|
get itemsControl(): AbstractControl {
|
||||||
return this.stringItemsForm.get('items');
|
return this.stringItemsForm.get('items');
|
||||||
}
|
}
|
||||||
@ -124,7 +132,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
|
|||||||
return this.stringItemsForm.get('item');
|
return this.stringItemsForm.get('item');
|
||||||
}
|
}
|
||||||
|
|
||||||
onTouched = () => {};
|
private onTouched = () => {};
|
||||||
private propagateChange: (value: any) => void = () => {};
|
private propagateChange: (value: any) => void = () => {};
|
||||||
private dirty = false;
|
private dirty = false;
|
||||||
|
|
||||||
@ -136,7 +144,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.predefinedValues) {
|
if (this.predefinedValues || isDefined(this.fetchOptionsFn)) {
|
||||||
this.filteredValues = this.itemControl.valueChanges
|
this.filteredValues = this.itemControl.valueChanges
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((value) => {
|
tap((value) => {
|
||||||
@ -147,7 +155,8 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
filter((value) => typeof value === 'string'),
|
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()
|
share()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -180,7 +189,7 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
|
|||||||
if (value != null && value.length > 0) {
|
if (value != null && value.length > 0) {
|
||||||
this.modelValue = [...value];
|
this.modelValue = [...value];
|
||||||
this.itemList = [];
|
this.itemList = [];
|
||||||
if (this.predefinedValues) {
|
if (this.predefinedValues && !this.allowUserValue) {
|
||||||
value.forEach(item => {
|
value.forEach(item => {
|
||||||
const findItem = this.predefinedValues.find(option => option.value === item);
|
const findItem = this.predefinedValues.find(option => option.value === item);
|
||||||
if (findItem) {
|
if (findItem) {
|
||||||
@ -199,19 +208,16 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
|
|||||||
this.dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addItem(event: MatChipInputEvent): void {
|
addOnBlur(event: FocusEvent) {
|
||||||
const item = event.value?.trim() ?? '';
|
const target: HTMLElement = event.relatedTarget as HTMLElement;
|
||||||
if (item) {
|
if (target && target.tagName !== 'MAT-OPTION') {
|
||||||
if (this.predefinedValues) {
|
this.addItem(this.stringItemInput.nativeElement.value ?? '')
|
||||||
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});
|
|
||||||
}
|
}
|
||||||
|
this.onTouched();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addOnEnd(event: MatChipInputEvent): void {
|
||||||
|
this.addItem(event.value ?? '')
|
||||||
}
|
}
|
||||||
|
|
||||||
removeItems(item: StringItemsOption) {
|
removeItems(item: StringItemsOption) {
|
||||||
@ -239,6 +245,27 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
|
|||||||
return values ? values.name : undefined;
|
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) {
|
private add(item: StringItemsOption) {
|
||||||
if (!this.modelValue || this.modelValue.indexOf(item.value) === -1) {
|
if (!this.modelValue || this.modelValue.indexOf(item.value) === -1) {
|
||||||
if (!this.modelValue) {
|
if (!this.modelValue) {
|
||||||
@ -256,7 +283,6 @@ export class StringItemsListComponent implements ControlValueAccessor, OnInit {
|
|||||||
if (!this.predefinedValues?.length) {
|
if (!this.predefinedValues?.length) {
|
||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
this.searchText = searchText;
|
|
||||||
let result = this.predefinedValues;
|
let result = this.predefinedValues;
|
||||||
if (searchText && searchText.length) {
|
if (searchText && searchText.length) {
|
||||||
result = this.predefinedValues.filter(option => option.name.toLowerCase().includes(searchText.toLowerCase()));
|
result = this.predefinedValues.filter(option => option.name.toLowerCase().includes(searchText.toLowerCase()));
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user