From 1dcfad9c699fe17edab1a1379db46c80c94a2415 Mon Sep 17 00:00:00 2001
From: jojohoch <joachim.hoch@iqb.hu-berlin.de>
Date: Wed, 28 Jul 2021 09:34:59 +0200
Subject: [PATCH] [player] Show different validation warnings

* Move setting of validators from `FormElementComponent` to
`ValidationMessageComponent`
* Add validators and warnings to `ValidationMessageComponent`
* Move subscriptions for form controls and their validators from
`PageComponent` to `FormComponent`
---
 .../form-element-component.directive.ts       | 18 +----
 projects/common/form.service.ts               |  2 +-
 .../src/app/components/form.component.ts      | 19 ++++-
 .../src/app/components/page.component.ts      | 38 +--------
 .../validation-message.component.ts           | 77 +++++++++++++++++--
 5 files changed, 92 insertions(+), 62 deletions(-)

diff --git a/projects/common/form-element-component.directive.ts b/projects/common/form-element-component.directive.ts
index c57858693..90efb4720 100644
--- a/projects/common/form-element-component.directive.ts
+++ b/projects/common/form-element-component.directive.ts
@@ -2,7 +2,7 @@ import {
   Directive, EventEmitter, OnDestroy, OnInit, Output
 } from '@angular/core';
 import {
-  FormControl, FormGroup, ValidatorFn, Validators
+  FormControl, FormGroup
 } from '@angular/forms';
 import { Subject } from 'rxjs';
 import { pairwise, startWith, takeUntil } from 'rxjs/operators';
@@ -40,22 +40,6 @@ export abstract class FormElementComponent extends ElementComponent implements O
           this.formValueChanged.emit({ id: this.elementModel.id, values: [prevValue, nextValue] });
         }
       });
-    // TODO: find better solution
-    setTimeout((): void => {
-      this.formService.setValidators({
-        id: this.elementModel.id,
-        validators: this.validators,
-        formGroup: this.parentForm
-      });
-    });
-  }
-
-  private get validators(): ValidatorFn[] {
-    const validators: ValidatorFn[] = [];
-    if (this.elementModel.required) {
-      validators.push(Validators.required);
-    }
-    return validators;
   }
 
   private get formControl(): FormControl {
diff --git a/projects/common/form.service.ts b/projects/common/form.service.ts
index e8633688c..ba2ef1355 100644
--- a/projects/common/form.service.ts
+++ b/projects/common/form.service.ts
@@ -42,6 +42,6 @@ export class FormService {
   }
 
   setValidators(validations: FormControlValidators): void {
-    this._validationsAdded.next(validations);
+    Promise.resolve().then(() => this._validationsAdded.next(validations));
   }
 }
diff --git a/projects/player/src/app/components/form.component.ts b/projects/player/src/app/components/form.component.ts
index b4dce6d80..0933e2355 100644
--- a/projects/player/src/app/components/form.component.ts
+++ b/projects/player/src/app/components/form.component.ts
@@ -5,7 +5,9 @@ import { takeUntil } from 'rxjs/operators';
 import { FormService } from '../../../../common/form.service';
 import { VeronaSubscriptionService } from '../services/verona-subscription.service';
 import { VeronaPostService } from '../services/verona-post.service';
-import { FormGroupPage, ValueChangeElement } from '../../../../common/form';
+import {
+  FormControlElement, FormControlValidators, FormGroupPage, ValueChangeElement
+} from '../../../../common/form';
 import {
   PlayerConfig, UnitState, VopNavigationDeniedNotification
 } from '../models/verona';
