From 39019b9d50966df18cab397b41fa9dab52ad7f80 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Fri, 15 Mar 2019 17:50:55 +0100
Subject: [PATCH 01/39] =?UTF-8?q?[WIP]=20nouveau=20syst=C3=A8me=20de=20par?=
 =?UTF-8?q?am=C3=A8tres=20li=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/calculate-all-params.e2e-spec.ts          |   2 -
 jalhyd_branch                                 |   2 +-
 .../app-setup/app-setup.component.ts          |   4 +-
 .../dialog-edit-param-values.component.html   |   6 +-
 .../dialog-edit-param-values.component.scss   |   1 +
 .../dialog-edit-param-values.component.ts     |  38 +++++-
 .../generic-input/generic-input.component.ts  |   2 -
 .../param-field-line.component.html           |   4 +-
 .../param-field-line.component.ts             |   4 +-
 .../param-link/param-link.component.html      |   3 -
 .../param-link/param-link.component.ts        | 123 ++++++++----------
 .../param-values/param-values.component.ts    |  38 ++++--
 .../concrete/form-parallel-structures.ts      |   2 -
 .../definition/form-def-fixedvar.ts           |  16 +--
 .../definition/form-def-paramcalc.ts          |   8 +-
 .../formulaire/definition/form-definition.ts  |  16 +--
 src/app/formulaire/fieldset.ts                |   3 +-
 src/app/formulaire/ngparam.ts                 |  86 +++++++-----
 src/app/results/var-results.ts                |   1 -
 .../services/formulaire/formulaire.service.ts |  98 ++++----------
 src/locale/messages.en.json                   |   1 +
 src/locale/messages.fr.json                   |   1 +
 22 files changed, 225 insertions(+), 234 deletions(-)

