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>&nbsp;<span color="primary">{{option[key]}}</span>&nbsp;<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>&nbsp;?' } });
+
+        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