diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9c8da9ef4ba33e073c2426f0966c5b0d71f2dadd..8b33c6b0929c3398bc015dc8ae43007fa5d38f32 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -20,7 +20,8 @@ import { ParamFieldLineComponent } from './components/param-field-line/param-fie import { NgParamMinComponent } from './components/param-values/ngparam-min.component'; import { NgParamMaxComponent } from './components/param-values/ngparam-max.component'; import { NgParamStepComponent } from './components/param-values/ngparam-step.component'; -import { ParamValuesComponent, ValueListComponent } from './components/param-values/param-values.component'; +import { ParamValuesComponent } from './components/param-values/param-values.component'; +import { ValueListComponent } from './components/param-values/value-list.component'; import { SelectFieldLineComponent } from './components/select-field-line/select-field-line.component'; import { CheckFieldLineComponent } from './components/check-field-line/check-field-line.component'; // import { AlertDialog } from './components/alert-dialog/alert-dialog.component'; diff --git a/src/app/components/base/base.component.ts b/src/app/components/base/base.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..3d7cef25df2ae91b3ea70da0b5718ce23077e120 --- /dev/null +++ b/src/app/components/base/base.component.ts @@ -0,0 +1,39 @@ +import { Output, EventEmitter, AfterViewChecked } from "@angular/core"; + +export abstract class BaseComponent implements AfterViewChecked { + /** + * true après le 1er affichage du composant + */ + private _firstViewChecked = false; + + /** + * true si on souhaite qu'un événement soit émis en même temps que l'appel à afterFirstViewChecked() + */ + protected emitFirstViewCheck: boolean = false; + + /** + * événement émis en même temps que l'appel à afterFirstViewChecked() + */ + @Output() + private onFirstViewCheck: EventEmitter<void>; + + constructor() { + this.onFirstViewCheck = new EventEmitter(); + } + + public ngAfterViewChecked() { + if (!this._firstViewChecked) { + this._firstViewChecked = true; + + this.afterFirstViewChecked(); + + if (this.emitFirstViewCheck) + this.onFirstViewCheck.emit(); + } + } + + /** + * appelé une fois, après l'affichage complet du composant + */ + protected abstract afterFirstViewChecked(); +} \ No newline at end of file diff --git a/src/app/components/field-set/field-set.component.html b/src/app/components/field-set/field-set.component.html index 9539cd8f0b121a026d4fea761b36bb0af49ff754..463113c331f09ce54075c15c8a14eee1b2137f61 100644 --- a/src/app/components/field-set/field-set.component.html +++ b/src/app/components/field-set/field-set.component.html @@ -15,7 +15,7 @@ --> <ng-template ngFor let-p [ngForOf]="fields"> - <param-field-line *ngIf="p.isInput" [param]=p (onRadio)=onRadioClick($event)> + <param-field-line *ngIf="p.isInput" [param]=p (onRadio)=onRadioClick($event) (onValid)=onParamLineValid()> </param-field-line> <select-field-line *ngIf="p.isSelect" [param]=p (onSelectChange)=onSelectChanged($event)> diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts index 817875d6e58a34aff9b80f81febe1cac327168a4..108f4724d2a38c635e31cc584682136ed161ecdf 100644 --- a/src/app/components/field-set/field-set.component.ts +++ b/src/app/components/field-set/field-set.component.ts @@ -1,9 +1,11 @@ -import { Component, Input, Output, EventEmitter } from "@angular/core"; +import { Component, Input, Output, EventEmitter, ViewChildren, QueryList } from "@angular/core"; import { ParamDefinition } from "jalhyd"; import { NgParameter, ParamRadioConfig } from "../../formulaire/ngparam"; import { FieldSet } from "../../formulaire/fieldset"; +import { ParamFieldLineComponent } from "../param-field-line/param-field-line.component"; +import { BaseComponent } from "../base/base.component"; @Component({ selector: "field-set", @@ -19,7 +21,7 @@ import { FieldSet } from "../../formulaire/fieldset"; }` ] }) -export class FieldSetComponent { +export class FieldSetComponent extends BaseComponent { /** * field set attribute */ @@ -30,6 +32,20 @@ export class FieldSetComponent { this._fieldSet = fs; } + @ViewChildren(ParamFieldLineComponent) + private _paramComponents: QueryList<ParamFieldLineComponent>; + + /** + * événément de changement de validité + */ + @Output() + private onValid = new EventEmitter(); + + /** + * flag de validité de la saisie + */ + private _isValid: boolean = false; + private get fields() { return this._fieldSet.fields; } @@ -85,11 +101,13 @@ export class FieldSetComponent { * cf. https://angular.io/guide/component-interaction#parent-listens-for-child-event */ private onRadioClick(info: string) { - // console.log("FieldSetComponent " + info); // on renvoie l'info au parent this.onRadio.emit(info); } + /** + * événément de changement d'état d'un radio + */ @Output() private onRadio = new EventEmitter<string>(); @@ -100,6 +118,48 @@ export class FieldSetComponent { this.onSelectChange.emit(val); // on transmet au parent } + public get isValid() { + return this._isValid; + } + + /** + * calcul de la validité de tous les ParamFieldLineComponent de la vue + */ + private updateValidity() { + if (this._paramComponents != undefined) + this._isValid = this._paramComponents.reduce( + // callback + ( + // accumulator (valeur précédente du résultat) + acc, + // currentValue (élément courant dans le tableau) + param, + // currentIndex (indice courant dans le tableau) + currIndex, + // array (tableau parcouru) + array + ) => { + return acc && param.isValid; + } + // valeur initiale + , true); + } + + protected afterFirstViewChecked() { + this.updateValidity(); + } + + /** + * réception d'un événement de validité de ParamFieldLineComponent + */ + private onParamLineValid(event: boolean) { + this.updateValidity(); + this.onValid.emit(); + } + + /** + * événément de changement d'état d'un select + */ @Output() private onSelectChange = new EventEmitter<string>(); } diff --git a/src/app/components/generic-calculator/calculator.component.html b/src/app/components/generic-calculator/calculator.component.html index a1f621c7e5dee4beb1df02333a85d30cb696fdf2..70d12c9e82441ec2bc56c6179319ed17cb05ae12 100644 --- a/src/app/components/generic-calculator/calculator.component.html +++ b/src/app/components/generic-calculator/calculator.component.html @@ -16,13 +16,14 @@ <div class="container-fluid"> <!-- chapitres --> <field-set *ngFor="let fs of fieldSets" [style.display]="getFieldsetStyleDisplay(fs.id)" [fieldSet]=fs (onRadio)=onRadioClick($event) - (onSelectChange)=onSelectChanged($event)></field-set> + (onSelectChange)=onSelectChanged($event) (onValid)=OnFieldsetValid()></field-set> </div> <!-- bouton calculer --> <div class="row"> <div class="col-12 text-center"> - <button type="button" class="button_compute" name="Calculer" (click)="doCompute()">{{uitextCalculer}}</button> + <button type="button" [ngClass]="(isCalculateDisabled) ? 'button_compute_err' : 'button_compute_ok'" name="Calculer" (click)="doCompute()" + [disabled]="isCalculateDisabled">{{uitextCalculer}}</button> <p></p> <p></p> </div> diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index ef8d98b0506d959e27f1334b58a86a834120c7ec..d3b4ea580b7905a0e356a9e396e4afa08d7dcdf6 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, DoCheck, OnDestroy, ViewChild } from "@angular/core"; +import { Component, OnInit, DoCheck, OnDestroy, ViewChild, ViewChildren, QueryList } from "@angular/core"; import { ActivatedRoute } from '@angular/router'; import { FormulaireService } from "../../services/formulaire/formulaire.service"; @@ -9,24 +9,38 @@ import { CalculatorType } from "../../formulaire/formulaire-definition"; import { CalculatorResultsComponent } from "../../components/calculator-results/calculator-results.component"; import { Observer } from "../../services/observer"; import { Subscription } from "rxjs/Subscription"; +import { FieldSetComponent } from "../field-set/field-set.component"; +import { BaseComponent } from "../base/base.component"; @Component({ selector: 'hydrocalc', templateUrl: "./calculator.component.html", styles: [` - .button_compute { + .button_compute_ok { height: 3em; width: 30%; } + .button_compute_err { + height: 3em; + width: 30%; + color: red + } + #close-button { font-size: 1.5em; } ` ] }) -export class GenericCalculatorComponent implements OnInit, DoCheck, OnDestroy, Observer { +export class GenericCalculatorComponent extends BaseComponent implements OnInit, DoCheck, OnDestroy, Observer { + /** + * liste des FieldSetComponent + */ + @ViewChildren(FieldSetComponent) + private _fieldsetComponents: QueryList<FieldSetComponent>; + /** * composant d'affichage des résultats */ @@ -45,9 +59,23 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, OnDestroy, O */ private _computeClicked: boolean; + /** + * flag de validité gloable de la saisie + * NB : la validité du bouton "calculer" dépend de la validité de la saisie dans l'UI et de celle indiquée par le formulaire. + * La validité de l'UI comprend la forme (pas de chaîne alpha dans les champs numériques, etc..). + * La validité formulaire comprend le domaine de définition des valeurs saisies. + */ + private _isUIValid: boolean = false; + + /** + * flag disabled du bouton "calculer" + */ + private isCalculateDisabled: boolean = true; + constructor(private intlService: InternationalisationService, private formulaireService: FormulaireService, private route: ActivatedRoute) { + super(); } private get fieldSets(): FieldSet[] { @@ -125,13 +153,8 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, OnDestroy, O } ngDoCheck() { - // let q = this.getParamFromSymbol("Q"); // A VIRER !!!! - // if (q != undefined) { - // q.radioState = ParamRadioConfig.VAR; - // q.minValue = 1.5; - // q.maxValue = 6; - // q.stepValue = 0.3; - // } + if (this._formulaire != undefined) + this.isCalculateDisabled = !(this._formulaire.isValid && this._isUIValid); } ngOnDestroy() { @@ -244,4 +267,41 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, OnDestroy, O this._formulaire.updateNodeType(); this._formulaire.applyDependencies(); } + + /** + * appelé après le 1er affichage du composant + */ + protected afterFirstViewChecked() { + this.updateUIValidity(); + } + + /** + * calcul de la validité globale de la vue + */ + private updateUIValidity() { + if (this._fieldsetComponents != undefined) + this._isUIValid = this._fieldsetComponents.reduce( + // callback + ( + // accumulator (valeur précédente du résultat) + acc, + // currentValue (élément courant dans le tableau) + fieldset, + // currentIndex (indice courant dans le tableau) + currIndex, + // array (tableau parcouru) + array + ) => { + return acc && fieldset.isValid; + } + // valeur initiale + , true); + } + + /** + * réception d'un événement de validité d'un FieldSetComponent + */ + private OnFieldsetValid() { + this.updateUIValidity(); + } } diff --git a/src/app/components/generic-input/generic-input.component.html b/src/app/components/generic-input/generic-input.component.html index afdbb59469d4a0ee167607ad408bcce87af80fa1..a34a609b9bf3d9f2410134de45fb5ad2e02e7ac1 100644 --- a/src/app/components/generic-input/generic-input.component.html +++ b/src/app/components/generic-input/generic-input.component.html @@ -1,6 +1,6 @@ <div class="md-form form-sm"> - <input mdbActive type="text" id="form1" class="form-control" [disabled]="isDisabled" [ngModel]="uiValue" (ngModelChange)="setUIValue($event)"> + <input mdbActive type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue"> <!-- on utilise [innerHTML] pour que les codes HTML comme soient interprétés correctement --> <label for="form1" [innerHTML]="_title"></label> - <small class="text-danger" [innerHTML]="_message"></small> + <small *ngIf="showError" class="text-danger" [innerHTML]="errorMessage"></small> </div> \ No newline at end of file diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts index 18158cf9c85bda75646b11798b87c11f16259ddb..2077a6bbb78005e599216d64696aa783c7640b4d 100644 --- a/src/app/components/generic-input/generic-input.component.ts +++ b/src/app/components/generic-input/generic-input.component.ts @@ -1,4 +1,6 @@ -import { Input, Output, EventEmitter, OnInit } from "@angular/core"; +import { Input, Output, EventEmitter } from "@angular/core"; + +import { BaseComponent } from "../base/base.component"; /* exemple de template : @@ -12,14 +14,22 @@ exemple de template : /** * classe de gestion générique d'un champ de saisie avec titre, validation et message d'erreur + * définitions : + * - modèle : valeur gérée, indépendement de la façon dont elle est affichée + * - UI : interface utilisateur, présentation de la valeur gérée */ -export abstract class GenericInputComponent { //implements OnInit { +export abstract class GenericInputComponent extends BaseComponent { /** - * enable/disable input field + * flag de désactivation de l'input */ @Input('inputDisabled') private _inputDisabled: boolean = false; + /** + * flag d'affichage du message d'erreur + */ + public showError = true; + /** * chaîne affichée dans l'input quand aucune valeur n'est saisie */ @@ -27,61 +37,146 @@ export abstract class GenericInputComponent { //implements OnInit { private _title: string; /** - * valeur dans le contrôle (saisie par l'utilisateur) + * événement signalant un changement : valeur du modèle, validité, ... + */ + @Output() + protected onChange = new EventEmitter<any>(); + + /** + * valeur saisie. + * Cette variable n'est modifiée que lorsqu'on affecte le modèle ou que l'utilisateur fait une saisie + */ + private _uiValue: string; + + /** + * flag de validité de la saisie dans l'UI + * par ex : est ce bien une valeur numérique ? n'y a-t-il que des minuscules ? etc... + */ + private _isValidUI = false; + + /** + * flag de validité de la valeur du modèle + * par ex : la valeur saisie fait elle bien partie d'un domaine de définition donné ? date inférieure à une limite ? etc... */ - // private _uiValue: string; + private _isValidModel = false; /** - * valeur du modèle + * message d'erreur UI */ - // private _modelValue: any; + private _errorMessageUI: string; /** - * message d'erreur + * message d'erreur modèle */ - private _message: string; + private _errorMessageModel: string; private get isDisabled(): boolean { return this._inputDisabled; } - public validateModel() { - let { isValid, message } = this.validateModelValue(this.getModelValue()); - this._message = message; + /** + * événement de changement de la validité de la saisie + */ + private emitValidChanged() { + this.onChange.emit({ "action": "valid", "value": this.isValid }); + } + + /** + * calcul de la validité globale du composant (UI+modèle) + */ + public get isValid() { + return this._isValidUI && this._isValidModel; + } + + private setUIValid(b: boolean) { + const old = this.isValid; + this._isValidUI = b; + if (this.isValid != old) + this.emitValidChanged(); + } + + private validateUI() { + let { isValid, message } = this.validateUIValue(this._uiValue); + this._errorMessageUI = message; + this.setUIValid(isValid); return isValid; } + private setModelValid(b: boolean) { + const old = this.isValid; + this._isValidModel = b; + if (this.isValid != old) + this.emitValidChanged(); + } + + private validateModel() { + let { isValid, message } = this.validateModelValue(this.getModelValue()); + this._errorMessageModel = message; + this.setModelValid(isValid); + } + + public validate() { + this.validateUI(); + this.validateModel(); + } + + /** + * getter du message d'erreur affiché. + * L'erreur de forme (UI) est prioritaire + */ + private get errorMessage() { + if (this._errorMessageUI != undefined) + return this._errorMessageUI; + return this._errorMessageModel; + } + public get model(): any { return this.getModelValue(); } - public set model(v: any) { + /** + * événement de changement de la valeur du modèle + */ + private emitModelChanged() { + this.onChange.emit({ "action": "model", "value": this.getModelValue() }); + } + + private setAndValidateModel(v: any) { this.setModelValue(v); - this.validateModel() + this.emitModelChanged(); + + this.validateModel(); } - private validateUI(ui: string) { - let { isValid, message } = this.validateUIValue(ui); - this._message = message; - return isValid; + public set model(v: any) { + this.setAndValidateModel(v); + + // MAJ UI + this._uiValue = this.modelToUI(this.getModelValue()); + this.validateUI(); } private get uiValue() { - return this.modelToUI(this.getModelValue()); + return this._uiValue; } /* * fonction appelée lorsque l'utilisateur fait une saisie - * @param event valeur du contrôle - */ - private setUIValue(ui: any) { - if (this.validateUI(ui)) { - const m = this.uiToModel(ui); - this.setModelValue(m); - //this.validateModel() - let { isValid, message } = this.validateModelValue(m); - this._message = message; - } + * @param ui valeur dans le contrôle + */ + private set uiValue(ui: any) { + this._uiValue = ui; + if (this.validateUI()) + this.setAndValidateModel(this.uiToModel(ui)); + } + + /** + * appelé après le 1er affichage du composant + * @see BaseComponent + */ + protected afterFirstViewChecked() { + this._uiValue = this.modelToUI(this.getModelValue()); + this.validate(); } /** diff --git a/src/app/components/generic-select/generic-select.component.ts b/src/app/components/generic-select/generic-select.component.ts index 7dd5f113a48068b58506e731d1eb3308120994ea..c440f34221c9743d78e35035de0199d006cd4583 100644 --- a/src/app/components/generic-select/generic-select.component.ts +++ b/src/app/components/generic-select/generic-select.component.ts @@ -41,8 +41,11 @@ export abstract class GenericSelectComponent { } private onSelect(event: any) { - this._selectedValue = event.target.value; - this.selectChange.emit(this._selectedValue); + const val = event.target.value; + if (val != undefined) { + this._selectedValue = val; + this.selectChange.emit(this._selectedValue); + } } /** diff --git a/src/app/components/ngparam-input/ngparam-input.component.ts b/src/app/components/ngparam-input/ngparam-input.component.ts index adfaea812209eb87b33e2fc984af6de5634c3675..064101c27cdc6d60909ecd2e12122a6dfde39ef7 100644 --- a/src/app/components/ngparam-input/ngparam-input.component.ts +++ b/src/app/components/ngparam-input/ngparam-input.component.ts @@ -15,20 +15,27 @@ import { GenericInputComponent } from "../generic-input/generic-input.component" }) export class NgParamInputComponent extends GenericInputComponent { /** - * managed parameter + * paramètre géré */ @Input('param') private _paramDef: NgParameter; + /** + * valeur intermédiaire nécessitée par le fait que toutes les valeurs numériques ne sont pas légales + * pour NgParameter (l'affecter peut provoquer une exception) et qui permet de faire fonctionner la validation du modèle + */ + private _model: number; + constructor(private intlService: InternationalisationService) { super(); } protected getModelValue(): any { - return this._paramDef.getValue(); + return this._model; } protected setModelValue(v: any) { + this._model = v; try { this._paramDef.setValue(v); } @@ -75,4 +82,11 @@ export class NgParamInputComponent extends GenericInputComponent { protected uiToModel(ui: string) { return +ui; } + + protected afterFirstViewChecked() { + if (this._paramDef != undefined) + if (this._paramDef.isDefined) + this._model = this._paramDef.getValue(); + super.afterFirstViewChecked(); + } } diff --git a/src/app/components/param-field-line/param-field-line.component.html b/src/app/components/param-field-line/param-field-line.component.html index e99500c62ff0d4c964e41384055462f4d9a8d098..815da06bd4a4d83380c1a6764895e4005b08d01a 100644 --- a/src/app/components/param-field-line/param-field-line.component.html +++ b/src/app/components/param-field-line/param-field-line.component.html @@ -1,7 +1,7 @@ <div class="row"> <!-- input de saisie de la valeur --> <div class="col-12 col-sm-9 pt-3"> - <ngparam-input [inputDisabled]="isInputDisabled" [param]="_param" [title]="title"></ngparam-input> + <ngparam-input [inputDisabled]="isInputDisabled" [param]="_param" [title]="title" (onChange)="onInputChange($event)"></ngparam-input> </div> <div class="btn-group col" role="group"> @@ -30,4 +30,4 @@ </div> </div> -<param-values *ngIf="isRadioVarChecked" [param]="_param"></param-values> \ No newline at end of file +<param-values *ngIf="isRadioVarChecked" [param]="_param" (onValid)=onParamValuesValid($event)></param-values> \ No newline at end of file diff --git a/src/app/components/param-field-line/param-field-line.component.ts b/src/app/components/param-field-line/param-field-line.component.ts index 6e7a402ee82849529eb75bbed4ac269306e6730e..3a9c63c26204d449b5226c4447d78efdcc9a176b 100644 --- a/src/app/components/param-field-line/param-field-line.component.ts +++ b/src/app/components/param-field-line/param-field-line.component.ts @@ -1,8 +1,8 @@ -import { Component, ViewChild, Input, Output, EventEmitter } from "@angular/core"; +import { Component, ViewChild, Input, Output, EventEmitter, AfterViewChecked } from "@angular/core"; import { InternationalisationService } from "../../services/internationalisation/internationalisation.service"; import { NgParameter, ParamRadioConfig } from "../../formulaire/ngparam"; -import { GenericInputComponent } from "../generic-input/generic-input.component"; +import { NgParamInputComponent } from "../ngparam-input/ngparam-input.component"; @Component({ selector: "param-field-line", @@ -24,11 +24,28 @@ import { GenericInputComponent } from "../generic-input/generic-input.component" }` ] }) -export class ParamFieldLineComponent { +export class ParamFieldLineComponent implements AfterViewChecked { @Input("param") private _param: NgParameter; + @ViewChild(NgParamInputComponent) + private _ngParamInputComponent: NgParamInputComponent; + + @Output() + private onValid: EventEmitter<void>; + + /** + * true si la valeur saisie est valide + */ + private _isInputValid: boolean = false; + + /** + * true si le min-max/liste est valide + */ + private _isRangeValid: boolean = true; + constructor(private intlService: InternationalisationService) { + this.onValid = new EventEmitter(); } private get title(): string { @@ -103,19 +120,14 @@ export class ParamFieldLineComponent { * calcule l'état du radio "paramètre fixé" */ private get radioFixCheck(): string { - if (this._param.radioState == ParamRadioConfig.FIX) - return "checked"; - return undefined; + return this.isRadioFixChecked ? "checked" : undefined; } /** * calcule l'état du radio "paramètre à varier" */ private get radioVarCheck(): string { - // console.log(this._param.symbol + " " + ParamRadioConfig[this._param.radioState]); - if (this._param.radioState == ParamRadioConfig.VAR) - return "checked"; - return undefined; + return this.isRadioVarChecked ? "checked" : undefined; } /** @@ -128,8 +140,15 @@ export class ParamFieldLineComponent { } /** - * retourne l'état du radio "paramètre à varier" sous forme booléenne - */ + * retourne l'état du radio "paramètre fixé" sous forme booléenne + */ + private get isRadioFixChecked(): boolean { + return this._param.radioState == ParamRadioConfig.FIX; + } + + /** + * retourne l'état du radio "paramètre à varier" sous forme booléenne + */ private get isRadioVarChecked(): boolean { return this._param.radioState == ParamRadioConfig.VAR; } @@ -145,6 +164,9 @@ export class ParamFieldLineComponent { private onRadioClick(symbol: string, option: string) { this.onRadio.emit(symbol + "_" + option); + + // MAJ validité + this.emitValidity(); } /** @@ -190,4 +212,49 @@ export class ParamFieldLineComponent { return this.radioCalCheck == undefined ? this.offClass : this.onClass; return ""; } + + /** + * validité des saisies du composant + */ + public get isValid(): boolean { + switch (this._param.radioState) { + case ParamRadioConfig.FIX: + return this._isInputValid + + case ParamRadioConfig.VAR: + return this._isRangeValid; + + default: + return true; + } + } + + /** + * émission d'un événement de validité + */ + private emitValidity() { + this.onValid.emit() + } + + /** + * réception d'un événement de NgParamInputComponent + */ + private onInputChange(event: any) { + if (event.action == "valid") { + this._isInputValid = event.value; + this.emitValidity(); + } + } + + /** + * réception d'un événement de validité de ParamValuesComponent + */ + private onParamValuesValid(event: boolean) { + this._isRangeValid = event; + this.emitValidity() + } + + public ngAfterViewChecked() { + this._ngParamInputComponent.showError = this.isRadioFixChecked; + } } diff --git a/src/app/components/param-values/ngparam-max.component.ts b/src/app/components/param-values/ngparam-max.component.ts index 1db61f59ae15491bf3c1bb8e3a584faea158337b..9fd292bd87a05866116acdca4e2456dd6adedf60 100644 --- a/src/app/components/param-values/ngparam-max.component.ts +++ b/src/app/components/param-values/ngparam-max.component.ts @@ -1,51 +1,37 @@ -import { Component, Output, EventEmitter } from "@angular/core"; +import { Component, Input } from "@angular/core"; -import { NumericalString, Pair } from "jalhyd"; +import { NumericalString } from "jalhyd"; import { GenericInputComponent } from "../generic-input/generic-input.component"; import { InternationalisationService } from "../../services/internationalisation/internationalisation.service"; +import { NgParameter } from "../../formulaire/ngparam"; @Component({ selector: "ngparam-max", templateUrl: "../generic-input/generic-input.component.html" }) export class NgParamMaxComponent extends GenericInputComponent { - /** - * valeur actuelle du maximum - */ - private _currentValue: number; - - /** - * reférence (valeur maxi pour le maximum) - */ - private _refValue: Pair; - - @Output() - private onChange = new EventEmitter<string>(); + @Input("param") + private _param: NgParameter; constructor(private intlService: InternationalisationService) { super(); } - public set refValue(v: Pair) { - this._refValue = v; - } - protected getModelValue(): any { - return this._currentValue; + return this._param.maxValue; } protected setModelValue(v: any) { - this._currentValue = v; - this.onChange.emit("value"); + this._param.maxValue = v; } protected validateModelValue(v: any): { isValid: boolean, message: string } { let msg = undefined; let valid = false; - if (!this._refValue.intervalHasValue(v)) - msg = "La valeur n'est pas dans " + this._refValue.toString(); + if (!this._param.checkMax(v)) + msg = "La valeur n'est pas dans ]" + this._param.minValue + " , " + this._param.domain.maxValue + "]"; else valid = true; diff --git a/src/app/components/param-values/ngparam-min.component.ts b/src/app/components/param-values/ngparam-min.component.ts index ca6749eb40445573a869cabbd8d4d8df10a53b38..a135468af07f8f25767c2c0ca8ab739bf44434d1 100644 --- a/src/app/components/param-values/ngparam-min.component.ts +++ b/src/app/components/param-values/ngparam-min.component.ts @@ -1,53 +1,37 @@ -import { Component, Output, EventEmitter } from "@angular/core"; +import { Component, Input } from "@angular/core"; -import { NumericalString, Pair } from "jalhyd"; +import { NumericalString } from "jalhyd"; import { GenericInputComponent } from "../generic-input/generic-input.component"; import { InternationalisationService } from "../../services/internationalisation/internationalisation.service"; +import { NgParameter } from "../../formulaire/ngparam"; @Component({ selector: "ngparam-min", templateUrl: "../generic-input/generic-input.component.html" }) export class NgParamMinComponent extends GenericInputComponent { - /** - * valeur actuelle du minimum - */0.49999999995 - private _currentValue: number; - - /** - * reférence (valeur mini pour le minimum) - */ - private _refValue: Pair; - - @Output() - private onChange = new EventEmitter<string>(); - - public isInit: boolean; + @Input("param") + private _param: NgParameter; constructor(private intlService: InternationalisationService) { super(); } - public set refValue(v: Pair) { - this._refValue = v; - } - protected getModelValue(): any { - return this._currentValue; + return this._param.minValue; } protected setModelValue(v: any) { - this._currentValue = v; - this.onChange.emit("value"); + this._param.minValue = v; } protected validateModelValue(v: any): { isValid: boolean, message: string } { let msg = undefined; let valid = false; - if (!this._refValue.intervalHasValue(v)) - msg = "La valeur n'est pas dans " + this._refValue.toString(); + if (!this._param.checkMin(v)) + msg = "La valeur n'est pas dans [" + this._param.domain.minValue + " , " + this._param.maxValue + "["; else valid = true; diff --git a/src/app/components/param-values/ngparam-step.component.ts b/src/app/components/param-values/ngparam-step.component.ts index e34225e948d9ad9318441ed8655faac4b6d18ca0..1d25366bfdffb2831c9329909f650ab86545ce22 100644 --- a/src/app/components/param-values/ngparam-step.component.ts +++ b/src/app/components/param-values/ngparam-step.component.ts @@ -1,56 +1,47 @@ -import { Component, Output, EventEmitter } from "@angular/core"; +import { Component, Input } from "@angular/core"; -import { NumericalString, Pair } from "jalhyd"; +import { NumericalString } from "jalhyd"; import { GenericInputComponent } from "../generic-input/generic-input.component"; import { InternationalisationService } from "../../services/internationalisation/internationalisation.service"; +import { NgParameter } from "../../formulaire/ngparam"; @Component({ selector: "ngparam-step", templateUrl: "../generic-input/generic-input.component.html" }) export class NgParamStepComponent extends GenericInputComponent { - /** - * valeur actuelle du pas - */ - private _currentValue: number; - - /** - * reférence (valeur maxi pour le pas) - */ - private _refValue: Pair; - - @Output() - private onChange = new EventEmitter<string>(); + @Input("param") + private _param: NgParameter; constructor(private intlService: InternationalisationService) { super(); } - public set refValue(v: Pair) { - this._refValue = v; - } - protected getModelValue(): any { - return this._currentValue; + return this._param.stepValue; } protected setModelValue(v: any) { - this._currentValue = v; - this.onChange.emit("value"); + this._param.stepValue = v; } protected validateModelValue(v: any): { isValid: boolean, message: string } { let msg = undefined; let valid = false; - if (!this._refValue.intervalHasValue(v)) - msg = "La valeur n'est pas dans " + this._refValue.toString(); - else { - valid = v > 0; - if (!valid) - msg = "La valeur ne peut pas être <= 0"; + if (this._param.isMinMaxValid) { + if (!this._param.checkStep(v)) { + msg = "La valeur n'est pas dans " + this._param.stepRefValue.toString(); + } + else { + valid = v > 0; + if (!valid) + msg = "La valeur ne peut pas être <= 0"; + } } + else + msg = "Veuillez corriger le min/max"; return { isValid: valid, message: msg }; } diff --git a/src/app/components/param-values/param-values.component.html b/src/app/components/param-values/param-values.component.html index e3adf9e933181dac3f7099bb05dc17f308cbf109..b576cf138c0bcea5d675242fdbf50cc517eded5d 100644 --- a/src/app/components/param-values/param-values.component.html +++ b/src/app/components/param-values/param-values.component.html @@ -1,7 +1,7 @@ <div class="row"> <div class="btn-group col-12 col-sm-3" dropdown (click)="onSelectValueMode($event)"> <button dropdownToggle class="btn btn-primary dropdown-toggle waves-light my-1" type="button" mdbRippleRadius> - {{currentLabel}} + {{currentModeSelectLabel}} </button> <div class="dropdown-menu"> <a class="dropdown-item" *ngFor="let e of _valueModes" [value]=e.value>{{e.label}}</a> @@ -9,16 +9,16 @@ </div> <div *ngIf="!isList" class="col-12 col-sm-3"> - <ngparam-min [title]="uitextValeurMini" (onChange)="onMinChanged($event)"></ngparam-min> + <ngparam-min [title]="uitextValeurMini" [param]=_param (onChange)="onMinChanged($event)"></ngparam-min> </div> <div *ngIf="!isList" class="col-12 col-sm-3"> - <ngparam-max [title]="uitextValeurMaxi" (onChange)="onMaxChanged($event)"></ngparam-max> + <ngparam-max [title]="uitextValeurMaxi" [param]=_param (onChange)="onMaxChanged($event)"></ngparam-max> </div> <div *ngIf="!isList" class="col-12 col-sm-3"> - <ngparam-step [title]="uitextPasVariation" (onChange)="onStepChanged($event)"></ngparam-step> + <ngparam-step [title]="uitextPasVariation" [param]=_param (onChange)="onStepChanged($event)"></ngparam-step> </div> <div *ngIf="isList" class="col-12 col-sm-6"> - <value-list title="valeurs séparées par ';'" (onChange)="onListChanged($event)"></value-list> + <value-list title="valeurs séparées par ';'" [param]=_param (onChange)="onListChanged($event)"></value-list> </div> </div> \ No newline at end of file diff --git a/src/app/components/param-values/param-values.component.ts b/src/app/components/param-values/param-values.component.ts index e0f63c158b5bc8e902f25d1782a74fe6ae4d936b..7a579e5e2520238a91d0e79cc7d139c13d77518f 100644 --- a/src/app/components/param-values/param-values.component.ts +++ b/src/app/components/param-values/param-values.component.ts @@ -1,103 +1,12 @@ -import { Component, Input, Output, EventEmitter, ViewChild, DoCheck } from "@angular/core"; - -import { ParamDomainValue, Pair } from "jalhyd"; +import { Component, Input, Output, EventEmitter, ViewChild, AfterViewChecked } from "@angular/core"; import { InternationalisationService } from "../../services/internationalisation/internationalisation.service"; import { NgParameter, ParamValueMode } from "../../formulaire/ngparam"; -import { GenericInputComponent } from "../generic-input/generic-input.component"; import { NgParamMinComponent } from "./ngparam-min.component"; import { NgParamMaxComponent } from "./ngparam-max.component"; import { NgParamStepComponent } from "./ngparam-step.component"; - - -@Component({ - selector: "value-list", - templateUrl: "../generic-input/generic-input.component.html" -}) -export class ValueListComponent extends GenericInputComponent { - @Output() - private onChange = new EventEmitter<string>(); - - public _list: number[]; - - public isInit; - - constructor(private intlService: InternationalisationService) { - super(); - this._list = []; - } - - protected getModelValue(): any { - return this._list; - } - - protected setModelValue(l: any) { - if (typeof (l) == "number") { - this._list = []; - this._list.push(l); - } - else - this._list = l; - this.onChange.emit("value"); - } - - protected validateModelValue(v: any): { isValid: boolean, message: string } { - let msg = undefined; - let valid = false; - - if (v instanceof Array) { - valid = true; - for (let e of v) { - valid = valid && (typeof (e) == "number") - if (!valid) { - msg = "La valeur n'est pas une liste de nombres" - break; - } - } - } - - return { isValid: valid, message: msg }; - } - - protected modelToUI(v: any): string { - let res = ""; - for (let e of v) { - if (res != "") - res += ";"; - res += String(e); - } - return res; - } - - protected validateUIValue(ui: string): { isValid: boolean, message: string } { - let valid: boolean = false; - let msg: string = undefined; - - let tmp: string[] = ui.split(";"); - let res = true; - for (let v of tmp) { - let isnum = v != "" && (+v == +v); - res = res && isnum; - if (!res) - break; - } - - if (!res) - msg = "Veuillez entrer une liste de nombres"; - else - valid = true; - - return { isValid: valid, message: msg }; - } - - protected uiToModel(ui: string) { - let tmp: string[] = ui.split(";"); - let res = []; - for (let v of tmp) - res.push(+v); - return res; - } -} +import { BaseComponent } from "../base/base.component"; +import { ValueListComponent } from "./value-list.component"; @Component({ selector: "param-values", @@ -119,16 +28,41 @@ export class ValueListComponent extends GenericInputComponent { }` ] }) -export class ParamValuesComponent implements DoCheck { +export class ParamValuesComponent extends BaseComponent implements AfterViewChecked { @Input("param") private _param: NgParameter; + private _valueModes = []; + /** - * true si liste de valeur, false si min/max/pas + * true quand les champs du composant et de ses enfants sont initialisés */ - private _list: boolean; + private _initCompleted = false; - private _valueModes = []; + /** + * true si la valeur min est valide + */ + private _validMin: boolean = false; + + /** + * true si la valeur max est valide + */ + private _validMax: boolean = false; + + /** + * true si la valeur du pas est valide + */ + private _validStep: boolean = false; + + /** + * true si la liste de valeurs est valide + */ + private _validList: boolean = false; + + /** + * flag signalant qu'on a sélectionné le mode "liste" et qu'il faut initialiser le composant ValueListComponent + */ + private _doInitList: boolean = false; /** * composant de saisie du minimum @@ -154,107 +88,171 @@ export class ParamValuesComponent implements DoCheck { @ViewChild(ValueListComponent) private _listComponent: ValueListComponent; + @Output() + private onValid: EventEmitter<boolean>; + constructor(private intlService: InternationalisationService) { + super(); this._valueModes.push({ "value": ParamValueMode.MINMAX, "label": "Min/max" }); this._valueModes.push({ "value": ParamValueMode.LISTE, "label": "Liste" }); + this.onValid = new EventEmitter(); } - + + /** + * init des champs min/max/pas + */ private initMinMaxStep() { - // valeur pour min (celle déjà définie ou celle déduite du domaine de définition) - let min: number = this._param.minValue; - let ok = min != undefined - if (ok) { - try { - // on la vérifie - this._param.checkValue(min); - ok = true; - } - catch (e) { - ok = false; - } - } - if (!ok) - min = this._param.getValue() / 2; - - // valeur pour max (celle déjà définie ou celle déduite du domaine de définition) - let max: number = this._param.maxValue; - ok = max != undefined - if (ok) { - try { - // on la vérifie - this._param.checkValue(max); - ok = true; - } - catch (e) { - ok = false; - } - } - if (!ok) - max = this._param.getValue() * 2; + if (this.isMinMax) { + // valeur pour min : celle déjà définie ou celle déduite de la valeur saisie + let min: number = this._param.minValue; + if (min == undefined) + min = this._param.getValue() / 2; - this._minComponent.refValue = new Pair(this._param.domain.minValue, max); - this._minComponent.model = min; + // valeur pour max : celle déjà définie ou celle déduite de la valeur saisie + let max: number = this._param.maxValue; + if (max == undefined) + max = this._param.getValue() * 2; - this._maxComponent.refValue = new Pair(min, this._param.domain.maxValue); - this._maxComponent.model = max; + this._minComponent.model = min; - this.updateStepComponentRef(); + this._maxComponent.model = max; - this._stepComponent.model = (this._maxComponent.model - this._minComponent.model) / 20; - } + // valeur du pas + let step = this._param.stepValue; + if (step == undefined) + step = (this._maxComponent.model - this._minComponent.model) / 20; + this._stepComponent.model = step; - private onMinChanged(val: string) { - this.updateStepComponentRef(); + this.validateAll(); - this._maxComponent.refValue = new Pair(this._minComponent.model, this._param.domain.maxValue); - this._maxComponent.validateModel(); + this._validMin = this._minComponent.isValid; + this._validMax = this._maxComponent.isValid; + this._validStep = this._stepComponent.isValid; + this.emitValidity(); - if (this._minComponent.validateModel()) - this._param.minValue = this._minComponent.model; + this._initCompleted = true; + } } - private onMaxChanged(val: string) { - this.updateStepComponentRef(); + /** + * initialisation de la liste de valeurs avec celle du paramètre géré + */ + private initList() { + if (this._doInitList && this._listComponent != undefined) { + this._doInitList = false; + let l = this._param.valueList; + if (l == undefined) { + if (this._param.isDefined) + l = [this._param.getValue()]; + else + l = []; + } + this._listComponent.model = l; + } + } + + /** + * revalidation de tous les composants enfants + */ + private validateAll() { + if (this._minComponent != undefined) + this._minComponent.validate(); + if (this._maxComponent != undefined) + this._maxComponent.validate(); + if (this._stepComponent != undefined) + this._stepComponent.validate(); + if (this._listComponent != undefined) + this._listComponent.validate(); + } - this._minComponent.refValue = new Pair(this._param.domain.minValue, this._maxComponent.model); - this._minComponent.validateModel(); + /** + * envoi d'un événement de validité + */ + private emitValidity() { + switch (this._param.valueMode) { + case ParamValueMode.LISTE: + this.onValid.emit(this._validList); + break; - if (this._maxComponent.validateModel()) - this._param.maxValue = this._maxComponent.model; + case ParamValueMode.MINMAX: + this.onValid.emit(this._validMin && this._validMax && this._validStep); + break; + } } - private onStepChanged(val: string) { - if (this._stepComponent.validateModel()) - this._param.stepValue = this._stepComponent.model; + /** + * réception d'un événement de NgParamMinComponent + */ + private onMinChanged(event: any) { + if (this._initCompleted) + switch (event.action) { + case "model": + this.validateAll(); + break; + + case "valid": + this._validMin = event.value; + this.emitValidity(); + break; + } } - private onListChanged(val: string) { - if (this._listComponent.validateModel()) - this._param.valueList = this._listComponent.model; + /** + * réception d'un événement de NgParamMaxComponent + */ + private onMaxChanged(event: any) { + if (this._initCompleted) + switch (event.action) { + case "model": + this.validateAll(); + break; + + case "valid": + this._validMax = event.value; + this.emitValidity(); + break; + } } - public ngDoCheck() { - // initialisation des champs min/max/step à l'apparition de ces contrôles - if (this._minComponent != undefined && !this._minComponent.isInit) { - this._minComponent.isInit = true; - this.initMinMaxStep(); - } + /** + * réception d'un événement de NgParamStepComponent + */ + private onStepChanged(event: any) { + if (this._initCompleted) + switch (event.action) { + case "model": + this.validateAll(); + break; - if (this._listComponent != undefined && !this._listComponent.isInit) { - this._listComponent.isInit = true; + case "valid": + this._validStep = event.value; + this.emitValidity(); + break; + } + } - this._listComponent.model = []; - if (this._param.isDefined) - this._listComponent.model.push(this._param.getValue()); - } + /** + * réception d'un événement de ValueListComponent + */ + private onListChanged(event: any) { + if (this._initCompleted) + switch (event.action) { + case "model": + this.validateAll(); + break; + + case "valid": + this._validList = event.value; + this.emitValidity(); + break; + } } /** - * met à jour la valeur de référence du composant gérant le pas de variation + * appelé au 1er affichage du composant */ - private updateStepComponentRef() { - this._stepComponent.refValue = new Pair(1e-9, this._maxComponent.model - this._minComponent.model); - this._stepComponent.validateModel(); + protected afterFirstViewChecked() { + this.initMinMaxStep(); } private get uitextValeurMini() { @@ -269,18 +267,43 @@ export class ParamValuesComponent implements DoCheck { return this.intlService.localizeText("INFO_PARAMFIELD_PASVARIATION"); } + /** + * true si mode "liste de valeurs" + */ private get isList(): boolean { return this._param.valueMode == ParamValueMode.LISTE; } /** - * valeur courante affichée dans le select + * true si mode "min/max/pas" */ - private get currentLabel(): string { + private get isMinMax(): boolean { + return this._param.valueMode == ParamValueMode.MINMAX; + } + + /** + * valeur courante affichée dans le select min-max/list + */ + private get currentModeSelectLabel(): string { return ParamValueMode[this._param.valueMode]; } + /** + * réception d'un événement du menu "min/max/liste" + */ private onSelectValueMode(event: any) { - this._param.valueMode = event.target.value; + const next = event.target.value; + + // on a sélectionné "liste" ? + if (this._param.valueMode != ParamValueMode.LISTE && next == ParamValueMode.LISTE) + this._doInitList = true; + + this._param.valueMode = next; + this.validateAll(); + } + + ngAfterViewChecked() { + super.ngAfterViewChecked(); + this.initList(); } } diff --git a/src/app/components/param-values/value-list.component.ts b/src/app/components/param-values/value-list.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..e3f3adde6ecc35b2f4308359b0d523e01f5c8f59 --- /dev/null +++ b/src/app/components/param-values/value-list.component.ts @@ -0,0 +1,95 @@ +import { Component, Input } from "@angular/core"; + +import { GenericInputComponent } from "../generic-input/generic-input.component"; +import { InternationalisationService } from "../../services/internationalisation/internationalisation.service"; +import { NgParameter } from "../../formulaire/ngparam"; +import { Message } from "jalhyd"; + +@Component({ + selector: "value-list", + templateUrl: "../generic-input/generic-input.component.html" +}) +export class ValueListComponent extends GenericInputComponent { + @Input("param") + private _param: NgParameter; + + constructor(private intlService: InternationalisationService) { + super(); + } + + protected getModelValue(): any { + return this._param.valueList; + } + + protected setModelValue(l: any) { + if (typeof (l) == "number") { + this._param.valueList = []; + this._param.valueList.push(l); + } + else + this._param.valueList = l; + } + + protected validateModelValue(v: any): { isValid: boolean, message: string } { + let msg = undefined; + let valid = false; + + if (v instanceof Array) { + valid = true; + try { + this._param.checkList(v); + } + catch (ex) { + valid = false; + if (ex instanceof Message) + msg = this.intlService.localizeMessage(ex); + else + msg = "invalid value"; + } + } + else + msg = "Veuillez entrer une liste de nombres"; + + return { isValid: valid, message: msg }; + } + + protected modelToUI(v: any): string { + let res = ""; + if (v != undefined) + for (let e of v) { + if (res != "") + res += ";"; + res += String(e); + } + return res; + } + + protected validateUIValue(ui: string): { isValid: boolean, message: string } { + let valid: boolean = false; + let msg: string = undefined; + + let tmp: string[] = ui.split(";"); + let res = true; + for (let v of tmp) { + let isnum = v != "" && (+v == +v); + res = res && isnum; + if (!res) + break; + } + + if (!res) + msg = "Veuillez entrer une liste de nombres"; + else + valid = true; + + return { isValid: valid, message: msg }; + } + + protected uiToModel(ui: string) { + let tmp: string[] = ui.split(";"); + let res = []; + for (let v of tmp) + res.push(+v); + return res; + } +} diff --git a/src/app/formulaire/check-field.ts b/src/app/formulaire/check-field.ts index 56c317e8a79f8d45eb142c70369c6f4447868a03..098e3abc6d9c756b8c935f18e3bd3bf814442eb3 100644 --- a/src/app/formulaire/check-field.ts +++ b/src/app/formulaire/check-field.ts @@ -23,4 +23,8 @@ export class CheckField extends Field { protected verifyDependency(d: Dependency): boolean { throw "CheckField.verifyDependency() : type de condition '" + DependencyConditionType[d.masterCondition.type] + "' non pris en charge"; } + + public get isValid(): boolean { + return true; + } } diff --git a/src/app/formulaire/field.ts b/src/app/formulaire/field.ts index 401ca359cfa3cedb5e081e3684b84c2210fee78d..88bceabd7bd1dc070a627e7e3e2f81cedcb24226 100644 --- a/src/app/formulaire/field.ts +++ b/src/app/formulaire/field.ts @@ -23,6 +23,8 @@ export abstract class Field extends FormulaireElement { return this._fieldType == FieldType.Check; } + public abstract get isValid(); + public abstract getValue(): any; public abstract setValue(val: any): void; } diff --git a/src/app/formulaire/fieldset.ts b/src/app/formulaire/fieldset.ts index bb03abbcf6a96aa50e572637b27e0cbe5c83322a..6e7911c5f879b38082bc0aa1de276688191b3bd2 100644 --- a/src/app/formulaire/fieldset.ts +++ b/src/app/formulaire/fieldset.ts @@ -45,4 +45,11 @@ export class FieldSet extends FormulaireElement { protected verifyDependency(d: Dependency): boolean { throw "FieldSet.verifyDependency() : type de condition '" + DependencyConditionType[d.masterCondition.type] + "' non pris en charge"; } + + public get isValid(): boolean { + let res: boolean = true; + for (let f of this._fields) + res = res && f.isValid; + return res; + } } \ No newline at end of file diff --git a/src/app/formulaire/formulaire-definition.ts b/src/app/formulaire/formulaire-definition.ts index 23615dcf41abd49f57a27b7121f7e6d3fd01d10a..30b7f7323f403a118349fe271a314045a9b77ea5 100644 --- a/src/app/formulaire/formulaire-definition.ts +++ b/src/app/formulaire/formulaire-definition.ts @@ -1223,4 +1223,11 @@ export class FormulaireDefinition extends Observable { if (this.hasResults) this.doCompute(); // pour mettre à jour la langue } + + public get isValid(): boolean { + let res: boolean = true; + for (let fs of this._fieldSets) + res = res && fs.isValid; + return res; + } } diff --git a/src/app/formulaire/ngparam.ts b/src/app/formulaire/ngparam.ts index 9d94db8e4395f696f59e929e9106b7fd29edc46e..7dd3fda876c24f7c7411bf204e5d4b44795da584 100644 --- a/src/app/formulaire/ngparam.ts +++ b/src/app/formulaire/ngparam.ts @@ -1,10 +1,9 @@ -import { ParamDefinition, ParamDomainValue, Interval, ParamDomain } from "jalhyd"; +import { ParamDefinition, Pair, ParamDomain } from "jalhyd"; import { InputField } from "./input-field"; import { Dependency } from "./dependency"; -import { DependencyCondition, DependencyConditionType } from "./dependency-condition"; +import { DependencyConditionType } from "./dependency-condition"; import { ValueDependencyCondition } from "./value-dependency-condition"; -import { StringMap } from "../stringmap"; export enum ParamRadioConfig { /** @@ -47,11 +46,31 @@ export class NgParameter extends InputField { public radioConfig: ParamRadioConfig; public radioState: ParamRadioConfig; public isDefault: boolean = false; // archi bug du langage ! si on relit cette propriété sans l'avoir modifiée entre-temps, elle vaut undefined ! - public minValue: number; // valeur min dans le cas ParamRadioConfig.VAR - public maxValue: number; // valeur max dans le cas ParamRadioConfig.VAR - public stepValue: number; // pas de progression dans le cas ParamRadioConfig.VAR - public valueList: number[]; - public valueMode: ParamValueMode = ParamValueMode.MINMAX; + + /** + * mode de génération des valeurs : min/max, liste, ... + */ + private _valueMode: ParamValueMode = ParamValueMode.MINMAX; + + /** + * valeur min dans le cas ParamRadioConfig.VAR && ParamValueMode.MINMAX + */ + private _minValue: number = undefined; + + /** + * valeur max dans le cas ParamRadioConfig.VAR && ParamValueMode.MINMAX + */ + private _maxValue: number = undefined; + + /** + * pas de progression dans le cas ParamRadioConfig.VAR && ParamValueMode.MINMAX + */ + private _stepValue: number = undefined; + + /** + * liste de valeurs dans le cas ParamRadioConfig.VAR && ParamValueMode.LISTE + */ + private _valueList: number[]; constructor(private _paramDef: ParamDefinition, formId: number) { super(_paramDef.computeNodeType, _paramDef.symbol, formId); @@ -81,8 +100,146 @@ export class NgParameter extends InputField { this._paramDef.checkValue(val); } - public get interval(): Interval { - return this._paramDef.interval; + public checkList(l: number[]) { + for (let e of l) + this.checkValue(e); + } + + public get valueMode() { + return this._valueMode; + } + + public set valueMode(m: ParamValueMode) { + // undefined si on clique en dehors du select après l'avoir ouvert (cad sans avoir fait de sélection) + // et au même niveau, cad à côté du bouton et non à côté du menu déroulant + if (m != undefined) + this._valueMode = m; + } + + /** + * vérifie si un min/max est valide par rapport au domaine de définition + */ + private isMinMaxDomainValid(v: number): boolean { + if (v == undefined) + return false; + + if (this._valueMode == ParamValueMode.MINMAX) + try { + this.checkValue(v); + } + catch (e) { + return false; + } + + return true; + } + + private checkMinMax(min: number, max: number): boolean { + return this.isMinMaxDomainValid(min) && this.isMinMaxDomainValid(max) && (min < max); + } + + public checkMin(min: number): boolean { + return this.isMinMaxDomainValid(min) && (min < this._maxValue); + } + + public checkMax(max: number): boolean { + return this.isMinMaxDomainValid(max) && (this._minValue < max); + } + + public get isMinMaxValid(): boolean { + return this.checkMinMax(this._minValue, this._maxValue); + } + + public get minValue() { + return this._minValue; + } + + public set minValue(v: number) { + this._minValue = v; + } + + public get maxValue() { + return this._maxValue; + } + + public set maxValue(v: number) { + this._maxValue = v; + } + + public checkStep(step: number): boolean { + return this.isMinMaxValid && this.stepRefValue.intervalHasValue(step); + } + + public get stepRefValue(): Pair { + return new Pair(1e-9, this._maxValue - this._minValue); + } + + public get stepValue() { + return this._stepValue; + } + + public set stepValue(v: number) { + this._stepValue = v; + } + + public get valueList() { + return this._valueList; + } + + public set valueList(l: number[]) { + this._valueList = l; + } + + private get isListValid(): boolean { + if (this._valueList == undefined) + return false; + + for (let v of this._valueList) + try { + this.checkValue(v); + } + catch (e) { + return false; + } + return true; + } + + private get isRangeValid(): boolean { + switch (this._valueMode) { + case ParamValueMode.LISTE: + return this.isListValid; + + case ParamValueMode.MINMAX: + return this.checkStep(this._stepValue); + } + + throw "NgParameter.isRangeValid() : valeur " + this._valueMode + " de ParamValueMode non prise en compte"; + } + + private get isValueValid(): boolean { + const v = this._paramDef.getValue(); + try { + this._paramDef.checkValue(v); + return true; + } + catch (e) { + return false; + } + } + + public get isValid() { + switch (this.radioState) { + case ParamRadioConfig.FIX: + return this.isValueValid; + + case ParamRadioConfig.VAR: + return this.isRangeValid; + + case ParamRadioConfig.CAL: + return true; + } + + throw "NgParameter.isValid() : valeur de ParamRadioConfig non prise en compte"; } public static getRadioConfig(s: string) { diff --git a/src/app/formulaire/select-field.ts b/src/app/formulaire/select-field.ts index 112e708a9434697ab8634a2e74781e3aac72028c..c0013a05ff8659cd8ceb379448e3aac219d8fdd6 100644 --- a/src/app/formulaire/select-field.ts +++ b/src/app/formulaire/select-field.ts @@ -43,6 +43,10 @@ export class SelectField extends Field { } } + public get isValid(): boolean { + return true; + } + public getLabel() { if (this.selectedEntry == undefined) return undefined;