@@ -50,6 +52,12 @@ export class FormComponent implements OnDestroy {
     this.formService.groupAdded
       .pipe(takeUntil(this.ngUnsubscribe))
       .subscribe((group: FormGroupPage): void => this.addGroup(group));
+    this.formService.controlAdded.pipe(
+      takeUntil(this.ngUnsubscribe)
+    ).subscribe((control: FormControlElement): void => this.addControl(control));
+    this.formService.validationsAdded.pipe(
+      takeUntil(this.ngUnsubscribe)
+    ).subscribe((validations: FormControlValidators): void => this.setValidators(validations));
     this.veronaSubscriptionService.vopNavigationDeniedNotification
       .pipe(takeUntil(this.ngUnsubscribe))
       .subscribe((message: VopNavigationDeniedNotification): void => this.onNavigationDenied(message));
@@ -58,6 +66,15 @@ export class FormComponent implements OnDestroy {
       .subscribe((formValues: { pages: Record<string, string>[] }): void => this.onFormChanges(formValues));
   }
 
+  private addControl = (control: FormControlElement): void => {
+    control.formGroup.addControl(control.id, control.formControl);
+  };
+
+  private setValidators = (validators: FormControlValidators): void => {
+    validators.formGroup.controls[validators.id].setValidators(validators.validators);
+    validators.formGroup.controls[validators.id].updateValueAndValidity();
+  };
+
   private onNavigationDenied(message: VopNavigationDeniedNotification): void {
     // eslint-disable-next-line no-console
     console.log('player: onNavigationDenied', message);
diff --git a/projects/player/src/app/components/page.component.ts b/projects/player/src/app/components/page.component.ts
index b8e18db44..d5f983d49 100644
--- a/projects/player/src/app/components/page.component.ts
+++ b/projects/player/src/app/components/page.component.ts
@@ -1,12 +1,9 @@
 import {
-  Component, Input, OnDestroy, OnInit
+  Component, Input, OnInit
 } from '@angular/core';
 import { FormGroup } from '@angular/forms';
-import { takeUntil } from 'rxjs/operators';
-import { Subject } from 'rxjs';
 import { UnitPage } from '../../../../common/unit';
 import { FormService } from '../../../../common/form.service';
-import { FormControlElement, FormControlValidators } from '../../../../common/form';
 
 @Component({
   selector: 'app-page',
@@ -25,46 +22,15 @@ import { FormControlElement, FormControlValidators } from '../../../../common/fo
   `
 })
 
-export class PageComponent implements OnInit, OnDestroy {
+export class PageComponent implements OnInit {
   @Input() page!: UnitPage;
   @Input() parentForm!: FormGroup;
   pageForm!: FormGroup;
-  private ngUnsubscribe = new Subject<void>();
 
   constructor(private formService: FormService) {}
 
   ngOnInit(): void {
     this.pageForm = new FormGroup({});
     this.formService.registerFormGroup({ id: this.page.id, formGroup: this.pageForm });
-    this.initSubscriptions();
-  }
-
-  private initSubscriptions(): void {
-    this.formService.controlAdded.pipe(
-      takeUntil(this.ngUnsubscribe)
-    ).subscribe((control: FormControlElement): void => this.addControl(control));
-    this.formService.validationsAdded.pipe(
-      takeUntil(this.ngUnsubscribe)
-    ).subscribe((validations: FormControlValidators): void => this.setValidators(validations));
-  }
-
-  private addControl(control: FormControlElement): void {
-    // we need to check that the control belongs to the page
-    if (this.pageForm === control.formGroup) {
-      this.pageForm.addControl(control.id, control.formControl);
-    }
-  }
-
-  private setValidators(validators: FormControlValidators): void {
-    // we need to check that the control belongs to the page
-    if (this.pageForm === validators.formGroup) {
-      this.pageForm.controls[validators.id].setValidators(validators.validators);
-      this.pageForm.controls[validators.id].updateValueAndValidity();
-    }
-  }
-
-  ngOnDestroy(): void {
-    this.ngUnsubscribe.next();
-    this.ngUnsubscribe.complete();
   }
 }
diff --git a/projects/player/src/app/components/validation-message.component.ts b/projects/player/src/app/components/validation-message.component.ts
index 59e39b9d1..8bf53ae60 100644
--- a/projects/player/src/app/components/validation-message.component.ts
+++ b/projects/player/src/app/components/validation-message.component.ts
@@ -1,13 +1,32 @@
 import { Component, OnInit } from '@angular/core';
-import { FormControl, FormGroup } from '@angular/forms';
-import { InputUIElement, UnitUIElement } from '../../../../common/unit';
+import {
+  FormControl, FormGroup, ValidatorFn, Validators
+} from '@angular/forms';
+import {
+  InputUIElement, NumberFieldElement, TextFieldElement, UnitUIElement
+} from '../../../../common/unit';
+import { FormService } from '../../../../common/form.service';
 
 @Component({
   selector: 'app-error-message',
   template: `
-      <mat-error *ngIf="formElementControl && formElementControl.touched && formElementControl.errors">
-          {{requiredMessage}}
-      </mat-error>
+      <ng-container *ngIf="formElementControl && formElementControl.touched">
+          <mat-error *ngIf="formElementControl.errors?.required">
+              {{requiredMessage}}
+          </mat-error>
+          <mat-error *ngIf="formElementControl.errors?.minlength">
+              {{minLengthMessage}}
+          </mat-error>
+          <mat-error *ngIf="formElementControl.errors?.maxlength">
+              {{maxLengthMessage}}
+          </mat-error>
+          <mat-error *ngIf="formElementControl.errors?.min">
+              {{minMessage}}
+          </mat-error>
+          <mat-error *ngIf="formElementControl.errors?.max">
+              {{maxMessage}}
+          </mat-error>
+      </ng-container>
   `
 })
 
@@ -16,12 +35,56 @@ export class ValidationMessageComponent implements OnInit {
   parentForm!: FormGroup;
   formElementControl!: FormControl;
 
+  constructor(private formService: FormService) {}
+
   ngOnInit(): void {
     this.formElementControl = this.parentForm.controls[this.elementModel.id] as FormControl;
+    this.formService.setValidators({
+      id: this.elementModel.id,
+      validators: this.validators,
+      formGroup: this.parentForm
+    });
+  }
+
+  private get validators(): ValidatorFn[] {
+    const validators: ValidatorFn[] = [];
+    if (this.elementModel.required) {
+      validators.push(Validators.required);
+    }
+    if (this.elementModel.min) {
+      if (this.elementModel.type === 'number_field') {
+        validators.push(Validators.min(<number> this.elementModel.min));
+      } else {
+        validators.push(Validators.minLength(<number> this.elementModel.min));
+      }
+    }
+    if (this.elementModel.max) {
+      if (this.elementModel.type === 'number_field') {
+        validators.push(Validators.max(<number> this.elementModel.max));
+      } else {
+        validators.push(Validators.maxLength(<number> this.elementModel.maxLength));
+      }
+    }
+    return validators;
   }
 
-  // eslint-disable-next-line class-methods-use-this
   get requiredMessage(): string {
-    return (this.elementModel as InputUIElement).validationWarnMessage || 'Wert muss angegeben werden';
+    return (this.elementModel as InputUIElement).requiredWarnMessage || 'Eingabe erforderlich';
+  }
+
+  get minLengthMessage(): string {
+    return (this.elementModel as TextFieldElement).minWarnMessage || 'Eingabe zu kurz';
+  }
+
+  get maxLengthMessage(): string {
+    return (this.elementModel as TextFieldElement).maxWarnMessage || 'Eingabe zu lang';
+  }
+
+  get minMessage(): string {
+    return (this.elementModel as NumberFieldElement).minWarnMessage || 'Wert zu klein';
+  }
+
+  get maxMessage(): string {
+    return (this.elementModel as NumberFieldElement).maxWarnMessage || 'Wert zu groß';
   }
 }
-- 
GitLab