generic-input.component.ts 11.13 KiB
import { Input, Output, EventEmitter, ChangeDetectorRef, OnChanges } from "@angular/core";
import { BaseComponent } from "../base/base.component";
/*
exemple de template :
<div class="md-form form-sm">
    <input mdbActive type="text" id="form1" class="form-control" [disabled]="isDisabled"
        [ngModel]="uiValue" (ngModelChange)="setUIValue($event)">
    <label for="form1">{{title}}</label>
    <small class="text-danger">{{_message}}</small>
</div>
/**
 * classe de gestion générique d'un champ de saisie avec titre, validation et message d'erreur
 * définitions :
 * - modèle : entité mémoire gérée, indépendamment de la façon dont elle est affichée.
 *   A noter que si cette entité est une classe, on peut ne présenter à l'interface qu'un membre de cette classe,
 *   cad que get model() et getModelValue() ne renverront pas la même chose.
 *   Par ex : get model()-> instance_de_la_classe_Toto, getModelValue() -> Toto.unMembreNumerique
 * - valeur gérée : entité elle même si c'est un type simple (number, string, ...) ou une partie d'un classe
 * - UI : interface utilisateur, présentation de la valeur gérée
export abstract class GenericInputComponent extends BaseComponent implements OnChanges {
    /**
     * entité mémoire gérée
    protected _model: any; // NgBaseParam mais aussi FormDefinition parfois (!?)
    /**
     * flag de désactivation de l'input
    @Input()
    private _inputDisabled = false;
    /**
     * flag d'affichage du message d'erreur
    public showError = true;
    /**
     * id de l'input, pour accorder <label> et <input> sans ambiguïté
    public inputId = "input1";
    /**
     * chaîne affichée dans l'input quand aucune valeur n'est saisie
    @Input()
    public title: string;
    /**
     * événement signalant un changement : valeur du modèle, validité, ...
    @Output()
    protected change = 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;
7172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
/** * 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 _isValidModel = false; /** * message d'erreur UI */ private _errorMessageUI: string; /** * message d'erreur modèle */ private _errorMessageModel: string; constructor(private cdRef: ChangeDetectorRef) { super(); // generate "unique" input id this.inputId = "form-" + btoa(String(Math.random())).substring(2, 10); } public get isDisabled(): boolean { return this._inputDisabled; } /** * événement de changement de la validité de la saisie */ private emitValidChanged() { this.change.emit({ "action": "valid", "value": this.isValid }); } /** * détection des changements dans l'UI par le ChangeDetector du framework */ protected detectChanges() { if (this.cdRef) { // if (!this.cdRef['destroyed']) // pour éviter l'erreur "Attempt to use a destroyed view: detectChanges" // this.cdRef.detectChanges(); this.cdRef.markForCheck(); } } /** * 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() { const { isValid, message } = this.validateUIValue(this._uiValue); this._errorMessageUI = message; this.detectChanges(); this.setUIValid(isValid); return isValid; } private setModelValid(b: boolean) { const old = this.isValid;
141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
this._isValidModel = b; if (this.isValid !== old) { this.emitValidChanged(); } } private validateModel() { const { isValid, message } = this.validateModelValue(this.getModelValue()); this._errorMessageModel = message; this.detectChanges(); 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._model; } /** * événement de changement de la valeur du modèle */ private emitModelChanged() { // console.log("emit model change", this.constructor.name); this.change.emit({ "action": "model", "value": this.getModelValue() }); } private setAndValidateModel(sender: any, v: any) { this.setModelValue(sender, v); this.emitModelChanged(); this.validateModel(); } public set model(v: any) { this.beforeSetModel(); this._model = v; this.afterSetModel(); this.updateAll(); this.detectChanges(); } /** * MAJ et validation de l'UI */ protected updateAndValidateUI() { this._uiValue = this.modelToUI(this.getModelValue()); this.validateUI(); } public get uiValue() { return this._uiValue; } /* * fonction appelée lorsque l'utilisateur fait une saisie
211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
* @param ui valeur dans le contrôle */ public set uiValue(ui: any) { this._uiValue = ui; this.updateModelFromUI(); } /** * met à jour le modèle d'après la saisie */ public updateModelFromUI() { if (this.validateUI()) { this.setAndValidateModel(this, this.uiToModel(this._uiValue)); } } private updateAll() { this.updateAndValidateUI(); this.validateModel(); } /** * appelé quand les @Input changent */ public ngOnChanges() { // console.log("ng on changes (generic input)", this.constructor.name); this.updateAll(); } /** * appelé avant le changement de modèle */ protected beforeSetModel() { } /** * appelé après le changement de modèle */ protected afterSetModel() { } /** * retourne la valeur du modèle */ protected abstract getModelValue(): any; /** * affecte la valeur du modèle */ protected abstract setModelValue(sender: any, v: any); /** * valide une valeur de modèle : est ce une valeur acceptable ? (par ex, nombre dans un intervalle, valeur dans une liste, ...) * @param v valeur à valider * @returns isValid : true si la valeur est valide, false sinon * @returns message : message d'erreur */ protected abstract validateModelValue(v: any): { isValid: boolean, message: string }; /** * convertit le modèle en valeur affichable par l'UI */ protected abstract modelToUI(v: any): string; /** * valide une valeur saisie dans l'UI (forme de la saisie : est ce bien une date, un nombre, ...) * @param ui saisie à valider * @returns isValid : true si la valeur est valide, false sinon * @returns message : message d'erreur */ protected abstract validateUIValue(ui: string): { isValid: boolean, message: string };
281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
/** * convertit une valeur saisie dans l'UI en valeur affectable au modèle */ protected abstract uiToModel(ui: string): any; } /* * exemples d'utilisation de GenericInputComponent */ /* import { Component } from "@angular/core"; import { isNumeric, Message } from "jalhyd"; // exemple où le modèle est un type simple (number) @Component({ selector: "test-input", template: `<div class="md-form form-sm"> <input mdbActive type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue"> <label for="form1">{{title}}</label> <small *ngIf="showError" class="text-danger">{{errorMessage}}</small> </div>` }) export class TestInputComponent extends GenericInputComponent { constructor() { super(); this._model = 0; } protected getModelValue(): any { return this._model; } protected setModelValue(v: any) { this._model = v; } protected validateModelValue(v: any): { isValid: boolean, message: string } { let msg = undefined; let valid = false; if (v < 0) msg = "La valeur n'est pas >= 0 "; else valid = true; return { isValid: valid, message: msg }; } protected modelToUI(v: any): string { if (typeof (v) === "number") return String(v); return undefined; } protected validateUIValue(ui: string): { isValid: boolean, message: string } { let valid: boolean = false; let msg: string = undefined; if (! isNumeric(ui)) msg = "Veuillez entrer une valeur numérique"; else valid = true; return { isValid: valid, message: msg }; } protected uiToModel(ui: string): any {
351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
return +ui; } } // exemple où le modèle est une classe dont on ne gère qu'un membre import { ParamDefinition } from "jalhyd"; @Component({ selector: "test2-input", template: `<div class="md-form form-sm"> <input mdbActive type="text" id="form1" class="form-control" [disabled]="isDisabled" [(ngModel)]="uiValue"> <label for="form1">{{title}}</label> <small *ngIf="showError" class="text-danger">{{errorMessage}}</small> </div>` }) export class Test2InputComponent extends GenericInputComponent { constructor() { super(); } // paramètre géré private get _param(): ParamDefinition { return this._model; } protected getModelValue(): any { return this._param.getValue(); } protected setModelValue(v: any) { this._param.setValue(v); } protected validateModelValue(v: any): { isValid: boolean, message: string } { let msg = undefined; let valid = false; if (v < 0) msg = "La valeur n'est pas >= 0 "; else valid = true; return { isValid: valid, message: msg }; } protected modelToUI(v: any): string { if (typeof (v) === "number") return String(v); return undefined; } protected validateUIValue(ui: string): { isValid: boolean, message: string } { let valid: boolean = false; let msg: string = undefined; if (! isNumeric(ui)) msg = "Veuillez entrer une valeur numérique"; else valid = true; return { isValid: valid, message: msg }; } protected uiToModel(ui: string): any { return +ui; } } /**/
421