diff --git a/projects/common/element-components/audio.component.ts b/projects/common/element-components/audio.component.ts index 74267d9b525f8427f1cffe84db55ae01b8b653cb..d07463380c101cdd7ff18c3c60b418024f0a439f 100644 --- a/projects/common/element-components/audio.component.ts +++ b/projects/common/element-components/audio.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Output } from '@angular/core'; import { ElementComponent } from '../element-component.directive'; import { AudioElement } from '../models/audio-element'; -import { ValueChangeElement } from '../form'; +import { ValueChangeElement } from '../models/uI-element'; @Component({ selector: 'app-audio', diff --git a/projects/common/element-components/compound-elements/likert.component.ts b/projects/common/element-components/compound-elements/likert.component.ts index 85d34ba97020442d8ba7b59cc04d25c8a4c517aa..955633abb368becf7af14b29abdd979337833785 100644 --- a/projects/common/element-components/compound-elements/likert.component.ts +++ b/projects/common/element-components/compound-elements/likert.component.ts @@ -1,8 +1,7 @@ import { Component, EventEmitter, Output } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { LikertElement } from '../../models/compound-elements/likert-element'; -import { ValueChangeElement } from '../../form'; -import { InputElementValue } from '../../models/uI-element'; +import { InputElementValue, ValueChangeElement } from '../../models/uI-element'; import { LikertElementRow } from '../../models/compound-elements/likert-element-row'; @Component({ diff --git a/projects/common/element-components/control-bar/control-bar.component.ts b/projects/common/element-components/control-bar/control-bar.component.ts index bd97d67fbad9860872be4a031e3ed80bbf5970a2..5159636a898a97d6a05526b005a7310b8afb9b60 100644 --- a/projects/common/element-components/control-bar/control-bar.component.ts +++ b/projects/common/element-components/control-bar/control-bar.component.ts @@ -4,7 +4,7 @@ import { import { MatSliderChange } from '@angular/material/slider'; import { AudioElement } from '../../models/audio-element'; import { VideoElement } from '../../models/video-element'; -import { ValueChangeElement } from '../../form'; +import { ValueChangeElement } from '../../models/uI-element'; @Component({ selector: 'app-control-bar', diff --git a/projects/common/element-components/video.component.ts b/projects/common/element-components/video.component.ts index 74874bd8d0755096a70cb719e2d0c10b0f9f2b9d..0398d550ae9de3d340cea18b8f18345408748374 100644 --- a/projects/common/element-components/video.component.ts +++ b/projects/common/element-components/video.component.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Output } from '@angular/core'; import { ElementComponent } from '../element-component.directive'; import { VideoElement } from '../models/video-element'; -import { ValueChangeElement } from '../form'; +import { ValueChangeElement } from '../models/uI-element'; @Component({ selector: 'app-video', diff --git a/projects/common/form-element-component.directive.ts b/projects/common/form-element-component.directive.ts index 1703b60c8b9d9b9279356d7530ba8fbf97997a7c..b0cf4cc0d1c89f065d253e113e58516794673caf 100644 --- a/projects/common/form-element-component.directive.ts +++ b/projects/common/form-element-component.directive.ts @@ -6,44 +6,30 @@ import { } from '@angular/forms'; import { Subject } from 'rxjs'; import { pairwise, startWith, takeUntil } from 'rxjs/operators'; -import { FormService } from './form.service'; -import { ValueChangeElement } from './form'; import { ElementComponent } from './element-component.directive'; -import { InputElement } from './models/uI-element'; +import { InputElement, InputElementValue, ValueChangeElement } from './models/uI-element'; @Directive() export abstract class FormElementComponent extends ElementComponent implements OnInit, OnDestroy { @Output() formValueChanged = new EventEmitter<ValueChangeElement>(); + @Output() setValidators = new EventEmitter<ValidatorFn[]>(); parentForm!: FormGroup; - defaultValue!: string | number | boolean | undefined; + defaultValue!: InputElementValue; elementFormControl!: FormControl; private ngUnsubscribe = new Subject<void>(); - constructor(private formService: FormService) { - super(); - } - ngOnInit(): void { - this.formService.registerFormControl({ - id: this.elementModel.id, - formControl: new FormControl((this.elementModel as InputElement).value), - formGroup: this.parentForm - }); this.elementFormControl = this.formControl; this.updateFormValue((this.elementModel as InputElement).value); - this.formService.setValidators({ - id: this.elementModel.id, - validators: this.validators, - formGroup: this.parentForm - }); + this.setValidators.emit(this.validators); this.elementFormControl.valueChanges .pipe( startWith((this.elementModel as InputElement).value), pairwise(), takeUntil(this.ngUnsubscribe) ) - .subscribe(([prevValue, nextValue] : [string | number | boolean | undefined, string | number | boolean]) => { + .subscribe(([prevValue, nextValue]: [InputElementValue, InputElementValue]) => { if (nextValue != null) { // invalid input on number fields generates event with null TODO find a better solution this.formValueChanged.emit({ id: this.elementModel.id, values: [prevValue, nextValue] }); } @@ -65,7 +51,7 @@ export abstract class FormElementComponent extends ElementComponent implements O new FormControl({}); } - updateFormValue(newValue: string | number | boolean | null): void { + updateFormValue(newValue: InputElementValue): void { this.elementFormControl?.setValue(newValue, { emitEvent: false }); } diff --git a/projects/common/models/uI-element.ts b/projects/common/models/uI-element.ts index ea5231369d91dadde60ac290a52d921336d1f67d..89747ecd191a788576637f9b4eb41ad202f91207 100644 --- a/projects/common/models/uI-element.ts +++ b/projects/common/models/uI-element.ts @@ -6,8 +6,13 @@ export type UIElementType = 'text' | 'button' | 'text-field' | 'text-area' | 'ch | 'dropdown' | 'radio' | 'image' | 'audio' | 'video' | 'likert' | 'likert_row'; export type InputElementValue = string | number | boolean | null; +export interface ValueChangeElement { + id: string; + values: [InputElementValue, InputElementValue]; +} + export abstract class UIElement { - [index: string]: string | number | boolean | string[] | AnswerOption[] | LikertRow[] | null | ((...args: any) => any); + [index: string]: InputElementValue | string[] | AnswerOption[] | LikertRow[] | ((...args: any) => any); type!: UIElementType; id: string = 'id_placeholder'; @@ -35,7 +40,7 @@ export abstract class UIElement { // This can be overwritten by elements if they need to handle some property specifics. Likert does. setProperty(property: string, - value: string | number | boolean | string[] | AnswerOption[] | LikertRow[] | null): void { + value: InputElementValue | string[] | AnswerOption[] | LikertRow[]): void { this[property] = value; } } diff --git a/projects/editor/src/app/unit-view/page-view/canvas/canvas-element-overlay.ts b/projects/editor/src/app/unit-view/page-view/canvas/canvas-element-overlay.ts index 145f72401a092de848d1523b045d158ca22382a6..fc70ebff1aace1432b50f5b47a138a507afa949b 100644 --- a/projects/editor/src/app/unit-view/page-view/canvas/canvas-element-overlay.ts +++ b/projects/editor/src/app/unit-view/page-view/canvas/canvas-element-overlay.ts @@ -8,10 +8,9 @@ import { takeUntil } from 'rxjs/operators'; import { UnitService } from '../../../unit.service'; import * as ElementFactory from '../../../../../../common/util/element.factory'; import { FormElementComponent } from '../../../../../../common/form-element-component.directive'; -import { ValueChangeElement } from '../../../../../../common/form'; import { ElementComponent } from '../../../../../../common/element-component.directive'; import { SelectionService } from '../../../selection.service'; -import { InputElement, UIElement } from '../../../../../../common/models/uI-element'; +import { InputElement, UIElement, ValueChangeElement } from '../../../../../../common/models/uI-element'; @Directive() export abstract class CanvasElementOverlay implements OnInit, OnDestroy { diff --git a/projects/player/src/app/components/element/element.component.ts b/projects/player/src/app/components/element/element.component.ts index b22cbcd8ba5405721fa35dc2aa53c6361042a2c2..2556a170aa748fe473a9a7dbab52479670d2534a 100644 --- a/projects/player/src/app/components/element/element.component.ts +++ b/projects/player/src/app/components/element/element.component.ts @@ -2,18 +2,24 @@ import { Component, OnInit, Input, ComponentFactoryResolver, ViewChild, ViewContainerRef } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { + FormBuilder, FormControl, FormGroup, ValidatorFn +} from '@angular/forms'; import { takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; import * as ElementFactory from '../../../../../common/util/element.factory'; import { KeyboardService } from '../../services/keyboard.service'; import { TextFieldComponent } from '../../../../../common/element-components/text-field.component'; import { TextAreaComponent } from '../../../../../common/element-components/text-area.component'; -import { FormService } from '../../../../../common/form.service'; -import { ValueChangeElement } from '../../../../../common/form'; +import { FormService } from '../../services/form.service'; import { UnitStateService } from '../../services/unit-state.service'; import { MarkingService } from '../../services/marking.service'; -import { InputElementValue, UIElement } from '../../../../../common/models/uI-element'; +import { + InputElement, + InputElementValue, + UIElement, + ValueChangeElement +} from '../../../../../common/models/uI-element'; import { TextFieldElement } from '../../../../../common/models/text-field-element'; import { FormElementComponent } from '../../../../../common/form-element-component.directive'; @@ -66,13 +72,6 @@ export class ElementComponent implements OnInit { this.unitStateService.registerElement(elementComponent.elementModel.id, elementComponent.elementModel.value); - if (this.elementModel.type === 'likert') { - elementComponent.getChildElementValues() - .forEach((element: { id: string, value: InputElementValue }) => ( - this.unitStateService.registerElement(element.id, element.value) - )); - } - if (elementComponent.applySelection) { elementComponent.applySelection .pipe(takeUntil(this.ngUnsubscribe)) @@ -94,6 +93,36 @@ export class ElementComponent implements OnInit { elementComponent.parentForm = elementForm; this.registerFormGroup(elementForm); + this.formService.registerFormControl({ + id: this.elementModel.id, + formControl: new FormControl((this.elementModel as InputElement).value), + formGroup: elementForm + }); + + if (this.elementModel.type === 'likert') { + elementComponent.getChildElementValues() + .forEach((element: { id: string, value: InputElementValue }) => { + this.unitStateService.registerElement(element.id, element.value); + this.formService.registerFormControl({ + id: element.id, + formControl: new FormControl((element as InputElement).value), + formGroup: elementForm + }); + }); + } + + if (elementComponent.setValidators) { + elementComponent.setValidators + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((validators: ValidatorFn[]) => { + this.formService.setValidators({ + id: this.elementModel.id, + validators: validators, + formGroup: elementForm + }); + }); + } + elementComponent.formValueChanged .pipe(takeUntil(this.ngUnsubscribe)) .subscribe((changeElement: ValueChangeElement) => { diff --git a/projects/player/src/app/components/page/page.component.ts b/projects/player/src/app/components/page/page.component.ts index 43b820f80cb5f7c8f980f6716082a14ab515ed83..884707906b06666461c1261a74716f24f6afec86 100644 --- a/projects/player/src/app/components/page/page.component.ts +++ b/projects/player/src/app/components/page/page.component.ts @@ -2,7 +2,7 @@ import { Component, Input, OnInit, Output, EventEmitter } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; -import { FormService } from '../../../../../common/form.service'; +import { FormService } from '../../services/form.service'; import { UnitStateService } from '../../services/unit-state.service'; import { Page } from '../../../../../common/models/page'; diff --git a/projects/player/src/app/components/section/section.component.ts b/projects/player/src/app/components/section/section.component.ts index 4d6283c4bc325dc1df8eebf51af39e2204f5eac6..785c6d2f86dfaebc32c1fb7928ea33c21626f325 100644 --- a/projects/player/src/app/components/section/section.component.ts +++ b/projects/player/src/app/components/section/section.component.ts @@ -3,7 +3,7 @@ import { } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { DOCUMENT } from '@angular/common'; -import { FormService } from '../../../../../common/form.service'; +import { FormService } from '../../services/form.service'; import { Section } from '../../../../../common/models/section'; import { UnitStateService } from '../../services/unit-state.service'; diff --git a/projects/player/src/app/components/unit-state/unit-state.component.ts b/projects/player/src/app/components/unit-state/unit-state.component.ts index 86033293733337c05eaff8178570c2fa4aa638c1..d08e486c94c3087ec9afd9a906cd8350826fe257 100644 --- a/projects/player/src/app/components/unit-state/unit-state.component.ts +++ b/projects/player/src/app/components/unit-state/unit-state.component.ts @@ -8,14 +8,14 @@ import { import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; -import { FormService } from '../../../../../common/form.service'; +import { FormService } from '../../services/form.service'; import { VeronaSubscriptionService } from '../../services/verona-subscription.service'; import { VeronaPostService } from '../../services/verona-post.service'; import { MessageService } from '../../../../../common/message.service'; import { MetaDataService } from '../../services/meta-data.service'; import { FormControlElement, FormControlValidators, ChildFormGroup -} from '../../../../../common/form'; +} from '../../models/form'; import { PlayerConfig, Progress, UnitState, VopNavigationDeniedNotification } from '../../models/verona'; diff --git a/projects/common/form.ts b/projects/player/src/app/models/form.ts similarity index 76% rename from projects/common/form.ts rename to projects/player/src/app/models/form.ts index bde7833e7478abebebfca94b29e9d806a69ea409..4c467ef6223941fcb316b9891b8a14d1a298208f 100644 --- a/projects/common/form.ts +++ b/projects/player/src/app/models/form.ts @@ -1,10 +1,5 @@ import { FormControl, FormGroup, ValidatorFn } from '@angular/forms'; -export interface ValueChangeElement { - id: string; - values: [string | number | boolean | null | undefined, string | number | boolean]; -} - export interface FormControlElement { id: string; formControl: FormControl; diff --git a/projects/common/form.service.ts b/projects/player/src/app/services/form.service.ts similarity index 97% rename from projects/common/form.service.ts rename to projects/player/src/app/services/form.service.ts index 9a5043a49b88759a470ddbd37f166364653802bc..4980ad78f2bc82073b17bc656bfa99f78c090ed9 100644 --- a/projects/common/form.service.ts +++ b/projects/player/src/app/services/form.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { FormControlElement, FormControlValidators, ChildFormGroup -} from './form'; +} from '../models/form'; @Injectable({ providedIn: 'root' diff --git a/projects/player/src/app/services/unit-state.service.ts b/projects/player/src/app/services/unit-state.service.ts index 0e127aa13bb463f793a2a14604e25da55f546c5f..665a1feb8ddd8e518efc46ce05777aee3eb79fd8 100644 --- a/projects/player/src/app/services/unit-state.service.ts +++ b/projects/player/src/app/services/unit-state.service.ts @@ -6,8 +6,7 @@ import { UnitStateElementCodeStatus, UnitStateElementCodeStatusValue } from '../models/verona'; -import { ValueChangeElement } from '../../../../common/form'; -import { InputElementValue } from '../../../../common/models/uI-element'; +import { InputElementValue, ValueChangeElement } from '../../../../common/models/uI-element'; @Injectable({ providedIn: 'root'