From 935341427623fd4644d64e69764a393ff5e6e4d7 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Mon, 14 May 2018 15:13:52 +0200
Subject: [PATCH 01/20]  #45 ajout d'un bouton de sauvegarde de la calculette

---
 .../components/generic-calculator/calculator.component.html  | 5 +++++
 .../components/generic-calculator/calculator.component.ts    | 4 ++++
 2 files changed, 9 insertions(+)

diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html
index 72737bb6f..3eda7ef8c 100644
--- a/src/app/components/generic-calculator/calculator.component.html
+++ b/src/app/components/generic-calculator/calculator.component.html
@@ -10,6 +10,11 @@
         <i *ngIf="enableHelpButton" class="fa fa-question-circle fa-3x" style="color:blue" (click)="openHelp()"></i>
     </div>
 
+    <div class="col-1 px-0 mx-0">
+        <!-- bouton de sauvegarde -->
+        <i class="fa fa-save fa-3x" (click)="saveCalculator()"></i>
+    </div>
+
     <div class="col-1 px-0 mx-0">
         <!-- bouton de fermeture -->
         <!-- <button type="button" id="close-button" class="btn btn-primary float-right black" (click)="confirmModal.show()">×</button> -->
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 70abce734..26cb224a1 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -371,4 +371,8 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     private openHelp() {
         this._formulaire.requestHelp();
     }
+
+
+    private saveCalculator() {
+    }
 }
-- 
GitLab


From 1755d171ff48d275d99d1be4a2acd2dd46aee172 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Tue, 15 May 2018 11:44:31 +0200
Subject: [PATCH 02/20] =?UTF-8?q?=20#45=20cr=C3=A9ation=20d'un=20composant?=
 =?UTF-8?q?=20dialogue=20de=20sauvegarde=20des=20calculettes=20proposant?=
 =?UTF-8?q?=20une=20liste=20des=20calculettes=20=C3=A0=20sauver?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.component.html                    |  3 +
 src/app/app.component.ts                      | 27 +++++++-
 src/app/app.module.ts                         |  6 +-
 .../calculator.component.ts                   |  2 +-
 .../save-calculator-anchor.directive.ts       | 27 ++++++++
 .../save-calculator.component.html            | 20 ++++++
 .../save-calculator.component.ts              | 69 +++++++++++++++++++
 .../services/formulaire/formulaire.service.ts |  7 ++
 8 files changed, 158 insertions(+), 3 deletions(-)
 create mode 100644 src/app/components/save-calculator/save-calculator-anchor.directive.ts
 create mode 100644 src/app/components/save-calculator/save-calculator.component.html
 create mode 100644 src/app/components/save-calculator/save-calculator.component.ts

diff --git a/src/app/app.component.html b/src/app/app.component.html
index 77966eddc..17a3b0cb7 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -39,6 +39,9 @@
     </div>
   </div>
 
+  <!-- sauvegarde des calculettes -->
+  <div saveCalcDialogAnchor></div>
+
   <!-- règle gradée des colonnes Bootstrap -->
   <div *ngIf="ruler" class="container-fluid">
     <div class="row">
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 8f89ea119..74ec9f1ca 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,4 +1,4 @@
-import { Component, ApplicationRef, OnInit, OnDestroy } from '@angular/core';
+import { Component, ApplicationRef, OnInit, OnDestroy, ViewChild, ComponentRef } from '@angular/core';
 //import { MdDialog } from '@angular/material';
 import { Router, ActivatedRoute } from '@angular/router';
 
@@ -16,6 +16,8 @@ import { ApplicationSetupService } from './services/app-setup/app-setup.service'
 import { HttpService } from './services/http/http.service';
 import { HelpService } from './services/help/help.service';
 import { HelpComponent } from './components/help/help.component';
+import { SaveCalcDialogAnchorDirective } from './components/save-calculator/save-calculator-anchor.directive';
+import { SaveCalculatorComponent } from './components/save-calculator/save-calculator.component';
 
 
 @Component({
@@ -34,6 +36,9 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
    */
   private _currentFormId: number;
 
+  @ViewChild(SaveCalcDialogAnchorDirective)
+  private saveCalcDialogAnchor: SaveCalcDialogAnchorDirective;
+
   /**
    * composant actuellement affiché par l'élément <router-outlet>
    */
@@ -147,6 +152,10 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
           }, 1);
           break;
 
+        case "saveForm":
+          this.saveForm(data["form"]);
+          break;
+
         case "closeForm":
           const form: FormulaireDefinition = data["form"];
           this.closeCalculator(form);
@@ -183,6 +192,22 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this._calculators[formIndex]["title"] = title;
   }
 
+  private saveForm(form: FormulaireDefinition) {
+    const compRef: ComponentRef<SaveCalculatorComponent> = this.saveCalcDialogAnchor.createDialog();
+
+    let list = [];
+    for (const c of this._calculators) {
+      const uid = +c["uid"];
+      list.push({
+        "selected": uid === form.uid,
+        "title": c["title"],
+        "uid": uid
+      })
+    }
+
+    compRef.instance.list = list;
+  }
+
   private closeCalculator(form: FormulaireDefinition) {
     const formId: number = form.uid;
 
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 56f5e83b5..3c42eccb6 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -49,6 +49,8 @@ import { VerticalResultElementComponent } from './components/result-element/vert
 import { LogEntryComponent } from './components/log-entry/log-entry.component';
 import { HelpService } from './services/help/help.service';
 import { HelpComponent } from './components/help/help.component';
+import { SaveCalculatorComponent } from './components/save-calculator/save-calculator.component';
+import { SaveCalcDialogAnchorDirective } from './components/save-calculator/save-calculator-anchor.directive';
 
 const appRoutes: Routes = [
   { path: 'list', component: CalculatorListComponent },
@@ -94,9 +96,11 @@ const appRoutes: Routes = [
     CalcCanvasComponent, SectionCanvasComponent,
     HorizontalResultElementComponent, VerticalResultElementComponent,
     FixedResultsComponent, VarResultsComponent,
-    HelpComponent
+    HelpComponent,
+    SaveCalculatorComponent, SaveCalcDialogAnchorDirective
   ],
   // entryComponents: [AlertDialog],
+  entryComponents: [SaveCalculatorComponent],
   providers: [ // services
     ParamService, InternationalisationService, HttpService, FormulaireService, ApplicationSetupService, HelpService
   ],
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 26cb224a1..b7b8a07fc 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -372,7 +372,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
         this._formulaire.requestHelp();
     }
 
-
     private saveCalculator() {
+        this.formulaireService.saveForm(this._formulaire);
     }
 }
diff --git a/src/app/components/save-calculator/save-calculator-anchor.directive.ts b/src/app/components/save-calculator/save-calculator-anchor.directive.ts
new file mode 100644
index 000000000..4317ff3ac
--- /dev/null
+++ b/src/app/components/save-calculator/save-calculator-anchor.directive.ts
@@ -0,0 +1,27 @@
+import { Directive, ComponentFactoryResolver, ComponentFactory, ComponentRef } from '@angular/core';
+
+import { ViewContainerRef } from '@angular/core';
+import { SaveCalculatorComponent } from './save-calculator.component';
+
+@Directive({
+    selector: '[saveCalcDialogAnchor]'
+})
+export class SaveCalcDialogAnchorDirective {
+    constructor(
+        private viewContainer: ViewContainerRef,
+        private componentFactoryResolver: ComponentFactoryResolver
+    ) { }
+
+    public createDialog(): ComponentRef<SaveCalculatorComponent> {
+        this.viewContainer.clear();
+
+        let compFactory: ComponentFactory<SaveCalculatorComponent> = this.componentFactoryResolver.resolveComponentFactory(SaveCalculatorComponent);
+        let compRef: ComponentRef<SaveCalculatorComponent> = this.viewContainer.createComponent(compFactory);
+
+        // dialogComponentRef.instance.close.subscribe(() => {
+        //     dialogComponentRef.destroy();
+        // });
+
+        return compRef;
+    }
+}
diff --git a/src/app/components/save-calculator/save-calculator.component.html b/src/app/components/save-calculator/save-calculator.component.html
new file mode 100644
index 000000000..1cc9083f7
--- /dev/null
+++ b/src/app/components/save-calculator/save-calculator.component.html
@@ -0,0 +1,20 @@
+<div mdbModal #saveDialog="mdb-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" [config]="{backdrop: false, ignoreBackdropClick: true,show:true}">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title w-100" id="myModalLabel">{{uitextDialogTitle}}</h4>
+            </div>
+            <div class="modal-body">
+                <div *ngFor="let c of _calculators">
+                    <input type="checkbox" value={{c.uid}} checked={{isSelected(c)}} (change)="onCheckCalc($event)">{{c.title}}
+                </div>
+                <button type="button" class="btn btn-mdb-color waves-light" (click)="selectAll()" mdbRippleRadius>{{uitextSelectAll}}</button>
+                <button type="button" class="btn btn-mdb-color waves-light" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-danger relative waves-light" (click)="saveDialog.hide()" mdbRippleRadius>{{uitextCloseDialogNo}}</button>
+                <button type="button" class="btn btn-success waves-light" aria-label="Close " (click)="saveDialog.hide();confirmSave()" mdbRippleRadius>{{uitextCloseDialogYes}}</button>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/components/save-calculator/save-calculator.component.ts b/src/app/components/save-calculator/save-calculator.component.ts
new file mode 100644
index 000000000..865054a33
--- /dev/null
+++ b/src/app/components/save-calculator/save-calculator.component.ts
@@ -0,0 +1,69 @@
+import { Component } from "@angular/core";
+import { ServiceFactory } from "../../services/service-factory";
+import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
+
+@Component({
+    selector: 'save-calc',
+    templateUrl: "./save-calculator.component.html"
+})
+export class SaveCalculatorComponent {
+    private _calculators: any[];
+
+    // services
+    private intlService: InternationalisationService;
+
+    constructor() {
+        this.intlService = ServiceFactory.instance.internationalisationService;
+    }
+
+    private get uitextDialogTitle() {
+        return "Enregister les calculettes";
+    }
+
+    private get uitextCloseDialogYes() {
+        return this.intlService.localizeText("INFO_OPTION_YES");
+    }
+
+    private get uitextCloseDialogNo() {
+        return this.intlService.localizeText("INFO_OPTION_NO");
+    }
+
+    private get uitextSelectAll() {
+        return "Toutes";
+    }
+
+    private get uitextDeselectAll() {
+        return "Aucune";
+    }
+
+    public set list(calc: any[]) {
+        this._calculators = calc;
+    }
+
+    private isSelected(c: any) {
+        return c.selected ? "checked" : undefined;
+    }
+
+    private onCheckCalc(event: any) {
+        for (const c of this._calculators)
+            if (c.uid == +event.target.value)
+                c.selected = event.target.checked;
+    }
+
+    private selectAll() {
+        for (const c of this._calculators)
+            c.selected = true;
+    }
+
+    private deselectAll() {
+        for (const c of this._calculators)
+            c.selected = false;
+    }
+
+    private confirmSave() {
+        console.log("---");
+        for (const c of this._calculators)
+            if (c.selected)
+                console.log(c.title);
+    }
+}
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 50e6ec6f7..cfd900310 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -327,4 +327,11 @@ export class FormulaireService extends Observable {
 
         return false;
     }
+
+    public saveForm(f: FormulaireDefinition) {
+        this.notifyObservers({
+            "action": "saveForm",
+            "form": f
+        });
+    }
 }
-- 
GitLab


From e7ac088e9dd68681dfcaa2eceaec3ce8a3d83d0a Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Tue, 15 May 2018 15:13:11 +0200
Subject: [PATCH 03/20] =?UTF-8?q?=20#45=20le=20composant=20de=20sauvegarde?=
 =?UTF-8?q?=20des=20calculettes=20(SaveCalculatorComponent)=20renvoie=20un?=
 =?UTF-8?q?e=20Promise=20pour=20g=C3=A9rer=20la=20confirmation/annulation?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.component.ts                      | 19 ++++++-
 .../save-calculator.component.html            |  2 +-
 .../save-calculator.component.ts              | 50 +++++++++++++++----
 3 files changed, 60 insertions(+), 11 deletions(-)

diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 74ec9f1ca..66d453fb4 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -192,9 +192,15 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this._calculators[formIndex]["title"] = title;
   }
 
+  /**
+   * sauvegarde du/des formulaires
+   * @param form formulaire à sélectionner par défaut dans la liste
+   */
   private saveForm(form: FormulaireDefinition) {
+    // création du dialogue de sélection des formulaires à sauver
     const compRef: ComponentRef<SaveCalculatorComponent> = this.saveCalcDialogAnchor.createDialog();
 
+    // création de la liste des formulaires
     let list = [];
     for (const c of this._calculators) {
       const uid = +c["uid"];
@@ -205,7 +211,18 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
       })
     }
 
-    compRef.instance.list = list;
+    // passage de la liste, récupération d'une Promise pour traiter le résultat
+    const prom: Promise<any[]> = compRef.instance.run(list);
+    prom.then(list => {
+      this.doSaveForm(list)
+    });
+  }
+
+  private doSaveForm(calcList: any[]) {
+    console.log("---");
+    for (const c of calcList)
+      if (c.selected)
+        console.log(c.title);
   }
 
   private closeCalculator(form: FormulaireDefinition) {
diff --git a/src/app/components/save-calculator/save-calculator.component.html b/src/app/components/save-calculator/save-calculator.component.html
index 1cc9083f7..3ead22063 100644
--- a/src/app/components/save-calculator/save-calculator.component.html
+++ b/src/app/components/save-calculator/save-calculator.component.html
@@ -12,7 +12,7 @@
                 <button type="button" class="btn btn-mdb-color waves-light" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button>
             </div>
             <div class="modal-footer">
-                <button type="button" class="btn btn-danger relative waves-light" (click)="saveDialog.hide()" mdbRippleRadius>{{uitextCloseDialogNo}}</button>
+                <button type="button" class="btn btn-danger relative waves-light" (click)="saveDialog.hide();cancelSave()" mdbRippleRadius>{{uitextCloseDialogNo}}</button>
                 <button type="button" class="btn btn-success waves-light" aria-label="Close " (click)="saveDialog.hide();confirmSave()" mdbRippleRadius>{{uitextCloseDialogYes}}</button>
             </div>
         </div>
diff --git a/src/app/components/save-calculator/save-calculator.component.ts b/src/app/components/save-calculator/save-calculator.component.ts
index 865054a33..99babe24c 100644
--- a/src/app/components/save-calculator/save-calculator.component.ts
+++ b/src/app/components/save-calculator/save-calculator.component.ts
@@ -1,4 +1,4 @@
-import { Component } from "@angular/core";
+import { Component, EventEmitter } from "@angular/core";
 import { ServiceFactory } from "../../services/service-factory";
 import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
 
@@ -9,6 +9,12 @@ import { InternationalisationService } from "../../services/internationalisation
 export class SaveCalculatorComponent {
     private _calculators: any[];
 
+    /**
+     * événement émis lors du clic sur "annuler"/"enregister"
+     * utilisé par la promise de gestion de la confirmation/annulation de la sauvegarde
+     */
+    private confirmResult = new EventEmitter();
+
     // services
     private intlService: InternationalisationService;
 
@@ -21,11 +27,13 @@ export class SaveCalculatorComponent {
     }
 
     private get uitextCloseDialogYes() {
-        return this.intlService.localizeText("INFO_OPTION_YES");
+        //return this.intlService.localizeText("INFO_OPTION_YES");
+        return "Sauver";
     }
 
     private get uitextCloseDialogNo() {
-        return this.intlService.localizeText("INFO_OPTION_NO");
+        // return this.intlService.localizeText("INFO_OPTION_NO");
+        return "Annuler";
     }
 
     private get uitextSelectAll() {
@@ -36,8 +44,19 @@ export class SaveCalculatorComponent {
         return "Aucune";
     }
 
-    public set list(calc: any[]) {
-        this._calculators = calc;
+    public run(calcList: any[]): Promise<any[]> {
+        this._calculators = calcList;
+
+        // promise de gestion de la confirmation/annulation de la sauvegarde
+        return new Promise((resolve, reject) => {
+            this.confirmResult.subscribe((confirm) => {
+                if (confirm) {
+                    resolve(this._calculators);
+                } else {
+                    reject();
+                }
+            });
+        });
     }
 
     private isSelected(c: any) {
@@ -60,10 +79,23 @@ export class SaveCalculatorComponent {
             c.selected = false;
     }
 
+    private set confirmed(b: boolean) {
+        setTimeout(() => {
+            this.confirmResult.next(b);
+        }, 0);
+    }
+
+    /**
+     * appelé quand on clique sur annuler
+     */
+    private cancelSave() {
+        this.confirmed = false;
+    }
+
+    /**
+     * appelé quand on clique sur sauver
+     */
     private confirmSave() {
-        console.log("---");
-        for (const c of this._calculators)
-            if (c.selected)
-                console.log(c.title);
+        this.confirmed = true;
     }
 }
-- 
GitLab


From 8b4d781ed47dfcac664e8203fe248898c3907126 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Tue, 15 May 2018 15:41:22 +0200
Subject: [PATCH 04/20] =?UTF-8?q?=20#45=20mise=20en=20place=20du=20m=C3=A9?=
 =?UTF-8?q?canisme=20de=20s=C3=A9rialisation=20JSON=20des=20formulaires?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.component.ts              |  5 ++++-
 src/app/formulaire/formulaire-node.ts | 14 ++++++++++++++
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 66d453fb4..6e6a75c47 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -221,8 +221,11 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
   private doSaveForm(calcList: any[]) {
     console.log("---");
     for (const c of calcList)
-      if (c.selected)
+      if (c.selected) {
         console.log(c.title);
+        const form: FormulaireDefinition = this.formulaireService.getFormulaireFromId(c.uid);
+        console.log(JSON.stringify(form.JSONserialise()));
+      }
   }
 
   private closeCalculator(form: FormulaireDefinition) {
diff --git a/src/app/formulaire/formulaire-node.ts b/src/app/formulaire/formulaire-node.ts
index 814d47c88..8493c7255 100644
--- a/src/app/formulaire/formulaire-node.ts
+++ b/src/app/formulaire/formulaire-node.ts
@@ -143,4 +143,18 @@ export abstract class FormulaireNode implements IObservable {
     notifyObservers(data: any, sender?: any) {
         this._observable.notifyObservers(data, sender);
     }
+
+    protected toJSON(): any {
+        return {};
+    }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): any {
+        let res = this.toJSON();
+        for (const k of this._kids)
+            res[k.id] = k.JSONserialise();
+        return res;
+    }
 }
-- 
GitLab


From 06c6e85f9597831b6f491c23728803d6d9f6966a Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Thu, 17 May 2018 11:21:06 +0200
Subject: [PATCH 05/20]  #45 ajout du bouton "charger une session" dans le
 sidenav

---
 src/app/app.component.html                        | 1 +
 src/app/app.component.ts                          | 9 +++++++++
 src/app/services/formulaire/formulaire.service.ts | 3 +++
 3 files changed, 13 insertions(+)

diff --git a/src/app/app.component.html b/src/app/app.component.html
index 17a3b0cb7..49a4e47eb 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -34,6 +34,7 @@
         <!-- ATTENTION ! pas de href="#" sous peine de rechargement de la page et réinitialisation de l'appli -->
         <a class="closebtn" (click)="closeNav()">×</a>
         <a (click)="newCalc()">{{uitextSidenavNewCalc}}</a>
+        <a (click)="loadSession()">{{uitextSidenavLoadSession}}</a>
         <a (click)="params()">{{uitextSidenavParams}}</a>
       </div>
     </div>
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 6e6a75c47..da4891688 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -89,6 +89,10 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     return this.intlService.localizeText("INFO_SETUP_TITLE");
   }
 
+  private get uitextSidenavLoadSession() {
+    return "Charger une session";
+  }
+
   /**
    * abonnement au service d'erreurs
    */
@@ -192,6 +196,11 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this._calculators[formIndex]["title"] = title;
   }
 
+  private loadSession() {
+    this.formulaireService.loadSession();
+    this.closeNav();
+  }
+
   /**
    * sauvegarde du/des formulaires
    * @param form formulaire à sélectionner par défaut dans la liste
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index cfd900310..681e6c405 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -328,6 +328,9 @@ export class FormulaireService extends Observable {
         return false;
     }
 
+    public loadSession() {
+    }
+
     public saveForm(f: FormulaireDefinition) {
         this.notifyObservers({
             "action": "saveForm",
-- 
GitLab


From e37dbbcbbc78cec40932d5f5ce60bc38e737fd0b Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Thu, 17 May 2018 11:26:36 +0200
Subject: [PATCH 06/20]  #45 classe FieldSet : remplacement du membre
 _parentForm par un getter

---
 .../formulaire/definition/form-definition.ts  |  2 +-
 src/app/formulaire/fieldset-template.ts       |  2 +-
 src/app/formulaire/fieldset.ts                | 20 +++++++++++--------
 3 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 37fbddfd4..dbe234bae 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -160,7 +160,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
 
     private parse_fieldset(json: {}) {
         const fs = this.createFieldset(this, json);
-        fs.parseConfig(json, { "parentForm": this });
+        fs.parseConfig(json);
         this.afterParseFieldset(fs);
     }
 
diff --git a/src/app/formulaire/fieldset-template.ts b/src/app/formulaire/fieldset-template.ts
index a5d618349..ce3fcf353 100644
--- a/src/app/formulaire/fieldset-template.ts
+++ b/src/app/formulaire/fieldset-template.ts
@@ -46,7 +46,7 @@ export class FieldsetTemplate {
     public instantiateTemplate(cont: FieldsetContainer, after: number): FieldSet {
         const parentForm = cont.parent as FormulaireDefinition;
         const res = parentForm.createFieldset(cont, this._jsonConfig, { "template": this, "after": after });
-        res.parseConfig(this._jsonConfig, { "parentForm": parentForm });
+        res.parseConfig(this._jsonConfig);
         parentForm.afterParseFieldset(res);
         return res;
     }
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 434cf1115..c562852e7 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -15,11 +15,6 @@ import { FieldsetContainer } from "./fieldset-container";
 import { FormulaireNode } from "./formulaire-node";
 
 export class FieldSet extends FormulaireElement implements Observer {
-    /**
-     * formulaire parent
-     */
-    private _parentForm: FormulaireDefinition;
-
     /**
      * SessionNub associé
      */
@@ -45,6 +40,16 @@ export class FieldSet extends FormulaireElement implements Observer {
         this._props = new Props();
     }
 
+    /**
+     * formulaire parent
+     */
+    private get parentForm(): FormulaireDefinition {
+        let res = this.parent;
+        while (!(res instanceof FormulaireDefinition))
+            res = res.parent;
+        return res as FormulaireDefinition;
+
+    }
     public get sessionNub(): SessionNub {
         return this._sessionNub;
     }
@@ -279,17 +284,16 @@ export class FieldSet extends FormulaireElement implements Observer {
 
         // fin MAJ selects
 
-        this.applyDependencies(this._parentForm);
+        this.applyDependencies(this.parentForm);
     }
 
     public parseConfig(json: {}, data?: {}) {
         this._jsonConfig = json;
-        this._parentForm = data["parentForm"];
 
         this._confId = json["id"];
 
         const ct: string = json["calcType"];
-        const calc_type: CalculatorType = ct == undefined ? this._parentForm.calculatorType : CalculatorType[ct];
+        const calc_type: CalculatorType = ct == undefined ? this.parentForm.calculatorType : CalculatorType[ct];
         this.setPropValue("calcType", calc_type);
 
         const nt: string = json["nodeType"];
-- 
GitLab


From 7c27a891c483cad1f2917694d5ffb6d9ba10380d Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Fri, 18 May 2018 10:08:14 +0200
Subject: [PATCH 07/20] =?UTF-8?q?=20#45=20correction=20d'un=20bug=20de=20I?=
 =?UTF-8?q?nternationalisationService.translateLabel()=20dans=20le=20cas?=
 =?UTF-8?q?=20d'un=20label=20ne=20repr=C3=A9sentant=20pas=20un=20code?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../internationalisation.service.ts           | 27 ++++++++++---------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/src/app/services/internationalisation/internationalisation.service.ts b/src/app/services/internationalisation/internationalisation.service.ts
index f418a8899..d5e3486b8 100644
--- a/src/app/services/internationalisation/internationalisation.service.ts
+++ b/src/app/services/internationalisation/internationalisation.service.ts
@@ -192,24 +192,27 @@ export class InternationalisationService extends Observable {
 
                 default:
                     const match = this.parseLabel(o);
-                    if (match)
+                    if (match) {
                         if (match[1] === "ouvrage")
                             res = this.localizeText("INFO_OUVRAGE") + " n°" + (+match[2] + 1);
 
-                    const p = match[3];
-                    switch (p) {
-                        case "Q":
-                            res += " : " + this.localizeText("INFO_GRANDEUR_" + p);
-                            break;
+                        const p = match[3];
+                        switch (p) {
+                            case "Q":
+                                res += " : " + this.localizeText("INFO_GRANDEUR_" + p);
+                                break;
 
-                        case "Q_Mode":
-                            res += " : " + this.localizeText("INFO_TYPE_ECOULEMENT");
-                            break;
+                            case "Q_Mode":
+                                res += " : " + this.localizeText("INFO_TYPE_ECOULEMENT");
+                                break;
 
-                        case "Q_Regime":
-                            res += " : " + this.localizeText("INFO_REGIME");
-                            break;
+                            case "Q_Regime":
+                                res += " : " + this.localizeText("INFO_REGIME");
+                                break;
+                        }
                     }
+                    else
+                        res = o;
                     break;
             }
         }
-- 
GitLab


From 8d8339c33d68b6badd36c01550177bcc94b18569 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Fri, 18 May 2018 16:16:48 +0200
Subject: [PATCH 08/20] =?UTF-8?q?=20#45=20cr=C3=A9ation=20d'un=20composant?=
 =?UTF-8?q?=20dialogue=20de=20chargement=20des=20calculettes=20(LoadCalcul?=
 =?UTF-8?q?atorComponent)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.component.html                    |   3 +
 src/app/app.component.ts                      |   5 +
 src/app/app.module.ts                         |   5 +-
 .../load-calculator-anchor.directive.ts       |  27 ++++
 .../load-calculator.component.html            |  22 ++++
 .../load-calculator.component.ts              | 118 ++++++++++++++++++
 6 files changed, 179 insertions(+), 1 deletion(-)
 create mode 100644 src/app/components/load-calculator/load-calculator-anchor.directive.ts
 create mode 100644 src/app/components/load-calculator/load-calculator.component.html
 create mode 100644 src/app/components/load-calculator/load-calculator.component.ts

diff --git a/src/app/app.component.html b/src/app/app.component.html
index 49a4e47eb..53236b7a7 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -40,6 +40,9 @@
     </div>
   </div>
 
+  <!-- chargement des calculettes -->
+  <div loadCalcDialogAnchor></div>
+
   <!-- sauvegarde des calculettes -->
   <div saveCalcDialogAnchor></div>
 
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index da4891688..45ef4aefc 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -16,6 +16,8 @@ import { ApplicationSetupService } from './services/app-setup/app-setup.service'
 import { HttpService } from './services/http/http.service';
 import { HelpService } from './services/help/help.service';
 import { HelpComponent } from './components/help/help.component';
+import { LoadCalcDialogAnchorDirective } from './components/load-calculator/load-calculator-anchor.directive';
+import { LoadCalculatorComponent } from './components/load-calculator/load-calculator.component';
 import { SaveCalcDialogAnchorDirective } from './components/save-calculator/save-calculator-anchor.directive';
 import { SaveCalculatorComponent } from './components/save-calculator/save-calculator.component';
 
@@ -36,6 +38,9 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
    */
   private _currentFormId: number;
 
+  @ViewChild(LoadCalcDialogAnchorDirective)
+  private loadCalcDialogAnchor: LoadCalcDialogAnchorDirective;
+
   @ViewChild(SaveCalcDialogAnchorDirective)
   private saveCalcDialogAnchor: SaveCalcDialogAnchorDirective;
 
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 3c42eccb6..e084fe592 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -49,6 +49,8 @@ import { VerticalResultElementComponent } from './components/result-element/vert
 import { LogEntryComponent } from './components/log-entry/log-entry.component';
 import { HelpService } from './services/help/help.service';
 import { HelpComponent } from './components/help/help.component';
+import { LoadCalculatorComponent } from './components/load-calculator/load-calculator.component';
+import { LoadCalcDialogAnchorDirective } from './components/load-calculator/load-calculator-anchor.directive';
 import { SaveCalculatorComponent } from './components/save-calculator/save-calculator.component';
 import { SaveCalcDialogAnchorDirective } from './components/save-calculator/save-calculator-anchor.directive';
 
@@ -97,10 +99,11 @@ const appRoutes: Routes = [
     HorizontalResultElementComponent, VerticalResultElementComponent,
     FixedResultsComponent, VarResultsComponent,
     HelpComponent,
+    LoadCalculatorComponent, LoadCalcDialogAnchorDirective,
     SaveCalculatorComponent, SaveCalcDialogAnchorDirective
   ],
   // entryComponents: [AlertDialog],
-  entryComponents: [SaveCalculatorComponent],
+  entryComponents: [LoadCalculatorComponent, SaveCalculatorComponent],
   providers: [ // services
     ParamService, InternationalisationService, HttpService, FormulaireService, ApplicationSetupService, HelpService
   ],
diff --git a/src/app/components/load-calculator/load-calculator-anchor.directive.ts b/src/app/components/load-calculator/load-calculator-anchor.directive.ts
new file mode 100644
index 000000000..f270813f1
--- /dev/null
+++ b/src/app/components/load-calculator/load-calculator-anchor.directive.ts
@@ -0,0 +1,27 @@
+import { Directive, ComponentFactoryResolver, ComponentFactory, ComponentRef } from '@angular/core';
+
+import { ViewContainerRef } from '@angular/core';
+import { LoadCalculatorComponent } from './load-calculator.component';
+
+@Directive({
+    selector: '[loadCalcDialogAnchor]'
+})
+export class LoadCalcDialogAnchorDirective {
+    constructor(
+        private viewContainer: ViewContainerRef,
+        private componentFactoryResolver: ComponentFactoryResolver
+    ) { }
+
+    public createDialog(): ComponentRef<LoadCalculatorComponent> {
+        this.viewContainer.clear();
+
+        let compFactory: ComponentFactory<LoadCalculatorComponent> = this.componentFactoryResolver.resolveComponentFactory(LoadCalculatorComponent);
+        let compRef: ComponentRef<LoadCalculatorComponent> = this.viewContainer.createComponent(compFactory);
+
+        // compRef.instance.confirmResult.subscribe(() => {
+        // compRef.destroy();
+        // });
+
+        return compRef;
+    }
+}
diff --git a/src/app/components/load-calculator/load-calculator.component.html b/src/app/components/load-calculator/load-calculator.component.html
new file mode 100644
index 000000000..d6ee5866c
--- /dev/null
+++ b/src/app/components/load-calculator/load-calculator.component.html
@@ -0,0 +1,22 @@
+<div mdbModal #loadDialog="mdb-modal" class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" [config]="{backdrop: false, ignoreBackdropClick: true,show:true}">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title w-100" id="myModalLabel">{{uitextDialogTitle}}</h4>
+            </div>
+            <div class="modal-body">
+                <div>
+                    <input type="file" #fileSelector multiple="false" accept="*.json" (change)="onFileSelect()">
+                </div>
+                <div *ngFor="let c of _calculators">
+                    <input type="checkbox" value={{c.uid}} checked={{isSelected(c)}} (change)="onCheckCalc($event)">{{c.title}}
+                </div>
+                <button *ngIf="showSelectButtons" type="button" class="btn btn-mdb-color waves-light" (click)="selectAll()" mdbRippleRadius>{{uitextSelectAll}}</button>
+                <button *ngIf="showSelectButtons" type="button" class="btn btn-mdb-color waves-light" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-danger relative waves-light" (click)="loadDialog.hide();cancelLoad()" mdbRippleRadius>{{uitextCancel}}</button>
+            </div>
+        </div>
+    </div>
+</div>
\ No newline at end of file
diff --git a/src/app/components/load-calculator/load-calculator.component.ts b/src/app/components/load-calculator/load-calculator.component.ts
new file mode 100644
index 000000000..f07ecb22a
--- /dev/null
+++ b/src/app/components/load-calculator/load-calculator.component.ts
@@ -0,0 +1,118 @@
+import { Component, EventEmitter, ViewChild } from "@angular/core";
+
+import { ServiceFactory } from "../../services/service-factory";
+import { InternationalisationService } from "../../services/internationalisation/internationalisation.service";
+
+@Component({
+    selector: 'load-calc',
+    templateUrl: "./load-calculator.component.html"
+})
+export class LoadCalculatorComponent {
+    @ViewChild('fileSelector') fileSelector;
+
+    private _selectFile: File;
+
+    private _calculators: any[];
+
+    /**
+     * événement émis lors du clic sur "annuler"/"charger"
+     * utilisé par la promise de gestion de la confirmation/annulation de la sauvegarde
+     */
+    private _confirmResult = new EventEmitter();
+
+    // services
+    private intlService: InternationalisationService;
+
+    constructor() {
+        this.intlService = ServiceFactory.instance.internationalisationService;
+    }
+
+    private get uitextDialogTitle() {
+        return "Charger des calculettes";
+    }
+
+    private get uitextCancel() {
+        // return this.intlService.localizeText("INFO_OPTION_NO");
+        return "Annuler";
+    }
+
+    private get uitextSelectAll() {
+        return "Toutes";
+    }
+
+    private get uitextDeselectAll() {
+        return "Aucune";
+    }
+
+    public run(): Promise<any[]> {
+        // promise de gestion de la confirmation/annulation de la sauvegarde
+        return new Promise((resolve, reject) => {
+            this._confirmResult.subscribe((confirm) => {
+                if (confirm) {
+                    resolve(this._calculators);
+                } else {
+                    reject();
+                }
+            });
+        });
+    }
+
+    private get showSelectButtons(): boolean {
+        return this._calculators && this._calculators.length != 0;
+    }
+
+    private onFileSelect() {
+        const files: { [key: string]: File } = this.fileSelector.nativeElement.files;
+        for (let key in files) {
+            if (!isNaN(parseInt(key))) {
+                this._selectFile = files[key];
+                this.confirmLoad();
+                break;
+            }
+        }
+    }
+
+    public get selectedFile(): File {
+        return this._selectFile;
+    }
+
+    private isSelected(c: any) {
+        return c.selected ? "checked" : undefined;
+    }
+
+    private onCheckCalc(event: any) {
+        for (const c of this._calculators)
+            if (c.uid == +event.target.value)
+                c.selected = event.target.checked;
+    }
+
+    private selectAll() {
+        for (const c of this._calculators)
+            c.selected = true;
+    }
+
+    private deselectAll() {
+        for (const c of this._calculators)
+            c.selected = false;
+    }
+
+    private set confirmed(b: boolean) {
+        setTimeout(() => {
+            this._confirmResult.next(b);
+        }, 0);
+    }
+
+    /**
+     * appelé quand on clique sur annuler
+     */
+    private cancelLoad() {
+        this.confirmed = false;
+    }
+
+    /**
+     * appelé quand on clique sur sauver
+     */
+    private confirmLoad() {
+        this.confirmed = true;
+    }
+}
-- 
GitLab


From 7b3f6531083be0fb4cec86c1ff995b628116372d Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Fri, 18 May 2018 16:30:33 +0200
Subject: [PATCH 09/20] =?UTF-8?q?=20#45=20impl=C3=A9mentation=20de=20la=20?=
 =?UTF-8?q?(d=C3=A9)s=C3=A9rialisation=20du=20formulaire?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.component.ts                      | 11 ++-
 .../formulaire/definition/form-definition.ts  | 66 +++++++++++++++++
 src/app/formulaire/fieldset-container.ts      | 57 ++++++++++++++
 src/app/formulaire/fieldset.ts                | 66 +++++++++++++++++
 src/app/formulaire/formulaire-node.ts         | 14 ++--
 src/app/formulaire/ngparam.ts                 | 74 +++++++++++++++++++
 src/app/formulaire/select-entry.ts            | 10 +++
 src/app/formulaire/select-field.ts            | 35 +++++++++
 .../services/formulaire/formulaire.service.ts | 34 +++++++++
 9 files changed, 356 insertions(+), 11 deletions(-)

diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 45ef4aefc..9cd3266d5 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -228,18 +228,21 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     // passage de la liste, récupération d'une Promise pour traiter le résultat
     const prom: Promise<any[]> = compRef.instance.run(list);
     prom.then(list => {
-      this.doSaveForm(list)
+      this.saveSession(list)
     });
   }
 
-  private doSaveForm(calcList: any[]) {
-    console.log("---");
+  private saveSession(calcList: any[]) {
+    let elems = [];
     for (const c of calcList)
       if (c.selected) {
         console.log(c.title);
         const form: FormulaireDefinition = this.formulaireService.getFormulaireFromId(c.uid);
-        console.log(JSON.stringify(form.JSONserialise()));
+        elems.push(form.JSONserialise());
       }
+    let session = { "session": { "elements": elems } };
+    console.log("---");
+    console.log(JSON.stringify(session));
   }
 
   private closeCalculator(form: FormulaireDefinition) {
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index dbe234bae..c596e5f4e 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -435,6 +435,72 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return new TopFormulaireElementIterator(this);
     }
 
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.calculatorName;
+        res["props"] = this._currentSessionNub.properties.props;
+        res["elements"] = this.serialiseKids();
+        return { "form": res };
+    }
+
+    private deserialiseFieldset(elements: {}): FieldSet {
+        const res: FieldSet = this.getFormulaireNodeById(elements["id"]) as FieldSet;
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseFieldsetContainer(elements: {}): FieldsetContainer {
+        const res: FieldsetContainer = this.getFormulaireNodeById(elements["id"]) as FieldsetContainer;
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseElement(element: {}) {
+        const keys = Object.keys(element);
+        if (keys.length !== 1)
+            throw new Error(`session file : invalid form object '${element}'`);
+
+        switch (keys[0]) {
+            case "fieldset":
+                this.deserialiseFieldset(element[keys[0]]);
+                break;
+
+            case "fieldset_container":
+                this.deserialiseFieldsetContainer(element[keys[0]]);
+                break;
+
+            default:
+                throw new Error(`session file : invalid key '${keys[0]}' in form object`);
+        }
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    this._calculatorName = elements[k];
+                    break;
+
+                case "props":
+                    break;
+
+                case "elements":
+                    for (const e of elements[k])
+                        this.deserialiseElement(e);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in form object`);
+            }
+        }
+    }
+
     //  interface Observer
 
     public update(sender: any, data: any) {
diff --git a/src/app/formulaire/fieldset-container.ts b/src/app/formulaire/fieldset-container.ts
index a354d2a7d..64aaee77a 100644
--- a/src/app/formulaire/fieldset-container.ts
+++ b/src/app/formulaire/fieldset-container.ts
@@ -105,4 +105,61 @@ export class FieldsetContainer extends FormulaireElement {
 
         super.updateLocalisation(loc);
     }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.id;
+        res["elements"] = this.serialiseKids();
+        return { "fieldset_container": res };
+    }
+
+    private deserialiseFieldset(elements: {}): FieldSet {
+        const ind = this.getTemplateIndex(elements["id"]);
+        const res: FieldSet = this.addFromTemplate(ind);
+        const props = elements["props"];
+        for (const k in props)
+            if (k !== "calcType" && k !== "nodeType")
+                res.setPropValue(k, props[k]);
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseElement(element: {}) {
+        const keys = Object.keys(element);
+        if (keys.length !== 1)
+            throw new Error(`session file : invalid fieldset object '${element}'`);
+
+        switch (keys[0]) {
+            case "fieldset":
+                this.deserialiseFieldset(element[keys[0]]);
+                break;
+
+            default:
+                throw new Error(`session file : invalid key '${keys[0]}' in fieldset object`);
+        }
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    this._confId = elements[k];
+                    break;
+
+                case "elements":
+                    for (const e of elements[k])
+                        this.deserialiseElement(e);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in form object`);
+            }
+        }
+    }
 }
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index c562852e7..967d8388b 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -402,4 +402,70 @@ export class FieldSet extends FormulaireElement implements Observer {
                     break;
             }
     }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.id;
