From 9f99a165dd3ef1bb841382b4c98d2a2d1ba8153d Mon Sep 17 00:00:00 2001 From: Alex ORLUC <alex.orluc@maarch.org> Date: Tue, 24 Sep 2019 16:17:22 +0200 Subject: [PATCH] FEAT #11271 TIME 4 add autocomplete manage route --- .../indexing-form.component.html | 2 +- .../indexing-form/indexing-form.component.ts | 4 +- src/frontend/lang/lang-en.ts | 1 + src/frontend/lang/lang-fr.ts | 1 + src/frontend/lang/lang-nl.ts | 3 +- .../autocomplete/autocomplete.component.html | 6 +- .../autocomplete/autocomplete.component.scss | 9 +- .../autocomplete/autocomplete.component.ts | 121 ++++++++++++++++-- 8 files changed, 130 insertions(+), 17 deletions(-) diff --git a/src/frontend/app/indexation/indexing-form/indexing-form.component.html b/src/frontend/app/indexation/indexing-form/indexing-form.component.html index 9a7314b63f8..3d1014d8c09 100644 --- a/src/frontend/app/indexation/indexing-form/indexing-form.component.html +++ b/src/frontend/app/indexation/indexing-form/indexing-form.component.html @@ -105,7 +105,7 @@ <ng-container *ngIf="field.type === 'autocomplete'"> <plugin-autocomplete [labelPlaceholder]="lang.searchValue" [routeDatas]="[field.values[0]]" [targetSearchKey]="'idToDisplay'" [size]="'small'" - [control]="arrFormControl[field.identifier]" + [control]="arrFormControl[field.identifier]" [manageDatas]="field.values[1]" style="width:100%;"></plugin-autocomplete> </ng-container> </div> diff --git a/src/frontend/app/indexation/indexing-form/indexing-form.component.ts b/src/frontend/app/indexation/indexing-form/indexing-form.component.ts index db6c5daa105..778f7c29023 100644 --- a/src/frontend/app/indexation/indexing-form/indexing-form.component.ts +++ b/src/frontend/app/indexation/indexing-form/indexing-form.component.ts @@ -109,7 +109,7 @@ export class IndexingFormComponent implements OnInit { system: true, mandatory: true, default_value : '', - values: ['/rest/autocomplete/folders'] + values: ['/rest/autocomplete/folders', '/rest/folders'] } ]; @@ -166,7 +166,7 @@ export class IndexingFormComponent implements OnInit { label: this.lang.tags, type: 'autocomplete', default_value : '', - values: ['/rest/autocomplete/tags'] + values: ['/rest/autocomplete/tags', '/rest/tags'] } ]; availableFieldsClone: any[] = []; diff --git a/src/frontend/lang/lang-en.ts b/src/frontend/lang/lang-en.ts index c18acab4058..652e561c917 100755 --- a/src/frontend/lang/lang-en.ts +++ b/src/frontend/lang/lang-en.ts @@ -1121,4 +1121,5 @@ export const LANG_EN = { "searchValue" : "Search a value", "initiatorEntityAlt" : "Initiator entity", "autocompleteInput" : "Autocomplete input", + "typeEnterToCreate" : "Type on <b>enter</b> to create element", }; diff --git a/src/frontend/lang/lang-fr.ts b/src/frontend/lang/lang-fr.ts index be9a44e91ac..b800763563a 100755 --- a/src/frontend/lang/lang-fr.ts +++ b/src/frontend/lang/lang-fr.ts @@ -1158,4 +1158,5 @@ export const LANG_FR = { "searchValue" : "Rechercher un élément", "initiatorEntityAlt" : "Entité initiatrice", "autocompleteInput" : "Auto-completion", + "typeEnterToCreate" : "Taper sur <b>entrée</b> pour créer l'element", }; diff --git a/src/frontend/lang/lang-nl.ts b/src/frontend/lang/lang-nl.ts index cad75edb387..0d2a96f27b0 100755 --- a/src/frontend/lang/lang-nl.ts +++ b/src/frontend/lang/lang-nl.ts @@ -1146,5 +1146,6 @@ export const LANG_NL = { "noSelectedValue" : "No selected value", //_TO_TRANSLATE "searchValue" : "Search a value", //_TO_TRANSLATE "initiatorEntityAlt" : "Initiator entity", //_TO_TRANSLATE - "autocompleteInput" : "Autocomplete input", //_TO_TRANSLATE + "autocompleteInput" : "Autocomplete input", //_TO_TRANSLATE + "typeEnterToCreate" : "Type on <b>enter</b> to create element", //_TO_TRANSLATE }; diff --git a/src/frontend/plugins/autocomplete/autocomplete.component.html b/src/frontend/plugins/autocomplete/autocomplete.component.html index ed1cbc79515..374c9d6c242 100644 --- a/src/frontend/plugins/autocomplete/autocomplete.component.html +++ b/src/frontend/plugins/autocomplete/autocomplete.component.html @@ -7,7 +7,7 @@ <mat-icon *ngIf="size !== 'small' && myControl.enabled" color="primary" class="fa fa-search" matPrefix></mat-icon> <input type="text" #autoCompleteInput [placeholder]="singleMode !== undefined ? '' : placeholder" aria-label="Number" matInput [formControl]="myControl" [matAutocomplete]="auto" (click)="$event.stopPropagation()" - (focus)="resetAutocomplete()" [required]="required"> + (focus)="resetAutocomplete()" [required]="required" (keyup.enter)="addItem()"> <button type="button" *ngIf="myControl.disabled" matSuffix mat-icon-button color="warn" (click)="unsetValue()"> <mat-icon class="fa fa-times" color="warn" [title]="lang.delete"></mat-icon> </button> @@ -18,7 +18,7 @@ </mat-icon> <span color="primary">{{option[key]}}</span> <small>{{option[subInfoKey]}}</small> </mat-option> </ng-container> - <mat-option *ngIf="options.length === 0 && !loading" [class.smallInputInfo]="size === 'small'" disabled + <mat-option class="autoCompleteInfoResult" *ngIf="options.length === 0 && !loading" [class.smallInputInfo]="size === 'small'" disabled [innerHTML]="listInfo"> </mat-option> <mat-option *ngIf="loading" disabled> @@ -30,7 +30,7 @@ <mat-chip-list class="mat-chip-list-stacked" color="default"> <mat-chip *ngFor="let item of controlAutocomplete.value;let i=index" class="activeListAutocomplete" color="default" [removable]="true" (removed)="removeItem(i)"> - <span style="display: flex;flex: 1;" [innerHTML]="item"></span> + <span style="display: flex;flex: 1;" [innerHTML]="this.valuesToDisplay[item]"></span> <mat-icon matChipRemove class="fa fa-times"></mat-icon> </mat-chip> </mat-chip-list> diff --git a/src/frontend/plugins/autocomplete/autocomplete.component.scss b/src/frontend/plugins/autocomplete/autocomplete.component.scss index 314094d962d..b393108f6f9 100644 --- a/src/frontend/plugins/autocomplete/autocomplete.component.scss +++ b/src/frontend/plugins/autocomplete/autocomplete.component.scss @@ -62,4 +62,11 @@ text-align: center; font-style: italic; opacity: 0.5; -} \ No newline at end of file +} + +.autoCompleteInfoResult { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} diff --git a/src/frontend/plugins/autocomplete/autocomplete.component.ts b/src/frontend/plugins/autocomplete/autocomplete.component.ts index 47c0800cbca..23cd73dd289 100644 --- a/src/frontend/plugins/autocomplete/autocomplete.component.ts +++ b/src/frontend/plugins/autocomplete/autocomplete.component.ts @@ -2,10 +2,13 @@ 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 } from 'rxjs/operators'; +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', @@ -24,28 +27,75 @@ export class PluginAutocomplete implements OnInit { 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 ) { } @@ -55,6 +105,7 @@ export class PluginAutocomplete implements OnInit { if (this.controlAutocomplete !== undefined) { this.controlAutocomplete.setValue(this.controlAutocomplete.value === null ? [] : this.controlAutocomplete.value); + this.initFormValue(); } this.size = this.size === undefined ? 'default' : this.size; @@ -86,7 +137,15 @@ export class PluginAutocomplete implements OnInit { tap(() => this.loading = true), switchMap((data: any) => this.getDatas(data)), tap((data: any) => { - this.listInfo = data.length === 0 ? this.lang.noAvailableValue : ''; + 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; @@ -115,12 +174,7 @@ export class PluginAutocomplete implements OnInit { selectOpt(ev: any) { if (this.controlAutocomplete !== undefined) { - let arrvalue = []; - if (this.controlAutocomplete.value !== null) { - arrvalue = this.controlAutocomplete.value; - } - arrvalue.push(ev.option.value[this.key]); - this.controlAutocomplete.setValue(arrvalue); + this.setFormValue(ev.option.value); } if (this.selectedOpt !== undefined) { @@ -130,6 +184,31 @@ export class PluginAutocomplete implements OnInit { } } + 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(''); @@ -160,4 +239,28 @@ export class PluginAutocomplete implements OnInit { arrValue.splice(index, 1); this.controlAutocomplete.setValue(arrValue); } + + addItem() { + 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(); + } } \ No newline at end of file -- GitLab