From 04a7f46b7244661d2dd9962b73490baa0b98a9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Grand?= <francois.grand@inrae.fr> Date: Fri, 20 May 2022 08:14:08 +0200 Subject: [PATCH] refactor: use DefinedBoolean to manage form validity flag refs #544 --- .../field-set/field-set.component.ts | 15 +++--- .../fieldset-container.component.ts | 15 +++--- .../calculator.component.ts | 24 ++++++---- .../generic-input/generic-input.component.ts | 32 +++++++------ .../pab-table/pab-table.component.ts | 11 +++-- .../pb-schema/pb-schema.component.ts | 15 +++--- src/app/definedvalue/definedboolean.ts | 7 +++ src/app/definedvalue/definedvalue.ts | 46 +++++++++++++++++++ 8 files changed, 115 insertions(+), 50 deletions(-) create mode 100644 src/app/definedvalue/definedboolean.ts create mode 100644 src/app/definedvalue/definedvalue.ts diff --git a/src/app/components/field-set/field-set.component.ts b/src/app/components/field-set/field-set.component.ts index 584f42efa..36d2701bf 100644 --- a/src/app/components/field-set/field-set.component.ts +++ b/src/app/components/field-set/field-set.component.ts @@ -16,6 +16,7 @@ import { I18nService } from "../../services/internationalisation.service"; import { sprintf } from "sprintf-js"; import { capitalize } from "jalhyd"; +import { DefinedBoolean } from "app/definedvalue/definedboolean"; @Component({ selector: "field-set", @@ -51,7 +52,7 @@ export class FieldSetComponent implements DoCheck { } public get isValid() { - return this._isValid; + return this._isValid.value; } /** flag d'affichage des boutons ajouter, supprimer, monter, descendre */ @@ -133,7 +134,7 @@ export class FieldSetComponent implements DoCheck { /** * flag de validité de la saisie */ - private _isValid = false; + private _isValid: DefinedBoolean; /** * événement de changement d'état d'un radio @@ -149,7 +150,9 @@ export class FieldSetComponent implements DoCheck { private notifService: NotificationsService, private i18nService: I18nService, private appSetupService: ApplicationSetupService - ) { } + ) { + this._isValid = new DefinedBoolean(); + } public hasRadioFix(): boolean { if (this._fieldSet.hasInputs) { @@ -264,11 +267,9 @@ export class FieldSetComponent implements DoCheck { } private updateValidity() { - const oldValidity = this._isValid; - // global validity - this._isValid = this.computeValidity(); - if (this._isValid !== oldValidity) { + this._isValid.value = this.computeValidity(); + if (this._isValid.changed) { this.validChange.emit(); } } diff --git a/src/app/components/fieldset-container/fieldset-container.component.ts b/src/app/components/fieldset-container/fieldset-container.component.ts index 4d60303d1..74fac2031 100644 --- a/src/app/components/fieldset-container/fieldset-container.component.ts +++ b/src/app/components/fieldset-container/fieldset-container.component.ts @@ -6,6 +6,7 @@ import { FieldSet } from "../../formulaire/elements/fieldset"; import { FormulaireDefinition } from "../../formulaire/definition/form-definition"; import { I18nService } from "../../services/internationalisation.service"; import { ApplicationSetupService } from "../../services/app-setup.service"; +import { DefinedBoolean } from "app/definedvalue/definedboolean"; @Component({ selector: "fieldset-container", @@ -27,7 +28,7 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { } public get isValid() { - return this._isValid; + return this._isValid.value; } @Input() private _container: FieldsetContainer; @@ -41,7 +42,7 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { /** * flag de validité des FieldSet enfants */ - private _isValid = false; + private _isValid: DefinedBoolean; /** * événément de changement d'état d'un radio @@ -68,7 +69,9 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { public constructor( private i18nService: I18nService, private appSetupService: ApplicationSetupService - ) {} + ) { + this._isValid = new DefinedBoolean(); + } /** * Ajoute un nouveau sous-nub (Structure, PabCloisons, YAXN… selon le cas) @@ -140,11 +143,9 @@ export class FieldsetContainerComponent implements DoCheck, AfterViewInit { } private updateValidity() { - const oldValidity = this._isValid; - // global validity - this._isValid = this.computeValidity(); - if (this._isValid !== oldValidity) { + this._isValid.value = this.computeValidity(); + if (this._isValid.changed) { this.validChange.emit(); } } diff --git a/src/app/components/generic-calculator/calculator.component.ts b/src/app/components/generic-calculator/calculator.component.ts index 859463c39..31aa8ae8e 100644 --- a/src/app/components/generic-calculator/calculator.component.ts +++ b/src/app/components/generic-calculator/calculator.component.ts @@ -61,6 +61,7 @@ import { sprintf } from "sprintf-js"; import * as XLSX from "xlsx"; import { ServiceFactory } from "app/services/service-factory"; +import { DefinedBoolean } from "app/definedvalue/definedboolean"; @Component({ selector: "hydrocalc", @@ -110,7 +111,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe * 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 = false; + private _isUIValid: DefinedBoolean; /** * flag disabled du bouton "calculer" @@ -158,6 +159,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe private formulaireService: FormulaireService, private matomoTracker: MatomoTracker ) { + this._isUIValid = new DefinedBoolean(); // hotkeys listeners this.hotkeysService.add(new Hotkey("alt+w", AppComponent.onHotkey(this.closeCalculator, this))); this.hotkeysService.add(new Hotkey("alt+d", AppComponent.onHotkey(this.cloneCalculator, this))); @@ -331,7 +333,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe * the UI validity state) */ ngDoCheck() { - this.isCalculateDisabled = !this._isUIValid; + this.isCalculateDisabled = !this._isUIValid.value; } ngOnDestroy() { @@ -473,12 +475,12 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe * calcul de la validité globale de la vue */ private updateUIValidity() { - this._isUIValid = false; + let res = false; if (!this._formulaire.calculateDisabled) { // all fieldsets must be valid - this._isUIValid = true; + res = true; if (this._fieldsetComponents !== undefined) { - this._isUIValid = this._isUIValid && this._fieldsetComponents.reduce( + res = res && this._fieldsetComponents.reduce( // callback ( // accumulator (valeur précédente du résultat) @@ -497,7 +499,7 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe } // all fieldset containers must be valid if (this._fieldsetContainerComponents !== undefined) { - this._isUIValid = this._isUIValid && this._fieldsetContainerComponents.reduce<boolean>( + res = res && this._fieldsetContainerComponents.reduce<boolean>( // callback ( // accumulator (valeur précédente du résultat) @@ -516,19 +518,23 @@ export class GenericCalculatorComponent implements OnInit, DoCheck, AfterViewChe } // special components must be valid if (this._pabTableComponent !== undefined) { - this._isUIValid = this._isUIValid && this._pabTableComponent.isValid; + res = res && this._pabTableComponent.isValid; } if (this._pbSchemaComponent !== undefined) { - this._isUIValid = this._isUIValid && this._pbSchemaComponent.isValid; + res = res && this._pbSchemaComponent.isValid; } if (this._formulaire.currentNub.calcType === CalculatorType.PreBarrage) { const form: FormulairePrebarrage = this._formulaire as FormulairePrebarrage; - this._isUIValid = this._isUIValid && form.checkParameters().length === 0; + res = res && form.checkParameters().length === 0; } } + this._isUIValid.value = res; + // update prébarrage schema validity + if (this._isUIValid.changed) { this._pbSchemaComponent.updateItemsValidity(); + } } public getElementStyleDisplay(id: string) { diff --git a/src/app/components/generic-input/generic-input.component.ts b/src/app/components/generic-input/generic-input.component.ts index 444127f7f..97d1f801d 100644 --- a/src/app/components/generic-input/generic-input.component.ts +++ b/src/app/components/generic-input/generic-input.component.ts @@ -5,6 +5,7 @@ import { FormulaireDefinition } from "../../formulaire/definition/form-definitio import { NgParameter } from "../../formulaire/elements/ngparam"; import { I18nService } from "../../services/internationalisation.service"; import { ApplicationSetupService } from "../../services/app-setup.service"; +import { DefinedBoolean } from "app/definedvalue/definedboolean"; /** * classe de gestion générique d'un champ de saisie avec titre, validation et message d'erreur @@ -79,6 +80,11 @@ export abstract class GenericInputComponentDirective implements OnChanges { */ private _isValidModel = false; + /** + * flag de validité globale + */ + private _isValid: DefinedBoolean; + /** * message d'erreur UI */ @@ -96,7 +102,9 @@ export abstract class GenericInputComponentDirective implements OnChanges { private cdRef: ChangeDetectorRef, protected intlService: I18nService, protected appSetupService: ApplicationSetupService - ) { } + ) { + this._isValid = new DefinedBoolean(); + } public get isDisabled(): boolean { if (this._model instanceof NgParameter) { @@ -107,10 +115,13 @@ export abstract class GenericInputComponentDirective implements OnChanges { } /** - * événement de changement de la validité de la saisie + * modification et émission d'un événement de changement de la validité */ - private emitValidChanged() { - this.change.emit({ "action": "valid", "value": this.isValid }); + private setAndEmitValid() { + this._isValid.value = this._isValidUI && this._isValidModel; + if (this._isValid.changed) { + this.change.emit({ "action": "valid", "value": this._isValid.value }); + } } /** @@ -128,15 +139,12 @@ export abstract class GenericInputComponentDirective implements OnChanges { * calcul de la validité globale du composant (UI+modèle) */ public get isValid() { - return this._isValidUI && this._isValidModel; + return this._isValid.value; } protected setUIValid(b: boolean) { - const old = this.isValid; this._isValidUI = b; - if (this.isValid !== old) { - this.emitValidChanged(); - } + this.setAndEmitValid(); } protected validateUI() { @@ -148,11 +156,9 @@ export abstract class GenericInputComponentDirective implements OnChanges { } protected setModelValid(b: boolean) { - const old = this.isValid; this._isValidModel = b; - if (this.isValid !== old) { - this.emitValidChanged(); - } + this.setAndEmitValid(); + // répercussion des erreurs sur le Form angular, pour faire apparaître/disparaître les mat-error if (b) { this.inputField.control.setErrors(null); diff --git a/src/app/components/pab-table/pab-table.component.ts b/src/app/components/pab-table/pab-table.component.ts index 597d28f3a..7d95042fb 100644 --- a/src/app/components/pab-table/pab-table.component.ts +++ b/src/app/components/pab-table/pab-table.component.ts @@ -28,6 +28,7 @@ import { PabTable } from "../../formulaire/elements/pab-table"; import { DialogEditPabComponent } from "../dialog-edit-pab/dialog-edit-pab.component"; import { AppComponent } from "../../app.component"; import { NgParameter, ParamRadioConfig } from "../../formulaire/elements/ngparam"; +import { DefinedBoolean } from "app/definedvalue/definedboolean"; /** * The big editable data grid for calculator type "Pab" (component) @@ -45,7 +46,7 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni private pabTable: PabTable; /** flag de validité des FieldSet enfants */ - private _isValid = false; + private _isValid: DefinedBoolean; /** événément de changement de validité */ @Output() @@ -84,6 +85,7 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni private notifService: NotificationsService ) { this.selectedItems = []; + this._isValid = new DefinedBoolean(); } /** update vary value from pab fish ladder and unable compute Button */ @@ -98,7 +100,7 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni /** Global Pab validity */ public get isValid() { - return this._isValid; + return this._isValid.value; } /** returns true if the cell has an underlying model (ie. is editable) */ @@ -1452,9 +1454,8 @@ export class PabTableComponent implements AfterViewInit, AfterViewChecked, OnIni } private updateValidity() { - const oldValidity = this._isValid; - this._isValid = this.computeValidity(); - if (this._isValid !== oldValidity) { + this._isValid.value = this.computeValidity(); + if (this._isValid.changed) { this.validChange.emit(); } } diff --git a/src/app/components/pb-schema/pb-schema.component.ts b/src/app/components/pb-schema/pb-schema.component.ts index 425b1ba88..ae157c56f 100644 --- a/src/app/components/pb-schema/pb-schema.component.ts +++ b/src/app/components/pb-schema/pb-schema.component.ts @@ -22,6 +22,7 @@ import { AppComponent } from "../../app.component"; import { fv } from "app/util"; import { FormulaireNode } from "app/formulaire/elements/formulaire-node"; import { ServiceFactory } from "app/services/service-factory"; +import { DefinedBoolean } from "app/definedvalue/definedboolean"; /** * The interactive schema for calculator type "PreBarrage" (component) @@ -45,7 +46,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni private nativeElement: any; /** flag de validité du composant */ - private _isValid = false; + private _isValid: DefinedBoolean; private upstreamId = "amont"; @@ -75,6 +76,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni private newPbCloisonDialog: MatDialog ) { this.hotkeysService.add(new Hotkey("del", AppComponent.onHotkey(this.removeOnHotkey, this))); + this._isValid = new DefinedBoolean(); } /** tracks the fullscreen state */ @@ -334,7 +336,7 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni /** Global Pb validity */ public get isValid() { - return this._isValid; + return this._isValid.value; } /** used for a cosmetics CSS trick only (mat-card-header right margin) */ @@ -641,16 +643,11 @@ export class PbSchemaComponent implements AfterViewInit, AfterContentInit, OnIni * Computes the global Pab validity : validity of every cell of every row */ private updateValidity() { - const oldValidity = this._isValid; - // check that at least 1 basin is present and a route from river // upstream to river downstream exists (2nd check includes 1st) - this._isValid = ( - this.model.hasUpDownConnection() - && ! this.model.hasBasinNotConnected() - ); + this._isValid.value = this.model.hasUpDownConnection() && !this.model.hasBasinNotConnected(); - if (this._isValid !== oldValidity) { + if (this._isValid.changed) { this.validChange.emit(); } } diff --git a/src/app/definedvalue/definedboolean.ts b/src/app/definedvalue/definedboolean.ts new file mode 100644 index 000000000..2d8d9a3b8 --- /dev/null +++ b/src/app/definedvalue/definedboolean.ts @@ -0,0 +1,7 @@ +import { DefinedValue } from "./definedvalue"; + +/** + * boolean value with initialised, changed, defined states + */ +export class DefinedBoolean extends DefinedValue<boolean> { +} diff --git a/src/app/definedvalue/definedvalue.ts b/src/app/definedvalue/definedvalue.ts new file mode 100644 index 000000000..1e72754b0 --- /dev/null +++ b/src/app/definedvalue/definedvalue.ts @@ -0,0 +1,46 @@ +/** + * value management with initialised, changed and defined states + */ +export abstract class DefinedValue<T> { + private _initialised: boolean; + + private _value: T; + + private _changed: boolean; + + constructor() { + this._initialised = false; + this._changed = false; + } + + /** + * @returns true if setter has been called at least once + */ + public get initialised(): boolean { + return this._initialised; + } + + /** + * @returns true if value is not undefined + */ + public get defined(): boolean { + return this._value !== undefined; + } + + /** + * @returns true if value has been modified by last call to setter + */ + public get changed(): boolean { + return this._changed; + } + + public get value(): T { + return this._value; + } + + public set value(v: T) { + this._changed = this._value !== v; + this._initialised = true; + this._value = v; + } +} -- GitLab