+        res["props"] = this._props.props;
+        res["elements"] = this.serialiseKids();
+        return { "fieldset": res };
+    }
+
+    private deserialiseParam(elements: {}): NgParameter {
+        const res: NgParameter = this.getFormulaireNodeById(elements["id"]) as NgParameter;
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseSelect(elements: {}): SelectField {
+        const res: SelectField = this.getFormulaireNodeById(elements["id"]) as SelectField;
+        res.deserialiseJSON(elements);
+        return res;
+    }
+
+    private deserialiseElement(element: {}) {
+        const keys = Object.keys(element);
+        if (keys.length !== 1)
+            throw new Error(`session file : invalid fieldset object '${element}'`);
+
+        switch (keys[0]) {
+            case "param":
+                this.deserialiseParam(element[keys[0]]);
+                break;
+
+            case "select":
+                this.deserialiseSelect(element[keys[0]]);
+                break;
+
+            default:
+                throw new Error(`session file : invalid key '${keys[0]}' in fieldset object`);
+        }
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    break;
+
+                case "props":
+                    this._props = new Props(elements[k]);
+                    break;
+
+                case "elements":
+                    for (const e of elements[k])
+                        this.deserialiseElement(e);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in form object`);
+            }
+        }
+    }
 }
diff --git a/src/app/formulaire/formulaire-node.ts b/src/app/formulaire/formulaire-node.ts
index 8493c7255..aca8ea0c1 100644
--- a/src/app/formulaire/formulaire-node.ts
+++ b/src/app/formulaire/formulaire-node.ts
@@ -144,17 +144,17 @@ export abstract class FormulaireNode implements IObservable {
         this._observable.notifyObservers(data, sender);
     }
 
-    protected toJSON(): any {
-        return {};
+    protected serialiseKids() {
+        const elems = [];
+        for (const k of this.kids)
+            elems.push(k.JSONserialise());
+        return elems;
     }
 
     /**
      * sérialisation en JSON
      */
-    public JSONserialise(): any {
-        let res = this.toJSON();
-        for (const k of this._kids)
-            res[k.id] = k.JSONserialise();
-        return res;
+    public JSONserialise(): {} {
+        return {};
     }
 }
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 671730429..552594bc4 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -320,4 +320,78 @@ export class NgParameter extends InputField {
             super.updateLocalisation(loc, key);
         }
     }
+
+    private paramValuesJSON(): any {
+        let res = {};
+        res["mode"] = ParamValueMode[this._paramValues.valueMode];
+        switch (this._paramValues.valueMode) {
+            case ParamValueMode.SINGLE:
+                res["value"] = this._paramValues.singleValue;
+                break;
+
+            case ParamValueMode.MINMAX:
+                res["min"] = this._paramValues.min;
+                res["max"] = this._paramValues.max;
+                res["step"] = this._paramValues.step;
+                break;
+
+            case ParamValueMode.LISTE:
+                res["values"] = this._paramValues.valueList;
+                break;
+        }
+        return res;
+    }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.id;
+        res["values"] = this.paramValuesJSON();
+        return { "param": res };
+    }
+
+    private deserialiseValues(json: {}) {
+        const m: ParamValueMode = (<any>ParamValueMode)[json["mode"]];
+        switch (m) {
+            case ParamValueMode.SINGLE:
+                this._paramValues.setValues(+json["value"]);
+                break;
+
+            case ParamValueMode.MINMAX:
+                this._paramValues.setValues(+json["min"], +json["max"], +json["step"]);
+                break;
+
+            case ParamValueMode.LISTE:
+                this._paramValues.setValues(json["values"]);
+                break;
+
+            case ParamValueMode.CALCUL:
+                this._paramValues.valueMode = ParamValueMode.CALCUL;
+                break;
+
+            default:
+                throw new Error(`session file : invalid value mode '${json["mode"]}' in param object`);
+        }
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    break;
+
+                case "values":
+                    this.deserialiseValues(elements[k]);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in param object`);
+            }
+        }
+    }
 }
