diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 4a6b1d4d7c84a92e4065a46daaac74f043aa8559..908e88488f942ca08320d8c7563d30c4f975aae4 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -177,8 +177,6 @@ Dans cet exemple, on définit un seul groupe de champs nommé arbitrairement "fs Le deuxième et dernier bloc contient les options pour ce module: ici uniquement le lien vers la page de documentation pour ce module (`help`). Les options peuvent également contenir : - * `defaultNodeType` : le type de section par défaut du module de calcul, pour les modules contenant une section - * `selectIds` : les identifiants des listes déroulantes * `resultsHelp` : l'aide associée aux résultats (voir ci-dessous) * `calculateDisabled` : pour masquer le bouton Calculer (ex: module Espèce) @@ -279,11 +277,13 @@ case CalculatorType.MacroRugoCompound: Les listes déroulantes sont toujours associées à des **propriétés** du Nub. -En général les valeurs autorisées sont tirées de l'**enum** correspondant, d'après le tableau `Session.enumFromProperty` de JaLHyd. Pour les autres cas, voir les paragraphes "source" et "liste déroulante personnalisée" ci-dessous. +En général les valeurs autorisées sont tirées de l'**enum** correspondant, d'après le tableau `Session.enumFromProperty` de JaLHyd. Pour les autres cas, voir le paragraphe "liste déroulante personnalisée" ci-dessous. #### configuration -Dans le fichier de configuration du module, ajouter la définition des listes déroulantes dans "fields", notamment la propriété associée et la valeur par défaut. Puis dans le bloc de configuration, déclarer les identifiants des listes dans "selectIds". Exemple dans `trigo/config.json` +Dans le fichier de configuration du module, ajouter la définition des listes déroulantes dans "fields", notamment la propriété associée et la valeur par défaut; cette dernière est optionnelle et en son absence, la 1ère valeur de la liste sera sélectionnée. + +Exemple dans `trigo/config.json` (ici le 2ème champ ne spécifie pas de valeur par défaut) : ```json { @@ -299,169 +299,108 @@ Dans le fichier de configuration du module, ajouter la définition des listes d "id": "select_unit", "type": "select", "property": "trigoUnit", - "default": "DEG" }, … ] }, -… -{ - "type": "options", - "selectIds": [ "select_operation", "select_unit" ], - … -} -``` - -**IMPORTANT** : les id doivent être de la forme `select_`_`unmotclesansespacenitirets`_ - -#### source - -Une liste déroulante telle que décrite ci-dessus peut être associée à une **source**, qui détermine quels sont les choix possibles. Ceci est utile lorsque les choix possibles ne proviennent pas d'un `enum`. Pour autant ce n'est pas équivalent à la méthode "liste déroulante personnalisée" décrite au chapitre suivant (ces deux techniques devraient être fusionnées). - -Pour ajouter une source, modifier la méthode `loadEntriesFromSource()` de la classe `SelectField`, dans le fichier `src/app/formulaire/elements/select-field.ts`. - -Exemple pour la source "remous_target" associée à la propriété "varCalc", dans le module CourbeRemous : - -```typescript -switch (source) { - // driven by string[], not enum (easier for variable names) - case "remous_target": - this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); - for (const at of CourbeRemous.availableTargets) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + at, at); - this.addEntry(e); - } - break; ``` -Ici on ajoute des options de type `SelectEntry` à l'aide de la méthode `addEntry()` : une option vide au début, puis une pour chaque élément d'un tableau. - -Puis dans le fichier de configuration du module, déclarer la source : - -```json -{ - "id": "select_target", - "type": "select", - "property": "varCalc", - "source": "remous_target", - … -``` +**IMPORTANT** : les ids doivent être de la forme `select_`_`unmotclesansespacenitirets`_ #### liste déroulante personnalisée -Il est possible de personnaliser complètement le comportement d'une liste déroulante pour des cas plus complexes, en utilisant l'élément de formulaire `SelectCustom`. +Il est possible de personnaliser complètement le comportement d'une liste déroulante pour des cas plus complexes. -De telles listes doivent être déclarées dans la configuration du module en utilisant le type `select_custom`, un identifiant de `source` (identique à celui vu au chapitre précédent, mais attention : il se comporte différemment) et l'identifiant de la liste doit être mentionné dans l'option `customSelectIds` (et non `selectIds`). +De telles listes doivent être déclarées dans la configuration du module en utilisant uniquement le type `select` et un identifiant de liste. Dans ce cas, on ne fournit pas de champ `property` ni `default`. Exemple pour la cible du Solveur : ```json … "fields": [ { + "type": "select", "id": "select_target_nub", - "type": "select_custom", - "source": "solveur_target" }, … ] … -{ - "type": "options", - "customSelectIds": [ "select_target_nub", … ], - … -} ``` -Dans le fichier `src/app/formulaire/elements/select-field-custom.ts`, remplir les méthodes `populate()` (définit les choix possibles) et `initSelectedValue()` (affecte la valeur actuellement définie dans le modèle). - +Il faut ensuite faire 2 choses : +- créer une classe dérivée de `SelectField` et implémenter les méthodes `populate()` (remplissage de la liste) et `initSelectedValue()` (fixation de la valeur par défaut) : -Exemple pour la cible du Solveur : `populate()` ```typescript -case "solveur_target": // Solveur, module cible (à calculer) - // find all Nubs having at least one link to another Nub's result - candidateNubs = - Session.getInstance().getDownstreamNubs().concat( - Session.getInstance().getUpstreamNubsHavingExtraResults() - ).filter( - (element, index, self) => self.findIndex((e) => e.uid === element.uid) === index - ); - for (const cn of candidateNubs) { - const nub = fs.getFormulaireFromId(cn.uid); - if (nub) { - const calc = nub.calculatorName; - let label = calc; - // calculated param - if (cn.calculatedParam !== undefined) { - const varName = fs.expandVariableName(cn.calcType, cn.calculatedParam.symbol); - label += ` / ${varName} (${cn.calculatedParam.symbol})`; - } - this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); - } else { - // silent fail, this Solveur nub was probably loaded before all the candidate nubs are done loading - } + export class SelectFieldSolverTarget extends SelectField { + protected populate() { + … + // une série de addEntry() + … } - break; -``` -Comme au chapitre précédent, il s'agit d'ajouter des options de type `SelectEntry` à l'aide de la méthode `addEntry()`. + protected initSelectedValue() { + // utiliser setValueFromId() avec un id calculé + // ou findAndSetDefaultValue() pour utiliser la 1ère valeur de la liste + } +} +``` -Exemple pour la cible du Solveur : `initSelectedValue()` +- modifier la méthode `newSelectField` de la classe `SelectFieldFactory` pour créer une instance de la classe dérivée en utilisant le champ `id` précisé dans le JSON de configuration : ```typescript -case "solveur_target": // Solveur, module cible (à calculer) - const ntc = (nub as Solveur).nubToCalculate; - if (ntc !== undefined) { - this.setValueFromId(this._entriesBaseId + ntc.uid); + public static newSelectField(json: {}, parent: FormulaireNode): SelectField { + switch (json["id"]) { + … + case "select_target_nub": + return new SelectFieldSolverTarget(parent); + … + } } - break; ``` -Ici il s'agit de choisir la bonne option du sélecteur, en fonction de la valeur courante de la propriété dans le modèle. - -Enfin, ce type de liste déroulante nécessite une classe de formulaire personnalisée, dans laquelle la méthode `update()` doit être enrichie. +Enfin, ce type de liste déroulante peut nécessiter une classe de formulaire personnalisée, dans laquelle la méthode `update()` doit être enrichie. -Exemple dans `src/app/formulaire/definition/form-solveur.ts`, pour la cible du Solveur : +Exemple dans `src/app/formulaire/definition/form-solveur.ts` pour la cible du Solveur : ```typescript public update(sender: IObservable, data: any) { … - if (sender instanceof SelectFieldCustom) { - if (sender.id === "select_target_nub" && data.action === "select") { - // update Solveur property: Nub to calculate - try { - // if searchedParam is set to a value that won't be available anymore - // once nubToCalculate is updated, setPropValue throws an error, but - // nubToCalculate is updated anyway; here, just inhibit the error - this._currentNub.properties.setPropValue("nubToCalculate", data.value.value); - } catch (e) { } - // refresh targetted result selector - const trSel = this.getFormulaireNodeById(this._targettedResultSelectId) as SelectField; - if (trSel) { - (trSel.parent as FieldSet).updateFields(); - // trick to re-set observers - this.completeParse(false); + if (sender instanceof SelectField) { + if (sender.id === "select_target_nub" && data.action === "select") { + // update Solveur property: Nub to calculate + try { + // if searchedParam is set to a value that won't be available anymore + // once nubToCalculate is updated, setPropValue throws an error, but + // nubToCalculate is updated anyway; here, just inhibit the error + this._currentNub.properties.setPropValue("nubToCalculate", data.value.value); + } catch (e) { } + // refresh targetted result selector + const trSel = this.getFormulaireNodeById(this._targettedResultSelectId) as SelectField; + if (trSel) { + (trSel.parent as FieldSet).updateFields(); + // trick to re-set observers + this.completeParse(false); + } + // refresh parameters selector + this.refreshParameterEntries(); } - // refresh parameters selector - this.refreshParameterEntries(); - } … } … } ``` -Ici on écoute l'événement généré par l'objet `SelectFieldCustom`, et on agit en conséquence : affecter la propriété concernée, et rafraîchir un champ dépendant. +Ici on écoute l'événement généré par l'objet dérivé de `SelectField`, et on agit en conséquence : affecter la propriété concernée et rafraîchir un champ dépendant. #### si l'affichage de certains champs dépend du choix dans la liste Les listes dont l'identifiant est déclaré dans le fichier de configuration du module déclencheront, lorsque leur valeur change, un effacement des résultats du module et une mise à jour de tous les "fieldset" du formulaire. -Cette dernière opération permet de vérifier la visibilité de chaque champ du formulaire, et afficher / masquer ceux dont la visibilité a changé. +Cette dernière opération permet de vérifier la visibilité de chaque champ du formulaire et afficher/masquer ceux dont la visibilité a changé. Ainsi, pour rendre la visibilité d'un champ dépendante du choix dans la liste, il faut, **dans le code du Nub dans JaLHyd** : - * écouter le changement de propriété (méthode `update()`, action `propertyChange`) - * selon la nouvelle valeur, ajuster la propriété `.visible` des paramètres concernés + * écouter le changement de propriété (méthode `update()`, action `propertyChange`), + * selon la nouvelle valeur, ajuster la propriété `visible` des paramètres concernés. Il n'y a rien à faire de particulier dans ngHyd. @@ -469,7 +408,7 @@ Ainsi, pour rendre la visibilité d'un champ dépendante du choix dans la liste, Il faut utiliser ou étendre `FormulaireSection`. -Dans la configuration du module, ajouter un sélecteur de section, associé à la propriété "nodeType" : +Dans la configuration du module, ajouter un sélecteur de section associé à la propriété "nodeType" (type de section) : ```json { @@ -484,17 +423,7 @@ Dans la configuration du module, ajouter un sélecteur de section, associé à l } } ``` - -Puis dans les options, déclarer ce sélecteur et ajouter "defaultNodeType" : - -```json -{ - "type": "options", - "defaultNodeType": "SectionRectangle", - "selectIds": [ "select_section" ], - … -} -``` +La section par défaut du formulaire sera celle du sélecteur, que celle ci soit ou non configurée explicitement par le champ `default`. ### si le module agrège des modules enfants @@ -568,27 +497,23 @@ Dans chaque fichier de langue du dossier `src/locale`, ajouter les traductions p Il faut utiliser ou étendre `FormulaireParallelStructure` (ex: Cloisons, Dever…). -Dans la configuration du module, dans le "fieldset_template", ajouter un sélecteur de structure associé à la propriété "structureType" et un sélecteur de loi de débit associé à la propriété "loiDebit", noter les propriétés "calcType" (toujours "Structure" dans ce cas), "defaultStructType" et "defaultLoiDebit" : +Dans la configuration du module, dans le "fieldset_template", ajouter un sélecteur de structure et un sélecteur de loi de débit, ajouter les propriétés "calcType" (toujours "Structure" dans ce cas) et les valeurs par défaut avec "default" dans la définition du select. A noter que les ids des select sont fixés et doivent obligatoirement être "select_structure" et "select_loidebit" : ```json { "id": "fs_ouvrage", "type": "fieldset_template", "calcType": "Structure", - "defaultStructType": "VanneFondRectangulaire", - "defaultLoiDebit": "GateCem88v", "fields": [ { "id": "select_structure", "type": "select", - "property": "structureType", - "source": "device_structure_type" + "default": "VanneFondRectangulaire", }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", + "default": "GateCem88v", "help": { "Orifice_OrificeSubmerged": "structures/orifice_noye.html", "SeuilRectangulaire_WeirVillemonte": "structures/kivi.html", @@ -602,16 +527,6 @@ Dans la configuration du module, dans le "fieldset_template", ajouter un sélect } ``` -Dans les options, déclarer les sélecteurs : - -```json -{ - "type": "options", - "selectIds": [ "select_structure", "select_loidebit" ], - … -} -``` - ### documentation Pour chaque langue, ajouter un fichier .md dans les dossiers `docs/*/calculators`, puis placer ce nouveau fichier dans la hiérarchie de la documentation, en ajoutant son chemin dans les fichiers `mkdocs-*.yml`. diff --git a/e2e/calculate-all-params.e2e-spec.ts b/e2e/calculate-all-params.e2e-spec.ts index 30887028d948a1150d6485a1cdff605008d789c2..39129ac615e92f804560d9bd3db5f34e8d14f98b 100644 --- a/e2e/calculate-all-params.e2e-spec.ts +++ b/e2e/calculate-all-params.e2e-spec.ts @@ -20,6 +20,9 @@ describe("ngHyd − calculate all parameters of all calculators", async () => { calcPage = new CalculatorPage(); prefPage = new PreferencesPage(); navBar = new Navbar(); + + // increase timeout to avoid "Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL" message + jasmine.DEFAULT_TIMEOUT_INTERVAL = 10 * 60 * 1000; // 10 min }); // get calculators list (IDs) @TODO read it from config, but can't import jalhyd here :/ diff --git a/e2e/select-default-value.e2e-spec.ts b/e2e/select-default-value.e2e-spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..9ec1205cdac37a9665052370a8ee95ab8147194a --- /dev/null +++ b/e2e/select-default-value.e2e-spec.ts @@ -0,0 +1,55 @@ +import { browser } from "protractor"; +import { CalculatorPage } from "./calculator.po"; +import { ListPage } from "./list.po"; +import { Navbar } from "./navbar.po"; +import { PreferencesPage } from "./preferences.po"; + +describe("check the select default value - ", () => { + let prefPage: PreferencesPage; + let listPage: ListPage; + let calcPage: CalculatorPage; + let navBar: Navbar; + + beforeAll(() => { + prefPage = new PreferencesPage(); + listPage = new ListPage(); + calcPage = new CalculatorPage(); + navBar = new Navbar(); + }); + + beforeEach(async () => { + // disable evil option "empty fields on module creation" + await prefPage.navigateTo(); + await prefPage.disableEvilEmptyFields(); + await browser.sleep(200); + + await prefPage.changeLanguage(1); // fr + await browser.sleep(200); + }); + + it("in the 'backwater curve' calculator", async () => { + // open backwater curve calculator + await navBar.clickNewCalculatorButton(); + await listPage.clickMenuEntryForCalcType(4); + await browser.sleep(200); + + // in the calculator configuration file, the default resolution method is 'Trapezes'. + // let's check this... + const sel = calcPage.getSelectById("select_resolution"); + const val = await calcPage.getSelectValueText(sel); + expect(val).toBe("Intégration par trapèzes"); + }); + + it("in the 'up/downstream elevations of a reach' calculator", async () => { + // open "up/downstream elevations of a reach" calculator + await navBar.clickNewCalculatorButton(); + await listPage.clickMenuEntryForCalcType(21); + await browser.sleep(200); + + // in the calculator configuration file, the default section method is 'Rectangulaire'. + // let's check this... + const sel = calcPage.getSelectById("select_section"); + const val = await calcPage.getSelectValueText(sel); + expect(val).toBe("Rectangulaire"); + }); +}); diff --git a/jalhyd_branch b/jalhyd_branch index 1f7391f92b6a3792204e07e99f71f643cc35e7e1..d9cf99b698bc5158b6a3112812a1c7d878d56f04 100644 --- a/jalhyd_branch +++ b/jalhyd_branch @@ -1 +1 @@ -master +328-fusionner-les-select-avec-source-et-les-select_custom diff --git a/src/app/calculators/bief/config.json b/src/app/calculators/bief/config.json index 95f2ca549d1b5dc05e6a6b4a750015c93ceca321..4c486031c82b37414774c0f30c853dffad1eb763 100644 --- a/src/app/calculators/bief/config.json +++ b/src/app/calculators/bief/config.json @@ -19,6 +19,7 @@ "id": "select_section", "type": "select", "property": "nodeType", + "default": "SectionRectangle", "help": { "1": "hsl/types_sections.html#section-rectangulaire", "0": "hsl/types_sections.html#section-circulaire", @@ -68,8 +69,6 @@ }, { "type": "options", - "defaultNodeType": "SectionRectangle", - "selectIds": [ "select_section", "select_regime" ], "help": "hsl/cote_amont_aval.html" } -] \ No newline at end of file +] diff --git a/src/app/calculators/cloisons/config.json b/src/app/calculators/cloisons/config.json index 82ccf331ee69f680da4dc26b1dc593fd26d58406..116afc8936b14240751baaeaeed635ee25644cc3 100644 --- a/src/app/calculators/cloisons/config.json +++ b/src/app/calculators/cloisons/config.json @@ -16,20 +16,16 @@ "id": "fs_ouvrage", "type": "fieldset_template", "calcType": "Structure", - "defaultStructType": "SeuilRectangulaire", - "defaultLoiDebit": "WeirSubmergedLarinier", "fields": [ { "id": "select_structure", "type": "select", - "property": "structureType", - "source": "device_structure_type" + "default": "SeuilRectangulaire" }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", + "default": "WeirSubmergedLarinier", "help": { "OrificeSubmerged": "structures/orifice_noye.html", "WeirSubmergedLarinier": "structures/fente_noyee.html", @@ -65,7 +61,6 @@ }, { "type": "options", - "selectIds": [ "select_structure", "select_loidebit" ], "help": "pab/cloisons.html" } ] diff --git a/src/app/calculators/courberemous/config.json b/src/app/calculators/courberemous/config.json index 272de86872224c219f6fe666b9a56243dab70180..d1682af8b8ab5bdec0470f3f45f807410931babd 100644 --- a/src/app/calculators/courberemous/config.json +++ b/src/app/calculators/courberemous/config.json @@ -7,6 +7,7 @@ "id": "select_section", "type": "select", "property": "nodeType", + "default": "SectionRectangle", "help": { "1": "hsl/types_sections.html#section-rectangulaire", "0": "hsl/types_sections.html#section-circulaire", @@ -72,8 +73,6 @@ { "id": "select_target", "type": "select", - "property": "varCalc", - "source": "remous_target", "help": { "B": "hsl/section_parametree.html#largeur-au-miroir-surface-et-perimetre-mouille", "P": "hsl/section_parametree.html#largeur-au-miroir-surface-et-perimetre-mouille", @@ -97,8 +96,6 @@ }, { "type": "options", - "defaultNodeType": "SectionRectangle", - "selectIds": [ "select_resolution", "select_section", "select_target" ], "help": "hsl/courbe_remous.html", "resultsHelp": { "B": "hsl/section_parametree.html#largeur-au-miroir-surface-et-perimetre-mouille", diff --git a/src/app/calculators/dever/config.json b/src/app/calculators/dever/config.json index ce6bea142fe35ea06a1cc374aad7ed5745fa9757..9055348a8df536450c1b6cfcb6bdb87750097f0e 100644 --- a/src/app/calculators/dever/config.json +++ b/src/app/calculators/dever/config.json @@ -14,20 +14,16 @@ "id": "fs_ouvrage", "type": "fieldset_template", "calcType": "Structure", - "defaultStructType": "SeuilRectangulaire", - "defaultLoiDebit": "WeirFree", "fields": [ { "id": "select_structure", "type": "select", - "property": "structureType", - "source": "device_structure_type" + "default": "SeuilRectangulaire" }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", + "default": "WeirFree", "help": { "WeirFree": "structures/seuil_denoye.html", "TriangularWeirFree": "structures/dever_triang.html", @@ -55,7 +51,6 @@ }, { "type": "options", - "selectIds": [ "select_structure", "select_loidebit" ], "help": "structures/dever.html" } ] \ No newline at end of file diff --git a/src/app/calculators/espece/config.json b/src/app/calculators/espece/config.json index 1b6185a5ee7ec30cf63aaf17086edea49597d95b..71a6f42f759511cb75de75d05737b58b83804e5f 100644 --- a/src/app/calculators/espece/config.json +++ b/src/app/calculators/espece/config.json @@ -94,7 +94,6 @@ { "type": "options", "help": "verif/especes_predefinies.html", - "selectIds": [ "select_divingjetsupported" ], "calculateDisabled": true } ] diff --git a/src/app/calculators/grille/config.json b/src/app/calculators/grille/config.json index e964fa9e33b84dc02f1ebe2c33df2761a59a0ce8..b01502d19326776b5a52bdbdba2cb33e10494330 100644 --- a/src/app/calculators/grille/config.json +++ b/src/app/calculators/grille/config.json @@ -96,7 +96,6 @@ }, { "type": "options", - "selectIds": [ "select_gridtype", "select_gridprofile" ], "help": "devalaison/grille.html", "resultsHelp": { "VAPDG": "devalaison/grille.html#vitesse-dapproche-moyenne-pour-le-debit-maximum-turbine-en-soustrayant-la-partie-superieure-eventuellement-obturee", diff --git a/src/app/calculators/lechaptcalmon/config.json b/src/app/calculators/lechaptcalmon/config.json index 431552de8fe835cc1124910f5ac257babf02690b..1b440a6ae98b420fbb98eed5dfc49ef134424cf8 100644 --- a/src/app/calculators/lechaptcalmon/config.json +++ b/src/app/calculators/lechaptcalmon/config.json @@ -2,7 +2,6 @@ { "id": "fs_materiau", "type": "fieldset", - "defaultMaterial": "UnlinedCastIronCoarseConcrete", "fields": [ { "id": "select_material", @@ -28,7 +27,6 @@ }, { "type": "options", - "selectIds": [ "select_material" ], "help": "hyd_en_charge/lechapt-calmon.html" } ] \ No newline at end of file diff --git a/src/app/calculators/macrorugocompound/config.json b/src/app/calculators/macrorugocompound/config.json index 8df7adca30b829076b1b3929bd2548686112fde9..79290dc661b94b9a3a9ed20ba698c467b6f8b728 100644 --- a/src/app/calculators/macrorugocompound/config.json +++ b/src/app/calculators/macrorugocompound/config.json @@ -78,7 +78,6 @@ }, { "type": "options", - "selectIds": [ "select_passtype" ], "help": "pam/macrorugo_complexe.html" } ] diff --git a/src/app/calculators/par/config.json b/src/app/calculators/par/config.json index dc6fcb41e4cf7a6f959f33bd0146576f55373154..c7ba05c46cae67a319ff58a2672c70935268aa85 100644 --- a/src/app/calculators/par/config.json +++ b/src/app/calculators/par/config.json @@ -38,7 +38,6 @@ }, { "type": "options", - "selectIds": [ "select_partype" ], "help": "par/calage.html" } ] diff --git a/src/app/calculators/parallelstructure/config.json b/src/app/calculators/parallelstructure/config.json index 26c3cf9a4414b5e672b7f5dc88a34ac15195da06..873f9a699e170a46420d8da0f969a9792c2ad4b6 100644 --- a/src/app/calculators/parallelstructure/config.json +++ b/src/app/calculators/parallelstructure/config.json @@ -13,20 +13,16 @@ "id": "fs_ouvrage", "type": "fieldset_template", "calcType": "Structure", - "defaultStructType": "SeuilOrificeRectangulaire", - "defaultLoiDebit": "GateCem88v", "fields": [ { "id": "select_structure", "type": "select", - "property": "structureType", - "source": "device_structure_type" + "default": "SeuilOrificeRectangulaire" }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", + "default": "GateCem88v", "help": { "KIVI": "structures/kivi.html", "WeirVillemonte": "structures/kivi.html", @@ -78,7 +74,6 @@ }, { "type": "options", - "selectIds": [ "select_structure", "select_loidebit" ], "help": "structures/lois_ouvrages.html", "resultsHelp": { "ENUM_StructureJetType": "structures/lois_ouvrages.html#type-de-jet" diff --git a/src/app/calculators/parsimulation/config.json b/src/app/calculators/parsimulation/config.json index a5cb2c9de1bd6c7dd5dacad8e69e20e2cd52fc8e..810625bf606e5297964528a3893abf79a9e30b38 100644 --- a/src/app/calculators/parsimulation/config.json +++ b/src/app/calculators/parsimulation/config.json @@ -54,7 +54,6 @@ }, { "type": "options", - "selectIds": [ "select_partype" ], "help": "par/simulation.html" } ] diff --git a/src/app/calculators/prebarrage/config.json b/src/app/calculators/prebarrage/config.json index 7dada6df94c09621a3c55d9ba3475b3e2298a9f6..1aff6ed820e63362d884802486b7cb55637e95f6 100644 --- a/src/app/calculators/prebarrage/config.json +++ b/src/app/calculators/prebarrage/config.json @@ -15,10 +15,6 @@ "Z1", "Z2" ] - }, - { - "type": "options", - "selectIds": [ ] } ] }, @@ -41,10 +37,6 @@ "templates": [ "fs_basin" ] - }, - { - "type": "options", - "selectIds": [ ] } ] }, @@ -56,20 +48,16 @@ "id": "fs_ouvrage", "type": "fieldset_template", "calcType": "Structure", - "defaultStructType": "SeuilRectangulaire", - "defaultLoiDebit": "WeirSubmergedLarinier", "fields": [ { "id": "select_structure", "type": "select", - "property": "structureType", - "source": "device_structure_type" + "default": "SeuilRectangulaire" }, { "id": "select_loidebit", "type": "select", - "property": "loiDebit", - "source": "device_loi_debit", + "default": "WeirSubmergedLarinier", "help": { "KIVI": "structures/kivi.html", "WeirVillemonte": "structures/kivi.html", @@ -171,13 +159,11 @@ "fields": [ { "id": "select_upstream_basin", - "type": "select_custom", - "source": "upstream_basin" + "type": "select" }, { "id": "select_downstream_basin", - "type": "select_custom", - "source": "downstream_basin" + "type": "select" } ] }, @@ -187,17 +173,11 @@ "templates": [ "fs_ouvrage" ] - }, - { - "type": "options", - "selectIds": [ "select_structure", "select_loidebit" ], - "customSelectIds": [ "select_upstream_basin", "select_downstream_basin" ] } ] }, { "type": "options", - "selectIds": [ ], "upstreamSelectId": "select_upstream", "downstreamSelectId": "select_downstream", "help": "pab/prebarrage.html" diff --git a/src/app/calculators/regimeuniforme/config.json b/src/app/calculators/regimeuniforme/config.json index 14a3239ef635d21c5ff75538d8db46dd630da574..c51679f69fa6f76e0dab55e116b2087def0ec427 100644 --- a/src/app/calculators/regimeuniforme/config.json +++ b/src/app/calculators/regimeuniforme/config.json @@ -7,6 +7,7 @@ "id": "select_section", "type": "select", "property": "nodeType", + "default": "SectionRectangle", "help": { "1": "hsl/types_sections.html#section-rectangulaire", "0": "hsl/types_sections.html#section-circulaire", @@ -49,8 +50,6 @@ }, { "type": "options", - "defaultNodeType": "SectionRectangle", - "selectIds": [ "select_section" ], "help": "hsl/regime_uniforme.html", "resultsHelp": { "V": "hsl/section_parametree.html#la-vitesse-moyenne-ms" diff --git a/src/app/calculators/sectionparametree/config.json b/src/app/calculators/sectionparametree/config.json index f24b88ca8954faf65b6764ecdf60dc8231995ead..a54bed3df167cb1d21602e2da88e22f3787f80d6 100644 --- a/src/app/calculators/sectionparametree/config.json +++ b/src/app/calculators/sectionparametree/config.json @@ -7,6 +7,7 @@ "id": "select_section", "type": "select", "property": "nodeType", + "default": "SectionRectangle", "help": { "1": "hsl/types_sections.html#section-rectangulaire", "0": "hsl/types_sections.html#section-circulaire", @@ -49,8 +50,6 @@ }, { "type": "options", - "defaultNodeType": "SectionRectangle", - "selectIds": [ "select_section" ], "help": "hsl/section_parametree.html", "resultsHelp": { "B": "hsl/section_parametree.html#largeur-au-miroir-surface-et-perimetre-mouille", diff --git a/src/app/calculators/solveur/config.json b/src/app/calculators/solveur/config.json index 71a23177b300afc843f27e7c3eed29817fca5aeb..9c0fd2b31099d71d7f5124b763a22133d47461c8 100644 --- a/src/app/calculators/solveur/config.json +++ b/src/app/calculators/solveur/config.json @@ -5,15 +5,11 @@ "fields": [ { "id": "select_target_nub", - "type": "select_custom", - "source": "solveur_target" + "type": "select" }, { "id": "select_target_result", - "type": "select", - "property": "targettedResult", - "source": "solveur_targetted_result", - "default": "" + "type": "select" }, "Ytarget" ] @@ -24,16 +20,13 @@ "fields": [ { "id": "select_searched_param", - "type": "select_custom", - "source": "solveur_searched" + "type": "select" }, "Xinit" ] }, { "type": "options", - "selectIds": [ "select_target_result" ], - "customSelectIds": [ "select_target_nub", "select_searched_param" ], "targettedResultSelectId": "select_target_result", "help": "maths/solver.html" } diff --git a/src/app/calculators/spp/config.json b/src/app/calculators/spp/config.json index 8506d7c73f7385b057410a5ee0a4b0b68abef2a8..e28d38ff84648d3b79245e7586d07111c4d1e291 100644 --- a/src/app/calculators/spp/config.json +++ b/src/app/calculators/spp/config.json @@ -31,7 +31,6 @@ }, { "type": "options", - "selectIds": [ "select_sppoperation" ], "help": "maths/operators.html#somme-et-produit-de-puissances" } ] \ No newline at end of file diff --git a/src/app/calculators/trigo/config.json b/src/app/calculators/trigo/config.json index 3b9f5d97a4b7eb5c65974554a57534edbe4f685d..264fe9299e91dbad8321ad2f62a27e36eeba1b05 100644 --- a/src/app/calculators/trigo/config.json +++ b/src/app/calculators/trigo/config.json @@ -24,7 +24,6 @@ }, { "type": "options", - "selectIds": [ "select_operation", "select_unit" ], "help": "maths/operators.html#fonction-trigonometrique" } ] \ No newline at end of file diff --git a/src/app/calculators/verificateur/config.json b/src/app/calculators/verificateur/config.json index 438d2bf32cc332050660af1651db011636be0891..7faef08de4d662fa42139f9e3603727a17e676a5 100644 --- a/src/app/calculators/verificateur/config.json +++ b/src/app/calculators/verificateur/config.json @@ -5,9 +5,7 @@ "fields": [ { "id": "select_target_pass", - "type": "select_custom", - "source": "verificateur_target", - "messageWhenEmpty": "INFO_VERIF_CREATE_PASS_FRIST" + "type": "select" } ] }, @@ -16,17 +14,13 @@ "type": "fieldset", "fields": [ { - "type": "select_custom", - "id": "select_species_list", - "source": "verificateur_species", - "multiple": true + "type": "select", + "id": "select_species_list" } ] }, { "type": "options", - "selectIds": [ "select_pab_jet_type" ], - "customSelectIds": [ "select_target_pass", "select_species_list" ], "help": "verif/principe.html" } ] diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts index 293fce6e21ae0b137e9a6035b2d20e163739e269..448d40a7dc63bfcd47dbec4e10bdb29ba884e857 100644 --- a/src/app/components/field-set/field-set.component.ts +++ b/src/app/components/field-set/field-set.component.ts @@ -5,7 +5,7 @@ import { FieldSet } from "../../formulaire/elements/fieldset"; import { ParamFieldLineComponent } from "../param-field-line/param-field-line.component"; import { Field } from "../../formulaire/elements/field"; import { InputField } from "../../formulaire/elements/input-field"; -import { SelectField } from "../../formulaire/elements/select-field"; +import { SelectField } from "../../formulaire/elements/select/select-field"; import { FormulairePab } from "../../formulaire/definition/form-pab"; import { SelectFieldLineComponent } from "../select-field-line/select-field-line.component"; import { FieldsetContainer } from "../../formulaire/elements/fieldset-container"; diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts index aa9c6639ddf3c27535e57db4832d5a894f6531d7..f612f362aa0ac7e2475b785e0e2bbf76bbded771 100644 --- a/src/app/components/fieldset-container/fieldset-container.component.ts +++ b/src/app/components/fieldset-container/fieldset-container.component.ts @@ -89,8 +89,8 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { const prms = after.backupParameters(); // replace in-place to change properties (overkill) // @WTF why only those two ? - newFs.setPropValue("structureType", after.properties.getPropValue("structureType")); - newFs.setPropValue("loiDebit", after.properties.getPropValue("loiDebit")); + newFs.setNubPropValue("structureType", after.properties.getPropValue("structureType")); + newFs.setNubPropValue("loiDebit", after.properties.getPropValue("loiDebit")); // au cas où un des paramètres du fieldset source est en mode calcul, // on met le paramètre copié en mode fixé (nghyd#567) diff --git a/src/app/components/generic-select/generic-select.component.html b/src/app/components/generic-select/generic-select.component.html index 9c6880e13e8913f9b7ff1d8073d4df4f5df4ddd0..0a75a714e006ecb3a3a4b20d2e946762b82c5831 100644 --- a/src/app/components/generic-select/generic-select.component.html +++ b/src/app/components/generic-select/generic-select.component.html @@ -1,6 +1,5 @@ <mat-form-field> - <mat-select [id]="selectId" [placeholder]="label" [(value)]="selectedValue" [multiple]="isMultiple" - [disabled]="isDisabled"> + <mat-select [id]="selectId" [placeholder]="label" [(value)]="selectedValue" [multiple]="isMultiple"> <mat-select-trigger *ngIf="isMultiple"> {{ selectedValue && selectedValue[0] ? entryLabel(selectedValue[0]) : '' }} <span *ngIf="selectedValue?.length > 1" class="multiple-selection-label"> @@ -23,4 +22,4 @@ </mat-form-field> <div *ngIf="messageWhenEmpty" class="message-when-empty"> {{ messageWhenEmpty }} -</div> \ No newline at end of file +</div> diff --git a/src/app/components/results-chart/chart-type.component.ts b/src/app/components/results-chart/chart-type.component.ts index 6ba573a915664ca901a5ce8813ee8c5fe10aa0e8..39839c3a96cb2feea1c220ebbf415e2855263880 100644 --- a/src/app/components/results-chart/chart-type.component.ts +++ b/src/app/components/results-chart/chart-type.component.ts @@ -1,7 +1,10 @@ import { Component } from "@angular/core"; -import { Observable, IObservable, Observer } from "jalhyd"; +import { IObservable, Observer } from "jalhyd"; import { I18nService } from "../../services/internationalisation.service"; import { ChartType } from "../../results/chart-type"; +import { SelectFieldChartType } from "app/formulaire/elements/select/select-field-charttype"; +import { SelectEntry } from "app/formulaire/elements/select/select-entry"; +import { decodeHtml } from "../../util"; @Component({ selector: "chart-type", @@ -11,53 +14,46 @@ import { ChartType } from "../../results/chart-type"; ] }) export class ChartTypeSelectComponent implements IObservable { - private _entries: ChartType[] = [ ChartType.Histogram, ChartType.Dots, ChartType.Scatter ]; - private _entriesLabels: string[] = []; - private _selected: ChartType; - - private _observable: Observable; - - public get selectId() { - return "chart-type"; // for generic select component - } + private _select: SelectFieldChartType; constructor(private intlService: I18nService) { - this._observable = new Observable(); - this._entriesLabels = [ - this.intlService.localizeText("INFO_PARAMFIELD_CHART_TYPE_HISTOGRAM"), - this.intlService.localizeText("INFO_PARAMFIELD_CHART_TYPE_DOTS"), - "XY" - ]; + this._select = new SelectFieldChartType(undefined, this.intlService); + this._select.afterParseConfig(); // fill entries, set default value + this._select.updateEntries(); } public get entries(): ChartType[] { - return this._entries; + return this._select.entryValues; } - protected entryLabel(entry: ChartType): string { - const i = this._entries.indexOf(entry); - return this._entriesLabels[i]; + public entryLabel(ct: ChartType): string { + const entry = this._select.getEntryFromValue(ct); + return decodeHtml(entry.label); } public get selectedValue(): ChartType { - return this._selected; + const val = this._select.getValue() as SelectEntry; + return val.value; } - public set selectedValue(v: ChartType) { - if (this._selected !== v) { - this._selected = v; - this.notifyObservers({ - "action": "graphTypeChanged", - "value": v - }, this); - } + public set selectedValue(ct: ChartType) { + const entry = this._select.getEntryFromValue(ct); + this._select.setValue(entry); } - public get isMultiple(): boolean { - return false; + public get selectId() { + return this._select.id; } - public get isDisabled(): boolean { + public get label(): string { + return this._select.label; + } + + public get messageWhenEmpty(): string { + return this._select.messageWhenEmpty; + } + + public get enableHelpButton(): boolean { return false; } @@ -65,8 +61,8 @@ export class ChartTypeSelectComponent implements IObservable { return false; } - public get label() { - return this.intlService.localizeText("INFO_PARAMFIELD_CHART_TYPE"); + public get isMultiple(): boolean { + return this._select.multiple; } // interface IObservable @@ -75,30 +71,20 @@ export class ChartTypeSelectComponent implements IObservable { * ajoute un observateur à la liste */ addObserver(o: Observer) { - this._observable.addObserver(o); + this._select.addObserver(o); } /** * supprime un observateur de la liste */ removeObserver(o: Observer) { - this._observable.removeObserver(o); + this._select.removeObserver(o); } /** * notifie un événement aux observateurs */ notifyObservers(data: any, sender?: any) { - this._observable.notifyObservers(data, sender); - } - - // implemented because of generic template - public get enableHelpButton() { - return false; - } - - // implemented because of generic template - public get messageWhenEmpty(): string { - return undefined; + this._select.notifyObservers(data, sender); } } diff --git a/src/app/components/results-chart/results-chart.component.ts b/src/app/components/results-chart/results-chart.component.ts index bb0c86a0120db83f21857ae0ba97d05c204255cf..0b33967f3c45ae7766943a382b9db32fc3593ed7 100644 --- a/src/app/components/results-chart/results-chart.component.ts +++ b/src/app/components/results-chart/results-chart.component.ts @@ -1,6 +1,6 @@ import { Component, ViewChild, AfterContentInit, ChangeDetectorRef, Input, OnChanges } from "@angular/core"; -import { BaseChartDirective, NgChartsModule } from "ng2-charts"; +import { BaseChartDirective } from "ng2-charts"; import { Observer, ParamFamily, Result } from "jalhyd"; @@ -16,6 +16,7 @@ import { AppComponent } from "../../app.component"; import zoomPlugin from 'chartjs-plugin-zoom'; import { Chart } from "chart.js"; +import { SelectFieldChartType } from "app/formulaire/elements/select/select-field-charttype"; @Component({ selector: "results-chart", @@ -495,9 +496,9 @@ export class ResultsChartComponent extends ResultsComponentDirective implements // interface Observer update(sender: any, data: any) { - if (sender instanceof ChartTypeSelectComponent) { - if (data.action === "graphTypeChanged") { - this._results.chartType = data.value; + if (sender instanceof SelectFieldChartType) { + if (data.action === "select") { + this._results.chartType = data.value.value; } } this.drawChart(); diff --git a/src/app/components/select-field-line/select-field-line.component.ts b/src/app/components/select-field-line/select-field-line.component.ts index eccc37a991e5cdb7462c7f1ec2caddcb3c2ddb5e..c6b2474a0f08e795ac58514bb58486d127e4ecdb 100644 --- a/src/app/components/select-field-line/select-field-line.component.ts +++ b/src/app/components/select-field-line/select-field-line.component.ts @@ -1,10 +1,9 @@ import { Component, Input, OnInit } from "@angular/core"; -import { SelectField } from "../../formulaire/elements/select-field"; -import { SelectEntry } from "../../formulaire/elements/select-entry"; +import { SelectField } from "../../formulaire/elements/select/select-field"; +import { SelectEntry } from "../../formulaire/elements/select/select-entry"; import { I18nService } from "../../services/internationalisation.service"; import { ApplicationSetupService } from "../../services/app-setup.service"; -import { SelectFieldCustom } from "../../formulaire/elements/select-field-custom"; import { decodeHtml } from "../../util"; @Component({ @@ -35,10 +34,6 @@ export class SelectFieldLineComponent implements OnInit { return this._select.multiple; } - public get isDisabled(): boolean { - return this._select.disabled; - } - public get entries(): SelectEntry[] { if (! this._select) { return []; @@ -124,8 +119,6 @@ export class SelectFieldLineComponent implements OnInit { // called every time we navigate to the module ngOnInit(): void { - if (this._select instanceof SelectFieldCustom) { - this._select.updateEntries(); - } + this._select.updateEntries(); } } diff --git a/src/app/formulaire/definition/config-parser.ts b/src/app/formulaire/definition/config-parser.ts new file mode 100644 index 0000000000000000000000000000000000000000..8deec7c969f832f9bd10567eb9a1c5f84352501e --- /dev/null +++ b/src/app/formulaire/definition/config-parser.ts @@ -0,0 +1,41 @@ +export class ConfigIterator implements IterableIterator<Object> { + private _index = 0; + + private _entries: [string, any][]; + + constructor(private _json: Object, private _key: string) { + this._entries = Object.entries(this._json); + } + + public next(): IteratorResult<Object> { + let i = this._index; + while (i < this._entries.length) { + const ent = this._entries[i][1]; + if (ent.type === this._key) { + this._index = i + 1; + return { + done: false, + value: ent + }; + } + i++; + } + return { + done: true, + value: undefined + }; + } + + [Symbol.iterator](): IterableIterator<Object> { + return this; + } +} + +export class ConfigParser { + constructor(private _json: {}) { + } + + public forAll(key: string): ConfigIterator { + return new ConfigIterator(this._json, key); + } +} \ No newline at end of file diff --git a/src/app/formulaire/definition/form-definition.ts b/src/app/formulaire/definition/form-definition.ts index 814db2e708fde90832361c51d496bf8a6c0531c2..9c9b948896b4019f8b92fca31b7698f71b42ffc3 100644 --- a/src/app/formulaire/definition/form-definition.ts +++ b/src/app/formulaire/definition/form-definition.ts @@ -24,8 +24,10 @@ import { TopFormulaireElementIterator } from "../form-iterator/top-element-itera import { CalculatorResults } from "../../results/calculator-results"; import { ServiceFactory } from "../../services/service-factory"; import { PabTable } from "../elements/pab-table"; -import { SelectEntry } from "../elements/select-entry"; -import { SelectField } from "../elements/select-field"; +import { SelectEntry } from "../elements/select/select-entry"; +import { SelectField } from "../elements/select/select-field"; +import { DeepSelectFieldIterator } from "../form-iterator/deep-selectfield-iterator"; +import { ConfigParser as ConfigParser } from "./config-parser"; /** * classe de base pour tous les formulaires @@ -42,7 +44,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs protected _props = {}; /** for SectionNubs only */ - protected _defaultNodeType; + private _defaultSectionType; /** aide en ligne pour les résultats */ protected _resultsHelpLinks: { [key: string]: string }; @@ -127,7 +129,7 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs // add new Section as first child, from given nodeType const propsSection = new Props(); propsSection.setPropValue("calcType", CalculatorType.Section); - propsSection.setPropValue("nodeType", this._defaultNodeType); + propsSection.setPropValue("nodeType", this._defaultSectionType); propsSection.setPropValue(Prop_NullParameters, ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit); const section = Session.getInstance().createNub(propsSection); this.currentNub.setSection(section as acSection); @@ -160,10 +162,8 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs } protected parseOptions(json: {}) { - const dnt = json["defaultNodeType"]; - if (dnt !== undefined) { - this._defaultNodeType = SectionType[dnt]; - } + this.parseDefaultSectionType(); + this._helpLink = json["help"]; this._resultsHelpLinks = json["resultsHelp"]; if (json["calculateDisabled"] !== undefined) { @@ -171,6 +171,25 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs } } + /** + * determine default section type from select configuration + */ + private parseDefaultSectionType() { + if (this._defaultSectionType === undefined) { + const jp = new ConfigParser(this._jsonConfig); + for (const fs of jp.forAll("fieldset")) { + const fsp = new ConfigParser(fs["fields"]); + for (const sel of fsp.forAll("select")) { + if (sel["id"] === "select_section") { + const st = sel["default"]; + this._defaultSectionType = SectionType[st]; + return; + } + } + } + } + } + /** called at the end of parseConfig() */ protected completeParse(firstNotif: boolean = true) { this.helpLinks = this._resultsHelpLinks; @@ -237,6 +256,8 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs public preparseConfig(json: {}) { this._jsonConfig = json; + this.parseDefaultSectionType(); + // analyse des options globales // il est utile de le faire avant le reste pour les modules de calcul utilisant // des sections (id des selects type de section/variable à calculer) @@ -410,6 +431,13 @@ export abstract class FormulaireDefinition extends FormulaireNode implements Obs return new TopFormulaireElementIterator(this); } + /** + * itère sur tous les SelectField + */ + public get allSelectFields(): IterableIterator<SelectField> { + return new DeepSelectFieldIterator(this); + } + // interface Observer public update(sender: any, data: any) { diff --git a/src/app/formulaire/definition/form-fixedvar.ts b/src/app/formulaire/definition/form-fixedvar.ts index 575f7cb100cff6c9f2a3061ce3c27f2812f9e747..0a329f489308d59093da98d30da932a160e249fa 100644 --- a/src/app/formulaire/definition/form-fixedvar.ts +++ b/src/app/formulaire/definition/form-fixedvar.ts @@ -2,12 +2,11 @@ import { FormulaireDefinition } from "./form-definition"; import { FixedResults } from "../../results/fixed-results"; import { VarResults } from "../../results/var-results"; import { ChartType } from "../../results/chart-type"; -import { CalculatorResults } from "../../results/calculator-results"; -import { ParamRadioConfig, NgParameter } from "../elements/ngparam"; +import { NgParameter } from "../elements/ngparam"; import { FieldSet } from "../elements/fieldset"; import { FormulaireNode } from "../elements/formulaire-node"; -import { SelectFieldCustom } from "../elements/select-field-custom"; +import { SelectField } from "../elements/select/select-field"; import { Nub, IObservable, VariatedDetails } from "jalhyd"; export class FormulaireFixedVar extends FormulaireDefinition { @@ -15,12 +14,6 @@ export class FormulaireFixedVar extends FormulaireDefinition { protected _fixedResults: FixedResults; protected _varResults: VarResults; - /** ids of select fields */ - private _selectIds: string[] = []; - - /** ids of "custom" select fields */ - private _customSelectIds: string[] = []; - constructor(parent?: FormulaireNode) { super(parent); this._fixedResults = new FixedResults(); @@ -32,10 +25,6 @@ export class FormulaireFixedVar extends FormulaireDefinition { return this._fixedResults; } - public get selectids(): string[] { - return this._selectIds; - } - public resetFormResults() { this._fixedResults.reset(); this._varResults.reset(); @@ -74,41 +63,23 @@ export class FormulaireFixedVar extends FormulaireDefinition { public afterParseFieldset(fs: FieldSet) { // observe all Select fields @see this.update() - if (this._selectIds.length > 0) { - for (const sId of this._selectIds) { - const sel = fs.getFormulaireNodeById(sId); - if (sel) { - // Formulaire is listening to FieldSet properties (@TODO why not directly Select ?) - fs.properties.addObserver(this); - } - } - } + fs.properties.addObserver(this); } protected completeParse(firstNotif: boolean = true) { super.completeParse(firstNotif); // observe all CustomSelect fields @TODO move to afterParseFieldset ? - if (this._customSelectIds.length > 0) { - for (const csId of this._customSelectIds) { - const sel = this.getFormulaireNodeById(csId); - // Formulaire is listening to Select value + for (const sel of this.allSelectFields) { // Formulaire is listening to Select value + if (!sel.hasAssociatedNubProperty) { // only to "custom" selects sel.addObserver(this); if (firstNotif) { // force 1st observation - (sel as SelectFieldCustom).notifyValueChanged(); + (sel as SelectField).notifyValueChanged(); } } } } - protected parseOptions(json: {}) { - super.parseOptions(json); - // get ids of all select fields - this._selectIds = this.getOption(json, "selectIds") || []; - // get ids of all "custom" select fields - this._customSelectIds = this.getOption(json, "customSelectIds") || []; - } - protected compute() { this.runNubCalc(this.currentNub); this.refreshFieldsets(); // important: before reaffectResultComponents() or it will break results components localization diff --git a/src/app/formulaire/definition/form-parallel-structures.ts b/src/app/formulaire/definition/form-parallel-structures.ts index 9e4dd86d6b05615acc3555d9b8fc6b1441870589..b47c4808b021bba832df9c201b65ebc4aadb76f5 100644 --- a/src/app/formulaire/definition/form-parallel-structures.ts +++ b/src/app/formulaire/definition/form-parallel-structures.ts @@ -2,7 +2,7 @@ import { Structure, Nub, ParallelStructure, StructureProperties, Props, Session, import { FieldsetContainer } from "../elements/fieldset-container"; import { FieldSet } from "../elements/fieldset"; -import { SelectField } from "../elements/select-field"; +import { SelectField } from "../elements/select/select-field"; import { NgParameter } from "../elements/ngparam"; import { FieldsetTemplate } from "../elements/fieldset-template"; import { FormulaireNode } from "../elements/formulaire-node"; @@ -57,14 +57,8 @@ export class FormulaireParallelStructure extends FormulaireRepeatableFieldset { } protected createChildNub(templ: FieldsetTemplate): Nub { - // !!! attention !!! - // Il doit y avoir cohérence dans le fichier de conf entre les valeurs defaultXXX et les valeurs possibles pour les select - // cad valeur par défaut du 1er select (type d'ouvrage), du 2ème (loi de débit). - // A terme, il faudrait analyser le fichier de conf (dépendances d'existence) pour déterminer automatiquement ces valeurs - const params = {}; params["calcType"] = templ.calcTypeFromConfig; - params["nodeType"] = templ.defaultNodeTypeFromConfig; params["structureType"] = templ.defaultStructTypeFromConfig; params["loiDebit"] = templ.defaultLoiDebitFromConfig; params[Prop_NullParameters] = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit; diff --git a/src/app/formulaire/definition/form-pb-cloison.ts b/src/app/formulaire/definition/form-pb-cloison.ts index 02934c401468a2fd0034bd5e506b6b2bee16b7cf..25910c87d6a1ed5681bd8d7af9e5a45092f876c0 100644 --- a/src/app/formulaire/definition/form-pb-cloison.ts +++ b/src/app/formulaire/definition/form-pb-cloison.ts @@ -5,7 +5,7 @@ import { FieldSet } from "../elements/fieldset"; import { FormulaireNode } from "../elements/formulaire-node"; import { FieldsetContainer } from "../elements/fieldset-container"; import { FormulairePrebarrage } from "./form-prebarrage"; -import { SelectFieldCustom } from "../elements/select-field-custom"; +import { SelectField } from "../elements/select/select-field"; import { ServiceFactory } from "app/services/service-factory"; export class FormulairePbCloison extends FormulaireParallelStructure { @@ -41,7 +41,7 @@ export class FormulairePbCloison extends FormulaireParallelStructure { } } - if (sender instanceof SelectFieldCustom) { + if (sender instanceof SelectField) { const nub = this._currentNub as PbCloison; const pb = nub.parent; const emptyFields: boolean = ServiceFactory.applicationSetupService.enableEmptyFieldsOnFormInit; diff --git a/src/app/formulaire/definition/form-solveur.ts b/src/app/formulaire/definition/form-solveur.ts index 481ea5f6d53bdc020197e68d0828bbbecb0e2212..f658c98eefb762eece2bae95a7180273a8f307ae 100644 --- a/src/app/formulaire/definition/form-solveur.ts +++ b/src/app/formulaire/definition/form-solveur.ts @@ -1,9 +1,8 @@ import { IObservable, ParamDefinition, Nub } from "jalhyd"; -import { SelectFieldCustom } from "../elements/select-field-custom"; import { NgParameter } from "../elements/ngparam"; import { FormulaireFixedVar } from "./form-fixedvar"; -import { SelectField } from "../elements/select-field"; +import { SelectField } from "../elements/select/select-field"; import { FieldSet } from "../elements/fieldset"; /** @@ -52,7 +51,7 @@ export class FormulaireSolveur extends FormulaireFixedVar { this.reset(); } - if (sender instanceof SelectFieldCustom) { + if (sender instanceof SelectField) { if (sender.id === "select_target_nub" && data.action === "select") { // update Solveur property: Nub to calculate try { @@ -95,7 +94,7 @@ export class FormulaireSolveur extends FormulaireFixedVar { * Re-populate searched parameter selector with fresh entries */ private refreshParameterEntries() { - const pSel = this.getFormulaireNodeById("select_searched_param") as SelectFieldCustom; + const pSel = this.getFormulaireNodeById("select_searched_param") as SelectField; if (pSel) { pSel.updateEntries(); // reflect changes in GUI diff --git a/src/app/formulaire/definition/form-verificateur.ts b/src/app/formulaire/definition/form-verificateur.ts index 3bcd463a65b77779aea4aa3ba7875ddae91adcac..893754665a7cb997437bf7ab7aaa26b6643c9695 100644 --- a/src/app/formulaire/definition/form-verificateur.ts +++ b/src/app/formulaire/definition/form-verificateur.ts @@ -1,9 +1,8 @@ import { IObservable, Nub, Verificateur, Result, VariatedDetails } from "jalhyd"; -import { SelectFieldCustom } from "../elements/select-field-custom"; +import { SelectField } from "../elements/select/select-field"; import { FormulaireFixedVar } from "./form-fixedvar"; import { VerificateurResults } from "../../results/verificateur-results"; -import { CalculatorResults } from "../../results/calculator-results"; import { ServiceFactory } from "../../services/service-factory"; /** @@ -94,7 +93,7 @@ export class FormulaireVerificateur extends FormulaireFixedVar { this.reset(); } - if (sender instanceof SelectFieldCustom) { + if (sender instanceof SelectField) { this.reset(); // reset results if (sender.id === "select_target_pass" && data.action === "select") { // update Verificateur property: Pass to check diff --git a/src/app/formulaire/elements/fieldset-container.ts b/src/app/formulaire/elements/fieldset-container.ts index 23b5488953e9c72500de7ecd8babd9bbacf86948..0f57d21fe479e6690b8eef1a7a2ac52aff1d7e5b 100644 --- a/src/app/formulaire/elements/fieldset-container.ts +++ b/src/app/formulaire/elements/fieldset-container.ts @@ -21,7 +21,7 @@ export class FieldsetContainer extends FormulaireElement { if (this._template !== undefined) { throw new Error("template already set!"); } - this._template = new FieldsetTemplate(fst); + this._template = fst; } public addFieldset(fs: FieldSet) { @@ -105,7 +105,9 @@ export class FieldsetContainer extends FormulaireElement { for (const t of templateNames) { for (const d of templates) { if (d.id === t) { - this.template = d; + const tmpl = new FieldsetTemplate(); + tmpl.parseConfig(d); + this.template = tmpl; } } } diff --git a/src/app/formulaire/elements/fieldset-template.ts b/src/app/formulaire/elements/fieldset-template.ts index b65b48874c37bc13364cb622c05da35bd4f4ebd9..9147d2cbb7431630e90a8b1f4b3d9d713fb922d6 100644 --- a/src/app/formulaire/elements/fieldset-template.ts +++ b/src/app/formulaire/elements/fieldset-template.ts @@ -2,13 +2,16 @@ import { FieldSet } from "./fieldset"; import { CalculatorType, LoiDebit, Nub, StructureType, SectionType } from "jalhyd"; import { FormulaireDefinition } from "../definition/form-definition"; import { FieldsetContainer } from "./fieldset-container"; +import { SelectFieldFactory } from "./select/select-field-factory"; export class FieldsetTemplate { private _jsonConfig: {}; - constructor(config: {}) { - this._jsonConfig = config; - } + // parsed default structure type from JSON configuration file (from select_structure field) + private _defaultStructureType: StructureType; + + // parsed default stage discharge law from JSON configuration file (from select_loidebit field) + private _defaultLoiDebit: LoiDebit; public get config() { return this._jsonConfig; @@ -19,22 +22,42 @@ export class FieldsetTemplate { return CalculatorType[ct]; } - public get defaultNodeTypeFromConfig(): SectionType { - const nt: string = this._jsonConfig["defaultNodeType"]; - return SectionType[nt]; - } - public get defaultStructTypeFromConfig(): StructureType { - const st: string = this._jsonConfig["defaultStructType"]; - return StructureType[st]; + return this._defaultStructureType; } public get defaultLoiDebitFromConfig(): LoiDebit { - const ld: string = this._jsonConfig["defaultLoiDebit"]; - if (LoiDebit[ld] === undefined) { - throw new Error(`FieldsetTemplate.defaultLoiDebitFromConfig: La loi de débit ${ld} n'est pas définie`); + return this._defaultLoiDebit; + } + + private findSelectField(id: string): any { + for (const f of this._jsonConfig["fields"]) { + if (f["type"] === "select" && f["id"] === id) { + return f; + } + } + } + + public parseConfig(config: {}) { + this._jsonConfig = config; + + if (this._jsonConfig["calcType"] === "Structure") { + // parse default stage discharge law + + const ld: string = this.findSelectField(SelectFieldFactory.SelectId_LoiDebit)["default"]; + if (LoiDebit[ld] === undefined) { + throw new Error(`FieldsetTemplate.defaultLoiDebitFromConfig : la loi de débit ${ld} n'est pas définie`); + } + this._defaultLoiDebit = LoiDebit[ld]; + + // parse default structure type + + const st: string = this.findSelectField(SelectFieldFactory.SelectId_StructureType)["default"]; + if (StructureType[st] === undefined) { + throw new Error(`FieldsetTemplate.defaultStructTypeFromConfig : le type de structure ${st} n'est pas défini`); + } + this._defaultStructureType = StructureType[st]; } - return LoiDebit[ld]; } /** diff --git a/src/app/formulaire/elements/fieldset.ts b/src/app/formulaire/elements/fieldset.ts index b73ff379a513b8a1c8b1f0ae0055ff97ffbb5d51..d0229afe762326464684558389b299439dd7f7d0 100644 --- a/src/app/formulaire/elements/fieldset.ts +++ b/src/app/formulaire/elements/fieldset.ts @@ -4,19 +4,17 @@ import { Props, Observer, Nub, - Session, + enumValueFromString } from "jalhyd"; import { FormulaireElement } from "./formulaire-element"; import { Field } from "./field"; -import { SelectField } from "./select-field"; +import { SelectField } from "./select/select-field"; import { NgParameter, ParamRadioConfig } from "./ngparam"; import { FieldsetContainer } from "./fieldset-container"; -import { SelectFieldCustom } from "./select-field-custom"; +import { SelectFieldFactory } from "./select/select-field-factory"; import { FormulaireFixedVar } from "../definition/form-fixedvar"; -import { SelectEntry } from "./select-entry"; -import { FormulaireNode } from "./formulaire-node"; -import { ServiceFactory } from "app/services/service-factory"; +import { SelectEntry } from "./select/select-entry"; export class FieldSet extends FormulaireElement implements Observer { @@ -109,19 +107,9 @@ export class FieldSet extends FormulaireElement implements Observer { } private parse_select(json: {}): SelectField { - const res: SelectField = new SelectField(this); - res.parseConfig(json); - res.addObserver(this); - return res; - } - - private parse_select_custom(json: {}): SelectField { - const source = json["source"]; - if (source === undefined || source === "") { - throw new Error(`Fieldset.parse_select_custom(): "source" must not be empty`); - } - const res: SelectField = new SelectFieldCustom(this); + const res: SelectField = SelectFieldFactory.newSelectField(json, this); res.parseConfig(json); + res.afterParseConfig(); res.addObserver(this); return res; } @@ -140,9 +128,11 @@ export class FieldSet extends FormulaireElement implements Observer { } /** + * get associated nub property value + * @param key property name * @param inSection if true, will look for the required property in the Nub's section (children[0]) */ - private getPropValue(key: string, inSection: boolean = false): any { + private getNubPropValue(key: string, inSection: boolean = false): any { if (inSection) { return this.sectionProperties.getPropValue(key); } else { @@ -150,7 +140,13 @@ export class FieldSet extends FormulaireElement implements Observer { } } - public setPropValue(key: string, val: any): boolean { + /** + * assign associated nub property + * @param key nub property name + * @param val value to assign with + * @returns true if property value has changed + */ + public setNubPropValue(key: string, val: any): boolean { return this.properties.setPropValue(key, val, this); } @@ -217,12 +213,6 @@ export class FieldSet extends FormulaireElement implements Observer { param = this.parse_select(field); this.addField(param); break; - - case "select_custom": - param = this.parse_select_custom(field); - this.addField(param); - break; - } } } @@ -245,9 +235,10 @@ export class FieldSet extends FormulaireElement implements Observer { // for all select fields known by the form, set selected value // from associated property if (this.parentForm instanceof FormulaireFixedVar) { - const selectIds = this.parentForm.selectids; - for (const sId of selectIds) { - this.setSelectValueFromProperty(sId, (this._confId === "fs_section")); + for (const sel of this.parentForm.allSelectFields) { + if (sel.hasAssociatedNubProperty) { // ie. if select is a standard select + this.setSelectValueFromProperty(sel.id, (this._confId === "fs_section")); + } } } } @@ -259,11 +250,11 @@ export class FieldSet extends FormulaireElement implements Observer { private setSelectValueFromProperty(selectId: string, inSection: boolean = false) { const selectField: SelectField = this.getFormulaireNodeById(selectId) as SelectField; if (selectField) { - let propVal: any = this.getPropValue(selectField.associatedProperty, inSection); + let propVal: any = this.getNubPropValue(selectField.associatedProperty, inSection); if (propVal === undefined) { propVal = ""; // clodo bullet-proof loading } - const selectElement = selectField.getSelectedEntryFromValue(propVal); + const selectElement = selectField.getEntryFromValue(propVal); try { selectField.setValue(selectElement); } catch (e) { @@ -288,7 +279,7 @@ export class FieldSet extends FormulaireElement implements Observer { const ct: string = json["calcType"]; const currentCt = this.properties.getPropValue("calcType"); const calc_type: CalculatorType = currentCt ? currentCt : (ct ? CalculatorType[ct] : this.parentForm.calculatorType); - this.setPropValue("calcType", calc_type); + this.setNubPropValue("calcType", calc_type); // parse fields once, so that SelectField elements are present // when setting default properties below @@ -297,29 +288,18 @@ export class FieldSet extends FormulaireElement implements Observer { // for all select fields known by the form, apply default value // to associated property, usually from associated enum if (this.parentForm instanceof FormulaireFixedVar) { - const selectIds = this.parentForm.selectids; - for (const sId of selectIds) { - // find select element in parent form - const fe = this.getFormulaireNodeById(sId); - if (fe) { - const prop = (fe as SelectField).associatedProperty; - const defaultValue = (fe as SelectField).defaultValue; + for (const sel of this.parentForm.allSelectFields) { + if (sel.hasAssociatedNubProperty) { // ie. if select is a standard select + const prop = sel.associatedProperty; + const defaultValue = sel.configDefaultValue; // Sets Nub default property, unless this property is already set const currentValue = this.properties.getPropValue(prop); if (defaultValue !== undefined && currentValue === undefined) { - let formalValue = defaultValue; - // !! property names must be unique throughout JaLHyd !! - const enumClass = Session.enumFromProperty[prop]; - if (enumClass) { - formalValue = enumClass[defaultValue]; - } - this.setPropValue(prop, formalValue); + this.setNubPropValue(prop, enumValueFromString(prop, defaultValue)); } } } } - - this.updateFields(); } public getNodeParameter(symbol: string): NgParameter { @@ -389,28 +369,27 @@ export class FieldSet extends FormulaireElement implements Observer { }); } } else { - if (this.parentForm instanceof FormulaireFixedVar) { - // for all select fields known by the form, apply received value - // to associated property - const selectIds = this.parentForm.selectids; - for (const sId of selectIds) { - if (senderId === sId) { - // find select element in parent form - const fe = this.parentForm.getFieldById(sId); - if (fe && data.value !== undefined) { - const prop = (fe as SelectField).associatedProperty; - // for multiple select - if (Array.isArray(data.value)) { - this.setPropValue(prop, data.value.map((v: any) => v.value)); - } else { - this.setPropValue(prop, data.value.value); + if (data.value !== undefined) { + if (this.parentForm instanceof FormulaireFixedVar) { + // for all select fields known by the form, apply received value + // to associated property + for (const sel of this.parentForm.allSelectFields) { + if (senderId === sel.id) { + // find select element in parent form + if (sel.hasAssociatedNubProperty) { // if select is a standard select + const prop = sel.associatedProperty; + // for multiple select + if (Array.isArray(data.value)) { + this.setNubPropValue(prop, data.value.map((v: any) => v.value)); + } else { + this.setNubPropValue(prop, data.value.value); + } } } } } } } - break; // switch (data.action) } } diff --git a/src/app/formulaire/elements/select-field-custom.ts b/src/app/formulaire/elements/select-field-custom.ts deleted file mode 100644 index 5c395281e5840ac070e79ad385bf96e4f806e4f2..0000000000000000000000000000000000000000 --- a/src/app/formulaire/elements/select-field-custom.ts +++ /dev/null @@ -1,343 +0,0 @@ -import { SelectEntry } from "./select-entry"; -import { ServiceFactory } from "../../services/service-factory"; -import { SelectField } from "./select-field"; -import { decodeHtml, arraysAreEqual } from "../../util"; - -import { FishSpecies, Session, Solveur, FishPass, CalculatorType, Verificateur, Nub, PbCloison, PreBarrage, acSection } from "jalhyd"; - -import { sprintf } from "sprintf-js"; - -/** - * A select field that populates itself with custom stuff (ex: references to Nubs, Parameters…) - */ -export class SelectFieldCustom extends SelectField { - - /** - * Loads UI with the value held by the model - */ - protected initSelectedValue() { - const nub = this.parentForm.currentNub; - - switch (this.source) { - - case "solveur_target": // Solveur, module cible (à calculer) - const ntc = (nub as Solveur).nubToCalculate; - if (ntc !== undefined) { - this.setValueFromId(this._entriesBaseId + ntc.uid); - } - break; - - case "solveur_searched": // Solveur, paramètre recherché (à faire varier) - const sp = (nub as Solveur).searchedParameter; - if (sp !== undefined) { - let parentNubUid; - const parentNub = sp.getParentComputeNode(false); - if (parentNub instanceof acSection) { - parentNubUid = parentNub.uid; - } - else { - parentNubUid = sp.nubUid - } - this.setValueFromId(this._entriesBaseId + parentNubUid + "_" + sp.symbol); - } - break; - - case "verificateur_target": // Vérificateur, passe cible (à vérifier) - const ntv = (nub as Verificateur).nubToVerify; - if (ntv !== undefined) { - this.setValueFromId(this._entriesBaseId + ntv.uid); - } - break; - - case "verificateur_species": // Vérificateur, liste d'espèces (choix multiple) - const sl = (nub as Verificateur).speciesList; - if (sl !== undefined) { - this.setValueFromId(sl.map((s) => { - const spgId = s.substring(s.lastIndexOf("_") + 1); - return this._entriesBaseId + spgId; - })); - } - break; - - case "upstream_basin": // PbCloisons, bassin amont - const ub = (nub as PbCloison).bassinAmont; - // console.log("-- load UB", ub, this._entriesBaseId + ub?.uid); - this.setValueFromId(this._entriesBaseId + (ub ? ub.uid : "none")); - break; - - case "downstream_basin": // PbCloisons, bassin aval - const db = (nub as PbCloison).bassinAval; - // console.log("-- load DB", db, this._entriesBaseId + db?.uid); - this.setValueFromId(this._entriesBaseId + (db ? db.uid : "none")); - break; - } - } - - /** - * Populates entries with available options - */ - protected populate() { - const fs = ServiceFactory.formulaireService; - let candidateNubs: any[]; - switch (this.source) { - - case "solveur_target": // Solveur, module cible (à calculer) - // find all Nubs having at least one link to another Nub's result - candidateNubs = - Session.getInstance().getDownstreamNubs().concat( - Session.getInstance().getUpstreamNubsHavingExtraResults() - ).filter( - (element, index, self) => self.findIndex((e) => e.uid === element.uid) === index - ); - for (const cn of candidateNubs) { - const nub = fs.getFormulaireFromId(cn.uid); - if (nub) { - const calc = nub.calculatorName; - let label = calc; - // calculated param - if (cn.calculatedParam !== undefined) { - const varName = fs.expandVariableName(cn.calcType, cn.calculatedParam.symbol); - label += ` / ${varName} (${cn.calculatedParam.symbol})`; - } - this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); - } else { - // silent fail, this Solveur nub was probably loaded before all the candidate nubs are done loading - } - } - break; - - case "solveur_searched": // Solveur, paramètre recherché (à faire varier) - // find all non-calculated, non-linked parameters of all Nubs that - // the current "target" Nub depends on (if any) - const solv = this.parentForm.currentNub as Solveur; - const ntc: Nub = solv.nubToCalculate; - const searchableParams = Solveur.getDependingNubsSearchableParams( - ntc, - solv.targettedResult !== undefined && solv.targettedResult !== "" - ); - for (const p of searchableParams) { - if (p.visible) { - const calc = fs.getFormulaireFromId(p.originNub.uid).calculatorName; - const varName = fs.expandVariableName(p.originNub.calcType, p.symbol); - const label = `${p.symbol} - ${varName} (${calc})`; - this.addEntry(new SelectEntry(this._entriesBaseId + p.getParentComputeNode(false).uid + "_" + p.symbol, p, decodeHtml(label))); - } - } - break; - - case "verificateur_target": // Vérificateur, passe cible (à vérifier) - // find all Nubs of type FishPass - candidateNubs = Session.getInstance().getAllNubs().filter((element) => { - return ( - (element instanceof FishPass) - && element.calcType !== CalculatorType.Par // ParSimulation extends Par @TODO find something better - ); - }); - for (const cn of candidateNubs) { - const nub = fs.getFormulaireFromId(cn.uid); - if (nub) { - const label = nub.calculatorName + " (" + fs.getLocalisedTitleFromCalculatorType(nub.calculatorType) + ")"; - this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); - } else { - // silent fail, this Verificateur nub was probably loaded before all the candidate nubs are done loading - } - } - break; - - case "verificateur_species": - // add UIDs of all Espece type Nubs in the session - const especeNubs = Session.getInstance().getAllNubs().filter((element) => element.calcType === CalculatorType.Espece); - for (const en of especeNubs) { - const form = ServiceFactory.formulaireService.getFormulaireFromNubId(en.uid); - this.addEntry( - new SelectEntry( - this._entriesBaseId + en.uid, - en.uid, - sprintf( - ServiceFactory.i18nService.localizeText("INFO_VERIFICATEUR_CUSTOM_SPECIES"), - form ? form.calculatorName : "*** form not loaded yet ***" - ) - ) - ); - } - // add all FishSpecies - for (let j = 1; j < Object.keys(FishSpecies).length / 2; j++) { // exclude "0" (SPECIES_CUSTOM) - const spgId = FishSpecies[j].substring(FishSpecies[j].lastIndexOf("_") + 1); - this.addEntry( - new SelectEntry( - this._entriesBaseId + spgId, - FishSpecies[j], - ServiceFactory.i18nService.localizeText("INFO_ENUM_" + FishSpecies[j]) - ) - ); - } - break; - - case "upstream_basin": // PbCloisons, bassin amont - const pbWallU = this.parentForm.currentNub as PbCloison; - const preBarrageU = pbWallU.parent as PreBarrage; - const posDb = pbWallU.bassinAval?.findPositionInParent(); - // river upstream - this.addEntry( - new SelectEntry( - this._entriesBaseId + "none", - undefined, - ServiceFactory.i18nService.localizeText("INFO_LIB_AMONT") - ) - ); - // all available basins, depending on current downstream basin - for (const b of preBarrageU.bassins) { - const pos = b.findPositionInParent(); - if (posDb === undefined || pos < posDb) { - this.addEntry( - new SelectEntry( - this._entriesBaseId + b.uid, - b.uid, - ServiceFactory.i18nService.localizeMessage(b.description) - ) - ); - } - } - break; - - case "downstream_basin": // PbCloisons, bassin aval - const pbWallD = this.parentForm.currentNub as PbCloison; - const preBarrageD = pbWallD.parent as PreBarrage; - const posUb = pbWallD.bassinAmont?.findPositionInParent(); - // all available basins, depending on current upstream basin - for (const b of preBarrageD.bassins) { - const pos = b.findPositionInParent(); - if (posUb === undefined || pos > posUb) { - this.addEntry( - new SelectEntry( - this._entriesBaseId + b.uid, - b.uid, - ServiceFactory.i18nService.localizeMessage(b.description) - ) - ); - } - } - // river downstream - this.addEntry( - new SelectEntry( - this._entriesBaseId + "none", - undefined, - ServiceFactory.i18nService.localizeText("INFO_LIB_AVAL") - ) - ); - break; - } - } - - protected setDefaultValue() { - // default to first available entry if any - if (this._entries.length > 0) { - if (this._multiple) { - this.setValue([this._entries[0]]); - } else { - this.setValue(this._entries[0]); - } - } else { - // notify observers that no value is selected anymore - this.notifyValueChanged(); - } - } - - /** - * Once config is parsed, init original value from model - * (needs config, for this._entriesBaseId to be set) - */ - protected afterParseConfig() { - this.populate(); - this.initSelectedValue(); - } - - /** - * Reloads available entries, trying to keep the current selected - * value; does not notify observers if value did not change - */ - public updateEntries() { - // store previous selected entry - const pse = this._selectedEntry; - // empty - this.clearEntries(); - // populate - this.populate(); - // if no entry is available anymore, unset value - if (this.entries.length === 0) { - if (this.multiple) { - super.setValue([]); - } else { - super.setValue(undefined); - } - } else { - // keep previously selected entry(ies) if possible - if (pse) { - if (!Array.isArray(pse) && pse.id) { - this.setValueFromId(pse.id); - } else if (Array.isArray(pse) && pse.length > 0) { - this.setValueFromId(pse.map((e) => e.id)); - } else { - this.setDefaultValue(); - } - } else { - this.setDefaultValue(); - } - } - } - - /** - * Updates selectedValue; notifies observers only if - * value.id has changed - */ - public setValue(v: SelectEntry | SelectEntry[]) { - const previousSelectedEntry = this._selectedEntry; - this._selectedEntry = v; - // if value changed - const valueChanged = ( - !previousSelectedEntry - || ( - !Array.isArray(previousSelectedEntry) - && !Array.isArray(v) - && previousSelectedEntry.id !== v.id - ) - || ( - Array.isArray(previousSelectedEntry) - && Array.isArray(v) - && !arraysAreEqual(previousSelectedEntry, v, "id", true) - ) - ); - if (valueChanged) { - this.notifyValueChanged(); - } - } - - /** - * Sets value from given ID(s); if it was not found, sets the - * first available entry as selectedValue - */ - public setValueFromId(id: string | string[]) { - let found = false; - const entries = []; - if (this._multiple && Array.isArray(id)) { - for (const e of this._entries) { - if (id.includes(e.id)) { - entries.push(e); - found = true; - } - } - this.setValue(entries); - } else { - for (const e of this._entries) { - if (e.id === id) { - found = true; - this.setValue(e); - } - } - } - if (!found) { - this.setDefaultValue(); - } - } - -} diff --git a/src/app/formulaire/elements/select-field.ts b/src/app/formulaire/elements/select-field.ts deleted file mode 100644 index b0833fb6193458f4e52c4ccbb42b4c2a95b49528..0000000000000000000000000000000000000000 --- a/src/app/formulaire/elements/select-field.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { - CourbeRemous, - Nub, - ParallelStructure, - StructureType, - LoiDebit, - Session, - Solveur, - StructureProperties - } from "jalhyd"; - -import { Field } from "./field"; -import { SelectEntry } from "./select-entry"; -import { FormulaireNode } from "./formulaire-node"; -import { FormulaireDefinition } from "../definition/form-definition"; -import { ServiceFactory } from "../../services/service-factory"; - -export class SelectField extends Field { - - /** string to build the select entries IDs from */ - protected _entriesBaseId: string; - - protected _entries: SelectEntry[]; - - protected _selectedEntry: SelectEntry | SelectEntry[]; - - /** name of the Nub property associated to this field, if any */ - protected _associatedProperty: string; - - /** default value for this field */ - protected _defaultValue: string; - - /** if true, user can select multiple values */ - protected _multiple = false; - - /** if true, select box is grayed out */ - public disabled = false; - - /** source identifier for populating with available values */ - protected source: string; - - /** - * message to display below the select field when no option is selected, - * if this message is defined and not empty - */ - protected _messageWhenEmpty: string; - - constructor(parent: FormulaireNode) { - super(parent); - this.clearEntries(); - } - - public get entriesBaseId(): string { - return this._entriesBaseId; - } - - public get entries() { - return this._entries; - } - - public get associatedProperty(): string { - return this._associatedProperty; - } - - public get defaultValue(): string { - return this._defaultValue; - } - - public get multiple(): boolean { - return this._multiple; - } - - public get messageWhenEmpty(): string { - let msg: string; - if (this._selectedEntry === undefined && this._messageWhenEmpty) { - msg = ServiceFactory.i18nService.localizeText(this._messageWhenEmpty); - } - return msg; - } - - public clearEntries() { - this._entries = []; - } - - public addEntry(e: SelectEntry) { - this._entries.push(e); - if (! this._selectedEntry) { - if (this._multiple) { - this.setValue([ e ]); - } else { - this.setValue(e); - } - } - } - - /** - * Triggered at the end of parseConfig() - */ - protected afterParseConfig() { } - - public getSelectedEntryFromValue(val: any): SelectEntry { - for (const se of this._entries) { - if (se.value === val) { - return se; - } - } - } - - public getValue(): SelectEntry | SelectEntry[] { - return this._selectedEntry; - } - - public setValue(v: SelectEntry | SelectEntry[]) { - if (this._selectedEntry !== v) { - this._selectedEntry = v; - this.notifyValueChanged(); - } - } - - public notifyValueChanged() { - this.notifyObservers({ - "action": "select", - "value": this._selectedEntry - }, this); - } - - public updateLocalisation() { - super.updateLocalisation(); - for (const e of this._entries) { - if (this.source === "solveur_targetted_result") { - // @WARNING clodo hack for Solveur - // 1. calculated param - const nub: Nub = (this.parentForm as FormulaireDefinition).currentNub; - const ntc = (nub as Solveur).nubToCalculate; - if (e.value !== undefined && ntc !== undefined) { - if (e.value === "" && ntc.calculatedParam !== undefined) { - const varName = ServiceFactory.formulaireService.expandVariableName(ntc.calcType, ntc.calculatedParam.symbol); - e.label = `${varName} (${ntc.calculatedParam.symbol})`; - } else { - // 2. extra results - const varName = ServiceFactory.formulaireService.expandVariableName(ntc.calcType, e.value); - e.label = `${varName} (${e.value})`; - } - } - } else { - // general case - const aId = e.id.split("_"); - const trad = ServiceFactory.formulaireService.localizeText( - `${aId[1].toUpperCase()}_${aId[2]}`, - this.parentForm.currentNub.calcType - ); - e.label = trad; - } - } - } - - public parseConfig(field: {}, data?: {}) { - this._confId = field["id"]; - this._entriesBaseId = this._confId + "_"; - this._helpLink = field["help"]; - this._associatedProperty = field["property"]; - this._defaultValue = field["default"]; - this._messageWhenEmpty = field["messageWhenEmpty"]; - if (field["multiple"] !== undefined) { - this._multiple = field["multiple"]; - } - this.source = field["source"]; - - this.loadEntriesFromSource(); - - this.afterParseConfig(); - } - - /** - * Adds available entries to the selector, depending on the "source" identifier - */ - protected loadEntriesFromSource() { - const nub: Nub = (this.parentForm as FormulaireDefinition).currentNub; - // ad-hoc cases - switch (this.source) { - // driven by string[], not enum (easier for variable names) - case "remous_target": - this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); - for (const at of CourbeRemous.availableTargets) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + at, at); - this.addEntry(e); - } - break; - - // driven by string[], not enum - case "solveur_targetted_result": - // @WARNING for localisation, @see hack in this.updateLocalisation() - // 1. calculated param - const ntc = (nub as Solveur).nubToCalculate; - if (ntc?.calculatedParam !== undefined) { // some nubs have no calculatedParam, for ex. SectionParam - this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); - } - // 2. extra results - if (ntc?.resultsFamilies !== undefined) { - for (const er of Object.keys(ntc.resultsFamilies)) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + er, er); - this.addEntry(e); - } - } - break; - - // possible values depend on CalcType - case "device_structure_type": - for (const st in (nub as ParallelStructure).getLoisAdmissibles()) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + st, StructureType[st]); - this.addEntry(e); - } - break; - - // possible values depend on CalcType - case "device_loi_debit": - // get current structure type from appropriate Nub child - const child = nub.getChildren()[this.parent.indexAsKid()]; - const la = (nub as ParallelStructure).getLoisAdmissibles(); - const loiDebit = child.properties.getPropValue("loiDebit"); - const stCode = StructureProperties.findCompatibleStructure(loiDebit, nub as ParallelStructure); - const stName = StructureType[stCode]; - if (la[stName] !== undefined) { - for (const ld of la[stName]) { - const e: SelectEntry = new SelectEntry(this._entriesBaseId + LoiDebit[ld], ld); - this.addEntry(e); - } - } - break; - - // general case : property values taken from an enum - default: - // find enum associated to property - const enumClass = Session.enumFromProperty[this._associatedProperty]; - if (enumClass !== undefined) { - // add one select entry per enum entry, in the enum order - for (let j = 0; j < Object.keys(enumClass).length / 2; j++) { - this.addEntry(new SelectEntry(this._entriesBaseId + j, j)); - } - } - } - } - -} diff --git a/src/app/formulaire/elements/select-entry.ts b/src/app/formulaire/elements/select/select-entry.ts similarity index 100% rename from src/app/formulaire/elements/select-entry.ts rename to src/app/formulaire/elements/select/select-entry.ts diff --git a/src/app/formulaire/elements/select/select-field-charttype.ts b/src/app/formulaire/elements/select/select-field-charttype.ts new file mode 100644 index 0000000000000000000000000000000000000000..b6f35d3f932c8026a4dee3cc6297e81b1b14adf5 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-charttype.ts @@ -0,0 +1,53 @@ +import { ChartType } from "app/results/chart-type"; +import { I18nService } from "app/services/internationalisation.service"; +import { FormulaireNode } from "../formulaire-node"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +export class SelectFieldChartType extends SelectField { + private _entryValues: ChartType[] = [ChartType.Histogram, ChartType.Dots, ChartType.Scatter]; + + constructor(parent: FormulaireNode, private intlService: I18nService) { + super(parent); + this._confId = "chart-type"; + this._multiple = false; + } + + public get entryValues(): ChartType[] { + return this._entryValues; + } + + protected populate() { + for (const v of this._entryValues) { + const id: string = ChartType[v]; + this.addEntry(new SelectEntry(id, v)); + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } + + public updateLocalisation() { + for (const e of this._entries) { + switch (e.value) { + case ChartType.Histogram: + e.label = this.intlService.localizeText("INFO_PARAMFIELD_CHART_TYPE_HISTOGRAM"); + break; + + case ChartType.Dots: + e.label = this.intlService.localizeText("INFO_PARAMFIELD_CHART_TYPE_DOTS"); + break; + + case ChartType.Scatter: + e.label = "XY"; + break; + + default: + throw new Error("SelectFieldChartType localisation not properly defined"); + } + } + + this._label = this.intlService.localizeText("INFO_PARAMFIELD_CHART_TYPE"); + } +} \ No newline at end of file diff --git a/src/app/formulaire/elements/select/select-field-device-loi-debit.ts b/src/app/formulaire/elements/select/select-field-device-loi-debit.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c556ff0a54de8e207e0d6e077e53f3f198d9039 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-device-loi-debit.ts @@ -0,0 +1,60 @@ +import { LoiDebit, ParallelStructure, StructureProperties, StructureType } from "jalhyd"; +import { SelectField } from "./select-field"; +import { SelectEntry } from "./select-entry"; +import { FormulaireNode } from "../formulaire-node"; + +/* + "id": "select_loidebit", + "type": "select", + "property": "loiDebit", + "source": "device_loi_debit", + "help": { +*/ + +export class SelectFieldDeviceLoiDebit extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + this._associatedProperty = "loiDebit"; + } + + public parseConfig(json: {}, data?: {}) { + super.parseConfig(json, data); + this._configDefaultValue = json["default"]; + } + + /** + * get this select's nub + */ + protected get nub(): ParallelStructure { + return super.nub as ParallelStructure; + } + + /** + * get discharge law linked to this select's nub + */ + private get loiDebit(): LoiDebit { + const child = this.nub.getChildren()[this.parent.indexAsKid()]; + return child.properties.getPropValue("loiDebit"); + } + + protected populate() { + // possible values depend on CalcType + + // get current structure type from appropriate Nub child + const la = this.nub.getLoisAdmissibles(); + const loiDebit: LoiDebit = this.loiDebit; + const stCode = StructureProperties.findCompatibleStructure(loiDebit, this.nub as ParallelStructure); + const stName = StructureType[stCode]; + if (la[stName] !== undefined) { + for (const ld of la[stName]) { + const e: SelectEntry = new SelectEntry(this._entriesBaseId + LoiDebit[ld], ld); + this.addEntry(e); + } + } + } + + protected initSelectedValue() { + // current discharge law from linked nub + this.findAndSetDefaultValue(LoiDebit[this.loiDebit]); + } +} diff --git a/src/app/formulaire/elements/select/select-field-device-structure-type.ts b/src/app/formulaire/elements/select/select-field-device-structure-type.ts new file mode 100644 index 0000000000000000000000000000000000000000..aee9297606d7f0d756521e19b4d6c36ceecd8e85 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-device-structure-type.ts @@ -0,0 +1,35 @@ +import { ParallelStructure, StructureType } from "jalhyd"; +import { SelectField } from "./select-field"; +import { SelectEntry } from "./select-entry"; +import { FormulaireNode } from "../formulaire-node"; + +/* + "id": "select_structure", + "type": "select", + "property": "structureType", + "source": "device_structure_type" +*/ + +export class SelectFieldDeviceStructureType extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + this._associatedProperty = "structureType"; + } + + public parseConfig(json: {}, data?: {}) { + super.parseConfig(json, data); + this._configDefaultValue = json["default"]; + } + + protected populate() { + // possible values depend on CalcType + for (const st in (this.nub as ParallelStructure).getLoisAdmissibles()) { + const e: SelectEntry = new SelectEntry(this._entriesBaseId + st, StructureType[st]); + this.addEntry(e); + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } +} diff --git a/src/app/formulaire/elements/select/select-field-downstream-basin.ts b/src/app/formulaire/elements/select/select-field-downstream-basin.ts new file mode 100644 index 0000000000000000000000000000000000000000..05675917d12907d7dbd408e7a72dcd3062b5e1d5 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-downstream-basin.ts @@ -0,0 +1,45 @@ +import { ServiceFactory } from "app/services/service-factory"; +import { PbCloison, PreBarrage } from "jalhyd"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + "id": "select_downstream_basin", + "type": "select_custom", + "source": "downstream_basin" +*/ + +// PbCloisons, bassin aval +export class SelectFieldDownstreamBasin extends SelectField { + protected populate() { + const pbWallD = this.parentForm.currentNub as PbCloison; + const preBarrageD = pbWallD.parent as PreBarrage; + const posUb = pbWallD.bassinAmont?.findPositionInParent(); + // all available basins, depending on current upstream basin + for (const b of preBarrageD.bassins) { + const pos = b.findPositionInParent(); + if (posUb === undefined || pos > posUb) { + this.addEntry( + new SelectEntry( + this._entriesBaseId + b.uid, + b.uid, + ServiceFactory.i18nService.localizeMessage(b.description) + ) + ); + } + } + // river downstream + this.addEntry( + new SelectEntry( + this._entriesBaseId + "none", + undefined, + ServiceFactory.i18nService.localizeText("INFO_LIB_AVAL") + ) + ); + } + + protected initSelectedValue() { + const db = (this.nub as PbCloison).bassinAval; + this.setValueFromId(this._entriesBaseId + (db ? db.uid : "none")); + } +} diff --git a/src/app/formulaire/elements/select/select-field-factory.ts b/src/app/formulaire/elements/select/select-field-factory.ts new file mode 100644 index 0000000000000000000000000000000000000000..49fed0c91265f84c008fff570c024693adf85591 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-factory.ts @@ -0,0 +1,78 @@ +import { FormulaireNode } from "../formulaire-node"; +import { SelectField } from "./select-field"; +import { SelectFieldDeviceLoiDebit } from "./select-field-device-loi-debit"; +import { SelectFieldDeviceStructureType } from "./select-field-device-structure-type"; +import { SelectFieldRemousTarget } from "./select-field-remous-target"; +import { SelectFieldSolverTargetedResult } from "./select-field-solver-targeted-result"; +import { SelectFieldUpstreamBasin } from "./select-field-upstream-basin"; +import { SelectFieldDownstreamBasin } from "./select-field-downstream-basin"; +import { SelectFieldSolverTarget } from "./select-field-solveur-target"; +import { SelectFieldSearchedParam } from "./select-field-searched-param"; +import { SelectFieldSpeciesList } from "./select-field-species-list"; +import { SelectFieldTargetPass } from "./select-field-target-pass"; +import { SelectFieldNubProperty } from "./select-field-nub-prop"; + +export class SelectFieldFactory { + /** + * Id of the select linked to structure type (JSON calculator configuration). + * Also used in fieldset template parsing. + */ + public static readonly SelectId_StructureType: string ="select_structure"; + + /** + * Id of the select linked to stage discharge law (JSON calculator configuration). + * Also used in fieldset template parsing. + */ + public static readonly SelectId_LoiDebit: string ="select_loidebit"; + + public static newSelectField(json: {}, parent: FormulaireNode): SelectField { + switch (json["id"]) { + case "select_downstream_basin": + return new SelectFieldDownstreamBasin(parent); + + case SelectFieldFactory.SelectId_LoiDebit: + return new SelectFieldDeviceLoiDebit(parent); + + case "select_searched_param": + return new SelectFieldSearchedParam(parent); + + case "select_species_list": + return new SelectFieldSpeciesList(parent); + + case SelectFieldFactory.SelectId_StructureType: + return new SelectFieldDeviceStructureType(parent); + + case "select_target": + return new SelectFieldRemousTarget(parent); + + case "select_target_nub": + return new SelectFieldSolverTarget(parent); + + case "select_target_pass": + return new SelectFieldTargetPass(parent); + + case "select_target_result": + return new SelectFieldSolverTargetedResult(parent); + + case "select_upstream_basin": + return new SelectFieldUpstreamBasin(parent); + + case "select_divingjetsupported": + case "select_gridprofile": + case "select_gridtype": + case "select_material": + case "select_operation": + case "select_partype": + case "select_passtype": + case "select_regime": + case "select_resolution": + case "select_section": + case "select_sppoperation": + case "select_unit": + return new SelectFieldNubProperty(parent); + + default: + throw new Error("unknown select id ${id}"); + } + } +} diff --git a/src/app/formulaire/elements/select/select-field-nub-prop.ts b/src/app/formulaire/elements/select/select-field-nub-prop.ts new file mode 100644 index 0000000000000000000000000000000000000000..df62610a8a9c288d571618c59d954eec395d7546 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-nub-prop.ts @@ -0,0 +1,42 @@ +import { Session } from "jalhyd"; +import { FormulaireNode } from "../formulaire-node"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + par exemple : + "id": "select_regime", + "type": "select", + "property": "regime", + "default": "Fluvial" + +*/ + +// nub property values taken from an enum +// "standard" select that normally does not require customisation +export class SelectFieldNubProperty extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + } + + public parseConfig(json: {}, data?: {}) { + super.parseConfig(json, data); + this._associatedProperty = json["property"]; + this._configDefaultValue = json["default"]; + } + + protected populate() { + // find enum associated to property + const enumClass = Session.enumFromProperty[this._associatedProperty]; + if (enumClass !== undefined) { + // add one select entry per enum entry, in the enum order + for (let j = 0; j < Object.keys(enumClass).length / 2; j++) { + this.addEntry(new SelectEntry(this._entriesBaseId + j, j)); + } + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } +} diff --git a/src/app/formulaire/elements/select/select-field-remous-target.ts b/src/app/formulaire/elements/select/select-field-remous-target.ts new file mode 100644 index 0000000000000000000000000000000000000000..20b40cc473bbe7395462953dd359638a1f9e217a --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-remous-target.ts @@ -0,0 +1,32 @@ +import { CourbeRemous } from "jalhyd"; +import { SelectField } from "./select-field"; +import { SelectEntry } from "./select-entry"; +import { FormulaireNode } from "../formulaire-node"; + +/* + "id": "select_target", + "type": "select", + "property": "varCalc", + "source": "remous_target", + "help": { +*/ + +export class SelectFieldRemousTarget extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + this._associatedProperty = "varCalc"; + } + + protected populate() { + // driven by string[], not enum (easier for variable names) + this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); + for (const at of CourbeRemous.availableTargets) { + const e: SelectEntry = new SelectEntry(this._entriesBaseId + at, at); + this.addEntry(e); + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } +} diff --git a/src/app/formulaire/elements/select/select-field-searched-param.ts b/src/app/formulaire/elements/select/select-field-searched-param.ts new file mode 100644 index 0000000000000000000000000000000000000000..1342fb643e31b378ea799dc166f3277714d9b123 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-searched-param.ts @@ -0,0 +1,55 @@ +import { ServiceFactory } from "app/services/service-factory"; +import { decodeHtml } from "app/util"; +import { acSection, Nub, Solveur } from "jalhyd"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + "id": "select_searched_param", + "type": "select_custom", + "source": "solveur_searched" +*/ + +// Solveur, paramètre recherché (à faire varier) +export class SelectFieldSearchedParam extends SelectField { + protected populate() { + const fs = ServiceFactory.formulaireService; + + // find all non-calculated, non-linked parameters of all Nubs that + // the current "target" Nub depends on (if any) + const solv = this.parentForm.currentNub as Solveur; + const ntc: Nub = solv.nubToCalculate; + const searchableParams = Solveur.getDependingNubsSearchableParams( + ntc, + solv.targettedResult !== undefined && solv.targettedResult !== "" + ); + for (const p of searchableParams) { + if (p.visible) { + const calc = fs.getFormulaireFromId(p.originNub.uid).calculatorName; + const varName = fs.expandVariableName(p.originNub.calcType, p.symbol); + const label = `${p.symbol} - ${varName} (${calc})`; + this.addEntry(new SelectEntry(this._entriesBaseId + p.getParentComputeNode(false).uid + "_" + p.symbol, p, decodeHtml(label))); + } + } + } + + protected initSelectedValue() { + const sp = (this.nub as Solveur).searchedParameter; + if (sp !== undefined) { + let parentNubUid; + const parentNub = sp.getParentComputeNode(false); + if (parentNub instanceof acSection) { + parentNubUid = parentNub.uid; + } + else { + parentNubUid = sp.nubUid + } + this.setValueFromId(this._entriesBaseId + parentNubUid + "_" + sp.symbol); + } + } + + public updateLocalisation() { + // do not override localisation done in populate() + // ie. avoid what is done by SelectField.updateLocalisation() + } +} diff --git a/src/app/formulaire/elements/select/select-field-solver-targeted-result.ts b/src/app/formulaire/elements/select/select-field-solver-targeted-result.ts new file mode 100644 index 0000000000000000000000000000000000000000..a547bde3c6e1036f697281d8449d6b0df74b2fcf --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-solver-targeted-result.ts @@ -0,0 +1,61 @@ +import { FormulaireDefinition } from "app/formulaire/definition/form-definition"; +import { ServiceFactory } from "app/services/service-factory"; +import { Nub, Solveur } from "jalhyd"; +import { FormulaireElement } from "../formulaire-element"; +import { SelectField } from "./select-field"; +import { SelectEntry } from "./select-entry"; +import { FormulaireNode } from "../formulaire-node"; + +/* + "id": "select_target_result", + "type": "select", + "property": "targettedResult", + "source": "solveur_targetted_result", + "default": "" +*/ +export class SelectFieldSolverTargetedResult extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + this._associatedProperty = "targettedResult"; + } + + protected populate() { + // @WARNING for localisation, @see hack in this.updateLocalisation() + // 1. calculated param + const ntc = (this.nub as Solveur).nubToCalculate; + if (ntc?.calculatedParam !== undefined) { // some nubs have no calculatedParam, for ex. SectionParam + this.addEntry(new SelectEntry(this._entriesBaseId + "none", "")); + } + // 2. extra results + if (ntc?.resultsFamilies !== undefined) { + for (const er of Object.keys(ntc.resultsFamilies)) { + const e: SelectEntry = new SelectEntry(this._entriesBaseId + er, er); + this.addEntry(e); + } + } + } + + protected initSelectedValue() { + this.findAndSetDefaultValue(); + } + + public updateLocalisation() { + FormulaireElement.prototype.updateLocalisation.call(this); + for (const e of this._entries) { + // @WARNING clodo hack for Solveur + // 1. calculated param + const nub: Nub = (this.parentForm as FormulaireDefinition).currentNub; + const ntc = (nub as Solveur).nubToCalculate; + if (e.value !== undefined && ntc !== undefined) { + if (e.value === "" && ntc.calculatedParam !== undefined) { + const varName = ServiceFactory.formulaireService.expandVariableName(ntc.calcType, ntc.calculatedParam.symbol); + e.label = `${varName} (${ntc.calculatedParam.symbol})`; + } else { + // 2. extra results + const varName = ServiceFactory.formulaireService.expandVariableName(ntc.calcType, e.value); + e.label = `${varName} (${e.value})`; + } + } + } + } +} diff --git a/src/app/formulaire/elements/select/select-field-solveur-target.ts b/src/app/formulaire/elements/select/select-field-solveur-target.ts new file mode 100644 index 0000000000000000000000000000000000000000..aecd238b8d87d8836879bedf48ea707e21894e40 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-solveur-target.ts @@ -0,0 +1,54 @@ +/* + "id": "select_target_nub", + "type": "select_custom", + "source": "solveur_target" +*/ + +import { ServiceFactory } from "app/services/service-factory"; +import { decodeHtml } from "../../../util"; +import { Session, Solveur } from "jalhyd"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +// Solveur, module cible (à calculer) +export class SelectFieldSolverTarget extends SelectField { + protected populate() { + + const fs = ServiceFactory.formulaireService; + + // find all Nubs having at least one link to another Nub's result + const candidateNubs: any[] = + Session.getInstance().getDownstreamNubs().concat( + Session.getInstance().getUpstreamNubsHavingExtraResults() + ).filter( + (element, index, self) => self.findIndex((e) => e.uid === element.uid) === index + ); + for (const cn of candidateNubs) { + const nub = fs.getFormulaireFromId(cn.uid); + if (nub) { + const calc = nub.calculatorName; + let label = calc; + // calculated param + if (cn.calculatedParam !== undefined) { + const varName = fs.expandVariableName(cn.calcType, cn.calculatedParam.symbol); + label += ` / ${varName} (${cn.calculatedParam.symbol})`; + } + this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); + } else { + // silent fail, this Solveur nub was probably loaded before all the candidate nubs are done loading + } + } + } + + protected initSelectedValue() { + const ntc = (this.nub as Solveur).nubToCalculate; + if (ntc !== undefined) { + this.setValueFromId(this._entriesBaseId + ntc.uid); + } + } + + public updateLocalisation() { + // do not override localisation done in populate() + // ie. avoid what is done by SelectField.updateLocalisation() + } +} diff --git a/src/app/formulaire/elements/select/select-field-species-list.ts b/src/app/formulaire/elements/select/select-field-species-list.ts new file mode 100644 index 0000000000000000000000000000000000000000..602a5685493dbfd44762bfa4660e81d92e65ec9f --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-species-list.ts @@ -0,0 +1,65 @@ +/* +"type": "select_custom", +"id": "select_species_list", +"source": "verificateur_species", +"multiple": true +*/ + +import { ServiceFactory } from "app/services/service-factory"; +import { CalculatorType, FishSpecies, Session, Verificateur } from "jalhyd"; +import { sprintf } from "sprintf-js"; +import { FormulaireNode } from "../formulaire-node"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +// Vérificateur, liste d'espèces (choix multiple) +export class SelectFieldSpeciesList extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + this._multiple = true; + } + + protected populate() { + // add UIDs of all Espece type Nubs in the session + const especeNubs = Session.getInstance().getAllNubs().filter((element) => element.calcType === CalculatorType.Espece); + for (const en of especeNubs) { + const form = ServiceFactory.formulaireService.getFormulaireFromNubId(en.uid); + this.addEntry( + new SelectEntry( + this._entriesBaseId + en.uid, + en.uid, + sprintf( + ServiceFactory.i18nService.localizeText("INFO_VERIFICATEUR_CUSTOM_SPECIES"), + form ? form.calculatorName : "*** form not loaded yet ***" + ) + ) + ); + } + // add all FishSpecies + for (let j = 1; j < Object.keys(FishSpecies).length / 2; j++) { // exclude "0" (SPECIES_CUSTOM) + const spgId = FishSpecies[j].substring(FishSpecies[j].lastIndexOf("_") + 1); + this.addEntry( + new SelectEntry( + this._entriesBaseId + spgId, + FishSpecies[j], + ServiceFactory.i18nService.localizeText("INFO_ENUM_" + FishSpecies[j]) + ) + ); + } + } + + protected initSelectedValue() { + const sl = (this.nub as Verificateur).speciesList; + if (sl !== undefined) { + this.setValueFromId(sl.map((s) => { + const spgId = s.substring(s.lastIndexOf("_") + 1); + return this._entriesBaseId + spgId; + })); + } + } + + public updateLocalisation() { + // do not override localisation done in populate() + // ie. avoid what is done by SelectField.updateLocalisation() + } +} diff --git a/src/app/formulaire/elements/select/select-field-target-pass.ts b/src/app/formulaire/elements/select/select-field-target-pass.ts new file mode 100644 index 0000000000000000000000000000000000000000..10cd639a1b125fec2a7af633bfeb4cbde056f07d --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-target-pass.ts @@ -0,0 +1,54 @@ +import { ServiceFactory } from "app/services/service-factory"; +import { decodeHtml } from "app/util"; +import { CalculatorType, FishPass, Session, Verificateur } from "jalhyd"; +import { FormulaireElement } from "../formulaire-element"; +import { FormulaireNode } from "../formulaire-node"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + "id": "select_target_pass", + "type": "select_custom", + "source": "verificateur_target", + "messageWhenEmpty": "INFO_VERIF_CREATE_PASS_FRIST" +*/ + +// Vérificateur, passe cible (à vérifier) +export class SelectFieldTargetPass extends SelectField { + constructor(parent: FormulaireNode) { + super(parent); + this._messageWhenEmpty = "INFO_VERIF_CREATE_PASS_FRIST"; + } + + protected populate() { + const fs = ServiceFactory.formulaireService; + // find all Nubs of type FishPass + const candidateNubs: any[] = Session.getInstance().getAllNubs().filter((element) => { + return ( + (element instanceof FishPass) + && element.calcType !== CalculatorType.Par // ParSimulation extends Par @TODO find something better + ); + }); + for (const cn of candidateNubs) { + const nub = fs.getFormulaireFromId(cn.uid); + if (nub) { + const label = nub.calculatorName + " (" + fs.getLocalisedTitleFromCalculatorType(nub.calculatorType) + ")"; + this.addEntry(new SelectEntry(this._entriesBaseId + cn.uid, cn.uid, decodeHtml(label))); + } else { + // silent fail, this Verificateur nub was probably loaded before all the candidate nubs are done loading + } + } + } + + protected initSelectedValue() { + const ntv = (this.nub as Verificateur).nubToVerify; + if (ntv !== undefined) { + this.setValueFromId(this._entriesBaseId + ntv.uid); + } + } + + public updateLocalisation() { + // do not override localisation done in populate() + // ie. avoid what is done by SelectField.updateLocalisation() + } +} diff --git a/src/app/formulaire/elements/select/select-field-upstream-basin.ts b/src/app/formulaire/elements/select/select-field-upstream-basin.ts new file mode 100644 index 0000000000000000000000000000000000000000..0eeb6b605b2e3565baf8bcb20bacead7bc7825d8 --- /dev/null +++ b/src/app/formulaire/elements/select/select-field-upstream-basin.ts @@ -0,0 +1,45 @@ +import { ServiceFactory } from "app/services/service-factory"; +import { PbCloison, PreBarrage } from "jalhyd"; +import { SelectEntry } from "./select-entry"; +import { SelectField } from "./select-field"; + +/* + "id": "select_upstream_basin", + "type": "select_custom", + "source": "upstream_basin" +*/ + +// PbCloisons, bassin amont +export class SelectFieldUpstreamBasin extends SelectField { + protected populate() { + const pbWallU = this.parentForm.currentNub as PbCloison; + const preBarrageU = pbWallU.parent as PreBarrage; + const posDb = pbWallU.bassinAval?.findPositionInParent(); + // river upstream + this.addEntry( + new SelectEntry( + this._entriesBaseId + "none", + undefined, + ServiceFactory.i18nService.localizeText("INFO_LIB_AMONT") + ) + ); + // all available basins, depending on current downstream basin + for (const b of preBarrageU.bassins) { + const pos = b.findPositionInParent(); + if (posDb === undefined || pos < posDb) { + this.addEntry( + new SelectEntry( + this._entriesBaseId + b.uid, + b.uid, + ServiceFactory.i18nService.localizeMessage(b.description) + ) + ); + } + } + } + + protected initSelectedValue() { + const ub = (this.nub as PbCloison).bassinAmont; + this.setValueFromId(this._entriesBaseId + (ub ? ub.uid : "none")); + } +} diff --git a/src/app/formulaire/elements/select/select-field.ts b/src/app/formulaire/elements/select/select-field.ts new file mode 100644 index 0000000000000000000000000000000000000000..5e490c754d4246759a7ffd9e820b50be973188cd --- /dev/null +++ b/src/app/formulaire/elements/select/select-field.ts @@ -0,0 +1,283 @@ +import { Field } from "../field"; +import { SelectEntry } from "./select-entry"; +import { arraysAreEqual } from "../../../util"; +import { FormulaireNode } from "../formulaire-node"; +import { ServiceFactory } from "app/services/service-factory"; +import { FormulaireDefinition } from "../../definition/form-definition"; +import { enumValueFromString, Nub } from "jalhyd"; + +export abstract class SelectField extends Field { + + /** + * select options + */ + protected _entries: SelectEntry[]; + + /** + * currently selected option + */ + protected _selectedEntry: SelectEntry | SelectEntry[]; + + /** if true, user can select multiple values */ + protected _multiple = false; + + /** string to build the select entries IDs from */ + protected _entriesBaseId: string; + + /** name of the Nub property associated to this field, if any */ + protected _associatedProperty: string; + + /** default value (from configuration) for this field */ + protected _configDefaultValue: string; + + /** + * message to display below the select field when no option is selected, + * if this message is defined and not empty + */ + protected _messageWhenEmpty: string; + + constructor(parent: FormulaireNode) { + super(parent); + this.clearEntries(); + } + + /** + * associated nub + */ + protected get nub(): Nub { + return (this.parentForm as FormulaireDefinition).currentNub; + } + + public getValue(): SelectEntry | SelectEntry[] { + return this._selectedEntry; + } + + /** + * Updates _selectedEntry; notifies observers only if + * value.id has changed + */ + public setValue(v: SelectEntry | SelectEntry[]) { + // if multiple is true, value must be an array + if (this._multiple && !Array.isArray(v)) { + throw new Error("select value is not an array"); + } + + const previousSelectedEntry = this._selectedEntry; + this._selectedEntry = v; + + // if old and new values are not both undefined + if (!(previousSelectedEntry === undefined && v === undefined)) { + // if value changed + const valueChanged = ( + (previousSelectedEntry === undefined && v !== undefined) + || ( + !Array.isArray(previousSelectedEntry) + && !Array.isArray(v) + && previousSelectedEntry.id !== v.id + ) + || ( + Array.isArray(previousSelectedEntry) + && Array.isArray(v) + && !arraysAreEqual(previousSelectedEntry, v, "id", true) + ) + ); + if (valueChanged) { + this.notifyValueChanged(); + } + } + } + + public notifyValueChanged() { + this.notifyObservers({ + "action": "select", + "value": this._selectedEntry + }, this); + } + + /** + * Sets value from given entry id(s); if it was not found, sets the + * first available entry as selectedValue + */ + protected setValueFromId(id: string | string[]) { + let found = false; + const entries = []; + if (Array.isArray(id)) { + for (const e of this._entries) { + if (id.includes(e.id)) { + entries.push(e); + found = true; + } + } + this.setValue(entries); + } else { + for (const e of this._entries) { + if (e.id === id) { + found = true; + this.setValue(e); + } + } + } + if (!found) { + this.findAndSetDefaultValue(); + } + } + + /** + * try to find a default value to select + */ + protected findAndSetDefaultValue(value?: string) { + // default to first available entry if any + if (this._entries.length > 0) { + let val: SelectEntry; + if (value !== undefined) { + val = this.getEntryFromValue(enumValueFromString(this._associatedProperty, value)); + } else if (this._configDefaultValue === undefined) { + val = this._entries[0]; + } else { + val = this.getEntryFromValue(enumValueFromString(this._associatedProperty, this._configDefaultValue)); + if (val === undefined) { + throw Error("invalid select default value " + this._configDefaultValue + " for " + this._associatedProperty + " property"); + } + } + if (this._multiple) { + this.setValue([val]); + } else { + this.setValue(val); + } + } else { + // notify observers that no value is selected anymore + this.notifyValueChanged(); + } + } + + public parseConfig(field: {}, data?: {}) { + this._confId = field["id"]; + this._entriesBaseId = this._confId + "_"; + this._helpLink = field["help"]; + //this.afterParseConfig(); // done in FieldSet.parse_select() + } + + /** + * Once config is parsed, init original value from model + * (needs config for this._entriesBaseId to be set). + * Triggered at the end of parseConfig() + */ + public afterParseConfig() { + this.populate(); + this.initSelectedValue(); + } + + /** + * fill select with options + */ + protected abstract populate(); + + /** + * initialise select (loads UI with the value held by the model) + */ + protected abstract initSelectedValue(); + + /** + * associated nub property + */ + public get associatedProperty(): string { + return this._associatedProperty; + } + + /** + * @returns true if select field is associated to a nub property + */ + public get hasAssociatedNubProperty(): boolean { + return this._associatedProperty !== undefined; + } + + /** + * default value from configuration + */ + public get configDefaultValue(): string { + return this._configDefaultValue; + } + + private clearEntries() { + this._entries = []; + } + + public get entries() { + return this._entries; + } + + protected addEntry(e: SelectEntry) { + this._entries.push(e); + } + + public getEntryFromValue(val: any): SelectEntry { + for (const se of this._entries) { + if (se.value === val) { + return se; + } + } + } + + /** + * Reloads available entries, trying to keep the current selected + * value; does not notify observers if value did not change + */ + public updateEntries() { + // store previous selected entry + const pse = this._selectedEntry; + // empty + this.clearEntries(); + // populate + this.populate(); + this.updateLocalisation(); + // if no entry is available anymore, unset value + if (this._entries.length === 0) { + if (this._multiple) { + this.setValue([]); + } else { + this.setValue(undefined); + } + } else { + // keep previously selected entry(ies) if possible + if (pse) { + if (!Array.isArray(pse) && pse.id) { + this.setValueFromId(pse.id); + } else if (Array.isArray(pse) && pse.length > 0) { + this.setValueFromId(pse.map((e) => e.id)); + } else { + this.findAndSetDefaultValue(); + } + } else { + this.findAndSetDefaultValue(); + } + } + } + + public get entriesBaseId(): string { + return this._entriesBaseId; + } + + public get messageWhenEmpty(): string { + let msg: string; + if (this._selectedEntry === undefined && this._messageWhenEmpty) { + msg = ServiceFactory.i18nService.localizeText(this._messageWhenEmpty); + } + return msg; + } + + public get multiple(): boolean { + return this._multiple; + } + + public updateLocalisation() { + super.updateLocalisation(); + for (const e of this._entries) { + const aId = e.id.split("_"); + const trad = ServiceFactory.formulaireService.localizeText( + `${aId[1].toUpperCase()}_${aId[2]}`, + this.parentForm.currentNub.calcType + ); + e.label = trad; + } + } +} diff --git a/src/app/formulaire/form-iterator/deep-selectfield-iterator.ts b/src/app/formulaire/form-iterator/deep-selectfield-iterator.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c854754391759334a0e90dbdf8343aca72b197f --- /dev/null +++ b/src/app/formulaire/form-iterator/deep-selectfield-iterator.ts @@ -0,0 +1,19 @@ +import { AbstractFormulaireNodeIterator } from "./abstract-node-iterator"; +import { FormulaireNode } from "../elements/formulaire-node"; +import { SelectField } from "../elements/select/select-field"; + +/** + * itérateur qui extrait récursivement les SelectField dans un tableau de FormulaireElement + * (qui peut contenir des FieldsetContainer) + */ +export class DeepSelectFieldIterator extends AbstractFormulaireNodeIterator<SelectField> implements IterableIterator<SelectField> { + protected isIterable(fe: FormulaireNode) { + return fe instanceof SelectField; + } + + // interface IterableIterator + + [Symbol.iterator](): IterableIterator<SelectField> { + return this; + } +} diff --git a/src/app/services/formulaire.service.ts b/src/app/services/formulaire.service.ts index 4c9e42baa9e5dc138d02427ff9d4d4ad1520bad8..83020d52734284438d807e65e6f402cb4fcc3127 100644 --- a/src/app/services/formulaire.service.ts +++ b/src/app/services/formulaire.service.ts @@ -32,7 +32,7 @@ import { NotificationsService } from "./notifications.service"; import { FormulaireDefinition } from "../formulaire/definition/form-definition"; import { FormulaireElement } from "../formulaire/elements/formulaire-element"; import { InputField } from "../formulaire/elements/input-field"; -import { SelectField } from "../formulaire/elements/select-field"; +import { SelectField } from "../formulaire/elements/select/select-field"; import { FormulaireSectionParametree } from "../formulaire/definition/form-section-parametree"; import { FormulaireCourbeRemous } from "../formulaire/definition/form-courbe-remous"; import { FormulaireParallelStructure } from "../formulaire/definition/form-parallel-structures";