import { Component, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; import { Input, EventEmitter, Output, ViewChild, ElementRef } from '@angular/core'; import { Observable, of, forkJoin } from 'rxjs'; import { map, startWith, debounceTime, filter, distinctUntilChanged, switchMap, tap, exhaustMap, catchError } from 'rxjs/operators'; import { LatinisePipe } from 'ngx-pipes'; import { LANG } from '../../app/translate.component'; import { HttpClient } from '@angular/common/http'; import { ConfirmComponent } from '../modal/confirm.component'; import { NotificationService } from '../../app/notification.service'; import { MatDialog, MatDialogRef } from '@angular/material'; @Component({ selector: 'plugin-autocomplete', templateUrl: 'autocomplete.component.html', styleUrls: ['autocomplete.component.scss', '../../app/indexation/indexing-form/indexing-form.component.scss'], }) export class PluginAutocomplete implements OnInit { lang: any = LANG; myControl = new FormControl(); loading = false; listInfo: string; type = { user: 'fa-user', entity: 'fa-sitemap' } /** * 'small', 'default' : can be used for real input or discret input filter */ @Input('size') size: string; /** * If true, input auto empty when trigger a value */ @Input('singleMode') singleMode: boolean; @Input('required') required: boolean; /** * Datas of options in autocomplete. Incompatible with @routeDatas */ @Input('datas') options: any; /** * Route datas used in async autocomplete. Incompatible with @datas */ @Input('routeDatas') routeDatas: string[]; /** * Placeholder used in input */ @Input('labelPlaceholder') placeholder: string; /** * DEPRECATED */ @Input('labelList') optGroupLabel: string; /** * Key of targeted info used when typing in input (ex : $data[0] = {id: 1, label: 'Jean Dupond'}; targetSearchKey => label) */ @Input('targetSearchKey') key: string; /** * Key of sub info in display (ex : $data[0] = {id: 1, label: 'Jean Dupond', entity: 'Pôle social'}; subInfoKey => entity) */ @Input('subInfoKey') subInfoKey: string; /** * FormControl used when autocomplete is used in form and must be catched in a form control. */ @Input('control') controlAutocomplete: FormControl; /** * Route used for set values / adding / deleting item in BDD (DataModel must return id and label) */ @Input('manageDatas') manageDatas: string; /** * Catch external event after select an element in autocomplete */ @Output('triggerEvent') selectedOpt = new EventEmitter(); @ViewChild('autoCompleteInput', { static: true }) autoCompleteInput: ElementRef; filteredOptions: Observable<string[]>; valuesToDisplay: any = {}; dialogRef: MatDialogRef<any>; constructor( public http: HttpClient, private notify: NotificationService, public dialog: MatDialog, private latinisePipe: LatinisePipe ) { } ngOnInit() { this.optGroupLabel = this.optGroupLabel === undefined ? this.lang.availableValues : this.optGroupLabel; this.placeholder = this.placeholder === undefined ? this.lang.chooseValue : this.placeholder; if (this.controlAutocomplete !== undefined) { this.controlAutocomplete.setValue(this.controlAutocomplete.value === null || this.controlAutocomplete.value === '' ? [] : this.controlAutocomplete.value); this.initFormValue(); } this.size = this.size === undefined ? 'default' : this.size; if (this.routeDatas !== undefined) { this.initAutocompleteRoute(); } else { this.initAutocompleteData(); } } initAutocompleteData() { this.listInfo = this.lang.noAvailableValue; this.filteredOptions = this.myControl.valueChanges .pipe( startWith(''), map(value => this._filter(value)) ); } initAutocompleteRoute() { this.listInfo = this.lang.autocompleteInfo; this.options = []; this.myControl.valueChanges .pipe( debounceTime(300), filter(value => value.length > 2), distinctUntilChanged(), tap(() => this.loading = true), switchMap((data: any) => this.getDatas(data)), tap((data: any) => { if (data.length === 0) { if (this.manageDatas !== undefined) { this.listInfo = this.lang.noAvailableValue + ' <div>' + this.lang.typeEnterToCreate + '</div>'; } else { this.listInfo = this.lang.noAvailableValue; } } else { this.listInfo = ''; } this.options = data; this.filteredOptions = of(this.options); this.loading = false; }) ).subscribe(); } getDatas(data: string) { let arrayObs: any = []; let test: any = []; this.routeDatas.forEach(element => { arrayObs.push(this.http.get('../..' + element, { params: { "search": data } })); }); return forkJoin(arrayObs).pipe( map(data => { data.forEach((element: any) => { element.forEach((element2: any) => { test.push(element2); }); }); return test; }) ); } selectOpt(ev: any) { if (this.singleMode) { this.myControl.setValue(ev.option.value[this.key]); } else if (this.controlAutocomplete !== undefined) { this.setFormValue(ev.option.value); } if (this.selectedOpt !== undefined) { if (this.singleMode) { this.resetAutocomplete(); } this.autoCompleteInput.nativeElement.blur(); this.selectedOpt.emit(ev.option.value); } } initFormValue() { this.controlAutocomplete.value.forEach((ids: any) => { this.http.get('../..' + this.manageDatas + '/' + ids).pipe( tap((data) => { for (var key in data) { this.valuesToDisplay[data[key].id] = data[key].label; } }) ).subscribe(); }); } setFormValue(item: any) { if (this.controlAutocomplete.value.indexOf(item['id']) === -1) { let arrvalue = []; if (this.controlAutocomplete.value !== null) { arrvalue = this.controlAutocomplete.value; } arrvalue.push(item['id']); this.valuesToDisplay[item['id']] = item[this.key]; this.controlAutocomplete.setValue(arrvalue); } } resetAutocomplete() { if (this.singleMode === undefined) { this.myControl.setValue(''); } if (this.routeDatas !== undefined) { this.options = []; this.listInfo = this.lang.autocompleteInfo; } } private _filter(value: string): string[] { if (typeof value === 'string') { const filterValue = this.latinisePipe.transform(value.toLowerCase()); return this.options.filter((option: any) => this.latinisePipe.transform(option[this.key].toLowerCase()).includes(filterValue)); } else { return this.options; } } unsetValue() { this.controlAutocomplete.setValue(''); this.myControl.setValue(''); this.myControl.enable(); } removeItem(index: number) { let arrValue = this.controlAutocomplete.value; arrValue.splice(index, 1); this.controlAutocomplete.setValue(arrValue); } addItem() { if (this.manageDatas !== undefined) { const newElem = {}; newElem[this.key] = this.myControl.value; this.dialogRef = this.dialog.open(ConfirmComponent, { autoFocus: false, disableClose: true, data: { title: this.lang.confirm, msg: 'Voulez-vous créer cet élément <b>' + newElem[this.key] + '</b> ?' } }); this.dialogRef.afterClosed().pipe( filter((data: string) => data === 'ok'), exhaustMap(() => this.http.post('../..' + this.manageDatas, { label: newElem[this.key] })), tap((data: any) => { for (var key in data) { newElem['id'] = data[key]; } this.setFormValue(newElem); this.notify.success(this.lang.elementAdded); }), catchError((err: any) => { this.notify.handleErrors(err); return of(false); }) ).subscribe(); } } }