diff --git a/src/app/formulaire/select-entry.ts b/src/app/formulaire/select-entry.ts
index 8da49428e..dc552ff9f 100644
--- a/src/app/formulaire/select-entry.ts
+++ b/src/app/formulaire/select-entry.ts
@@ -27,4 +27,14 @@ export class SelectEntry {
     get value(): any {
         return this._value;
     }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise() {
+        let res = {};
+        res["id"] = this.id;
+        res["value"] = this._value;
+        return res;
+    }
 }
diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts
index d6366564c..916fee39c 100644
--- a/src/app/formulaire/select-field.ts
+++ b/src/app/formulaire/select-field.ts
@@ -117,4 +117,39 @@ export class SelectField extends Field {
             this.addEntry(e);
         }
     }
+
+    /**
+     * sérialisation en JSON
+     */
+    public JSONserialise(): {} {
+        let res = {};
+        res["id"] = this.id;
+        res["selected_id"] = this._selectedEntry.id;
+        return { "select": res };
+    }
+
+    /**
+     * désérialisation depuis JSON
+     */
+    public deserialiseJSON(elements: {}) {
+        for (const k in elements) {
+            switch (k) {
+                case "id":
+                    this._confId = elements[k];
+                    break;
+
+                case "selected_id":
+                    const sel = elements[k];
+                    for (const e of this._entries)
+                        if (e.id === sel) {
+                            this._selectedEntry = e;
+                            break;
+                        }
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${k}' in select object`);
+            }
+        }
+    }
 }
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 681e6c405..194b0cb09 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -337,4 +337,38 @@ export class FormulaireService extends Observable {
             "form": f
         });
     }
+
+    private deserialiseForm(elements: {}) {
+        const props = elements["props"];
+        const ct: CalculatorType = props["calcType"];
+        this.createFormulaire(ct, elements);
+    }
+
+    private deserialiseSessionElement(element: {}) {
+        const keys = Object.keys(element);
+        if (keys.length !== 1)
+            throw new Error(`session file : invalid session object '${element}'`);
+
+        switch (keys[0]) {
+            case "form":
+                this.deserialiseForm(element[keys[0]]);
+                break;
+
+            default:
+                throw new Error(`session file : invalid key '${keys[0]}' in session object`);
+        }
+    }
+
+    private deserialiseSession(elements: {}) {
+        for (const ks in elements)
+            switch (ks) {
+                case "elements":
+                    for (const e of elements[ks])
+                        this.deserialiseSessionElement(e);
+                    break;
+
+                default:
+                    throw new Error(`session file : invalid key '${ks}' in session object`);
+            }
+    }
 }
-- 
GitLab


From 7d86fb31b8d845e189eb302b53f0e3a14efe1bd3 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Fri, 18 May 2018 16:39:05 +0200
Subject: [PATCH 10/20] =?UTF-8?q?=20#45=20impl=C3=A9mentation=20du=20charg?=
 =?UTF-8?q?ement=20de=20la=20session?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.component.ts                      | 18 ++++--
 src/app/formulaire/fieldset-container.ts      | 14 ++++-
 .../services/formulaire/formulaire.service.ts | 58 ++++++++++++++++++-
 3 files changed, 81 insertions(+), 9 deletions(-)

diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 9cd3266d5..a1024c868 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -201,11 +201,6 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this._calculators[formIndex]["title"] = title;
   }
 
