From d41b63837d7fcd324d12e847af12dca588ef5055 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 30 Nov 2021 20:15:56 +0100 Subject: [PATCH] Refactoring/forms (#800) * Get rid of formbuilder * Temp * Cleanup forms. * More tests. * More improvements. * Cleanup. --- backend/i18n/frontend_en.json | 1 + backend/i18n/frontend_it.json | 1 + backend/i18n/frontend_nl.json | 1 + backend/i18n/frontend_zh.json | 1 + backend/i18n/source/frontend_en.json | 1 + .../pages/restore/restore-page.component.ts | 4 +- .../pages/users/user-page.component.ts | 4 +- .../administration/state/users.forms.ts | 54 +-- .../pages/asset-tag-dialog.component.ts | 4 +- .../content/editor/content-field.component.ts | 4 +- .../editor/field-copy-button.component.ts | 2 +- .../shared/forms/array-item.component.html | 4 + .../shared/forms/array-item.component.ts | 59 ++- .../shared/forms/field-editor.component.html | 2 +- .../shared/forms/iframe-editor.component.ts | 81 ++-- .../rules/pages/rule/rule-page.component.ts | 4 +- .../common/schema-edit-form.component.ts | 4 +- .../export/schema-export-form.component.ts | 4 +- .../schema/fields/field-wizard.component.ts | 6 +- .../pages/schema/fields/field.component.ts | 4 +- .../schema-preview-urls-form.component.ts | 4 +- .../schema-field-rules-form.component.ts | 4 +- .../scripts/schema-scripts-form.component.ts | 4 +- .../pages/schemas/schema-form.component.ts | 4 +- .../pages/schemas/schemas-page.component.ts | 5 +- .../asset-scripts-page.component.ts | 4 +- .../clients/client-add-form.component.ts | 4 +- .../contributor-add-form.component.ts | 4 +- .../import-contributors-dialog.component.ts | 4 +- .../languages/language-add-form.component.ts | 4 +- .../pages/languages/language.component.ts | 4 +- .../pages/more/more-page.component.ts | 4 +- .../pages/roles/role-add-form.component.ts | 4 +- .../settings/pages/roles/role.component.ts | 4 +- .../pages/settings/settings-page.component.ts | 4 +- .../workflows/workflow-add-form.component.ts | 4 +- .../forms/editable-title.component.html | 4 +- .../angular/forms/editable-title.component.ts | 25 +- .../angular/forms/extended-form-array.spec.ts | 128 +++++ ...e-form-array.ts => extended-form-array.ts} | 38 +- .../angular/forms/extended-form-group.spec.ts | 110 +++++ ...e-form-group.ts => extended-form-group.ts} | 44 +- .../framework/angular/forms/forms-helper.ts | 12 +- frontend/app/framework/angular/forms/model.ts | 4 +- .../angular/forms/templated-form-array.ts | 2 +- .../angular/forms/templated-form-group.ts | 2 +- .../forms/undefinable-form-array.spec.ts | 89 ---- .../forms/undefinable-form-group.spec.ts | 71 --- frontend/app/framework/declarations.ts | 8 +- .../shared/components/app-form.component.ts | 4 +- .../assets/asset-dialog.component.ts | 4 +- .../assets/asset-folder-dialog.component.ts | 4 +- .../components/comments/comments.component.ts | 4 +- .../forms/geolocation-editor.component.ts | 25 +- .../search/search-form.component.ts | 8 +- frontend/app/shared/state/apps.forms.ts | 114 ++--- .../app/shared/state/assets.forms.spec.ts | 3 +- frontend/app/shared/state/assets.forms.ts | 124 ++--- frontend/app/shared/state/backups.forms.ts | 32 +- frontend/app/shared/state/clients.forms.ts | 40 +- frontend/app/shared/state/comments.form.ts | 14 +- .../shared/state/contents.forms-helpers.ts | 11 +- .../app/shared/state/contents.forms.spec.ts | 126 ++++- frontend/app/shared/state/contents.forms.ts | 108 +++-- .../app/shared/state/contributors.forms.ts | 54 ++- frontend/app/shared/state/languages.forms.ts | 48 +- frontend/app/shared/state/roles.forms.ts | 48 +- frontend/app/shared/state/rules.forms.ts | 45 +- frontend/app/shared/state/schemas.forms.ts | 454 +++++++++--------- frontend/app/shared/state/workflows.forms.ts | 24 +- 70 files changed, 1161 insertions(+), 980 deletions(-) create mode 100644 frontend/app/framework/angular/forms/extended-form-array.spec.ts rename frontend/app/framework/angular/forms/{undefinable-form-array.ts => extended-form-array.ts} (74%) create mode 100644 frontend/app/framework/angular/forms/extended-form-group.spec.ts rename frontend/app/framework/angular/forms/{undefinable-form-group.ts => extended-form-group.ts} (71%) delete mode 100644 frontend/app/framework/angular/forms/undefinable-form-array.spec.ts delete mode 100644 frontend/app/framework/angular/forms/undefinable-form-group.spec.ts diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index b7a297806..fa0f27499 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -408,6 +408,7 @@ "contents.changeStatusTo": "Change content item(s) to {action}", "contents.changeStatusToImmediately": "Set to {action} immediately.", "contents.changeStatusToLater": "Set to {action} at a later point date and time.", + "contents.componentInvalid": "Invalid component, schema has been deleted.", "contents.componentNoSchema": "Add at least one schema to set component.", "contents.componentsNoSchema": "Add at least one schema to add components.", "contents.contentNotValid": "Content element not valid, please check the field with the red bar on the left in all languages (if localizable).", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index c2bae2104..ccad631a0 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -408,6 +408,7 @@ "contents.changeStatusTo": "Cambia gli elementi del contenuto in {action}", "contents.changeStatusToImmediately": "Imposta {action} immediatamente.", "contents.changeStatusToLater": "Imposta {action} ad una data e ora successiva.", + "contents.componentInvalid": "Invalid component, schema has been deleted.", "contents.componentNoSchema": "Add at least one schema to set component.", "contents.componentsNoSchema": "Add at least one schema to add components.", "contents.contentNotValid": "Un elemento del contenuto non è valido, verifica il campo con la barra rossa per tutte le lingue impostate (se presenti).", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index c5566c3c6..7f3ad2615 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -408,6 +408,7 @@ "contents.changeStatusTo": "Verander inhoud item(s) in {action}", "contents.changeStatusToImmediately": "Zet onmiddellijk op {action}.", "contents.changeStatusToLater": "Zet op {action} op een latere datum en tijd.", + "contents.componentInvalid": "Invalid component, schema has been deleted.", "contents.componentNoSchema": "Add at least one schema to set component.", "contents.componentsNoSchema": "Add at least one schema to add components.", "contents.contentNotValid": "Inhoudselement niet geldig, controleer het veld met de rode balk aan de linkerkant in alle talen (indien lokaliseerbaar).", diff --git a/backend/i18n/frontend_zh.json b/backend/i18n/frontend_zh.json index fc687a926..a7ed7f409 100644 --- a/backend/i18n/frontend_zh.json +++ b/backend/i18n/frontend_zh.json @@ -408,6 +408,7 @@ "contents.changeStatusTo": "将内容项更改为 {action}", "contents.changeStatusToImmediately": "立即设置为 {action}。", "contents.changeStatusToLater": "在稍后的日期和时间设置为 {action}。", + "contents.componentInvalid": "Invalid component, schema has been deleted.", "contents.componentNoSchema": "添加至少一个Schemas来设置组件。", "contents.componentsNoSchema": "添加至少一个Schemas来添加组件。", "contents.contentNotValid": "内容元素无效,请用所有语言(如果可本地化)检查左侧带有红色条的字段。", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index b7a297806..fa0f27499 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -408,6 +408,7 @@ "contents.changeStatusTo": "Change content item(s) to {action}", "contents.changeStatusToImmediately": "Set to {action} immediately.", "contents.changeStatusToLater": "Set to {action} at a later point date and time.", + "contents.componentInvalid": "Invalid component, schema has been deleted.", "contents.componentNoSchema": "Add at least one schema to set component.", "contents.componentsNoSchema": "Add at least one schema to add components.", "contents.contentNotValid": "Content element not valid, please check the field with the red bar on the left in all languages (if localizable).", diff --git a/frontend/app/features/administration/pages/restore/restore-page.component.ts b/frontend/app/features/administration/pages/restore/restore-page.component.ts index d3900f351..8347f17b1 100644 --- a/frontend/app/features/administration/pages/restore/restore-page.component.ts +++ b/frontend/app/features/administration/pages/restore/restore-page.component.ts @@ -6,7 +6,6 @@ */ import { Component } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AuthService, BackupsService, DialogService, RestoreForm, switchSafe } from '@app/shared'; import { timer } from 'rxjs'; @@ -16,7 +15,7 @@ import { timer } from 'rxjs'; templateUrl: './restore-page.component.html', }) export class RestorePageComponent { - public restoreForm = new RestoreForm(this.formBuilder); + public restoreForm = new RestoreForm(); public restoreJob = timer(0, 2000).pipe(switchSafe(() => this.backupsService.getRestore())); @@ -25,7 +24,6 @@ export class RestorePageComponent { public readonly authState: AuthService, private readonly backupsService: BackupsService, private readonly dialogs: DialogService, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/features/administration/pages/users/user-page.component.ts b/frontend/app/features/administration/pages/users/user-page.component.ts index e54c5f101..d8781c4e9 100644 --- a/frontend/app/features/administration/pages/users/user-page.component.ts +++ b/frontend/app/features/administration/pages/users/user-page.component.ts @@ -6,7 +6,6 @@ */ import { Component, OnInit } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { CreateUserDto, UserDto, UserForm, UsersState } from '@app/features/administration/internal'; import { ResourceOwner } from '@app/shared'; @@ -20,11 +19,10 @@ export class UserPageComponent extends ResourceOwner implements OnInit { public isEditable = false; public user?: UserDto | null; - public userForm = new UserForm(this.formBuilder); + public userForm = new UserForm(); constructor( public readonly usersState: UsersState, - private readonly formBuilder: FormBuilder, private readonly route: ActivatedRoute, private readonly router: Router, ) { diff --git a/frontend/app/features/administration/state/users.forms.ts b/frontend/app/features/administration/state/users.forms.ts index b104c7d0d..524e7791d 100644 --- a/frontend/app/features/administration/state/users.forms.ts +++ b/frontend/app/features/administration/state/users.forms.ts @@ -5,39 +5,31 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, ValidatorsEx } from '@app/shared'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, ExtendedFormGroup, ValidatorsEx } from '@app/shared'; import { UpdateUserDto, UserDto } from './../services/users.service'; -export class UserForm extends Form { - constructor( - formBuilder: FormBuilder, - ) { - super(formBuilder.group({ - email: ['', - [ - Validators.email, - Validators.required, - Validators.maxLength(100), - ], - ], - displayName: ['', - [ - Validators.required, - Validators.maxLength(100), - ], - ], - password: ['', - [ - Validators.required, - ], - ], - passwordConfirm: ['', - [ - ValidatorsEx.match('password', 'i18n:users.passwordConfirmValidationMessage'), - ], - ], - permissions: [''], +export class UserForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + email: new FormControl('', [ + Validators.email, + Validators.required, + Validators.maxLength(100), + ]), + displayName: new FormControl('', [ + Validators.required, + Validators.maxLength(100), + ]), + password: new FormControl('', + Validators.required, + ), + passwordConfirm: new FormControl('', + ValidatorsEx.match('password', 'i18n:users.passwordConfirmValidationMessage'), + ), + permissions: new FormControl('', + Validators.nullValidator, + ), })); } diff --git a/frontend/app/features/assets/pages/asset-tag-dialog.component.ts b/frontend/app/features/assets/pages/asset-tag-dialog.component.ts index 41390c63b..8527bb9a6 100644 --- a/frontend/app/features/assets/pages/asset-tag-dialog.component.ts +++ b/frontend/app/features/assets/pages/asset-tag-dialog.component.ts @@ -6,7 +6,6 @@ */ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AssetsState, RenameAssetTagForm } from '@app/shared/internal'; @Component({ @@ -21,11 +20,10 @@ export class AssetTagDialogComponent implements OnInit { @Input() public tagName: string; - public editForm = new RenameAssetTagForm(this.formBuilder); + public editForm = new RenameAssetTagForm(); constructor( private readonly assetsState: AssetsState, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/features/content/pages/content/editor/content-field.component.ts b/frontend/app/features/content/pages/content/editor/content-field.component.ts index 6abda7516..0d826bdf6 100644 --- a/frontend/app/features/content/pages/content/editor/content-field.component.ts +++ b/frontend/app/features/content/pages/content/editor/content-field.component.ts @@ -91,12 +91,12 @@ export class ContentFieldComponent implements OnChanges { public copy() { if (this.formModel && this.formModelCompare) { if (this.showAllControls) { - this.formModel.setValue(this.formModelCompare.getRawValue()); + this.formModel.setValue(this.formModelCompare.form.value); } else { const target = this.formModel.get(this.language.iso2Code); if (target) { - target.setValue(this.formModelCompare.get(this.language.iso2Code)?.getRawValue()); + target.setValue(this.formModelCompare.get(this.language.iso2Code)?.form.value); } } } diff --git a/frontend/app/features/content/pages/content/editor/field-copy-button.component.ts b/frontend/app/features/content/pages/content/editor/field-copy-button.component.ts index 74064dfb4..d0f99d505 100644 --- a/frontend/app/features/content/pages/content/editor/field-copy-button.component.ts +++ b/frontend/app/features/content/pages/content/editor/field-copy-button.component.ts @@ -49,7 +49,7 @@ export class FieldCopyButtonComponent implements OnChanges { public copy() { if (this.copySource && this.copyTargets?.length > 0) { - const source = this.formModel.get(this.copySource).getRawValue(); + const source = this.formModel.get(this.copySource).form.value; for (const target of this.copyTargets) { if (target !== this.copySource) { diff --git a/frontend/app/features/content/shared/forms/array-item.component.html b/frontend/app/features/content/shared/forms/array-item.component.html index d966b8d87..2c6fb0769 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.html +++ b/frontend/app/features/content/shared/forms/array-item.component.html @@ -54,5 +54,9 @@ [languages]="languages"> + + + {{ 'contents.componentInvalid' | sqxTranslate }} + \ No newline at end of file diff --git a/frontend/app/features/content/shared/forms/array-item.component.ts b/frontend/app/features/content/shared/forms/array-item.component.ts index 6384ef563..da76945e1 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.ts +++ b/frontend/app/features/content/shared/forms/array-item.component.ts @@ -7,7 +7,8 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnInit, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core'; import { AppLanguageDto, ComponentForm, EditContentForm, FieldDto, FieldFormatter, FieldSection, invalid$, ObjectFormBase, RootFieldDto, StatefulComponent, Types, valueProjection$ } from '@app/shared'; -import { Observable } from 'rxjs'; +import { Observable, of } from 'rxjs'; +import { map } from 'rxjs/operators'; import { ComponentSectionComponent } from './component-section.component'; interface State { @@ -69,6 +70,7 @@ export class ArrayItemComponent extends StatefulComponent implements OnCh public isCollapsed = false; public isInvalid: Observable; + public isInvalidComponent: Observable; public title: Observable; @@ -87,32 +89,14 @@ export class ArrayItemComponent extends StatefulComponent implements OnCh if (changes['formModel']) { this.isInvalid = invalid$(this.formModel.form); - this.title = valueProjection$(this.formModel.form, x => this.getTitle(x)); - } - } - - private getTitle(value: any) { - const values: string[] = []; - - if (Types.is(this.formModel, ComponentForm) && this.formModel.schema) { - values.push(this.formModel.schema.displayName); - } - - if (Types.is(this.formModel.field, RootFieldDto)) { - for (const field of this.formModel.field.nested) { - const fieldValue = value[field.name]; - - if (fieldValue) { - const formatted = FieldFormatter.format(field, fieldValue); - - if (formatted) { - values.push(formatted); - } - } + if (Types.is(this.formModel, ComponentForm)) { + this.isInvalidComponent = this.formModel.schemaChanges.pipe(map(x => !x)); + } else { + this.isInvalidComponent = of(false); } - } - return values.join(', '); + this.title = valueProjection$(this.formModel.form, () => getTitle(this.formModel)); + } } public collapse() { @@ -149,3 +133,28 @@ export class ArrayItemComponent extends StatefulComponent implements OnCh return section.separator?.fieldId; } } + +function getTitle(formModel: ObjectFormBase) { + const value = formModel.form.value; + const values: string[] = []; + + if (Types.is(formModel, ComponentForm) && formModel.schema) { + values.push(formModel.schema.displayName); + } + + if (Types.is(formModel.field, RootFieldDto)) { + for (const field of formModel.field.nested) { + const fieldValue = value[field.name]; + + if (fieldValue) { + const formatted = FieldFormatter.format(field, fieldValue); + + if (formatted) { + values.push(formatted); + } + } + } + } + + return values.join(', '); +} diff --git a/frontend/app/features/content/shared/forms/field-editor.component.html b/frontend/app/features/content/shared/forms/field-editor.component.html index 967307480..b1cc2d5c2 100644 --- a/frontend/app/features/content/shared/forms/field-editor.component.html +++ b/frontend/app/features/content/shared/forms/field-editor.component.html @@ -11,7 +11,7 @@ diff --git a/frontend/app/features/content/shared/forms/iframe-editor.component.ts b/frontend/app/features/content/shared/forms/iframe-editor.component.ts index bf283fe37..316d2bf1c 100644 --- a/frontend/app/features/content/shared/forms/iframe-editor.component.ts +++ b/frontend/app/features/content/shared/forms/iframe-editor.component.ts @@ -5,33 +5,27 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, HostListener, Input, OnChanges, OnDestroy, Renderer2, SimpleChanges, ViewChild } from '@angular/core'; -import { NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, Renderer2, SimpleChanges, ViewChild } from '@angular/core'; +import { AbstractControl } from '@angular/forms'; import { Router } from '@angular/router'; -import { DialogModel, DialogService, StatefulControlComponent, Types } from '@app/framework'; +import { DialogModel, DialogService, disabled$, StatefulComponent, Types, value$ } from '@app/framework'; import { AppsState, AssetDto, computeEditorUrl } from '@app/shared'; -export const SQX_IFRAME_EDITOR_CONTROL_VALUE_ACCESSOR: any = { - provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => IFrameEditorComponent), multi: true, -}; - interface State { // True, when the editor is shown as fullscreen. isFullscreen: boolean; } @Component({ - selector: 'sqx-iframe-editor[context][formValue]', + selector: 'sqx-iframe-editor[context][formValue][formControlBinding]', styleUrls: ['./iframe-editor.component.scss'], templateUrl: './iframe-editor.component.html', - providers: [ - SQX_IFRAME_EDITOR_CONTROL_VALUE_ACCESSOR, - ], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class IFrameEditorComponent extends StatefulControlComponent implements OnChanges, OnDestroy { +export class IFrameEditorComponent extends StatefulComponent implements OnChanges, OnDestroy { private value: any; private isInitialized = false; + private isDisabled = false; private assetsCorrelationId: any; @ViewChild('iframe', { static: false }) @@ -55,9 +49,12 @@ export class IFrameEditorComponent extends StatefulControlComponent @Input() public language?: string | null; + @Input() + public formControlBinding: AbstractControl; + @Input() public set disabled(value: boolean | undefined | null) { - this.setDisabledState(value === true); + this.updatedisabled(value === true); } @Input() @@ -81,21 +78,39 @@ export class IFrameEditorComponent extends StatefulControlComponent } public ngOnDestroy() { + super.ngOnDestroy(); + this.toggleFullscreen(false); } public ngOnChanges(changes: SimpleChanges) { - if (this.iframe?.nativeElement) { - if (changes['formValue']) { - this.sendFormValue(); - } + if (changes['formValue']) { + this.sendFormValue(); + } - if (changes['language']) { - this.sendLanguage(); - } + if (changes['language']) { + this.sendLanguage(); + } - if (changes['formIndex']) { - this.sendMoved(); + if (changes['formIndex']) { + this.sendMoved(); + } + + if (changes['formControlBinding']) { + this.unsubscribeAll(); + + const control = this.formControlBinding; + + if (control) { + this.own(value$(control) + .subscribe(value => { + this.updateValue(value); + })); + + this.own(disabled$(control) + .subscribe(isDisabled => { + this.updatedisabled(isDisabled); + })); } } } @@ -135,10 +150,10 @@ export class IFrameEditorComponent extends StatefulControlComponent if (!Types.equals(this.value, value)) { this.value = value; - this.callChange(value); + this.formControlBinding?.reset(value); } } else if (type === 'touched') { - this.callTouched(); + this.formControlBinding?.markAsTouched(); } else if (type === 'notifyInfo') { const { text } = event.data; @@ -182,14 +197,20 @@ export class IFrameEditorComponent extends StatefulControlComponent this.assetsDialog.hide(); } - public writeValue(obj: any) { - this.value = obj; + public updateValue(obj: any) { + if (!Types.equals(obj, this.value)) { + this.value = obj; - this.sendValue(); + this.sendValue(); + } } - public onDisabled() { - this.sendDisabled(); + public updatedisabled(isDisabled: boolean) { + if (isDisabled !== this.isDisabled) { + this.isDisabled === isDisabled; + + this.sendDisabled(); + } } public reset() { @@ -209,7 +230,7 @@ export class IFrameEditorComponent extends StatefulControlComponent } private sendDisabled() { - this.sendMessage('disabled', { isDisabled: this.snapshot.isDisabled }); + this.sendMessage('disabled', { isDisabled: this.isDisabled }); } private sendFormValue() { diff --git a/frontend/app/features/rules/pages/rule/rule-page.component.ts b/frontend/app/features/rules/pages/rule/rule-page.component.ts index f76022b6c..78ddd8157 100644 --- a/frontend/app/features/rules/pages/rule/rule-page.component.ts +++ b/frontend/app/features/rules/pages/rule/rule-page.component.ts @@ -6,7 +6,6 @@ */ import { Component, OnInit } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { ActionForm, ALL_TRIGGERS, ResourceOwner, RuleDto, RuleElementDto, RulesService, RulesState, SchemasState, TriggerForm } from '@app/shared'; @@ -45,7 +44,6 @@ export class RulePageComponent extends ResourceOwner implements OnInit { public readonly rulesState: RulesState, public readonly rulesService: RulesService, public readonly schemasState: SchemasState, - private readonly formBuilder: FormBuilder, private readonly route: ActivatedRoute, private readonly router: Router, ) { @@ -97,7 +95,7 @@ export class RulePageComponent extends ResourceOwner implements OnInit { } public selectTrigger(type: string, values = {}) { - const form = new TriggerForm(this.formBuilder, type); + const form = new TriggerForm(type); form.setEnabled(this.isEditable); form.load(values); diff --git a/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts b/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts index bea1f4b62..88d7f8198 100644 --- a/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/common/schema-edit-form.component.ts @@ -6,7 +6,6 @@ */ import { Component, Input, OnChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { EditSchemaForm, SchemaDto, SchemasState } from '@app/shared'; @Component({ @@ -18,12 +17,11 @@ export class SchemaEditFormComponent implements OnChanges { @Input() public schema: SchemaDto; - public fieldForm = new EditSchemaForm(this.formBuilder); + public fieldForm = new EditSchemaForm(); public isEditable?: boolean | null; constructor( - private readonly formBuilder: FormBuilder, private readonly schemasState: SchemasState, ) { } diff --git a/frontend/app/features/schemas/pages/schema/export/schema-export-form.component.ts b/frontend/app/features/schemas/pages/schema/export/schema-export-form.component.ts index 9a33af692..f6c5e7a32 100644 --- a/frontend/app/features/schemas/pages/schema/export/schema-export-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/export/schema-export-form.component.ts @@ -6,7 +6,6 @@ */ import { Component, Input, OnChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { SchemaDto, SchemasState, SynchronizeSchemaForm } from '@app/shared'; @Component({ @@ -18,12 +17,11 @@ export class SchemaExportFormComponent implements OnChanges { @Input() public schema: SchemaDto; - public synchronizeForm = new SynchronizeSchemaForm(this.formBuilder); + public synchronizeForm = new SynchronizeSchemaForm(); public isEditable = false; constructor( - private readonly formBuilder: FormBuilder, private readonly schemasState: SchemasState, ) { } diff --git a/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts b/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts index 8d3d8d837..58ab32c97 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/field-wizard.component.ts @@ -6,7 +6,6 @@ */ import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AddFieldForm, AppSettingsDto, createProperties, EditFieldForm, FieldDto, fieldTypes, LanguagesState, RootFieldDto, SchemaDto, SchemasState, Types } from '@app/shared'; const DEFAULT_FIELD = { name: '', partitioning: 'invariant', properties: createProperties('String') }; @@ -39,12 +38,11 @@ export class FieldWizardComponent implements OnInit { public fieldTypes = fieldTypes; public field: FieldDto; - public addFieldForm = new AddFieldForm(this.formBuilder); + public addFieldForm = new AddFieldForm(); public editForm?: EditFieldForm; constructor( - private readonly formBuilder: FormBuilder, private readonly schemasState: SchemasState, public readonly languagesState: LanguagesState, ) { @@ -76,7 +74,7 @@ export class FieldWizardComponent implements OnInit { this.nameInput.nativeElement.focus(); } } else if (edit) { - this.editForm = new EditFieldForm(this.formBuilder, this.field.properties); + this.editForm = new EditFieldForm(this.field.properties); this.editForm.load(this.field.properties); } else { this.emitComplete(); diff --git a/frontend/app/features/schemas/pages/schema/fields/field.component.ts b/frontend/app/features/schemas/pages/schema/fields/field.component.ts index 1c78f079c..68f50515c 100644 --- a/frontend/app/features/schemas/pages/schema/fields/field.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/field.component.ts @@ -7,7 +7,6 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AppSettingsDto, createProperties, DialogModel, EditFieldForm, fadeAnimation, LanguageDto, ModalModel, NestedFieldDto, RootFieldDto, SchemaDto, SchemasState, sorted } from '@app/shared'; @Component({ @@ -50,7 +49,6 @@ export class FieldComponent implements OnChanges { public addFieldDialog = new DialogModel(); constructor( - private readonly formBuilder: FormBuilder, private readonly schemasState: SchemasState, ) { this.trackByFieldFn = this.trackByField.bind(this); @@ -60,7 +58,7 @@ export class FieldComponent implements OnChanges { if (changes['field']) { this.isEditable = this.field.canUpdate; - this.editForm = new EditFieldForm(this.formBuilder, this.field.properties); + this.editForm = new EditFieldForm(this.field.properties); this.editForm.load(this.field.properties); } } diff --git a/frontend/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts b/frontend/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts index f4ac8252d..9d685fe43 100644 --- a/frontend/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts @@ -6,7 +6,6 @@ */ import { Component, Input, OnChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { ConfigurePreviewUrlsForm, SchemaDto, SchemasState } from '@app/shared'; @Component({ @@ -18,12 +17,11 @@ export class SchemaPreviewUrlsFormComponent implements OnChanges { @Input() public schema: SchemaDto; - public editForm = new ConfigurePreviewUrlsForm(this.formBuilder); + public editForm = new ConfigurePreviewUrlsForm(); public isEditable = false; constructor( - private readonly formBuilder: FormBuilder, private readonly schemasState: SchemasState, ) { } diff --git a/frontend/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.ts b/frontend/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.ts index 93b2425b9..18273a406 100644 --- a/frontend/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/rules/schema-field-rules-form.component.ts @@ -6,7 +6,6 @@ */ import { Component, Input, OnChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { ConfigureFieldRulesForm, FIELD_RULE_ACTIONS, SchemaDto, SchemasState } from '@app/shared'; @Component({ @@ -18,7 +17,7 @@ export class SchemaFieldRulesFormComponent implements OnChanges { @Input() public schema: SchemaDto; - public editForm = new ConfigureFieldRulesForm(this.formBuilder); + public editForm = new ConfigureFieldRulesForm(); public fieldNames: ReadonlyArray; public fieldActions = FIELD_RULE_ACTIONS; @@ -26,7 +25,6 @@ export class SchemaFieldRulesFormComponent implements OnChanges { public isEditable = false; constructor( - private readonly formBuilder: FormBuilder, private readonly schemasState: SchemasState, ) { } diff --git a/frontend/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.ts b/frontend/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.ts index 8a4baf44c..fb8772eac 100644 --- a/frontend/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.ts +++ b/frontend/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.ts @@ -6,7 +6,6 @@ */ import { Component, Input, OnChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AppsState, EditSchemaScriptsForm, SchemaCompletions, SchemaDto, SchemasService, SchemasState } from '@app/shared'; import { EMPTY, Observable } from 'rxjs'; @@ -22,13 +21,12 @@ export class SchemaScriptsFormComponent implements OnChanges { public schemaScript = 'query'; public schemaCompletions: Observable = EMPTY; - public editForm = new EditSchemaScriptsForm(this.formBuilder); + public editForm = new EditSchemaScriptsForm(); public isEditable = false; constructor( private readonly appsState: AppsState, - private readonly formBuilder: FormBuilder, private readonly schemasState: SchemasState, private readonly schemasService: SchemasService, ) { diff --git a/frontend/app/features/schemas/pages/schemas/schema-form.component.ts b/frontend/app/features/schemas/pages/schemas/schema-form.component.ts index cfc3eaae8..a902543c5 100644 --- a/frontend/app/features/schemas/pages/schemas/schema-form.component.ts +++ b/frontend/app/features/schemas/pages/schemas/schema-form.component.ts @@ -6,7 +6,6 @@ */ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { ApiUrlConfig, AppsState, CreateSchemaForm, SchemaDto, SchemasState } from '@app/shared'; @Component({ @@ -24,7 +23,7 @@ export class SchemaFormComponent implements OnInit { @Input() public import: any; - public createForm = new CreateSchemaForm(this.formBuilder); + public createForm = new CreateSchemaForm(); public showImport = false; @@ -32,7 +31,6 @@ export class SchemaFormComponent implements OnInit { public readonly apiUrl: ApiUrlConfig, public readonly appsState: AppsState, public readonly schemasState: SchemasState, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/features/schemas/pages/schemas/schemas-page.component.ts b/frontend/app/features/schemas/pages/schemas/schemas-page.component.ts index 7e6912f12..f25c6d319 100644 --- a/frontend/app/features/schemas/pages/schemas/schemas-page.component.ts +++ b/frontend/app/features/schemas/pages/schemas/schemas-page.component.ts @@ -6,7 +6,7 @@ */ import { Component, OnInit } from '@angular/core'; -import { FormBuilder, FormControl } from '@angular/forms'; +import { FormControl } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { CreateCategoryForm, DialogModel, getCategoryTree, MessageBus, ResourceOwner, SchemaCategory, SchemaDto, SchemasState, value$ } from '@app/shared'; import { combineLatest } from 'rxjs'; @@ -20,7 +20,7 @@ import { SchemaCloning } from './../messages'; }) export class SchemasPageComponent extends ResourceOwner implements OnInit { public addSchemaDialog = new DialogModel(); - public addCategoryForm = new CreateCategoryForm(this.formBuilder); + public addCategoryForm = new CreateCategoryForm(); public schemasFilter = new FormControl(); @@ -37,7 +37,6 @@ export class SchemasPageComponent extends ResourceOwner implements OnInit { constructor( public readonly schemasState: SchemasState, - private readonly formBuilder: FormBuilder, private readonly messageBus: MessageBus, private readonly route: ActivatedRoute, private readonly router: Router, diff --git a/frontend/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts b/frontend/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts index 7899ba1df..b2804b31a 100644 --- a/frontend/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts +++ b/frontend/app/features/settings/pages/asset-scripts/asset-scripts-page.component.ts @@ -6,7 +6,6 @@ */ import { Component, OnInit } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AppsState, AssetCompletions, AssetScriptsState, AssetsService, EditAssetScriptsForm, ResourceOwner } from '@app/shared'; import { EMPTY, Observable } from 'rxjs'; @@ -19,13 +18,12 @@ export class AssetScriptsPageComponent extends ResourceOwner implements OnInit { public assetScript = 'create'; public assetCompletions: Observable = EMPTY; - public editForm = new EditAssetScriptsForm(this.formBuilder); + public editForm = new EditAssetScriptsForm(); public isEditable = false; constructor( private readonly appsState: AppsState, - private readonly formBuilder: FormBuilder, private readonly assetScriptsState: AssetScriptsState, private readonly assetsService: AssetsService, ) { diff --git a/frontend/app/features/settings/pages/clients/client-add-form.component.ts b/frontend/app/features/settings/pages/clients/client-add-form.component.ts index 63602cb51..2283b9971 100644 --- a/frontend/app/features/settings/pages/clients/client-add-form.component.ts +++ b/frontend/app/features/settings/pages/clients/client-add-form.component.ts @@ -6,7 +6,6 @@ */ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AddClientForm, ClientsState } from '@app/shared'; @Component({ @@ -16,11 +15,10 @@ import { AddClientForm, ClientsState } from '@app/shared'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class ClientAddFormComponent { - public addClientForm = new AddClientForm(this.formBuilder); + public addClientForm = new AddClientForm(); constructor( private readonly clientsState: ClientsState, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/features/settings/pages/contributors/contributor-add-form.component.ts b/frontend/app/features/settings/pages/contributors/contributor-add-form.component.ts index 23ea48b20..92b4df2d6 100644 --- a/frontend/app/features/settings/pages/contributors/contributor-add-form.component.ts +++ b/frontend/app/features/settings/pages/contributors/contributor-add-form.component.ts @@ -6,7 +6,6 @@ */ import { Component, Injectable, Input, OnChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AssignContributorForm, AutocompleteSource, ContributorsState, DialogModel, DialogService, RoleDto, UsersService } from '@app/shared'; import { Observable } from 'rxjs'; import { withLatestFrom } from 'rxjs/operators'; @@ -48,7 +47,7 @@ export class ContributorAddFormComponent implements OnChanges { @Input() public roles: ReadonlyArray; - public assignContributorForm = new AssignContributorForm(this.formBuilder); + public assignContributorForm = new AssignContributorForm(); public importDialog = new DialogModel(); @@ -56,7 +55,6 @@ export class ContributorAddFormComponent implements OnChanges { public readonly contributorsState: ContributorsState, public readonly usersDataSource: UsersDataSource, private readonly dialogs: DialogService, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/features/settings/pages/contributors/import-contributors-dialog.component.ts b/frontend/app/features/settings/pages/contributors/import-contributors-dialog.component.ts index bf55a3531..99ed32eef 100644 --- a/frontend/app/features/settings/pages/contributors/import-contributors-dialog.component.ts +++ b/frontend/app/features/settings/pages/contributors/import-contributors-dialog.component.ts @@ -6,7 +6,6 @@ */ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { ContributorsState, ErrorDto, ImportContributorsForm, RoleDto } from '@app/shared'; import { EMPTY, of } from 'rxjs'; import { catchError, mergeMap, tap } from 'rxjs/operators'; @@ -30,12 +29,11 @@ export class ImportContributorsDialogComponent { @Input() public roles: ReadonlyArray; - public importForm = new ImportContributorsForm(this.formBuilder); + public importForm = new ImportContributorsForm(); public importStatus: ReadonlyArray = []; public importStage: 'Start' | 'Change' | 'Wait' = 'Start'; constructor( - private readonly formBuilder: FormBuilder, private readonly contributorsState: ContributorsState, ) { } diff --git a/frontend/app/features/settings/pages/languages/language-add-form.component.ts b/frontend/app/features/settings/pages/languages/language-add-form.component.ts index 61fc90f41..db3e08854 100644 --- a/frontend/app/features/settings/pages/languages/language-add-form.component.ts +++ b/frontend/app/features/settings/pages/languages/language-add-form.component.ts @@ -6,7 +6,6 @@ */ import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AddLanguageForm, LanguageDto, LanguagesState } from '@app/shared'; @Component({ @@ -19,11 +18,10 @@ export class LanguageAddFormComponent implements OnChanges { @Input() public newLanguages: ReadonlyArray; - public addLanguageForm = new AddLanguageForm(this.formBuilder); + public addLanguageForm = new AddLanguageForm(); constructor( private readonly languagesState: LanguagesState, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/features/settings/pages/languages/language.component.ts b/frontend/app/features/settings/pages/languages/language.component.ts index f3708d5f3..f86864652 100644 --- a/frontend/app/features/settings/pages/languages/language.component.ts +++ b/frontend/app/features/settings/pages/languages/language.component.ts @@ -7,7 +7,6 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { Component, Input, OnChanges } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AppLanguageDto, EditLanguageForm, LanguageDto, LanguagesState, sorted } from '@app/shared'; @Component({ @@ -30,10 +29,9 @@ export class LanguageComponent implements OnChanges { public isEditing?: boolean | null; public isEditable = false; - public editForm = new EditLanguageForm(this.formBuilder); + public editForm = new EditLanguageForm(); constructor( - private readonly formBuilder: FormBuilder, private readonly languagesState: LanguagesState, ) { } diff --git a/frontend/app/features/settings/pages/more/more-page.component.ts b/frontend/app/features/settings/pages/more/more-page.component.ts index 2861af820..4af53a0e2 100644 --- a/frontend/app/features/settings/pages/more/more-page.component.ts +++ b/frontend/app/features/settings/pages/more/more-page.component.ts @@ -6,7 +6,6 @@ */ import { Component, OnInit } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { AppDto, AppsState, defined, ResourceOwner, Types, UpdateAppForm } from '@app/shared'; @@ -25,11 +24,10 @@ export class MorePageComponent extends ResourceOwner implements OnInit { public uploading = false; public uploadProgress = 10; - public updateForm = new UpdateAppForm(this.formBuilder); + public updateForm = new UpdateAppForm(); constructor( private readonly appsState: AppsState, - private readonly formBuilder: FormBuilder, private readonly router: Router, ) { super(); diff --git a/frontend/app/features/settings/pages/roles/role-add-form.component.ts b/frontend/app/features/settings/pages/roles/role-add-form.component.ts index 4f218803e..4de8d531d 100644 --- a/frontend/app/features/settings/pages/roles/role-add-form.component.ts +++ b/frontend/app/features/settings/pages/roles/role-add-form.component.ts @@ -6,7 +6,6 @@ */ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AddRoleForm, RolesState } from '@app/shared'; @Component({ @@ -16,11 +15,10 @@ import { AddRoleForm, RolesState } from '@app/shared'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class RoleAddFormComponent { - public addRoleForm = new AddRoleForm(this.formBuilder); + public addRoleForm = new AddRoleForm(); constructor( private readonly rolesState: RolesState, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/features/settings/pages/roles/role.component.ts b/frontend/app/features/settings/pages/roles/role.component.ts index 46306daa7..028ece35d 100644 --- a/frontend/app/features/settings/pages/roles/role.component.ts +++ b/frontend/app/features/settings/pages/roles/role.component.ts @@ -6,7 +6,6 @@ */ import { ChangeDetectionStrategy, Component, Input, OnChanges, SimpleChanges, ViewChild } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AddPermissionForm, AutocompleteComponent, AutocompleteSource, EditRoleForm, RoleDto, RolesState, SchemaDto, Settings } from '@app/shared'; const DESCRIPTIONS = { @@ -64,12 +63,11 @@ export class RoleComponent implements OnChanges { public isEditing = false; public isEditable = false; - public addPermissionForm = new AddPermissionForm(this.formBuilder); + public addPermissionForm = new AddPermissionForm(); public editForm = new EditRoleForm(); constructor( - private readonly formBuilder: FormBuilder, private readonly rolesState: RolesState, ) { } diff --git a/frontend/app/features/settings/pages/settings/settings-page.component.ts b/frontend/app/features/settings/pages/settings/settings-page.component.ts index 8bd2ae970..08d8c12d7 100644 --- a/frontend/app/features/settings/pages/settings/settings-page.component.ts +++ b/frontend/app/features/settings/pages/settings/settings-page.component.ts @@ -6,7 +6,6 @@ */ import { Component, OnInit } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AppSettingsDto, AppsState, EditAppSettingsForm, ResourceOwner } from '@app/shared'; @Component({ @@ -17,12 +16,11 @@ import { AppSettingsDto, AppsState, EditAppSettingsForm, ResourceOwner } from '@ export class SettingsPageComponent extends ResourceOwner implements OnInit { public isEditable = false; - public editForm = new EditAppSettingsForm(this.formBuilder); + public editForm = new EditAppSettingsForm(); public editingSettings: AppSettingsDto; constructor( private readonly appsState: AppsState, - private readonly formBuilder: FormBuilder, ) { super(); } diff --git a/frontend/app/features/settings/pages/workflows/workflow-add-form.component.ts b/frontend/app/features/settings/pages/workflows/workflow-add-form.component.ts index a31a81881..d876e82da 100644 --- a/frontend/app/features/settings/pages/workflows/workflow-add-form.component.ts +++ b/frontend/app/features/settings/pages/workflows/workflow-add-form.component.ts @@ -6,7 +6,6 @@ */ import { ChangeDetectionStrategy, Component } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AddWorkflowForm, WorkflowsState } from '@app/shared'; @Component({ @@ -16,11 +15,10 @@ import { AddWorkflowForm, WorkflowsState } from '@app/shared'; changeDetection: ChangeDetectionStrategy.OnPush, }) export class WorkflowAddFormComponent { - public addWorkflowForm = new AddWorkflowForm(this.formBuilder); + public addWorkflowForm = new AddWorkflowForm(); constructor( private readonly workflowsState: WorkflowsState, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/framework/angular/forms/editable-title.component.html b/frontend/app/framework/angular/forms/editable-title.component.html index e009966f5..a6af55ddd 100644 --- a/frontend/app/framework/angular/forms/editable-title.component.html +++ b/frontend/app/framework/angular/forms/editable-title.component.html @@ -1,11 +1,11 @@
-
+
- +
diff --git a/frontend/app/framework/angular/forms/editable-title.component.ts b/frontend/app/framework/angular/forms/editable-title.component.ts index 75d11bfa8..cdcc4050f 100644 --- a/frontend/app/framework/angular/forms/editable-title.component.ts +++ b/frontend/app/framework/angular/forms/editable-title.component.ts @@ -6,7 +6,7 @@ */ import { Component, EventEmitter, Input, Output } from '@angular/core'; -import { FormBuilder, Validators } from '@angular/forms'; +import { FormControl, Validators } from '@angular/forms'; import { Keys } from '@app/framework/internal'; @Component({ @@ -37,22 +37,11 @@ export class EditableTitleComponent { Validators.required : Validators.nullValidator; - this.renameForm.controls['name'].setValidators(validator); + this.renameForm.setValidators(validator); } public renaming = false; - public renameForm = this.formBuilder.group({ - name: ['', - [ - Validators.required, - ], - ], - }); - - constructor( - private readonly formBuilder: FormBuilder, - ) { - } + public renameForm = new FormControl(); public onKeyDown(event: KeyboardEvent) { if (Keys.isEscape(event)) { @@ -65,7 +54,7 @@ export class EditableTitleComponent { return; } - this.renameForm.setValue({ name: this.name || '' }); + this.renameForm.setValue(this.name || ''); this.renaming = !this.renaming; } @@ -75,10 +64,10 @@ export class EditableTitleComponent { } if (this.renameForm.valid) { - const value = this.renameForm.value; + const name = this.renameForm.value; - this.nameChange.emit(value.name); - this.name = value.name; + this.nameChange.emit(name); + this.name = name; this.renaming = false; } diff --git a/frontend/app/framework/angular/forms/extended-form-array.spec.ts b/frontend/app/framework/angular/forms/extended-form-array.spec.ts new file mode 100644 index 000000000..7d45d3ba8 --- /dev/null +++ b/frontend/app/framework/angular/forms/extended-form-array.spec.ts @@ -0,0 +1,128 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { FormArray, FormControl } from '@angular/forms'; +import { ExtendedFormArray, UndefinableFormArray } from './extended-form-array'; + +describe('ExtendedFormArray', () => { + it('should provide value even if controls are disabled', () => { + const control = new ExtendedFormArray([ + new FormControl('1'), + new FormControl('2'), + ]); + + expect(control.value).toEqual(['1', '2']); + + assertValue(control, ['1', '2'], () => { + control.controls[0].disable(); + }); + }); +}); + +describe('UndefinableFormArray', () => { + const tests = [{ + name: 'undefined (on)', + undefinable: true, + valueExpected: undefined, + valueActual: undefined, + }, { + name: 'defined (on)', + undefinable: true, + valueExpected: [1], + valueActual: [1], + }, { + name: 'defined (off)', + undefinable: false, + valueExpected: [1], + valueActual: [1], + }]; + + it('should provide value even if controls are disabled', () => { + const control = new UndefinableFormArray([ + new FormControl('1'), + new FormControl('2'), + ]); + + expect(control.value).toEqual(['1', '2']); + + assertValue(control, ['1', '2'], () => { + control.controls[0].disable(); + }); + }); + + tests.forEach(x => { + it(`should set value as <${x.name}>`, () => { + const control = buildControl(x.undefinable); + + assertValue(control, x.valueExpected, () => { + control.setValue(x.valueActual as any); + }); + }); + }); + + tests.forEach(x => { + it(`should patch value as <${x.name}>`, () => { + const control = buildControl(x.undefinable); + + assertValue(control, x.valueExpected, () => { + control.patchValue(x.valueActual as any); + }); + }); + }); + + tests.forEach(x => { + it(`should reset value as <${x.name}>`, () => { + const control = buildControl(x.undefinable); + + assertValue(control, x.valueExpected, () => { + control.reset(x.valueActual as any); + }); + }); + }); + + it('should reset value back after push', () => { + const control = new UndefinableFormArray([]); + + assertValue(control, ['1'], () => { + control.setValue(undefined); + control.push(new FormControl('1')); + }); + }); + + it('should reset value back after insert', () => { + const control = new UndefinableFormArray([]); + + assertValue(control, ['1'], () => { + control.setValue(undefined); + control.insert(0, new FormControl('1')); + }); + }); + + function buildControl(undefinable: boolean) { + return undefinable ? + new UndefinableFormArray([ + new FormControl(''), + ]) : + new ExtendedFormArray([ + new FormControl(''), + ]); + } +}); + +function assertValue(control: FormArray, expected: any, action: () => void) { + let currentValue: any; + + control.valueChanges.subscribe(value => { + currentValue = value; + }); + + action(); + + expect(currentValue).toEqual(expected); + expect(control.getRawValue()).toEqual(expected); + expect(control.value).toEqual(expected); +} diff --git a/frontend/app/framework/angular/forms/undefinable-form-array.ts b/frontend/app/framework/angular/forms/extended-form-array.ts similarity index 74% rename from frontend/app/framework/angular/forms/undefinable-form-array.ts rename to frontend/app/framework/angular/forms/extended-form-array.ts index b86e29063..86be338ff 100644 --- a/frontend/app/framework/angular/forms/undefinable-form-array.ts +++ b/frontend/app/framework/angular/forms/extended-form-array.ts @@ -5,11 +5,24 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { EventEmitter } from '@angular/core'; import { AbstractControl, AbstractControlOptions, AsyncValidatorFn, FormArray, ValidatorFn } from '@angular/forms'; import { Types } from '@app/framework/internal'; -export class UndefinableFormArray extends FormArray { +export class ExtendedFormArray extends FormArray { + constructor(controls: AbstractControl[], validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) { + super(controls, validatorOrOpts, asyncValidator); + + this['_reduceValue'] = () => { + return this.controls.map(x => x.value); + }; + + this['_updateValue'] = () => { + (this as { value: any }).value = this['_reduceValue'](); + }; + } +} + +export class UndefinableFormArray extends ExtendedFormArray { private isUndefined = false; constructor(controls: AbstractControl[], validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) { @@ -79,25 +92,4 @@ export class UndefinableFormArray extends FormArray { this.clear({ emitEvent: false }); } } - - public updateValueAndValidity(opts: { onlySelf?: boolean; emitEvent?: boolean } = {}) { - super.updateValueAndValidity({ emitEvent: false, onlySelf: true }); - - if (this.isUndefined) { - this.unsetValue(); - } - - if (opts.emitEvent !== false) { - (this.valueChanges as EventEmitter).emit(this.value); - (this.statusChanges as EventEmitter).emit(this.status); - } - - if (this.parent && !opts.onlySelf) { - this.parent.updateValueAndValidity(opts); - } - } - - private unsetValue() { - (this as { value: any }).value = undefined; - } } diff --git a/frontend/app/framework/angular/forms/extended-form-group.spec.ts b/frontend/app/framework/angular/forms/extended-form-group.spec.ts new file mode 100644 index 000000000..a5a1fbb77 --- /dev/null +++ b/frontend/app/framework/angular/forms/extended-form-group.spec.ts @@ -0,0 +1,110 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { FormControl, FormGroup } from '@angular/forms'; +import { ExtendedFormGroup, UndefinableFormGroup } from './extended-form-group'; + +describe('UndefinableFormGroup', () => { + it('should provide value even if controls are disabled', () => { + const control = new ExtendedFormGroup({ + test1: new FormControl('1'), + test2: new FormControl('2'), + }); + + expect(control.value).toEqual({ test1: '1', test2: '2' }); + + assertValue(control, { test1: '1', test2: '2' }, () => { + control.controls['test1'].disable(); + }); + }); +}); + +describe('ExtendedFormGroup', () => { + const tests = [{ + name: 'undefined (on)', + undefinable: true, + valueExpected: undefined, + valueActual: undefined, + }, { + name: 'defined (on)', + undefinable: true, + valueExpected: { field: 1 }, + valueActual: { field: 1 }, + }, { + name: 'defined (off)', + undefinable: false, + valueExpected: { field: 1 }, + valueActual: { field: 1 }, + }]; + + it('should provide value even if controls are disabled', () => { + const control = new ExtendedFormGroup({ + test1: new FormControl('1'), + test2: new FormControl('2'), + }); + + expect(control.value).toEqual({ test1: '1', test2: '2' }); + + assertValue(control, { test1: '1', test2: '2' }, () => { + control.controls['test1'].disable(); + }); + }); + + tests.forEach(x => { + it(`should set value as <${x.name}>`, () => { + const control = buildControl(x.undefinable); + + assertValue(control, x.valueExpected, () => { + control.setValue(x.valueActual as any); + }); + }); + }); + + tests.forEach(x => { + it(`should patch value as <${x.name}>`, () => { + const control = buildControl(x.undefinable); + + assertValue(control, x.valueExpected, () => { + control.patchValue(x.valueActual as any); + }); + }); + }); + + tests.forEach(x => { + it(`should reset value as <${x.name}>`, () => { + const control = buildControl(x.undefinable); + + assertValue(control, x.valueExpected, () => { + control.reset(x.valueActual); + }); + }); + }); + + function buildControl(undefinable: boolean) { + return undefinable ? + new UndefinableFormGroup({ + field: new FormControl(), + }) : + new ExtendedFormGroup({ + field: new FormControl(), + }); + } +}); + +function assertValue(control: FormGroup, expected: any, action: () => void) { + let currentValue: any; + + control.valueChanges.subscribe(value => { + currentValue = value; + }); + + action(); + + expect(currentValue).toEqual(expected); + expect(control.getRawValue()).toEqual(expected); + expect(control.value).toEqual(expected); +} diff --git a/frontend/app/framework/angular/forms/undefinable-form-group.ts b/frontend/app/framework/angular/forms/extended-form-group.ts similarity index 71% rename from frontend/app/framework/angular/forms/undefinable-form-group.ts rename to frontend/app/framework/angular/forms/extended-form-group.ts index d3632c335..09a3d9514 100644 --- a/frontend/app/framework/angular/forms/undefinable-form-group.ts +++ b/frontend/app/framework/angular/forms/extended-form-group.ts @@ -5,11 +5,30 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { EventEmitter } from '@angular/core'; import { AbstractControl, AbstractControlOptions, AsyncValidatorFn, FormGroup, ValidatorFn } from '@angular/forms'; import { Types } from '@app/framework/internal'; -export class UndefinableFormGroup extends FormGroup { +export class ExtendedFormGroup extends FormGroup { + constructor(controls: { [key: string]: AbstractControl }, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) { + super(controls, validatorOrOpts, asyncValidator); + + this['_reduceValue'] = () => { + const result = {}; + + for (const [key, control] of Object.entries(this.controls)) { + result[key] = control.value; + } + + return result; + }; + + this['_updateValue'] = () => { + (this as { value: any }).value = this['_reduceValue'](); + }; + } +} + +export class UndefinableFormGroup extends ExtendedFormGroup { private isUndefined = false; constructor(controls: { [key: string]: AbstractControl }, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null) { @@ -63,25 +82,4 @@ export class UndefinableFormGroup extends FormGroup { private checkUndefined(value?: {}) { this.isUndefined = Types.isUndefined(value); } - - public updateValueAndValidity(opts: { onlySelf?: boolean; emitEvent?: boolean } = {}) { - super.updateValueAndValidity({ emitEvent: false, onlySelf: true }); - - if (this.isUndefined) { - this.unsetValue(); - } - - if (opts.emitEvent !== false) { - (this.valueChanges as EventEmitter).emit(this.value); - (this.statusChanges as EventEmitter).emit(this.status); - } - - if (this.parent && !opts.onlySelf) { - this.parent.updateValueAndValidity(opts); - } - } - - private unsetValue() { - (this as { value: any }).value = undefined; - } } diff --git a/frontend/app/framework/angular/forms/forms-helper.ts b/frontend/app/framework/angular/forms/forms-helper.ts index 2d6c67345..7179249c5 100644 --- a/frontend/app/framework/angular/forms/forms-helper.ts +++ b/frontend/app/framework/angular/forms/forms-helper.ts @@ -96,7 +96,7 @@ export function invalid$(form: AbstractControl): Observable { } export function value$(form: AbstractControl): Observable { - return form.valueChanges.pipe(map(() => getRawValue(form)), startWith(getRawValue(form)), distinctUntilChanged()); + return form.valueChanges.pipe(startWith(form.value), distinctUntilChanged()); } export function valueProjection$(form: AbstractControl, projection: (value: any) => T): Observable { @@ -159,16 +159,6 @@ function isValid(value: any) { return !Types.isNull(value) && !Types.isUndefined(value); } -export function getRawValue(form: AbstractControl): any { - if (Types.is(form, FormGroup)) { - return form.getRawValue(); - } else if (Types.is(form, FormArray)) { - return form.getRawValue(); - } else { - return form.value; - } -} - export function hasNonCustomError(form: AbstractControl) { if (form.errors) { for (const key in form.errors) { diff --git a/frontend/app/framework/angular/forms/model.ts b/frontend/app/framework/angular/forms/model.ts index d8ffa9370..b10a93ca2 100644 --- a/frontend/app/framework/angular/forms/model.ts +++ b/frontend/app/framework/angular/forms/model.ts @@ -9,7 +9,7 @@ import { AbstractControl, ValidatorFn } from '@angular/forms'; import { ErrorDto, Types } from '@app/framework/internal'; import { State } from './../../state'; import { ErrorValidator } from './error-validator'; -import { addValidator, getRawValue, hasNonCustomError, updateAll } from './forms-helper'; +import { addValidator, hasNonCustomError, updateAll } from './forms-helper'; export interface FormState { // The number of submits. @@ -92,7 +92,7 @@ export class Form { this.form.markAllAsTouched(); if (!hasNonCustomError(this.form)) { - const value = this.transformSubmit(getRawValue(this.form)); + const value = this.transformSubmit(this.form.value); if (value) { this.disable(); diff --git a/frontend/app/framework/angular/forms/templated-form-array.ts b/frontend/app/framework/angular/forms/templated-form-array.ts index 4b2493567..0f160a80f 100644 --- a/frontend/app/framework/angular/forms/templated-form-array.ts +++ b/frontend/app/framework/angular/forms/templated-form-array.ts @@ -7,7 +7,7 @@ import { AbstractControl, AbstractControlOptions, AsyncValidatorFn, ValidatorFn } from '@angular/forms'; import { Types } from '@app/framework/internal'; -import { UndefinableFormArray } from './undefinable-form-array'; +import { UndefinableFormArray } from './extended-form-array'; export interface FormArrayTemplate { createControl(value: any, initialValue?: any): AbstractControl; diff --git a/frontend/app/framework/angular/forms/templated-form-group.ts b/frontend/app/framework/angular/forms/templated-form-group.ts index 097d49db3..297b2db44 100644 --- a/frontend/app/framework/angular/forms/templated-form-group.ts +++ b/frontend/app/framework/angular/forms/templated-form-group.ts @@ -7,7 +7,7 @@ import { AbstractControlOptions, AsyncValidatorFn, FormGroup, ValidatorFn } from '@angular/forms'; import { Types } from '@app/framework/internal'; -import { UndefinableFormGroup } from './undefinable-form-group'; +import { UndefinableFormGroup } from './extended-form-group'; export interface FormGroupTemplate { setControls(form: FormGroup, value: any): void; diff --git a/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts b/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts deleted file mode 100644 index b0da75704..000000000 --- a/frontend/app/framework/angular/forms/undefinable-form-array.spec.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { FormArray, FormControl } from '@angular/forms'; -import { UndefinableFormArray } from './undefinable-form-array'; - -describe('UndefinableFormArray', () => { - const tests = [{ - name: 'undefined', - value: undefined, - }, { - name: 'defined', - value: ['1'], - }]; - - tests.forEach(x => { - it(`should set value as <${x.name}>`, () => { - const control = - new UndefinableFormArray([ - new FormControl(''), - ]); - - assertValue(control, x.value, () => { - control.setValue(x.value); - }); - }); - }); - - tests.forEach(x => { - it(`should patch value as <${x.name}>`, () => { - const control = - new UndefinableFormArray([ - new FormControl(''), - ]); - - assertValue(control, x.value, () => { - control.patchValue(x.value); - }); - }); - }); - - tests.forEach(x => { - it(`should reset value as <${x.name}>`, () => { - const control = - new UndefinableFormArray([ - new FormControl(''), - ]); - - assertValue(control, x.value, () => { - control.reset(x.value); - }); - }); - }); - - it('should reset value back after push', () => { - const control = new UndefinableFormArray([]); - - assertValue(control, ['1'], () => { - control.setValue(undefined); - control.push(new FormControl('1')); - }); - }); - - it('should reset value back after insert', () => { - const control = new UndefinableFormArray([]); - - assertValue(control, ['1'], () => { - control.setValue(undefined); - control.insert(0, new FormControl('1')); - }); - }); - - function assertValue(control: FormArray, expected: any, action: () => void) { - let currentValue: any; - - control.valueChanges.subscribe(value => { - currentValue = value; - }); - - action(); - - expect(currentValue).toEqual(expected); - expect(control.getRawValue()).toEqual(expected); - } -}); diff --git a/frontend/app/framework/angular/forms/undefinable-form-group.spec.ts b/frontend/app/framework/angular/forms/undefinable-form-group.spec.ts deleted file mode 100644 index 193913961..000000000 --- a/frontend/app/framework/angular/forms/undefinable-form-group.spec.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { FormControl, FormGroup } from '@angular/forms'; -import { UndefinableFormGroup } from './undefinable-form-group'; - -describe('UndefinableFormGroup', () => { - const tests = [{ - name: 'undefined', - value: undefined, - }, { - name: 'defined', - value: { field: ['1'] }, - }]; - - tests.forEach(x => { - it(`should set value as <${x.name}>`, () => { - const control = - new UndefinableFormGroup({ - field: new FormControl(), - }); - - assertValue(control, x.value, () => { - control.setValue(x.value); - }); - }); - }); - - tests.forEach(x => { - it(`should patch value as <${x.name}>`, () => { - const control = - new UndefinableFormGroup({ - field: new FormControl(), - }); - - assertValue(control, x.value, () => { - control.patchValue(x.value); - }); - }); - }); - - tests.forEach(x => { - it(`should reset value as <${x.name}>`, () => { - const control = - new UndefinableFormGroup({ - field: new FormControl(), - }); - - assertValue(control, x.value, () => { - control.reset(x.value); - }); - }); - }); - - function assertValue(control: FormGroup, expected: any, action: () => void) { - let currentValue: any; - - control.valueChanges.subscribe(value => { - currentValue = value; - }); - - action(); - - expect(currentValue).toEqual(expected); - expect(control.getRawValue()).toEqual(expected); - } -}); diff --git a/frontend/app/framework/declarations.ts b/frontend/app/framework/declarations.ts index 126b840c0..290a69549 100644 --- a/frontend/app/framework/declarations.ts +++ b/frontend/app/framework/declarations.ts @@ -22,6 +22,8 @@ export * from './angular/forms/editors/localized-input.component'; export * from './angular/forms/editors/stars.component'; export * from './angular/forms/editors/tag-editor.component'; export * from './angular/forms/editors/toggle.component'; +export * from './angular/forms/extended-form-array'; +export * from './angular/forms/extended-form-group'; export * from './angular/forms/file-drop.directive'; export * from './angular/forms/focus-on-init.directive'; export * from './angular/forms/form-alert.component'; @@ -33,17 +35,15 @@ export * from './angular/forms/model'; export * from './angular/forms/progress-bar.component'; export * from './angular/forms/templated-form-array'; export * from './angular/forms/transform-input.directive'; -export * from './angular/forms/undefinable-form-array'; -export * from './angular/forms/undefinable-form-group'; export * from './angular/forms/validators'; export * from './angular/hover-background.directive'; export * from './angular/http/caching.interceptor'; export * from './angular/http/http-extensions'; export * from './angular/http/loading.interceptor'; export * from './angular/image-source.directive'; -export * from './angular/layout.component'; -export * from './angular/layout-container.directive'; export * from './angular/language-selector.component'; +export * from './angular/layout-container.directive'; +export * from './angular/layout.component'; export * from './angular/list-view.component'; export * from './angular/modals/dialog-renderer.component'; export * from './angular/modals/modal-dialog.component'; diff --git a/frontend/app/shared/components/app-form.component.ts b/frontend/app/shared/components/app-form.component.ts index 1b552fa84..826d10a22 100644 --- a/frontend/app/shared/components/app-form.component.ts +++ b/frontend/app/shared/components/app-form.component.ts @@ -6,7 +6,6 @@ */ import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { ApiUrlConfig, AppsState, CreateAppForm } from '@app/shared/internal'; @Component({ @@ -22,12 +21,11 @@ export class AppFormComponent { @Input() public template = ''; - public createForm = new CreateAppForm(this.formBuilder); + public createForm = new CreateAppForm(); constructor( public readonly apiUrl: ApiUrlConfig, private readonly appsStore: AppsState, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/shared/components/assets/asset-dialog.component.ts b/frontend/app/shared/components/assets/asset-dialog.component.ts index 64532d3b3..2a3707009 100644 --- a/frontend/app/shared/components/assets/asset-dialog.component.ts +++ b/frontend/app/shared/components/assets/asset-dialog.component.ts @@ -6,7 +6,6 @@ */ import { ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, Output, QueryList, ViewChildren } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AnnotateAssetDto, AnnotateAssetForm, AppsState, AssetDto, AssetsState, AssetUploaderState, AuthService, DialogService, Types, UploadCanceled } from '@app/shared/internal'; import { AssetsService } from '@app/shared/services/assets.service'; import { AssetPathItem, ROOT_ITEM } from '@app/shared/state/assets.state'; @@ -53,7 +52,7 @@ export class AssetDialogComponent implements OnChanges { public selectedTab = 0; - public annotateForm = new AnnotateAssetForm(this.formBuilder); + public annotateForm = new AnnotateAssetForm(); public get isImage() { return this.asset.type === 'Image'; @@ -74,7 +73,6 @@ export class AssetDialogComponent implements OnChanges { private readonly assetsService: AssetsService, private readonly changeDetector: ChangeDetectorRef, private readonly dialogs: DialogService, - private readonly formBuilder: FormBuilder, public readonly authService: AuthService, ) { } diff --git a/frontend/app/shared/components/assets/asset-folder-dialog.component.ts b/frontend/app/shared/components/assets/asset-folder-dialog.component.ts index 3d6e9dbbb..93aa0637b 100644 --- a/frontend/app/shared/components/assets/asset-folder-dialog.component.ts +++ b/frontend/app/shared/components/assets/asset-folder-dialog.component.ts @@ -6,7 +6,6 @@ */ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { AssetFolderDto, AssetsState, RenameAssetFolderForm } from '@app/shared/internal'; @Component({ @@ -21,11 +20,10 @@ export class AssetFolderDialogComponent implements OnInit { @Input() public assetFolder: AssetFolderDto; - public editForm = new RenameAssetFolderForm(this.formBuilder); + public editForm = new RenameAssetFolderForm(); constructor( private readonly assetsState: AssetsState, - private readonly formBuilder: FormBuilder, ) { } diff --git a/frontend/app/shared/components/comments/comments.component.ts b/frontend/app/shared/components/comments/comments.component.ts index 8659ee865..c381b0bff 100644 --- a/frontend/app/shared/components/comments/comments.component.ts +++ b/frontend/app/shared/components/comments/comments.component.ts @@ -6,7 +6,6 @@ */ import { ChangeDetectorRef, Component, ElementRef, Input, OnChanges, QueryList, ViewChild, ViewChildren } from '@angular/core'; -import { FormBuilder } from '@angular/forms'; import { Router } from '@angular/router'; import { switchSafe } from '@app/framework'; import { AppsState, AuthService, CommentDto, CommentsService, CommentsState, ContributorsState, DialogService, ResourceOwner, UpsertCommentForm } from '@app/shared/internal'; @@ -31,7 +30,7 @@ export class CommentsComponent extends ResourceOwner implements OnChanges { public commentsUrl: string; public commentsState: CommentsState; - public commentForm = new UpsertCommentForm(this.formBuilder); + public commentForm = new UpsertCommentForm(); public mentionUsers = this.contributorsState.contributors; public mentionConfig: MentionConfig = { dropUp: true, labelKey: 'contributorEmail' }; @@ -44,7 +43,6 @@ export class CommentsComponent extends ResourceOwner implements OnChanges { private readonly contributorsState: ContributorsState, private readonly changeDetector: ChangeDetectorRef, private readonly dialogs: DialogService, - private readonly formBuilder: FormBuilder, private readonly router: Router, ) { super(); diff --git a/frontend/app/shared/components/forms/geolocation-editor.component.ts b/frontend/app/shared/components/forms/geolocation-editor.component.ts index 8ee091fb1..f8f825b3f 100644 --- a/frontend/app/shared/components/forms/geolocation-editor.component.ts +++ b/frontend/app/shared/components/forms/geolocation-editor.component.ts @@ -6,8 +6,8 @@ */ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, forwardRef, Input, ViewChild } from '@angular/core'; -import { FormBuilder, NG_VALUE_ACCESSOR } from '@angular/forms'; -import { LocalStoreService, ResourceLoaderService, Settings, StatefulControlComponent, Types, UIOptions, ValidatorsEx } from '@app/shared/internal'; +import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { LocalStoreService, ResourceLoaderService, Settings, StatefulControlComponent, Types, UIOptions, ExtendedFormGroup, ValidatorsEx } from '@app/shared/internal'; declare const L: any; declare const google: any; @@ -54,19 +54,13 @@ export class GeolocationEditorComponent extends StatefulControlComponent; public saveQueryDialog = new DialogModel(); - public saveQueryForm = new SaveQueryForm(this.formBuilder); + public saveQueryForm = new SaveQueryForm(); public searchDialog = new DialogModel(false); public hasFilter: boolean; - constructor( - private readonly formBuilder: FormBuilder, - ) { - } - public ngOnChanges(changes: SimpleChanges) { if (changes['query'] || changes['queries']) { this.updateSaveKey(); diff --git a/frontend/app/shared/state/apps.forms.ts b/frontend/app/shared/state/apps.forms.ts index 569f10554..23cf5bae4 100644 --- a/frontend/app/shared/state/apps.forms.ts +++ b/frontend/app/shared/state/apps.forms.ts @@ -7,99 +7,99 @@ /* eslint-disable no-useless-escape */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, TemplatedFormArray, ValidatorsEx } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, TemplatedFormArray, ExtendedFormGroup, ValidatorsEx } from '@app/framework'; import { AppDto, AppSettingsDto, CreateAppDto, UpdateAppDto, UpdateAppSettingsDto } from './../services/apps.service'; -export class CreateAppForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - name: ['', - [ - Validators.required, - Validators.maxLength(40), - ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'i18n:apps.appNameValidationMessage'), - ], - ], +export class CreateAppForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + name: new FormControl('', [ + Validators.required, + Validators.maxLength(40), + ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'i18n:apps.appNameValidationMessage'), + ]), })); } } -export class UpdateAppForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - label: ['', - [ - Validators.maxLength(40), - ], - ], - description: '', +export class UpdateAppForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + label: new FormControl('', + Validators.maxLength(40), + ), + description: new FormControl('', + Validators.nullValidator, + ), })); } } -export class EditAppSettingsForm extends Form { +export class EditAppSettingsForm extends Form { public get patterns() { - return this.form.controls['patterns']! as TemplatedFormArray; + return this.form.controls['patterns'] as TemplatedFormArray; } - public get patternsControls(): ReadonlyArray { + public get patternsControls(): ReadonlyArray { return this.patterns.controls as any; } public get editors() { - return this.form.controls['editors']! as TemplatedFormArray; + return this.form.controls['editors'] as TemplatedFormArray; } - public get editorsControls(): ReadonlyArray { + public get editorsControls(): ReadonlyArray { return this.editors.controls as any; } - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - patterns: new TemplatedFormArray(new PatternTemplate(formBuilder)), - hideScheduler: false, - hideDateTimeButtons: false, - editors: new TemplatedFormArray(new EditorTemplate(formBuilder)), + constructor() { + super(new ExtendedFormGroup({ + patterns: new TemplatedFormArray( + PatternTemplate.INSTANCE, + ), + hideScheduler: new FormControl(false, + Validators.nullValidator, + ), + hideDateTimeButtons: new FormControl(false, + Validators.nullValidator, + ), + editors: new TemplatedFormArray( + EditorTemplate.INSTANCE, + ), })); } } class PatternTemplate { - constructor(private readonly formBuilder: FormBuilder) {} + public static readonly INSTANCE = new PatternTemplate(); public createControl() { - return this.formBuilder.group({ - name: ['', - [ - Validators.required, - ], - ], - regex: ['', - [ - Validators.required, - ], - ], - message: '', + return new FormControl({ + name: new FormControl('', + Validators.required, + ), + regex: new FormControl('', + Validators.required, + ), + message: new FormControl('', + Validators.nullValidator, + ), }); } } class EditorTemplate { - constructor(private readonly formBuilder: FormBuilder) {} + public static readonly INSTANCE = new EditorTemplate(); public createControl() { - return this.formBuilder.group({ - name: ['', - [ - Validators.required, - ], - ], - url: ['', - [ - Validators.required, - ], - ], + return new FormControl({ + name: new FormControl('', + Validators.required, + ), + url: new FormControl('', + Validators.required, + ), }); } } diff --git a/frontend/app/shared/state/assets.forms.spec.ts b/frontend/app/shared/state/assets.forms.spec.ts index 07782dd3e..4247fffc6 100644 --- a/frontend/app/shared/state/assets.forms.spec.ts +++ b/frontend/app/shared/state/assets.forms.spec.ts @@ -5,7 +5,6 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder } from '@angular/forms'; import { AnnotateAssetForm } from './assets.forms'; describe('AnnotateAssetForm', () => { @@ -28,7 +27,7 @@ describe('AnnotateAssetForm', () => { }; beforeEach(() => { - form = new AnnotateAssetForm(new FormBuilder()); + form = new AnnotateAssetForm(); }); it('shoulde remove extension if loading asset file name', () => { diff --git a/frontend/app/shared/state/assets.forms.ts b/frontend/app/shared/state/assets.forms.ts index a96245331..c947f279b 100644 --- a/frontend/app/shared/state/assets.forms.ts +++ b/frontend/app/shared/state/assets.forms.ts @@ -5,43 +5,37 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, Mutable, TemplatedFormArray, Types } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, Mutable, TemplatedFormArray, Types, ExtendedFormGroup } from '@app/framework'; import slugify from 'slugify'; import { AnnotateAssetDto, AssetDto, AssetFolderDto, RenameAssetFolderDto, RenameAssetTagDto } from './../services/assets.service'; -export class AnnotateAssetForm extends Form { +export class AnnotateAssetForm extends Form { public get metadata() { - return this.form.get('metadata')! as TemplatedFormArray; + return this.form.controls['metadata'] as TemplatedFormArray; } - public get metadataControls(): ReadonlyArray { + public get metadataControls(): ReadonlyArray { return this.metadata.controls as any; } - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - isProtected: [false, - [ - Validators.nullValidator, - ], - ], - fileName: ['', - [ - Validators.required, - ], - ], - slug: ['', - [ - Validators.required, - ], - ], - tags: [[], - [ - Validators.nullValidator, - ], - ], - metadata: new TemplatedFormArray(new MetadataTemplate(formBuilder)), + constructor() { + super(new ExtendedFormGroup({ + isProtected: new FormControl(false, + Validators.nullValidator, + ), + fileName: new FormControl('', + Validators.required, + ), + slug: new FormControl('', + Validators.required, + ), + tags: new FormControl([], + Validators.nullValidator, + ), + metadata: new TemplatedFormArray( + MetadataTemplate.INSTANCE, + ), })); } @@ -149,7 +143,7 @@ export class AnnotateAssetForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - annotate: '', - create: '', - delete: '', - move: '', - update: '', +export class EditAssetScriptsForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + annotate: new FormControl('', + Validators.nullValidator, + ), + create: new FormControl('', + Validators.nullValidator, + ), + delete: new FormControl('', + Validators.nullValidator, + ), + move: new FormControl('', + Validators.nullValidator, + ), + update: new FormControl('', + Validators.nullValidator, + ), })); } } -export class RenameAssetFolderForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - folderName: ['', - [ - Validators.required, - ], - ], +export class RenameAssetFolderForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + folderName: new FormControl('', + Validators.required, + ), })); } } -export class RenameAssetTagForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - tagName: ['', - [ - Validators.required, - ], - ], +export class RenameAssetTagForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + tagName: new FormControl('', + Validators.required, + ), })); } } diff --git a/frontend/app/shared/state/backups.forms.ts b/frontend/app/shared/state/backups.forms.ts index b2576eb39..6f2ba9d88 100644 --- a/frontend/app/shared/state/backups.forms.ts +++ b/frontend/app/shared/state/backups.forms.ts @@ -7,26 +7,28 @@ /* eslint-disable no-useless-escape */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, hasNoValue$, ValidatorsEx } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, hasNoValue$, ExtendedFormGroup, ValidatorsEx } from '@app/framework'; import { StartRestoreDto } from './../services/backups.service'; -export class RestoreForm extends Form { - public hasNoUrl = hasNoValue$(this.form.controls['url']); +export class RestoreForm extends Form { + public get url() { + return this.form.controls['url']; + } + + public hasNoUrl = hasNoValue$(this.url); - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - name: ['', - [ + constructor() { + super( + new ExtendedFormGroup({ + name: new FormControl('', [ Validators.maxLength(40), ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'i18n:apps.appNameValidationMessage'), - ], - ], - url: ['', - [ + ]), + url: new FormControl('', Validators.required, - ], - ], - })); + ), + }), + ); } } diff --git a/frontend/app/shared/state/clients.forms.ts b/frontend/app/shared/state/clients.forms.ts index 76296dd4a..3f9b70c6c 100644 --- a/frontend/app/shared/state/clients.forms.ts +++ b/frontend/app/shared/state/clients.forms.ts @@ -7,33 +7,33 @@ /* eslint-disable no-useless-escape */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, hasNoValue$, ValidatorsEx } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, hasNoValue$, ExtendedFormGroup, ValidatorsEx } from '@app/framework'; import { ClientDto, CreateClientDto, UpdateClientDto } from './../services/clients.service'; -export class RenameClientForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - name: ['', - [ - Validators.required, - ], - ], +export class RenameClientForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + name: new FormControl('', + Validators.required, + ), })); } } -export class AddClientForm extends Form { - public hasNoId = hasNoValue$(this.form.controls['id']); +export class AddClientForm extends Form { + public get id() { + return this.form.controls['id']; + } + + public hasNoId = hasNoValue$(this.id); - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - id: ['', - [ - Validators.maxLength(40), - ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'i18n:clients.clientIdValidationMessage'), - ], - ], + constructor() { + super(new ExtendedFormGroup({ + id: new FormControl('', [ + Validators.maxLength(40), + ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'i18n:clients.clientIdValidationMessage'), + ]), })); } } diff --git a/frontend/app/shared/state/comments.form.ts b/frontend/app/shared/state/comments.form.ts index a21394628..797588ecd 100644 --- a/frontend/app/shared/state/comments.form.ts +++ b/frontend/app/shared/state/comments.form.ts @@ -5,14 +5,16 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder, FormGroup } from '@angular/forms'; -import { Form } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, ExtendedFormGroup } from '@app/framework'; import { UpsertCommentDto } from './../services/comments.service'; -export class UpsertCommentForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - text: '', +export class UpsertCommentForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + text: new FormControl('', + Validators.nullValidator, + ), })); } } diff --git a/frontend/app/shared/state/contents.forms-helpers.ts b/frontend/app/shared/state/contents.forms-helpers.ts index fd67c9dba..b7fe13b05 100644 --- a/frontend/app/shared/state/contents.forms-helpers.ts +++ b/frontend/app/shared/state/contents.forms-helpers.ts @@ -9,7 +9,6 @@ /* eslint-disable no-useless-return */ import { AbstractControl, ValidatorFn } from '@angular/forms'; -import { getRawValue } from '@app/framework'; import { BehaviorSubject, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AppLanguageDto } from './../services/app-languages.service'; @@ -149,7 +148,7 @@ export abstract class AbstractContentForm { }, }); + // Should hide fields. expect(array.get(0)!.get('field1')!.hidden).toBeTruthy(); expect(array.get(1)!.get('field1')!.hidden).toBeFalsy(); }); + it('should replace component with new fields', () => { + const component1Id = MathHelper.guid(); + const component1 = createSchema({ + id: 1, + fields: [ + createField({ + id: 11, + properties: createProperties('String'), + partitioning: 'invariant', + }), + ], + }); + + const component2Id = MathHelper.guid(); + const component2 = createSchema({ + id: 2, + fields: [ + createField({ + id: 21, + properties: createProperties('String'), + partitioning: 'invariant', + }), + ], + }); + + const contentForm = createForm([ + createField({ + id: 4, + properties: createProperties('Component'), + partitioning: 'invariant', + }), + ], [], { + [component1Id]: component1, + [component2Id]: component2, + }); + + contentForm.load({}); + + // Should be undefined by default. + expect(contentForm.value).toEqual({ + field4: { + iv: undefined, + }, + }); + + contentForm.load({ + field4: { + iv: { + schemaId: component1Id, + }, + }, + }); + + // Should add field from component1. + expect(contentForm.value).toEqual({ + field4: { + iv: { + schemaId: component1Id, + field11: null, + }, + }, + }); + + contentForm.load({ + field4: { + iv: { + schemaId: component2Id, + }, + }, + }); + + // Should add field from component1. + expect(contentForm.value).toEqual({ + field4: { + iv: { + schemaId: component2Id, + field21: null, + }, + }, + }); + }); + + it('should ignore invalid schema ids', () => { + const componentId = MathHelper.guid(); + const component = createSchema({ + id: 1, + fields: [ + createField({ + id: 11, + properties: createProperties('String'), + partitioning: 'invariant', + }), + ], + }); + + const contentForm = createForm([ + createField({ + id: 4, + properties: createProperties('Component'), + partitioning: 'invariant', + }), + ], [], { + [componentId]: component, + }); + + contentForm.load({ + field4: { + iv: { + schemaId: 'invalid', + }, + }, + }); + + // Should ignore invalid id. + expect(contentForm.value).toEqual({ + field4: { + iv: {}, + }, + }); + }); + it('should load with array and not enable disabled nested fields', () => { const { contentForm, array } = createArrayFormWith2Items(); @@ -539,7 +661,7 @@ describe('ContentForm', () => { array.sort([array.get(1), array.get(0)]); expectLength(array, 2); - expect(array.form.value).toEqual([{ nested41: 'Text2' }, { nested41: 'Text1' }]); + expect(array.form.value).toEqual([{ nested41: 'Text2', nested42: null }, { nested41: 'Text1', nested42: null }]); }); it('should remove array item', () => { @@ -548,7 +670,7 @@ describe('ContentForm', () => { array.removeItemAt(0); expectLength(array, 1); - expect(array.form.value).toEqual([{ nested41: 'Text2' }]); + expect(array.form.value).toEqual([{ nested41: 'Text2', nested42: null }]); }); it('should reset array item', () => { diff --git a/frontend/app/shared/state/contents.forms.ts b/frontend/app/shared/state/contents.forms.ts index 5233f7cc8..7ed264a17 100644 --- a/frontend/app/shared/state/contents.forms.ts +++ b/frontend/app/shared/state/contents.forms.ts @@ -5,8 +5,8 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { debounceTimeSafe, Form, FormArrayTemplate, getRawValue, TemplatedFormArray, Types, value$ } from '@app/framework'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; +import { debounceTimeSafe, Form, FormArrayTemplate, TemplatedFormArray, Types, ExtendedFormGroup, value$ } from '@app/framework'; import { FormGroupTemplate, TemplatedFormGroup } from '@app/framework/angular/forms/templated-form-group'; import { BehaviorSubject, distinctUntilChanged, Observable } from 'rxjs'; import { AppLanguageDto } from './../services/app-languages.service'; @@ -19,27 +19,27 @@ import { FieldDefaultValue, FieldsValidators } from './contents.forms.visitors'; type SaveQueryFormType = { name: string; user: boolean }; -export class SaveQueryForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - name: ['', - [ - Validators.required, - ], - ], - user: false, +export class SaveQueryForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + name: new FormControl('', + Validators.required, + ), + user: new FormControl(false, + Validators.nullValidator, + ), })); } } -export class PatchContentForm extends Form { +export class PatchContentForm extends Form { private readonly editableFields: ReadonlyArray; constructor( private readonly listFields: ReadonlyArray, private readonly language: AppLanguageDto, ) { - super(new FormGroup({})); + super(new ExtendedFormGroup({})); this.editableFields = this.listFields.filter(x => Types.is(x, RootFieldDto) && x.isInlineEditable) as any; @@ -73,7 +73,7 @@ export class PatchContentForm extends Form { } } -export class EditContentForm extends Form { +export class EditContentForm extends Form { private readonly fields: { [name: string]: FieldForm } = {}; private readonly valueChange$ = new BehaviorSubject(this.form.value); private initialData: any; @@ -94,7 +94,7 @@ export class EditContentForm extends Form { public context: any, debounce = 100, ) { - super(new FormGroup({})); + super(new ExtendedFormGroup({})); const globals: FormGlobals = { schema, @@ -185,7 +185,7 @@ export class EditContentForm extends Form { const context = { ...this.context || {}, data }; for (const field of Object.values(this.fields)) { - field.updateState(context, data[field.field.name], data, { isDisabled: this.form.disabled }); + field.updateState(context, data, { isDisabled: this.form.disabled }); } for (const section of this.sections) { @@ -194,7 +194,7 @@ export class EditContentForm extends Form { } private updateInitialData() { - this.initialData = this.form.getRawValue(); + this.initialData = this.form.value; } } @@ -236,7 +236,7 @@ export class FieldForm extends AbstractContentForm { } } - protected updateCustomState(context: any, fieldData: any, itemData: any, state: AbstractContentFormState) { + protected updateCustomState(context: any, itemData: any, state: AbstractContentFormState) { const isRequired = state.isRequired === true; if (this.isRequired !== isRequired) { @@ -262,13 +262,13 @@ export class FieldForm extends AbstractContentForm { } } - for (const [key, partition] of Object.entries(this.partitions)) { - partition.updateState(context, fieldData?.[key], itemData, state); + for (const partition of Object.values(this.partitions)) { + partition.updateState(context, itemData, state); } } private static buildForm() { - return new FormGroup({}); + return new ExtendedFormGroup({}); } } @@ -283,7 +283,7 @@ export class FieldValueForm extends AbstractContentForm { this.isRequired = field.properties.isRequired && !isOptional; } - protected updateCustomState(_context: any, _fieldData: any, _itemData: any, state: AbstractContentFormState) { + protected updateCustomState(_context: any, _itemData: any, state: AbstractContentFormState) { const isRequired = state.isRequired === true; if (!this.isOptional && this.isRequired !== isRequired) { @@ -335,7 +335,7 @@ export class FieldArrayForm extends AbstractContentForm this), FieldsValidators.create(field, isOptional)), isOptional, rules); this.form.template['form'] = this; @@ -346,7 +346,7 @@ export class FieldArrayForm extends AbstractContentForm FieldArrayForm, + ) { + } public createControl() { - const child = this.form.isComponents ? + const child = this.model.isComponents ? this.createComponent() : this.createItem(); - this.form.items = [...this.form.items, child]; + this.model.items = [...this.model.items, child]; return child.form; } public removeControl(index: number) { - this.form.items = this.form.items.filter((_, i) => i !== index); + this.model.items = this.model.items.filter((_, i) => i !== index); } public clearControls() { - this.form.items = []; + this.model.items = []; } private createItem() { return new ArrayItemForm( - this.form.globals, - this.form.field as RootFieldDto, - this.form.fieldPath, - this.form.isOptional, - this.form.rules, - this.form.partition); + this.model.globals, + this.model.field as RootFieldDto, + this.model.fieldPath, + this.model.isOptional, + this.model.rules, + this.model.partition); } private createComponent() { return new ComponentForm( - this.form.globals, - this.form.field as RootFieldDto, - this.form.fieldPath, - this.form.isOptional, - this.form.rules, - this.form.partition); + this.model.globals, + this.model.field as RootFieldDto, + this.model.fieldPath, + this.model.isOptional, + this.model.rules, + this.model.partition); } } @@ -475,9 +478,9 @@ export class ObjectFormBase extends Abstract return this.fields[field['name'] || field]; } - protected updateCustomState(context: any, fieldData: any, _: any, state: AbstractContentFormState) { - for (const [key, field] of Object.entries(this.fields)) { - field.updateState(context, fieldData?.[key], fieldData, state); + protected updateCustomState(context: any, _: any, state: AbstractContentFormState) { + for (const field of Object.values(this.fields)) { + field.updateState(context, this.form.value, state); } for (const section of this.fieldSections) { @@ -606,6 +609,7 @@ export class ComponentForm extends ObjectFormBase { new ComponentTemplate(() => this), partition); + this.form.reset(undefined); this.form.build(); } @@ -616,7 +620,7 @@ export class ComponentForm extends ObjectFormBase { class ComponentTemplate extends ObjectTemplate { public getSchema(value: any, model: ComponentForm) { - return model.globals.schemas[value?.schemaId].fields; + return model.globals.schemas[value?.schemaId]?.fields; } protected setControlsCore(schema: ReadonlyArray, value: any, model: ComponentForm, form: FormGroup) { diff --git a/frontend/app/shared/state/contributors.forms.ts b/frontend/app/shared/state/contributors.forms.ts index 960eef20f..853a795b6 100644 --- a/frontend/app/shared/state/contributors.forms.ts +++ b/frontend/app/shared/state/contributors.forms.ts @@ -5,27 +5,27 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, hasNoValue$, Types, value$ } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, hasNoValue$, Types, ExtendedFormGroup, value$ } from '@app/framework'; import { debounceTime, map, shareReplay } from 'rxjs/operators'; import { AssignContributorDto } from './../services/contributors.service'; import { UserDto } from './../services/users.service'; -export class AssignContributorForm extends Form { - public hasNoUser = hasNoValue$(this.form.controls['user']); - - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - user: [null, - [ - Validators.required, - ], - ], - role: [null, - [ - Validators.required, - ], - ], +export class AssignContributorForm extends Form { + public get user() { + return this.form.controls['user']; + } + + public hasNoUser = hasNoValue$(this.user); + + constructor() { + super(new ExtendedFormGroup({ + user: new FormControl('', + Validators.required, + ), + role: new FormControl('', + Validators.required, + ), })); } @@ -42,18 +42,20 @@ export class AssignContributorForm extends Form type ImportContributorsFormType = ReadonlyArray; -export class ImportContributorsForm extends Form { - public numberOfEmails = value$(this.form.controls['import']).pipe(debounceTime(100), map(v => extractEmails(v).length), shareReplay(1)); +export class ImportContributorsForm extends Form { + public get import() { + return this.form.controls['import']; + } + + public numberOfEmails = value$(this.import).pipe(debounceTime(100), map(v => extractEmails(v).length), shareReplay(1)); public hasNoUser = this.numberOfEmails.pipe(map(v => v === 0)); - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - import: ['', - [ - Validators.required, - ], - ], + constructor() { + super(new ExtendedFormGroup({ + import: new FormControl('', + Validators.required, + ), })); } diff --git a/frontend/app/shared/state/languages.forms.ts b/frontend/app/shared/state/languages.forms.ts index 534652e93..9dece4e9b 100644 --- a/frontend/app/shared/state/languages.forms.ts +++ b/frontend/app/shared/state/languages.forms.ts @@ -5,29 +5,41 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, value$ } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, ExtendedFormGroup, value$ } from '@app/framework'; import { AppLanguageDto, UpdateAppLanguageDto } from './../services/app-languages.service'; import { LanguageDto } from './../services/languages.service'; -export class EditLanguageForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - isMaster: false, - isOptional: false, +export class EditLanguageForm extends Form { + public get isMaster() { + return this.form.controls['isMaster']; + } + + public get isOptional() { + return this.form.controls['isOptional']; + } + + constructor() { + super(new ExtendedFormGroup({ + isMaster: new FormControl(false, + Validators.nullValidator, + ), + isOptional: new FormControl(false, + Validators.nullValidator, + ), })); - value$(this.form.controls['isMaster']) + value$(this.isMaster) .subscribe(value => { if (value) { - this.form.controls['isOptional'].setValue(false); + this.isOptional.setValue(false); } }); - value$(this.form.controls['isOptional']) + value$(this.isMaster) .subscribe(value => { if (value) { - this.form.controls['isMaster'].setValue(false); + this.isOptional.setValue(false); } }); } @@ -35,14 +47,12 @@ export class EditLanguageForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - language: [null, - [ - Validators.required, - ], - ], +export class AddLanguageForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + language: new FormControl(null, + Validators.required, + ), })); } } diff --git a/frontend/app/shared/state/roles.forms.ts b/frontend/app/shared/state/roles.forms.ts index 07bc9c15e..dd9051dd9 100644 --- a/frontend/app/shared/state/roles.forms.ts +++ b/frontend/app/shared/state/roles.forms.ts @@ -5,8 +5,8 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { Form, hasNoValue$, hasValue$, TemplatedFormArray } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, hasNoValue$, hasValue$, TemplatedFormArray, ExtendedFormGroup } from '@app/framework'; import { CreateRoleDto, RoleDto, UpdateRoleDto } from './../services/roles.service'; export class EditRoleForm extends Form { @@ -15,7 +15,7 @@ export class EditRoleForm extends Form { - public hasPermission = hasValue$(this.form.controls['permission']); +export class AddPermissionForm extends Form { + public get permission() { + return this.form.controls['permission']; + } + + public hasPermission = hasValue$(this.permission); - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - permission: ['', - [ - Validators.required, - ], - ], + constructor() { + super(new ExtendedFormGroup({ + permission: new FormControl('', + Validators.required, + ), })); } } -export class AddRoleForm extends Form { - public hasNoName = hasNoValue$(this.form.controls['name']); +export class AddRoleForm extends Form { + public get name() { + return this.form.controls['name']; + } + + public hasNoName = hasNoValue$(this.name); - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - name: ['', - [ - Validators.required, - ], - ], + constructor() { + super(new ExtendedFormGroup({ + name: new FormControl('', + Validators.required, + ), })); } } diff --git a/frontend/app/shared/state/rules.forms.ts b/frontend/app/shared/state/rules.forms.ts index 16a727870..d4f84126e 100644 --- a/frontend/app/shared/state/rules.forms.ts +++ b/frontend/app/shared/state/rules.forms.ts @@ -5,8 +5,8 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; -import { Form, ValidatorsEx } from '@app/framework'; +import { AbstractControl, FormControl, FormGroup, Validators } from '@angular/forms'; +import { Form, ExtendedFormGroup, ValidatorsEx } from '@app/framework'; import { RuleElementDto } from '../services/rules.service'; export class ActionForm extends Form { @@ -28,7 +28,7 @@ export class ActionForm extends Form { controls[property.name] = new FormControl(undefined, validator); } - return new FormGroup(controls); + return new ExtendedFormGroup(controls); } protected transformSubmit(value: any): any { @@ -39,33 +39,40 @@ export class ActionForm extends Form { } export class TriggerForm extends Form { - constructor(formBuilder: FormBuilder, + constructor( private readonly triggerType: string, ) { - super(TriggerForm.builForm(formBuilder, triggerType)); + super(TriggerForm.builForm(triggerType)); } - private static builForm(formBuilder: FormBuilder, triggerType: string) { + private static builForm(triggerType: string) { switch (triggerType) { case 'ContentChanged': { - return formBuilder.group({ handleAll: false, schemas: undefined }); + return new ExtendedFormGroup({ + handleAll: new FormControl(false, + Validators.nullValidator, + ), + schemas: new FormControl(undefined, + Validators.nullValidator, + ), + }); } case 'Usage': { - return formBuilder.group({ - limit: [20000, - [ - Validators.required, - ], - ], - numDays: [3, - [ - ValidatorsEx.between(1, 30), - ], - ], + return new ExtendedFormGroup({ + limit: new FormControl(20000, + Validators.required, + ), + numDays: new FormControl(3, + ValidatorsEx.between(1, 30), + ), }); } default: { - return formBuilder.group({ condition: undefined }); + return new ExtendedFormGroup({ + condition: new FormControl('', + Validators.nullValidator, + ), + }); } } } diff --git a/frontend/app/shared/state/schemas.forms.ts b/frontend/app/shared/state/schemas.forms.ts index c4573ace2..e31383fcb 100644 --- a/frontend/app/shared/state/schemas.forms.ts +++ b/frontend/app/shared/state/schemas.forms.ts @@ -7,39 +7,41 @@ /* eslint-disable no-useless-escape */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, TemplatedFormArray, ValidatorsEx, value$ } from '@app/framework'; +import { AbstractControl, FormControl, Validators } from '@angular/forms'; +import { Form, TemplatedFormArray, ExtendedFormGroup, ValidatorsEx, value$ } from '@app/framework'; import { map } from 'rxjs/operators'; import { AddFieldDto, CreateSchemaDto, FieldRule, SchemaDto, SchemaPropertiesDto, SynchronizeSchemaDto, UpdateSchemaDto } from './../services/schemas.service'; import { createProperties, FieldPropertiesDto, FieldPropertiesVisitor } from './../services/schemas.types'; type CreateCategoryFormType = { name: string }; -export class CreateCategoryForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - name: [''], +export class CreateCategoryForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + name: new FormControl('', + Validators.nullValidator, + ), })); } } -export class CreateSchemaForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - name: ['', - [ - Validators.required, - Validators.maxLength(40), - ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'i18n:schemas.schemaNameValidationMessage'), - ], - ], - type: ['Default', - [ - Validators.required, - ], - ], - initialCategory: undefined, - importing: {}, +export class CreateSchemaForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + name: new FormControl('', [ + Validators.required, + Validators.maxLength(40), + ValidatorsEx.pattern('[a-z0-9]+(\-[a-z0-9]+)*', 'i18n:schemas.schemaNameValidationMessage'), + ]), + type: new FormControl('Default', + Validators.required, + ), + initialCategory: new FormControl(undefined, + Validators.nullValidator, + ), + importing: new FormControl({}, + Validators.nullValidator, + ), })); } @@ -56,17 +58,23 @@ export class CreateSchemaForm extends Form { } } -export class SynchronizeSchemaForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - json: {}, - fieldsDelete: false, - fieldsRecreate: false, +export class SynchronizeSchemaForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + json: new FormControl({}, + Validators.nullValidator, + ), + fieldsDelete: new FormControl(false, + Validators.nullValidator, + ), + fieldsRecreate: new FormControl(false, + Validators.nullValidator, + ), })); } public loadSchema(schema: SchemaDto) { - this.form.get('json')!.setValue(schema.export()); + this.form.patchValue({ json: schema.export() }); } public transformSubmit(value: any) { @@ -79,12 +87,12 @@ export class SynchronizeSchemaForm extends Form } export class ConfigureFieldRulesForm extends Form, SchemaDto> { - public get rulesControls(): ReadonlyArray { + public get rulesControls(): ReadonlyArray { return this.form.controls as any; } - constructor(formBuilder: FormBuilder) { - super(new TemplatedFormArray(new FieldRuleTemplate(formBuilder))); + constructor() { + super(new TemplatedFormArray(FieldRuleTemplate.INSTANCE)); } public add(fieldNames: ReadonlyArray) { @@ -101,25 +109,19 @@ export class ConfigureFieldRulesForm extends Form) { - return this.formBuilder.group({ - action: ['Disable', - [ - Validators.required, - ], - ], - field: [fieldNames?.[0], - [ - Validators.required, - ], - ], - condition: ['', - [ - Validators.required, - ], - ], + return new ExtendedFormGroup({ + name: new FormControl('Disable', + Validators.required, + ), + field: new FormControl(fieldNames?.[0], + Validators.required, + ), + condition: new FormControl('', + Validators.required, + ), }); } } @@ -127,12 +129,12 @@ class FieldRuleTemplate { type ConfigurePreviewUrlsFormType = { [name: string]: string }; export class ConfigurePreviewUrlsForm extends Form { - public get previewControls(): ReadonlyArray { + public get previewControls(): ReadonlyArray { return this.form.controls as any; } - constructor(formBuilder: FormBuilder) { - super(new TemplatedFormArray(new PreviewUrlTemplate(formBuilder))); + constructor() { + super(new TemplatedFormArray(PreviewUrlTemplate.INSTANCE)); } public transformLoad(value: Partial) { @@ -159,179 +161,189 @@ export class ConfigurePreviewUrlsForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - query: '', - create: '', - change: '', - delete: '', - update: '', +export class EditSchemaScriptsForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + query: new FormControl('', + Validators.nullValidator, + ), + create: new FormControl('', + Validators.nullValidator, + ), + change: new FormControl('', + Validators.nullValidator, + ), + delete: new FormControl('', + Validators.nullValidator, + ), + update: new FormControl('', + Validators.nullValidator, + ), })); } } -export class EditFieldForm extends Form { - constructor(formBuilder: FormBuilder, properties: FieldPropertiesDto) { - super(EditFieldForm.buildForm(formBuilder, properties)); +export class EditFieldForm extends Form { + constructor(properties: FieldPropertiesDto) { + super(EditFieldForm.buildForm(properties)); } - private static buildForm(formBuilder: FormBuilder, properties: FieldPropertiesDto) { + private static buildForm(properties: FieldPropertiesDto) { const config = { - label: ['', - [ - Validators.maxLength(100), - ], - ], - hints: ['', - [ - Validators.maxLength(1000), - ], - ], - placeholder: ['', - [ - Validators.maxLength(1000), - ], - ], - editor: undefined, - editorUrl: undefined, - isRequired: false, - isRequiredOnPublish: false, - isHalfWidth: false, - tags: [], + label: new FormControl('', + Validators.maxLength(100), + ), + hints: new FormControl('', + Validators.maxLength(1000), + ), + placeholder: new FormControl('', + Validators.maxLength(1000), + ), + editor: new FormControl(undefined, + Validators.nullValidator, + ), + editorUrl: new FormControl(undefined, + Validators.nullValidator, + ), + isRequired: new FormControl(false, + Validators.nullValidator, + ), + isRequiredOnPublish: new FormControl(false, + Validators.nullValidator, + ), + isHalfWidth: new FormControl(false, + Validators.nullValidator, + ), + tags: new FormControl([], + Validators.nullValidator, + ), }; - const visitor = new EditFieldFormVisitor(config); + properties.accept(new EditFieldFormVisitor(config)); - properties.accept(visitor); - - return formBuilder.group(config); + return new ExtendedFormGroup(config); } } export class EditFieldFormVisitor implements FieldPropertiesVisitor { constructor( - private readonly config: { [key: string]: any }, + private readonly config: { [key: string]: AbstractControl }, ) { } public visitArray() { - this.config['maxItems'] = undefined; - this.config['minItems'] = undefined; - this.config['uniqueFields'] = undefined; + this.config['maxItems'] = new FormControl(undefined); + this.config['minItems'] = new FormControl(undefined); + this.config['uniqueFields'] = new FormControl(undefined); } public visitAssets() { - this.config['allowDuplicates'] = undefined; - this.config['allowedExtensions'] = undefined; - this.config['aspectHeight'] = undefined; - this.config['aspectHeight'] = undefined; - this.config['aspectWidth'] = undefined; - this.config['defaultValue'] = undefined; - this.config['defaultValues'] = undefined; - this.config['expectedType'] = undefined; - this.config['folderId'] = undefined; - this.config['maxHeight'] = undefined; - this.config['maxItems'] = undefined; - this.config['maxSize'] = undefined; - this.config['maxWidth'] = undefined; - this.config['minHeight'] = undefined; - this.config['minItems'] = undefined; - this.config['minSize'] = undefined; - this.config['minWidth'] = undefined; - this.config['previewMode'] = undefined; - this.config['resolveFirst'] = undefined; + this.config['allowDuplicates'] = new FormControl(undefined); + this.config['allowedExtensions'] = new FormControl(undefined); + this.config['aspectHeight'] = new FormControl(undefined); + this.config['aspectHeight'] = new FormControl(undefined); + this.config['aspectWidth'] = new FormControl(undefined); + this.config['defaultValue'] = new FormControl(undefined); + this.config['defaultValues'] = new FormControl(undefined); + this.config['expectedType'] = new FormControl(undefined); + this.config['folderId'] = new FormControl(undefined); + this.config['maxHeight'] = new FormControl(undefined); + this.config['maxItems'] = new FormControl(undefined); + this.config['maxSize'] = new FormControl(undefined); + this.config['maxWidth'] = new FormControl(undefined); + this.config['minHeight'] = new FormControl(undefined); + this.config['minItems'] = new FormControl(undefined); + this.config['minSize'] = new FormControl(undefined); + this.config['minWidth'] = new FormControl(undefined); + this.config['previewMode'] = new FormControl(undefined); + this.config['resolveFirst'] = new FormControl(undefined); } public visitBoolean() { - this.config['inlineEditable'] = undefined; - this.config['defaultValues'] = undefined; - this.config['defaultValue'] = undefined; + this.config['inlineEditable'] = new FormControl(undefined); + this.config['defaultValues'] = new FormControl(undefined); + this.config['defaultValue'] = new FormControl(undefined); } public visitComponent() { - this.config['schemaIds'] = undefined; + this.config['schemaIds'] = new FormControl(undefined); } public visitComponents() { - this.config['schemaIds'] = undefined; - this.config['maxItems'] = undefined; - this.config['minItems'] = undefined; - this.config['uniqueFields'] = undefined; + this.config['schemaIds'] = new FormControl(undefined); + this.config['maxItems'] = new FormControl(undefined); + this.config['minItems'] = new FormControl(undefined); + this.config['uniqueFields'] = new FormControl(undefined); } public visitDateTime() { - this.config['calculatedDefaultValue'] = undefined; - this.config['defaultValue'] = undefined; - this.config['defaultValues'] = undefined; - this.config['format'] = undefined; - this.config['maxValue'] = [undefined, ValidatorsEx.validDateTime()]; - this.config['minValue'] = [undefined, ValidatorsEx.validDateTime()]; + this.config['calculatedDefaultValue'] = new FormControl(undefined); + this.config['defaultValue'] = new FormControl(undefined); + this.config['defaultValues'] = new FormControl(undefined); + this.config['format'] = new FormControl(undefined); + this.config['maxValue'] = new FormControl(undefined, ValidatorsEx.validDateTime()); + this.config['minValue'] = new FormControl(undefined, ValidatorsEx.validDateTime()); } public visitNumber() { - this.config['allowedValues'] = undefined; - this.config['defaultValue'] = undefined; - this.config['defaultValues'] = undefined; - this.config['inlineEditable'] = undefined; - this.config['isUnique'] = undefined; - this.config['maxValue'] = undefined; - this.config['minValue'] = undefined; + this.config['allowedValues'] = new FormControl(undefined); + this.config['defaultValue'] = new FormControl(undefined); + this.config['defaultValues'] = new FormControl(undefined); + this.config['inlineEditable'] = new FormControl(undefined); + this.config['isUnique'] = new FormControl(undefined); + this.config['maxValue'] = new FormControl(undefined); + this.config['minValue'] = new FormControl(undefined); } public visitReferences() { - this.config['allowDuplicates'] = undefined; - this.config['defaultValue'] = undefined; - this.config['defaultValues'] = undefined; - this.config['maxItems'] = undefined; - this.config['minItems'] = undefined; - this.config['mustBePublished'] = false; - this.config['resolveReference'] = false; - this.config['schemaIds'] = undefined; + this.config['allowDuplicates'] = new FormControl(undefined); + this.config['defaultValue'] = new FormControl(undefined); + this.config['defaultValues'] = new FormControl(undefined); + this.config['maxItems'] = new FormControl(undefined); + this.config['minItems'] = new FormControl(undefined); + this.config['mustBePublished'] = new FormControl(false); + this.config['resolveReference'] = new FormControl(false); + this.config['schemaIds'] = new FormControl(undefined); } public visitString() { - this.config['allowedValues'] = undefined; - this.config['contentType'] = undefined; - this.config['defaultValue'] = undefined; - this.config['defaultValues'] = undefined; - this.config['folderId'] = undefined; - this.config['inlineEditable'] = undefined; - this.config['isUnique'] = undefined; - this.config['maxCharacters'] = undefined; - this.config['maxLength'] = undefined; - this.config['maxWords'] = undefined; - this.config['minCharacters'] = undefined; - this.config['minLength'] = undefined; - this.config['minWords'] = undefined; - this.config['pattern'] = undefined; - this.config['patternMessage'] = undefined; + this.config['allowedValues'] = new FormControl(undefined); + this.config['contentType'] = new FormControl(undefined); + this.config['defaultValue'] = new FormControl(undefined); + this.config['defaultValues'] = new FormControl(undefined); + this.config['folderId'] = new FormControl(undefined); + this.config['inlineEditable'] = new FormControl(undefined); + this.config['isUnique'] = new FormControl(undefined); + this.config['maxCharacters'] = new FormControl(undefined); + this.config['maxLength'] = new FormControl(undefined); + this.config['maxWords'] = new FormControl(undefined); + this.config['minCharacters'] = new FormControl(undefined); + this.config['minLength'] = new FormControl(undefined); + this.config['minWords'] = new FormControl(undefined); + this.config['pattern'] = new FormControl(undefined); + this.config['patternMessage'] = new FormControl(undefined); } public visitTags() { - this.config['allowedValues'] = undefined; - this.config['defaultValue'] = undefined; - this.config['defaultValues'] = undefined; - this.config['maxItems'] = undefined; - this.config['minItems'] = undefined; + this.config['allowedValues'] = new FormControl(undefined); + this.config['defaultValue'] = new FormControl(undefined); + this.config['defaultValues'] = new FormControl(undefined); + this.config['maxItems'] = new FormControl(undefined); + this.config['minItems'] = new FormControl(undefined); } public visitGeolocation() { @@ -347,64 +359,72 @@ export class EditFieldFormVisitor implements FieldPropertiesVisitor { } } -export class EditSchemaForm extends Form { - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - label: ['', - [ - Validators.maxLength(100), - ], - ], - hints: ['', - [ - Validators.maxLength(1000), - ], - ], - contentsSidebarUrl: '', - contentSidebarUrl: '', - contentEditorUrl: '', - validateOnPublish: false, - tags: [], +export class EditSchemaForm extends Form { + constructor() { + super(new ExtendedFormGroup({ + label: new FormControl('', + Validators.maxLength(100), + ), + hints: new FormControl('', + Validators.maxLength(1000), + ), + contentsSidebarUrl: new FormControl('', + Validators.nullValidator, + ), + contentSidebarUrl: new FormControl('', + Validators.nullValidator, + ), + contentEditorUrl: new FormControl('', + Validators.nullValidator, + ), + validateOnPublish: new FormControl(false, + Validators.nullValidator, + ), + tags: new FormControl([], + Validators.nullValidator, + ), })); } } -export class AddFieldForm extends Form { - public isContentField = value$(this.form.get('type')!).pipe(map(x => x !== 'UI')); - - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - type: ['String', - [ - Validators.required, - ], - ], - name: ['', - [ - Validators.required, - Validators.maxLength(40), - ValidatorsEx.pattern('[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*', 'i18n:schemas.field.nameValidationMessage'), - ], - ], - isLocalizable: false, +export class AddFieldForm extends Form { + public isContentField = value$(this.form.controls['type']).pipe(map(x => x !== 'UI')); + + constructor() { + super(new ExtendedFormGroup({ + type: new FormControl('String', + Validators.required, + ), + name: new FormControl('', [ + Validators.required, + Validators.maxLength(40), + ValidatorsEx.pattern('[a-zA-Z0-9]+(\\-[a-zA-Z0-9]+)*', 'i18n:schemas.field.nameValidationMessage'), + ]), + isLocalizable: new FormControl(false, + Validators.nullValidator, + ), })); } public transformLoad(value: Partial) { - const isLocalizable = value.partitioning === 'language'; + const { name, properties, partitioning } = value; + + const isLocalizable = partitioning === 'language'; const type = - value.properties ? - value.properties.fieldType : + properties ? + properties.fieldType : 'String'; - return { name: value.name, isLocalizable, type }; + return { name, isLocalizable, type }; } public transformSubmit(value: any) { - const properties = createProperties(value.type); - const partitioning = value.isLocalizable ? 'language' : 'invariant'; + const { name, type, isLocalizable } = value; + + const properties = createProperties(type); + const partitioning = isLocalizable ? 'language' : 'invariant'; - return { name: value.name, partitioning, properties }; + return { name, partitioning, properties }; } } diff --git a/frontend/app/shared/state/workflows.forms.ts b/frontend/app/shared/state/workflows.forms.ts index 2c9db9539..63ea33d27 100644 --- a/frontend/app/shared/state/workflows.forms.ts +++ b/frontend/app/shared/state/workflows.forms.ts @@ -5,20 +5,22 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { Form, hasNoValue$ } from '@app/framework'; +import { FormControl, Validators } from '@angular/forms'; +import { Form, hasNoValue$, ExtendedFormGroup } from '@app/framework'; import { CreateWorkflowDto } from './../services/workflows.service'; -export class AddWorkflowForm extends Form { - public hasNoName = hasNoValue$(this.form.controls['name']); +export class AddWorkflowForm extends Form { + public get name() { + return this.form.controls['name']; + } + + public hasNoName = hasNoValue$(this.name); - constructor(formBuilder: FormBuilder) { - super(formBuilder.group({ - name: ['', - [ - Validators.required, - ], - ], + constructor() { + super(new ExtendedFormGroup({ + name: new FormControl('', + Validators.required, + ), })); } }