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-chip-row>
 | 
			
		||||
    <input matInput type="text"
 | 
			
		||||
           (blur)="onTouched()"
 | 
			
		||||
           (blur)="addOnBlur($event)"
 | 
			
		||||
           placeholder="{{ placeholder }}"
 | 
			
		||||
           style="max-width: 300px;min-width: 250px"
 | 
			
		||||
           #stringItemInput
 | 
			
		||||
@ -40,8 +40,7 @@
 | 
			
		||||
           [matChipInputFor]="itemsChipList"
 | 
			
		||||
           [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
 | 
			
		||||
           matAutocompleteOrigin
 | 
			
		||||
           matChipInputAddOnBlur
 | 
			
		||||
           (matChipInputTokenEnd)="addItem($event)"
 | 
			
		||||
           (matChipInputTokenEnd)="addOnEnd($event)"
 | 
			
		||||
           [matAutocompleteConnectedTo]="origin"
 | 
			
		||||
           [matAutocomplete]="stringItemAutocomplete"
 | 
			
		||||
           [matAutocompleteDisabled]="!predefinedValues?.length">
 | 
			
		||||
@ -49,12 +48,11 @@
 | 
			
		||||
  <mat-autocomplete #stringItemAutocomplete="matAutocomplete"
 | 
			
		||||
                    [displayWith]="displayValueFn"
 | 
			
		||||
                    class="tb-autocomplete">
 | 
			
		||||
    <mat-option *ngFor="let value of filteredValues | async" [value]="value">
 | 
			
		||||
      <span [innerHTML]="value.name | highlight:searchText"></span>
 | 
			
		||||
    </mat-option>
 | 
			
		||||
    <mat-option *ngIf="!(filteredValues | async)?.length" [value]="null">
 | 
			
		||||
      {{ 'common.not-found' | translate }}
 | 
			
		||||
    </mat-option>
 | 
			
		||||
    @for (value of filteredValues | async; track value.value) {
 | 
			
		||||
      <mat-option [value]="value"><span [innerHTML]="value.name | highlight:searchText"></span></mat-option>
 | 
			
		||||
    } @empty {
 | 
			
		||||
      <mat-option [value]="null">{{ 'common.not-found' | translate }}</mat-option>
 | 
			
		||||
    }
 | 
			
		||||
  </mat-autocomplete>
 | 
			
		||||
  <mat-hint [hidden]="!hint">
 | 
			
		||||
    {{ hint }}
 | 
			
		||||
 | 
			
		||||
@ -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()));
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user