-  private loadSession() {
-    this.formulaireService.loadSession();
-    this.closeNav();
-  }
-
   /**
    * sauvegarde du/des formulaires
    * @param form formulaire à sélectionner par défaut dans la liste
@@ -336,6 +331,19 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     this.toList();
   }
 
+  private loadSession() {
+    this.closeNav();
+
+    // création du dialogue de sélection des formulaires à sauver
+    const compRef: ComponentRef<LoadCalculatorComponent> = this.loadCalcDialogAnchor.createDialog();
+
+    const prom: Promise<any[]> = compRef.instance.run();
+    prom.then(list => {
+      this.formulaireService.loadSession(compRef.instance.selectedFile);
+      compRef.destroy();
+    });
+  }
+
   private params() {
     this.closeNav();
     this.router.navigate(['/setup']);
diff --git a/src/app/formulaire/fieldset-container.ts b/src/app/formulaire/fieldset-container.ts
index 64aaee77a..1f384a140 100644
--- a/src/app/formulaire/fieldset-container.ts
+++ b/src/app/formulaire/fieldset-container.ts
@@ -28,6 +28,16 @@ export class FieldsetContainer extends FormulaireElement {
         return this._templates[index];
     }
 
+    private getTemplateIndex(id: string): number {
+        let i = 0;
+        for (const t of this._templates) {
+            if (t.config["id"] == id)
+                return i;
+            i++;
+        }
+        throw new Error(`template ${id} non trouvé`);
+    }
+
     public addFieldset(fs: FieldSet) {
         this.fieldsets.push(fs);
     }
@@ -68,7 +78,7 @@ export class FieldsetContainer extends FormulaireElement {
      * @param templateIndex indice du template dans la liste
      * @param after insère le nouveau FieldSet après cette position, à la fin sinon
      */