diff --git a/e2e/calculate-all-params.e2e-spec.ts b/e2e/calculate-all-params.e2e-spec.ts
index e71105a54..eebcfa7e8 100644
--- a/e2e/calculate-all-params.e2e-spec.ts
+++ b/e2e/calculate-all-params.e2e-spec.ts
@@ -32,11 +32,9 @@ describe("ngHyd − calculate all parameters of all calculators", () => {
         // get all parameters IDs
         const inputs = await calcPage.getParamInputsHavingCalcMode();
 
-        // console.log("> Inputs having calc", inputs.length);
         if (inputs.length > 0) {
           // for each param
           for (const input of inputs) {
-            // console.log(">> Trying", await input.getAttribute("id"));
             // click "calc" mode button for this parameter
             await calcPage.setParamMode(input, "cal");
             // check that only 1 button is in "calc" state
diff --git a/jalhyd_branch b/jalhyd_branch
index 1f7391f92..6ba102133 100644
--- a/jalhyd_branch
+++ b/jalhyd_branch
@@ -1 +1 @@
-master
+54-ameliorer-le-filtre-de-choix-des-parametres-pouvant-etre-lies
diff --git a/src/app/components/app-setup/app-setup.component.ts b/src/app/components/app-setup/app-setup.component.ts
index 0b373a0d2..87b96145b 100644
--- a/src/app/components/app-setup/app-setup.component.ts
+++ b/src/app/components/app-setup/app-setup.component.ts
@@ -83,7 +83,7 @@ export class ApplicationSetupComponent extends BaseComponent implements Observer
     public storePreferences() {
         this.appSetupService.saveValuesIntoLocalStorage();
         this.snackBar.open(this.intlService.localizeText("INFO_SNACKBAR_SETTINGS_SAVED"), "OK", {
-            duration: 1200
+            duration: 2500
         });
     }
 
@@ -91,7 +91,7 @@ export class ApplicationSetupComponent extends BaseComponent implements Observer
         const text = this.intlService.localizeText("INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED");
         this.appSetupService.restoreDefaultValues().then(() => {
             this.snackBar.open(text, "OK", {
-                duration: 1200
+                duration: 2500
             });
         });
     }
diff --git a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.html b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.html
index c4ace21ec..8fbe604a5 100644
--- a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.html
+++ b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.html
@@ -20,7 +20,7 @@
         <form>
             <mat-form-field>
                 <input matInput class="form-control" type="number" inputmode="numeric" name="min-value" step="0.01"
-                    [placeholder]="uitextValeurMini" [(ngModel)]="param.minValue" #min="ngModel" name="min"
+                    [placeholder]="uitextValeurMini" [(ngModel)]="minValue" #min="ngModel" name="min"
                     [appJalhydModelValidationMin]="param" required pattern="^-?([0-9]*\.)?([0-9]+[Ee]-?)?[0-9]+$">
 
                 <mat-error *ngIf="min.errors">
@@ -35,7 +35,7 @@
 
             <mat-form-field>
                 <input matInput class="form-control" type="number" inputmode="numeric" name="max-value" step="0.01"
-                    [placeholder]="uitextValeurMaxi" [(ngModel)]="param.maxValue" #max="ngModel" name="max"
+                    [placeholder]="uitextValeurMaxi" [(ngModel)]="maxValue" #max="ngModel" name="max"
                     [appJalhydModelValidationMax]="param" required pattern="^-?([0-9]*\.)?([0-9]+[Ee]-?)?[0-9]+$">
 
                     <mat-error *ngIf="max.errors">
@@ -50,7 +50,7 @@
 
             <mat-form-field>
                 <input matInput class="form-control" type="number" inputmode="numeric" name="step-value" step="0.01"
-                    [placeholder]="uitextPasVariation" [(ngModel)]="param.stepValue" #step="ngModel" name="step"
+                    [placeholder]="uitextPasVariation" [(ngModel)]="stepValue" #step="ngModel" name="step"
                     [appJalhydModelValidationStep]="param" required pattern="^-?([0-9]*\.)?([0-9]+[Ee]-?)?[0-9]+$">
 
                     <mat-error *ngIf="step.errors">
diff --git a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.scss b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.scss
index 111eef749..0ffd355d6 100644
--- a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.scss
+++ b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.scss
@@ -19,6 +19,7 @@ mat-form-field {
 
 .min-max-step-container {
     margin-top: -8px;
+    padding-bottom: 1em; // for possible mat-error on step field
 }
 
 .mat-dialog-content {
diff --git a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.ts b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.ts
index 0d1ad08b8..bb9168725 100644
--- a/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.ts
+++ b/src/app/components/dialog-edit-param-values/dialog-edit-param-values.component.ts
@@ -114,6 +114,32 @@ export class DialogEditParamValuesComponent implements OnInit {
         };
     }
 
+    // proxy to model values
+
+    public get minValue() {
+        return this.param.minValue;
+    }
+
+    public set minValue(v) {
+        this.param.setMinValue(this, v);
+    }
+
+    public get maxValue() {
+        return this.param.maxValue;
+    }
+
+    public set maxValue(v) {
+        this.param.setMaxValue(this, v);
+    }
+
+    public get stepValue() {
+        return this.param.stepValue;
+    }
+
+    public set stepValue(v) {
+        this.param.setStepValue(this, v);
+    }
+
     /**
      * regular expression pattern for values list validation (depends on decimal separator)
      */
@@ -172,7 +198,7 @@ export class DialogEditParamValuesComponent implements OnInit {
                 vals.push(Number(e));
             }
         });
-        this.param.valueList = vals;
+        this.param.setValueList(this, vals);
     }
 
     public toggleViewChart() {
@@ -280,24 +306,24 @@ export class DialogEditParamValuesComponent implements OnInit {
         // init min / max / step
         if (this.isMinMax) {
             if (this.param.minValue === undefined) {
-                this.param.minValue = this.param.getValue() / 2;
+                this.param.setMinValue(this, this.param.getValue() / 2);
             }
             if (this.param.maxValue === undefined) {
-                this.param.maxValue = this.param.getValue() * 2;
+                this.param.setMaxValue(this, this.param.getValue() * 2);
             }
             let step = this.param.stepValue;
             if (step === undefined) {
                 step = (this.param.maxValue - this.param.minValue) / 20;
             }
-            this.param.stepValue = step;
+            this.param.setStepValue(this, step);
         }
         // init values list
         if (this.isListe) {
             if (this.param.valueList === undefined) {
                 if (this.param.isDefined) {
-                    this.param.valueList = [ this.param.getValue() ];
+                    this.param.setValueList(this, [ this.param.getValue() ]);
                 } else {
-                    this.param.valueList = [];
+                    this.param.setValueList(this, []);
                 }
                 // set form control initial value
                 this.valuesListForm.controls.valuesList.setValue(this.valuesList);
diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index 4b34c8113..8f9f2f7a2 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -182,7 +182,6 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
      * événement de changement de la valeur du modèle
      */
     private emitModelChanged() {
-        // console.log("emit model change", this.constructor.name);
         this.change.emit({ "action": "model", "value": this.getModelValue() });
     }
 
@@ -240,7 +239,6 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
      * appelé quand les @Input changent
      */
     public ngOnChanges() {
-        // console.log("ng on changes (generic input)", this.constructor.name);
         this.updateAll();
     }
 
diff --git a/src/app/components/param-field-line/param-field-line.component.html b/src/app/components/param-field-line/param-field-line.component.html
index a9847d2b2..973ec8c81 100644
--- a/src/app/components/param-field-line/param-field-line.component.html
+++ b/src/app/components/param-field-line/param-field-line.component.html
@@ -10,10 +10,10 @@
         <param-computed *ngIf="isRadioCalChecked" [title]="title" [param]="param"></param-computed>
 
         <!-- composant pour gérer le cas "paramètre à varier" (min-max/liste de valeurs) -->
-        <param-values *ngIf="isRadioVarChecked" [title]="title" [param]="param" (valid)=onParamValuesValid($event)></param-values>
+        <param-values *ngIf="isRadioVarChecked" [title]="title" [param]="param" (change)="onInputChange($event)" (valid)=onParamValuesValid($event)></param-values>
     
         <!-- composant pour gérer le cas "paramètre lié" -->
-        <param-link *ngIf="isRadioLinkChecked" [title]="title" [param]="param" (valid)=onParamValuesValid($event)></param-link>
+        <param-link *ngIf="isRadioLinkChecked" [title]="title" [param]="param" (change)="onInputChange($event)" (valid)=onParamValuesValid($event)></param-link>
     </div>
 
     <div class="toggle-group-container" fxFlex="0 0 auto">
diff --git a/src/app/components/param-field-line/param-field-line.component.ts b/src/app/components/param-field-line/param-field-line.component.ts
index 435f63cfb..198dc15cd 100644
--- a/src/app/components/param-field-line/param-field-line.component.ts
+++ b/src/app/components/param-field-line/param-field-line.component.ts
@@ -199,14 +199,14 @@ export class ParamFieldLineComponent implements OnChanges {
         if (this._formService.formulaires.length > 0) {
             // au moins 2 modules de calcul ouverts
             if (this._formService.formulaires.length > 1) {
-                return this._formService.filterLinkableValues(this._formService.getLinkableValues(this.param)).length > 0;
+                return this._formService.getLinkableValues(this.param).length > 0;
             }
 
             // ou un seul module de calcul "ouvrages parallèles"
             if (this._formService.formulaires[0].calculatorType === CalculatorType.ParallelStructure) {
                 const ps: ParallelStructure = this._formService.formulaires[0].currentNub as ParallelStructure;
                 if (ps.structures.length > 1) {
-                    return this._formService.filterLinkableValues(this._formService.getLinkableValues(this.param)).length > 0;
+                    return this._formService.getLinkableValues(this.param).length > 0;
                 }
             }
 
diff --git a/src/app/components/param-link/param-link.component.html b/src/app/components/param-link/param-link.component.html
index c1a76465e..9f8fce8bf 100644
--- a/src/app/components/param-link/param-link.component.html
+++ b/src/app/components/param-link/param-link.component.html
@@ -5,9 +5,6 @@
             {{ selectItemLabel(e) }}
         </mat-option>
     </mat-select>
-    <mat-error>
-        {{ message }}
-    </mat-error>
 </mat-form-field>
 
 <mat-icon #tooltip="matTooltip" [matTooltip]="tooltipText" matTooltipClass="linked-param-tooltip"
diff --git a/src/app/components/param-link/param-link.component.ts b/src/app/components/param-link/param-link.component.ts
index a382729a4..5601ced0a 100644
--- a/src/app/components/param-link/param-link.component.ts
+++ b/src/app/components/param-link/param-link.component.ts
@@ -2,7 +2,7 @@ import { Component, Input, Output, EventEmitter, OnChanges, OnDestroy } from "@a
 
 import { NgParameter } from "../../formulaire/ngparam";
 import { ServiceFactory } from "../../services/service-factory";
-import { ParamValueMode, Observer, Structure, ParallelStructure } from "jalhyd";
+import { LinkedValue, ParamValueMode, Observer, Structure } from "jalhyd";
 import { FormulaireService } from "../../services/formulaire/formulaire.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
 import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
@@ -26,32 +26,18 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
     private valid: EventEmitter<boolean>;
 
     /**
-     * indice actuel du paramètre sélectionné dans la liste
+     * événement signalant un changement de valeur du modèle
+     * @TODO l'utiliser aussi pour le changement de validité à
+     * la place de this.valid, comme dans GenericInputComponent
      */
-    private _currentIndex = -1;
+    @Output()
+    protected change = new EventEmitter<any>();
 
-    /**
-     * message affiché à côté du select des paramètres
-     */
-    private _message: string;
+    /** indice actuel du paramètre sélectionné dans la liste */
+    private _currentIndex = -1;
 
-    /**
-     * liste des paramètres liables sous la forme
-     * {"name":<étiquette>, "value":<valeur liable>, "nub":<Nub d'origine du paramètre>, "formTitle":<nom du module de calcul lié au nub>}
-     *
-     * l'étiquette "name" (cf. INubReference.defineReference dans jalhyd) est de la forme <n | ouvrage[n] | N1>[.[N2]]
-     *     n : indice de de l'ouvrage dans le cas des ouvrages parallèles
-     *     N1 : un nom de paramètre/résultat (dans le cas d'un résultat, il est suivi d'un point)
-     *     N2 : nom de résultat complémentaire (optionnel)
-     *     ex :
-     *       Q, Z1 (paramètres)
-     *       J. (résultat)
-     *       .Yf (résultat complémentaire du résultat courant)
-     *       Q.Yf (résultat complémentaire du résultat nommé "Q")
-     *       0.Q : paramètre Q du 1er ouvrage (ouvrages parallèles)
-     *       ouvrage[1].Q_Mode : résultat complémentaire du 2ème ouvrage (ouvrages parallèles)
-     */
-    private _linkableParams: any[];
+    /** liste des paramètres liables */
+    private _linkableParams: LinkedValue[];
 
     private _formService: FormulaireService;
 
@@ -71,24 +57,21 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
         return this._linkableParams;
     }
 
-    public get message() {
-        return this._message;
-    }
-
     public get label() {
         return this.intlService.localizeText("INFO_PARAMFIELD_PARAMLIE_LABEL");
     }
 
-    public set currentLinkedParam(p: any) {
+    public set currentLinkedParam(p: LinkedValue) {
         for (let i = 0; i < this._linkableParams.length; i++) {
-            if (this._linkableParams[i].nub.uid === p.nub.uid) {
+            const li = this._linkableParams[i];
+            if (li.equals(p)) {
                 this.linkTo(i);
                 break;
             }
         }
     }
 
-    public get currentLinkedParam() {
+    public get currentLinkedParam(): LinkedValue {
         if (this._linkableParams !== undefined) {
             if (this._currentIndex !== -1 && this._currentIndex < this._linkableParams.length) {
                 return this._linkableParams[this._currentIndex];
@@ -117,47 +100,40 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
     }
 
     /**
-     * attribut "label" d'une entrée du select des paramètres
+     * Label d'une entrée du select des paramètres liés
      */
-    private selectItemLabel(i: any): string {
-        const s = i.name; // nom associé au paramètre/à la valeur
-        const c = i.formTitle; // nom du module de calcul
+    private selectItemLabel(i: LinkedValue): string {
+        const s = i.symbol; // nom associé au paramètre/à la valeur
+        const c = i.meta["formTitle"]; // nom du module de calcul
 
-        // pointeur direct vers une Structure dans un Nub de type parallèle
-        if (i.nub && i.nub instanceof Structure) {
+        // 1. Paramètre / résultat d'un ouvrage dans un Nub de type parallèle
+        if (i.nub instanceof Structure) {
             let p: number;
             p = i.nub.findPositionInParent();
-            // forme xxx. (résultat)
-            const re4 = /([^\.]+)\.$/;
-            const match4 = re4.exec(s);
-            if (match4 !== null) {
+            if (i.isResult()) {
                 // résultat d'ouvrage
-                return `${match4[1]} (résultat de ${c}, ouvrage ${p + 1})`;
+                return `${s} (résultat de ${c}, ouvrage ${p + 1})`;
             } else {
                 // paramètre d'ouvrage
                 return `${s} (${c}, ouvrage ${p + 1})`;
             }
+        } else
+        // 2. Résultat
+        if (i.isResult()) {
+            return `${s} (résultat de ${c})`;
+        } else
+        // 3. Résultat complémentaire
+        if (i.isExtraResult()) {
+            if (i.meta["result"]) {
+                // @TODO not used ?
+                return `${s} (${c}, résultat complémentaire de ${i.meta["result"]})`;
+            } else {
+                return `${s} (${c}, résultat complémentaire)`;
+            }
+        } else {
+            // 4. Paramètre (cas général)
+            return `${s} (${c})`;
         }
-
-        const re1 = /([^\.]+)\.$/;  // forme xxx. (résultat)
-        const match1 = re1.exec(s);
-        if (match1 !== null) {
-            return `${match1[1]} (résultat de ${c})`;
-        }
-
-        const re2 = /([^\.]+)\.(.+)/;  // forme xxx.yyy (résultat complémentaire)
-        const match2 = re2.exec(s);
-        if (match2 !== null) {
-            return `${match2[2]} (${c}, résultat complémentaire de ${match2[1]})`;
-        }
-
-        const re3 = /^\.(.+)/;  // forme .xxx (résultat complémentaire)
-        const match3 = re3.exec(s);
-        if (match3 !== null) {
-            return `${match3[1]} (${c}, résultat complémentaire)`;
-        }
-
-        return `${s} (${c})`; // forme simple (paramètre)
     }
 
     /**
@@ -168,14 +144,23 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
         if (this._currentIndex !== index) {
             this._currentIndex = index;
             const lp = this._linkableParams[index];
-            this.param.linkToParameter(lp.nub, lp.name);
+            this.param.linkToValue(lp);
+            // reset form results when a new target is selected
+            this.emitModelChanged();
         }
     }
 
+    /**
+     * événement de changement de la valeur du modèle
+     */
+    private emitModelChanged() {
+        this.change.emit({ "action": "model", "value": this.currentLinkedParam.getValue() });
+    }
+
     public updateParamList() {
         // liste des paramètres liables
         if (this.param.valueMode === ParamValueMode.LINK) {
-            this._linkableParams = this._formService.filterLinkableValues(this._formService.getLinkableValues(this.param));
+            this._linkableParams = this._formService.getLinkableValues(this.param);
         } else {
             this._linkableParams = [];
         }
@@ -187,8 +172,8 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
                 if (this.param.valueMode === ParamValueMode.LINK && this._linkableParams !== undefined) {
                     let i = 0;
                     for (const e of this._linkableParams) {
-                        if (this.param.paramDefinition.paramValues.referencedNub
-                            && e.nub.uid === this.param.paramDefinition.paramValues.referencedNub["_uid"]) {
+                        const refValue = this.param.paramDefinition.referencedValue;
+                        if (refValue && e.equals(refValue)) {
                             this._currentIndex = i;
                             break;
                         } else {
@@ -198,10 +183,8 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
                 }
                 this.linkTo(Math.max(this._currentIndex, 0)); // might still be -1
             }
-            this._message = undefined;
         } else {
             this._currentIndex = -1;
-            this._message = "Aucun paramètre compatible trouvé";
         }
     }
 
@@ -234,12 +217,12 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
                         if (
                             lp.nub.uid === f.currentNub.uid
                             || ( // repeatable structure inside parent module
-                                lp.nub.parent instanceof ParallelStructure
+                                lp.nub instanceof Structure
                                 && lp.nub.parent.uid === f.currentNub.uid
                             )
                         ) {
                             // update <select> option
-                            lp.formTitle = data.value;
+                            lp.meta["formTitle"] = data.value;
                         }
                     }
                     break;
diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts
index d9020c604..07a32d455 100644
--- a/src/app/components/param-values/param-values.component.ts
+++ b/src/app/components/param-values/param-values.component.ts
@@ -1,10 +1,8 @@
-import { Component, Input, AfterViewInit } from "@angular/core";
+import { Component, Input, AfterViewInit, Output, EventEmitter } from "@angular/core";
 import { NgParameter } from "../../formulaire/ngparam";
 import { DialogEditParamValuesComponent } from "../dialog-edit-param-values/dialog-edit-param-values.component";
 import { MatDialog } from "@angular/material";
-import { ParamValueMode } from "jalhyd";
-import { I18nService } from "../../services/internationalisation/internationalisation.service";
-import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";
+import { ParamValueMode, Observer } from "jalhyd";
 
 @Component({
     selector: "param-values",
@@ -13,7 +11,7 @@ import { ApplicationSetupService } from "../../services/app-setup/app-setup.serv
         "./param-values.component.scss"
     ]
 })
-export class ParamValuesComponent implements AfterViewInit {
+export class ParamValuesComponent implements AfterViewInit, Observer {
 
     @Input()
     public param: NgParameter;
@@ -21,11 +19,14 @@ export class ParamValuesComponent implements AfterViewInit {
     @Input()
     public title: string;
 
+    /**
+     * événement signalant un changement de valeur du modèle
+     */
+    @Output()
+    protected change = new EventEmitter<any>();
 
     constructor(
-        private editValuesDialog: MatDialog,
-        private intlService: I18nService,
-        private appSetupService: ApplicationSetupService
+        private editValuesDialog: MatDialog
     ) { }
 
     public get isMinMax() {
@@ -62,5 +63,26 @@ export class ParamValuesComponent implements AfterViewInit {
                 this.openDialog();
             });
         }
+        // subscribe to parameter values change (through dialog actions) @TODO draft
+        this.param.addObserver(this);
     }
+
+    /**
+     * événement de changement de la valeur du modèle
+     */
+    private emitModelChanged() {
+        this.change.emit({ "action": "model", "value": this.param.getValue() });
+    }
+
+    public update(sender: any, data: any): void {
+        if (sender instanceof DialogEditParamValuesComponent) {
+            switch (data.action) {
+                case "ngparamAfterValue":
+                    // tell the form to clear the results
+                    this.emitModelChanged();
+                    break;
+            }
+        }
+    }
+
 }
diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index 2f3f6bd41..e6f7550fc 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -168,14 +168,12 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
     }
 
     public removeFieldset(fs: FieldSet) {
-        // console.log("==> FormParallelStructures removeFieldset", fs.uid);
         if (fs.nub instanceof Structure) {
             // suppression du sous-nub dans le Nub parent
             this.deleteNub(fs.nub);
 
             // suppression du fieldset
             this.fieldsetContainer.removeFieldset(fs);
-            // console.log("====> nb struct dans le parent après", (this.currentNub as ParallelStructure).structures.length);
 
             this.resetResults();
         } else { super.removeFieldset(fs); }
diff --git a/src/app/formulaire/definition/form-def-fixedvar.ts b/src/app/formulaire/definition/form-def-fixedvar.ts
index d34f67110..d4add5b8f 100644
--- a/src/app/formulaire/definition/form-def-fixedvar.ts
+++ b/src/app/formulaire/definition/form-def-fixedvar.ts
@@ -82,16 +82,9 @@ export class FormDefFixedVar {
                         break;
 
                     case ParamValueMode.LINK:  // nouvel état
-                        if (sourceParam.paramDefinition.hasMultipleValues) {
+                        if (sourceParam.paramDefinition.isReferenceDefined() && sourceParam.paramDefinition.hasMultipleValues) {
                             this.resetOtherRadio(sourceParam, ParamRadioConfig.CAL);
-                        } else {
-                            const refParamValues = sourceParam.paramDefinition.referencedParamValues;
-                            if (refParamValues !== undefined) { // cad si on référence un paramètre et non un Result par ex
-                                if (refParamValues.valueMode === ParamValueMode.LINK) {
-                                    throw new Error(`références de paramètre en chaîne non pris en charge`);
-                                }
-                            } // cas à traiter
-                        }
+                        } // @TODO vérifier tous les cas problématiques (liens en chaîne ?)
                         break;
                 }
                 break;
@@ -105,10 +98,11 @@ export class FormDefFixedVar {
 
                     case ParamValueMode.LINK:  // nouvel état
                         // mode du paramètre référencé
-                        const refParamValues = sourceParam.paramDefinition.referencedParamValues;
+                        /* const refParamValues = sourceParam.paramDefinition.referencedParamValues;
                         if (refParamValues.valueMode === ParamValueMode.LINK) {
                             throw new Error(`références de paramètre en chaîne non pris en charge`);
-                        }
+                        } */
+                        // @TODO vérifier tous les cas problématiques (liens en chaîne ?)
                         break;
                 }
                 break;
diff --git a/src/app/formulaire/definition/form-def-paramcalc.ts b/src/app/formulaire/definition/form-def-paramcalc.ts
index cd867bde8..ac743a1ff 100644
--- a/src/app/formulaire/definition/form-def-paramcalc.ts
+++ b/src/app/formulaire/definition/form-def-paramcalc.ts
@@ -1,4 +1,4 @@
-import { ParamValueMode } from "jalhyd";
+import { ParamValueMode, ParamDefinition } from "jalhyd";
 
 import { NgParameter } from "../ngparam";
 import { FormulaireDefinition } from "./form-definition";
@@ -82,9 +82,9 @@ export class FormDefParamToCalculate extends FormDefFixedVar {
                             this.setDefault();
                         } else {
                             // mode du paramètre référencé
-                            const refParamValues = sourceParam.paramDefinition.referencedParamValues;
-                            if (refParamValues !== undefined) {
-                                switch (refParamValues.valueMode) {
+                            const refValue = sourceParam.paramDefinition.referencedValue;
+                            if (refValue && refValue.isParameter()) {
+                                switch ((refValue.element as ParamDefinition).valueMode) {
                                     case ParamValueMode.MINMAX:
                                     case ParamValueMode.LISTE:
                                     case ParamValueMode.CALCUL:
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 37d42aeb2..564726436 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -269,17 +269,6 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         }
 
         this.completeParse(this._jsonConfig);
-
-        // console.log("-----");
-
-        // for (const n of Session.getInstance().NubIterator) {
-        //     console.log(n.nub);
-        //     for (const p of n.nub.parameterIterator)
-        //         console.log(`${p.symbol} uid  ${p.uid} props ${n.properties} mode ${p.valueMode} val ${p.uncheckedValue}`);
-        // }
-
-        // logObject(this._fieldSets, "fieldsets");
-        // logObject(this._dependencies, "dependences");
         this.parseDependencies(this._jsonConfig);
     }
 
@@ -435,7 +424,10 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
      * gestion d'un clic sur les radios
      */
     public onRadioClick(info: any) {
-        this.reset();
+        // if mdoe changed, reset form results
+        if (info.oldValueMode !== info.param.valueMode) {
+            this.reset();
+        }
     }
 
     /**
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 5dd19f2e5..9c001b21d 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -153,7 +153,8 @@ export class FieldSet extends FormulaireElement implements Observer {
         if (res) {
             res.parseConfig(json, { "radioConfig": default_radio_config });
             // set parent Nub on Parameter to ensure UID availability
-            res.paramDefinition.parent = this._nub;
+            // should always be done @TODO check
+            // res.paramDefinition.parent = this._nub;
         }
 
         return res;
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 30ec5d0d5..8a4364854 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -1,5 +1,5 @@
 import { Interval, ParamDefinition, ParamDomain, ParamValueMode, INumberIterator,
-    Nub, Observer, asObservable, ParamCalculability } from "jalhyd";
+    Nub, Observer, asObservable, ParamCalculability, LinkedValue } from "jalhyd";
 
 import { sprintf } from "sprintf-js";
 
@@ -54,7 +54,7 @@ export class NgParameter extends InputField implements Observer {
      */
     public static preview(p: ParamDefinition): string {
         let valuePreview: string;
-
+        // console.log("NgParam::preview()", p.symbol, ParamValueMode[p.valueMode], p);
         switch (p.valueMode) {
             case ParamValueMode.SINGLE:
                 valuePreview = String(p.getValue());
@@ -89,8 +89,16 @@ export class NgParameter extends InputField implements Observer {
                 }
                 break;
             case ParamValueMode.LINK:
-                // recursive call
-                valuePreview = NgParameter.preview(p.referencedObject as ParamDefinition);
+                if (p.isReferenceDefined()) {
+                    if (p.referencedValue.isParameter()) {
+                        // recursive call
+                        valuePreview =  NgParameter.preview(p.referencedValue.element as ParamDefinition);
+                    } else {
+                        // @TODO what if the result was already computed ?
+                        // Is the computed value fresh or stale ?
+                        valuePreview = ServiceFactory.instance.i18nService.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
+                    }
+                }
         }
 
         return valuePreview;
@@ -101,7 +109,7 @@ export class NgParameter extends InputField implements Observer {
     }
 
     get domain(): ParamDomain {
-        return this._paramDef.getDomain();
+        return this._paramDef.domain;
     }
 
     public set confId(id: string) {
@@ -144,6 +152,7 @@ export class NgParameter extends InputField implements Observer {
         return this._paramDef.valueMode;
     }
 
+    // @TODO vérifier : ça a une tête à faire 40x trop d'opérations pour rien
     public set valueMode(m: ParamValueMode) {
         // undefined si on clique en dehors du select après l'avoir ouvert (cad sans avoir fait de sélection)
         // et au même niveau, cad à côté du bouton et non à côté du menu déroulant
@@ -165,18 +174,10 @@ export class NgParameter extends InputField implements Observer {
         return this._paramValues.min;
     }
 
-    public set minValue(v: number) {
-        this._paramValues.min = v;
-    }
-
     public get maxValue() {
         return this._paramValues.max;
     }
 
-    public set maxValue(v: number) {
-        this._paramValues.max = v;
-    }
-
     public get stepRefValue(): Interval {
         return this._paramValues.stepRefValue;
     }
@@ -185,18 +186,10 @@ export class NgParameter extends InputField implements Observer {
         return this._paramValues.step;
     }
 
-    public set stepValue(v: number) {
-        this._paramValues.step = v;
-    }
-
     public get valueList() {
         return this._paramValues.valueList;
     }
 
-    public set valueList(l: number[]) {
-        this._paramValues.valueList = l;
-    }
-
     public get isValid() {
         if (this.radioState === undefined) {
             return false;
@@ -265,11 +258,43 @@ export class NgParameter extends InputField implements Observer {
         this.notifyValueModified(sender);
     }
 
+    public setMinValue(sender: any, v: number) {
+        const changed = (this._paramValues.min !== v);
+        this._paramValues.min = v;
+        if (changed) {
+            this.notifyValueModified(sender);
+        }
+    }
+
+    public setMaxValue(sender: any, v: number) {
+        const changed = (this._paramValues.max !== v);
+        this._paramValues.max = v;
+        if (changed) {
+            this.notifyValueModified(sender);
+        }
+    }
+
+    public setStepValue(sender: any, v: number) {
+        const changed = (this._paramValues.step !== v);
+        this._paramValues.step = v;
+        if (changed) {
+            this.notifyValueModified(sender);
+        }
+    }
+
+    public setValueList(sender: any, l: number[]) {
+        const changed = (JSON.stringify(this._paramValues.valueList) !== JSON.stringify(l));
+        this._paramValues.valueList = l;
+        if (changed) {
+            this.notifyValueModified(sender);
+        }
+    }
+
     /**
      * supprime un lien avec un paramètre
      */
     private unlinkParameter() {
-        const o = asObservable(this._paramDef.referencedObject);
+        const o = asObservable(this._paramDef.referencedValue);
         if (this.valueMode === ParamValueMode.LINK) {
             this._paramDef.undefineReference();
             if (o !== undefined) {
@@ -281,21 +306,24 @@ export class NgParameter extends InputField implements Observer {
     /**
      * crée le lien avec un paramètre
      */
-    public linkToParameter(n: Nub, ref: string) {
+    public linkToValue(target: LinkedValue) {
         const changed: boolean =
-            this._paramDef.valueMode !== ParamValueMode.LINK || this._paramDef.referencedNub !== n ||
-            this._paramDef.referenceDefinition !== ref;
+            // changement de mode
+            ! this._paramDef.isReferenceDefined()
+            // ou changement de référence
+            || ! this._paramDef.referencedValue.equals(target);
+
         if (changed) {
-            let o = asObservable(this._paramDef.referencedObject);
+            let o = asObservable(this._paramDef.referencedValue);
             if (o !== undefined) {
                 o.removeObserver(this);
             }
 
-            this.valueMode = ParamValueMode.LINK;
+            // this.valueMode = ParamValueMode.LINK; // supposed to be done at model level by instruction below
             // changement de lien
-            this._paramDef.defineReference(n, ref);
+            this._paramDef.defineReferenceFromLinkedValue(target);
 
-            o = asObservable(this._paramDef.referencedObject);
+            o = asObservable(this._paramDef.referencedValue);
             if (o !== undefined) {
                 o.addObserver(this); // pour être prévenu des changements de valeur de l'object référencé
             }
diff --git a/src/app/results/var-results.ts b/src/app/results/var-results.ts
index 752664fcb..51208365d 100644
--- a/src/app/results/var-results.ts
+++ b/src/app/results/var-results.ts
@@ -116,7 +116,6 @@ export class VarResults extends CalculatedParamResults {
      * @param symbol parameter / result symbol (ex: "Q")
      */
     public getValuesSeries(symbol: string) {
-        // console.log("GVS for symbol", symbol);
         const series = [];
         // 1. calculated param ?
         if (this._calculatedParam.symbol === symbol) {
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 066e82f7a..f841c194f 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -3,7 +3,7 @@ import { Injectable } from "@angular/core";
 import { decode } from "he";
 import { saveAs } from "file-saver";
 
-import { CalculatorType, EnumEx, Observable, ParamDefinition, Session, Nub, ParallelStructure } from "jalhyd";
+import { CalculatorType, LinkedValue, Observable, ParamDefinition, Session, Nub, ParallelStructure } from "jalhyd";
 
 import { HttpService } from "../../services/http/http.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
@@ -545,78 +545,36 @@ export class FormulaireService extends Observable {
     }
 
     /**
-     * MAJ des liens entre paramètres lors de la désérialisation
+     * Demande à la Session JalHYd la liste des paramètres/résultats pouvant être liés au
+     * paramètre fourni
      */
-    /* private updateParamsLinks(json: {}, formInfos: any[], oldFormCount: number) {
-        // table de correspondance des uid fichier <-> objets mémoire
-        // forme : tableau d'objets de la forme :
-        // { "type" : <type de l'objet. "form" pour formulaire>,
-        //   "old": <uid dans le fichier>,
-        //   "new": <uid de l'objet mémoire>}
-        const uidMap = [];
-        for (const ks in json) {
-            switch (ks) {
-                case "elements":
-                    let n = oldFormCount;
-                    for (const e of json[ks]) {
-                        if (Object.keys(e)[0] === "form") {
-                            uidMap.push({
-                                "type": "form",
-                                "old": e["form"]["uid"],
-                                "new": this._formulaires[n].uid
-                            });
-                            n++;
-                        }
-                    }
-            }
-        }
-        // MAJ liens
-        for (const ks in json) {
-            switch (ks) {
-                case "elements":
-                    let n = 0;
-                    for (const e of json[ks]) {
-                        if (Object.keys(e)[0] === "form") {
-                            this.updateFormLinks(e["form"], formInfos, this._formulaires[n + oldFormCount], uidMap);
-                            n++;
+    public getLinkableValues(p: NgParameter): LinkedValue[] {
+        let linkableValues: LinkedValue[] = [];
+        if (p) {
+            linkableValues = Session.getInstance().getLinkableValues(p.paramDefinition);
+            // join form names to ease usage
+            for (let i = 0; i < linkableValues.length; i++) {
+                const lv = linkableValues[i];
+                for (const f of this._formulaires) {
+                    if (f.currentNub) {
+                        if (f.currentNub.uid === lv.nub.uid) {
+                            lv.meta["formTitle"] = f.calculatorName;
+                        } else {
+                            // child structures ?
+                            if (f.currentNub instanceof ParallelStructure) {
+                                for (const s of f.currentNub.structures) {
+                                    if (s.uid === lv.nub.uid) {
+                                        lv.meta["formTitle"] = f.calculatorName;
+                                    }
+                                }
+                            }
                         }
                     }
-                    break;
-
-                default:
-                    throw new Error(`session file : invalid key '${ks}' in session object`);
-            }
-        }
-    } */
-
-    /**
-     * @returns liste des valeurs liables à un paramètre sous la forme d'un tableau d'objets
-     * {"param":<paramètre lié>, "nub":<Nub d'origine du paramètre lié>, "formTitle":<nom du module de calcul liée au nub>}
-     * @param p paramètre qui sert de clé de recherche des paramètres liables
-     */
-    public getLinkableValues(p: NgParameter): any[] {
-        const res: any[] = [];
-        if (p !== undefined) {
-            for (const f of this._formulaires) {
-                // nub associé au formulaire
-                const sn = f.currentNub;
-                try {
-                    // on vérifie que le paramètre en entrée appartient au nub
-                    const np = sn.getParameter(p.symbol);
-                    // si oui, on demande à exclure des valeurs retournées le résultat du même nom que le paramètre
-                    const exclude = np !== undefined ? p.paramDefinition.uid === np.uid : false;
-                    // valeurs liables
-                    const ps = sn.getLinkableValues(p.paramDefinition, undefined, exclude);
-                    for (const npp of ps) {
-                        npp["formTitle"] = f.calculatorName;
-                        res.push(npp);
-                    }
-                } catch (e) {
-                    //  p.symbol n'existe pas dans le nub testé
                 }
             }
         }
-        return res;
+        linkableValues = this.filterLinkableValues(linkableValues);
+        return linkableValues;
     }
 
     /**
@@ -626,16 +584,12 @@ export class FormulaireService extends Observable {
      * @param values valeurs liables (modifié par la méthode)
      */
     public filterLinkableValues(values: any[]): any[] {
-        // suppression des paramètres non affichés
-
         for (let i = values.length - 1; i >= 0; i--) {
             const v = values[i].value;
             if (v instanceof ParamDefinition) {
                 // pour chaque paramètre...
                 const prm: ParamDefinition = v;
-
                 const parentForm: FormulaireDefinition = this.getParamdefParentForm(prm);
-
                 // ... on cherche s'il est affiché dans son parent
                 let found = false;
                 if (parentForm !== undefined) {
@@ -648,13 +602,11 @@ export class FormulaireService extends Observable {
                         }
                     }
                 }
-
                 if (!found) {
                     values.splice(i, 1);
                 }
             }
         }
-
         return values;
     }
 }
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index bed80e332..0c952da55 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -22,6 +22,7 @@
     "ERROR_PARAMDEF_VALUE_POS": "value %value% of '%symbol%' parameter is invalid (cannot be <=0)",
     "ERROR_PARAMDEF_VALUE_POSNULL": "value %value% of '%symbol%' parameter is invalid (cannot be <0)",
     "ERROR_PARAMDEF_VALUE_UNDEFINED": "value of '%symbol%' parameter is undefined",
+    "ERROR_PARAMDEF_LINKED_VALUE_UNDEFINED": "value of '%symbol%' linked parameter is undefined",
     "ERROR_PARAMDOMAIN_INTERVAL_BOUNDS": "invalid %minValue%/%maxValue% min/max boundaries for 'interval' parameter definition domain",
     "ERROR_PARAMDOMAIN_INVALID": "parameter '%symbol%: non supported '%domain%' definition domain",
     "ERROR_REMOUS_PAS_CALCUL_DEPUIS_AMONT": "Upstream boundary condition < Critical elevation: no possible calculation from upstream",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index ea0bae031..e48c43e99 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -22,6 +22,7 @@
     "ERROR_PARAMDEF_VALUE_POS": "La valeur %value% du paramètre '%symbol%' est incorrecte (<=0)",
     "ERROR_PARAMDEF_VALUE_POSNULL": "La valeur %value% du paramètre '%symbol%' est incorrecte (<0)",
     "ERROR_PARAMDEF_VALUE_UNDEFINED": "La valeur du paramètre %symbol% n'est pas définie",
+    "ERROR_PARAMDEF_LINKED_VALUE_UNDEFINED": "La valeur du paramètre lié %symbol% n'est pas définie",
     "ERROR_PARAMDOMAIN_INTERVAL_BOUNDS": "Les bornes (%minValue%/%maxValue%) de l'intervalle sont incorrectes",
     "ERROR_PARAMDOMAIN_INVALID": "Paramètre '%symbol%'&nbsp;: le domaine de définition '%domain%' est incorrect",
     "ERROR_REMOUS_PAS_CALCUL_DEPUIS_AMONT": "Condition limite amont > Hauteur critique&nbsp;: pas de calcul possible depuis l'amont",
-- 
GitLab


From bc35330bcba61f315f7f2e46ddc92b2ff33b230b Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 18 Mar 2019 17:09:38 +0100
Subject: [PATCH 02/39] Fix #166

---
 .../dialog-confirm-close-calc.component.html  | 13 ++++++++++-
 .../dialog-confirm-close-calc.component.scss  |  3 +++
 .../dialog-confirm-close-calc.component.ts    | 22 ++++++++++++++++++-
 .../calculator.component.ts                   |  7 +++++-
 src/locale/messages.en.json                   |  1 +
 src/locale/messages.fr.json                   |  1 +
 6 files changed, 44 insertions(+), 3 deletions(-)
 create mode 100644 src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.scss

diff --git a/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.html b/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.html
index 8018436a7..f1abc0500 100644
--- a/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.html
+++ b/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.html
@@ -1,7 +1,18 @@
 <h1 mat-dialog-title [innerHTML]="uitextCloseCalcTitle"></h1>
 <div mat-dialog-content>
-  <p [innerHTML]="uitextCloseCalcBody"></p>
+
+  <div *ngIf="dependingNubs.length">
+    {{ uitextDependingModules }}
+    <mat-list role="list">
+      <mat-list-item role="listitem" *ngFor="let dn of dependingNubs">
+        {{ dn }}
+      </mat-list-item>
+    </mat-list>
+  </div>
 </div>
+
+<p [innerHTML]="uitextCloseCalcBody"></p>
+
 <div mat-dialog-actions>
   <button mat-raised-button color="primary" [mat-dialog-close]="false" cdkFocusInitial>
     {{ uitextNo }}
diff --git a/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.scss b/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.scss
new file mode 100644
index 000000000..268f18ad8
--- /dev/null
+++ b/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.scss
@@ -0,0 +1,3 @@
+.mat-list-base .mat-list-item {
+    height: 24px;
+}
diff --git a/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.ts b/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.ts
index e8a1f44ca..63cdc8fa4 100644
--- a/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.ts
+++ b/src/app/components/dialog-confirm-close-calc/dialog-confirm-close-calc.component.ts
@@ -1,18 +1,30 @@
 import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material";
 import { Inject, Component } from "@angular/core";
+import { FormulaireService } from "../../services/formulaire/formulaire.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
+import { Session } from "jalhyd";
 
 @Component({
     selector: "dialog-confirm-close-calc",
     templateUrl: "dialog-confirm-close-calc.component.html",
+    styleUrls: [
+      "dialog-confirm-close-calc.component.scss"
+    ]
 })
 export class DialogConfirmCloseCalcComponent {
 
+    private _dependingNubs: any[] = [];
+
     constructor(
         public dialogRef: MatDialogRef<DialogConfirmCloseCalcComponent>,
         private intlService: I18nService,
+        private formService: FormulaireService,
         @Inject(MAT_DIALOG_DATA) public data: any
-    ) { }
+    ) {
+        this._dependingNubs = Session.getInstance().getDependingNubs(data.uid).map((n) => {
+            return this.formService.getFormulaireFromNubId(n.uid).calculatorName;
+        });
+    }
 
     public get uitextYes() {
       return this.intlService.localizeText("INFO_OPTION_YES");
@@ -30,4 +42,12 @@ export class DialogConfirmCloseCalcComponent {
       return this.intlService.localizeText("INFO_CLOSE_DIALOGUE_TEXT");
     }
 
+    public get uitextDependingModules() {
+      return this.intlService.localizeText("INFO_CLOSE_DIALOGUE_DEPENDING_MODULES");
+    }
+
+    public get dependingNubs() {
+      return this._dependingNubs;
+    }
+
 }
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index af807c109..a0cc4b468 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -386,7 +386,12 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     public closeCalculator() {
         const dialogRef = this.confirmCloseCalcDialog.open(
             DialogConfirmCloseCalcComponent,
-            { disableClose: true }
+            {
+                data: {
+                  uid: this._formulaire.currentNub.uid
+                },
+                disableClose: true
+            }
         );
         dialogRef.afterClosed().subscribe(result => {
             if (result) {
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 0c952da55..506bda9e7 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -44,6 +44,7 @@
     "INFO_CLOISONS_TITRE_COURT": "Cross walls",
     "INFO_CLOSE_DIALOGUE_TEXT": "Warning ! Parameters and results will be lost. Really close?",
     "INFO_CLOSE_DIALOGUE_TITRE": "Please confirm",
+    "INFO_CLOSE_DIALOGUE_DEPENDING_MODULES": "The following modules depend on the one you are closing:",
     "INFO_CONDUITEDISTRIBUTRICE_TITRE": "Distributor pipe",
     "INFO_CONDUITEDISTRIBUTRICE_TITRE_COURT": "D. pipe",
     "INFO_COURBEREMOUS_TITRE": "Backwater curves",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index e48c43e99..90b8466f0 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -44,6 +44,7 @@
     "INFO_CLOISONS_TITRE_COURT": "Cloisons",
     "INFO_CLOSE_DIALOGUE_TEXT": "Attention&nbsp;! Les paramètres et résultats du module de calcul seront perdus. Vraiment fermer&nbsp;?",
     "INFO_CLOSE_DIALOGUE_TITRE": "Confirmer la fermeture",
+    "INFO_CLOSE_DIALOGUE_DEPENDING_MODULES": "Les modules suivants dépendent de celui que vous êtes en train de fermer :",
     "INFO_CONDUITEDISTRIBUTRICE_TITRE": "Conduite distributrice",
     "INFO_CONDUITEDISTRIBUTRICE_TITRE_COURT": "Conduite distri.",
     "INFO_COURBEREMOUS_TITRE": "Courbes de remous",
-- 
GitLab


From 71e006eaf195f04b790c38597343fcbd91e5cf64 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 19 Mar 2019 11:13:12 +0100
Subject: [PATCH 03/39] Localisation: do not reload / recompute whole form eery
 time it is loaded

---
 .../formulaire/definition/form-definition.ts  | 13 +++++++--
 .../services/formulaire/formulaire.service.ts | 28 ++++++-------------
 2 files changed, 19 insertions(+), 22 deletions(-)

diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 564726436..fe7e94009 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -36,6 +36,9 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
     /** clé-valeurs du fichier de localisation spécifique à ce module */
     private _specificLocalisation: StringMap;
 
+    /** ISO 639-1 language code of the current language (to avoid unnecessary localisation reload) */
+    private _currentLanguage: string;
+
     constructor() {
         super(undefined);
     }
@@ -54,6 +57,10 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return this._specificLocalisation;
     }
 
+    public get currentLanguage() {
+        return this._currentLanguage;
+    }
+
     public get calculatorType(): CalculatorType {
         const props = this._currentNub === undefined ? this.defaultProperties : (this._currentNub.properties as Props).props;
         return props["calcType"];
@@ -387,14 +394,14 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
     public abstract get hasResults(): boolean;
     public abstract get results(): CalculatorResults[];
 
-    public updateLocalisation(localisation: StringMap) {
+    public updateLocalisation(localisation: StringMap, lang: string) {
         this._specificLocalisation = localisation;
+        this._currentLanguage = lang;
         for (const fe of this.topFormElements) {
             fe.updateLocalisation(localisation);
         }
-
         if (this.hasResults) {
-            this.doCompute(); // pour mettre à jour la langue
+            this.doCompute(); // pour mettre à jour la langue des intitulés de résultats
         }
     }
 
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index f841c194f..59a85bd44 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -97,27 +97,17 @@ export class FormulaireService extends Observable {
     }
 
     /**
-     * Met à jour la langue du formulaire
-     * @param formId id unique du formulaire
-     * @param localisation ensemble id-message traduit
-     */
-    private updateFormulaireLocalisation(formId: string, localisation: StringMap) {
-        for (const f of this._formulaires) {
-            if (f.uid === formId) {
-                f.updateLocalisation(localisation);
-                break;
-            }
-        }
-    }
-
-    /**
-     * charge la localisation et met à jour la langue du formulaire
+     * Loads localisation file corresponding to current language then updates all form strings,
+     * only if form language was not already set to current language
      */
     public loadUpdateFormulaireLocalisation(f: FormulaireDefinition): Promise<FormulaireDefinition> {
-        return this.loadLocalisation(f.calculatorType).then(localisation => {
-            this.updateFormulaireLocalisation(f.uid, localisation);
-            return f;
-        });
+        const requiredLang = this._intlService.currentLanguage;
+        if (requiredLang !== f.currentLanguage) {
+            return this.loadLocalisation(f.calculatorType).then(localisation => {
+                f.updateLocalisation(localisation, requiredLang);
+                return f;
+            });
+        }
     }
 
     /**
-- 
GitLab


From deec5e98c45c8556ec2b4b252a782c4e92c1fafa Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 19 Mar 2019 12:27:34 +0100
Subject: [PATCH 04/39] Factorisation de code dans les Formulaires

---
 .../definition/concrete/form-base.ts          | 16 ++++---
 .../definition/concrete/form-courbe-remous.ts | 24 ++--------
 .../concrete/form-lechapt-calmon.ts           |  8 ++++
 .../concrete/form-parallel-structures.ts      | 47 ++-----------------
 .../concrete/form-regime-uniforme.ts          | 46 ++----------------
 .../concrete/form-section-parametree.ts       | 40 ++--------------
 6 files changed, 31 insertions(+), 150 deletions(-)

diff --git a/src/app/formulaire/definition/concrete/form-base.ts b/src/app/formulaire/definition/concrete/form-base.ts
index 6b28a0a2f..1d1c0e26e 100644
--- a/src/app/formulaire/definition/concrete/form-base.ts
+++ b/src/app/formulaire/definition/concrete/form-base.ts
@@ -1,24 +1,24 @@
-import { FormDefFixedVar } from "../form-def-fixedvar";
-import { FormResultFixedVar } from "../form-result-fixedvar";
 import { FormulaireDefinition } from "../form-definition";
 import { CalculatorResults } from "../../../results/calculator-results";
 import { FormDefParamToCalculate } from "../form-def-paramcalc";
+import { FormResult } from "../form-result";
+import { FormCompute } from "../form-compute";
+import { FormResultFixedVar } from "../form-result-fixedvar";
 import { FormComputeFixedVar } from "../form-compute-fixedvar";
 
 export class FormulaireBase extends FormulaireDefinition {
 
-    private _formParamCalc: FormDefParamToCalculate;
+    protected _formParamCalc: FormDefParamToCalculate;
 
-    private _formCompute: FormComputeFixedVar;
+    protected _formCompute: FormCompute;
 
-    private _formResult: FormResultFixedVar;
+    protected _formResult: FormResult;
 
     constructor() {
         super();
-        // this._formFixedVar = new FormDefFixedVar(this);
         this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
-        this._formCompute = new FormComputeFixedVar(this, this._formResult);
+        this._formCompute = new FormComputeFixedVar(this, (this._formResult as FormResultFixedVar));
     }
 
     protected completeParse(json: {}) {
@@ -35,6 +35,8 @@ export class FormulaireBase extends FormulaireDefinition {
 
     public resetResults() {
         this._formResult.resetResults();
+        // @TODO cascade
+        // @TODO this.notifyReset() ?
     }
 
     public doCompute() {
diff --git a/src/app/formulaire/definition/concrete/form-courbe-remous.ts b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
index e65fc2e3b..ee2e61019 100644
--- a/src/app/formulaire/definition/concrete/form-courbe-remous.ts
+++ b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
@@ -3,16 +3,12 @@ import { IObservable, MethodeResolution } from "jalhyd";
 import { FormResultRemous } from "../form-result-remous";
 import { FormDefSection } from "../form-def-section";
 import { FormComputeCourbeRemous } from "../form-compute-courbe-remous";
-import { FormulaireDefinition } from "../form-definition";
-import { CalculatorResults } from "../../../results/calculator-results";
 import { FieldSet } from "../../fieldset";
+import { FormulaireBase } from "./form-base";
 
-export class FormulaireCourbeRemous extends FormulaireDefinition {
-    private _formSection: FormDefSection;
-
-    private _formCompute: FormComputeCourbeRemous;
+export class FormulaireCourbeRemous extends FormulaireBase {
 
-    private _formResult: FormResultRemous;
+    private _formSection: FormDefSection;
 
     /**
      * id du select configurant la méthode de résolution
@@ -23,7 +19,7 @@ export class FormulaireCourbeRemous extends FormulaireDefinition {
         super();
         this._formSection = new FormDefSection(this);
         this._formResult = new FormResultRemous(this);
-        this._formCompute = new FormComputeCourbeRemous(this, this._formSection, this._formResult);
+        this._formCompute = new FormComputeCourbeRemous(this, this._formSection, (this._formResult as FormResultRemous));
         // default properties
         this._props["methodeResolution"] = MethodeResolution.Trapezes;
         this._props["varCalc"] = undefined; // important
@@ -56,18 +52,6 @@ export class FormulaireCourbeRemous extends FormulaireDefinition {
         this.notifyReset();
     }
 
-    public doCompute() {
-        this._formCompute.doCompute();
-    }
-
-    public get hasResults(): boolean {
-        return this._formResult.hasResults;
-    }
-
-    public get results(): CalculatorResults[] {
-        return this._formResult.results;
-    }
-
     // interface Observer
 
     update(sender: IObservable, data: any) {
diff --git a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
index 69124f646..c84864baa 100644
--- a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
+++ b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
@@ -2,9 +2,17 @@ import { Observer } from "jalhyd";
 import { SelectField } from "../../select-field";
 import { FormulaireBase } from "./form-base";
 import { NgParamInputComponent } from "../../../components/ngparam-input/ngparam-input.component";
+import { FormResultFixedVar } from "../form-result-fixedvar";
+import { FormComputeFixedVar } from "../form-compute-fixedvar";
 
 export class FormulaireLechaptCalmon extends FormulaireBase implements Observer {
 
+    constructor() {
+        super();
+        this._formResult = new FormResultFixedVar(this, false);
+        this._formCompute = new FormComputeFixedVar(this, (this._formResult as FormResultFixedVar));
+    }
+
     protected completeParse(json: {}) {
         super.completeParse(json);
         // abonnement au changement de valeur du select de matériau
diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index e6f7550fc..d71119c87 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -1,8 +1,5 @@
 import { Structure, Nub, ParallelStructure, StructureType, LoiDebit, StructureProperties, Props, Session } from "jalhyd";
 
-import { FormulaireDefinition } from "../form-definition";
-import { CalculatorResults } from "../../../results/calculator-results";
-import { FormDefParamToCalculate } from "../form-def-paramcalc";
 import { FormDefParallelStructures } from "../form-def-parallel-structures";
 import { FormComputeParallelStructures } from "../form-compute-parallel-structures";
 import { FormResultFixedVar } from "../form-result-fixedvar";
@@ -12,19 +9,12 @@ import { SelectField } from "../../select-field";
 import { NgParameter } from "../../ngparam";
 import { FieldsetTemplate } from "../../fieldset-template";
 import { FormulaireNode } from "../../formulaire-node";
+import { FormulaireBase } from "./form-base";
 
-export class FormulaireParallelStructure extends FormulaireDefinition {
-
-    // private _formFixedVar: FormDefFixedVar;
+export class FormulaireParallelStructure extends FormulaireBase {
 
     private _formParallelStruct: FormDefParallelStructures;
 
-    private _formParamCalc: FormDefParamToCalculate;
-
-    private _formCompute: FormComputeParallelStructures;
-
-    private _formResult: FormResultFixedVar;
-
     /**
      * id du select configurant le type d'ouvrage
      */
@@ -32,11 +22,9 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
 
     constructor() {
         super();
-        // this._formFixedVar = new FormDefFixedVar(this);
-        this._formParamCalc = new FormDefParamToCalculate(this);
         this._formResult = new FormResultFixedVar(this, false);
         this._formParallelStruct = new FormDefParallelStructures();
-        this._formCompute = new FormComputeParallelStructures(this, this._formParallelStruct, this._formResult);
+        this._formCompute = new FormComputeParallelStructures(this, this._formParallelStruct, (this._formResult as FormResultFixedVar));
     }
 
     private createStructNub(templ: FieldsetTemplate): Nub {
@@ -184,35 +172,6 @@ export class FormulaireParallelStructure extends FormulaireDefinition {
         this.subscribeFieldsetContainer();
     }
 
-    /**
-     * gestion du clic sur les radios "paramètre fixé, à varier, à calculer"
-     */
-    public onRadioClick(info: string) {
-        super.onRadioClick(info);
-        this._formParamCalc.onRadioClick(info);
-    }
-
-    /**
-     * @return une chaîne représentant le "contexte" courant (ici, combinaison type d'ouvrage-loi de débit)
-     * @param fs FieldSet contenant les listes déroulantes type d'ouvrage et loi de débit
-     */
-
-    public resetResults() {
-        this._formResult.resetResults();
-    }
-
-    public doCompute() {
-        this._formCompute.doCompute();
-    }
-
-    public get hasResults(): boolean {
-        return this._formResult.hasResults;
-    }
-
-    public get results(): CalculatorResults[] {
-        return this._formResult.results;
-    }
-
     private get fieldsetContainer(): FieldsetContainer {
         const n = this.getFormulaireNodeById("struct_container");
         if (n === undefined || !(n instanceof FieldsetContainer)) {
diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
index 7e607af99..35a08225c 100644
--- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
+++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
@@ -1,31 +1,19 @@
-import { FormDefFixedVar } from "../form-def-fixedvar";
 import { IObservable, Observer } from "jalhyd";
 import { FormResultFixedVar } from "../form-result-fixedvar";
-import { FormulaireDefinition } from "../form-definition";
 import { FormDefSection } from "../form-def-section";
-import { CalculatorResults } from "../../../results/calculator-results";
-import { FormDefParamToCalculate } from "../form-def-paramcalc";
 import { FieldSet } from "../../fieldset";
 import { FormComputeFixedVar } from "../form-compute-fixedvar";
+import { FormulaireBase } from "./form-base";
 
-export class FormulaireRegimeUniforme extends FormulaireDefinition implements Observer {
-    private _formFixedVar: FormDefFixedVar;
-
-    private _formParamCalc: FormDefParamToCalculate;
+export class FormulaireRegimeUniforme extends FormulaireBase implements Observer {
 
     private _formSection: FormDefSection;
 
-    private _formCompute: FormComputeFixedVar;
-
-    private _formResult: FormResultFixedVar;
-
     constructor() {
         super();
-        this._formFixedVar = new FormDefFixedVar(this);
-        this._formParamCalc = new FormDefParamToCalculate(this);
         this._formSection = new FormDefSection(this);
         this._formResult = new FormResultFixedVar(this, true);
-        this._formCompute = new FormComputeFixedVar(this, this._formResult);
+        this._formCompute = new FormComputeFixedVar(this, (this._formResult as FormResultFixedVar));
     }
 
     protected parseOptions(json: {}) {
@@ -42,34 +30,6 @@ export class FormulaireRegimeUniforme extends FormulaireDefinition implements Ob
         super.completeParse(json);
     }
 
-    /**
-     * gestion du clic sur les radios "paramètre fixé, à varier, à calculer"
-     */
-    public onRadioClick(info: string) {
-        super.onRadioClick(info);
-        this._formParamCalc.onRadioClick(info);
-    }
-
-    public resetResults() {
-        this._formResult.resetResults();
-    }
-
-    public doCompute() {
-        this._formCompute.doCompute();
-    }
-
-    public get hasResults(): boolean {
-        return this._formResult.hasResults;
-    }
-
-    public get results(): CalculatorResults[] {
-        return this._formResult.results;
-    }
-
-    public reset() {
-        super.reset();
-    }
-
     // interface Observer
 
     update(sender: IObservable, data: any) {
diff --git a/src/app/formulaire/definition/concrete/form-section-parametree.ts b/src/app/formulaire/definition/concrete/form-section-parametree.ts
index 04508ccaf..9c48c038b 100644
--- a/src/app/formulaire/definition/concrete/form-section-parametree.ts
+++ b/src/app/formulaire/definition/concrete/form-section-parametree.ts
@@ -3,26 +3,18 @@ import { IObservable } from "jalhyd";
 import { FormResultSection } from "../form-result-section";
 import { FormDefSection } from "../form-def-section";
 import { FormComputeSectionParametree } from "../form-compute-section-parametree";
-import { FormulaireDefinition } from "../form-definition";
-import { CalculatorResults } from "../../../results/calculator-results";
-import { FormDefFixedVar } from "../form-def-fixedvar";
 import { FieldSet } from "../../fieldset";
+import { FormulaireBase } from "./form-base";
 
-export class FormulaireSectionParametree extends FormulaireDefinition {
-    private _formFixedVar: FormDefFixedVar;
+export class FormulaireSectionParametree extends FormulaireBase {
 
     private _formSection: FormDefSection;
 
-    private _formCompute: FormComputeSectionParametree;
-
-    private _formSectionResult: FormResultSection;
-
     constructor() {
         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);
+        this._formResult = new FormResultSection(this, this._formSection);
+        this._formCompute = new FormComputeSectionParametree(this, this._formSection, (this._formResult as FormResultSection));
         // default properties
         this._props["varCalc"] = "Hs";
     }
@@ -36,30 +28,6 @@ export class FormulaireSectionParametree extends FormulaireDefinition {
         this._formSection.afterParseFieldset(fs);
     }
 
-    /**
-     * gestion du clic sur les radios "paramètre fixé, à varier, à calculer"
-     */
-    public onRadioClick(info: string) {
-        super.onRadioClick(info);
-        this._formFixedVar.onRadioClick(info);
-    }
-
-    public resetResults() {
-        this._formSectionResult.resetResults();
-    }
-
-    public doCompute() {
-        this._formCompute.doCompute();
-    }
-
-    public get hasResults(): boolean {
-        return this._formSectionResult.hasResults;
-    }
-
-    public get results(): CalculatorResults[] {
-        return this._formSectionResult.results;
-    }
-
     // interface Observer
 
     update(sender: IObservable, data: any) {
-- 
GitLab


From e2ce663f5fea14cde6d5509448b2f91ff8793170 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 19 Mar 2019 14:15:01 +0100
Subject: [PATCH 05/39] Fix #171

---
 src/app/formulaire/definition/concrete/form-base.ts |  5 +++--
 .../definition/concrete/form-courbe-remous.ts       |  6 ------
 src/app/services/formulaire/formulaire.service.ts   | 13 +++++++++++++
 3 files changed, 16 insertions(+), 8 deletions(-)

diff --git a/src/app/formulaire/definition/concrete/form-base.ts b/src/app/formulaire/definition/concrete/form-base.ts
index 1d1c0e26e..9d7e4b120 100644
--- a/src/app/formulaire/definition/concrete/form-base.ts
+++ b/src/app/formulaire/definition/concrete/form-base.ts
@@ -5,6 +5,7 @@ import { FormResult } from "../form-result";
 import { FormCompute } from "../form-compute";
 import { FormResultFixedVar } from "../form-result-fixedvar";
 import { FormComputeFixedVar } from "../form-compute-fixedvar";
+import { ServiceFactory } from "../../../services/service-factory";
 
 export class FormulaireBase extends FormulaireDefinition {
 
@@ -35,8 +36,8 @@ export class FormulaireBase extends FormulaireDefinition {
 
     public resetResults() {
         this._formResult.resetResults();
-        // @TODO cascade
-        // @TODO this.notifyReset() ?
+        // reset the results of all forms depending on this one
+        ServiceFactory.instance.formulaireService.resetAllDependingFormsResults(this);
     }
 
     public doCompute() {
diff --git a/src/app/formulaire/definition/concrete/form-courbe-remous.ts b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
index ee2e61019..4e96da330 100644
--- a/src/app/formulaire/definition/concrete/form-courbe-remous.ts
+++ b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
@@ -46,12 +46,6 @@ export class FormulaireCourbeRemous extends FormulaireBase {
         }
     }
 
-    public resetResults() {
-        this._formResult.resetResults();
-        // prévenir les composants qu'il faut détecter les changements
-        this.notifyReset();
-    }
-
     // interface Observer
 
     update(sender: IObservable, data: any) {
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 59a85bd44..755083ba6 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -521,6 +521,7 @@ export class FormulaireService extends Observable {
 
     /**
      * met à jour les liens d'un formulaire
+     * @TODO rewrite
      * @param json conf du formulaire
      * @param formInfos métadonnées sur les formulaires chargés
      * @param form formulaire dont on met à jour les liens
@@ -599,4 +600,16 @@ export class FormulaireService extends Observable {
         }
         return values;
     }
+
+    /**
+     * Resets the results of all forms depending on the given form "f"
+     * @param f
+     */
+    public resetAllDependingFormsResults(f: FormulaireDefinition) {
+        const dependingNubs = Session.getInstance().getDependingNubs(f.currentNub.uid);
+        for (const dn of dependingNubs) {
+            const form = this.getFormulaireFromNubId(dn.uid);
+            form.resetResults();
+        }
+    }
 }
-- 
GitLab


From 63b8715c6778eb5aedb37055bb6ccbbe7c0b7508 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 19 Mar 2019 14:16:23 +0100
Subject: [PATCH 06/39] Comments

---
 .../dialog-edit-param-computed.component.html                   | 1 -
 .../components/param-field-line/param-field-line.component.ts   | 2 +-
 src/app/formulaire/definition/form-def-fixedvar.ts              | 2 +-
 3 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.html b/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.html
index 9efaf2887..517a0f0d1 100644
--- a/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.html
+++ b/src/app/components/dialog-edit-param-computed/dialog-edit-param-computed.component.html
@@ -4,7 +4,6 @@
 
   <div mat-dialog-content>
     <ngparam-input [title]="param.title" ></ngparam-input>
-    <!-- (change)="onInputChange($event)" -->
   </div>
 
   <div mat-dialog-actions>
diff --git a/src/app/components/param-field-line/param-field-line.component.ts b/src/app/components/param-field-line/param-field-line.component.ts
index 198dc15cd..ed9bb5063 100644
--- a/src/app/components/param-field-line/param-field-line.component.ts
+++ b/src/app/components/param-field-line/param-field-line.component.ts
@@ -219,7 +219,7 @@ export class ParamFieldLineComponent implements OnChanges {
         switch (option) {
             case "fix":
                 this.param.valueMode = ParamValueMode.SINGLE;
-                // @WTF why do we reset the value here ?
+                // reset the value to avoid "undefined" after exiting CALC or LINK mode
                 this.param.setValue(this, this.param.paramDefinition.paramValues.singleValue);
                 break;
 
diff --git a/src/app/formulaire/definition/form-def-fixedvar.ts b/src/app/formulaire/definition/form-def-fixedvar.ts
index d4add5b8f..4474e9a7c 100644
--- a/src/app/formulaire/definition/form-def-fixedvar.ts
+++ b/src/app/formulaire/definition/form-def-fixedvar.ts
@@ -148,7 +148,7 @@ export class FormDefFixedVar {
                 }
             }
             // if the current calculated parameter was set to another mode, set a new param
-            // to calculated mode (there must always be at least one)
+            // to calculated mode (there must always be exactly one)
             if (newCal) {
                 newCal.valueMode = ParamValueMode.CALCUL;
             }
-- 
GitLab


From 15d8df116b292c7bca1565a4fae25358004cc141 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 19 Mar 2019 14:27:50 +0100
Subject: [PATCH 07/39] Linked param: back to FIX mode when no more targets are
 available

---
 src/app/components/param-link/param-link.component.ts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/app/components/param-link/param-link.component.ts b/src/app/components/param-link/param-link.component.ts
index 5601ced0a..edad76498 100644
--- a/src/app/components/param-link/param-link.component.ts
+++ b/src/app/components/param-link/param-link.component.ts
@@ -137,7 +137,9 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
     }
 
     /**
-     * lie le paramètre géré à un des paramètres liables de la liste
+     * lie le paramètre géré à un des paramètres liables de la liste; appelé
+     * systématiquement lorsqu'on construit le formulaire, même si le
+     * paramètre est déjà lié
      * @param index indice dans la liste
      */
     private linkTo(index: number) {
@@ -185,6 +187,8 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
             }
         } else {
             this._currentIndex = -1;
+            // back to SINGLE mode by default
+            this.param.valueMode = ParamValueMode.SINGLE;
         }
     }
 
-- 
GitLab


From e488c13671807505b2d1d25f929f5cdd79ea1990 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 19 Mar 2019 14:35:33 +0100
Subject: [PATCH 08/39] Reset all depending forms results when closing a
 calculator

---
 src/app/services/formulaire/formulaire.service.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 755083ba6..d8db5e661 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -403,6 +403,8 @@ export class FormulaireService extends Observable {
                 "action": "closeForm",
                 "form": form
             });
+            // reset the results of all forms depending on this one
+            this.resetAllDependingFormsResults(form);
         }
         if (nub) {
             Session.getInstance().deleteNub(nub);
-- 
GitLab


From c43d270da8fc9b5e711361b2d0f8f487b769f8ea Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 19 Mar 2019 17:40:18 +0100
Subject: [PATCH 09/39] =?UTF-8?q?Aper=C3=A7u=20des=20param=C3=A8tres=20li?=
 =?UTF-8?q?=C3=A9s:=20gestion=20des=20r=C3=A9sultats=20de=20calcul?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/formulaire/ngparam.ts | 49 ++++++++++++++++++++++++++++-------
 1 file changed, 40 insertions(+), 9 deletions(-)

diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 8a4364854..de3ce46dc 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -51,9 +51,11 @@ export class NgParameter extends InputField implements Observer {
 
     /**
      * Returns a text preview of the current value(s), depending on the value mode
+     * @TODO use display precision to limit decimals
      */
     public static preview(p: ParamDefinition): string {
         let valuePreview: string;
+        const i18n = ServiceFactory.instance.i18nService;
         // console.log("NgParam::preview()", p.symbol, ParamValueMode[p.valueMode], p);
         switch (p.valueMode) {
             case ParamValueMode.SINGLE:
@@ -73,30 +75,59 @@ export class NgParameter extends InputField implements Observer {
                 if (step) {
                     step = step.toFixed(nDigits);
                 }
-                valuePreview = ServiceFactory.instance.i18nService.localizeText("INFO_PARAMFIELD_PARAMVARIER_MINMAXSTEP");
+                valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_MINMAXSTEP");
                 valuePreview = sprintf(valuePreview, min, max, step);
                 break;
             case ParamValueMode.LISTE:
-                valuePreview = ServiceFactory.instance.i18nService.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
+                valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
                 const vals = p.paramValues.valueList || [];
                 valuePreview += " " + vals.slice(0, 5).join("; ") + "…";
                 break;
             case ParamValueMode.CALCUL:
-                valuePreview = ServiceFactory.instance.i18nService.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
+                valuePreview = i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
                 if (p.calculability === ParamCalculability.DICHO) {
-                    valuePreview += " (" + ServiceFactory.instance.i18nService.localizeText("INFO_PARAMFIELD_IN_CALCULATION_INITIAL_VALUE")
+                    valuePreview += " (" + i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION_INITIAL_VALUE")
                         + ": " + p.getValue() + ")";
                 }
                 break;
             case ParamValueMode.LINK:
                 if (p.isReferenceDefined()) {
                     if (p.referencedValue.isParameter()) {
-                        // recursive call
-                        valuePreview =  NgParameter.preview(p.referencedValue.element as ParamDefinition);
+                        const targetParam = (p.referencedValue.element as ParamDefinition);
+                        // calculated param ?
+                        if (targetParam.valueMode === ParamValueMode.CALCUL) {
+                            // was the result already computed ?
+                            if (p.referencedValue.nub.result) {
+                                if (p.referencedValue.hasMultipleValues()) {
+                                    // like LIST mode
+                                    valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
+                                    valuePreview += " " + p.referencedValue.nub.result.getCalculatedValues().slice(0, 5).join("; ") + "…";
+                                } else {
+                                    valuePreview = String(p.referencedValue.nub.result.vCalc);
+                                }
+                            } else {
+                                valuePreview = i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
+                            }
+                        } else {
+                            // recursive call
+                            valuePreview =  NgParameter.preview(targetParam);
+                        }
                     } else {
-                        // @TODO what if the result was already computed ?
-                        // Is the computed value fresh or stale ?
-                        valuePreview = ServiceFactory.instance.i18nService.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
+                        // was the result already computed ?
+                        try {
+                            const remoteValues = p.referencedValue.getParamValues();
+                            // @TODO is the computed value fresh or stale ?
+                            if (p.referencedValue.hasMultipleValues()) {
+                                // like LIST mode
+                                valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
+                                valuePreview += " " + remoteValues.valueList.slice(0, 5).join("; ") + "…";
+                            } else {
+                                // like SINGLE mode
+                                valuePreview = String(remoteValues.currentValue);
+                            }
+                        } catch (e) {
+                            valuePreview = i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
+                        }
                     }
                 }
         }
-- 
GitLab


From 82a6d8111c48a4baa5d0fbe3882b7595bb58ba41 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 20 Mar 2019 11:40:51 +0100
Subject: [PATCH 10/39] Fix #172

---
 .../param-field-line.component.ts             |  1 +
 .../definition/form-def-fixedvar.ts           |  8 -----
 src/app/formulaire/ngparam.ts                 | 29 ++++++++++++-------
 3 files changed, 20 insertions(+), 18 deletions(-)

diff --git a/src/app/components/param-field-line/param-field-line.component.ts b/src/app/components/param-field-line/param-field-line.component.ts
index ed9bb5063..e147f1f9f 100644
--- a/src/app/components/param-field-line/param-field-line.component.ts
+++ b/src/app/components/param-field-line/param-field-line.component.ts
@@ -220,6 +220,7 @@ export class ParamFieldLineComponent implements OnChanges {
             case "fix":
                 this.param.valueMode = ParamValueMode.SINGLE;
                 // reset the value to avoid "undefined" after exiting CALC or LINK mode
+                // @TODO not always necessary; find out why
                 this.param.setValue(this, this.param.paramDefinition.paramValues.singleValue);
                 break;
 
diff --git a/src/app/formulaire/definition/form-def-fixedvar.ts b/src/app/formulaire/definition/form-def-fixedvar.ts
index 4474e9a7c..469bda41b 100644
--- a/src/app/formulaire/definition/form-def-fixedvar.ts
+++ b/src/app/formulaire/definition/form-def-fixedvar.ts
@@ -155,14 +155,6 @@ export class FormDefFixedVar {
         }
     }
 
-    private logParams() {
-        for (const fe of this._formBase.allFormElements) {
-            if (fe instanceof NgParameter) {
-                console.log(`${fe.paramDefinition.symbol} : ${ParamValueMode[fe.paramDefinition.valueMode]}`);
-            }
-        }
-    }
-
     /**
      * gestion des événements clic sur les radios
      */
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index de3ce46dc..ffc0b9183 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -56,13 +56,13 @@ export class NgParameter extends InputField implements Observer {
     public static preview(p: ParamDefinition): string {
         let valuePreview: string;
         const i18n = ServiceFactory.instance.i18nService;
+        const nDigits = ServiceFactory.instance.applicationSetupService.displayDigits;
         // console.log("NgParam::preview()", p.symbol, ParamValueMode[p.valueMode], p);
         switch (p.valueMode) {
             case ParamValueMode.SINGLE:
-                valuePreview = String(p.getValue());
+                valuePreview = String(p.getValue().toFixed(nDigits));
                 break;
             case ParamValueMode.MINMAX:
-                const nDigits = ServiceFactory.instance.applicationSetupService.displayDigits;
                 let min: any = p.paramValues.min;
                 let max: any = p.paramValues.max;
                 let step: any = p.paramValues.step;
@@ -81,13 +81,15 @@ export class NgParameter extends InputField implements Observer {
             case ParamValueMode.LISTE:
                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
                 const vals = p.paramValues.valueList || [];
-                valuePreview += " " + vals.slice(0, 5).join("; ") + "…";
+                valuePreview += " " + vals.slice(0, 5).map((v) => {
+                    return v.toFixed(nDigits);
+                }).join("; ") + "…";
                 break;
             case ParamValueMode.CALCUL:
                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
                 if (p.calculability === ParamCalculability.DICHO) {
                     valuePreview += " (" + i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION_INITIAL_VALUE")
-                        + ": " + p.getValue() + ")";
+                        + ": " + p.getValue().toFixed(nDigits) + ")";
                 }
                 break;
             case ParamValueMode.LINK:
@@ -101,9 +103,11 @@ export class NgParameter extends InputField implements Observer {
                                 if (p.referencedValue.hasMultipleValues()) {
                                     // like LIST mode
                                     valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
-                                    valuePreview += " " + p.referencedValue.nub.result.getCalculatedValues().slice(0, 5).join("; ") + "…";
+                                    valuePreview += " " + p.referencedValue.nub.result.getCalculatedValues().map((v) => {
+                                        return v.toFixed(nDigits);
+                                    }).slice(0, 5).join("; ") + "…";
                                 } else {
-                                    valuePreview = String(p.referencedValue.nub.result.vCalc);
+                                    valuePreview = String(p.referencedValue.nub.result.vCalc.toFixed(nDigits));
                                 }
                             } else {
                                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
@@ -120,10 +124,12 @@ export class NgParameter extends InputField implements Observer {
                             if (p.referencedValue.hasMultipleValues()) {
                                 // like LIST mode
                                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
-                                valuePreview += " " + remoteValues.valueList.slice(0, 5).join("; ") + "…";
+                                valuePreview += " " + remoteValues.valueList.slice(0, 5).map((v) => {
+                                    return v.toFixed(nDigits);
+                                }).join("; ") + "…";
                             } else {
                                 // like SINGLE mode
-                                valuePreview = String(remoteValues.currentValue);
+                                valuePreview = String(remoteValues.currentValue.toFixed(nDigits));
                             }
                         } catch (e) {
                             valuePreview = i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
@@ -183,16 +189,19 @@ export class NgParameter extends InputField implements Observer {
         return this._paramDef.valueMode;
     }
 
-    // @TODO vérifier : ça a une tête à faire 40x trop d'opérations pour rien
+    /**
+     * Unlinks the parameter and updates its value when value mode changes
+     */
     public set valueMode(m: ParamValueMode) {
         // undefined si on clique en dehors du select après l'avoir ouvert (cad sans avoir fait de sélection)
         // et au même niveau, cad à côté du bouton et non à côté du menu déroulant
         if (m !== undefined && this._paramDef.valueMode !== m) {
+            const nDigits = ServiceFactory.instance.applicationSetupService.displayDigits;
             this.unlinkParameter();
             this._paramDef.valueMode = m;
             this.notifyObservers({
                 "action": "valueModeChange",
-                "value": this._paramDef.getValue()
+                "value": Number(this._paramDef.getValue().toFixed(nDigits))
             });
         }
     }
-- 
GitLab


From 33a5a24de86e2cff824ea0873af41d622ecaf1aa Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 20 Mar 2019 13:59:19 +0100
Subject: [PATCH 11/39] =?UTF-8?q?Invalidation=20des=20r=C3=A9sultats=20?=
 =?UTF-8?q?=C3=A0=20l'aval=20:=20r=C3=A9percussion=20sur=20le=20mod=C3=A8l?=
 =?UTF-8?q?e?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/formulaire/definition/concrete/form-base.ts | 4 +++-
 src/app/services/formulaire/formulaire.service.ts   | 5 ++++-
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/app/formulaire/definition/concrete/form-base.ts b/src/app/formulaire/definition/concrete/form-base.ts
index 9d7e4b120..27ee0ab3f 100644
--- a/src/app/formulaire/definition/concrete/form-base.ts
+++ b/src/app/formulaire/definition/concrete/form-base.ts
@@ -36,7 +36,9 @@ export class FormulaireBase extends FormulaireDefinition {
 
     public resetResults() {
         this._formResult.resetResults();
-        // reset the results of all forms depending on this one
+        // reset model results
+        this.currentNub.resetResult();
+        // reset the result panels of all forms depending on this one
         ServiceFactory.instance.formulaireService.resetAllDependingFormsResults(this);
     }
 
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index d8db5e661..1c4bfc87b 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -403,7 +403,10 @@ export class FormulaireService extends Observable {
                 "action": "closeForm",
                 "form": form
             });
-            // reset the results of all forms depending on this one
+
+            // reset model results
+            form.currentNub.resetResult();
+            // reset the result panels of all forms depending on this one
             this.resetAllDependingFormsResults(form);
         }
         if (nub) {
-- 
GitLab


From 209b9cbc56bdf9555e5255f8cf38106e0aa65ebf Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 20 Mar 2019 15:06:12 +0100
Subject: [PATCH 12/39] Onglets: correction CSS pour Chrome

---
 src/app/app.component.scss | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/app/app.component.scss b/src/app/app.component.scss
index c45d95ed1..98e0c6c01 100644
--- a/src/app/app.component.scss
+++ b/src/app/app.component.scss
@@ -115,7 +115,7 @@ button:focus {
         border-bottom: none;
     }
     .calculator-button.mat-raised-button {
-        height: calc(#{$navbar_height} - 3px);
+        height: calc(#{$navbar_height} - 5px);
 
         &:not([class*="mat-elevation-z"]) {
             box-shadow: -1px -2px 2px 0 rgba(0,0,0,.4),1px -2px 5px 0 rgba(0,0,0,.1);
-- 
GitLab


From 5996ec78082bbcf73c1e3fc564868b983c975941 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Thu, 21 Mar 2019 10:21:20 +0100
Subject: [PATCH 13/39] Fix #167

---
 src/app/app.module.ts                         |  2 +
 .../dialog-load-session.component.html        | 17 ++++-
 .../dialog-load-session.component.scss        | 21 ++++++
 .../dialog-load-session.component.ts          | 74 +++++++++++++++++++
 .../services/formulaire/formulaire.service.ts | 28 +++++--
 src/locale/messages.en.json                   |  2 +
 src/locale/messages.fr.json                   |  2 +
 7 files changed, 138 insertions(+), 8 deletions(-)

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 2759ef9f5..a1f172ab0 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -19,6 +19,7 @@ import {
   MatCardModule,
   MatTableModule,
   MatSnackBarModule,
+  MatBadgeModule,
   ErrorStateMatcher,
   MatButtonToggleModule
 } from "@angular/material";
@@ -104,6 +105,7 @@ const appRoutes: Routes = [
     ChartModule,
     HttpClientModule,
     FlexLayoutModule,
+    MatBadgeModule,
     MatButtonModule,
     MatButtonToggleModule,
     MatCardModule,
diff --git a/src/app/components/dialog-load-session/dialog-load-session.component.html b/src/app/components/dialog-load-session/dialog-load-session.component.html
index 0473b4cde..97e2d6e42 100644
--- a/src/app/components/dialog-load-session/dialog-load-session.component.html
+++ b/src/app/components/dialog-load-session/dialog-load-session.component.html
@@ -13,7 +13,8 @@
     </mat-form-field>
 
     <div class="cb-container">
-      <mat-checkbox [name]="c.uid" *ngFor="let c of calculators" [(ngModel)]="c.selected" [ngModelOptions]="{standalone: true}">
+      <mat-checkbox [name]="c.uid" *ngFor="let c of calculators" (change)="checkLinkedParamsDependencies()"
+        [(ngModel)]="c.selected" [ngModelOptions]="{standalone: true}">
         {{ c.title }}
       </mat-checkbox>
     </div>
@@ -26,6 +27,20 @@
         {{ uitextNone }}
       </button>
     </div>
+
+    <div class="dependencies-problems" *ngIf="dependenciesProblems.length > 0">
+      <mat-list role="list">
+        <mat-list-item role="listitem" *ngFor="let dp of dependenciesProblems">
+          <mat-icon color="warn">error_outline</mat-icon> {{ dp.message }}
+        </mat-list-item>
+      </mat-list>
+      <p>
+        <button mat-raised-button (click)="fixDependencies()"
+            [matBadge]="dependenciesProblems.length" matBadgeColor="warn">
+          {{ uitextFixMissingDependencies }}
+        </button>
+      </p>
+    </div>
   </div>
 
   <div mat-dialog-actions>
diff --git a/src/app/components/dialog-load-session/dialog-load-session.component.scss b/src/app/components/dialog-load-session/dialog-load-session.component.scss
index e612ad2cf..f9cad2765 100644
--- a/src/app/components/dialog-load-session/dialog-load-session.component.scss
+++ b/src/app/components/dialog-load-session/dialog-load-session.component.scss
@@ -17,3 +17,24 @@ mat-form-field {
         margin-right: 5px;
     }
 }
+
+.dependencies-problems {
+
+    .mat-list-base {
+        padding-top: 0;
+
+        .mat-list-item {
+            height: 32px;
+            font-size: .9em;
+
+            ::ng-deep .mat-list-item-content {
+                padding-left: 0;
+
+                mat-icon {
+                    transform: scale(0.8);
+                    margin-right: 5px;
+                }
+            }
+        }
+    }
+}
diff --git a/src/app/components/dialog-load-session/dialog-load-session.component.ts b/src/app/components/dialog-load-session/dialog-load-session.component.ts
index 3aa55c836..b4bb4c836 100644
--- a/src/app/components/dialog-load-session/dialog-load-session.component.ts
+++ b/src/app/components/dialog-load-session/dialog-load-session.component.ts
@@ -17,6 +17,8 @@ export class DialogLoadSessionComponent {
 
     public loadSessionForm: FormGroup;
 
+    public dependenciesProblems: any[] = [];
+
     constructor(
       public dialogRef: MatDialogRef<DialogLoadSessionComponent>,
       private intlService: I18nService,
@@ -40,6 +42,74 @@ export class DialogLoadSessionComponent {
       }
     }
 
+    public checkLinkedParamsDependencies() {
+      this.dependenciesProblems = [];
+      // for all checked Nubs
+      this.calculators.forEach((c) => {
+        if (c.selected) {
+          // do all required nubs are checked ?
+          c.requires.forEach((r) => {
+            if (! this.isCalculatorOrParentSelected(r)) {
+              const realUid = this.getUidOrParentUid(r);
+              const depTitle = this.getTitleFromUid(realUid);
+              this.dependenciesProblems.push({
+                requiring: c.title,
+                required: depTitle,
+                requiredUid: realUid,
+                message: c.title + " " + this.intlService.localizeText("INFO_REQUIRES") + " " + depTitle
+              });
+            }
+          });
+        }
+      });
+    }
+
+    public fixDependencies() {
+      for (const dp of this.dependenciesProblems) {
+        this.selectRequiredModule(dp.requiredUid);
+      }
+    }
+
+    private isCalculatorOrParentSelected(uid: string): boolean {
+      let isSelected = false;
+      this.calculators.forEach((c) => {
+        if (c.uid === uid || c.children.includes(uid)) {
+          isSelected = c.selected;
+        }
+      });
+      return isSelected;
+    }
+
+    private getUidOrParentUid(uid: string): string {
+      let realUid: string;
+      this.calculators.forEach((c) => {
+        if (c.uid === uid || c.children.includes(uid)) {
+          realUid = c.uid;
+        }
+      });
+      return realUid;
+    }
+
+    private getTitleFromUid(uid: string): string {
+      let title: string;
+      this.calculators.forEach((c) => {
+        if (c.uid === uid) {
+          title = c.title;
+        }
+      });
+      return title;
+    }
+
+    private selectRequiredModule(uid: string) {
+      this.calculators.forEach((c) => {
+        if (c.uid === uid) {
+          c.selected = true;
+        }
+      });
+      // re-run dependency checking
+      this.checkLinkedParamsDependencies();
+    }
+
     public onFileSelected(event: any) {
       if (event.target.files && event.target.files.length) {
         this.file = event.target.files[0];
@@ -94,4 +164,8 @@ export class DialogLoadSessionComponent {
     public get uitextLoadSessionTitle() {
       return this.intlService.localizeText("INFO_DIALOG_LOAD_SESSION_TITLE");
     }
+
+    public get uitextFixMissingDependencies() {
+      return this.intlService.localizeText("INFO_DIALOG_FIX_MISSING_DEPENDENCIES");
+    }
 }
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 1c4bfc87b..7a34e00ae 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -451,16 +451,13 @@ export class FormulaireService extends Observable {
     private readSingleFile(file: File): Promise<any> {
         return new Promise<any>((resolve, reject) => {
             const 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);
         });
     }
@@ -507,10 +504,27 @@ export class FormulaireService extends Observable {
             // liste des noms de modules de calcul
             if (data.session && Array.isArray(data.session)) {
                 data.session.forEach((e: any) => {
-                   res.push({
-                       uid: e.uid,
-                       title: e.meta && e.meta.title ? e.meta.title : undefined
-                    });
+                    const nubInfo = {
+                        uid: e.uid,
+                        title: e.meta && e.meta.title ? e.meta.title : undefined,
+                        requires: [],
+                        children: []
+                    };
+                    // list linked params dependencies for each Nub
+                    if (e.parameters) {
+                        e.parameters.forEach((p) => {
+                            if (p.targetNub && ! nubInfo.requires.includes(p.targetNub)) {
+                                nubInfo.requires.push(p.targetNub);
+                            }
+                        });
+                    }
+                    // list children nubs for each Nub
+                    if (e.structures) {
+                        e.structures.forEach((p) => {
+                            nubInfo.children.push(p.uid);
+                        });
+                    }
+                    res.push(nubInfo);
                 });
             }
             return res;
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 506bda9e7..d265692b8 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -62,6 +62,7 @@
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Submerged",
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Zero flow",
     "INFO_DIALOG_COMPUTED_VALUE_TITLE": "Edit initial value",
+    "INFO_DIALOG_FIX_MISSING_DEPENDENCIES": "Fix missing dependencies",
     "INFO_DIALOG_LOAD_SESSION_FILENAME": "Choose a file",
     "INFO_DIALOG_LOAD_SESSION_TITLE": "Load calculator modules",
     "INFO_DIALOG_SAVE_SESSION_FILENAME": "File name",
@@ -196,6 +197,7 @@
     "INFO_REMOUS_LARGEUR_BERGE": "Width at embankment level = %B% m",
     "INFO_REMOUS_RESSAUT_DEHORS": "Hydraulic jump detected %sens% abscissa %x% m",
     "INFO_REMOUS_RESSAUT_HYDRO": "Hydraulic jump detected between abscissa %xmin% and %xmax% m",
+    "INFO_REQUIRES": "requires",
     "INFO_SECTIONPARAMETREE_TITRE": "Parametric section",
     "INFO_SECTIONPARAMETREE_TITRE_COURT": "Param. section",
     "INFO_SETUP_NEWTON_MAX_ITER": "Newton iteration limit",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 90b8466f0..5c40ca5d0 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -62,6 +62,7 @@
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_2": "Noyé",
     "INFO_EXTRARES_ENUM_STRUCTUREFLOWREGIME_3": "Débit nul",
     "INFO_DIALOG_COMPUTED_VALUE_TITLE": "Modifier la valeur initiale",
+    "INFO_DIALOG_FIX_MISSING_DEPENDENCIES": "Résoudre les dépendances",
     "INFO_DIALOG_LOAD_SESSION_FILENAME": "Choisir un fichier",
     "INFO_DIALOG_LOAD_SESSION_TITLE": "Charger des modules de calcul",
     "INFO_DIALOG_SAVE_SESSION_FILENAME": "Nom de fichier",
@@ -196,6 +197,7 @@
     "INFO_REMOUS_LARGEUR_BERGE": "Largeur au niveau des berges = %B% m",
     "INFO_REMOUS_RESSAUT_DEHORS": "Ressaut hydraulique détecté à l'%sens% de l'abscisse %x% m",
     "INFO_REMOUS_RESSAUT_HYDRO": "Ressaut hydraulique détecté entre les abscisses %xmin% et %xmax% m",
+    "INFO_REQUIRES": "dépend de",
     "INFO_SECTIONPARAMETREE_TITRE": "Section paramétrée",
     "INFO_SECTIONPARAMETREE_TITRE_COURT": "Sec. param.",
     "INFO_SETUP_NEWTON_MAX_ITER": "Newton : nombre d'itérations maximum",
-- 
GitLab


From 0abdc81a3af664b223dc48d4889486c699ca7bc6 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Thu, 21 Mar 2019 10:22:15 +0100
Subject: [PATCH 14/39] Ajout test e2e chargement de session avec liens
 complexes

---
 e2e/calculator.po.ts               |   4 +
 e2e/load-linked-params.e2e-spec.ts |  89 +++++++++
 e2e/session-liens-spaghetti.json   | 307 +++++++++++++++++++++++++++++
 3 files changed, 400 insertions(+)
 create mode 100644 e2e/load-linked-params.e2e-spec.ts
 create mode 100644 e2e/session-liens-spaghetti.json

diff --git a/e2e/calculator.po.ts b/e2e/calculator.po.ts
index 75d501000..c9276cad4 100644
--- a/e2e/calculator.po.ts
+++ b/e2e/calculator.po.ts
@@ -29,6 +29,10 @@ export class CalculatorPage {
     return element(by.css("mat-select#" + id));
   }
 
+  async getSelectValueText(select: ElementFinder) {
+    return await select.element(by.css(".mat-select-value-text > span")).getText();
+  }
+
   getInputById(id: string) {
     return element(by.css("input#" + id));
   }
diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts
new file mode 100644
index 000000000..62405f439
--- /dev/null
+++ b/e2e/load-linked-params.e2e-spec.ts
@@ -0,0 +1,89 @@
+import { AppPage } from "./app.po";
+import { CalculatorPage } from "./calculator.po";
+import { Navbar } from "./navbar.po";
+import { SideNav } from "./sidenav.po";
+import { browser } from "protractor";
+
+/**
+ * Load a session containing 4 calculators, having multiple linked parameters
+ * from one to another
+ * @TODO les valeurs des Select sont comparées au français, pas très générique :/
+ */
+describe("ngHyd − load session with multiple linked parameters", () => {
+  let startPage: AppPage;
+  let calcPage: CalculatorPage;
+  let navbar: Navbar;
+  let sidenav: SideNav;
+
+  function init() {
+      startPage = new AppPage();
+      calcPage = new CalculatorPage();
+      navbar = new Navbar();
+      sidenav = new SideNav();
+  }
+  beforeEach(init);
+
+  it("when loading session-liens-spaghetti.json, all links should point to the right target - ", async () => {
+    await startPage.navigateTo();
+
+    await navbar.clickMenuButton();
+    await browser.sleep(200);
+
+    await sidenav.clickLoadSessionButton();
+    await browser.sleep(200);
+
+    await sidenav.loadSessionFile("./session-liens-spaghetti.json");
+    await browser.sleep(200);
+
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(4);
+
+    // 1. check Section paramétrée
+    await navbar.clickCalculatorTab(0);
+
+    // check target params values
+    const sp_lb = calcPage.getSelectById("linked_LargeurBerge");
+    const sp_lbv = await calcPage.getSelectValueText(sp_lb);
+    expect(sp_lbv).toEqual("L (Ouvrages, ouvrage 3)");
+
+    // 2. check Passe à macro-rugosités
+    await navbar.clickCalculatorTab(1);
+
+    // check target params values
+    const mr_b = calcPage.getSelectById("linked_B");
+    const mr_bv = await calcPage.getSelectValueText(mr_b);
+    expect(mr_bv).toEqual("L (résultat de Ouvrages, ouvrage 1)");
+
+    const mr_q = calcPage.getSelectById("linked_Q");
+    const mr_qv = await calcPage.getSelectValueText(mr_q);
+    expect(mr_qv).toEqual("Q (résultat de R. uniforme)");
+
+    // 3. check Lois d'ouvrages
+    await navbar.clickCalculatorTab(2);
+
+    // check target params values
+    const lo_z1 = calcPage.getSelectById("linked_Z1");
+    const lo_z1v = await calcPage.getSelectValueText(lo_z1);
+    expect(lo_z1v).toEqual("ZF2 (Macro-rugo., résultat complémentaire)");
+
+    const lo_l = calcPage.getSelectById("linked_L"); // attention ID non unique, voir nghyd#173
+    const lo_lv = await calcPage.getSelectValueText(lo_l);
+    expect(lo_lv).toEqual("L (résultat de Ouvrages, ouvrage 1)");
+
+    const lo_w = calcPage.getSelectById("linked_W"); // attention ID non unique, voir nghyd#173
+    const lo_wv = await calcPage.getSelectValueText(lo_w);
+    expect(lo_wv).toEqual("W (Ouvrages, ouvrage 2)");
+
+    const lo_cd = calcPage.getSelectById("linked_Cd"); // attention ID non unique, voir nghyd#173
+    const lo_cdv = await calcPage.getSelectValueText(lo_cd);
+    expect(lo_cdv).toEqual("Cd (Ouvrages, ouvrage 1)");
+
+    // 4. check Régime uniforme
+    await navbar.clickCalculatorTab(3);
+
+    // check target params values
+    const lo_y = calcPage.getSelectById("linked_Y");
+    const lo_yv = await calcPage.getSelectValueText(lo_y);
+    expect(lo_yv).toEqual("Yt (Sec. param., résultat complémentaire)");
+  });
+
+});
diff --git a/e2e/session-liens-spaghetti.json b/e2e/session-liens-spaghetti.json
new file mode 100644
index 000000000..4cc7304df
--- /dev/null
+++ b/e2e/session-liens-spaghetti.json
@@ -0,0 +1,307 @@
+{
+    "session": [
+        {
+            "uid": "OW9rd3",
+            "props": {
+                "varCalc": "Hs",
+                "calcType": 2,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "Sec. param."
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 1.2
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "LINK",
+                    "targetNub": "dmh4Z3",
+                    "targetParam": "L"
+                }
+            ]
+        },
+        {
+            "uid": "dTB3ZG",
+            "props": {
+                "calcType": 11,
+                "nodeType": 0
+            },
+            "meta": {
+                "title": "Macro-rugo."
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "ZF1",
+                    "mode": "SINGLE",
+                    "value": 12.5
+                },
+                {
+                    "symbol": "L",
+                    "mode": "SINGLE",
+                    "value": 6
+                },
+                {
+                    "symbol": "B",
+                    "mode": "LINK",
+                    "targetNub": "cjdyYW",
+                    "targetParam": "L"
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.05
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "LINK",
+                    "targetNub": "OGhuZj",
+                    "targetParam": "Q"
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 0.01
+                },
+                {
+                    "symbol": "C",
+                    "mode": "SINGLE",
+                    "value": 0.05
+                },
+                {
+                    "symbol": "PBD",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "PBH",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "Cd0",
+                    "mode": "MINMAX",
+                    "min": 0.75,
+                    "max": 3,
+                    "step": 0.1125
+                }
+            ]
+        },
+        {
+            "uid": "ZW9icn",
+            "props": {
+                "calcType": 8,
+                "nodeType": 0
+            },
+            "meta": {
+                "title": "Ouvrages"
+            },
+            "structures": [
+                {
+                    "uid": "cjdyYW",
+                    "props": {
+                        "calcType": 7,
+                        "nodeType": 5,
+                        "structureType": 1,
+                        "loiDebit": 1
+                    },
+                    "parameters": [
+                        {
+                            "symbol": "ZDV",
+                            "mode": "SINGLE",
+                            "value": 100
+                        },
+                        {
+                            "symbol": "W",
+                            "mode": "SINGLE",
+                            "value": 0.5
+                        },
+                        {
+                            "symbol": "L",
+                            "mode": "CALCUL"
+                        },
+                        {
+                            "symbol": "Cd",
+                            "mode": "SINGLE",
+                            "value": 0.6
+                        }
+                    ]
+                },
+                {
+                    "uid": "Ynpnaj",
+                    "props": {
+                        "calcType": 7,
+                        "nodeType": 5,
+                        "structureType": 1,
+                        "loiDebit": 1
+                    },
+                    "parameters": [
+                        {
+                            "symbol": "ZDV",
+                            "mode": "SINGLE",
+                            "value": 100
+                        },
+                        {
+                            "symbol": "W",
+                            "mode": "SINGLE",
+                            "value": 0.5
+                        },
+                        {
+                            "symbol": "L",
+                            "mode": "LINK",
+                            "targetNub": "cjdyYW",
+                            "targetParam": "L"
+                        },
+                        {
+                            "symbol": "Cd",
+                            "mode": "SINGLE",
+                            "value": 0.6
+                        }
+                    ]
+                },
+                {
+                    "uid": "dmh4Z3",
+                    "props": {
+                        "calcType": 7,
+                        "nodeType": 5,
+                        "structureType": 1,
+                        "loiDebit": 1
+                    },
+                    "parameters": [
+                        {
+                            "symbol": "ZDV",
+                            "mode": "SINGLE",
+                            "value": 100
+                        },
+                        {
+                            "symbol": "W",
+                            "mode": "LINK",
+                            "targetNub": "Ynpnaj",
+                            "targetParam": "W"
+                        },
+                        {
+                            "symbol": "L",
+                            "mode": "SINGLE",
+                            "value": 2
+                        },
+                        {
+                            "symbol": "Cd",
+                            "mode": "LINK",
+                            "targetNub": "cjdyYW",
+                            "targetParam": "Cd"
+                        }
+                    ]
+                }
+            ],
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "Z1",
+                    "mode": "LINK",
+                    "targetNub": "dTB3ZG",
+                    "targetParam": "ZF2"
+                },
+                {
+                    "symbol": "Z2",
+                    "mode": "SINGLE",
+                    "value": 101.5
+                }
+            ]
+        },
+        {
+            "uid": "OGhuZj",
+            "props": {
+                "calcType": 3,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "R. uniforme"
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "If",
+                    "mode": "MINMAX",
+                    "min": 0.0005,
+                    "max": 0.002,
+                    "step": 0.00007500000000000001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "LINK",
+                    "targetNub": "OW9rd3",
+                    "targetParam": "Yt"
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "SINGLE",
+                    "value": 2.5
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
-- 
GitLab


From 01a93892cc4a6224fe030f8760782d968a7e4bbd Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Fri, 22 Mar 2019 14:51:55 +0100
Subject: [PATCH 15/39] =?UTF-8?q?Chargement=20de=20session:=20meilleure=20?=
 =?UTF-8?q?d=C3=A9tection=20des=20d=C3=A9pendances?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../dialog-load-session/dialog-load-session.component.ts      | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/app/components/dialog-load-session/dialog-load-session.component.ts b/src/app/components/dialog-load-session/dialog-load-session.component.ts
index b4bb4c836..b91673e4b 100644
--- a/src/app/components/dialog-load-session/dialog-load-session.component.ts
+++ b/src/app/components/dialog-load-session/dialog-load-session.component.ts
@@ -34,12 +34,16 @@ export class DialogLoadSessionComponent {
       for (const c of this.calculators) {
         c.selected = true;
       }
+      // re-run dependency checking
+      this.checkLinkedParamsDependencies();
     }
 
     public selectNone() {
       for (const c of this.calculators) {
         c.selected = false;
       }
+      // re-run dependency checking
+      this.checkLinkedParamsDependencies();
     }
 
     public checkLinkedParamsDependencies() {
-- 
GitLab


From cf8616f603a787716d937e9358b22314e4f0868a Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Fri, 22 Mar 2019 14:52:41 +0100
Subject: [PATCH 16/39] =?UTF-8?q?=C3=89vite=20une=20boucle=20infinie=20dan?=
 =?UTF-8?q?s=20la=20r=C3=A9initialisation=20des=20r=C3=A9sultats?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../generic-calculator/calculator.component.ts      |  2 +-
 .../section-results/section-results.component.ts    |  1 -
 src/app/formulaire/definition/concrete/form-base.ts | 10 ++++++++--
 src/app/formulaire/definition/form-definition.ts    |  4 ++--
 src/app/formulaire/ngparam.ts                       |  2 +-
 src/app/services/formulaire/formulaire.service.ts   | 13 ++++++++-----
 6 files changed, 20 insertions(+), 12 deletions(-)

diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index a0cc4b468..890d21a4a 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -362,7 +362,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
      * réception d'un événement de changement de valeur d'un input
      */
     private onInputChange() {
-        this._formulaire.resetResults();
+        this._formulaire.resetResults([]);
     }
 
     /**
diff --git a/src/app/components/section-results/section-results.component.ts b/src/app/components/section-results/section-results.component.ts
index 34248eae2..40eb93801 100644
--- a/src/app/components/section-results/section-results.component.ts
+++ b/src/app/components/section-results/section-results.component.ts
@@ -6,7 +6,6 @@ import { SectionCanvasComponent } from "../section-canvas/section-canvas.compone
 import { SectionResults } from "../../results/section-results";
 import { ApplicationSetupService } from "../../services/app-setup/app-setup.service";
 import { CalculatorResults } from "../../results/calculator-results";
-import { FixedResults } from "../../results/fixed-results";
 
 @Component({
     selector: "section-results",
diff --git a/src/app/formulaire/definition/concrete/form-base.ts b/src/app/formulaire/definition/concrete/form-base.ts
index 27ee0ab3f..edc9f93c2 100644
--- a/src/app/formulaire/definition/concrete/form-base.ts
+++ b/src/app/formulaire/definition/concrete/form-base.ts
@@ -34,12 +34,18 @@ export class FormulaireBase extends FormulaireDefinition {
         this._formParamCalc.onRadioClick(info);
     }
 
-    public resetResults() {
+    /**
+     * Resets the form results, the results panel on screen, the model
+     * results, and does the same for all depending modules
+     */
+    public resetResults(visited: string[] = []) {
+        visited.push(this.currentNub.uid);
+        // reset GUI results
         this._formResult.resetResults();
         // reset model results
         this.currentNub.resetResult();
         // reset the result panels of all forms depending on this one
-        ServiceFactory.instance.formulaireService.resetAllDependingFormsResults(this);
+        ServiceFactory.instance.formulaireService.resetAllDependingFormsResults(this, visited);
     }
 
     public doCompute() {
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index fe7e94009..585e1d9a0 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -356,7 +356,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
      * effacement des résultats, application des dépendances, ...
      */
     public reset() {
-        this.resetResults();
+        this.resetResults([]);
         this.applyDependencies();
 
         // prévenir les composants qu'il faut détecter les changements
@@ -389,7 +389,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         }
     }
 
-    public abstract resetResults();
+    public abstract resetResults(visited: string[]);
     public abstract doCompute();
     public abstract get hasResults(): boolean;
     public abstract get results(): CalculatorResults[];
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index ffc0b9183..809b9d1f7 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -57,7 +57,7 @@ export class NgParameter extends InputField implements Observer {
         let valuePreview: string;
         const i18n = ServiceFactory.instance.i18nService;
         const nDigits = ServiceFactory.instance.applicationSetupService.displayDigits;
-        // console.log("NgParam::preview()", p.symbol, ParamValueMode[p.valueMode], p);
+
         switch (p.valueMode) {
             case ParamValueMode.SINGLE:
                 valuePreview = String(p.getValue().toFixed(nDigits));
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 7a34e00ae..0bbc9aece 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -404,12 +404,13 @@ export class FormulaireService extends Observable {
                 "form": form
             });
 
-            // reset model results
-            form.currentNub.resetResult();
             // reset the result panels of all forms depending on this one
+            // @TODO UI should detect model change instead of doing this manually
             this.resetAllDependingFormsResults(form);
         }
         if (nub) {
+            // reset model results (important, also resets dependent Nubs results in chain)
+            form.currentNub.resetResult();
             Session.getInstance().deleteNub(nub);
         }
     }
@@ -624,11 +625,13 @@ export class FormulaireService extends Observable {
      * Resets the results of all forms depending on the given form "f"
      * @param f
      */
-    public resetAllDependingFormsResults(f: FormulaireDefinition) {
+    public resetAllDependingFormsResults(f: FormulaireDefinition, visited: string[] = []) {
         const dependingNubs = Session.getInstance().getDependingNubs(f.currentNub.uid);
         for (const dn of dependingNubs) {
-            const form = this.getFormulaireFromNubId(dn.uid);
-            form.resetResults();
+            if (! visited.includes(dn.uid)) {
+                const form = this.getFormulaireFromNubId(dn.uid);
+                form.resetResults(visited);
+            }
         }
     }
 }
-- 
GitLab


From 486a60f69f3d7e42d8846e9db6b99fb59e576bb9 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Fri, 22 Mar 2019 15:57:26 +0100
Subject: [PATCH 17/39] =?UTF-8?q?M=C3=A0J=20fichier=20de=20session=20de=20?=
 =?UTF-8?q?test=20e2e?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/session-liens-spaghetti.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/e2e/session-liens-spaghetti.json b/e2e/session-liens-spaghetti.json
index 4cc7304df..bbccd213b 100644
--- a/e2e/session-liens-spaghetti.json
+++ b/e2e/session-liens-spaghetti.json
@@ -245,7 +245,7 @@
                     "symbol": "Z1",
                     "mode": "LINK",
                     "targetNub": "dTB3ZG",
-                    "targetParam": "ZF2"
+                    "targetParam": "ZF1"
                 },
                 {
                     "symbol": "Z2",
-- 
GitLab


From 3abba8f11083d6de65938c5456af60d8ba43ede5 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Fri, 22 Mar 2019 17:12:32 +0100
Subject: [PATCH 18/39] =?UTF-8?q?Nouveau=20service=20de=20notifications=20?=
 =?UTF-8?q?multiples;=20l'utilisateur=20est=20averti=20lorsque=20des=20r?=
 =?UTF-8?q?=C3=A9sultats=20sont=20invalid=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.module.ts                         |  2 +
 .../services/formulaire/formulaire.service.ts | 13 ++++-
 .../notifications/notifications.service.ts    | 49 +++++++++++++++++++
 src/locale/messages.en.json                   |  1 +
 src/locale/messages.fr.json                   |  1 +
 5 files changed, 64 insertions(+), 2 deletions(-)
 create mode 100644 src/app/services/notifications/notifications.service.ts

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index a1f172ab0..da7f30bba 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -44,6 +44,7 @@ import { FormulaireService } from "./services/formulaire/formulaire.service";
 import { I18nService } from "./services/internationalisation/internationalisation.service";
 import { HttpService } from "./services/http/http.service";
 import { ApplicationSetupService } from "./services/app-setup/app-setup.service";
+import { NotificationsService } from "./services/notifications/notifications.service";
 
 import { AppComponent } from "./app.component";
 import { NgParamInputComponent } from "./components/ngparam-input/ngparam-input.component";
@@ -191,6 +192,7 @@ const appRoutes: Routes = [
     FormulaireService,
     HttpService,
     I18nService,
+    NotificationsService,
     {
       provide: ErrorStateMatcher,
       useClass: ImmediateErrorStateMatcher
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 0bbc9aece..5bd1cf92a 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -22,6 +22,7 @@ import { FormulaireParallelStructure } from "../../formulaire/definition/concret
 import { NgParameter } from "../../formulaire/ngparam";
 import { FieldsetContainer } from "../..//formulaire/fieldset-container";
 import { ApplicationSetupService } from "../app-setup/app-setup.service";
+import { NotificationsService } from "../notifications/notifications.service";
 
 @Injectable()
 export class FormulaireService extends Observable {
@@ -36,7 +37,9 @@ export class FormulaireService extends Observable {
     constructor(
         private i18nService: I18nService,
         private appSetupService: ApplicationSetupService,
-        private httpService: HttpService
+        private httpService: HttpService,
+        private intlService: I18nService,
+        private notificationsService: NotificationsService
     ) {
         super();
         this._formulaires = [];
@@ -630,7 +633,13 @@ export class FormulaireService extends Observable {
         for (const dn of dependingNubs) {
             if (! visited.includes(dn.uid)) {
                 const form = this.getFormulaireFromNubId(dn.uid);
-                form.resetResults(visited);
+                if (form.hasResults) {
+                    form.resetResults(visited);
+                    this.notificationsService.notify(
+                        this.intlService.localizeText("INFO_SNACKBAR_RESULTS_INVALIDATED") + " " + form.calculatorName,
+                        2000
+                    );
+                }
             }
         }
     }
diff --git a/src/app/services/notifications/notifications.service.ts b/src/app/services/notifications/notifications.service.ts
new file mode 100644
index 000000000..9caf0d7ff
--- /dev/null
+++ b/src/app/services/notifications/notifications.service.ts
@@ -0,0 +1,49 @@
+import { Injectable } from "@angular/core";
+import { MatSnackBar } from "@angular/material";
+
+/**
+ * Displays a notifications queue as consecutive snackbars
+ */
+@Injectable()
+export class NotificationsService {
+
+    /** FIFO queue for notifications */
+    private notifications: any[];
+
+    private isOpen: boolean;
+
+    public constructor(
+        private snackBar: MatSnackBar
+    ) {
+        this.notifications = [];
+        this.isOpen = false;
+    }
+
+    /** Push a notification and display it as soon as possible */
+    public notify(message: string, duration: number, action: string = "OK") {
+        this.notifications.push({
+            message: message,
+            duration: duration,
+            action: action
+        });
+        this.show();
+    }
+
+    /** Show all messages in the FIFO queue one after another */
+    public show() {
+        if (! this.isOpen) {
+            // process next notification
+            if (this.notifications.length > 0) {
+                const notif = this.notifications.shift();
+                this.isOpen = true;
+                const ref = this.snackBar.open(notif.message, notif.action, {
+                    duration: notif.duration
+                });
+                ref.afterDismissed().subscribe(() => {
+                    this.isOpen = false;
+                    this.show();
+                });
+            }
+        }
+    }
+}
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index d265692b8..859d160ba 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -204,6 +204,7 @@
     "INFO_SETUP_PRECISION_AFFICHAGE": "Display accuracy",
     "INFO_SETUP_PRECISION_CALCUL": "Computation accuracy",
     "INFO_SETUP_TITLE": "Application setup",
+    "INFO_SNACKBAR_RESULTS_INVALIDATED": "Results invalidated for",
     "INFO_SNACKBAR_SETTINGS_SAVED": "Settings saved on this device",
     "INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED": "Default settings restored",
     "INFO_THEME_CREDITS": "Credit",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 5c40ca5d0..350dd11ad 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -204,6 +204,7 @@
     "INFO_SETUP_PRECISION_AFFICHAGE": "Précision d'affichage",
     "INFO_SETUP_PRECISION_CALCUL": "Précision de calcul",
     "INFO_SETUP_TITLE": "Paramètres de l'application",
+    "INFO_SNACKBAR_RESULTS_INVALIDATED": "Résultats invalidés pour",
     "INFO_SNACKBAR_SETTINGS_SAVED": "Paramètres enregistrés sur cet appareil",
     "INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED": "Paramètres par défaut restaurés",
     "INFO_THEME_CREDITS": "Crédit",
-- 
GitLab


From 5f0e2b5c34daa4d19291e70f9d4adee192223dfd Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Fri, 22 Mar 2019 17:24:55 +0100
Subject: [PATCH 19/39] Pas de snackbar lorsqu'on ferme un module

---
 src/app/services/formulaire/formulaire.service.ts | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 5bd1cf92a..78b0e303c 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -409,7 +409,7 @@ export class FormulaireService extends Observable {
 
             // reset the result panels of all forms depending on this one
             // @TODO UI should detect model change instead of doing this manually
-            this.resetAllDependingFormsResults(form);
+            this.resetAllDependingFormsResults(form, [], false);
         }
         if (nub) {
             // reset model results (important, also resets dependent Nubs results in chain)
@@ -628,17 +628,19 @@ export class FormulaireService extends Observable {
      * Resets the results of all forms depending on the given form "f"
      * @param f
      */
-    public resetAllDependingFormsResults(f: FormulaireDefinition, visited: string[] = []) {
+    public resetAllDependingFormsResults(f: FormulaireDefinition, visited: string[] = [], notify: boolean = true) {
         const dependingNubs = Session.getInstance().getDependingNubs(f.currentNub.uid);
         for (const dn of dependingNubs) {
             if (! visited.includes(dn.uid)) {
                 const form = this.getFormulaireFromNubId(dn.uid);
                 if (form.hasResults) {
                     form.resetResults(visited);
-                    this.notificationsService.notify(
-                        this.intlService.localizeText("INFO_SNACKBAR_RESULTS_INVALIDATED") + " " + form.calculatorName,
-                        2000
-                    );
+                    if (notify) {
+                        this.notificationsService.notify(
+                            this.intlService.localizeText("INFO_SNACKBAR_RESULTS_INVALIDATED") + " " + form.calculatorName,
+                            2000
+                        );
+                    }
                 }
             }
         }
-- 
GitLab


From 8d7670751419511eebd354196cfd48ce5bd5a906 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Fri, 22 Mar 2019 17:41:57 +0100
Subject: [PATCH 20/39] =?UTF-8?q?M=C3=A0J=20test=20e2e=20param=C3=A8tres?=
 =?UTF-8?q?=20li=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/load-linked-params.e2e-spec.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts
index 62405f439..e77ed5aea 100644
--- a/e2e/load-linked-params.e2e-spec.ts
+++ b/e2e/load-linked-params.e2e-spec.ts
@@ -63,7 +63,7 @@ describe("ngHyd − load session with multiple linked parameters", () => {
     // check target params values
     const lo_z1 = calcPage.getSelectById("linked_Z1");
     const lo_z1v = await calcPage.getSelectValueText(lo_z1);
-    expect(lo_z1v).toEqual("ZF2 (Macro-rugo., résultat complémentaire)");
+    expect(lo_z1v).toEqual("ZF1 (Macro-rugo.)");
 
     const lo_l = calcPage.getSelectById("linked_L"); // attention ID non unique, voir nghyd#173
     const lo_lv = await calcPage.getSelectValueText(lo_l);
-- 
GitLab


From c0e1d3e78f68a61a5b68fdb293dfcd6286b02673 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 25 Mar 2019 09:43:04 +0100
Subject: [PATCH 21/39] Fix #134

---
 .../formulaire/definition/form-definition.ts  | 20 ---------
 src/app/formulaire/fieldset.ts                | 42 -------------------
 .../services/formulaire/formulaire.service.ts | 16 -------
 3 files changed, 78 deletions(-)

diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 585e1d9a0..e34b286f2 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -476,26 +476,6 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return undefined;
     }
 
-    /**
-      * @param json conf du formulaire
-      * @param uidMap table de correspondance uid dans le fichier de conf <-> uid en mémoire
-      */
-    public updateParamsLinks(json: {}, uidMap: {}[]) {
-        for (const ks in json) {
-            switch (ks) {
-                case "elements":
-                    let n = 0;
-                    for (const e of json[ks]) {
-                        if (Object.keys(e)[0] === "fieldset") {
-                            this.getNthFieldset(n).updateParamsLinks(e["fieldset"], uidMap);
-                            n++;
-                        }
-                    }
-                    break;
-            }
-        }
-    }
-
     //  interface Observer
 
     public update(sender: any, data: any) {
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 9c001b21d..06106cf6f 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -402,46 +402,4 @@ export class FieldSet extends FormulaireElement implements Observer {
             }
         }
     }
-
-    /**
-     * MAJ des liens entre paramètres lors de la désérialisation
-     * @param json conf du fieldset issue du fichier
-     * @param uidMap table de correspondance uid dans le fichier de conf <-> uid en mémoire
-     */
-    public updateParamsLinks(json: {}, uidMap: {}[]) {
-        for (const ks in json) {
-            switch (ks) {
-                case "elements":
-                    for (const e of json[ks]) {
-                        if (Object.keys(e)[0] === "param") {
-                            const prm = e["param"];
-                            if (prm["values"]["mode"] === "LINK") {
-                                // id du formulaire cible dans le fichier
-                                const oldFormUid = +prm["values"]["form_uid"];
-
-                                // correspondance avec l'objet mémoire
-                                let newFormUid: string;
-                                for (const m of uidMap) {
-                                    if (m["type"] === "form" && m["old"] === oldFormUid) {
-                                        newFormUid = m["new"];
-                                        break;
-                                    }
-                                }
-
-                                // formulaire dont le Nub est la cible du lien
-                                const destForm: FormulaireDefinition
-                                    = ServiceFactory.instance.formulaireService.getFormulaireFromId(newFormUid);
-
-                                // paramètre source (celui qui est lié à une valeur)
-                                const src: NgParameter = this.getFormulaireNodeById(prm["id"]) as NgParameter;
-
-                                // création du lien
-                                src.paramDefinition.defineReference(destForm.currentNub, prm["values"]["ref"]);
-                            }
-                            break;
-                        }
-                    }
-            }
-        }
-    }
 }
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 78b0e303c..49533c522 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -542,22 +542,6 @@ export class FormulaireService extends Observable {
         });
     }
 
-    /**
-     * met à jour les liens d'un formulaire
-     * @TODO rewrite
-     * @param json conf du formulaire
-     * @param formInfos métadonnées sur les formulaires chargés
-     * @param form formulaire dont on met à jour les liens
-     * @param uidMap table de correspondance uid dans le fichier de conf <-> uid en mémoire
-     */
-    private updateFormLinks(json: {}, formInfos: any[], form: FormulaireDefinition, uidMap: {}[]) {
-        for (const i of formInfos) {
-            if (i["uid"] === json["uid"] && i["selected"]) {
-                form.updateParamsLinks(json, uidMap);
-            }
-        }
-    }
-
     /**
      * Demande à la Session JalHYd la liste des paramètres/résultats pouvant être liés au
      * paramètre fourni
-- 
GitLab


From 3841aeeacda02f9c83217dab0d5d3e155b060836 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 25 Mar 2019 10:49:21 +0100
Subject: [PATCH 22/39] =?UTF-8?q?Ajout=20test=20e2e=20pour=20calcul=20en?=
 =?UTF-8?q?=20cha=C3=AEne?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/compute-reset-chained-links.e2e-spec.ts | 160 +++++++++
 e2e/load-linked-params.e2e-spec.ts          |   4 +-
 e2e/navbar.po.ts                            |   9 +
 e2e/session-6-calc.test.json                | 343 +++++++++++++++++++-
 e2e/session-cascade-params.json             | 146 +++++++++
 e2e/session-cascade-results.json            | 191 +++++++++++
 e2e/session-optional-params.test.json       |  58 +++-
 src/app/app.component.html                  |   6 +-
 8 files changed, 910 insertions(+), 7 deletions(-)
 create mode 100644 e2e/compute-reset-chained-links.e2e-spec.ts
 create mode 100644 e2e/session-cascade-params.json
 create mode 100644 e2e/session-cascade-results.json

diff --git a/e2e/compute-reset-chained-links.e2e-spec.ts b/e2e/compute-reset-chained-links.e2e-spec.ts
new file mode 100644
index 000000000..17a716766
--- /dev/null
+++ b/e2e/compute-reset-chained-links.e2e-spec.ts
@@ -0,0 +1,160 @@
+import { AppPage } from "./app.po";
+import { CalculatorPage } from "./calculator.po";
+import { Navbar } from "./navbar.po";
+import { SideNav } from "./sidenav.po";
+import { browser } from "protractor";
+
+/**
+ * Load a session containing 3 calculators, having linked parameters
+ * from one to another, triggers computation from the top-most, triggers
+ * results reset from the bottom-most.
+ * Does it once with parameters linked to parameters, once with parameters
+ * linked to results.
+ */
+describe("ngHyd − compute then reset chained results", () => {
+  let startPage: AppPage;
+  let calcPage: CalculatorPage;
+  let navbar: Navbar;
+  let sidenav: SideNav;
+
+  function init() {
+      startPage = new AppPage();
+      calcPage = new CalculatorPage();
+      navbar = new Navbar();
+      sidenav = new SideNav();
+  }
+  beforeEach(init);
+
+  async function doTheJob(filename: string, topMostId: string, bottomMostId: string) {
+    // load session file
+    await startPage.navigateTo();
+    await navbar.clickMenuButton();
+    await browser.sleep(200);
+    await sidenav.clickLoadSessionButton();
+    await browser.sleep(200);
+    await sidenav.loadSessionFile(filename);
+    await browser.sleep(500);
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
+
+    // 1. get top most module
+    await navbar.clickCalculatorTabForUid(topMostId);
+
+    // check that "compute" button is active
+    const calcButton = calcPage.getCalculateButton();
+    const disabledState = await calcButton.getAttribute("disabled");
+    expect(disabledState).not.toBe("disabled");
+    // click "compute" button
+    await calcButton.click();
+
+    // check all 3 modules for results
+    for (let i = 0; i < 3; i++) {
+      await navbar.clickCalculatorTab(i);
+      const hasResults = await calcPage.hasResults();
+      expect(hasResults).toBe(true);
+    }
+
+    // 2. get bottom-most module
+    await navbar.clickCalculatorTabForUid(bottomMostId);
+
+    // modify any input (for ex. "Ks")
+    await calcPage.getInputById("Ks").clear();
+    await calcPage.getInputById("Ks").sendKeys("42");
+
+    // check all 3 modules for absence of results
+    for (let i = 0; i < 3; i++) {
+      await navbar.clickCalculatorTab(i);
+      const hasResults = await calcPage.hasResults();
+      expect(hasResults).toBe(false);
+    }
+  }
+
+  it("when loading session-cascade-params.json, computation should not be chained, but results reset should", async () => {
+    // load session file
+    await startPage.navigateTo();
+    await navbar.clickMenuButton();
+    await browser.sleep(200);
+    await sidenav.clickLoadSessionButton();
+    await browser.sleep(200);
+    await sidenav.loadSessionFile("./session-cascade-params.json");
+    await browser.sleep(500);
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
+
+    // 1. get top most module
+    await navbar.clickCalculatorTabForUid("dWs5bm");
+
+    // check that "compute" button is active
+    const calcButton = calcPage.getCalculateButton();
+    const disabledState = await calcButton.getAttribute("disabled");
+    expect(disabledState).not.toBe("disabled");
+    // click "compute" button
+    await calcButton.click();
+
+    // only top-most module should have results
+    let hasResults = await calcPage.hasResults();
+    // other two should not
+    await navbar.clickCalculatorTabForUid("OGFzOH");
+    hasResults = await calcPage.hasResults();
+    expect(hasResults).toBe(false);
+    await navbar.clickCalculatorTabForUid("NWp1a3");
+    hasResults = await calcPage.hasResults();
+    expect(hasResults).toBe(false);
+
+    // 2. get bottom-most module
+    await navbar.clickCalculatorTabForUid("OGFzOH");
+
+    // modify any input (for ex. "Ks")
+    await calcPage.getInputById("Ks").clear();
+    await calcPage.getInputById("Ks").sendKeys("42");
+
+    // check all 3 modules for absence of results
+    for (let i = 0; i < 3; i++) {
+      await navbar.clickCalculatorTab(i);
+      hasResults = await calcPage.hasResults();
+      expect(hasResults).toBe(false);
+    }
+  });
+
+  it("when loading session-cascade-results.json, computation and results reset should be chained", async () => {
+    // load session file
+    await startPage.navigateTo();
+    await navbar.clickMenuButton();
+    await browser.sleep(200);
+    await sidenav.clickLoadSessionButton();
+    await browser.sleep(200);
+    await sidenav.loadSessionFile("./session-cascade-results.json");
+    await browser.sleep(500);
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
+
+    // 1. get top most module
+    await navbar.clickCalculatorTabForUid("YWFqMD");
+
+    // check that "compute" button is active
+    const calcButton = calcPage.getCalculateButton();
+    const disabledState = await calcButton.getAttribute("disabled");
+    expect(disabledState).not.toBe("disabled");
+    // click "compute" button
+    await calcButton.click();
+
+    // check all 3 modules for results
+    for (let i = 0; i < 3; i++) {
+      await navbar.clickCalculatorTab(i);
+      const hasResults = await calcPage.hasResults();
+      expect(hasResults).toBe(true);
+    }
+
+    // 2. get bottom-most module
+    await navbar.clickCalculatorTabForUid("ZHd0ej");
+
+    // modify any input (for ex. "Ks")
+    await calcPage.getInputById("Ks").clear();
+    await calcPage.getInputById("Ks").sendKeys("42");
+
+    // check all 3 modules for absence of results
+    for (let i = 0; i < 3; i++) {
+      await navbar.clickCalculatorTab(i);
+      const hasResults = await calcPage.hasResults();
+      expect(hasResults).toBe(false);
+    }
+  });
+
+});
diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts
index e77ed5aea..cd0b97a4c 100644
--- a/e2e/load-linked-params.e2e-spec.ts
+++ b/e2e/load-linked-params.e2e-spec.ts
@@ -23,7 +23,7 @@ describe("ngHyd − load session with multiple linked parameters", () => {
   }
   beforeEach(init);
 
-  it("when loading session-liens-spaghetti.json, all links should point to the right target - ", async () => {
+  it("when loading session-liens-spaghetti.json, all links should point to the right target", async () => {
     await startPage.navigateTo();
 
     await navbar.clickMenuButton();
@@ -33,7 +33,7 @@ describe("ngHyd − load session with multiple linked parameters", () => {
     await browser.sleep(200);
 
     await sidenav.loadSessionFile("./session-liens-spaghetti.json");
-    await browser.sleep(200);
+    await browser.sleep(500);
 
     expect(await navbar.getAllCalculatorTabs().count()).toBe(4);
 
diff --git a/e2e/navbar.po.ts b/e2e/navbar.po.ts
index 2112ceecf..621416ac9 100644
--- a/e2e/navbar.po.ts
+++ b/e2e/navbar.po.ts
@@ -5,6 +5,10 @@ export class Navbar {
     return element.all(by.css("#tabs-container > button.calculator-button"));
   }
 
+  getCalculatorTabForUid(uid: string) {
+    return element(by.css("#tabs-container > button.calculator-button.calculator-uid-" + uid));
+  }
+
   getNewCalculatorButton() {
     return element(by.css("#new-calculator"));
   }
@@ -18,6 +22,11 @@ export class Navbar {
     await tabs.get(n).click();
   }
 
+  async clickCalculatorTabForUid(uid: string) {
+    const tab = this.getCalculatorTabForUid(uid);
+    await tab.click();
+  }
+
   async clickRandomCalculatorTab(n: number) {
     const tabs = this.getAllCalculatorTabs();
     const l = await tabs.count();
diff --git a/e2e/session-6-calc.test.json b/e2e/session-6-calc.test.json
index 54df75d63..a24a0012f 100644
--- a/e2e/session-6-calc.test.json
+++ b/e2e/session-6-calc.test.json
@@ -1 +1,342 @@
-{"session":[{"uid":"NHY0cX","props":{"calcType":5,"nodeType":0},"meta":{"title":"PAB : dimensions"},"parameters":[{"symbol":"Pr","mode":"SINGLE","value":0.0001},{"symbol":"L","mode":"SINGLE","value":2},{"symbol":"W","mode":"SINGLE","value":1},{"symbol":"Y","mode":"SINGLE","value":0.5},{"symbol":"V","mode":"CALCUL"}]},{"uid":"YzAwMW","props":{"calcType":11,"nodeType":0},"meta":{"title":"Macro-rugo."},"parameters":[{"symbol":"Pr","mode":"SINGLE","value":0.0001},{"symbol":"ZF1","mode":"SINGLE","value":12.5},{"symbol":"L","mode":"SINGLE","value":6},{"symbol":"B","mode":"SINGLE","value":1},{"symbol":"If","mode":"SINGLE","value":0.05},{"symbol":"Q","mode":"CALCUL"},{"symbol":"Y","mode":"SINGLE","value":0.6},{"symbol":"Ks","mode":"SINGLE","value":0.01},{"symbol":"C","mode":"SINGLE","value":0.05},{"symbol":"PBD","mode":"SINGLE","value":0.5},{"symbol":"PBH","mode":"SINGLE","value":0.8},{"symbol":"Cd0","mode":"SINGLE","value":1.5}]},{"uid":"dGc5MD","props":{"calcType":8,"nodeType":0},"meta":{"title":"Ouvrages"},"structures":[{"uid":"NjZob3","props":{"calcType":7,"nodeType":5,"structureType":1,"loiDebit":1},"parameters":[{"symbol":"ZDV","mode":"SINGLE","value":100},{"symbol":"W","mode":"SINGLE","value":0.5},{"symbol":"L","mode":"SINGLE","value":2},{"symbol":"Cd","mode":"SINGLE","value":0.6}]}],"parameters":[{"symbol":"Pr","mode":"SINGLE","value":0.0001},{"symbol":"Q","mode":"CALCUL"},{"symbol":"Z1","mode":"SINGLE","value":102},{"symbol":"Z2","mode":"SINGLE","value":101.5}]},{"uid":"OGZ4cm","props":{"varCalc":"Hs","calcType":2,"nodeType":2},"meta":{"title":"Sec. param."},"parameters":[{"symbol":"Pr","mode":"SINGLE","value":0.0001},{"symbol":"Ks","mode":"SINGLE","value":40},{"symbol":"Q","mode":"SINGLE","value":1.2},{"symbol":"If","mode":"SINGLE","value":0.001},{"symbol":"YB","mode":"SINGLE","value":1},{"symbol":"Y","mode":"SINGLE","value":0.8},{"symbol":"LargeurBerge","mode":"SINGLE","value":2.5}]},{"uid":"ZTNvMD","props":{"methodeResolution":0,"calcType":4,"nodeType":2},"meta":{"title":"Remous"},"parameters":[{"symbol":"Pr","mode":"SINGLE","value":0.0001},{"symbol":"Yamont","mode":"SINGLE","value":0.15},{"symbol":"Yaval","mode":"SINGLE","value":0.4},{"symbol":"Long","mode":"SINGLE","value":100},{"symbol":"Dx","mode":"SINGLE","value":5},{"symbol":"Ks","mode":"SINGLE","value":40},{"symbol":"Q","mode":"SINGLE","value":1.2},{"symbol":"If","mode":"SINGLE","value":0.001},{"symbol":"YB","mode":"SINGLE","value":1},{"symbol":"Y","mode":"SINGLE","value":0.2863766123093061},{"symbol":"LargeurBerge","mode":"SINGLE","value":2.5}]},{"uid":"eWllan","props":{"calcType":1,"nodeType":0},"meta":{"title":"Lechapt-Calmon"},"parameters":[{"symbol":"Pr","mode":"SINGLE","value":0.0001},{"symbol":"Q","mode":"SINGLE","value":3},{"symbol":"D","mode":"SINGLE","value":1.2},{"symbol":"J","mode":"CALCUL"},{"symbol":"Lg","mode":"SINGLE","value":100},{"symbol":"L","mode":"SINGLE","value":"1.863"},{"symbol":"M","mode":"SINGLE","value":"2"},{"symbol":"N","mode":"SINGLE","value":"5.33"}]}]}
\ No newline at end of file
+{
+    "session": [
+        {
+            "uid": "NHY0cX",
+            "props": {
+                "calcType": 5,
+                "nodeType": 0
+            },
+            "meta": {
+                "title": "PAB : dimensions"
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "L",
+                    "mode": "SINGLE",
+                    "value": 2
+                },
+                {
+                    "symbol": "W",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "V",
+                    "mode": "CALCUL"
+                }
+            ]
+        },
+        {
+            "uid": "YzAwMW",
+            "props": {
+                "calcType": 11,
+                "nodeType": 0
+            },
+            "meta": {
+                "title": "Macro-rugo."
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "ZF1",
+                    "mode": "SINGLE",
+                    "value": 12.5
+                },
+                {
+                    "symbol": "L",
+                    "mode": "SINGLE",
+                    "value": 6
+                },
+                {
+                    "symbol": "B",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.05
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.6
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 0.01
+                },
+                {
+                    "symbol": "C",
+                    "mode": "SINGLE",
+                    "value": 0.05
+                },
+                {
+                    "symbol": "PBD",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "PBH",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "Cd0",
+                    "mode": "SINGLE",
+                    "value": 1.5
+                }
+            ]
+        },
+        {
+            "uid": "dGc5MD",
+            "props": {
+                "calcType": 8,
+                "nodeType": 0
+            },
+            "meta": {
+                "title": "Ouvrages"
+            },
+            "structures": [
+                {
+                    "uid": "NjZob3",
+                    "props": {
+                        "calcType": 7,
+                        "nodeType": 5,
+                        "structureType": 1,
+                        "loiDebit": 1
+                    },
+                    "parameters": [
+                        {
+                            "symbol": "ZDV",
+                            "mode": "SINGLE",
+                            "value": 100
+                        },
+                        {
+                            "symbol": "W",
+                            "mode": "SINGLE",
+                            "value": 0.5
+                        },
+                        {
+                            "symbol": "L",
+                            "mode": "SINGLE",
+                            "value": 2
+                        },
+                        {
+                            "symbol": "Cd",
+                            "mode": "SINGLE",
+                            "value": 0.6
+                        }
+                    ]
+                }
+            ],
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "Z1",
+                    "mode": "SINGLE",
+                    "value": 102
+                },
+                {
+                    "symbol": "Z2",
+                    "mode": "SINGLE",
+                    "value": 101.5
+                }
+            ]
+        },
+        {
+            "uid": "OGZ4cm",
+            "props": {
+                "varCalc": "Hs",
+                "calcType": 2,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "Sec. param."
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 1.2
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "SINGLE",
+                    "value": 2.5
+                }
+            ]
+        },
+        {
+            "uid": "ZTNvMD",
+            "props": {
+                "methodeResolution": 0,
+                "calcType": 4,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "Remous"
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Yamont",
+                    "mode": "SINGLE",
+                    "value": 0.15
+                },
+                {
+                    "symbol": "Yaval",
+                    "mode": "SINGLE",
+                    "value": 0.4
+                },
+                {
+                    "symbol": "Long",
+                    "mode": "SINGLE",
+                    "value": 100
+                },
+                {
+                    "symbol": "Dx",
+                    "mode": "SINGLE",
+                    "value": 5
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 1.2
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.2863766123093061
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "SINGLE",
+                    "value": 2.5
+                }
+            ]
+        },
+        {
+            "uid": "eWllan",
+            "props": {
+                "calcType": 1,
+                "nodeType": 0
+            },
+            "meta": {
+                "title": "Lechapt-Calmon"
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 3
+                },
+                {
+                    "symbol": "D",
+                    "mode": "SINGLE",
+                    "value": 1.2
+                },
+                {
+                    "symbol": "J",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "Lg",
+                    "mode": "SINGLE",
+                    "value": 100
+                },
+                {
+                    "symbol": "L",
+                    "mode": "SINGLE",
+                    "value": "1.863"
+                },
+                {
+                    "symbol": "M",
+                    "mode": "SINGLE",
+                    "value": "2"
+                },
+                {
+                    "symbol": "N",
+                    "mode": "SINGLE",
+                    "value": "5.33"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/e2e/session-cascade-params.json b/e2e/session-cascade-params.json
new file mode 100644
index 000000000..773053d54
--- /dev/null
+++ b/e2e/session-cascade-params.json
@@ -0,0 +1,146 @@
+{
+    "session": [
+        {
+            "uid": "OGFzOH",
+            "props": {
+                "varCalc": "Hs",
+                "calcType": 2,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "Sec. param."
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 1.2
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "SINGLE",
+                    "value": 2.5
+                }
+            ]
+        },
+        {
+            "uid": "NWp1a3",
+            "props": {
+                "calcType": 3,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "R. uniforme"
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "LINK",
+                    "targetNub": "OGFzOH",
+                    "targetParam": "LargeurBerge"
+                }
+            ]
+        },
+        {
+            "uid": "dWs5bm",
+            "props": {
+                "calcType": 3,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "R. uniforme 1"
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "LINK",
+                    "targetNub": "NWp1a3",
+                    "targetParam": "LargeurBerge"
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/e2e/session-cascade-results.json b/e2e/session-cascade-results.json
new file mode 100644
index 000000000..9825f301c
--- /dev/null
+++ b/e2e/session-cascade-results.json
@@ -0,0 +1,191 @@
+{
+    "session": [
+        {
+            "uid": "YWFqMD",
+            "props": {
+                "calcType": 11,
+                "nodeType": 0
+            },
+            "meta": {
+                "title": "Macro-rugo."
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "ZF1",
+                    "mode": "SINGLE",
+                    "value": 12.5
+                },
+                {
+                    "symbol": "L",
+                    "mode": "SINGLE",
+                    "value": 6
+                },
+                {
+                    "symbol": "B",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.05
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "LINK",
+                    "targetNub": "MXB0en",
+                    "targetParam": "Q"
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.6
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 0.01
+                },
+                {
+                    "symbol": "C",
+                    "mode": "SINGLE",
+                    "value": 0.05
+                },
+                {
+                    "symbol": "PBD",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                },
+                {
+                    "symbol": "PBH",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "Cd0",
+                    "mode": "SINGLE",
+                    "value": 1.5
+                }
+            ]
+        },
+        {
+            "uid": "MXB0en",
+            "props": {
+                "calcType": 3,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "R. uniforme"
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "LINK",
+                    "targetNub": "ZHd0ej",
+                    "targetParam": "Yco"
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "SINGLE",
+                    "value": 2.5
+                }
+            ]
+        },
+        {
+            "uid": "ZHd0ej",
+            "props": {
+                "methodeResolution": 0,
+                "calcType": 4,
+                "nodeType": 2
+            },
+            "meta": {
+                "title": "Remous"
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Yamont",
+                    "mode": "SINGLE",
+                    "value": 0.15
+                },
+                {
+                    "symbol": "Yaval",
+                    "mode": "SINGLE",
+                    "value": 0.4
+                },
+                {
+                    "symbol": "Long",
+                    "mode": "SINGLE",
+                    "value": 100
+                },
+                {
+                    "symbol": "Dx",
+                    "mode": "SINGLE",
+                    "value": 5
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 1.2
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.5643749999999994
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "SINGLE",
+                    "value": 2.5
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/e2e/session-optional-params.test.json b/e2e/session-optional-params.test.json
index 804c559cb..6fb0e40e8 100644
--- a/e2e/session-optional-params.test.json
+++ b/e2e/session-optional-params.test.json
@@ -1 +1,57 @@
-{"session":[{"uid":"N2U4OH","props":{"varCalc":"Hs","calcType":2,"nodeType":4},"meta":{"title":"Sec. param."},"parameters":[{"symbol":"Pr","mode":"SINGLE","value":0.0001},{"symbol":"Ks","mode":"SINGLE","value":40},{"symbol":"Q","mode":"SINGLE","value":1.2},{"symbol":"If","mode":"SINGLE","value":0.001},{"symbol":"YB","mode":"SINGLE","value":1},{"symbol":"Y","mode":"SINGLE","value":0.8},{"symbol":"LargeurBerge","mode":"SINGLE","value":4},{"symbol":"k","mode":"SINGLE","value":0.5}]}]}
\ No newline at end of file
+{
+    "session": [
+        {
+            "uid": "N2U4OH",
+            "props": {
+                "varCalc": "Hs",
+                "calcType": 2,
+                "nodeType": 4
+            },
+            "meta": {
+                "title": "Sec. param."
+            },
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Ks",
+                    "mode": "SINGLE",
+                    "value": 40
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "SINGLE",
+                    "value": 1.2
+                },
+                {
+                    "symbol": "If",
+                    "mode": "SINGLE",
+                    "value": 0.001
+                },
+                {
+                    "symbol": "YB",
+                    "mode": "SINGLE",
+                    "value": 1
+                },
+                {
+                    "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "SINGLE",
+                    "value": 4
+                },
+                {
+                    "symbol": "k",
+                    "mode": "SINGLE",
+                    "value": 0.5
+                }
+            ]
+        }
+    ]
+}
\ No newline at end of file
diff --git a/src/app/app.component.html b/src/app/app.component.html
index f2863b8e4..fc3d49453 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -40,9 +40,9 @@
 
     <!-- calculators list as a tabs bar-->
     <div id="tabs-container" [hidden]="! tabsFitInNavbar">
-      <button mat-raised-button color="primary" *ngFor="let c of calculators" class="calculator-button" [title]="c.title"
-        [routerLink]="['/calculator/',c.uid]" [color]="c.active ? '' : 'primary'" [class.active]="c.active"
-        (click)="setActiveCalc(c.uid)">
+      <button mat-raised-button color="primary" *ngFor="let c of calculators" class="" [title]="c.title"
+        [routerLink]="['/calculator/',c.uid]" [color]="c.active ? '' : 'primary'" (click)="setActiveCalc(c.uid)"
+        [ngClass]="['calculator-button', 'calculator-uid-' + c.uid, c.active ? 'active' : '' ]">
 
         <span class="calc-name">
           {{ c.title }}
-- 
GitLab


From 66513ecae431b119bee582a8f3fb54905a884067 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 25 Mar 2019 11:09:43 +0100
Subject: [PATCH 23/39] =?UTF-8?q?Am=C3=A9lioration=20de=20l'invalidation?=
 =?UTF-8?q?=20des=20r=C3=A9sultats=20en=20cascade?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/compute-reset-chained-links.e2e-spec.ts   |  4 +-
 e2e/session-cascade-results.json              | 46 ++++++-------------
 .../services/formulaire/formulaire.service.ts |  6 ++-
 3 files changed, 19 insertions(+), 37 deletions(-)

diff --git a/e2e/compute-reset-chained-links.e2e-spec.ts b/e2e/compute-reset-chained-links.e2e-spec.ts
index 17a716766..44fb7022b 100644
--- a/e2e/compute-reset-chained-links.e2e-spec.ts
+++ b/e2e/compute-reset-chained-links.e2e-spec.ts
@@ -126,7 +126,7 @@ describe("ngHyd − compute then reset chained results", () => {
     expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
 
     // 1. get top most module
-    await navbar.clickCalculatorTabForUid("YWFqMD");
+    await navbar.clickCalculatorTabForUid("YjJua2");
 
     // check that "compute" button is active
     const calcButton = calcPage.getCalculateButton();
@@ -143,7 +143,7 @@ describe("ngHyd − compute then reset chained results", () => {
     }
 
     // 2. get bottom-most module
-    await navbar.clickCalculatorTabForUid("ZHd0ej");
+    await navbar.clickCalculatorTabForUid("OGd2em");
 
     // modify any input (for ex. "Ks")
     await calcPage.getInputById("Ks").clear();
diff --git a/e2e/session-cascade-results.json b/e2e/session-cascade-results.json
index 9825f301c..e0284cc66 100644
--- a/e2e/session-cascade-results.json
+++ b/e2e/session-cascade-results.json
@@ -1,7 +1,7 @@
 {
     "session": [
         {
-            "uid": "YWFqMD",
+            "uid": "YjJua2",
             "props": {
                 "calcType": 11,
                 "nodeType": 0
@@ -37,7 +37,7 @@
                 {
                     "symbol": "Q",
                     "mode": "LINK",
-                    "targetNub": "MXB0en",
+                    "targetNub": "eW1hY3",
                     "targetParam": "Q"
                 },
                 {
@@ -73,7 +73,7 @@
             ]
         },
         {
-            "uid": "MXB0en",
+            "uid": "eW1hY3",
             "props": {
                 "calcType": 3,
                 "nodeType": 2
@@ -103,14 +103,14 @@
                 },
                 {
                     "symbol": "YB",
-                    "mode": "LINK",
-                    "targetNub": "ZHd0ej",
-                    "targetParam": "Yco"
+                    "mode": "SINGLE",
+                    "value": 1
                 },
                 {
                     "symbol": "Y",
-                    "mode": "SINGLE",
-                    "value": 0.8
+                    "mode": "LINK",
+                    "targetNub": "OGd2em",
+                    "targetParam": "Yco"
                 },
                 {
                     "symbol": "LargeurBerge",
@@ -120,14 +120,14 @@
             ]
         },
         {
-            "uid": "ZHd0ej",
+            "uid": "OGd2em",
             "props": {
-                "methodeResolution": 0,
-                "calcType": 4,
+                "varCalc": "Hs",
+                "calcType": 2,
                 "nodeType": 2
             },
             "meta": {
-                "title": "Remous"
+                "title": "Sec. param."
             },
             "parameters": [
                 {
@@ -135,26 +135,6 @@
                     "mode": "SINGLE",
                     "value": 0.0001
                 },
-                {
-                    "symbol": "Yamont",
-                    "mode": "SINGLE",
-                    "value": 0.15
-                },
-                {
-                    "symbol": "Yaval",
-                    "mode": "SINGLE",
-                    "value": 0.4
-                },
-                {
-                    "symbol": "Long",
-                    "mode": "SINGLE",
-                    "value": 100
-                },
-                {
-                    "symbol": "Dx",
-                    "mode": "SINGLE",
-                    "value": 5
-                },
                 {
                     "symbol": "Ks",
                     "mode": "SINGLE",
@@ -178,7 +158,7 @@
                 {
                     "symbol": "Y",
                     "mode": "SINGLE",
-                    "value": 0.5643749999999994
+                    "value": 0.8
                 },
                 {
                     "symbol": "LargeurBerge",
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 49533c522..fe3e0bfe6 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -617,8 +617,10 @@ export class FormulaireService extends Observable {
         for (const dn of dependingNubs) {
             if (! visited.includes(dn.uid)) {
                 const form = this.getFormulaireFromNubId(dn.uid);
-                if (form.hasResults) {
-                    form.resetResults(visited);
+                const hadResults = form.hasResults;
+                // form might not have a result, but still have another form depending on it !
+                form.resetResults(visited);
+                if (hadResults) {
                     if (notify) {
                         this.notificationsService.notify(
                             this.intlService.localizeText("INFO_SNACKBAR_RESULTS_INVALIDATED") + " " + form.calculatorName,
-- 
GitLab


From a8bef84f531f5fe746a66265a6010f6d3fa41f96 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 25 Mar 2019 16:14:15 +0100
Subject: [PATCH 24/39] =?UTF-8?q?Adaptation=20=C3=A0=20la=20nouvelle=20rep?=
 =?UTF-8?q?r=C3=A9sentation=20des=20variables=20de=20Structure=20=C3=A0=20?=
 =?UTF-8?q?calculer;=20mise=20=C3=A0=20jour=20des=20tests?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/load-linked-params.e2e-spec.ts            | 22 ++++--
 e2e/session-liens-spaghetti.json              | 77 +++++++++++++++++--
 .../form-compute-parallel-structures.ts       | 18 +++--
 .../formulaire/definition/form-definition.ts  |  2 +-
 .../services/formulaire/formulaire.service.ts | 20 ++---
 5 files changed, 109 insertions(+), 30 deletions(-)

diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts
index cd0b97a4c..56f84364a 100644
--- a/e2e/load-linked-params.e2e-spec.ts
+++ b/e2e/load-linked-params.e2e-spec.ts
@@ -35,7 +35,7 @@ describe("ngHyd − load session with multiple linked parameters", () => {
     await sidenav.loadSessionFile("./session-liens-spaghetti.json");
     await browser.sleep(500);
 
-    expect(await navbar.getAllCalculatorTabs().count()).toBe(4);
+    expect(await navbar.getAllCalculatorTabs().count()).toBe(5);
 
     // 1. check Section paramétrée
     await navbar.clickCalculatorTab(0);
@@ -51,11 +51,11 @@ describe("ngHyd − load session with multiple linked parameters", () => {
     // check target params values
     const mr_b = calcPage.getSelectById("linked_B");
     const mr_bv = await calcPage.getSelectValueText(mr_b);
-    expect(mr_bv).toEqual("L (résultat de Ouvrages, ouvrage 1)");
+    expect(mr_bv).toEqual("L (résultat de Ouvrages)");
 
     const mr_q = calcPage.getSelectById("linked_Q");
     const mr_qv = await calcPage.getSelectValueText(mr_q);
-    expect(mr_qv).toEqual("Q (résultat de R. uniforme)");
+    expect(mr_qv).toEqual("Q (R. uniforme)");
 
     // 3. check Lois d'ouvrages
     await navbar.clickCalculatorTab(2);
@@ -67,7 +67,7 @@ describe("ngHyd − load session with multiple linked parameters", () => {
 
     const lo_l = calcPage.getSelectById("linked_L"); // attention ID non unique, voir nghyd#173
     const lo_lv = await calcPage.getSelectValueText(lo_l);
-    expect(lo_lv).toEqual("L (résultat de Ouvrages, ouvrage 1)");
+    expect(lo_lv).toEqual("L (résultat de Ouvrages)");
 
     const lo_w = calcPage.getSelectById("linked_W"); // attention ID non unique, voir nghyd#173
     const lo_wv = await calcPage.getSelectValueText(lo_w);
@@ -81,9 +81,17 @@ describe("ngHyd − load session with multiple linked parameters", () => {
     await navbar.clickCalculatorTab(3);
 
     // check target params values
-    const lo_y = calcPage.getSelectById("linked_Y");
-    const lo_yv = await calcPage.getSelectValueText(lo_y);
-    expect(lo_yv).toEqual("Yt (Sec. param., résultat complémentaire)");
+    const lo_q = calcPage.getSelectById("linked_Q");
+    const lo_qv = await calcPage.getSelectValueText(lo_q);
+    expect(lo_qv).toEqual("CvQT (Déver. dénoyés, résultat complémentaire)");
+
+    // 5. check Déver. dénoyés
+    await navbar.clickCalculatorTab(4);
+
+    // check target params values
+    const lo_br = calcPage.getSelectById("linked_BR");
+    const lo_brv = await calcPage.getSelectValueText(lo_br);
+    expect(lo_brv).toEqual("LargeurBerge (Sec. param.)");
   });
 
 });
diff --git a/e2e/session-liens-spaghetti.json b/e2e/session-liens-spaghetti.json
index bbccd213b..87b17d2a1 100644
--- a/e2e/session-liens-spaghetti.json
+++ b/e2e/session-liens-spaghetti.json
@@ -77,7 +77,7 @@
                 {
                     "symbol": "B",
                     "mode": "LINK",
-                    "targetNub": "cjdyYW",
+                    "targetNub": "ZW9icn",
                     "targetParam": "L"
                 },
                 {
@@ -186,7 +186,7 @@
                         {
                             "symbol": "L",
                             "mode": "LINK",
-                            "targetNub": "cjdyYW",
+                            "targetNub": "ZW9icn",
                             "targetParam": "L"
                         },
                         {
@@ -276,7 +276,9 @@
                 },
                 {
                     "symbol": "Q",
-                    "mode": "CALCUL"
+                    "mode": "LINK",
+                    "targetNub": "NGlpMn",
+                    "targetParam": "CvQT"
                 },
                 {
                     "symbol": "If",
@@ -292,14 +294,77 @@
                 },
                 {
                     "symbol": "Y",
+                    "mode": "SINGLE",
+                    "value": 0.8
+                },
+                {
+                    "symbol": "LargeurBerge",
+                    "mode": "CALCUL"
+                }
+            ]
+        },
+        {
+            "uid": "NGlpMn",
+            "props": {
+                "calcType": 9,
+                "nodeType": 0
+            },
+            "meta": {
+                "title": "Déver. dénoyés"
+            },
+            "structures": [
+                {
+                    "uid": "M29rcW",
+                    "props": {
+                        "calcType": 7,
+                        "nodeType": 5,
+                        "structureType": 0,
+                        "loiDebit": 7
+                    },
+                    "parameters": [
+                        {
+                            "symbol": "ZDV",
+                            "mode": "SINGLE",
+                            "value": 100
+                        },
+                        {
+                            "symbol": "L",
+                            "mode": "SINGLE",
+                            "value": 2
+                        },
+                        {
+                            "symbol": "Cd",
+                            "mode": "SINGLE",
+                            "value": 0.4
+                        }
+                    ]
+                }
+            ],
+            "parameters": [
+                {
+                    "symbol": "Pr",
+                    "mode": "SINGLE",
+                    "value": 0.0001
+                },
+                {
+                    "symbol": "Q",
+                    "mode": "CALCUL"
+                },
+                {
+                    "symbol": "Z1",
+                    "mode": "SINGLE",
+                    "value": 102
+                },
+                {
+                    "symbol": "BR",
                     "mode": "LINK",
                     "targetNub": "OW9rd3",
-                    "targetParam": "Yt"
+                    "targetParam": "LargeurBerge"
                 },
                 {
-                    "symbol": "LargeurBerge",
+                    "symbol": "ZR",
                     "mode": "SINGLE",
-                    "value": 2.5
+                    "value": 99
                 }
             ]
         }
diff --git a/src/app/formulaire/definition/form-compute-parallel-structures.ts b/src/app/formulaire/definition/form-compute-parallel-structures.ts
index 9daa4cbd6..580a52f08 100644
--- a/src/app/formulaire/definition/form-compute-parallel-structures.ts
+++ b/src/app/formulaire/definition/form-compute-parallel-structures.ts
@@ -1,4 +1,4 @@
-import { ComputeNode, ParallelStructure } from "jalhyd";
+import { ComputeNode, ParallelStructure, Structure } from "jalhyd";
 
 import { FormComputeFixedVar } from "./form-compute-fixedvar";
 import { FormResultFixedVar } from "./form-result-fixedvar";
@@ -38,15 +38,19 @@ export class FormComputeParallelStructures extends FormComputeFixedVar {
     }
 
     /**
-     * construit un identifiant de type "n.X" avec "n" l'index de l'ouvrage auquel appartient le paramètre et "X" son symbole
+     * construit un identifiant de type { uid: "abcdef", symbol: "X" }
+     * avec "abcdef" l'index de l'ouvrage et "X" son paramètre
      */
-    protected getParameterRefid(p: NgParameter) {
-        const [fsc, fs, i] = this.structureParents(p);
-        if (i === -1) {
+    protected getParameterRefid(p: NgParameter): any {
+        const nub = p.paramDefinition.parentComputeNode;
+        if (nub instanceof Structure) {
+            return {
+                uid: nub.uid,
+                symbol: p.symbol
+            };
+        } else {
             return super.getParameterRefid(p);
         }
-
-        return `${i}.${p.symbol}`;
     }
 
     protected setParameterValue(node: ComputeNode, p: NgParameter, val: number) {
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index e34b286f2..e1d9f5433 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -162,7 +162,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
      * @param uid id à rechercher
      */
     public hasNubId(uid: string): boolean {
-        return this._currentNub.uid === uid;
+        return (this._currentNub && this._currentNub.uid === uid);
     }
 
     public moveFieldsetUp(fs: FieldSet) {
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index fe3e0bfe6..c9d21d2d3 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -617,15 +617,17 @@ export class FormulaireService extends Observable {
         for (const dn of dependingNubs) {
             if (! visited.includes(dn.uid)) {
                 const form = this.getFormulaireFromNubId(dn.uid);
-                const hadResults = form.hasResults;
-                // form might not have a result, but still have another form depending on it !
-                form.resetResults(visited);
-                if (hadResults) {
-                    if (notify) {
-                        this.notificationsService.notify(
-                            this.intlService.localizeText("INFO_SNACKBAR_RESULTS_INVALIDATED") + " " + form.calculatorName,
-                            2000
-                        );
+                if (form) {
+                    const hadResults = form.hasResults;
+                    // form might not have a result, but still have another form depending on it !
+                    form.resetResults(visited);
+                    if (hadResults) {
+                        if (notify) {
+                            this.notificationsService.notify(
+                                this.intlService.localizeText("INFO_SNACKBAR_RESULTS_INVALIDATED") + " " + form.calculatorName,
+                                2000
+                            );
+                        }
                     }
                 }
             }
-- 
GitLab


From 6deadcb5befedf3cbd031359fddc926206d8ddec Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 25 Mar 2019 16:34:02 +0100
Subject: [PATCH 25/39] =?UTF-8?q?M=C3=A0J=20tests=20e2e?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/calculate-all-params.e2e-spec.ts        |  2 +-
 e2e/compute-reset-chained-links.e2e-spec.ts | 57 +++---------------
 e2e/session-cascade-results.json            | 64 +++++++++------------
 3 files changed, 34 insertions(+), 89 deletions(-)

diff --git a/e2e/calculate-all-params.e2e-spec.ts b/e2e/calculate-all-params.e2e-spec.ts
index eebcfa7e8..eaff7587d 100644
--- a/e2e/calculate-all-params.e2e-spec.ts
+++ b/e2e/calculate-all-params.e2e-spec.ts
@@ -43,7 +43,7 @@ describe("ngHyd − calculate all parameters of all calculators", () => {
             // check that "compute" button is active
             const calcButton = calcPage.getCalculateButton();
             const disabledState = await calcButton.getAttribute("disabled");
-            expect(disabledState).not.toBe("disabled");
+            expect(disabledState).not.toBe("true");
             // click "compute" button
             await calcButton.click();
             // check that result is not empty
diff --git a/e2e/compute-reset-chained-links.e2e-spec.ts b/e2e/compute-reset-chained-links.e2e-spec.ts
index 44fb7022b..6399c764c 100644
--- a/e2e/compute-reset-chained-links.e2e-spec.ts
+++ b/e2e/compute-reset-chained-links.e2e-spec.ts
@@ -25,49 +25,6 @@ describe("ngHyd − compute then reset chained results", () => {
   }
   beforeEach(init);
 
-  async function doTheJob(filename: string, topMostId: string, bottomMostId: string) {
-    // load session file
-    await startPage.navigateTo();
-    await navbar.clickMenuButton();
-    await browser.sleep(200);
-    await sidenav.clickLoadSessionButton();
-    await browser.sleep(200);
-    await sidenav.loadSessionFile(filename);
-    await browser.sleep(500);
-    expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
-
-    // 1. get top most module
-    await navbar.clickCalculatorTabForUid(topMostId);
-
-    // check that "compute" button is active
-    const calcButton = calcPage.getCalculateButton();
-    const disabledState = await calcButton.getAttribute("disabled");
-    expect(disabledState).not.toBe("disabled");
-    // click "compute" button
-    await calcButton.click();
-
-    // check all 3 modules for results
-    for (let i = 0; i < 3; i++) {
-      await navbar.clickCalculatorTab(i);
-      const hasResults = await calcPage.hasResults();
-      expect(hasResults).toBe(true);
-    }
-
-    // 2. get bottom-most module
-    await navbar.clickCalculatorTabForUid(bottomMostId);
-
-    // modify any input (for ex. "Ks")
-    await calcPage.getInputById("Ks").clear();
-    await calcPage.getInputById("Ks").sendKeys("42");
-
-    // check all 3 modules for absence of results
-    for (let i = 0; i < 3; i++) {
-      await navbar.clickCalculatorTab(i);
-      const hasResults = await calcPage.hasResults();
-      expect(hasResults).toBe(false);
-    }
-  }
-
   it("when loading session-cascade-params.json, computation should not be chained, but results reset should", async () => {
     // load session file
     await startPage.navigateTo();
@@ -85,7 +42,7 @@ describe("ngHyd − compute then reset chained results", () => {
     // check that "compute" button is active
     const calcButton = calcPage.getCalculateButton();
     const disabledState = await calcButton.getAttribute("disabled");
-    expect(disabledState).not.toBe("disabled");
+    expect(disabledState).not.toBe("true");
     // click "compute" button
     await calcButton.click();
 
@@ -125,13 +82,13 @@ describe("ngHyd − compute then reset chained results", () => {
     await browser.sleep(500);
     expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
 
-    // 1. get top most module
-    await navbar.clickCalculatorTabForUid("YjJua2");
+    // 1. get top most module (PAB Dimensions)
+    await navbar.clickCalculatorTabForUid("bGZqcz");
 
     // check that "compute" button is active
     const calcButton = calcPage.getCalculateButton();
-    const disabledState = await calcButton.getAttribute("disabled");
-    expect(disabledState).not.toBe("disabled");
+    const disabledState = await calcButton.getAttribute("true");
+    expect(disabledState).not.toBe("true");
     // click "compute" button
     await calcButton.click();
 
@@ -142,8 +99,8 @@ describe("ngHyd − compute then reset chained results", () => {
       expect(hasResults).toBe(true);
     }
 
-    // 2. get bottom-most module
-    await navbar.clickCalculatorTabForUid("OGd2em");
+    // 2. get bottom-most module (Macro-rugo)
+    await navbar.clickCalculatorTabForUid("dnRiY2");
 
     // modify any input (for ex. "Ks")
     await calcPage.getInputById("Ks").clear();
diff --git a/e2e/session-cascade-results.json b/e2e/session-cascade-results.json
index e0284cc66..3c1441850 100644
--- a/e2e/session-cascade-results.json
+++ b/e2e/session-cascade-results.json
@@ -1,7 +1,7 @@
 {
     "session": [
         {
-            "uid": "YjJua2",
+            "uid": "dnRiY2",
             "props": {
                 "calcType": 11,
                 "nodeType": 0
@@ -27,18 +27,17 @@
                 },
                 {
                     "symbol": "B",
-                    "mode": "CALCUL"
+                    "mode": "SINGLE",
+                    "value": 1
                 },
                 {
                     "symbol": "If",
-                    "mode": "SINGLE",
-                    "value": 0.05
+                    "mode": "CALCUL"
                 },
                 {
                     "symbol": "Q",
-                    "mode": "LINK",
-                    "targetNub": "eW1hY3",
-                    "targetParam": "Q"
+                    "mode": "SINGLE",
+                    "value": 1.57
                 },
                 {
                     "symbol": "Y",
@@ -73,7 +72,7 @@
             ]
         },
         {
-            "uid": "eW1hY3",
+            "uid": "YjZyND",
             "props": {
                 "calcType": 3,
                 "nodeType": 2
@@ -94,7 +93,9 @@
                 },
                 {
                     "symbol": "Q",
-                    "mode": "CALCUL"
+                    "mode": "LINK",
+                    "targetNub": "dnRiY2",
+                    "targetParam": "Q_GuideTech"
                 },
                 {
                     "symbol": "If",
@@ -108,26 +109,23 @@
                 },
                 {
                     "symbol": "Y",
-                    "mode": "LINK",
-                    "targetNub": "OGd2em",
-                    "targetParam": "Yco"
+                    "mode": "SINGLE",
+                    "value": 0.8
                 },
                 {
                     "symbol": "LargeurBerge",
-                    "mode": "SINGLE",
-                    "value": 2.5
+                    "mode": "CALCUL"
                 }
             ]
         },
         {
-            "uid": "OGd2em",
+            "uid": "bGZqcz",
             "props": {
-                "varCalc": "Hs",
-                "calcType": 2,
-                "nodeType": 2
+                "calcType": 5,
+                "nodeType": 0
             },
             "meta": {
-                "title": "Sec. param."
+                "title": "PAB : dimensions"
             },
             "parameters": [
                 {
@@ -136,34 +134,24 @@
                     "value": 0.0001
                 },
                 {
-                    "symbol": "Ks",
-                    "mode": "SINGLE",
-                    "value": 40
-                },
-                {
-                    "symbol": "Q",
-                    "mode": "SINGLE",
-                    "value": 1.2
-                },
-                {
-                    "symbol": "If",
+                    "symbol": "L",
                     "mode": "SINGLE",
-                    "value": 0.001
+                    "value": 2
                 },
                 {
-                    "symbol": "YB",
-                    "mode": "SINGLE",
-                    "value": 1
+                    "symbol": "W",
+                    "mode": "LINK",
+                    "targetNub": "YjZyND",
+                    "targetParam": "LargeurBerge"
                 },
                 {
                     "symbol": "Y",
                     "mode": "SINGLE",
-                    "value": 0.8
+                    "value": 0.5
                 },
                 {
-                    "symbol": "LargeurBerge",
-                    "mode": "SINGLE",
-                    "value": 2.5
+                    "symbol": "V",
+                    "mode": "CALCUL"
                 }
             ]
         }
-- 
GitLab


From fe2b90121bf946ea51814d2f7aadcc9059b8af6f Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 25 Mar 2019 16:59:48 +0100
Subject: [PATCH 26/39] =?UTF-8?q?Suppression=20de=20code=20inutilis=C3=A9?=
 =?UTF-8?q?=20:=20champ=20checkbox?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/app.module.ts                         |  2 --
 .../check-field-line.component.html           |  6 ----
 .../check-field-line.component.ts             | 20 -------------
 .../field-set/field-set.component.html        |  2 --
 .../field-set/field-set.component.ts          |  8 ------
 src/app/formulaire/check-field.ts             | 28 -------------------
 src/app/formulaire/fieldset.ts                | 10 -------
 .../services/formulaire/formulaire.service.ts |  9 ------
 8 files changed, 85 deletions(-)
 delete mode 100644 src/app/components/check-field-line/check-field-line.component.html
 delete mode 100644 src/app/components/check-field-line/check-field-line.component.ts
 delete mode 100644 src/app/formulaire/check-field.ts

diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index da7f30bba..059c34d50 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -54,7 +54,6 @@ import { ParamComputedComponent } from "./components/param-computed/param-comput
 import { ParamFieldLineComponent } from "./components/param-field-line/param-field-line.component";
 import { ParamValuesComponent } from "./components/param-values/param-values.component";
 import { SelectFieldLineComponent } from "./components/select-field-line/select-field-line.component";
-import { CheckFieldLineComponent } from "./components/check-field-line/check-field-line.component";
 import { CalculatorResultsComponent } from "./components/calculator-results/calculator-results.component";
 import { FixedVarResultsComponent } from "./components/fixedvar-results/fixedvar-results.component";
 import { SectionResultsComponent } from "./components/section-results/section-results.component";
@@ -143,7 +142,6 @@ const appRoutes: Routes = [
     CalculatorListComponent,
     CalculatorNameComponent,
     CalculatorResultsComponent,
-    CheckFieldLineComponent,
     DialogConfirmCloseCalcComponent,
     DialogConfirmEmptySessionComponent,
     DialogEditParamComputedComponent,
diff --git a/src/app/components/check-field-line/check-field-line.component.html b/src/app/components/check-field-line/check-field-line.component.html
deleted file mode 100644
index f00f6fca5..000000000
--- a/src/app/components/check-field-line/check-field-line.component.html
+++ /dev/null
@@ -1,6 +0,0 @@
-<tr>
-    <td align="right">{{ check.label }}</td>
-    <td colspan="3">
-        <input type="checkbox" [(ngModel)]=currentValue (ngModelChange)="onChange($event)">
-    </td>
-</tr>
\ No newline at end of file
diff --git a/src/app/components/check-field-line/check-field-line.component.ts b/src/app/components/check-field-line/check-field-line.component.ts
deleted file mode 100644
index ae159f511..000000000
--- a/src/app/components/check-field-line/check-field-line.component.ts
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Component, Input, Output, EventEmitter } from "@angular/core";
-
-import { CheckField } from "../../formulaire/check-field";
-
-@Component({
-    selector: "check-field-line",
-    templateUrl: "./check-field-line.component.html",
-})
-export class CheckFieldLineComponent {
-    @Input()
-    public check: CheckField;
-
-    public currentValue: boolean;
-
-    constructor() { }
-
-    public onChange(event: any) {
-        this.check.setValue(event);
-    }
-}
diff --git a/src/app/components/field-set/field-set.component.html b/src/app/components/field-set/field-set.component.html
index e048b3915..444001156 100644
--- a/src/app/components/field-set/field-set.component.html
+++ b/src/app/components/field-set/field-set.component.html
@@ -25,7 +25,5 @@
 
         <select-field-line *ngIf="isSelectField(p)" [_select]=p>
         </select-field-line>
-
-        <check-field-line *ngIf="isCheckField(p)" [check]=p></check-field-line>
     </ng-template>
 </mat-card-content>
diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts
index 170ba7cda..1541cba64 100644
--- a/src/app/components/field-set/field-set.component.ts
+++ b/src/app/components/field-set/field-set.component.ts
@@ -6,7 +6,6 @@ import { ParamFieldLineComponent } from "../param-field-line/param-field-line.co
 import { Field } from "../../formulaire/field";
 import { InputField } from "../../formulaire/input-field";
 import { SelectField } from "../../formulaire/select-field";
-import { CheckField } from "../../formulaire/check-field";
 
 @Component({
     selector: "field-set",
@@ -172,13 +171,6 @@ export class FieldSetComponent implements DoCheck {
         return f instanceof SelectField && f.isDisplayed;
     }
 
-    /**
-     * détermine si un Field est du type CheckField
-     */
-    private isCheckField(f: Field): boolean {
-        return f instanceof CheckField && f.isDisplayed;
-    }
-
     /*
      * gestion des événements clic sur les radios :
      * réception d'un message du composant enfant (param-field)
diff --git a/src/app/formulaire/check-field.ts b/src/app/formulaire/check-field.ts
deleted file mode 100644
index 073816613..000000000
--- a/src/app/formulaire/check-field.ts
+++ /dev/null
@@ -1,28 +0,0 @@
-import { Field } from "./field";
-import { FormulaireNode } from "./formulaire-node";
-
-export class CheckField extends Field {
-    private _value: boolean;
-
-    constructor(parent: FormulaireNode) {
-        super(parent);
-        this._value = false;
-    }
-
-    public getValue(): boolean {
-        return this._value;
-    }
-
-    public setValue(val: boolean) {
-        this._value = val;
-    }
-
-    public get isValid(): boolean {
-        return true;
-    }
-
-    public parseConfig(json: {}, data?: {}) {
-        this._confId = json["id"];
-        this.setValue(json["value"] === "true");
-    }
-}
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 06106cf6f..14e44ce41 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -2,7 +2,6 @@ import { CalculatorType, ComputeNodeType, ParamDefinition, LoiDebit, StructureTy
 
 import { FormulaireElement } from "./formulaire-element";
 import { Field } from "./field";
-import { CheckField } from "./check-field";
 import { SelectField } from "./select-field";
 import { NgParameter, ParamRadioConfig } from "./ngparam";
 import { ServiceFactory } from "../services/service-factory";
@@ -87,12 +86,6 @@ export class FieldSet extends FormulaireElement implements Observer {
         return res;
     }
 
-    private parse_check(json: {}): CheckField {
-        const res: CheckField = new CheckField(this);
-        res.parseConfig(json);
-        return res;
-    }
-
     private parse_select(json: {}): SelectField {
         const res: SelectField = new SelectField(this);
         res.parseConfig(json);
@@ -175,9 +168,6 @@ export class FieldSet extends FormulaireElement implements Observer {
             } else if (field["type"] === "select") {
                 const param = this.parse_select(field);
                 this.addField(param);
-            } else if (field["type"] === "check") {
-                const param = this.parse_check(field);
-                this.addField(param);
             }
         }
     }
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index c9d21d2d3..367b8d5b7 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -11,7 +11,6 @@ import { FormulaireDefinition } from "../../formulaire/definition/form-definitio
 import { FormulaireElement } from "../../formulaire/formulaire-element";
 import { InputField } from "../../formulaire/input-field";
 import { SelectField } from "../../formulaire/select-field";
-import { CheckField } from "../../formulaire/check-field";
 import { StringMap } from "../../stringmap";
 import { FormulaireBase } from "../../formulaire/definition/concrete/form-base";
 import { FormulaireLechaptCalmon } from "../../formulaire/definition/concrete/form-lechapt-calmon";
@@ -295,14 +294,6 @@ export class FormulaireService extends Observable {
         return <InputField>s;
     }
 
-    public getCheckField(formId: string, elemId: string): CheckField {
-        const s = this.getFormulaireElementById(formId, elemId);
-        if (!(s instanceof CheckField)) {
-            throw new Error("Form element with id '" + elemId + "' is not a checkbox");
-        }
-        return <CheckField>s;
-    }
-
     public getSelectField(formId: string, elemId: string): SelectField {
         const s = this.getFormulaireElementById(formId, elemId);
         if (!(s instanceof SelectField)) {
-- 
GitLab


From 393ebdcdefa29953531682b9596639f0a91a7dcd Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Mon, 25 Mar 2019 17:55:44 +0100
Subject: [PATCH 27/39] Suppression de code de validation inutile

---
 .../generic-calculator/calc-name.component.ts |   7 --
 .../calculator.component.ts                   |  47 +++-----
 .../generic-input/generic-input.component.ts  | 113 +-----------------
 .../formulaire/definition/form-definition.ts  |  10 --
 src/app/formulaire/field.ts                   |   1 -
 src/app/formulaire/fieldset.ts                |  12 --
 src/app/formulaire/select-field.ts            |   4 -
 7 files changed, 21 insertions(+), 173 deletions(-)

diff --git a/src/app/components/generic-calculator/calc-name.component.ts b/src/app/components/generic-calculator/calc-name.component.ts
index 208d11e97..3de415d2e 100644
--- a/src/app/components/generic-calculator/calc-name.component.ts
+++ b/src/app/components/generic-calculator/calc-name.component.ts
@@ -76,11 +76,4 @@ export class CalculatorNameComponent extends GenericInputComponent {
 
         return { isValid: valid, message: msg };
     }
-
-    /**
-     * convertit une valeur saisie dans l'UI en valeur affectable au modèle
-     */
-    protected uiToModel(ui: string): any {
-        return ui;
-    }
 }
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 890d21a4a..c6c9afed1 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -1,7 +1,7 @@
 import { Component, OnInit, DoCheck, OnDestroy, ViewChild, ViewChildren, QueryList, AfterViewChecked } from "@angular/core";
 import { ActivatedRoute, Router } from "@angular/router";
 
-import { Observer, Session, ParallelStructure } from "jalhyd";
+import { Observer, Session } from "jalhyd";
 
 import { FormulaireService } from "../../services/formulaire/formulaire.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
@@ -122,7 +122,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     /**
      * détermine si un FormulaireElement est du type FieldsetContainer
      */
-    private isFieldsetContainer(fe: any): boolean {
+    public isFieldsetContainer(fe: any): boolean {
         return fe instanceof FieldsetContainer;
     }
 
@@ -164,9 +164,8 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     }
 
     ngDoCheck() {
-        if (this._formulaire !== undefined) {
-            this.isCalculateDisabled = !(this._formulaire.isValid && this._isUIValid);
-        }
+        console.log(">>> UI validity", this._isUIValid);
+        this.isCalculateDisabled = ! this._isUIValid;
     }
 
     ngOnDestroy() {
@@ -192,7 +191,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     /*
      * gestion des événements clic sur les radios
      */
-    private onRadioClick(info: any) {
+    public onRadioClick(info: any) {
         this.updateLinkedParameters();
         this._pendingRadioClick = true;
         this._pendingRadioClickInfo = info;
@@ -246,11 +245,6 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
         }
     }
 
-    private getFieldsetStyleDisplay(id: string) {
-        const isDisplayed: boolean = this._formulaire.isDisplayed(id);
-        return isDisplayed ? "block" : "none";
-    }
-
     private setForm(f: FormulaireDefinition) {
         if (this._formulaire !== undefined) {
             this._formulaire.removeObserver(this);
@@ -313,11 +307,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
                     // accumulator (valeur précédente du résultat)
                     acc,
                     // currentValue (élément courant dans le tableau)
-                    fieldset,
-                    // currentIndex (indice courant dans le tableau)
-                    currIndex,
-                    // array (tableau parcouru)
-                    array
+                    fieldset
                 ) => {
                     return acc && fieldset.isValid;
                 }
@@ -331,11 +321,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
                     // accumulator (valeur précédente du résultat)
                     acc,
                     // currentValue (élément courant dans le tableau)
-                    fieldsetContainer,
-                    // currentIndex (indice courant dans le tableau)
-                    currIndex,
-                    // array (tableau parcouru)
-                    array
+                    fieldsetContainer
                 ) => {
                     return acc && fieldsetContainer.isValid;
                 }
@@ -344,27 +330,36 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
         }
     }
 
+    public getFieldsetStyleDisplay(id: string) {
+        const isDisplayed: boolean = this._formulaire.isDisplayed(id);
+        return isDisplayed ? "block" : "none";
+    }
+
     /**
      * réception d'un événement de validité d'un FieldSetComponent
      */
-    private OnFieldsetValid() {
+    public OnFieldsetValid() {
         this.updateUIValidity();
     }
 
     /**
      * réception d'un événement de validité d'un FieldsetContainerComponent
      */
-    private onFieldsetContainerValid() {
+    public onFieldsetContainerValid() {
         this.updateUIValidity();
     }
 
     /**
      * réception d'un événement de changement de valeur d'un input
      */
-    private onInputChange() {
+    public onInputChange() {
         this._formulaire.resetResults([]);
     }
 
+    public openHelp() {
+        window.open("assets/docs-fr/calculators/" + this._formulaire.helpLink + "/", "_blank");
+    }
+
     /**
      * flag d'affichage du bouton d'aide
      */
@@ -375,10 +370,6 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
         return false;
     }
 
-    private openHelp() {
-        window.open("assets/docs-fr/calculators/" + this._formulaire.helpLink + "/", "_blank");
-    }
-
     public saveCalculator() {
         this.formulaireService.saveForm(this._formulaire);
     }
diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index 8f9f2f7a2..213dfeeaa 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -204,7 +204,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
      * MAJ et validation de l'UI
      */
     protected updateAndValidateUI() {
-        this._uiValue = this.modelToUI(this.getModelValue());
+        this._uiValue = String(this.getModelValue());
         this.validateUI();
     }
 
@@ -226,7 +226,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
      */
     public updateModelFromUI() {
         if (this.validateUI()) {
-            this.setAndValidateModel(this, this.uiToModel(this._uiValue));
+            this.setAndValidateModel(this, +this._uiValue); // cast UI value to Number
         }
     }
 
@@ -270,13 +270,6 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
      */
     protected abstract validateModelValue(v: any): { isValid: boolean, message: string };
 
-    /**
-     * convertit le modèle en valeur affichable par l'UI
-     */
-    protected modelToUI(v: any): string {
-        return String(v);
-    }
-
     /**
      * valide une valeur saisie dans l'UI (forme de la saisie : est ce bien une date, un nombre, ...)
      * @param ui saisie à valider
@@ -295,106 +288,4 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
 
         return { isValid: valid, message: msg };
     }
-
-    /**
-     * convertit une valeur saisie dans l'UI en valeur affectable au modèle
-     */
-    protected uiToModel(ui: string): any {
-        return +ui;
-    }
-}
-
-/*
- * exemples d'utilisation de GenericInputComponent
- */
-
-/*
-import { Component } from "@angular/core";
-
-import { isNumeric, Message } from "jalhyd";
-
-// exemple où le modèle est un type simple (number)
-
-@Component({
-    selector: "test-input",
-    template: `<div class="md-form form-sm">
-    <input  type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue">
-    <label for="form1">{{title}}</label>
-    <small *ngIf="showError" class="text-danger">{{errorMessage}}</small>
-</div>`
-})
-export class TestInputComponent extends GenericInputComponent {
-    constructor() {
-        super();
-        this._model = 0;
-    }
-
-    protected getModelValue(): any {
-        return this._model;
-    }
-
-    protected setModelValue(v: any) {
-        this._model = v;
-    }
-
-    protected validateModelValue(v: any): { isValid: boolean, message: string } {
-        let msg = undefined;
-        let valid = false;
-
-        if (v < 0)
-            msg = "La valeur n'est pas >= 0 ";
-        else
-            valid = true;
-
-        return { isValid: valid, message: msg };
-    }
-
-    protected uiToModel(ui: string): any {
-        return +ui;
-    }
-}
-
-
-// exemple où le modèle est une classe dont on ne gère qu'un membre
-
-import { ParamDefinition } from "jalhyd";
-
-@Component({
-    selector: "test2-input",
-    template: `<div class="md-form form-sm">
-    <input type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue">
-    <label for="form1">{{title}}</label>
-    <small *ngIf="showError" class="text-danger">{{errorMessage}}</small>
-</div>`
-})
-export class Test2InputComponent extends GenericInputComponent {
-    constructor() {
-        super();
-    }
-
-    // paramètre géré
-    private get _param(): ParamDefinition {
-        return this._model;
-    }
-
-    protected getModelValue(): any {
-        return this._param.getValue();
-    }
-
-    protected setModelValue(v: any) {
-        this._param.setValue(v);
-    }
-
-    protected validateModelValue(v: any): { isValid: boolean, message: string } {
-        let msg = undefined;
-        let valid = false;
-
-        if (v < 0)
-            msg = "La valeur n'est pas >= 0 ";
-        else
-            valid = true;
-
-        return { isValid: valid, message: msg };
-    }
 }
-/**/
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index e1d9f5433..4d14b15b7 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -417,16 +417,6 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         return (<FormulaireElement>this.getFormulaireNodeById(id)).isDisplayed;
     }
 
-    public get isValid(): boolean {
-        let res = true;
-        for (const fs of this.allFieldsets) {
-            if (fs.isDisplayed) {
-                res = res && fs.isValid;
-            }
-        }
-        return res;
-    }
-
     /**
      * gestion d'un clic sur les radios
      */
diff --git a/src/app/formulaire/field.ts b/src/app/formulaire/field.ts
index c8d2f84b6..3bdbef3c0 100644
--- a/src/app/formulaire/field.ts
+++ b/src/app/formulaire/field.ts
@@ -4,7 +4,6 @@ import { Dependency } from "./dependency/dependency";
 import { isNumber } from "util";
 
 export abstract class Field extends FormulaireElement {
-    public abstract get isValid();
 
     public abstract getValue(): any;
     public abstract setValue(sender: any, val: any): void;
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index 14e44ce41..ffa32a38d 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -74,18 +74,6 @@ export class FieldSet extends FormulaireElement implements Observer {
         }
     }
 
-    public get isValid(): boolean {
-        let res = true;
-        for (const f of this.kids) {
-            if (f instanceof Field) {
-                if (f.isDisplayed) {
-                    res = res && f.isValid;
-                }
-            }
-        }
-        return res;
-    }
-
     private parse_select(json: {}): SelectField {
         const res: SelectField = new SelectField(this);
         res.parseConfig(json);
diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts
index df078bfbf..c60b4dd50 100644
--- a/src/app/formulaire/select-field.ts
+++ b/src/app/formulaire/select-field.ts
@@ -51,10 +51,6 @@ export class SelectField extends Field {
         }
     }
 
-    public get isValid(): boolean {
-        return true;
-    }
-
     public getLabel() {
         if (this._selectedEntry) {
             return this._selectedEntry.label;
-- 
GitLab


From c2970c6f7b6fe9431c5fa3c03d9d758dfb5ffa4f Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 26 Mar 2019 15:41:19 +0100
Subject: [PATCH 28/39] =?UTF-8?q?Fix=20#174,=20#177=20-=20modules=20li?=
 =?UTF-8?q?=C3=A9s=20par=20leurs=20r=C3=A9sultats:=20calcul=20en=20cha?=
 =?UTF-8?q?=C3=AEne=20de=20l'aval=20vers=20l'amont?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/clone-calc.e2e-spec.ts                    |  3 --
 .../base-param-input.component.ts             |  2 +-
 .../fixedvar-results.component.ts             |  1 +
 .../param-values/param-values.component.ts    |  2 +-
 .../definition/concrete/form-courbe-remous.ts |  3 ++
 .../concrete/form-lechapt-calmon.ts           |  3 ++
 .../concrete/form-parallel-structures.ts      |  3 ++
 .../concrete/form-regime-uniforme.ts          |  3 ++
 .../concrete/form-section-parametree.ts       |  3 ++
 .../definition/form-compute-courbe-remous.ts  | 20 ++++++--
 .../definition/form-compute-fixedvar.ts       | 18 ++++---
 .../form-compute-section-parametree.ts        | 19 +++++---
 src/app/formulaire/definition/form-compute.ts | 48 ++++++++++++++++---
 .../form-def-parallel-structures.ts           |  4 --
 .../formulaire/definition/form-definition.ts  | 31 +++++++++++-
 src/app/formulaire/fieldset.ts                |  3 --
 src/app/formulaire/ngparam.ts                 | 16 +++++--
 17 files changed, 140 insertions(+), 42 deletions(-)

diff --git a/e2e/clone-calc.e2e-spec.ts b/e2e/clone-calc.e2e-spec.ts
index 459b3d767..dab35d149 100644
--- a/e2e/clone-calc.e2e-spec.ts
+++ b/e2e/clone-calc.e2e-spec.ts
@@ -2,7 +2,6 @@ import { AppPage } from "./app.po";
 import { ListPage } from "./list.po";
 import { CalculatorPage } from "./calculator.po";
 import { Navbar } from "./navbar.po";
-import { SideNav } from "./sidenav.po";
 import { browser } from "protractor";
 
 /**
@@ -13,14 +12,12 @@ describe("ngHyd − clone a calculator", () => {
   let listPage: ListPage;
   let calcPage: CalculatorPage;
   let navbar: Navbar;
-  let sidenav: SideNav;
 
   beforeEach(() => {
     startPage = new AppPage();
     listPage = new ListPage();
     calcPage = new CalculatorPage();
     navbar = new Navbar();
-    sidenav = new SideNav();
   });
 
   it("when cloning a calculator, the clone should have the same values for all parameters", async () => {
diff --git a/src/app/components/base-param-input/base-param-input.component.ts b/src/app/components/base-param-input/base-param-input.component.ts
index e04b0a460..001a186d7 100644
--- a/src/app/components/base-param-input/base-param-input.component.ts
+++ b/src/app/components/base-param-input/base-param-input.component.ts
@@ -75,7 +75,7 @@ export class NgBaseParam extends Observable {
                 valid = true;
             } catch (e) {
                 if (e instanceof Message) {
-                    // @TODO ici au début le service de localisation n'a pas encore chargé ses messages…
+                    // ici au début le service de localisation n'a pas encore chargé ses messages…
                     msg = ServiceFactory.instance.i18nService.localizeMessage(e);
                 } else {
                     msg = "invalid value";
diff --git a/src/app/components/fixedvar-results/fixedvar-results.component.ts b/src/app/components/fixedvar-results/fixedvar-results.component.ts
index d6dca7a41..3392fb698 100644
--- a/src/app/components/fixedvar-results/fixedvar-results.component.ts
+++ b/src/app/components/fixedvar-results/fixedvar-results.component.ts
@@ -98,6 +98,7 @@ export class FixedVarResultsComponent implements DoCheck {
             this.resultsGraphComponent.results = undefined;
         }
 
+        // set _doUpdate flag so that results are rebuilt on the next Angular display cycle
         this._doUpdate = false;
         if (this._fixedResults !== undefined) {
             this._doUpdate = this._fixedResults.hasResults || this._fixedResults.hasLog;
diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts
index 07a32d455..95c9e3c71 100644
--- a/src/app/components/param-values/param-values.component.ts
+++ b/src/app/components/param-values/param-values.component.ts
@@ -63,7 +63,7 @@ export class ParamValuesComponent implements AfterViewInit, Observer {
                 this.openDialog();
             });
         }
-        // subscribe to parameter values change (through dialog actions) @TODO draft
+        // subscribe to parameter values change (through dialog actions)
         this.param.addObserver(this);
     }
 
diff --git a/src/app/formulaire/definition/concrete/form-courbe-remous.ts b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
index 4e96da330..99be68efb 100644
--- a/src/app/formulaire/definition/concrete/form-courbe-remous.ts
+++ b/src/app/formulaire/definition/concrete/form-courbe-remous.ts
@@ -49,6 +49,9 @@ export class FormulaireCourbeRemous extends FormulaireBase {
     // interface Observer
 
     update(sender: IObservable, data: any) {
+
+        super.update(sender, data);
+
         if (sender instanceof FieldSet && data.action === "propertyChange") {
             switch (sender.id) {
                 case "fs_section":
diff --git a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
index c84864baa..db2991206 100644
--- a/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
+++ b/src/app/formulaire/definition/concrete/form-lechapt-calmon.ts
@@ -26,6 +26,9 @@ export class FormulaireLechaptCalmon extends FormulaireBase implements Observer
     // interface Observer
 
     public update(sender: any, data: any) {
+
+        super.update(sender, data);
+
         // en cas de changement de valeur du select de matériau, effacement des résultats et MAJ des champs L,M,N
         if (sender instanceof SelectField) {
             if (data.action === "select") {
diff --git a/src/app/formulaire/definition/concrete/form-parallel-structures.ts b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
index d71119c87..ec840a970 100644
--- a/src/app/formulaire/definition/concrete/form-parallel-structures.ts
+++ b/src/app/formulaire/definition/concrete/form-parallel-structures.ts
@@ -338,6 +338,9 @@ export class FormulaireParallelStructure extends FormulaireBase {
     // interface Observer
 
     public update(sender: any, data: any) {
+
+        super.update(sender, data);
+
         if (sender instanceof FieldsetContainer) {
             switch (data.action) {
                 case "newFieldset":
diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
index 35a08225c..503bdaba7 100644
--- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
+++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
@@ -33,6 +33,9 @@ export class FormulaireRegimeUniforme extends FormulaireBase implements Observer
     // interface Observer
 
     update(sender: IObservable, data: any) {
+
+        super.update(sender, data);
+
         // changement de propriété du FieldSet contenant le select de choix du type de section
         if (sender instanceof FieldSet && sender.id === "fs_section" && data.action === "propertyChange") {
             this.replaceCurrentNub(sender.properties);
diff --git a/src/app/formulaire/definition/concrete/form-section-parametree.ts b/src/app/formulaire/definition/concrete/form-section-parametree.ts
index 9c48c038b..1a3ef62b1 100644
--- a/src/app/formulaire/definition/concrete/form-section-parametree.ts
+++ b/src/app/formulaire/definition/concrete/form-section-parametree.ts
@@ -31,6 +31,9 @@ export class FormulaireSectionParametree extends FormulaireBase {
     // interface Observer
 
     update(sender: IObservable, data: any) {
+
+        super.update(sender, data);
+
         // changement de propriété du FieldSet contenant le select de choix du type de section
         if (sender instanceof FieldSet && data.action === "propertyChange") {
             switch (sender.id) {
diff --git a/src/app/formulaire/definition/form-compute-courbe-remous.ts b/src/app/formulaire/definition/form-compute-courbe-remous.ts
index dc3a6a2e2..b69986e83 100644
--- a/src/app/formulaire/definition/form-compute-courbe-remous.ts
+++ b/src/app/formulaire/definition/form-compute-courbe-remous.ts
@@ -7,6 +7,11 @@ import { FormCompute } from "./form-compute";
 import { FormResultRemous } from "./form-result-remous";
 
 export class FormComputeCourbeRemous extends FormCompute {
+
+    private resultYn: Result;
+
+    private resultYc: Result;
+
     constructor(formBase: FormulaireDefinition, private _formSection: FormDefSection, formResult: FormResultRemous) {
         super(formBase, formResult);
     }
@@ -21,8 +26,15 @@ export class FormComputeCourbeRemous extends FormCompute {
         const prmCR: CourbeRemousParams = cr.prms as CourbeRemousParams;
         const sect: acSection = prmCR.Sn;
 
-        const Yn: Result = sect.Calc("Yn"); // hauteur normale
-        const Yc: Result = sect.Calc("Yc"); // hauteur critique
+        this.resultYn = sect.Calc("Yn"); // hauteur normale
+        this.resultYc = sect.Calc("Yc"); // hauteur critique
+
+        this.reaffectResultComponents();
+    }
+
+    protected reaffectResultComponents() {
+        const cr: CourbeRemous = this._formBase.currentNub as CourbeRemous;
+        const prmCR: CourbeRemousParams = cr.prms as CourbeRemousParams;
 
         this.remousResults.parameters = prmCR;
 
@@ -33,8 +45,8 @@ export class FormComputeCourbeRemous extends FormCompute {
         this.remousResults.result = cr.calculRemous(this.remousResults.extraParamSymbol);
 
         // données du graphe
-        this.remousResults.hauteurNormale = Yn.resultElement;
-        this.remousResults.hauteurCritique = Yc.resultElement;
+        this.remousResults.hauteurNormale = this.resultYn.resultElement;
+        this.remousResults.hauteurCritique = this.resultYc.resultElement;
         if (this.remousResults.extraParamSymbol) {
             this.remousResults.extraGraph = ["Hs", "Hsc", "Yf", "Yt", "Yco"].indexOf(this.remousResults.extraParamSymbol) === -1;
         } else {
diff --git a/src/app/formulaire/definition/form-compute-fixedvar.ts b/src/app/formulaire/definition/form-compute-fixedvar.ts
index 151b875bf..616899160 100644
--- a/src/app/formulaire/definition/form-compute-fixedvar.ts
+++ b/src/app/formulaire/definition/form-compute-fixedvar.ts
@@ -1,4 +1,4 @@
-import { Nub, Result, ComputeNode, ParamValueMode } from "jalhyd";
+import { Nub, Result, ComputeNode } from "jalhyd";
 
 import { FormCompute } from "./form-compute";
 import { NgParameter, ParamRadioConfig } from "../ngparam";
@@ -45,22 +45,28 @@ export class FormComputeFixedVar extends FormCompute {
     protected compute() {
         const nub: Nub = this._formBase.currentNub;
         const computedParam: NgParameter = this.getComputedParameter();
+
+        const res: Result = this.runNubCalc(nub, computedParam);
+
+        this.reaffectResultComponents();
+    }
+
+    protected reaffectResultComponents() {
+        const nub: Nub = this._formBase.currentNub;
+        const computedParam: NgParameter = this.getComputedParameter();
         this.formResult.addFixedParameters();
         const varParam: NgParameter = this.getVariatedParameter();
 
         if (varParam === undefined) {
             // pas de paramètre à varier
-            const res: Result = this.runNubCalc(nub, computedParam);
-            this.formResult.fixedResults.result = res;
+            this.formResult.fixedResults.result = nub.result;
             this.formResult.fixedResults.calculatedParameter = computedParam;
         } else {
             // il y a un paramètre à varier
-            const res: Result = this.runNubCalc(nub, computedParam);
-
             this.formResult.varResults.variatedParameter = varParam;
             this.formResult.varResults.calculatedParameter = computedParam;
 
-            this.formResult.varResults.result = res;
+            this.formResult.varResults.result = nub.result;
             this.formResult.varResults.update(false);
         }
     }
diff --git a/src/app/formulaire/definition/form-compute-section-parametree.ts b/src/app/formulaire/definition/form-compute-section-parametree.ts
index fc41974d9..068dce9d2 100644
--- a/src/app/formulaire/definition/form-compute-section-parametree.ts
+++ b/src/app/formulaire/definition/form-compute-section-parametree.ts
@@ -12,6 +12,8 @@ import { FormulaireNode } from "../formulaire-node";
 
 export class FormComputeSectionParametree extends FormCompute {
 
+    private tmpResult: Result;
+
     constructor(formBase: FormulaireDefinition, private _formSection: FormDefSection, formResult: FormResult) {
         super(formBase, formResult);
     }
@@ -62,20 +64,25 @@ export class FormComputeSectionParametree extends FormCompute {
 
         const sectNub: SectionParametree = this._formBase.currentNub as SectionParametree;
 
-        const sect: acSection = sectNub.section;
-        this._sectionResults.section = sect;
-
-        const tmpResult: Result = sectNub.CalcSerie(
+        this.tmpResult = sectNub.CalcSerie(
             undefined, // valeur initiale, non utilisée dans ce cas
             undefined // variable à calculer, non utilisée
         );
 
+        this.reaffectResultComponents();
+    }
+
+    protected reaffectResultComponents() {
+        const sectNub: SectionParametree = this._formBase.currentNub as SectionParametree;
+        const sect: acSection = sectNub.section;
+        this._sectionResults.section = sect;
+
         // résultats de section (avec le graphique de section)
-        this._sectionResults.result = tmpResult;
+        this._sectionResults.result = this.tmpResult;
 
         // résultats complémentaires des paramètres fixés
         this._formSectionResult.addSectionFixedParameters(false);
-        this._formSectionResult.fixedResults.result = tmpResult;
+        this._formSectionResult.fixedResults.result = this.tmpResult;
     }
 
     /**
diff --git a/src/app/formulaire/definition/form-compute.ts b/src/app/formulaire/definition/form-compute.ts
index 4e8494753..d1e848b28 100644
--- a/src/app/formulaire/definition/form-compute.ts
+++ b/src/app/formulaire/definition/form-compute.ts
@@ -1,11 +1,14 @@
-import { Nub, Result, ParamDomainValue } from "jalhyd";
+import { Nub, Result, ParamDomainValue, Observer } from "jalhyd";
 
 import { FormResult } from "./form-result";
 import { FormulaireDefinition } from "./form-definition";
 import { NgParameter } from "../ngparam";
 
-export abstract class FormCompute {
+export abstract class FormCompute implements Observer {
+
     constructor(protected _formBase: FormulaireDefinition, protected _formResult: FormResult) {
+        // indirectly subscribe to Nub result updates
+        this._formBase.addObserver(this);
     }
 
     protected abstract compute();
@@ -23,14 +26,25 @@ export abstract class FormCompute {
     }
 
     /**
-     * lance le calcul d'un paramètre en déterminant une valeur initiale
+     * Copies current Nub result into result components for display on page.
+     * Should be called every time the Nub result changes
+     */
+    protected abstract reaffectResultComponents();
+
+    /**
+     * Lance le calcul d'un paramètre en déterminant une valeur initiale.
+     * Si nécessaire déclenche un calcul en chaîne des modules en amont.
      */
     protected runNubCalc(nub: Nub, computedParam: NgParameter): Result {
         let init: number;
+        // require chain computation; redundant with Nub.CalcSerie but required
+        // to get initial value here...
+        const computedParamValue = computedParam.getValue(true);
+
         switch (computedParam.domain.domain) {
             case ParamDomainValue.ANY:
                 if (computedParam && computedParam.isDefined) {
-                    init = computedParam.getValue();
+                    init = computedParamValue;
                 }
                 if (init === undefined) {
                     init = 0;
@@ -39,7 +53,7 @@ export abstract class FormCompute {
 
             case ParamDomainValue.POS_NULL:
                 if (computedParam && computedParam.isDefined) {
-                    init = Math.max(computedParam.getValue(), 0);
+                    init = Math.max(computedParamValue, 0);
                 }
                 if (init === undefined) {
                     init = 0;
@@ -52,7 +66,7 @@ export abstract class FormCompute {
 
             case ParamDomainValue.NOT_NULL:
                 if (computedParam && computedParam.isDefined) {
-                    init = computedParam.getValue();
+                    init = computedParamValue;
                 }
                 if (init === undefined || init === 0) {
                     init = 1e-8;
@@ -61,7 +75,7 @@ export abstract class FormCompute {
 
             case ParamDomainValue.POS:
                 if (computedParam && computedParam.isDefined) {
-                    init = Math.max(computedParam.getValue(), 1e-8);
+                    init = Math.max(computedParamValue, 1e-8);
                 }
                 if (init === undefined) {
                     init = 1e-8;
@@ -72,6 +86,9 @@ export abstract class FormCompute {
         return nub.CalcSerie(init, this.getParameterRefid(computedParam));
     }
 
+    /**
+     * Triggers computation of the Nub, updates form results
+     */
     public doCompute() {
         this._formResult.resetResults();
 
@@ -81,4 +98,21 @@ export abstract class FormCompute {
             "action": "resultsUpdated",
         }, this._formBase);
     }
+
+    // interface Observer
+
+    public update(sender: any, data: any): void {
+        if (sender instanceof Nub) {
+            switch (data.action) {
+                case "nubResultUpdated":
+                    // forward Nub results update notification to FormCompute objects
+                    this.reaffectResultComponents();
+                    /* console.log("_____forwarding 2");
+                    this._formBase.notifyObservers({
+                        "action": "resultsUpdated",
+                    }, this._formBase); */
+                    break;
+            }
+        }
+    }
 }
diff --git a/src/app/formulaire/definition/form-def-parallel-structures.ts b/src/app/formulaire/definition/form-def-parallel-structures.ts
index a4856994d..5206d9cc0 100644
--- a/src/app/formulaire/definition/form-def-parallel-structures.ts
+++ b/src/app/formulaire/definition/form-def-parallel-structures.ts
@@ -1,7 +1,3 @@
-import { CalculatorType, StructureType, LoiDebit } from "jalhyd";
-
-import { FieldSet } from "../fieldset";
-
 /**
  * gestion des formulaires "ouvrages parallèles"
  */
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index 4d14b15b7..daa57be19 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -1,4 +1,4 @@
-import { CalculatorType, ComputeNodeType, Nub, Props, Observer, Session, ParallelStructure } from "jalhyd";
+import { CalculatorType, ComputeNodeType, Nub, Props, Observer, Session } from "jalhyd";
 
 import { FormulaireElement } from "../formulaire-element";
 import { NgParameter, ParamRadioConfig } from "../ngparam";
@@ -94,7 +94,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
     }
 
     public initNub(props?: Props) {
-        this._currentNub = this.createNub(props ? props : new Props(this.defaultProperties));
+        this.currentNub = this.createNub(props ? props : new Props(this.defaultProperties));
     }
 
     public get currentNub(): Nub {
@@ -108,7 +108,15 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
                 `Nub ${n.properties["calcType"]} incompatible avec le formulaire ${this._calculatorName} (${this._props["calcType"]})`
             );
         }
+        // unsubscribe from old Nub
+        if (this._currentNub) {
+            this._currentNub.removeObserver(this);
+        }
+        // replace Nub
         this._currentNub = n;
+        // subscribe to new Nub (for result updates)
+        console.log("SET CURRENT NUB -- (re)subscribe to Nub", this._currentNub.uid);
+        this._currentNub.addObserver(this);
     }
 
     /**
@@ -351,6 +359,16 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         }, this);
     }
 
+    /**
+     * Forwards Nub's result updated notification.
+     * Used by FormCompute to update results display
+     */
+    protected notifyNubResultUpdated(sender) {
+        this.notifyObservers({
+            action: "nubResultUpdated"
+        }, sender);
+    }
+
     /**
      * réinitialisation du formulaire suite à un changement d'une valeur, d'une option, ... :
      * effacement des résultats, application des dépendances, ...
@@ -469,5 +487,14 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
     //  interface Observer
 
     public update(sender: any, data: any) {
+        console.log("--- FormDefinition received update", sender.constructor.name, data);
+        if (sender instanceof Nub) {
+            switch (data.action) {
+                case "resultUpdated":
+                    // forward Nub results update notification to FormCompute objects
+                    this.notifyNubResultUpdated(sender);
+                    break;
+            }
+        }
     }
 }
diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts
index ffa32a38d..ff2789eb3 100644
--- a/src/app/formulaire/fieldset.ts
+++ b/src/app/formulaire/fieldset.ts
@@ -133,9 +133,6 @@ export class FieldSet extends FormulaireElement implements Observer {
 
         if (res) {
             res.parseConfig(json, { "radioConfig": default_radio_config });
-            // set parent Nub on Parameter to ensure UID availability
-            // should always be done @TODO check
-            // res.paramDefinition.parent = this._nub;
         }
 
         return res;
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 809b9d1f7..10ed51962 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -51,7 +51,6 @@ export class NgParameter extends InputField implements Observer {
 
     /**
      * Returns a text preview of the current value(s), depending on the value mode
-     * @TODO use display precision to limit decimals
      */
     public static preview(p: ParamDefinition): string {
         let valuePreview: string;
@@ -107,7 +106,12 @@ export class NgParameter extends InputField implements Observer {
                                         return v.toFixed(nDigits);
                                     }).slice(0, 5).join("; ") + "…";
                                 } else {
-                                    valuePreview = String(p.referencedValue.nub.result.vCalc.toFixed(nDigits));
+                                    const vCalc = p.referencedValue.nub.result.vCalc;
+                                    if (vCalc) {
+                                        valuePreview = String(vCalc.toFixed(nDigits));
+                                    } else {
+                                        throw new Error("NgParameter.preview() : No vCalc for computed target Nub !");
+                                    }
                                 }
                             } else {
                                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_IN_CALCULATION");
@@ -120,7 +124,6 @@ export class NgParameter extends InputField implements Observer {
                         // was the result already computed ?
                         try {
                             const remoteValues = p.referencedValue.getParamValues();
-                            // @TODO is the computed value fresh or stale ?
                             if (p.referencedValue.hasMultipleValues()) {
                                 // like LIST mode
                                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
@@ -269,8 +272,11 @@ export class NgParameter extends InputField implements Observer {
         throw new Error("invalid parameter radio configuration " + s);
     }
 
-    public getValue() {
-        return this._paramDef.v;
+    /**
+     * Asks the ParamDefinition for its current value
+     */
+    public getValue(triggerChainComputation: boolean = false) {
+        return this._paramDef.getValue(triggerChainComputation);
     }
 
     /**
-- 
GitLab


From 2e519f7710185e4c7706cdfeee532d0ed990640e Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Tue, 26 Mar 2019 17:26:01 +0100
Subject: [PATCH 29/39] Removed debug messages; worked on notifications for
 chained computation

---
 src/app/app.component.ts                      | 14 ++++----------
 .../calculator.component.ts                   |  2 +-
 src/app/formulaire/definition/form-compute.ts | 19 +++++++++++++++----
 .../formulaire/definition/form-definition.ts  |  2 --
 .../services/formulaire/formulaire.service.ts |  2 +-
 src/app/services/service-factory.ts           |  3 +++
 src/locale/messages.en.json                   |  1 +
 src/locale/messages.fr.json                   |  1 +
 8 files changed, 26 insertions(+), 18 deletions(-)

diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 4e9c48e53..e12e52603 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -18,6 +18,7 @@ import { nghydDateRev } from "../date_revision";
 import { DialogConfirmEmptySessionComponent } from "./components/dialog-confirm-empty-session/dialog-confirm-empty-session.component";
 import { DialogLoadSessionComponent } from "./components/dialog-load-session/dialog-load-session.component";
 import { DialogSaveSessionComponent } from "./components/dialog-save-session/dialog-save-session.component";
+import { NotificationsService } from "./services/notifications/notifications.service";
 
 
 @Component({
@@ -66,6 +67,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     private router: Router,
     private formulaireService: FormulaireService,
     private httpService: HttpService,
+    private notificationsService: NotificationsService,
     private confirmEmptySessionDialog: MatDialog,
     private saveSessionDialog: MatDialog,
     private loadSessionDialog: MatDialog,
@@ -76,6 +78,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
     ServiceFactory.instance.applicationSetupService = appSetupService;
     ServiceFactory.instance.i18nService = intlService;
     ServiceFactory.instance.formulaireService = formulaireService;
+    ServiceFactory.instance.notificationsService = notificationsService;
 
     this.router.events.subscribe((event: Event) => {
       // close side navigation when clicking a calculator tab
@@ -217,16 +220,7 @@ export class AppComponent implements OnInit, OnDestroy, Observer {
   // interface Observer
 
   update(sender: any, data: any): void {
-    if (sender instanceof ErrorService) {
-      // on ouvre un dialogue avec le message d'erreur reçu
-      // if (this._displayErrorDialog) {
-      //   let dialogRef = this.dialog.open(AlertDialog);
-      //   let ad: AlertDialog = dialogRef.componentInstance;
-      //   ad.text = String(data);
-      // }
-      // else
-      console.log(data);
-    } else if (sender instanceof FormulaireService) {
+    if (sender instanceof FormulaireService) {
       switch (data["action"]) {
         case "createForm":
         // add newly created form to calculators list
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index c6c9afed1..d385c1f60 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -164,7 +164,6 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
     }
 
     ngDoCheck() {
-        console.log(">>> UI validity", this._isUIValid);
         this.isCalculateDisabled = ! this._isUIValid;
     }
 
@@ -203,6 +202,7 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
             this._formulaire.onRadioClick(this._pendingRadioClickInfo);
             this._pendingRadioClickInfo = undefined;
         }
+        // @TODO call this._isUIValid here ?
     }
 
     public onCloseForm() {
diff --git a/src/app/formulaire/definition/form-compute.ts b/src/app/formulaire/definition/form-compute.ts
index d1e848b28..9a32be715 100644
--- a/src/app/formulaire/definition/form-compute.ts
+++ b/src/app/formulaire/definition/form-compute.ts
@@ -3,12 +3,22 @@ import { Nub, Result, ParamDomainValue, Observer } from "jalhyd";
 import { FormResult } from "./form-result";
 import { FormulaireDefinition } from "./form-definition";
 import { NgParameter } from "../ngparam";
+import { NotificationsService } from "../../services/notifications/notifications.service";
+import { ServiceFactory } from "../../services/service-factory";
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
 
 export abstract class FormCompute implements Observer {
 
+    private notificationsService: NotificationsService;
+
+    private intlService: I18nService;
+
     constructor(protected _formBase: FormulaireDefinition, protected _formResult: FormResult) {
         // indirectly subscribe to Nub result updates
         this._formBase.addObserver(this);
+
+        this.notificationsService = ServiceFactory.instance.notificationsService;
+        this.intlService = ServiceFactory.instance.i18nService;
     }
 
     protected abstract compute();
@@ -107,10 +117,11 @@ export abstract class FormCompute implements Observer {
                 case "nubResultUpdated":
                     // forward Nub results update notification to FormCompute objects
                     this.reaffectResultComponents();
-                    /* console.log("_____forwarding 2");
-                    this._formBase.notifyObservers({
-                        "action": "resultsUpdated",
-                    }, this._formBase); */
+                    // @TODO reenable later wen chain computation is executed only once or it is a snackbar hell !
+                    /* this.notificationsService.notify(
+                        this.intlService.localizeText("INFO_SNACKBAR_RESULTS_CALCULATED") + " " + this._formBase.calculatorName,
+                        1500
+                    ); */
                     break;
             }
         }
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index daa57be19..be1369a87 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -115,7 +115,6 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
         // replace Nub
         this._currentNub = n;
         // subscribe to new Nub (for result updates)
-        console.log("SET CURRENT NUB -- (re)subscribe to Nub", this._currentNub.uid);
         this._currentNub.addObserver(this);
     }
 
@@ -487,7 +486,6 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
     //  interface Observer
 
     public update(sender: any, data: any) {
-        console.log("--- FormDefinition received update", sender.constructor.name, data);
         if (sender instanceof Nub) {
             switch (data.action) {
                 case "resultUpdated":
diff --git a/src/app/services/formulaire/formulaire.service.ts b/src/app/services/formulaire/formulaire.service.ts
index 367b8d5b7..79f728dcb 100644
--- a/src/app/services/formulaire/formulaire.service.ts
+++ b/src/app/services/formulaire/formulaire.service.ts
@@ -616,7 +616,7 @@ export class FormulaireService extends Observable {
                         if (notify) {
                             this.notificationsService.notify(
                                 this.intlService.localizeText("INFO_SNACKBAR_RESULTS_INVALIDATED") + " " + form.calculatorName,
-                                2000
+                                1500
                             );
                         }
                     }
diff --git a/src/app/services/service-factory.ts b/src/app/services/service-factory.ts
index 0627bdf99..838a00cfc 100644
--- a/src/app/services/service-factory.ts
+++ b/src/app/services/service-factory.ts
@@ -2,6 +2,7 @@ import { ApplicationSetupService } from "./app-setup/app-setup.service";
 import { FormulaireService } from "./formulaire/formulaire.service";
 import { I18nService } from "./internationalisation/internationalisation.service";
 import { HttpService } from "./http/http.service";
+import { NotificationsService } from "./notifications/notifications.service";
 
 export class ServiceFactory {
     private static _instance: ServiceFactory; // instance pour le pattern singleton
@@ -16,6 +17,8 @@ export class ServiceFactory {
 
     public httpService: HttpService;
 
+    public notificationsService: NotificationsService;
+
     public static get instance() {
         if (ServiceFactory._instance === undefined) {
             ServiceFactory._instance = new ServiceFactory();
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index 859d160ba..beb07be9f 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -204,6 +204,7 @@
     "INFO_SETUP_PRECISION_AFFICHAGE": "Display accuracy",
     "INFO_SETUP_PRECISION_CALCUL": "Computation accuracy",
     "INFO_SETUP_TITLE": "Application setup",
+    "INFO_SNACKBAR_RESULTS_CALCULATED": "Results calculated for",
     "INFO_SNACKBAR_RESULTS_INVALIDATED": "Results invalidated for",
     "INFO_SNACKBAR_SETTINGS_SAVED": "Settings saved on this device",
     "INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED": "Default settings restored",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 350dd11ad..2dddd1782 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -204,6 +204,7 @@
     "INFO_SETUP_PRECISION_AFFICHAGE": "Précision d'affichage",
     "INFO_SETUP_PRECISION_CALCUL": "Précision de calcul",
     "INFO_SETUP_TITLE": "Paramètres de l'application",
+    "INFO_SNACKBAR_RESULTS_CALCULATED": "Résultats calculés pour",
     "INFO_SNACKBAR_RESULTS_INVALIDATED": "Résultats invalidés pour",
     "INFO_SNACKBAR_SETTINGS_SAVED": "Paramètres enregistrés sur cet appareil",
     "INFO_SNACKBAR_DEFAULT_SETTINGS_RESTORED": "Paramètres par défaut restaurés",
-- 
GitLab


From bc36bc597496d58e9cb8f0dc03193d29df14fd70 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 27 Mar 2019 09:30:01 +0100
Subject: [PATCH 30/39] =?UTF-8?q?Suppression=20code=20inutilis=C3=A9?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/formulaire/definition/form-compute.ts | 15 ---------------
 1 file changed, 15 deletions(-)

diff --git a/src/app/formulaire/definition/form-compute.ts b/src/app/formulaire/definition/form-compute.ts
index 9a32be715..3e5683d6e 100644
--- a/src/app/formulaire/definition/form-compute.ts
+++ b/src/app/formulaire/definition/form-compute.ts
@@ -3,22 +3,12 @@ import { Nub, Result, ParamDomainValue, Observer } from "jalhyd";
 import { FormResult } from "./form-result";
 import { FormulaireDefinition } from "./form-definition";
 import { NgParameter } from "../ngparam";
-import { NotificationsService } from "../../services/notifications/notifications.service";
-import { ServiceFactory } from "../../services/service-factory";
-import { I18nService } from "../../services/internationalisation/internationalisation.service";
 
 export abstract class FormCompute implements Observer {
 
-    private notificationsService: NotificationsService;
-
-    private intlService: I18nService;
-
     constructor(protected _formBase: FormulaireDefinition, protected _formResult: FormResult) {
         // indirectly subscribe to Nub result updates
         this._formBase.addObserver(this);
-
-        this.notificationsService = ServiceFactory.instance.notificationsService;
-        this.intlService = ServiceFactory.instance.i18nService;
     }
 
     protected abstract compute();
@@ -117,11 +107,6 @@ export abstract class FormCompute implements Observer {
                 case "nubResultUpdated":
                     // forward Nub results update notification to FormCompute objects
                     this.reaffectResultComponents();
-                    // @TODO reenable later wen chain computation is executed only once or it is a snackbar hell !
-                    /* this.notificationsService.notify(
-                        this.intlService.localizeText("INFO_SNACKBAR_RESULTS_CALCULATED") + " " + this._formBase.calculatorName,
-                        1500
-                    ); */
                     break;
             }
         }
-- 
GitLab


From 8f2a171536854bd85f2b4d7afcee3db39c1241fd Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 27 Mar 2019 11:07:38 +0100
Subject: [PATCH 31/39] Fix #183

---
 src/app/formulaire/ngparam.ts | 4 +++-
 src/locale/messages.en.json   | 1 +
 src/locale/messages.fr.json   | 1 +
 3 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 10ed51962..9d9ce7c9c 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -98,6 +98,7 @@ export class NgParameter extends InputField implements Observer {
                         // calculated param ?
                         if (targetParam.valueMode === ParamValueMode.CALCUL) {
                             // was the result already computed ?
+                            // @WAARNING .result might be set but the computation might have failed (dichotomy for ex.)
                             if (p.referencedValue.nub.result) {
                                 if (p.referencedValue.hasMultipleValues()) {
                                     // like LIST mode
@@ -110,7 +111,8 @@ export class NgParameter extends InputField implements Observer {
                                     if (vCalc) {
                                         valuePreview = String(vCalc.toFixed(nDigits));
                                     } else {
-                                        throw new Error("NgParameter.preview() : No vCalc for computed target Nub !");
+                                        // computation has been run but has failed
+                                        valuePreview = i18n.localizeText("INFO_PARAMFIELD_CALCULATION_FAILED");
                                     }
                                 }
                             } else {
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index beb07be9f..d4e1a6420 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -159,6 +159,7 @@
     "INFO_PARAMFIELD_GRAPH_TYPE_HISTOGRAM": "Histogram",
     "INFO_PARAMFIELD_GRAPH_SELECT_X_AXIS": "Variable for X axis",
     "INFO_PARAMFIELD_GRAPH_SELECT_Y_AXIS": "Variable for Y axis",
+    "INFO_PARAMFIELD_CALCULATION_FAILED": "Calculation failed",
     "INFO_PARAMFIELD_IN_CALCULATION": "In calculation",
     "INFO_PARAMFIELD_IN_CALCULATION_INITIAL_VALUE": "initial value",
     "INFO_PARAMFIELD_PARAMCALCULER": "Calculate",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 2dddd1782..8627ade07 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -159,6 +159,7 @@
     "INFO_PARAMFIELD_GRAPH_TYPE_HISTOGRAM": "Histogramme",
     "INFO_PARAMFIELD_GRAPH_SELECT_X_AXIS": "Variable en abscisse",
     "INFO_PARAMFIELD_GRAPH_SELECT_Y_AXIS": "Variable en ordonnée",
+    "INFO_PARAMFIELD_CALCULATION_FAILED": "Échec du calcul",
     "INFO_PARAMFIELD_IN_CALCULATION": "En calcul",
     "INFO_PARAMFIELD_IN_CALCULATION_INITIAL_VALUE": "valeur initiale",
     "INFO_PARAMFIELD_PARAMCALCULER": "calculer",
-- 
GitLab


From cc93de0da3ffe37abfff909c7c1dfdf358a331eb Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 27 Mar 2019 11:17:05 +0100
Subject: [PATCH 32/39] =?UTF-8?q?R=C3=A9sultat=20=C3=A0=20l'=C3=A9cran:=20?=
 =?UTF-8?q?=C3=A9vite=20d'ajouter=20plusieurs=20fois=20les=20param=C3=A8tr?=
 =?UTF-8?q?es=20fix=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 src/app/formulaire/definition/form-compute-fixedvar.ts | 1 +
 src/app/formulaire/definition/form-compute.ts          | 3 ++-
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/app/formulaire/definition/form-compute-fixedvar.ts b/src/app/formulaire/definition/form-compute-fixedvar.ts
index 616899160..873812579 100644
--- a/src/app/formulaire/definition/form-compute-fixedvar.ts
+++ b/src/app/formulaire/definition/form-compute-fixedvar.ts
@@ -54,6 +54,7 @@ export class FormComputeFixedVar extends FormCompute {
     protected reaffectResultComponents() {
         const nub: Nub = this._formBase.currentNub;
         const computedParam: NgParameter = this.getComputedParameter();
+        this.formResult.resetResults(); // to avoid adding fixed parameters more than once (see below)
         this.formResult.addFixedParameters();
         const varParam: NgParameter = this.getVariatedParameter();
 
diff --git a/src/app/formulaire/definition/form-compute.ts b/src/app/formulaire/definition/form-compute.ts
index 3e5683d6e..a9fb302a1 100644
--- a/src/app/formulaire/definition/form-compute.ts
+++ b/src/app/formulaire/definition/form-compute.ts
@@ -27,7 +27,8 @@ export abstract class FormCompute implements Observer {
 
     /**
      * Copies current Nub result into result components for display on page.
-     * Should be called every time the Nub result changes
+     * Should be called every time the Nub result changes.
+     * Must be idempotent.
      */
     protected abstract reaffectResultComponents();
 
-- 
GitLab


From 9ce295b75e665db26aa6fa1f74f8f98f5ac968a6 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 27 Mar 2019 13:59:49 +0100
Subject: [PATCH 33/39] =?UTF-8?q?Correction=20bug=20passage=20du=20mode=20?=
 =?UTF-8?q?LI=C3=89=20au=20mode=20CALC?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../components/generic-calculator/calculator.component.ts   | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index d385c1f60..2635777d6 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -1,7 +1,7 @@
 import { Component, OnInit, DoCheck, OnDestroy, ViewChild, ViewChildren, QueryList, AfterViewChecked } from "@angular/core";
 import { ActivatedRoute, Router } from "@angular/router";
 
-import { Observer, Session } from "jalhyd";
+import { Observer, Session, ParamValueMode } from "jalhyd";
 
 import { FormulaireService } from "../../services/formulaire/formulaire.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
@@ -191,7 +191,9 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
      * gestion des événements clic sur les radios
      */
     public onRadioClick(info: any) {
-        this.updateLinkedParameters();
+        if (info.param.valueMode === ParamValueMode.LINK) {
+            this.updateLinkedParameters(); // only when switching to LINK mode
+        }
         this._pendingRadioClick = true;
         this._pendingRadioClickInfo = info;
     }
-- 
GitLab


From 95d14ff0d525c06d8c326e26ee1a4757a97f659a Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 27 Mar 2019 14:08:43 +0100
Subject: [PATCH 34/39] Fix #168

---
 src/app/formulaire/definition/form-def-paramcalc.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/app/formulaire/definition/form-def-paramcalc.ts b/src/app/formulaire/definition/form-def-paramcalc.ts
index ac743a1ff..230a3257c 100644
--- a/src/app/formulaire/definition/form-def-paramcalc.ts
+++ b/src/app/formulaire/definition/form-def-paramcalc.ts
@@ -79,7 +79,7 @@ export class FormDefParamToCalculate extends FormDefFixedVar {
                     case ParamValueMode.LINK:  // nouvel état
                         if (sourceParam.paramDefinition.hasMultipleValues) {
                             super.resetOtherRadio(sourceParam);
-                            this.setDefault();
+                            this.setDefault(sourceParam);
                         } else {
                             // mode du paramètre référencé
                             const refValue = sourceParam.paramDefinition.referencedValue;
@@ -89,7 +89,7 @@ export class FormDefParamToCalculate extends FormDefFixedVar {
                                     case ParamValueMode.LISTE:
                                     case ParamValueMode.CALCUL:
                                         super.resetOtherRadio(sourceParam);
-                                        this.setDefault();
+                                        this.setDefault(sourceParam);
                                         break;
 
                                     case ParamValueMode.LINK:
-- 
GitLab


From 8ad77bade6f05acdc9e1ab268443aadae3da4829 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 27 Mar 2019 16:13:53 +0100
Subject: [PATCH 35/39] =?UTF-8?q?Traduction=20des=20libell=C3=A9s=20de=20v?=
 =?UTF-8?q?aleurs=20li=C3=A9es?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../param-link/param-link.component.ts        | 26 +++++++++++++++----
 src/locale/messages.en.json                   |  5 ++++
 src/locale/messages.fr.json                   |  5 ++++
 3 files changed, 31 insertions(+), 5 deletions(-)

diff --git a/src/app/components/param-link/param-link.component.ts b/src/app/components/param-link/param-link.component.ts
index edad76498..4d3efa4e0 100644
--- a/src/app/components/param-link/param-link.component.ts
+++ b/src/app/components/param-link/param-link.component.ts
@@ -6,6 +6,7 @@ import { LinkedValue, ParamValueMode, Observer, Structure } from "jalhyd";
 import { FormulaireService } from "../../services/formulaire/formulaire.service";
 import { I18nService } from "../../services/internationalisation/internationalisation.service";
 import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
+import { sprintf } from "sprintf-js";
 
 @Component({
     selector: "param-link",
@@ -112,23 +113,38 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
             p = i.nub.findPositionInParent();
             if (i.isResult()) {
                 // résultat d'ouvrage
-                return `${s} (résultat de ${c}, ouvrage ${p + 1})`;
+                return sprintf(
+                    this.intlService.localizeText("INFO_LINKED_VALUE_DEVICE_RESULT"),
+                    s, c, (p + 1)
+                );
             } else {
                 // paramètre d'ouvrage
-                return `${s} (${c}, ouvrage ${p + 1})`;
+                return sprintf(
+                    this.intlService.localizeText("INFO_LINKED_VALUE_DEVICE"),
+                    s, c, (p + 1)
+                );
             }
         } else
         // 2. Résultat
         if (i.isResult()) {
-            return `${s} (résultat de ${c})`;
+            return sprintf(
+                this.intlService.localizeText("INFO_LINKED_VALUE_RESULT"),
+                s, c
+            );
         } else
         // 3. Résultat complémentaire
         if (i.isExtraResult()) {
             if (i.meta["result"]) {
                 // @TODO not used ?
-                return `${s} (${c}, résultat complémentaire de ${i.meta["result"]})`;
+                return sprintf(
+                    this.intlService.localizeText("INFO_LINKED_VALUE_EXTRA_RESULT_OF"),
+                    s, c, i.meta["result"]
+                );
             } else {
-                return `${s} (${c}, résultat complémentaire)`;
+                return sprintf(
+                    this.intlService.localizeText("INFO_LINKED_VALUE_EXTRA_RESULT"),
+                    s, c
+                );
             }
         } else {
             // 4. Paramètre (cas général)
diff --git a/src/locale/messages.en.json b/src/locale/messages.en.json
index d4e1a6420..66c9f9d6b 100644
--- a/src/locale/messages.en.json
+++ b/src/locale/messages.en.json
@@ -127,6 +127,11 @@
     "INFO_LIB_ZDV": "Crest weir elevation or gate base",
     "INFO_LIB_ZRAM": "Upstream apron elevation",
     "INFO_LIB_ZT": "Triangle top elevation",
+    "INFO_LINKED_VALUE_DEVICE": "%s (%s, device %s)",
+    "INFO_LINKED_VALUE_RESULT": "%s (result of %s)",
+    "INFO_LINKED_VALUE_DEVICE_RESULT": "%s (result of %s, device %s)",
+    "INFO_LINKED_VALUE_EXTRA_RESULT": "%s (%s, extra result)",
+    "INFO_LINKED_VALUE_EXTRA_RESULT_OF": "%s (%s, extra result of %s)",
     "INFO_MACRORUGO_TITRE": "Rock-ramp fishpasses",
     "INFO_MACRORUGO_TITRE_COURT": "RR fishpasses",
     "INFO_MENU_HELP_TITLE": "Help",
diff --git a/src/locale/messages.fr.json b/src/locale/messages.fr.json
index 8627ade07..c8d611b36 100644
--- a/src/locale/messages.fr.json
+++ b/src/locale/messages.fr.json
@@ -127,6 +127,11 @@
     "INFO_LIB_ZDV": "Cote de la crête du déversoir ou du radier de la vanne",
     "INFO_LIB_ZRAM": "Cote du radier amont",
     "INFO_LIB_ZT": "Cote haute du triangle",
+    "INFO_LINKED_VALUE_DEVICE": "%s (%s, ouvrage %s)",
+    "INFO_LINKED_VALUE_RESULT": "%s (résultat de %s)",
+    "INFO_LINKED_VALUE_DEVICE_RESULT": "%s (résultat de %s, ouvrage %s)",
+    "INFO_LINKED_VALUE_EXTRA_RESULT": "%s (%s, résultat complémentaire)",
+    "INFO_LINKED_VALUE_EXTRA_RESULT_OF": "%s (%s, résultat complémentaire de %s)",
     "INFO_MENU_HELP_TITLE": "Aide",
     "INFO_MENU_LOAD_SESSION_TITLE": "Charger une session",
     "INFO_MENU_SAVE_SESSION_TITLE": "Enregistrer la session",
-- 
GitLab


From 0aea540f1139c7f477fe81aaad176addc11e3c23 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 27 Mar 2019 17:24:10 +0100
Subject: [PATCH 36/39] Fix #184
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

plus mise à jour test e2e
---
 e2e/calculator.po.ts                          |  8 +++++
 e2e/load-linked-params.e2e-spec.ts            | 31 ++++++++++++++++++-
 .../field-set/field-set.component.html        |  2 +-
 .../param-field-line.component.ts             |  4 +--
 4 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/e2e/calculator.po.ts b/e2e/calculator.po.ts
index c9276cad4..b47d2ef68 100644
--- a/e2e/calculator.po.ts
+++ b/e2e/calculator.po.ts
@@ -50,6 +50,14 @@ export class CalculatorPage {
     return element.all(by.css('mat-button-toggle.radio_cal[ng-reflect-checked="true"]'));
   }
 
+  getAddStructureButton() {
+    return element(by.css("fieldset-container .hyd-window-btns button.add-structure"));
+  }
+
+  getAllLinkButtons() {
+    return element.all(by.css("mat-button-toggle.radio_link"));
+  }
+
   scrollTo(elt: ElementFinder) {
     browser.controlFlow().execute(function() {
       browser.executeScript("arguments[0].scrollIntoView(true)", elt.getWebElement());
diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts
index 56f84364a..2d6e91a20 100644
--- a/e2e/load-linked-params.e2e-spec.ts
+++ b/e2e/load-linked-params.e2e-spec.ts
@@ -1,4 +1,5 @@
 import { AppPage } from "./app.po";
+import { ListPage } from "./list.po";
 import { CalculatorPage } from "./calculator.po";
 import { Navbar } from "./navbar.po";
 import { SideNav } from "./sidenav.po";
@@ -11,6 +12,7 @@ import { browser } from "protractor";
  */
 describe("ngHyd − load session with multiple linked parameters", () => {
   let startPage: AppPage;
+  let listPage: ListPage;
   let calcPage: CalculatorPage;
   let navbar: Navbar;
   let sidenav: SideNav;
@@ -20,10 +22,11 @@ describe("ngHyd − load session with multiple linked parameters", () => {
       calcPage = new CalculatorPage();
       navbar = new Navbar();
       sidenav = new SideNav();
+      listPage = new ListPage();
   }
   beforeEach(init);
 
-  it("when loading session-liens-spaghetti.json, all links should point to the right target", async () => {
+   it("when loading session-liens-spaghetti.json, all links should point to the right target", async () => {
     await startPage.navigateTo();
 
     await navbar.clickMenuButton();
@@ -94,4 +97,30 @@ describe("ngHyd − load session with multiple linked parameters", () => {
     expect(lo_brv).toEqual("LargeurBerge (Sec. param.)");
   });
 
+  it("when creating parallel structures, devices should be linkabke to one another", async () => {
+
+    // 1. check Lois d'ouvrages
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(8);
+    await calcPage.getAddStructureButton().click();
+    const nb1 = await calcPage.getAllLinkButtons().count();
+    expect(nb1).toBe(8); // link buttons on children but not on parent
+
+    // 2. check Passe à bassin: Cloisons
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(10);
+    await calcPage.getAddStructureButton().click();
+    const nb2 = await calcPage.getAllLinkButtons().count();
+    expect(nb2).toBe(4); // link buttons on children but not on parent
+
+
+    // 3. check Lois de déversoirs dénoyés
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(9);
+    await calcPage.getAddStructureButton().click();
+    const nb3 = await calcPage.getAllLinkButtons().count();
+    expect(nb3).toBe(6); // link buttons on children but not on parent
+
+  });
+
 });
diff --git a/src/app/components/field-set/field-set.component.html b/src/app/components/field-set/field-set.component.html
index 444001156..b87bbe724 100644
--- a/src/app/components/field-set/field-set.component.html
+++ b/src/app/components/field-set/field-set.component.html
@@ -3,7 +3,7 @@
         {{ title }}
     </mat-card-title>
     <div *ngIf="showButtons" class="hyd-window-btns">
-        <button type="button" mat-icon-button (click)="onAddClick()">
+        <button type="button" mat-icon-button (click)="onAddClick()" class="add-structure">
             <mat-icon>add_box</mat-icon>
         </button>
         <button type="button" mat-icon-button [disabled]="! enableRemoveButton" (click)="onRemoveClick()">
diff --git a/src/app/components/param-field-line/param-field-line.component.ts b/src/app/components/param-field-line/param-field-line.component.ts
index e147f1f9f..47a79d393 100644
--- a/src/app/components/param-field-line/param-field-line.component.ts
+++ b/src/app/components/param-field-line/param-field-line.component.ts
@@ -203,8 +203,8 @@ export class ParamFieldLineComponent implements OnChanges {
             }
 
             // ou un seul module de calcul "ouvrages parallèles"
-            if (this._formService.formulaires[0].calculatorType === CalculatorType.ParallelStructure) {
-                const ps: ParallelStructure = this._formService.formulaires[0].currentNub as ParallelStructure;
+            if (this._formService.formulaires[0].currentNub instanceof ParallelStructure) {
+                const ps: ParallelStructure = this._formService.formulaires[0].currentNub;
                 if (ps.structures.length > 1) {
                     return this._formService.getLinkableValues(this.param).length > 0;
                 }
-- 
GitLab


From 77de25fff5cbf8bc533bd9309363425e76b5a388 Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Thu, 28 Mar 2019 11:46:33 +0100
Subject: [PATCH 37/39] =?UTF-8?q?Nouveau=20test=20e2e=20pour=20le=20calcul?=
 =?UTF-8?q?=20de=20param=C3=A8tres=20li=C3=A9s?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/calculate-all-params.e2e-spec.ts    |   4 -
 e2e/calculate-linked-params.e2e-spec.ts | 490 ++++++++++++++++++++++++
 e2e/calculator.po.ts                    |  14 +-
 e2e/load-linked-params.e2e-spec.ts      |   2 +-
 4 files changed, 504 insertions(+), 6 deletions(-)
 create mode 100644 e2e/calculate-linked-params.e2e-spec.ts

diff --git a/e2e/calculate-all-params.e2e-spec.ts b/e2e/calculate-all-params.e2e-spec.ts
index eaff7587d..49d6cc12c 100644
--- a/e2e/calculate-all-params.e2e-spec.ts
+++ b/e2e/calculate-all-params.e2e-spec.ts
@@ -1,9 +1,5 @@
-import { AppPage } from "./app.po";
 import { ListPage } from "./list.po";
 import { CalculatorPage } from "./calculator.po";
-import { Navbar } from "./navbar.po";
-import { SideNav } from "./sidenav.po";
-import { browser } from "protractor";
 
 /**
  * For all calculators, try to calculate every parameter: check that only one parameter
diff --git a/e2e/calculate-linked-params.e2e-spec.ts b/e2e/calculate-linked-params.e2e-spec.ts
new file mode 100644
index 000000000..179b55f54
--- /dev/null
+++ b/e2e/calculate-linked-params.e2e-spec.ts
@@ -0,0 +1,490 @@
+import { AppPage } from "./app.po";
+import { ListPage } from "./list.po";
+import { CalculatorPage } from "./calculator.po";
+import { Navbar } from "./navbar.po";
+import { SideNav } from "./sidenav.po";
+import { browser } from "protractor";
+
+/**
+ * Uses an example configuration to calculate :
+ *  - with a parameter linked to a single parameter
+ *  - with a parameter linked to a single parameter, plus local variated parameter
+ *  - with a parameter linked to a variated parameter
+ *  - with a parameter linked to a single result
+ *  - with a parameter linked to a single result, plus local variated parameter
+ *  - with a parameter linked to a variated result
+ *  - with a parameter linked to a single extra result
+ *  - with a parameter linked to a single extra result, plus local variated parameter
+ *  - with a parameter linked to a variated extra result
+ *
+ *  => plus all those combinations in indeirect link mode (linked parameter target
+ *     is also a linked parameter)
+ */
+describe("ngHyd − calculate with linked parameters", () => {
+  let listPage: ListPage;
+  let calcPage: CalculatorPage;
+  let navBar: Navbar;
+  let startPage: AppPage;
+
+  beforeEach(() => {
+    listPage = new ListPage();
+    calcPage = new CalculatorPage();
+    navBar = new Navbar();
+    startPage = new AppPage();
+
+  });
+
+  async function computeAndCheckPresenceOfResults() {
+    // check that "compute" button is active
+    const calcButton = calcPage.getCalculateButton();
+    const disabledState = await calcButton.getAttribute("disabled");
+    expect(disabledState).not.toBe("true");
+    // click "compute" button
+    await calcButton.click();
+    // check that result is not empty
+    const hasResults = await calcPage.hasResults();
+    expect(hasResults).toBe(true);
+  }
+
+  it(" − direct links : parameter linked to a single parameter", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (R uniforme)
+    const Y = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y);
+    await calcPage.changeSelectValue(sel, 1);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − direct links : parameter linked to a single parameter, plus local variated parameter", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (R uniforme)
+    const Y = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y);
+    await calcPage.changeSelectValue(sel, 1);
+    // vary W
+    const W = calcPage.getInputById("W");
+    await calcPage.setParamMode(W, "var");
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − direct links : parameter linked to a variated parameter", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+    // vary Y
+    const Y1 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y1, "var");
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (R uniforme)
+    const Y2 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y2, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y2);
+    await calcPage.changeSelectValue(sel, 1);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − direct links : parameter linked to a single result", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+    // calculate Y
+    const Y1 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y1, "cal");
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (R uniforme)
+    const Y2 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y2, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y2);
+    await calcPage.changeSelectValue(sel, 1);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − direct links : parameter linked to a single result, plus local variated parameter", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+    // calculate Y
+    const Y1 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y1, "cal");
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (R uniforme)
+    const Y2 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y2, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y2);
+    await calcPage.changeSelectValue(sel, 1);
+    // vary W
+    const W = calcPage.getInputById("W");
+    await calcPage.setParamMode(W, "var");
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − direct links : parameter linked to a variated result", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+    // vary Q
+    const Q = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Q, "var");
+    // calculate Y
+    const Y1 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y1, "cal");
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (R uniforme)
+    const Y2 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y2, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y2);
+    await calcPage.changeSelectValue(sel, 1);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − direct links : parameter linked to a single extra result", async () => {
+    // create a Déversoirs dénoyés
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(9);
+
+    // create a Régime uniforme
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(3);
+    // link Q to CvQT (Déversoirs dénoyés)
+    const Q = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Q, "link");
+    const sel = await calcPage.getLinkedValueSelect(Q);
+    await calcPage.changeSelectValue(sel, 1);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − direct links : parameter linked to a single extra result, plus local variated parameter", async () => {
+    // create a Déversoirs dénoyés
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(9);
+
+    // create a Régime uniforme
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(3);
+    // link Q to CvQT (Déversoirs dénoyés)
+    const Q = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Q, "link");
+    const sel = await calcPage.getLinkedValueSelect(Q);
+    await calcPage.changeSelectValue(sel, 1);
+    // vary YB
+    const YB = calcPage.getInputById("YB");
+    await calcPage.setParamMode(YB, "var");
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − direct links : parameter linked to a variated extra result", async () => {
+    // create a Déversoirs dénoyés
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(9);
+    // vary BR
+    const BR = calcPage.getInputById("BR");
+    await calcPage.setParamMode(BR, "var");
+
+    // create a Régime uniforme
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(3);
+    // link Q to CvQT (Déversoirs dénoyés)
+    const Q = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Q, "link");
+    const sel = await calcPage.getLinkedValueSelect(Q);
+    await calcPage.changeSelectValue(sel, 1);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a single parameter", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Y to Y (R uniforme)
+    const Yproxy = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Yproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Yproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (Sec param)
+    const Y = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Yproxy, "link");
+    const sel = await calcPage.getLinkedValueSelect(Yproxy);
+    await calcPage.changeSelectValue(selYproxy, 3);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a single parameter, plus local variated parameter", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Y to Y (R uniforme)
+    const Yproxy = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Yproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Yproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (Sec param)
+    const Y = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y);
+    await calcPage.changeSelectValue(sel, 3);
+    // vary W
+    const W = calcPage.getInputById("W");
+    await calcPage.setParamMode(W, "var");
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a variated parameter", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+    // vary Y
+    const Y1 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y1, "var");
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Y to Y (R uniforme)
+    const Yproxy = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Yproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Yproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (Sec param)
+    const Y2 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y2, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y2);
+    await calcPage.changeSelectValue(sel, 3);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a single result", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+    // calculate Y
+    const Y1 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y1, "cal");
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Y to Y (R uniforme)
+    const Yproxy = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Yproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Yproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (Sec param)
+    const Y2 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y2, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y2);
+    await calcPage.changeSelectValue(sel, 3);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a single result, plus local variated parameter", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+    // calculate Y
+    const Y1 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y1, "cal");
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Y to Y (R uniforme)
+    const Yproxy = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Yproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Yproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (Sec param)
+    const Y2 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y2, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y2);
+    await calcPage.changeSelectValue(sel, 3);
+    // vary W
+    const W = calcPage.getInputById("W");
+    await calcPage.setParamMode(W, "var");
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a variated result", async () => {
+    // create a Régime uniforme
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(3);
+    // vary Q
+    const Q = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Q, "var");
+    // calculate Y
+    const Y1 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y1, "cal");
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Y to Y (R uniforme)
+    const Yproxy = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Yproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Yproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a PAB : dimensions
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(5);
+    // link Y to Y (Sec param)
+    const Y2 = calcPage.getInputById("Y");
+    await calcPage.setParamMode(Y2, "link");
+    const sel = await calcPage.getLinkedValueSelect(Y2);
+    await calcPage.changeSelectValue(sel, 3);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a single extra result", async () => {
+    // create a Déversoirs dénoyés
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(9);
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Q to CvQT (Déversoirs dénoyés)
+    const Qproxy = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Qproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Qproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a Régime uniforme
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(3);
+    // link Q to CvQT (Sec param)
+    const Q = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Q, "link");
+    const sel = await calcPage.getLinkedValueSelect(Q);
+    await calcPage.changeSelectValue(sel, 3);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a single extra result, plus local variated parameter", async () => {
+    // create a Déversoirs dénoyés
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(9);
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Q to CvQT (Déversoirs dénoyés)
+    const Qproxy = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Qproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Qproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a Régime uniforme
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(3);
+    // link Q to CvQT (Sec param)
+    const Q = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Q, "link");
+    const sel = await calcPage.getLinkedValueSelect(Q);
+    await calcPage.changeSelectValue(sel, 3);
+    // vary YB
+    const YB = calcPage.getInputById("YB");
+    await calcPage.setParamMode(YB, "var");
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+  it(" − indirect links : parameter linked to a variated extra result", async () => {
+    // create a Déversoirs dénoyés
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(9);
+    // vary BR
+    const BR = calcPage.getInputById("BR");
+    await calcPage.setParamMode(BR, "var");
+
+    // create a Section paramétrée
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(2);
+    // link Q to CvQT (Déversoirs dénoyés)
+    const Qproxy = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Qproxy, "link");
+    const selYproxy = await calcPage.getLinkedValueSelect(Qproxy);
+    await calcPage.changeSelectValue(selYproxy, 1);
+
+    // create a Régime uniforme
+    await navBar.clickNewCalculatorButton();
+    await listPage.clickMenuEntryForCalcType(3);
+    // link Q to CvQT (Sec param)
+    const Q = calcPage.getInputById("Q");
+    await calcPage.setParamMode(Q, "link");
+    const sel = await calcPage.getLinkedValueSelect(Q);
+    await calcPage.changeSelectValue(sel, 3);
+
+    await computeAndCheckPresenceOfResults();
+  });
+
+});
diff --git a/e2e/calculator.po.ts b/e2e/calculator.po.ts
index b47d2ef68..69cc7a7b0 100644
--- a/e2e/calculator.po.ts
+++ b/e2e/calculator.po.ts
@@ -120,11 +120,23 @@ export class CalculatorPage {
     await button.click();
     // for "var" mode, close the modal
     if (mode === "var") {
-      await browser.sleep(500);
+      await browser.sleep(500); // wait for the modal to appear
       await element(by.css("dialog-edit-param-values .mat-dialog-actions button")).click();
+      await browser.sleep(500); // wait for the navbar to reappear after modal dismissal
     }
   }
 
+  /**
+   * @param elt an <input> element
+   */
+  async getLinkedValueSelect(elt: ElementFinder): Promise<ElementFinder> {
+    // get parent (div.container)
+    const container = await this.findParentContainer(elt) as ElementFinder;
+    // find <select>
+    const select = container.element(by.css("param-link mat-select"));
+    return select;
+  }
+
   /**
    * Returns an object containing all the calculator's inputs values, indexed
    * by parameter ID
diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts
index 2d6e91a20..bac4ab8ee 100644
--- a/e2e/load-linked-params.e2e-spec.ts
+++ b/e2e/load-linked-params.e2e-spec.ts
@@ -10,7 +10,7 @@ import { browser } from "protractor";
  * from one to another
  * @TODO les valeurs des Select sont comparées au français, pas très générique :/
  */
-describe("ngHyd − load session with multiple linked parameters", () => {
+describe("ngHyd − load session with multiple linked parameters − ", () => {
   let startPage: AppPage;
   let listPage: ListPage;
   let calcPage: CalculatorPage;
-- 
GitLab


From 0e72e00f96f3223c85408317d53faf687052b73e Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Thu, 28 Mar 2019 15:41:34 +0100
Subject: [PATCH 38/39] Fix #186

---
 .../base-param-input.component.ts             |  4 +-
 .../generic-calculator/calc-name.component.ts | 39 +++++++------------
 .../generic-input/generic-input.component.ts  |  9 +++--
 .../ngparam-input/ngparam-input.component.ts  |  4 +-
 4 files changed, 22 insertions(+), 34 deletions(-)

diff --git a/src/app/components/base-param-input/base-param-input.component.ts b/src/app/components/base-param-input/base-param-input.component.ts
index 001a186d7..9b69dd05e 100644
--- a/src/app/components/base-param-input/base-param-input.component.ts
+++ b/src/app/components/base-param-input/base-param-input.component.ts
@@ -92,8 +92,8 @@ export class NgBaseParam extends Observable {
     templateUrl: "../generic-input/generic-input.component.html",
 })
 export class BaseParamInputComponent extends GenericInputComponent {
-    constructor(private intlService: I18nService, cdRef: ChangeDetectorRef) {
-        super(cdRef);
+    constructor(intlService: I18nService, cdRef: ChangeDetectorRef) {
+        super(cdRef, intlService);
     }
 
     /**
diff --git a/src/app/components/generic-calculator/calc-name.component.ts b/src/app/components/generic-calculator/calc-name.component.ts
index 3de415d2e..309526b77 100644
--- a/src/app/components/generic-calculator/calc-name.component.ts
+++ b/src/app/components/generic-calculator/calc-name.component.ts
@@ -1,6 +1,7 @@
-import { Component, Input } from "@angular/core";
+import { Component } from "@angular/core";
 import { GenericInputComponent } from "../generic-input/generic-input.component";
 import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
 
 @Component({
     selector: "calc-name",
@@ -11,8 +12,8 @@ import { FormulaireDefinition } from "../../formulaire/definition/form-definitio
 })
 export class CalculatorNameComponent extends GenericInputComponent {
 
-    constructor() {
-        super(null);
+    constructor(intlService: I18nService) {
+        super(null, intlService);
     }
 
     /**
@@ -39,41 +40,27 @@ export class CalculatorNameComponent extends GenericInputComponent {
         this.updateAndValidateUI();
     }
 
-    /**
-     * valide une valeur de modèle : est ce une valeur acceptable ? (par ex, nombre dans un intervalle, valeur dans une liste, ...)
-     * @param v valide la valeur du modèle
-     * @returns isValid : true si la valeur est valide, false sinon
-     * @returns message : message d'erreur
-     */
     protected validateModelValue(v: any): { isValid: boolean, message: string } {
-        let msg;
-        let valid = false;
-
-        if (!(typeof (v) === "string") || v.length < 1) {
-            msg = "Veuillez entrer un nom";
-        } else {
-            valid = true;
-        }
-
-        return { isValid: valid, message: msg };
+        // no model validation for a simple string
+        return { isValid: true, message: undefined };
     }
 
-    /**
-     * valide une valeur saisie dans l'UI (forme de la saisie : est ce bien une date, un nombre, ...)
-     * @param ui valide la valeur saisie
-     * @returns isValid : true si la valeur est valide, false sinon
-     * @returns message : message d'erreur
-     */
     protected validateUIValue(ui: string): { isValid: boolean, message: string } {
         let valid = false;
         let msg: string;
 
         if (ui === undefined || ui.length < 1) {
-            msg = "Veuillez entrer un nom";
+            msg = "Veuillez entrer un nom tralala";
         } else {
             valid = true;
         }
 
         return { isValid: valid, message: msg };
     }
+
+    public updateModelFromUI() {
+        if (this.validateUI()) {
+            this.setAndValidateModel(this, this.uiValue); // do NOT cast UI value to Number
+        }
+    }
 }
diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts
index 213dfeeaa..1bac27018 100644
--- a/src/app/components/generic-input/generic-input.component.ts
+++ b/src/app/components/generic-input/generic-input.component.ts
@@ -4,6 +4,7 @@ import { BaseComponent } from "../base/base.component";
 import { isNumeric } from "jalhyd";
 import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
 import { NgParameter } from "../../formulaire/ngparam";
+import { I18nService } from "../../services/internationalisation/internationalisation.service";
 
 /**
  * classe de gestion générique d'un champ de saisie avec titre, validation et message d'erreur
@@ -88,7 +89,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
 
     @ViewChild("inputControl") inputField: NgModel;
 
-    constructor(private cdRef: ChangeDetectorRef) {
+    constructor(private cdRef: ChangeDetectorRef, protected intlService: I18nService) {
         super();
     }
 
@@ -129,7 +130,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
         }
     }
 
-    private validateUI() {
+    protected validateUI() {
         const { isValid, message } = this.validateUIValue(this._uiValue);
         this._errorMessageUI = message;
         this.detectChanges();
@@ -185,7 +186,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
         this.change.emit({ "action": "model", "value": this.getModelValue() });
     }
 
-    private setAndValidateModel(sender: any, v: any) {
+    protected setAndValidateModel(sender: any, v: any) {
         this.setModelValue(sender, v);
         this.emitModelChanged();
 
@@ -281,7 +282,7 @@ export abstract class GenericInputComponent extends BaseComponent implements OnC
         let msg: string;
 
         if (! isNumeric(ui)) {
-            msg = "Veuillez entrer une valeur numérique";
+            msg = this.intlService.localizeText("ERROR_PARAM_MUST_BE_A_NUMBER");
         } else {
             valid = true;
         }
diff --git a/src/app/components/ngparam-input/ngparam-input.component.ts b/src/app/components/ngparam-input/ngparam-input.component.ts
index 7e1af55ce..2717729d8 100644
--- a/src/app/components/ngparam-input/ngparam-input.component.ts
+++ b/src/app/components/ngparam-input/ngparam-input.component.ts
@@ -29,8 +29,8 @@ export class NgParamInputComponent extends GenericInputComponent implements Obse
      */
     private _tmp: number;
 
-    constructor(private intlService: I18nService, cdRef: ChangeDetectorRef) {
-        super(cdRef);
+    constructor(intlService: I18nService, cdRef: ChangeDetectorRef) {
+        super(cdRef, intlService);
     }
 
     /**
-- 
GitLab


From e697fb810e3bfcdd63e6f43667ab0bd8867fa9ef Mon Sep 17 00:00:00 2001
From: "mathias.chouet" <mathias.chouet@irstea.fr>
Date: Wed, 10 Apr 2019 15:36:09 +0200
Subject: [PATCH 39/39] =?UTF-8?q?Adaptation=20=C3=A0=20jalhyd#79?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 e2e/clone-calc.e2e-spec.ts                    |  17 +-
 e2e/compute-reset-chained-links.e2e-spec.ts   |  10 +-
 e2e/link-parallel-devices.e2e-spec.ts         |  47 ++++++
 e2e/load-linked-params.e2e-spec.ts            |  26 ---
 .../base-param-input.component.ts             |   4 +-
 .../generic-calculator/calc-name.component.ts |   2 +-
 .../calculator.component.ts                   |   1 -
 .../param-field-line.component.html           |   6 +-
 .../param-field-line.component.ts             |  24 ++-
 .../param-link/param-link.component.ts        |  11 +-
 .../remous-results.component.ts               |   2 +-
 .../definition/concrete/form-base.ts          |   8 -
 .../concrete/form-regime-uniforme.ts          |   1 -
 .../definition/form-compute-fixedvar.ts       |   2 +-
 .../form-compute-parallel-structures.ts       |   6 +-
 .../form-compute-section-parametree.ts        |   2 +-
 src/app/formulaire/definition/form-compute.ts |  14 +-
 .../definition/form-def-fixedvar.ts           | 154 +-----------------
 .../definition/form-def-paramcalc.ts          |  88 ----------
 .../formulaire/definition/form-definition.ts  |   2 +-
 .../definition/form-result-fixedvar.ts        |   2 -
 src/app/formulaire/ngparam.ts                 |  93 +++++------
 22 files changed, 151 insertions(+), 371 deletions(-)
 create mode 100644 e2e/link-parallel-devices.e2e-spec.ts

diff --git a/e2e/clone-calc.e2e-spec.ts b/e2e/clone-calc.e2e-spec.ts
index dab35d149..b768d34f0 100644
--- a/e2e/clone-calc.e2e-spec.ts
+++ b/e2e/clone-calc.e2e-spec.ts
@@ -72,22 +72,7 @@ describe("ngHyd − clone a calculator", () => {
       const displayedVal = await calcPage.getInputById(k).getAttribute("value");
       expect(displayedVal).toBe("" + v);
     });
-  });
-
-  it("cloning a parallel-structures calculator should work", async () => {
-    await startPage.navigateTo();
-
-    // create source module to clone
-    await navbar.clickNewCalculatorButton();
-    await listPage.clickMenuEntryForCalcType(8); // Lois d'ouvrages
-    await browser.sleep(500);
-
-    // otherwise clickCloneCalcButton() fails with "Element is not clickable at point"
-    // await browser.executeScript("window.scrollTo(0, 0);");
-    await calcPage.clickCloneCalcButton();
-    await browser.sleep(500);
 
-    // check existence of the cloned module
-    expect(await navbar.getAllCalculatorTabs().count()).toBe(2);
+    // @TODO check linked value (see above)
   });
 });
diff --git a/e2e/compute-reset-chained-links.e2e-spec.ts b/e2e/compute-reset-chained-links.e2e-spec.ts
index 6399c764c..dc72e70c0 100644
--- a/e2e/compute-reset-chained-links.e2e-spec.ts
+++ b/e2e/compute-reset-chained-links.e2e-spec.ts
@@ -36,7 +36,7 @@ describe("ngHyd − compute then reset chained results", () => {
     await browser.sleep(500);
     expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
 
-    // 1. get top most module
+    // 1. get down-most module
     await navbar.clickCalculatorTabForUid("dWs5bm");
 
     // check that "compute" button is active
@@ -46,7 +46,7 @@ describe("ngHyd − compute then reset chained results", () => {
     // click "compute" button
     await calcButton.click();
 
-    // only top-most module should have results
+    // only down-most module should have results
     let hasResults = await calcPage.hasResults();
     // other two should not
     await navbar.clickCalculatorTabForUid("OGFzOH");
@@ -56,7 +56,7 @@ describe("ngHyd − compute then reset chained results", () => {
     hasResults = await calcPage.hasResults();
     expect(hasResults).toBe(false);
 
-    // 2. get bottom-most module
+    // 2. get up-most module
     await navbar.clickCalculatorTabForUid("OGFzOH");
 
     // modify any input (for ex. "Ks")
@@ -82,7 +82,7 @@ describe("ngHyd − compute then reset chained results", () => {
     await browser.sleep(500);
     expect(await navbar.getAllCalculatorTabs().count()).toBe(3);
 
-    // 1. get top most module (PAB Dimensions)
+    // 1. get down-most module (PAB Dimensions)
     await navbar.clickCalculatorTabForUid("bGZqcz");
 
     // check that "compute" button is active
@@ -99,7 +99,7 @@ describe("ngHyd − compute then reset chained results", () => {
       expect(hasResults).toBe(true);
     }
 
-    // 2. get bottom-most module (Macro-rugo)
+    // 2. get up-most module (Macro-rugo)
     await navbar.clickCalculatorTabForUid("dnRiY2");
 
     // modify any input (for ex. "Ks")
diff --git a/e2e/link-parallel-devices.e2e-spec.ts b/e2e/link-parallel-devices.e2e-spec.ts
new file mode 100644
index 000000000..f5c73aa4a
--- /dev/null
+++ b/e2e/link-parallel-devices.e2e-spec.ts
@@ -0,0 +1,47 @@
+import { AppPage } from "./app.po";
+import { ListPage } from "./list.po";
+import { CalculatorPage } from "./calculator.po";
+
+/**
+ * Load a session containing 4 calculators, having multiple linked parameters
+ * from one to another
+ * @TODO les valeurs des Select sont comparées au français, pas très générique :/
+ */
+describe("ngHyd − load session with multiple linked parameters − ", () => {
+  let startPage: AppPage;
+  let listPage: ListPage;
+  let calcPage: CalculatorPage;
+
+  function init() {
+      startPage = new AppPage();
+      calcPage = new CalculatorPage();
+      listPage = new ListPage();
+  }
+  beforeEach(init);
+
+  it("when creating parallel structures, devices should be linkable to one another", async () => {
+
+    // 1. check Lois d'ouvrages
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(8);
+    await calcPage.getAddStructureButton().click();
+    const nb1 = await calcPage.getAllLinkButtons().count();
+    expect(nb1).toBe(8); // link buttons on children but not on parent
+
+    // 2. check Passe à bassin: Cloisons
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(10);
+    await calcPage.getAddStructureButton().click();
+    const nb2 = await calcPage.getAllLinkButtons().count();
+    expect(nb2).toBe(4); // link buttons on children but not on parent
+
+
+    // 3. check Lois de déversoirs dénoyés
+    await startPage.navigateTo();
+    await listPage.clickMenuEntryForCalcType(9);
+    await calcPage.getAddStructureButton().click();
+    const nb3 = await calcPage.getAllLinkButtons().count();
+    expect(nb3).toBe(6); // link buttons on children but not on parent
+
+  });
+});
diff --git a/e2e/load-linked-params.e2e-spec.ts b/e2e/load-linked-params.e2e-spec.ts
index bac4ab8ee..2b67e12b6 100644
--- a/e2e/load-linked-params.e2e-spec.ts
+++ b/e2e/load-linked-params.e2e-spec.ts
@@ -97,30 +97,4 @@ describe("ngHyd − load session with multiple linked parameters − ", () => {
     expect(lo_brv).toEqual("LargeurBerge (Sec. param.)");
   });
 
-  it("when creating parallel structures, devices should be linkabke to one another", async () => {
-
-    // 1. check Lois d'ouvrages
-    await startPage.navigateTo();
-    await listPage.clickMenuEntryForCalcType(8);
-    await calcPage.getAddStructureButton().click();
-    const nb1 = await calcPage.getAllLinkButtons().count();
-    expect(nb1).toBe(8); // link buttons on children but not on parent
-
-    // 2. check Passe à bassin: Cloisons
-    await startPage.navigateTo();
-    await listPage.clickMenuEntryForCalcType(10);
-    await calcPage.getAddStructureButton().click();
-    const nb2 = await calcPage.getAllLinkButtons().count();
-    expect(nb2).toBe(4); // link buttons on children but not on parent
-
-
-    // 3. check Lois de déversoirs dénoyés
-    await startPage.navigateTo();
-    await listPage.clickMenuEntryForCalcType(9);
-    await calcPage.getAddStructureButton().click();
-    const nb3 = await calcPage.getAllLinkButtons().count();
-    expect(nb3).toBe(6); // link buttons on children but not on parent
-
-  });
-
 });
diff --git a/src/app/components/base-param-input/base-param-input.component.ts b/src/app/components/base-param-input/base-param-input.component.ts
index 9b69dd05e..4c31d115f 100644
--- a/src/app/components/base-param-input/base-param-input.component.ts
+++ b/src/app/components/base-param-input/base-param-input.component.ts
@@ -34,7 +34,7 @@ export class NgBaseParam extends Observable {
     }
 
     public checkValue(val: number) {
-        return this._param.checkValue(val);
+        return this._param.checkValueAgainstDomain(val);
     }
 
     public checkMin(min: number): boolean {
@@ -71,7 +71,7 @@ export class NgBaseParam extends Observable {
             msg = ServiceFactory.instance.i18nService.localizeText("ERROR_PARAM_NULL");
         } else {
             try {
-                this._param.checkValue(v);
+                this._param.checkValueAgainstDomain(v);
                 valid = true;
             } catch (e) {
                 if (e instanceof Message) {
diff --git a/src/app/components/generic-calculator/calc-name.component.ts b/src/app/components/generic-calculator/calc-name.component.ts
index 309526b77..b4c109b6f 100644
--- a/src/app/components/generic-calculator/calc-name.component.ts
+++ b/src/app/components/generic-calculator/calc-name.component.ts
@@ -50,7 +50,7 @@ export class CalculatorNameComponent extends GenericInputComponent {
         let msg: string;
 
         if (ui === undefined || ui.length < 1) {
-            msg = "Veuillez entrer un nom tralala";
+            msg = "Veuillez entrer un nom";
         } else {
             valid = true;
         }
diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts
index 2635777d6..31842b969 100644
--- a/src/app/components/generic-calculator/calculator.component.ts
+++ b/src/app/components/generic-calculator/calculator.component.ts
@@ -204,7 +204,6 @@ export class GenericCalculatorComponent extends BaseComponent implements OnInit,
             this._formulaire.onRadioClick(this._pendingRadioClickInfo);
             this._pendingRadioClickInfo = undefined;
         }
-        // @TODO call this._isUIValid here ?
     }
 
     public onCloseForm() {
diff --git a/src/app/components/param-field-line/param-field-line.component.html b/src/app/components/param-field-line/param-field-line.component.html
index 973ec8c81..acea333d2 100644
--- a/src/app/components/param-field-line/param-field-line.component.html
+++ b/src/app/components/param-field-line/param-field-line.component.html
@@ -20,13 +20,13 @@
         <mat-button-toggle-group *ngIf="hasRadioFix() || hasRadioVar() || hasRadioCal() || hasRadioLink()">
 
             <mat-button-toggle class="radio_fix" value="radio_fix" 
-                (click)="onRadioClick('fix')" [checked]="isRadioFixChecked">
+                (click)="onRadioClick('fix')" [checked]="isRadioFixChecked" [disabled]="! canExitCalcMode()">
                 <span fxHide.xxs>{{ uitextParamFixe }}</span>
                 <span fxHide.gt-xxs>F</span>
             </mat-button-toggle>
         
             <mat-button-toggle class="radio_var" value="radio_var" *ngIf="hasRadioVar()"
-                (click)="onRadioClick('var')" [checked]="isRadioVarChecked">
+                (click)="onRadioClick('var')" [checked]="isRadioVarChecked" [disabled]="! canExitCalcMode()">
                 <span fxHide.xxs>{{ uitextParamVarier }}</span>
                 <span fxHide.gt-xxs>V</span>
             </mat-button-toggle>
@@ -38,7 +38,7 @@
             </mat-button-toggle>
         
             <mat-button-toggle class="radio_link" value="radio_link" *ngIf="hasRadioLink()"
-                (click)="onRadioClick('link')" [checked]="isRadioLinkChecked">
+                (click)="onRadioClick('link')" [checked]="isRadioLinkChecked" [disabled]="! canExitCalcMode()">
                 <span fxHide.xxs>{{ uitextParamLie }}</span>
                 <span fxHide.gt-xxs>L</span>
             </mat-button-toggle>
diff --git a/src/app/components/param-field-line/param-field-line.component.ts b/src/app/components/param-field-line/param-field-line.component.ts
index 47a79d393..06a78419f 100644
--- a/src/app/components/param-field-line/param-field-line.component.ts
+++ b/src/app/components/param-field-line/param-field-line.component.ts
@@ -4,7 +4,7 @@ import { I18nService } from "../../services/internationalisation/internationalis
 import { NgParameter, ParamRadioConfig } from "../../formulaire/ngparam";
 import { NgParamInputComponent } from "../ngparam-input/ngparam-input.component";
 import { ServiceFactory } from "../../services/service-factory";
-import { ParamValueMode, CalculatorType, ParallelStructure } from "jalhyd";
+import { ParamValueMode, ParallelStructure, Nub } from "jalhyd";
 import { FormulaireService } from "../../services/formulaire/formulaire.service";
 import { ParamLinkComponent } from "../param-link/param-link.component";
 import { ParamComputedComponent } from "../param-computed/param-computed.component";
@@ -214,14 +214,28 @@ export class ParamFieldLineComponent implements OnChanges {
         return false;
     }
 
-    private onRadioClick(option: string) {
+    /**
+     * Returns true if the current parameter is in CALC mode but no SINGLE
+     * parameter is available to take its place
+     */
+    public canExitCalcMode() {
+        let ret = true;
+        if (this.param.paramDefinition.isCalculated) {
+            const nub = this.param.paramDefinition.parentNub;
+            const p = nub.findFirstSingleParameter(this.param.paramDefinition);
+            ret = (p !== undefined);
+        }
+        return ret;
+    }
+
+    public onRadioClick(option: string) {
         const oldValue = this.param.valueMode;
         switch (option) {
             case "fix":
                 this.param.valueMode = ParamValueMode.SINGLE;
                 // reset the value to avoid "undefined" after exiting CALC or LINK mode
                 // @TODO not always necessary; find out why
-                this.param.setValue(this, this.param.paramDefinition.paramValues.singleValue);
+                this.param.setValue(this, this.param.paramDefinition.singleValue);
                 break;
 
             case "var":
@@ -237,7 +251,7 @@ export class ParamFieldLineComponent implements OnChanges {
                 break;
 
             case "cal":
-                this.param.valueMode = ParamValueMode.CALCUL;
+                this.param.setCalculated(); // sets mode to CALCUL and more
                 break;
 
             case "link":
@@ -278,7 +292,7 @@ export class ParamFieldLineComponent implements OnChanges {
     /**
      * réception d'un événement de validité de ParamValuesComponent
      */
-    private onParamValuesValid(event: boolean) {
+    public onParamValuesValid(event: boolean) {
         this._isRangeValid = event;
         this.emitValidity();
     }
diff --git a/src/app/components/param-link/param-link.component.ts b/src/app/components/param-link/param-link.component.ts
index 4d3efa4e0..7c3e8887e 100644
--- a/src/app/components/param-link/param-link.component.ts
+++ b/src/app/components/param-link/param-link.component.ts
@@ -172,7 +172,16 @@ export class ParamLinkComponent implements OnChanges, Observer, OnDestroy {
      * événement de changement de la valeur du modèle
      */
     private emitModelChanged() {
-        this.change.emit({ "action": "model", "value": this.currentLinkedParam.getValue() });
+        let value;
+        try {
+            value = this.currentLinkedParam.getValue();
+        } catch (e) {
+            // console.log("undefined target value (pending calculation)");
+        }
+        this.change.emit({
+            "action": "model",
+            "value": value
+        });
     }
 
     public updateParamList() {
diff --git a/src/app/components/remous-results/remous-results.component.ts b/src/app/components/remous-results/remous-results.component.ts
index d62a814e0..a59ea0c96 100644
--- a/src/app/components/remous-results/remous-results.component.ts
+++ b/src/app/components/remous-results/remous-results.component.ts
@@ -350,7 +350,7 @@ export class RemousResultsComponent implements DoCheck {
     }
 
     private get abscisseIterator(): INumberIterator {
-        return this._remousResults.varResults.variatedParameter.paramDefinition.paramValues.getValuesIterator();
+        return this._remousResults.varResults.variatedParameter.paramDefinition.valuesIterator;
     }
 
     private connectRessaut(lineFlu: LineData, lineTor: LineData) {
diff --git a/src/app/formulaire/definition/concrete/form-base.ts b/src/app/formulaire/definition/concrete/form-base.ts
index edc9f93c2..f1afa17ea 100644
--- a/src/app/formulaire/definition/concrete/form-base.ts
+++ b/src/app/formulaire/definition/concrete/form-base.ts
@@ -26,14 +26,6 @@ export class FormulaireBase extends FormulaireDefinition {
         this._formParamCalc.parseOptions(json);
     }
 
-    /**
-     * gestion du clic sur les radios "paramètre fixé, à varier, à calculer"
-     */
-    public onRadioClick(info: string) {
-        super.onRadioClick(info);
-        this._formParamCalc.onRadioClick(info);
-    }
-
     /**
      * Resets the form results, the results panel on screen, the model
      * results, and does the same for all depending modules
diff --git a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
index 503bdaba7..3d846e31d 100644
--- a/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
+++ b/src/app/formulaire/definition/concrete/form-regime-uniforme.ts
@@ -41,7 +41,6 @@ export class FormulaireRegimeUniforme extends FormulaireBase implements Observer
             this.replaceCurrentNub(sender.properties);
             for (const fs of this.allFieldsets) {
                 fs.setNub(this._currentNub);
-                this._formParamCalc.setDefault();
                 // treat the fieldset as new to re-subscribe to Nub properties change events
                 this.afterParseFieldset(fs);
             }
diff --git a/src/app/formulaire/definition/form-compute-fixedvar.ts b/src/app/formulaire/definition/form-compute-fixedvar.ts
index 873812579..205e9e9dc 100644
--- a/src/app/formulaire/definition/form-compute-fixedvar.ts
+++ b/src/app/formulaire/definition/form-compute-fixedvar.ts
@@ -46,7 +46,7 @@ export class FormComputeFixedVar extends FormCompute {
         const nub: Nub = this._formBase.currentNub;
         const computedParam: NgParameter = this.getComputedParameter();
 
-        const res: Result = this.runNubCalc(nub, computedParam);
+        const res: Result = this.runNubCalc(nub);
 
         this.reaffectResultComponents();
     }
diff --git a/src/app/formulaire/definition/form-compute-parallel-structures.ts b/src/app/formulaire/definition/form-compute-parallel-structures.ts
index 580a52f08..8f169e5e3 100644
--- a/src/app/formulaire/definition/form-compute-parallel-structures.ts
+++ b/src/app/formulaire/definition/form-compute-parallel-structures.ts
@@ -1,4 +1,4 @@
-import { ComputeNode, ParallelStructure, Structure } from "jalhyd";
+import { ComputeNode, ParallelStructure, Structure, ParamDefinition } from "jalhyd";
 
 import { FormComputeFixedVar } from "./form-compute-fixedvar";
 import { FormResultFixedVar } from "./form-result-fixedvar";
@@ -41,8 +41,8 @@ export class FormComputeParallelStructures extends FormComputeFixedVar {
      * construit un identifiant de type { uid: "abcdef", symbol: "X" }
      * avec "abcdef" l'index de l'ouvrage et "X" son paramètre
      */
-    protected getParameterRefid(p: NgParameter): any {
-        const nub = p.paramDefinition.parentComputeNode;
+    protected getParameterRefid(p: ParamDefinition): any {
+        const nub = p.parentComputeNode;
         if (nub instanceof Structure) {
             return {
                 uid: nub.uid,
diff --git a/src/app/formulaire/definition/form-compute-section-parametree.ts b/src/app/formulaire/definition/form-compute-section-parametree.ts
index 068dce9d2..4c78233a0 100644
--- a/src/app/formulaire/definition/form-compute-section-parametree.ts
+++ b/src/app/formulaire/definition/form-compute-section-parametree.ts
@@ -51,7 +51,7 @@ export class FormComputeSectionParametree extends FormCompute {
         const computedParam: NgParameter = this.createParameter(computedParamInfo.symbol, this._formBase);
         this._varResults.calculatedParameter = computedParam;
 
-        this._varResults.result = this.runNubCalc(sectNub, computedParam);
+        this._varResults.result = this.runNubCalc(sectNub);
         this._varResults.update(false);
     }
 
diff --git a/src/app/formulaire/definition/form-compute.ts b/src/app/formulaire/definition/form-compute.ts
index a9fb302a1..5f077f0c2 100644
--- a/src/app/formulaire/definition/form-compute.ts
+++ b/src/app/formulaire/definition/form-compute.ts
@@ -1,4 +1,4 @@
-import { Nub, Result, ParamDomainValue, Observer } from "jalhyd";
+import { Nub, Result, ParamDomainValue, Observer, ParamDefinition } from "jalhyd";
 
 import { FormResult } from "./form-result";
 import { FormulaireDefinition } from "./form-definition";
@@ -21,7 +21,7 @@ export abstract class FormCompute implements Observer {
      * retourne un identifiant du paramètre dans le formulaire
      * surchargé dans le cas des ouvrages //
      */
-    protected getParameterRefid(p: NgParameter) {
+    protected getParameterRefid(p: ParamDefinition) {
         return p.symbol;
     }
 
@@ -36,11 +36,17 @@ export abstract class FormCompute implements Observer {
      * Lance le calcul d'un paramètre en déterminant une valeur initiale.
      * Si nécessaire déclenche un calcul en chaîne des modules en amont.
      */
-    protected runNubCalc(nub: Nub, computedParam: NgParameter): Result {
+    protected runNubCalc(nub: Nub, computedParam?: ParamDefinition): Result {
         let init: number;
+
+        // by default, use Nub's calculatedParam
+        if (computedParam === undefined) {
+            computedParam = nub.calculatedParam;
+        }
+
         // require chain computation; redundant with Nub.CalcSerie but required
         // to get initial value here...
-        const computedParamValue = computedParam.getValue(true);
+        const computedParamValue = computedParam.getValue();
 
         switch (computedParam.domain.domain) {
             case ParamDomainValue.ANY:
diff --git a/src/app/formulaire/definition/form-def-fixedvar.ts b/src/app/formulaire/definition/form-def-fixedvar.ts
index 469bda41b..efad7a577 100644
--- a/src/app/formulaire/definition/form-def-fixedvar.ts
+++ b/src/app/formulaire/definition/form-def-fixedvar.ts
@@ -1,166 +1,14 @@
-import { ParamValueMode } from "jalhyd";
-
-import { ParamRadioConfig, NgParameter } from "../ngparam";
 import { FormulaireDefinition } from "./form-definition";
 
 /**
  * gestion des formulaires avec "paramètre fixé" et "paramètre à varier"
  */
 export class FormDefFixedVar {
+
     protected _formBase: FormulaireDefinition;
 
     constructor(base: FormulaireDefinition) {
         this._formBase = base;
     }
 
-    /**
-     * remet les radios de tous les paramètres à FIX sauf "me" et ceux (celui) à l'état "except"
-     */
-    protected resetOtherRadio(me: NgParameter, except?: ParamRadioConfig) {
-        for (const p of this._formBase.allFormElements) {
-            if (p instanceof NgParameter) {
-                if (p !== me && p.radioState !== except
-                    && p.radioState !== ParamRadioConfig.LINK
-                    && p.radioConfig !== ParamRadioConfig.FIX) {
-
-                    p.valueMode = ParamValueMode.SINGLE;
-                }
-            }
-        }
-    }
-
-    /**
-     * gère un changement de mode pour un paramètre
-     * règles :
-     *   - 1 seul paramètre CAL à la fois
-     *   - 1 seul paramètre multivalué (VAR) à la fois (directement ou par liaison)
-     *   - plusieurs paramètres FIX à la fois possible
-     *   - plusieurs paramètres LINK à la fois possible si les 2 1ères règles sont respectées
-     *
-     * analyse :
-     * ancien état    nouvel état    action(s)
-     * FIX            VAR            action1
-     * FIX            CAL            action2
-     * FIX            LINK           si paramètre lié FIX : aucune
-     *                               si paramètre lié VAR : action1
-     *                               si paramètre lié CAL : si valeur unique : aucune
-     *                                                      si valeur multiple : action1
-     *                               si paramètre lié LINK : recommencer ce cas avec le paramètre lié
-     *
-     * VAR            FIX            aucune
-     * VAR            CAL            action2
-     * VAR            LINK           si paramètre lié FIX : aucune
-     *                               si paramètre lié VAR : aucune
-     *                               si paramètre lié CAL : si valeur unique : aucune
-     *                                                      si valeur multiple : aucune
-     *                               si paramètre lié LINK : recommencer ce cas avec le paramètre lié
-     *
-     * CAL            FIX            action5
-     * CAL            VAR            action3 + action5
-     * CAL            LINK           si paramètre lié FIX : aucune
-     *                               si paramètre lié VAR : action3 + action4|action5
-     *                               si paramètre lié CAL : action3 + action4|action5
-     *                               si paramètre lié LINK : recommencer ce cas avec le paramètre lié
-     *
-     * action1 : reset (à FIX) de tous les autres paramètres que celui modifié sauf celui à CAL
-     * action2 : reset (à FIX) de tous les autres paramètres que celui modifié sauf celui/ceux à VAR
-     * action3 : reset (à FIX) de tous les autres paramètres que celui modifié
-     * action4 : mettre le paramètre désigné par la conf comme "par défault" à CAL
-     * action5 : mettre le 1er paramètre du module de calcul à CAL
-     */
-    protected processRadioStateChange(sourceParam: NgParameter, oldState: ParamValueMode) {
-        switch (oldState) {
-            case ParamValueMode.SINGLE:  // ancien état
-                switch (sourceParam.valueMode) {
-                    case ParamValueMode.MINMAX:  // nouvel état
-                    case ParamValueMode.LISTE:
-                        this.resetOtherRadio(sourceParam, ParamRadioConfig.CAL);
-                        break;
-
-                    case ParamValueMode.CALCUL:  // nouvel état
-                        this.resetOtherRadio(sourceParam, ParamRadioConfig.VAR);
-                        break;
-
-                    case ParamValueMode.LINK:  // nouvel état
-                        if (sourceParam.paramDefinition.isReferenceDefined() && sourceParam.paramDefinition.hasMultipleValues) {
-                            this.resetOtherRadio(sourceParam, ParamRadioConfig.CAL);
-                        } // @TODO vérifier tous les cas problématiques (liens en chaîne ?)
-                        break;
-                }
-                break;
-
-            case ParamValueMode.LISTE:  // ancien état
-            case ParamValueMode.MINMAX:
-                switch (sourceParam.valueMode) {
-                    case ParamValueMode.CALCUL:  // nouvel état
-                        this.resetOtherRadio(sourceParam, ParamRadioConfig.VAR);
-                        break;
-
-                    case ParamValueMode.LINK:  // nouvel état
-                        // mode du paramètre référencé
-                        /* const refParamValues = sourceParam.paramDefinition.referencedParamValues;
-                        if (refParamValues.valueMode === ParamValueMode.LINK) {
-                            throw new Error(`références de paramètre en chaîne non pris en charge`);
-                        } */
-                        // @TODO vérifier tous les cas problématiques (liens en chaîne ?)
-                        break;
-                }
-                break;
-
-            case ParamValueMode.LINK:  // ancien état
-                switch (sourceParam.valueMode) {
-                    case ParamValueMode.MINMAX:  // nouvel état
-                    case ParamValueMode.LISTE:
-                        this.resetOtherRadio(sourceParam, ParamRadioConfig.CAL);
-                        break;
-
-                    case ParamValueMode.CALCUL:  // nouvel état
-                        this.resetOtherRadio(sourceParam, ParamRadioConfig.VAR);
-                        break;
-                }
-                break;
-        }
-    }
-
-    /**
-     * modifie les boutons radio "fix", "var", "cal" de tous les paramètres
-     * en fonction de la modification de l'état d'un des paramètres
-     * @param uid id numérique unique du paramètre source
-     * @param option nouvel état "fix", "var" ou "cal" du paramètre source
-     */
-    protected resetRadiosAndResults(sourceParam: NgParameter, oldState: ParamValueMode) {
-        this.processRadioStateChange(sourceParam, oldState);
-
-        // on vérifie qu'il y a au moins un paramètre "à calculer" et sinon, on prend le 1er qui est à "fixé"
-        if (this._formBase.getDisplayedParamFromState(ParamRadioConfig.CAL) === undefined) {
-            let newCal: NgParameter;
-
-            for (const p of this._formBase.allFormElements) {
-                if (p instanceof NgParameter) {
-                    // change all radio button groups except the one that sent the event
-                    if (p.radioConfig === ParamRadioConfig.CAL && p.radioState === ParamRadioConfig.FIX && p !== sourceParam) {
-                        newCal = p;
-                        break;
-                    }
-                }
-                if (newCal) {
-                    break;
-                }
-            }
-            // if the current calculated parameter was set to another mode, set a new param
-            // to calculated mode (there must always be exactly one)
-            if (newCal) {
-                newCal.valueMode = ParamValueMode.CALCUL;
-            }
-        }
-    }
-
-    /**
-     * gestion des événements clic sur les radios
-     */
-    public onRadioClick(info: any) {
-        const param: NgParameter = info.param; // paramètre source de l'événement radio
-        const old: ParamValueMode = info.oldValueMode; // ancien état (radio)
-        this.resetRadiosAndResults(param, old);
-    }
 }
diff --git a/src/app/formulaire/definition/form-def-paramcalc.ts b/src/app/formulaire/definition/form-def-paramcalc.ts
index 230a3257c..c16a0397e 100644
--- a/src/app/formulaire/definition/form-def-paramcalc.ts
+++ b/src/app/formulaire/definition/form-def-paramcalc.ts
@@ -1,6 +1,3 @@
-import { ParamValueMode, ParamDefinition } from "jalhyd";
-
-import { NgParameter } from "../ngparam";
 import { FormulaireDefinition } from "./form-definition";
 import { FormDefFixedVar } from "./form-def-fixedvar";
 
@@ -8,97 +5,12 @@ import { FormDefFixedVar } from "./form-def-fixedvar";
  * gestion des formulaires avec "paramètre à calculer" (conduite distributrice, Lechapt-Calmon, régime uniforme, passes à bassin)
  */
 export class FormDefParamToCalculate extends FormDefFixedVar {
-    /**
-     * symbole du paramètre à calculer par défaut (cf config "idCal")
-     */
-    private _defaultCalculatedParam: string;
 
     constructor(base: FormulaireDefinition) {
         super(base);
     }
 
     public parseOptions(json: {}) {
-        this._defaultCalculatedParam = undefined;
-        // browse config file to find "options" chapter
-        for (const k in json) {
-            const o = json[k];
-            if (o.type === "options") {
-                // id du paramètre à calculer par défaut
-                this._defaultCalculatedParam = o["idCal"];
-                // this._formBase
-                if (this._defaultCalculatedParam && ! this.findCalculatedParam()) {
-                    const p = this.setDefault();
-                    p.isDefault = true;
-                }
-            }
-        }
-    }
-
-    /**
-     * Find the parameter that is set to CALC mode
-     */
-    private findCalculatedParam() {
-        for (const p of this._formBase.currentNub.parameterIterator) {
-            if (p.valueMode === ParamValueMode.CALCUL) {
-                return p;
-            }
-        }
-    }
-
-    /**
-     * met le paramètre par défaut à CAL sauf si c'est "except"
-     * @param except paramètre à ne pas remettre à CAL
-     */
-    public setDefault(except?: NgParameter): NgParameter {
-        const defaultParamCal: NgParameter = this._formBase.getParamFromSymbol(this._defaultCalculatedParam);
-        if (except === undefined || defaultParamCal.uid !== except.uid) {
-            defaultParamCal.valueMode = ParamValueMode.CALCUL;
-        }
-        return defaultParamCal;
     }
 
-    /**
-     * @see FormDefFixedVar.processRadioStateChange pour l'analyse
-     */
-    protected processRadioStateChange(sourceParam: NgParameter, oldState: ParamValueMode) {
-        super.processRadioStateChange(sourceParam, oldState);
-
-        switch (oldState) {
-            case ParamValueMode.CALCUL:  // ancien état
-                switch (sourceParam.valueMode) {
-                    case ParamValueMode.SINGLE:  // nouvel état
-                        this.setDefault(sourceParam);
-                        break;
-
-                    case ParamValueMode.MINMAX:  // nouvel état
-                    case ParamValueMode.LISTE:
-                        super.resetOtherRadio(sourceParam);
-                        this.setDefault(sourceParam);
-                        break;
-
-                    case ParamValueMode.LINK:  // nouvel état
-                        if (sourceParam.paramDefinition.hasMultipleValues) {
-                            super.resetOtherRadio(sourceParam);
-                            this.setDefault(sourceParam);
-                        } else {
-                            // mode du paramètre référencé
-                            const refValue = sourceParam.paramDefinition.referencedValue;
-                            if (refValue && refValue.isParameter()) {
-                                switch ((refValue.element as ParamDefinition).valueMode) {
-                                    case ParamValueMode.MINMAX:
-                                    case ParamValueMode.LISTE:
-                                    case ParamValueMode.CALCUL:
-                                        super.resetOtherRadio(sourceParam);
-                                        this.setDefault(sourceParam);
-                                        break;
-
-                                    case ParamValueMode.LINK:
-                                        throw new Error(`références de paramètre en chaîne non pris en charge`); // cas à traiter
-                                }
-                            }
-                        }
-                        break;
-                }
-        }
-    }
 }
diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts
index be1369a87..0c43a6bf9 100644
--- a/src/app/formulaire/definition/form-definition.ts
+++ b/src/app/formulaire/definition/form-definition.ts
@@ -438,7 +438,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs
      * gestion d'un clic sur les radios
      */
     public onRadioClick(info: any) {
-        // if mdoe changed, reset form results
+        // if mode changed, reset form results
         if (info.oldValueMode !== info.param.valueMode) {
             this.reset();
         }
diff --git a/src/app/formulaire/definition/form-result-fixedvar.ts b/src/app/formulaire/definition/form-result-fixedvar.ts
index 291a6373a..be7137587 100644
--- a/src/app/formulaire/definition/form-result-fixedvar.ts
+++ b/src/app/formulaire/definition/form-result-fixedvar.ts
@@ -1,5 +1,3 @@
-import { ResultElement, cLog, ParamValueMode } from "jalhyd";
-
 import { FixedResults } from "../../results/fixed-results";
 import { GraphType, VarResults } from "../../results/var-results";
 import { ParamRadioConfig, NgParameter } from "../ngparam";
diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts
index 9d9ce7c9c..3f9f58711 100644
--- a/src/app/formulaire/ngparam.ts
+++ b/src/app/formulaire/ngparam.ts
@@ -1,5 +1,5 @@
 import { Interval, ParamDefinition, ParamDomain, ParamValueMode, INumberIterator,
-    Nub, Observer, asObservable, ParamCalculability, LinkedValue } from "jalhyd";
+    Observer, asObservable, ParamCalculability, LinkedValue } from "jalhyd";
 
 import { sprintf } from "sprintf-js";
 
@@ -11,24 +11,16 @@ import { ServiceFactory } from "../services/service-factory";
 import { FormulaireNode } from "./formulaire-node";
 
 export enum ParamRadioConfig {
-    /**
-     * pas de radio, paramètre modifiable à la main uniquement
-     */
+    /** pas de radio, paramètre modifiable à la main uniquement */
     FIX,
 
-    /**
-     * boutons radio "paramètre fixé" et "paramètre à varier"
-     */
+    /** boutons radio "paramètre fixé" et "paramètre à varier" */
     VAR,
 
-    /**
-     * boutons radio "paramètre fixé", "paramètre à varier" et "paramètre à calculer"
-     */
+    /** boutons radio "paramètre fixé", "paramètre à varier" et "paramètre à calculer" */
     CAL,
 
-    /**
-     * boutons radio "paramètre fixé", "paramètre à varier" et "paramètre à calculer", "paramètre lié"
-     */
+    /** boutons radio "paramètre fixé", "paramètre à varier" et "paramètre à calculer", "paramètre lié" */
     LINK
 }
 
@@ -37,17 +29,13 @@ export enum ParamRadioConfig {
  */
 export class NgParameter extends InputField implements Observer {
 
-    constructor(private _paramDef: ParamDefinition, parent: FormulaireNode) {
-        super(parent);
-    }
     public unit: string;
     public radioConfig: ParamRadioConfig;
 
-    /**
-     * true si ce paramètre est celui par défaut dans un formulaire
-     * (cf. fichier de conf des modules de calcul, objet "options", champ "idCal")
-     */
-    public isDefault = false; // archi bug du langage ! si on relit cette propriété sans l'avoir modifiée entre-temps, elle vaut undefined !
+    constructor(private _paramDef: ParamDefinition, parent: FormulaireNode) {
+        super(parent);
+        this.radioConfig = this.radioState;
+    }
 
     /**
      * Returns a text preview of the current value(s), depending on the value mode
@@ -62,9 +50,9 @@ export class NgParameter extends InputField implements Observer {
                 valuePreview = String(p.getValue().toFixed(nDigits));
                 break;
             case ParamValueMode.MINMAX:
-                let min: any = p.paramValues.min;
-                let max: any = p.paramValues.max;
-                let step: any = p.paramValues.step;
+                let min: any = p.min;
+                let max: any = p.max;
+                let step: any = p.step;
                 if (min) {
                     min = min.toFixed(nDigits);
                 }
@@ -79,7 +67,7 @@ export class NgParameter extends InputField implements Observer {
                 break;
             case ParamValueMode.LISTE:
                 valuePreview = i18n.localizeText("INFO_PARAMFIELD_PARAMVARIER_VALUES");
-                const vals = p.paramValues.valueList || [];
+                const vals = p.valueList || [];
                 valuePreview += " " + vals.slice(0, 5).map((v) => {
                     return v.toFixed(nDigits);
                 }).join("; ") + "…";
@@ -161,10 +149,6 @@ export class NgParameter extends InputField implements Observer {
         this._confId = id;
     }
 
-    private get _paramValues() {
-        return this._paramDef.paramValues;
-    }
-
     public get paramDefinition() {
         return this._paramDef;
     }
@@ -216,23 +200,23 @@ export class NgParameter extends InputField implements Observer {
     }
 
     public get minValue() {
-        return this._paramValues.min;
+        return this._paramDef.min;
     }
 
     public get maxValue() {
-        return this._paramValues.max;
+        return this._paramDef.max;
     }
 
     public get stepRefValue(): Interval {
-        return this._paramValues.stepRefValue;
+        return this._paramDef.stepRefValue;
     }
 
     public get stepValue() {
-        return this._paramValues.step;
+        return this._paramDef.step;
     }
 
     public get valueList() {
-        return this._paramValues.valueList;
+        return this._paramDef.valueList;
     }
 
     public get isValid() {
@@ -274,11 +258,19 @@ export class NgParameter extends InputField implements Observer {
         throw new Error("invalid parameter radio configuration " + s);
     }
 
+    /**
+     * Sets this parameter as the one to be computed
+     */
+    public setCalculated() {
+        this.paramDefinition.setCalculated();
+    }
+
     /**
      * Asks the ParamDefinition for its current value
+     * @TODO replace with singleValue to avoid displaying computation results ?
      */
-    public getValue(triggerChainComputation: boolean = false) {
-        return this._paramDef.getValue(triggerChainComputation);
+    public getValue() {
+        return this._paramDef.getValue();
     }
 
     /**
@@ -307,32 +299,32 @@ export class NgParameter extends InputField implements Observer {
     }
 
     public setMinValue(sender: any, v: number) {
-        const changed = (this._paramValues.min !== v);
-        this._paramValues.min = v;
+        const changed = (this._paramDef.min !== v);
+        this._paramDef.min = v;
         if (changed) {
             this.notifyValueModified(sender);
         }
     }
 
     public setMaxValue(sender: any, v: number) {
-        const changed = (this._paramValues.max !== v);
-        this._paramValues.max = v;
+        const changed = (this._paramDef.max !== v);
+        this._paramDef.max = v;
         if (changed) {
             this.notifyValueModified(sender);
         }
     }
 
     public setStepValue(sender: any, v: number) {
-        const changed = (this._paramValues.step !== v);
-        this._paramValues.step = v;
+        const changed = (this._paramDef.step !== v);
+        this._paramDef.step = v;
         if (changed) {
             this.notifyValueModified(sender);
         }
     }
 
     public setValueList(sender: any, l: number[]) {
-        const changed = (JSON.stringify(this._paramValues.valueList) !== JSON.stringify(l));
-        this._paramValues.valueList = l;
+        const changed = (JSON.stringify(this._paramDef.valueList) !== JSON.stringify(l));
+        this._paramDef.valueList = l;
         if (changed) {
             this.notifyValueModified(sender);
         }
@@ -376,15 +368,22 @@ export class NgParameter extends InputField implements Observer {
                 o.addObserver(this); // pour être prévenu des changements de valeur de l'object référencé
             }
 
+            let value;
+            try {
+                value = this.getValue();
+            } catch (e) {
+                // console.log("undefined target value (pending calculation)");
+            }
+
             this.notifyObservers({
                 "action": "valueLinkChange",
-                "value": this.getValue()
+                "value": value
             });
         }
     }
 
     public checkValue(val: number) {
-        this._paramDef.checkValue(val);
+        this._paramDef.checkValueAgainstDomain(val);
     }
 
     public checkList(l: number[]) {
@@ -415,8 +414,6 @@ export class NgParameter extends InputField implements Observer {
             this.setValue(this, +val);
         }
         this.radioConfig = NgParameter.getRadioConfig(radioConfig);
-        // tslint:disable-next-line:max-line-length
-        this.isDefault = false; // malgré le fait qu'il soit initialisé dans la déclaration de la classe NgParam à false, quand on relit sa valeur, il vaut undefined (merci Microsoft)
     }
 
     public verifiesDependency(d: Dependency): boolean {
-- 
GitLab