Skip to content
Snippets Groups Projects
formulaire.service.ts 21 KiB
Newer Older
import { saveAs } from "file-saver";
mathias.chouet's avatar
mathias.chouet committed
import { CalculatorType, EnumEx, Observable, ParamDefinition } from "jalhyd";
import { HttpService } from "../../services/http/http.service";
import { I18nService } from "../../services/internationalisation/internationalisation.service";
import { FormulaireDefinition } from "../../formulaire/definition/form-definition";
import { FormulaireElement } from "../../formulaire/formulaire-element";
import { InputField } from "../../formulaire/input-field";
import { SelectField } from "../../formulaire/select-field";
import { CheckField } from "../../formulaire/check-field";
import { StringMap } from "../../stringmap";
import { FormulaireBase } from "../../formulaire/definition/concrete/form-base";
import { FormulaireLechaptCalmon } from "../../formulaire/definition/concrete/form-lechapt-calmon";
import { FormulaireSectionParametree } from "../../formulaire/definition/concrete/form-section-parametree";
import { FormulaireCourbeRemous } from "../../formulaire/definition/concrete/form-courbe-remous";
import { FormulaireRegimeUniforme } from "../../formulaire/definition/concrete/form-regime-uniforme";
import { FormulaireParallelStructure } from "../../formulaire/definition/concrete/form-parallel-structures";
import { NgParameter } from "../../formulaire/ngparam";

@Injectable()
export class FormulaireService extends Observable {
    private _formulaires: FormulaireDefinition[];

    private _currentFormId: string = null;
    constructor(
        private i18nService: I18nService,
        private httpService: HttpService) {

        super();
        this._formulaires = [];
    }