-    public addFromTemplate(templateIndex: number, after?: number) {
+    public addFromTemplate(templateIndex: number, after?: number): FieldSet {
         const templ: FieldsetTemplate = this._templates[templateIndex];
 
         const inst: FieldSet = templ.instantiateTemplate(this, after);
@@ -80,6 +90,8 @@ export class FieldsetContainer extends FormulaireElement {
             "action": "newFieldset",
             "fieldset": inst
         }, this);
+
+        return inst;
     }
 
     public get fieldsets(): FieldSet[] {
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 194b0cb09..c08bb630e 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -146,7 +146,7 @@ export class FormulaireService extends Observable {
         return this._httpService.httpGetRequest(undefined, undefined, undefined, f, processData);
     }
 
-    public createFormulaire(ct: CalculatorType): Promise<FormulaireDefinition> {
+    private newFormulaire(ct: CalculatorType, jsonState?: {}): FormulaireDefinition {
         let f: FormulaireDefinition;
         switch (ct) {
             case CalculatorType.ConduiteDistributrice:
@@ -185,10 +185,31 @@ export class FormulaireService extends Observable {
                 throw new Error(`FormulaireService.createFormulaire() : type de calculette ${ct} non pris en charge`)
         }
 
-        f.calculatorName = decode(this.getLocalisedTitleFromCalculatorType(ct) + " (" + f.uid + ")");
+        if (jsonState !== undefined) {
+            const props = jsonState["props"];
+            f.initSessionNub(props);
+        }
+        else
+            f.initSessionNub();
+
+        return f;
+    }
+
+    /**
+     * crée un formulaire d'un type donné
+     * @param ct type de formulaire
+     * @param jsonState 
+     */
+    public createFormulaire(ct: CalculatorType, jsonState?: {}): Promise<FormulaireDefinition> {
+        const f: FormulaireDefinition = this.newFormulaire(ct, jsonState);
+
+        if (jsonState === undefined)
+            f.calculatorName = decode(this.getLocalisedTitleFromCalculatorType(ct) + " (" + f.uid + ")");
         this._formulaires.push(f);
         let prom: Promise<Response> = this.loadConfig(f, ct);
         return prom.then(_ => {
+            if (jsonState !== undefined)
+                f.deserialiseJSON(jsonState);
             return f;
         }).then(f => {
             this.loadUpdateFormulaireLocalisation(f);
@@ -328,7 +349,38 @@ export class FormulaireService extends Observable {
         return false;
     }
 
-    public loadSession() {
+    private readSingleFile(file: File): Promise<any> {
+        return new Promise<any>((resolve, reject) => {
+            var fr = new FileReader();
+
+            fr.onload = () => {
+                resolve(fr.result);
+            };
+
+            fr.onerror = () => {
+                fr.abort();
+                reject(new Error(`Erreur de lecture du fichier ${file.name}`));
+            };
+
+            fr.readAsText(file);
+        });
+    }
+
+    public loadSession(f: File) {
+        this.readSingleFile(f).then(s => {
+            const session = JSON.parse(s);
+            for (const k in session)
+                switch (k) {
+                    case "session":
+                        this.deserialiseSession(session[k]);
+                        break;
+
+                    default:
+                        throw new Error(`session file : invalid key '${k}'`);
+                }
+        }).catch(err => {
+            throw err;
+        });
     }
 
     public saveForm(f: FormulaireDefinition) {
-- 
GitLab


From de9dc0b633589b893e28d1712c988469c7dc183a Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Fri, 18 May 2018 16:40:47 +0200
Subject: [PATCH 11/20] =?UTF-8?q?=20#45=20=C3=A0=20l'ouverture=20de=20la?=
 =?UTF-8?q?=20calculette=20"ouvrages=20parall=C3=A8les",=20un=20nouvel=20o?=
 =?UTF-8?q?uvrage=20est=20cr=C3=A9=C3=A9=20automatiquement.=20L'impl=C3=A9?=
 =?UTF-8?q?mentation=20a=20=C3=A9t=C3=A9=20modifi=C3=A9e=20de=20fa=C3=A7on?=
 =?UTF-8?q?=20=C3=A0=20ne=20pas=20le=20faire=20dans=20le=20cas=20du=20char?=
 =?UTF-8?q?gement=20d'une=20session?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../calculator-list/calculator-list.component.ts      | 11 +++++++++++
 .../fieldset-container.component.ts                   |  2 +-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/src/app/components/calculator-list/calculator-list.component.ts b/src/app/components/calculator-list/calculator-list.component.ts
index 91b239baa..1a14c08d0 100644
--- a/src/app/components/calculator-list/calculator-list.component.ts
+++ b/src/app/components/calculator-list/calculator-list.component.ts
@@ -6,6 +6,8 @@ import { CalculatorType, EnumEx } from "jalhyd";
 import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
 import { ServiceFactory } from "../../services/service-factory";
 import { InternationalisationService } from '../../services/internationalisation/internationalisation.service';
+import { FormulaireParallelStructure } from "../../formulaire/definition/concrete/form-parallel-structures";
+import { FieldsetContainer } from "../../formulaire/fieldset-container";
 
 class ListElement {
     private _label: string;
@@ -41,6 +43,15 @@ export class CalculatorListComponent implements OnInit {
         const p: Promise<FormulaireDefinition> = ServiceFactory.instance.formulaireService.createFormulaire(t);
         p.then(f => {
             this.router.navigate(['/calculator', f.uid]);
+            return f;
+        }).then(f => {
+            // on ajoute un ouvrage après l'ouverture de la calculette "ouvrages parallèles"
+            if (f instanceof FormulaireParallelStructure)
+                for (const e of f.allFormElements)
+                    if (e instanceof FieldsetContainer) {
+                        e.addFromTemplate(0);
+                        break;
+                    }
         });
     }
 
diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts
index baa3ce418..240f408b9 100644
--- a/src/app/components/fieldset-container/fieldset-container.component.ts
+++ b/src/app/components/fieldset-container/fieldset-container.component.ts
@@ -73,8 +73,8 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit {
     }
 
     public ngAfterViewInit() {
+        this.onFielsetListChange();
         this._fieldsetComponents.changes.subscribe(_ => this.onFielsetListChange());
-        this.addStructure();
     }
 
     /*
-- 
GitLab


From e05aaa0594c56955c46297807a36d6ba83c56d30 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Fri, 18 May 2018 16:48:07 +0200
Subject: [PATCH 12/20] =?UTF-8?q?=20#45=20fichier=20de=20conf=20des=20calc?=
 =?UTF-8?q?ulettes=20:=20suppression=20du=20champ=20"defaultNodeType"=20da?=
 =?UTF-8?q?ns=20les=20fieldsets=20-=20FormulaireDefinition=20:=20remplacem?=
 =?UTF-8?q?ent=20du=20membre=20=5FcalcType=20par=20des=20propri=C3=A9t?=
 =?UTF-8?q?=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../regime-uniforme.config.json               |  4 ---
 src/app/calculators/remous/remous.config.json |  4 ---
 .../section-param/section-param.config.json   |  4 ---
 .../definition/concrete/form-cond-distri.ts   |  6 ++++-
 .../definition/concrete/form-courbe-remous.ts |  6 ++++-
 .../concrete/form-lechapt-calmon.ts           |  6 ++++-
 .../concrete/form-parallel-structures.ts      | 11 ++++++--
 .../concrete/form-passe-bassin-dim.ts         |  6 ++++-
 .../concrete/form-passe-bassin-puissance.ts   |  6 ++++-
 .../concrete/form-regime-uniforme.ts          |  6 ++++-
 .../concrete/form-section-parametree.ts       |  6 ++++-
 .../formulaire/definition/form-definition.ts  | 25 ++++++++++---------
 src/app/formulaire/fieldset.ts                | 15 +++--------
 13 files changed, 60 insertions(+), 45 deletions(-)

diff --git a/src/app/calculators/regime-uniforme/regime-uniforme.config.json b/src/app/calculators/regime-uniforme/regime-uniforme.config.json
index 5710ce3e1..7286a71d8 100644
--- a/src/app/calculators/regime-uniforme/regime-uniforme.config.json
+++ b/src/app/calculators/regime-uniforme/regime-uniforme.config.json
@@ -2,7 +2,6 @@
     {
         "id": "fs_section",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "cal",
         "fields": [
             {
@@ -104,7 +103,6 @@
     {
         "id": "fs_bief",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "cal",
         "fields": [
             {
@@ -127,7 +125,6 @@
     {
         "id": "fs_hydraulique",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "cal",
         "fields": [
             {
@@ -145,7 +142,6 @@
     {
         "id": "fs_param_calc",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
diff --git a/src/app/calculators/remous/remous.config.json b/src/app/calculators/remous/remous.config.json
index f530722a3..289a274e1 100644
--- a/src/app/calculators/remous/remous.config.json
+++ b/src/app/calculators/remous/remous.config.json
@@ -2,7 +2,6 @@
     {
         "id": "fs_section",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
@@ -104,7 +103,6 @@
     {
         "id": "fs_bief",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
@@ -132,7 +130,6 @@
     {
         "id": "fs_condlim",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
@@ -155,7 +152,6 @@
     {
         "id": "fs_param_calc",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
diff --git a/src/app/calculators/section-param/section-param.config.json b/src/app/calculators/section-param/section-param.config.json
index 88aac4d94..56ea1dd3c 100644
--- a/src/app/calculators/section-param/section-param.config.json
+++ b/src/app/calculators/section-param/section-param.config.json
@@ -2,7 +2,6 @@
     {
         "id": "fs_section",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "var",
         "fields": [
             {
@@ -104,7 +103,6 @@
     {
         "id": "fs_bief",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "var",
         "fields": [
             {
@@ -127,7 +125,6 @@
     {
         "id": "fs_hydraulique",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "var",
         "fields": [
             {
@@ -145,7 +142,6 @@
     {
         "id": "fs_param_calc",
         "type": "fieldset",
-        "defaultNodeType": "SectionTrapeze",
         "option": "fix",
         "fields": [
             {
diff --git a/src/app/formulaire/definition/concrete/form-cond-distri.ts b/src/app/formulaire/definition/concrete/form-cond-distri.ts
index b79c5d50c..c9e70be83 100644
--- a/src/app/formulaire/definition/concrete/form-cond-distri.ts
+++ b/src/app/formulaire/definition/concrete/form-cond-distri.ts
@@ -16,13 +16,17 @@ export class FormulaireConduiteDistributrice extends FormulaireDefinition {
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.ConduiteDistributrice);
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.ConduiteDistributrice, "nodeType": ComputeNodeType.None };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-courbe-remous.ts b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
index 0f60d8a59..d67e9bf24 100644
--- a/src/app/formulaire/definition/concrete/form-courbe-remous.ts
+++ b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
@@ -21,12 +21,16 @@ export class FormulaireCourbeRemous extends FormulaireDefinition {
     private _resolveMethSelectId: string;
 
     constructor() {
-        super(CalculatorType.CourbeRemous)
+        super();
         this._formSection = new FormDefSection(this);
         this._formResult = new FormResultRemous(this);
         this._formCompute = new FormComputeCourbeRemous(this, this._formSection, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.CourbeRemous, "nodeType": ComputeNodeType.SectionPuissance };
+    }
+
     protected parseOptions(json: {}) {
         this._formSection.parseOptions(json);
 
diff --git a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
index baf385278..96d6fad59 100644
--- a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
+++ b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
@@ -18,13 +18,17 @@ export class FormulaireLechaptCalmon extends FormulaireDefinition implements Obs
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.LechaptCalmon)
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.LechaptCalmon, "nodeType": ComputeNodeType.None };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index 66838edba..d06971f41 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -1,4 +1,4 @@
-import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionNub } from "jalhyd";
+import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionNub, StructureType, LoiDebit } from "jalhyd";
 
 import { FormulaireDefinition } from "../form-definition";
 import { CalculatorResults } from "../../../results/calculator-results";
@@ -33,7 +33,7 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
     private __ouvrageSelectId: string;
 
     constructor() {
-        super(CalculatorType.ParallelStructure)
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
@@ -41,6 +41,13 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
         this._formCompute = new FormComputeParallelStructures(this, this._formParallelStruct, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return {
+            "calcType": CalculatorType.ParallelStructure, "nodeType": ComputeNodeType.None,
+            "structureType": StructureType.SeuilRectangulaire, "loiDebit": LoiDebit.Cem88v
+        };
+    }
+
     private createStructNub(templ: FieldsetTemplate): SessionNub {
         const paramService: ParamService = ServiceFactory.instance.paramService;
 
diff --git a/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts b/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts
index bc8d202d1..64a5141cd 100644
--- a/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts
+++ b/src/app/formulaire/definition/concrete/form-passe-bassin-dim.ts
@@ -17,13 +17,17 @@ export class FormulairePasseBassinDimensions extends FormulaireDefinition {
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.PabDimensions);
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.PabDimensions, "nodeType": ComputeNodeType.None };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts b/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts
index 02ed7376a..31d2a8639 100644
--- a/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts
+++ b/src/app/formulaire/definition/concrete/form-passe-bassin-puissance.ts
@@ -17,13 +17,17 @@ export class FormulairePasseBassinPuissance extends FormulaireDefinition {
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.PabPuissance);
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.PabPuissance, "nodeType": ComputeNodeType.None };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
index b4f322431..50e7aeff7 100644
--- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
+++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
@@ -20,7 +20,7 @@ export class FormulaireRegimeUniforme extends FormulaireDefinition implements Ob
     private _formResult: FormResultFixedVar;
 
     constructor() {
-        super(CalculatorType.RegimeUniforme)
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formSection = new FormDefSection(this);
@@ -28,6 +28,10 @@ export class FormulaireRegimeUniforme extends FormulaireDefinition implements Ob
         this._formCompute = new FormComputeFixedVar(this, this._formResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.RegimeUniforme, "nodeType": ComputeNodeType.SectionCercle };
+    }
+
     protected initParse() {
         this._formParamCalc.initParse();
     }
diff --git a/src/app/formulaire/definition/concrete/form-section-parametree.ts b/src/app/formulaire/definition/concrete/form-section-parametree.ts
index 63f636138..1f14c0f66 100644
--- a/src/app/formulaire/definition/concrete/form-section-parametree.ts
+++ b/src/app/formulaire/definition/concrete/form-section-parametree.ts
@@ -22,13 +22,17 @@ export class FormulaireSectionParametree extends FormulaireDefinition {
     private _formSectionResult: FormResultSection;
 
     constructor() {
-        super(CalculatorType.SectionParametree);
+        super();
         this._formFixedVar = new FormDefFixedVar(this);
         this._formSection = new FormDefSection(this);
         this._formSectionResult = new FormResultSection(this, this._formSection);
         this._formCompute = new FormComputeSectionParametree(this, this._formSection, this._formSectionResult);
     }
 
+    protected get defaultProperties(): {} {
+        return { "calcType": CalculatorType.SectionParametree, "nodeType": ComputeNodeType.SectionCercle };
+    }
+
     protected parseOptions(json: {}) {
         this._formSection.parseOptions(json);
     }
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index c596e5f4e..7d577b03d 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -20,11 +20,6 @@ import { FieldsetTemplate } from "../fieldset-template";
  * classe de base pour tous les formulaires
  */
 export abstract class FormulaireDefinition extends FormulaireNode implements Observer {
-    /**
-     * type de calculette
-     */
-    private _calcType: CalculatorType;
-
     /**
      * nom de la calculette
      */
@@ -47,15 +42,19 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
 
     protected _paramService: ParamService;
 
-    constructor(calcType: CalculatorType) {
+    constructor() {
         super(undefined);
-        this._calcType = calcType;
         this._paramService = ServiceFactory.instance.paramService;
-        this.initSessionNub();
     }
 
     public get calculatorType(): CalculatorType {
-        return this._calcType;
+        const props = this._currentSessionNub === undefined ? this.defaultProperties : this._currentSessionNub.properties.props;
+        return props["calcType"];
+    }
+
+    public get nodeType(): ComputeNodeType {
+        const props = this._currentSessionNub === undefined ? this.defaultProperties : this._currentSessionNub.properties.props;
+        return props["nodeType"];
     }
 
     public get calculatorName() {
@@ -70,15 +69,17 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return this._jsonConfig;
     }
 
-    protected initSessionNub() {
-        this._currentSessionNub = this.createSessionNub({ "calcType": this._calcType, "nodeType": ComputeNodeType.None });
+    protected abstract get defaultProperties(): {};
+
+    public initSessionNub(props?: {}) {
+        this._currentSessionNub = this.createSessionNub(props === undefined ? this.defaultProperties : props);
     }
 
     public get currentSessionNub(): SessionNub {
         return this._currentSessionNub;
     }
 
-    protected findNub(params: Props | {}) {
+    private findNub(params: Props | {}) {
         return this._paramService.findSessionNub(params);
     }
 
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 967d8388b..1a35a58bb 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -112,7 +112,7 @@ export class FieldSet extends FormulaireElement implements Observer {
         return this._props;
     }
 
-    public getPropValue(key: string): any {
+    private getPropValue(key: string): any {
         return this._props.getPropValue(key);
     }
 
@@ -296,18 +296,9 @@ export class FieldSet extends FormulaireElement implements Observer {
         const calc_type: CalculatorType = ct == undefined ? this.parentForm.calculatorType : CalculatorType[ct];
         this.setPropValue("calcType", calc_type);
 
-        const nt: string = json["nodeType"];
-        const node_type: ComputeNodeType = nt == undefined ? ComputeNodeType.None : ComputeNodeType[nt];
-
         const dnt: string = json["defaultNodeType"];
-        const default_node_type: ComputeNodeType = dnt == undefined ? ComputeNodeType.None : ComputeNodeType[dnt];
-
-        if (nt !== undefined && dnt !== undefined)
-            throw new Error("les champs 'nodeType' et 'defaultNodeType' ne doivent pas être définis en même temps")
-
-        const ntype = dnt !== undefined ? default_node_type : node_type;
-
-        this.setPropValue("nodeType", ntype);
+        const node_type: ComputeNodeType = dnt == undefined ? this.parentForm.nodeType : ComputeNodeType[dnt];
+        this.setPropValue("nodeType", node_type);
 
         const st: string = json["defaultStructType"];
         if (st)
-- 
GitLab


From 4453d496d5edd7154f5e2b9576bcc60794a257a8 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Tue, 22 May 2018 09:57:08 +0200
Subject: [PATCH 13/20] =?UTF-8?q?=20#45=20remous=20:=20correction=20d'un?=
 =?UTF-8?q?=20bug=20d'affichage=20quand=20il=20n'y=20a=20pas=20de=20r?=
 =?UTF-8?q?=C3=A9sultat=20vari=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/components/remous-results/remous-results.component.ts | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/app/components/remous-results/remous-results.component.ts b/src/app/components/remous-results/remous-results.component.ts
index 5f6f261c0..07bbca115 100644
--- a/src/app/components/remous-results/remous-results.component.ts
+++ b/src/app/components/remous-results/remous-results.component.ts
@@ -225,7 +225,8 @@ export class RemousResultsComponent {
         // le dernier dataset de la liste datasets est dessiné en 1er
 
         this._remousResults.update();
-        this.varResultsComponent.results = this._remousResults.varResults;
+        if (this.varResultsComponent)
+            this.varResultsComponent.results = this._remousResults.varResults;
 
         const penteFond: number = this._remousResults.penteFond;
 
-- 
GitLab


From 7340ffad1f7f0b704a3c6dd1b80b452713f072a1 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Tue, 22 May 2018 12:24:17 +0200
Subject: [PATCH 14/20] =?UTF-8?q?=20#45=20classe=20FieldSet=20:=20modif=20?=
 =?UTF-8?q?update()=20pour=20=C3=A9viter=20un=20crash=20quand=20la=20nouve?=
 =?UTF-8?q?lle=20valeur=20est=20undefined?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/formulaire/fieldset.ts | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 1a35a58bb..9d89536e2 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -378,17 +378,17 @@ export class FieldSet extends FormulaireElement implements Observer {
         if (data.action)
             switch (data.action) {
                 case "select":
-                    if (data.value.id.indexOf("select_section_") != -1)  // sections paramétrées
+                    if (sender.id === "select_section")  // sections paramétrées
                         this.setPropValue("nodeType", data.value.value);
-                    if (data.value.id.indexOf("select_ouvrage") != -1) // ouvrages parallèles
+                    if (sender.id === "select_ouvrage") // ouvrages parallèles
                         this.setPropValue("structureType", data.value.value);
-                    else if (data.value.id.indexOf("select_loidebit1") != -1) // ouvrages parallèles
+                    else if (sender.id === "select_loidebit1") // ouvrages parallèles
                         this.setPropValue("loiDebit", data.value.value);
-                    else if (data.value.id.indexOf("select_loidebit2") != -1) // ouvrages parallèles
+                    else if (sender.id === "select_loidebit2") // ouvrages parallèles
                         this.setPropValue("loiDebit", data.value.value);
-                    else if (data.value.id.indexOf("select_resolution") != -1) // courbes de remous, méthode de résolution
+                    else if (sender.id === "select_resolution") // courbes de remous, méthode de résolution
                         this.setPropValue("methodeResolution", data.value.value);
-                    else if (data.value.id.indexOf("select_target") != -1) // courbes de remous, variable à calculer
+                    else if (sender.id === "select_target") // courbes de remous, variable à calculer
                         this.setPropValue("varCalc", data.value.value);
                     break;
             }
-- 
GitLab


From 07c584c62f4bbdb1380d06622d9be0994f0a9b43 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Tue, 22 May 2018 12:26:09 +0200
Subject: [PATCH 15/20] =?UTF-8?q?=20#45=20d=C3=A9placement=20(FieldSet=20-?=
 =?UTF-8?q?>=20FormulaireParallelStructure)=20de=20la=20v=C3=A9rification?=
 =?UTF-8?q?=20de=20la=20coh=C3=A9rence=20des=20valeurs=20de=20type=20de=20?=
 =?UTF-8?q?structure/loi=20de=20d=C3=A9bit=20(cf.=20m=C3=A9thode=20Formula?=
 =?UTF-8?q?ireParallelStructure.adjustProperties())?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../concrete/form-parallel-structures.ts      | 28 +++++++++++++++++--
 src/app/formulaire/fieldset.ts                | 17 +++--------
 2 files changed, 29 insertions(+), 16 deletions(-)

diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index d06971f41..358fa6e66 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -1,4 +1,4 @@
-import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionNub, StructureType, LoiDebit } from "jalhyd";
+import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionNub, StructureType, LoiDebit, StructureProperties, Props } from "jalhyd";
 
 import { FormulaireDefinition } from "../form-definition";
 import { CalculatorResults } from "../../../results/calculator-results";
@@ -199,6 +199,28 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
         return n as FieldsetContainer;
     }
 
+    /**
+     * après une modification, détermine si les propriétés sont compatibles entre elles et les ajuste au besoin
+     * @param props propriétés à vérifier
+     * @param name nom de la propriété qui vient de changer
+     * @param val nouvelle valeur de la propriété
+     */
+    private adjustProperties(props: Props, name: string, val: any) {
+        const res: Props = props.clone();
+
+        // si prop=type d'ouvrage, on prend une loi de débit compatible avec (spécifique aux ouvrages //) comme valeur par défaut
+        if (name === "structureType") {
+            if (!StructureProperties.isCompatibleValues(val, res.getPropValue("loiDebit")))
+                res.setPropValue("loiDebit", StructureProperties.findCompatibleLoiDebit(val));
+        }
+        // si prop=loi débit, on prend un type d'ouvrage compatible
+        else if (name === "loiDebit")
+            if (!StructureProperties.isCompatibleValues(res.getPropValue("structureType"), val))
+                res.setPropValue("structureType", StructureProperties.findCompatibleStructure(val));
+
+        return res;
+    }
+
     /**
      * abonnement en tant qu'observateur du FieldsetContainer
      */
@@ -236,8 +258,8 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
         else if (sender instanceof FieldSet && data.action == "propertyChange") {
             switch (sender.id) {
                 case "fs_ouvrage":
-                    // this.replaceCurrentSessionNub(sender.properties);
-                    const newNub = this.replaceSessionNub(sender.sessionNub, sender.properties);
+                    const props = this.adjustProperties(sender.properties, data["name"], data["value"]);
+                    const newNub = this.replaceSessionNub(sender.sessionNub, props);
                     sender.setSessionNub(newNub);
                     this.reset();
                     break;
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 9d89536e2..fefcb06c4 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -1,4 +1,4 @@
-import { CalculatorType, ComputeNodeType, ParamDefinition, LoiDebit, StructureType, Props, SessionNub, StructureProperties, Observer } from "jalhyd";
+import { CalculatorType, ComputeNodeType, ParamDefinition, LoiDebit, StructureType, Props, SessionNub, Observer } from "jalhyd";
 
 import { FormulaireElement } from "./formulaire-element";
 import { Dependency } from "./dependency/dependency";
@@ -56,6 +56,7 @@ export class FieldSet extends FormulaireElement implements Observer {
 
     public setSessionNub(sn: SessionNub, update: boolean = true) {
         this._sessionNub = sn;
+        this._props.setProps(sn.properties, this);
         if (update)
             this.updateFields();
     }
@@ -116,18 +117,8 @@ export class FieldSet extends FormulaireElement implements Observer {
         return this._props.getPropValue(key);
     }
 
-    public setPropValue(key: string, val: any) {
-        if (this._props.setPropValue(key, val, this)) {
-            // si prop=type d'ouvrage, on prend une loi de débit compatible avec (spécifique aux ouvrages //) comme valeur par défaut
-            if (key === "structureType") {
-                if (!StructureProperties.isCompatibleValues(val, this.getPropValue("loiDebit")))
-                    this.setPropValue("loiDebit", StructureProperties.findCompatibleLoiDebit(val));
-            }
-            // si prop=loi débit, on prend un type d'ouvrage compatible
-            else if (key === "loiDebit")
-                if (!StructureProperties.isCompatibleValues(this.getPropValue("structureType"), val))
-                    this.setPropValue("structureType", StructureProperties.findCompatibleStructure(val));
-        }
+    public setPropValue(key: string, val: any): boolean {
+        return this._props.setPropValue(key, val, this);
     }
 
     /**
-- 
GitLab


From 08a267cee2f9f10d19590dedffa09692b333051a Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Wed, 23 May 2018 10:04:26 +0200
Subject: [PATCH 16/20] =?UTF-8?q?=20#45=20ouvrages=20:=20correction=20d'un?=
 =?UTF-8?q?=20bug=20r=C3=A9cr=C3=A9ant=20un=20Nub=20non=20n=C3=A9cessaire?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../definition/concrete/form-parallel-structures.ts         | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index 358fa6e66..a9afe9717 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -69,11 +69,7 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
     }
 
     private get parallelStructureNub(): ParallelStructure {
-        const params = {
-            "calcType": CalculatorType.ParallelStructure,
-            "nodeType": ComputeNodeType.None
-        };
-        return this.getSessionNub(params).nub as ParallelStructure;
+        return this.currentSessionNub.nub as ParallelStructure;
     }
 
     public createFieldset(parent: FormulaireNode, json: {}, data?: {}): FieldSet {
-- 
GitLab


From 3a2f4683278dfea6612070017b2cb1a4f1686d48 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Wed, 23 May 2018 10:23:34 +0200
Subject: [PATCH 17/20]  #45 FormulaireParallelStructure.createStructNub() :
 modif purement formelle

---
 .../definition/concrete/form-parallel-structures.ts         | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index a9afe9717..64b83a279 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -2,8 +2,6 @@ import { CalculatorType, ComputeNodeType, Structure, ParallelStructure, SessionN
 
 import { FormulaireDefinition } from "../form-definition";
 import { CalculatorResults } from "../../../results/calculator-results";
-import { ServiceFactory } from "../../../services/service-factory";
-import { ParamService } from "../../../services/param/param.service";
 import { FormDefParamToCalculate } from "../form-def-paramcalc";
 import { FormDefFixedVar } from "../form-def-fixedvar";
 import { FormDefParallelStructures } from "../form-def-parallel-structures";
@@ -49,14 +47,12 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
     }
 
     private createStructNub(templ: FieldsetTemplate): SessionNub {
-        const paramService: ParamService = ServiceFactory.instance.paramService;
-
         // valeurs par défaut de CalculatorType, ComputeNodeType, StructureType, LoiDebit
         // !!! attention !!! pour l'instant, il doit y avoir cohérence entre ces valeurs et celles du fichier de conf
         // cad valeur par défaut du 1er select (type d'ouvrage), du 2ème (loi de débit).
         // A terme, il faudrait analyser le fichier de conf (dépendances d'existence) pour déterminer automatiquement ces valeurs
         const params = FieldSet.makeDefaultProps(templ.calcTypeFromConfig, templ.defaultNodeTypeFromConfig);
-        return paramService.createSessionNub(params);
+        return this.createSessionNub(params);
     }
 
     /**
-- 
GitLab


From bfd239d44644d7075a8655132e98afdfa9778e7b Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Wed, 23 May 2018 16:11:50 +0200
Subject: [PATCH 18/20] =?UTF-8?q?=20#45=20modif=20composant=20LoadCalculat?=
 =?UTF-8?q?orComponent=20pour=20afficher=20et=20permettre=20de=20s=C3=A9le?=
 =?UTF-8?q?ctionner=20les=20calculettes=20trouv=C3=A9es=20dans=20le=20fich?=
 =?UTF-8?q?ier=20session=20-=20ajout=20du=20champ=20"uid"=20pour=20les=20"?=
 =?UTF-8?q?form"=20dans=20les=20fichiers=20session?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.component.ts                      |  7 ++-
 .../load-calculator.component.html            |  2 +
 .../load-calculator.component.ts              | 63 ++++++++++++++++---
 .../save-calculator.component.ts              |  6 ++
 .../formulaire/definition/form-definition.ts  |  2 +
 .../services/formulaire/formulaire.service.ts | 51 +++++++++++++--
 6 files changed, 114 insertions(+), 17 deletions(-)

diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index a1024c868..2f6879d8a 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -30,6 +30,11 @@ import { SaveCalculatorComponent } from './components/save-calculator/save-calcu
 export class AppComponent implements OnInit, OnDestroy, Observer {
   private _displayErrorDialog: boolean = false;
 
+  /**
+   * liste des calculettes. Forme des objets :
+   * "title": nom de la calculette
+   * "uid": id unique du formulaire
+   */
   private _calculators: any[] = [];
 
   /**
@@ -339,7 +344,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
 
     const prom: Promise<any[]> = compRef.instance.run();
     prom.then(list => {
-      this.formulaireService.loadSession(compRef.instance.selectedFile);
+      this.formulaireService.loadSession(compRef.instance.selectedFile, compRef.instance.calculators);
       compRef.destroy();
     });
   }
diff --git a/src/app/components/load-calculator/load-calculator.component.html b/src/app/components/load-calculator/load-calculator.component.html
index d6ee5866c..b596cadbf 100644
--- a/src/app/components/load-calculator/load-calculator.component.html
+++ b/src/app/components/load-calculator/load-calculator.component.html
@@ -16,6 +16,8 @@
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-danger relative waves-light" (click)="loadDialog.hide();cancelLoad()" mdbRippleRadius>{{uitextCancel}}</button>
+                <button type="button" class="btn btn-success waves-light" [disabled]="disableLoadButton" (click)="loadDialog.hide();confirmLoad()"
+                    mdbRippleRadius>{{uitextLoad}}</button>
             </div>
         </div>
     </div>
diff --git a/src/app/components/load-calculator/load-calculator.component.ts b/src/app/components/load-calculator/load-calculator.component.ts
index f07ecb22a..c1a3f8b71 100644
--- a/src/app/components/load-calculator/load-calculator.component.ts
+++ b/src/app/components/load-calculator/load-calculator.component.ts
@@ -12,6 +12,11 @@ export class LoadCalculatorComponent {
 
     private _selectFile: File;
 
+    /**
+     * liste des calculettes affichées. Forme des objets :
+     * "title": nom de la calculette
+     * "selected": flag de sélection pour la sauvegarde
+     */
     private _calculators: any[];
 
     /**
@@ -36,6 +41,11 @@ export class LoadCalculatorComponent {
         return "Annuler";
     }
 
+    private get uitextLoad() {
+        // return this.intlService.localizeText("INFO_OPTION_NO");
+        return "Charger";
+    }
+
     private get uitextSelectAll() {
         return "Toutes";
     }
@@ -44,6 +54,24 @@ export class LoadCalculatorComponent {
         return "Aucune";
     }
 
+    /**
+     * calcule l'état du bouton charger
+     */
+    private get disableLoadButton() {
+        // pas de fichier sélectionné -> bouton grisé
+        if (this._selectFile === undefined)
+            return true;
+
+        // au moins une calculette sélectionnée -> dégrisé
+        if (this._calculators !== undefined)
+            for (const c of this._calculators)
+                if (c.selected)
+                    return false;
+
+        // grisé sinon
+        return true;
+    }
+
     public run(): Promise<any[]> {
         // promise de gestion de la confirmation/annulation de la sauvegarde
         return new Promise((resolve, reject) => {
@@ -61,29 +89,44 @@ export class LoadCalculatorComponent {
         return this._calculators && this._calculators.length != 0;
     }
 
-    private onFileSelect() {
+    private getSelectedFile(): File {
         const files: { [key: string]: File } = this.fileSelector.nativeElement.files;
-        for (let key in files) {
-            if (!isNaN(parseInt(key))) {
-                this._selectFile = files[key];
-                this.confirmLoad();
-                break;
-            }
-        }
+        for (const key in files)
+            if (!isNaN(parseInt(key)))
+                return files[key];
+        return undefined;
+    }
+
+    private onFileSelect() {
+        const formService = ServiceFactory.instance.formulaireService;
+        this._selectFile = this.getSelectedFile();
+        if (this._selectFile !== undefined)
+            formService.calculatorInfosFromSessionFile(this._selectFile).then(
+                calcInfos => {
+                    this._calculators = calcInfos;
+                    for (const n of this._calculators)
+                        n["selected"] = true;
+                });
     }
 
     public get selectedFile(): File {
         return this._selectFile;
     }
 
+    public get calculators(): any[] {
+        return this._calculators;
+    }
+
     private isSelected(c: any) {
         return c.selected ? "checked" : undefined;
     }
 
     private onCheckCalc(event: any) {
         for (const c of this._calculators)
-            if (c.uid == +event.target.value)
+            if (c.uid == +event.target.value) {
                 c.selected = event.target.checked;
+                break;
+            }
     }
 
     private selectAll() {
@@ -110,7 +153,7 @@ export class LoadCalculatorComponent {
     }
 
     /**
-     * appelé quand on clique sur sauver
+     * appelé quand on clique sur charger
      */
     private confirmLoad() {
         this.confirmed = true;
diff --git a/src/app/components/save-calculator/save-calculator.component.ts b/src/app/components/save-calculator/save-calculator.component.ts
index 99babe24c..f00fd173e 100644
--- a/src/app/components/save-calculator/save-calculator.component.ts
+++ b/src/app/components/save-calculator/save-calculator.component.ts
@@ -7,6 +7,12 @@ import { InternationalisationService } from "../../services/internationalisation
     templateUrl: "./save-calculator.component.html"
 })
 export class SaveCalculatorComponent {
+    /**
+     * liste des calculettes affichées. Forme des objets :
+     * "title": nom de la calculette
+     * "selected": flag de sélection pour la sauvegarde
+     * "uid": id unique du formulaire
+     */
     private _calculators: any[];
 
     /**
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 7d577b03d..1a4340300 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -442,6 +442,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
     public JSONserialise(): {} {
         let res = {};
         res["id"] = this.calculatorName;
+        res["uid"] = this.uid;
         res["props"] = this._currentSessionNub.properties.props;
         res["elements"] = this.serialiseKids();
         return { "form": res };
@@ -488,6 +489,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
                     this._calculatorName = elements[k];
                     break;
 
+                case "uid":
                 case "props":
                     break;
 
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index c08bb630e..b89e1baaa 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -366,13 +366,18 @@ export class FormulaireService extends Observable {
         });
     }
 
-    public loadSession(f: File) {
+    /**
+     * charge une session en tenant compte des calculettes sélectionnées
+     * @param f fichier session
+     * @param formInfos infos sur les calculettes @see LoadCalculatorComponent._calculators
+     */
+    public loadSession(f: File, formInfos: any[]) {
         this.readSingleFile(f).then(s => {
             const session = JSON.parse(s);
             for (const k in session)
                 switch (k) {
                     case "session":
-                        this.deserialiseSession(session[k]);
+                        this.deserialiseSession(session[k], formInfos);
                         break;
 
                     default:
@@ -383,6 +388,36 @@ export class FormulaireService extends Observable {
         });
     }
 
+    /**
+     * obtient des infos (nom, uid des calculettes) d'un fichier session
+     * @param f fichier session
+     */
+    public calculatorInfosFromSessionFile(f: File): Promise<any[]> {
+        return this.readSingleFile(f).then(s => {
+            const res: any[] = [];
+            const session = JSON.parse(s);
+
+            // liste des noms de calculettes
+            for (const k in session)
+                switch (k) {
+                    case "session":
+                        const sess = session[k];
+                        const elems = sess["elements"];
+                        for (const e of elems)
+                            for (const k in e)
+                                if (k === "form") {
+                                    const form = e[k];
+                                    res.push({ "uid": form["uid"], "title": form["id"] });
+                                }
+                        break;
+
+                    default:
+                        throw new Error(`session file : invalid key '${k}'`);
+                }
+            return res;
+        });
+    }
+
     public saveForm(f: FormulaireDefinition) {
         this.notifyObservers({
             "action": "saveForm",
@@ -396,14 +431,18 @@ export class FormulaireService extends Observable {
         this.createFormulaire(ct, elements);
     }
 
-    private deserialiseSessionElement(element: {}) {
+    private deserialiseSessionElement(element: {}, formInfos: any[]) {
         const keys = Object.keys(element);
         if (keys.length !== 1)
             throw new Error(`session file : invalid session object '${element}'`);
 
         switch (keys[0]) {
             case "form":
-                this.deserialiseForm(element[keys[0]]);
+                const form = element[keys[0]];
+
+                for (const i of formInfos)
+                    if (i["uid"] == form["uid"] && i["selected"])
+                        this.deserialiseForm(form);
                 break;
 
             default:
@@ -411,12 +450,12 @@ export class FormulaireService extends Observable {
         }
     }
 
-    private deserialiseSession(elements: {}) {
+    private deserialiseSession(elements: {}, formInfos: any[]) {
         for (const ks in elements)
             switch (ks) {
                 case "elements":
                     for (const e of elements[ks])
-                        this.deserialiseSessionElement(e);
+                        this.deserialiseSessionElement(e, formInfos);
                     break;
 
                 default:
-- 
GitLab


From d9a0000bb37edaf3f5f123fcc2bc0ebf38baec84 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Mon, 28 May 2018 15:50:21 +0200
Subject: [PATCH 19/20] =?UTF-8?q?=20#45=20ajout=20de=20l'=C3=A9criture=20d?=
 =?UTF-8?q?e=20la=20session=20dans=20un=20fichier=20(utilise=20la=20lib=20?=
 =?UTF-8?q?FileSaver)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 package.json                                      | 2 ++
 src/app/app.component.ts                          | 3 +--
 src/app/services/formulaire/formulaire.service.ts | 6 ++++++
 3 files changed, 9 insertions(+), 2 deletions(-)

diff --git a/package.json b/package.json
index 4f2602358..f612aaad6 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
     "angular-bootstrap-md": "^5.0.5",
     "angular2-chartjs": "^0.4.1",
     "core-js": "^2.4.1",
+    "file-saver": "^1.3.8",
     "he": "^1.1.1",
     "jalhyd": "file:../jalhyd/jalhyd-1.0.0.tgz",
     "ngx-md": "^3.1.1",
@@ -36,6 +37,7 @@
     "@angular/cli": "1.5.4",
     "@angular/compiler-cli": "^5.0.0",
     "@angular/language-service": "^5.0.0",
+    "@types/file-saver": "^1.3.0",
     "@types/jasmine": "~2.5.53",
     "@types/jasminewd2": "~2.0.2",
     "@types/node": "~6.0.60",
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 2f6879d8a..ed45462d1 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -241,8 +241,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
         elems.push(form.JSONserialise());
       }
     let session = { "session": { "elements": elems } };
-    console.log("---");
-    console.log(JSON.stringify(session));
+    this.formulaireService.saveSession(session);
   }
 
   private closeCalculator(form: FormulaireDefinition) {
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index b89e1baaa..ccf34e328 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -3,6 +3,7 @@ import { Response } from "@angular/http";
 import { Observable as rxObservable } from "rxjs/Observable";
 import "rxjs/add/operator/toPromise";
 import { decode } from "he";
+import { saveAs } from "file-saver"
 
 import { CalculatorType, EnumEx, Observable } from "jalhyd";
 
@@ -388,6 +389,11 @@ export class FormulaireService extends Observable {
         });
     }
 
+    public saveSession(session: {}) {
+        const blob = new Blob([JSON.stringify(session)], { type: "text/plain;charset=utf-8" });
+        saveAs(blob);
+    }
+
     /**
      * obtient des infos (nom, uid des calculettes) d'un fichier session
      * @param f fichier session
-- 
GitLab


From 957e38eb5c94f563750d119069df4ac3d47a25d3 Mon Sep 17 00:00:00 2001
From: "francois.grand" <francois.grand@irstea.fr>
Date: Mon, 28 May 2018 16:50:18 +0200
Subject: [PATCH 20/20] =?UTF-8?q?=20#45=20sauvegarde=20de=20la=20session?=
 =?UTF-8?q?=20:=20possibilit=C3=A9=20de=20saisir=20le=20nom=20de=20fichier?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.component.ts                           | 14 +++++++++++---
 .../save-calculator/save-calculator.component.html | 14 +++++++++++++-
 .../save-calculator/save-calculator.component.ts   | 11 +++++++++++
 src/app/services/formulaire/formulaire.service.ts  |  4 ++--
 4 files changed, 37 insertions(+), 6 deletions(-)

diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index ed45462d1..95e286053 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -228,11 +228,19 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     // passage de la liste, récupération d'une Promise pour traiter le résultat
     const prom: Promise<any[]> = compRef.instance.run(list);
     prom.then(list => {
-      this.saveSession(list)
+      let name = compRef.instance.filename;
+
+      // ajout extension ".json"
+      const re = /.+\.json/;
+      const match = re.exec(name.toLowerCase());
+      if (match === null)
+        name = name + ".json";
+
+      this.saveSession(list, name)
     });
   }
 
-  private saveSession(calcList: any[]) {
+  private saveSession(calcList: any[], filename) {
     let elems = [];
     for (const c of calcList)
       if (c.selected) {
@@ -241,7 +249,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
         elems.push(form.JSONserialise());
       }
     let session = { "session": { "elements": elems } };
-    this.formulaireService.saveSession(session);
+    this.formulaireService.saveSession(session, filename);
   }
 
   private closeCalculator(form: FormulaireDefinition) {
diff --git a/src/app/components/save-calculator/save-calculator.component.html b/src/app/components/save-calculator/save-calculator.component.html
index 3ead22063..cab54103e 100644
--- a/src/app/components/save-calculator/save-calculator.component.html
+++ b/src/app/components/save-calculator/save-calculator.component.html
@@ -5,11 +5,23 @@
                 <h4 class="modal-title w-100" id="myModalLabel">{{uitextDialogTitle}}</h4>
             </div>
             <div class="modal-body">
+                <!-- liste de calculettes avec check -->
                 <div *ngFor="let c of _calculators">
                     <input type="checkbox" value={{c.uid}} checked={{isSelected(c)}} (change)="onCheckCalc($event)">{{c.title}}
                 </div>
+
+                <!-- bouton "tout sélectionnner" -->
                 <button type="button" class="btn btn-mdb-color waves-light" (click)="selectAll()" mdbRippleRadius>{{uitextSelectAll}}</button>
-                <button type="button" class="btn btn-mdb-color waves-light" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button>
+
+                <!-- bouton "tout désélectionnner" -->
+                <button type="button" class="btn btn-mdb-color waves-light py-10" (click)="deselectAll()" mdbRippleRadius>{{uitextDeselectAll}}</button>
+
+                <!-- nom du fichier -->
+                <div class="md-form form-sm mt-4">
+                    <input mdbActive type="text" id="form1" class="form-control" [(ngModel)]="_filename">
+                    <!-- on utilise [innerHTML] pour que les codes HTML comme &nbsp; soient interprétés correctement -->
+                    <label for="form1" [innerHTML]="_filenameTitle"></label>
+                </div>
             </div>
             <div class="modal-footer">
                 <button type="button" class="btn btn-danger relative waves-light" (click)="saveDialog.hide();cancelSave()" mdbRippleRadius>{{uitextCloseDialogNo}}</button>
diff --git a/src/app/components/save-calculator/save-calculator.component.ts b/src/app/components/save-calculator/save-calculator.component.ts
index f00fd173e..6e30cd2db 100644
--- a/src/app/components/save-calculator/save-calculator.component.ts
+++ b/src/app/components/save-calculator/save-calculator.component.ts
@@ -15,6 +15,13 @@ export class SaveCalculatorComponent {
      */
     private _calculators: any[];
 
+    /**
+     * nom du fichier
+     */
+    private _filename: string = "session";
+
+    private _filenameTitle = "Nom de fichier";
+
     /**
      * événement émis lors du clic sur "annuler"/"enregister"
      * utilisé par la promise de gestion de la confirmation/annulation de la sauvegarde
@@ -50,6 +57,10 @@ export class SaveCalculatorComponent {
         return "Aucune";
     }
 
+    public get filename(): string {
+        return this._filename;
+    }
+
     public run(calcList: any[]): Promise<any[]> {
         this._calculators = calcList;
 
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index ccf34e328..5984c3f9d 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -389,9 +389,9 @@ export class FormulaireService extends Observable {
         });
     }
 
-    public saveSession(session: {}) {
+    public saveSession(session: {}, filename?: string) {
         const blob = new Blob([JSON.stringify(session)], { type: "text/plain;charset=utf-8" });
-        saveAs(blob);
+        saveAs(blob, filename);
     }
 
     /**
-- 
GitLab