    private get _intlService(): I18nService {
        return this.i18nService;
        return this.httpService;
    public get formulaires(): FormulaireDefinition[] {
        return this._formulaires;
    }

    private loadLocalisation(calc: CalculatorType): Promise<any> {
mathias.chouet's avatar
mathias.chouet committed
        const f: string = this.getConfigPathPrefix(calc) + this._intlService.currentLanguage.tag + ".json";
        const prom = this._httpService.httpGetRequestPromise(f);
        return prom.then((j) => {
            return j as StringMap;
    }

    /**
     * met à jour la langue du formulaire
     * @param formId id unique du formulaire
     * @param localisation ensemble id-message traduit
     */
    private updateFormulaireLocalisation(formId: string, localisation: StringMap) {
mathias.chouet's avatar
mathias.chouet committed
        for (const f of this._formulaires) {
David Dorchies's avatar
David Dorchies committed
            if (f.uid === formId) {
                f.updateLocalisation(localisation);
                break;
            }
    /**
     * charge la localisation et met à jour la langue du formulaire
     */
    private loadUpdateFormulaireLocalisation(f: FormulaireDefinition): Promise<FormulaireDefinition> {
        return this.loadLocalisation(f.calculatorType)
            .then(localisation => {
                this.updateFormulaireLocalisation(f.uid, localisation);
            });
    }

    public updateLocalisation() {
        for (const c of EnumEx.getValues(CalculatorType)) {
            const prom: Promise<StringMap> = this.loadLocalisation(c);
            prom.then(loc => {
David Dorchies's avatar
David Dorchies committed
                for (const f of this._formulaires) {
                    if (f.calculatorType === c) {
                        this.updateFormulaireLocalisation(f.uid, loc);
    public getLocalisedTitleFromCalculatorType(type: CalculatorType) {
        const sCalculator: string = CalculatorType[type].toUpperCase();
        return this._intlService.localizeText(`INFO_${sCalculator}_TITRE`);
    public loadConfig(ct: CalculatorType): Promise<any> {
        const f: string = this.getConfigPathPrefix(ct) + "config.json";
        return this._httpService.httpGetRequestPromise(f);
    private newFormulaire(ct: CalculatorType, jsonState?: {}): FormulaireDefinition {
        let f: FormulaireDefinition;
        switch (ct) {
            case CalculatorType.ConduiteDistributrice:
            case CalculatorType.PabDimensions:
            case CalculatorType.PabPuissance:
            case CalculatorType.MacroRugo:
                f = new FormulaireBase();
            case CalculatorType.Dever:
            case CalculatorType.Cloisons:
                f = new FormulaireParallelStructure();
mathias.chouet's avatar
mathias.chouet committed
                throw new Error(`FormulaireService.newFormulaire() : type de calculette ${CalculatorType[ct]} non pris en charge`);
        f.defaultProperties["calcType"] = ct;

        return f;
    }

    /**
     * crée un formulaire d'un type donné
     * @param ct type de formulaire
     */
    public createFormulaire(ct: CalculatorType, jsonState?: {}): Promise<FormulaireDefinition> {
        const f: FormulaireDefinition = this.newFormulaire(ct);
        this._formulaires.push(f);
mathias.chouet's avatar
mathias.chouet committed
        const prom: Promise<any> = this.loadConfig(ct);
mathias.chouet's avatar
mathias.chouet committed
            f.preparseConfig(s);
                f.calculatorName = decode(this.getLocalisedTitleFromCalculatorType(ct));
            f.initNub();
            f.parseConfig();
                f.deserialiseJSON(jsonState);
            // la méthode loadUpdateFormulaireLocalisation retourne une Promise; le fait de retourner une Promise dans un then
            // fait que le then suivant est exécuté juste après.
            return this.loadUpdateFormulaireLocalisation(f);
mathias.chouet's avatar
mathias.chouet committed
        }).then(fi => {
            fi.applyDependencies();
                    "action": "createForm",
mathias.chouet's avatar
mathias.chouet committed
                    "form": fi
mathias.chouet's avatar
mathias.chouet committed
            return fi;
    public getFormulaireFromId(uid: string): FormulaireDefinition {
mathias.chouet's avatar
mathias.chouet committed
        for (const f of this._formulaires) {
            if (f.uid === uid) {
                return f;
mathias.chouet's avatar
mathias.chouet committed
            }
        }
    public getInputField(formId: string, elemId: string): InputField {
        const s = this.getFormulaireElementById(formId, elemId);
mathias.chouet's avatar
mathias.chouet committed
        if (!(s instanceof InputField)) {
            throw new Error("Form element with id '" + elemId + "' is not an input");
        }
        return <InputField>s;
    }

    public getCheckField(formId: string, elemId: string): CheckField {
        const s = this.getFormulaireElementById(formId, elemId);
mathias.chouet's avatar
mathias.chouet committed
        if (!(s instanceof CheckField)) {
            throw new Error("Form element with id '" + elemId + "' is not a checkbox");
mathias.chouet's avatar
mathias.chouet committed
        }
        return <CheckField>s;
    public getSelectField(formId: string, elemId: string): SelectField {
        const s = this.getFormulaireElementById(formId, elemId);
mathias.chouet's avatar
mathias.chouet committed
        if (!(s instanceof SelectField)) {
            throw new Error("Form element with id '" + elemId + "' is not a select");
mathias.chouet's avatar
mathias.chouet committed
        }
        return <SelectField>s;
    private getFormulaireElementById(formId: string, elemId: string): FormulaireElement {
mathias.chouet's avatar
mathias.chouet committed
        for (const f of this._formulaires) {
David Dorchies's avatar
David Dorchies committed
            if (f.uid === formId) {
                const s = f.getFormulaireNodeById(elemId);
mathias.chouet's avatar
mathias.chouet committed
                if (s !== undefined) {
mathias.chouet's avatar
mathias.chouet committed
                }
mathias.chouet's avatar
mathias.chouet committed
        }
    public getParamdefParentForm(prm: ParamDefinition): FormulaireDefinition {
mathias.chouet's avatar
mathias.chouet committed
        for (const f of this._formulaires) {
            for (const p of f.allFormElements) {
                if (p instanceof NgParameter) {
                    if (p.paramDefinition.uid === prm.uid) {
mathias.chouet's avatar
mathias.chouet committed
    /**
     * retrouve un formulaire à partir d'un uid de Nub
     */
    public getFormulaireFromNubId(uid: string) {
        for (const f of this._formulaires) {
            if (f.hasNubId(uid)) {
                return f;
            }
        }
    }

    public getConfigPathPrefix(ct: CalculatorType): string {
mathias.chouet's avatar
mathias.chouet committed
        if (ct === undefined) {
            throw new Error("FormulaireService.getConfigPathPrefix() : invalid undefined CalculatorType");
        }

        switch (ct) {
            case CalculatorType.ConduiteDistributrice:
                return "app/calculators/cond_distri/cond_distri.";

            case CalculatorType.LechaptCalmon:
                return "app/calculators/lechapt-calmon/lechapt-calmon.";

            case CalculatorType.SectionParametree:
                return "app/calculators/section-param/section-param.";

            case CalculatorType.RegimeUniforme:
                return "app/calculators/regime-uniforme/regime-uniforme.";

            case CalculatorType.CourbeRemous:
                return "app/calculators/remous/remous.";

            case CalculatorType.PabDimensions:
                return "app/calculators/pab-dimensions/pab-dimensions.";

            case CalculatorType.PabPuissance:
                return "app/calculators/pab-puissance/pab-puissance.";

francois.grand's avatar
francois.grand committed
            case CalculatorType.Structure:
                return "app/calculators/ouvrages/ouvrages.";

            case CalculatorType.ParallelStructure:
                return "app/calculators/parallel-structures/parallel-structures.";

            case CalculatorType.Dever:
                return "app/calculators/dever/dever.";

            case CalculatorType.Cloisons:
                return "app/calculators/cloisons/cloisons.";

            case CalculatorType.MacroRugo:
                return "app/calculators/macrorugo/macrorugo.";

mathias.chouet's avatar
mathias.chouet committed
                throw new Error("FormulaireService.getConfigPathPrefix() : valeur de CalculatorType " + ct + " non implémentée");
    public requestCloseForm(uid: string) {
        const form = this.getFormulaireFromId(uid);
David Dorchies's avatar
David Dorchies committed
        if (form !== undefined) {
            this._formulaires = this._formulaires.filter(f => f.uid !== uid);

            this.notifyObservers({
                "action": "closeForm",
            });
        }
    }

    public get currentFormId() {
        return this._currentFormId;
    }

    public setCurrentForm(formId: string) {
        const form = this.getFormulaireFromId(formId);
David Dorchies's avatar
David Dorchies committed
        if (form === undefined) {
            this._currentFormId = null;
            this.notifyObservers({
                "action": "invalidFormId",
                "formId": formId
            });
David Dorchies's avatar
David Dorchies committed
        } else {
            this._currentFormId = formId;
            this.notifyObservers({
                "action": "currentFormChanged",
                "formId": formId

    private get currentForm(): FormulaireDefinition {
        return this.getFormulaireFromId(this._currentFormId);
    }

    public get currentFormHasResults(): boolean {
        const form = this.currentForm;
mathias.chouet's avatar
mathias.chouet committed
        if (form !== undefined) {
mathias.chouet's avatar
mathias.chouet committed
        }
    private readSingleFile(file: File): Promise<any> {
        return new Promise<any>((resolve, reject) => {
            const fr = new FileReader();

            fr.onload = () => {
                resolve(fr.result);
            };

            fr.onerror = () => {
                fr.abort();
                reject(new Error(`Erreur de lecture du fichier ${file.name}`));
            };

            fr.readAsText(file);
        });
    }

    /**
     * charge une session en tenant compte des calculettes sélectionnées
     * @param f fichier session
     * @param formInfos infos sur les calculettes @see DialogLoadSessionComponent.calculators
     */
    public loadSession(f: File, formInfos: any[]) {
        this.readSingleFile(f).then(s => {
            const session = JSON.parse(s);
mathias.chouet's avatar
mathias.chouet committed
            for (const k in session) {
                switch (k) {
                    case "session":
                        this.deserialiseSession(session[k], formInfos);
                        break;

                    default:
                        throw new Error(`session file : invalid key '${k}'`);
                }
mathias.chouet's avatar
mathias.chouet committed
            }
        }).catch(err => {
            throw err;
        });
    public saveSession(session: {}, filename?: string) {
        const blob = new Blob([JSON.stringify(session)], { type: "text/plain;charset=utf-8" });
    /**
     * obtient des infos (nom, uid des calculettes) d'un fichier session
     * @param f fichier session
     */
    public calculatorInfosFromSessionFile(f: File): Promise<any[]> {
        return this.readSingleFile(f).then(s => {
            const res: any[] = [];
            const session = JSON.parse(s);

            // liste des noms de calculettes
mathias.chouet's avatar
mathias.chouet committed
            for (const k in session) {
                switch (k) {
                    case "session":
                        const sess = session[k];
                        const elems = sess["elements"];
mathias.chouet's avatar
mathias.chouet committed
                        for (const e of elems) {
mathias.chouet's avatar
mathias.chouet committed
                            for (const l in e) {
                                if (l === "form") {
                                    const form = e[l];
                                    res.push({ "uid": form["uid"], "title": form["id"] });
                                }
mathias.chouet's avatar
mathias.chouet committed
                            }
                        }
                        break;

                    default:
                        throw new Error(`session file : invalid key '${k}'`);
                }
mathias.chouet's avatar
mathias.chouet committed
            }
    public saveForm(f: FormulaireDefinition) {
        this.notifyObservers({
            "action": "saveForm",
            "form": f
        });
    }
    private deserialiseForm(elements: {}): Promise<FormulaireDefinition> {
        const props = elements["props"];
        const ct: CalculatorType = props["calcType"];
        return this.createFormulaire(ct, elements);
    private deserialiseSessionElement(element: {}, formInfos: any[]): Promise<FormulaireDefinition> {
        const keys = Object.keys(element);
mathias.chouet's avatar
mathias.chouet committed
        if (keys.length !== 1) {
            throw new Error(`session file : invalid session object '${element}'`);
mathias.chouet's avatar
mathias.chouet committed
        }
mathias.chouet's avatar
mathias.chouet committed
                for (const i of formInfos) {
                    if (i["uid"] === form["uid"] && i["selected"]) {
                        return this.deserialiseForm(form);
mathias.chouet's avatar
mathias.chouet committed
                    }
                }
                break;

            default:
                throw new Error(`session file : invalid key '${keys[0]}' in session object`);
        }
    }

    /**
     * met à jour les liens d'un formulaire
     * @param json conf du formulaire
     * @param formInfos métadonnées sur les formulaires chargés
     * @param form formulaire dont on met à jour les liens
     * @param uidMap table de correspondance uid dans le fichier de conf <-> uid en mémoire
     */
    private updateFormLinks(json: {}, formInfos: any[], form: FormulaireDefinition, uidMap: {}[]) {
        for (const i of formInfos) {
            if (i["uid"] === json["uid"] && i["selected"]) {
                form.updateParamsLinks(json, uidMap);
            }
        }
    }

    /**
     * MAJ des liens entre paramètres lors de la désérialisation JSON
     */

    private updateParamsLinks(json: {}, formInfos: any[], oldFormCount: number) {
        // table de correspondance des uid fichier <-> objets mémoire
        // forme : tableau d'objets de la forme :
        // { "type" : <type de l'objet. "form" pour formulaire>,
        //   "old": <uid dans le fichier>,
        //   "new": <uid de l'objet mémoire>}

        const uidMap = [];
        for (const ks in json) {
            switch (ks) {
                case "elements":
                    let n = oldFormCount;
                    for (const e of json[ks]) {
                        if (Object.keys(e)[0] === "form") {
                            uidMap.push({
                                "type": "form",
                                "old": e["form"]["uid"],
                                "new": this._formulaires[n].uid
                            });
                            n++;
                        }
                    }
            }
        }

        // MAJ liens

        for (const ks in json) {
            switch (ks) {
                case "elements":
                    let n = 0;
                    for (const e of json[ks]) {
                        if (Object.keys(e)[0] === "form") {
                            this.updateFormLinks(e["form"], formInfos, this._formulaires[n + oldFormCount], uidMap);
                            n++;
                        }
                    }
                    break;

                default:
                    throw new Error(`session file : invalid key '${ks}' in session object`);
            }
        }
    }

    private deserialiseSession(elements: {}, formInfos: any[]) {
        let p: Promise<FormulaireDefinition>;

        const oldFormCount = this._formulaires.length;
mathias.chouet's avatar
mathias.chouet committed
        for (const ks in elements) {
mathias.chouet's avatar
mathias.chouet committed
                    for (const e of elements[ks]) {
                        if (p === undefined) {
                            p = this.deserialiseSessionElement(e, formInfos);
                        } else {
                            p = p.then(_ => {
                                return this.deserialiseSessionElement(e, formInfos);
                            });
                        }
mathias.chouet's avatar
mathias.chouet committed
                    }
                    break;

                default:
                    throw new Error(`session file : invalid key '${ks}' in session object`);
            }
mathias.chouet's avatar
mathias.chouet committed
        }
        p.then(_ => this.updateParamsLinks(elements, formInfos, oldFormCount));
     * @returns liste des valeurs liables à un paramètre sous la forme d'un tableau d'objets
     * {"param":<paramètre lié>, "nub":<Nub d'origine du paramètre lié>, "formTitle":<nom de la calculette liée au nub>}
     * @param p paramètre qui sert de clé de recherche des paramètres liables
    public getLinkableValues(p: NgParameter): any[] {
        const res: any[] = [];
mathias.chouet's avatar
mathias.chouet committed
        if (p !== undefined) {
                const sn = f.currentNub;
                try {
                    // on vérifie que le paramètre en entrée appartient au nub
                    const np = sn.getParameter(p.symbol);
                    // si oui, on demande à exclure des valeurs retournées le résultat du même nom que le paramètre
                    const exclude = np !== undefined ? p.paramDefinition.uid === np.uid : false;
                    // valeurs liables
                    const ps = sn.getLinkableValues(p.paramDefinition, undefined, exclude);
mathias.chouet's avatar
mathias.chouet committed
                    for (const npp of ps) {
                        npp["formTitle"] = f.calculatorName;
                        res.push(npp);
mathias.chouet's avatar
mathias.chouet committed
                } catch (e) {
mathias.chouet's avatar
mathias.chouet committed
        }

    /**
     * filtre les valeurs liables à un paramètre :
mathias.chouet's avatar
mathias.chouet committed
     * - supprime les valeurs non affichées dans leur parent (ce n'est pas le cas par ex
     * pour Q, Z1, Z2 dans les ouvrages enfants des ouvrages //)
     * @param values valeurs liables (modifié par la méthode)
     */
    public filterLinkableValues(values: any[]): any[] {
        // suppression des paramètres non affichés

        for (let i = values.length - 1; i >= 0; i--) {
            const v = values[i].value;
            if (v instanceof ParamDefinition) {
                // pour chaque paramètre...
                const prm: ParamDefinition = v;

                const parentForm: FormulaireDefinition = this.getParamdefParentForm(prm);

                // ... on cherche s'il est affiché dans son parent
                let found = false;
mathias.chouet's avatar
mathias.chouet committed
                if (parentForm !== undefined) {
                    for (const fe of parentForm.allFormElements) {
                        if (fe instanceof NgParameter) {
                            if (fe.paramDefinition.uid === prm.uid) {
                                found = true;
                                break;
                            }
mathias.chouet's avatar
mathias.chouet committed
                if (!found) {
mathias.chouet's avatar
mathias.chouet committed
                }