From aa49450b8bec29a4ccc15af1a2ff2d95fd1a67e2 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Tue, 11 Feb 2020 19:20:48 +0100 Subject: [PATCH] Fix/value changes (#487) * Fix for value changes * References # Conflicts: # frontend/app/features/content/shared/forms/array-item.component.ts * More fixes for value changes. --- .../pages/content/content-page.component.ts | 4 ++ .../shared/forms/array-item.component.ts | 6 +-- .../forms/stock-photo-editor.component.html | 4 +- .../forms/stock-photo-editor.component.ts | 34 ++++++++------ .../types/assets-validation.component.html | 2 +- .../types/string-validation.component.html | 2 +- .../types/string-validation.component.ts | 41 +++++++++++------ .../forms/editors/autocomplete.component.ts | 4 +- .../forms/editors/dropdown.component.html | 2 +- .../forms/editors/dropdown.component.ts | 13 +++++- .../forms/editors/tag-editor.component.ts | 6 ++- .../angular/forms/forms-helper.spec.ts | 46 +++++++++++++++++++ .../framework/angular/forms/forms-helper.ts | 4 +- .../forms/references-dropdown.component.ts | 13 +++--- .../forms/references-tags.component.ts | 12 ++--- frontend/app/shared/state/contents.forms.ts | 5 +- 16 files changed, 139 insertions(+), 59 deletions(-) create mode 100644 frontend/app/framework/angular/forms/forms-helper.spec.ts diff --git a/frontend/app/features/content/pages/content/content-page.component.ts b/frontend/app/features/content/pages/content/content-page.component.ts index ae03e9975b..22f10e7b0d 100644 --- a/frontend/app/features/content/pages/content/content-page.component.ts +++ b/frontend/app/features/content/pages/content/content-page.component.ts @@ -282,6 +282,10 @@ export class ContentPageComponent extends ResourceOwner implements CanComponentD } private checkPendingChanges(action: string) { + if (this.content && !this.content.canUpdateAny) { + return of(true); + } + return this.contentForm.hasChanged() ? this.dialogs.confirm('Unsaved changes', `You have unsaved changes.\n\nWhen you ${action} you will loose them.\n\n**Do you want to continue anyway?**`) : of(true); 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 1feaee0567..e9731a8517 100644 --- a/frontend/app/features/content/shared/forms/array-item.component.ts +++ b/frontend/app/features/content/shared/forms/array-item.component.ts @@ -8,7 +8,6 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core'; import { AbstractControl, FormGroup } from '@angular/forms'; import { Observable, Subscription } from 'rxjs'; -import { startWith } from 'rxjs/operators'; import { AppLanguageDto, @@ -16,7 +15,8 @@ import { FieldDto, FieldFormatter, invalid$, - RootFieldDto + RootFieldDto, + value$ } from '@app/shared'; import { FieldEditorComponent } from './field-editor.component'; @@ -103,7 +103,7 @@ export class ArrayItemComponent implements OnChanges, OnDestroy { this.unsubscribeFromForm(); this.subscription = - this.itemForm.valueChanges.pipe(startWith(this.itemForm.value)) + value$(this.itemForm) .subscribe(() => { this.updateTitle(); }); diff --git a/frontend/app/features/content/shared/forms/stock-photo-editor.component.html b/frontend/app/features/content/shared/forms/stock-photo-editor.component.html index 534e3cbc7d..2b729412bf 100644 --- a/frontend/app/features/content/shared/forms/stock-photo-editor.component.html +++ b/frontend/app/features/content/shared/forms/stock-photo-editor.component.html @@ -10,8 +10,8 @@ -
- +
+
diff --git a/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts b/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts index a6d5e18f58..b352ec3d6f 100644 --- a/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts +++ b/frontend/app/features/content/shared/forms/stock-photo-editor.component.ts @@ -8,14 +8,15 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, OnInit } from '@angular/core'; import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { of } from 'rxjs'; -import { debounceTime, distinctUntilChanged, map, shareReplay, startWith, switchMap, tap } from 'rxjs/operators'; +import { debounceTime, map, switchMap, tap } from 'rxjs/operators'; import { StatefulControlComponent, StockPhotoDto, StockPhotoService, thumbnail, - Types + Types, + value$ } from '@app/shared'; interface State { @@ -30,6 +31,8 @@ export const SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StockPhotoEditorComponent), multi: true }; +const NO_EMIT = { emitEvent: false }; + @Component({ selector: 'sqx-stock-photo-editor', styleUrls: ['./stock-photo-editor.component.scss'], @@ -42,18 +45,11 @@ export const SQX_STOCK_PHOTO_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export class StockPhotoEditorComponent extends StatefulControlComponent implements OnInit { public valueControl = new FormControl(''); - public valueThumb = - this.valueControl.valueChanges.pipe( - startWith(this.valueControl.value), - shareReplay(1), - map(value => thumbnail(value, 400) || value)); - + public stockPhotoThumbnail = value$(this.valueControl).pipe(map(v => thumbnail(v, 400) || v)); public stockPhotoSearch = new FormControl(''); public stockPhotos = - this.stockPhotoSearch.valueChanges.pipe( - startWith(this.stockPhotoSearch.value), - distinctUntilChanged(), + value$(this.stockPhotoSearch).pipe( debounceTime(500), tap(query => { if (query && query.length > 0) { @@ -78,8 +74,6 @@ export class StockPhotoEditorComponent extends StatefulControlComponent { @@ -89,9 +83,19 @@ export class StockPhotoEditorComponent extends StatefulControlComponent
diff --git a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html index 4d17ab9f47..bf9bafc52a 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html @@ -61,7 +61,7 @@

Suggestions

{{patternName}}
-
+
diff --git a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts index ea8f1610ee..9844620992 100644 --- a/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts +++ b/frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; @@ -13,12 +13,14 @@ import { fadeAnimation, FieldDto, hasNoValue$, + hasValue$, ModalModel, PatternDto, ResourceOwner, RootFieldDto, StringFieldPropertiesDto, - Types + Types, + value$ } from '@app/shared'; @Component({ @@ -29,7 +31,7 @@ import { fadeAnimation ] }) -export class StringValidationComponent extends ResourceOwner implements OnInit { +export class StringValidationComponent extends ResourceOwner implements OnChanges, OnInit { @Input() public editForm: FormGroup; @@ -43,7 +45,7 @@ export class StringValidationComponent extends ResourceOwner implements OnInit { public patterns: ReadonlyArray; public showDefaultValue: Observable; - public showPatternMessage: boolean; + public showPatternMessage: Observable; public showPatternSuggestions: Observable; public patternName: string; @@ -80,13 +82,16 @@ export class StringValidationComponent extends ResourceOwner implements OnInit { this.showPatternSuggestions = hasNoValue$(this.editForm.controls['pattern']); + this.showPatternSuggestions = + hasNoValue$(this.editForm.controls['pattern']); + this.showPatternMessage = - this.editForm.controls['pattern'].value && this.editForm.controls['pattern'].value.trim().length > 0; + hasValue$(this.editForm.controls['pattern']); this.own( - this.editForm.controls['pattern'].valueChanges + value$(this.editForm.controls['pattern']) .subscribe((value: string) => { - if (!value || value.length === 0) { + if (!value) { this.editForm.controls['patternMessage'].setValue(undefined); } @@ -96,22 +101,28 @@ export class StringValidationComponent extends ResourceOwner implements OnInit { this.setPatternName(); } + public ngOnChanges() { + this.setPatternName(); + } + public setPattern(pattern: PatternDto) { - this.patternName = pattern.name; this.editForm.controls['pattern'].setValue(pattern.pattern); this.editForm.controls['patternMessage'].setValue(pattern.message); - this.showPatternMessage = true; } private setPatternName() { - const matchingPattern = this.patterns.find(x => x.pattern === this.editForm.controls['pattern'].value); + const value = this.editForm.controls['pattern'].value; - if (matchingPattern) { - this.patternName = matchingPattern.name; - } else if (this.editForm.controls['pattern'].value && this.editForm.controls['pattern'].value.trim() !== '') { - this.patternName = 'Advanced'; - } else { + if (!value) { this.patternName = ''; + } else { + const matchingPattern = this.patterns.find(x => x.pattern === this.editForm.controls['pattern'].value); + + if (matchingPattern) { + this.patternName = matchingPattern.name; + } else { + this.patternName = 'Advanced'; + } } } } \ No newline at end of file diff --git a/frontend/app/framework/angular/forms/editors/autocomplete.component.ts b/frontend/app/framework/angular/forms/editors/autocomplete.component.ts index 2201cb176a..60829b82c9 100644 --- a/frontend/app/framework/angular/forms/editors/autocomplete.component.ts +++ b/frontend/app/framework/angular/forms/editors/autocomplete.component.ts @@ -20,8 +20,6 @@ export interface AutocompleteSource { find(query: string): Observable>; } -const NO_EMIT = { emitEvent: false }; - export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true }; @@ -34,6 +32,8 @@ interface State { suggestedIndex: number; } +const NO_EMIT = { emitEvent: false }; + @Component({ selector: 'sqx-autocomplete', styleUrls: ['./autocomplete.component.scss'], diff --git a/frontend/app/framework/angular/forms/editors/dropdown.component.html b/frontend/app/framework/angular/forms/editors/dropdown.component.html index a2a3c422dc..4d065e9130 100644 --- a/frontend/app/framework/angular/forms/editors/dropdown.component.html +++ b/frontend/app/framework/angular/forms/editors/dropdown.component.html @@ -17,7 +17,7 @@
- +
diff --git a/frontend/app/framework/angular/forms/editors/dropdown.component.ts b/frontend/app/framework/angular/forms/editors/dropdown.component.ts index 5382b164ae..fd32c8b8cc 100644 --- a/frontend/app/framework/angular/forms/editors/dropdown.component.ts +++ b/frontend/app/framework/angular/forms/editors/dropdown.component.ts @@ -34,6 +34,8 @@ interface State { query?: RegExp; } +const NO_EMIT = { emitEvent: false }; + @Component({ selector: 'sqx-dropdown', styleUrls: ['./dropdown.component.scss'], @@ -134,6 +136,16 @@ export class DropdownComponent extends StatefulControlComponent ({ ...s, selectedIndex, selectedItem: value })); } - } } \ No newline at end of file diff --git a/frontend/app/framework/angular/forms/editors/tag-editor.component.ts b/frontend/app/framework/angular/forms/editors/tag-editor.component.ts index f373a0b188..0a12cfb860 100644 --- a/frontend/app/framework/angular/forms/editors/tag-editor.component.ts +++ b/frontend/app/framework/angular/forms/editors/tag-editor.component.ts @@ -139,6 +139,8 @@ interface State { items: ReadonlyArray; } +const NO_EMIT = { emitEvent: false }; + @Component({ selector: 'sqx-tag-editor', styleUrls: ['./tag-editor.component.scss'], @@ -302,9 +304,9 @@ export class TagEditorComponent extends StatefulControlComponent { + describe('value$', () => { + it('should provide change values', () => { + const form = new FormControl('1', Validators.required); + + const values: any[] = []; + + value$(form).subscribe(x => { + values.push(x); + }); + + form.setValue('2'); + form.setValue('3'); + + expect(values).toEqual(['1', '2', '3']); + }); + + it('should not trigger on disable', () => { + const form = new FormControl('1', Validators.required); + + const values: any[] = []; + + value$(form).subscribe(x => { + values.push(x); + }); + + form.setValue('2'); + form.enable(); + form.setValue('3'); + form.disable(); + + expect(values).toEqual(['1', '2', '3']); + }); + }); +}); \ No newline at end of file diff --git a/frontend/app/framework/angular/forms/forms-helper.ts b/frontend/app/framework/angular/forms/forms-helper.ts index fb752c147d..b431fc0ee6 100644 --- a/frontend/app/framework/angular/forms/forms-helper.ts +++ b/frontend/app/framework/angular/forms/forms-helper.ts @@ -7,7 +7,7 @@ import { AbstractControl, FormArray, FormGroup } from '@angular/forms'; import { Observable } from 'rxjs'; -import { distinctUntilChanged, map, startWith } from 'rxjs/operators'; +import { distinctUntilChanged, filter, map, startWith } from 'rxjs/operators'; import { Types } from './../../utils/types'; @@ -26,7 +26,7 @@ export function invalid$(form: AbstractControl): Observable { } export function value$(form: AbstractControl): Observable { - return form.valueChanges.pipe(startWith(form.value)); + return form.valueChanges.pipe(startWith(form.value), filter(_ => form.enabled), distinctUntilChanged()); } export function hasValue$(form: AbstractControl): Observable { diff --git a/frontend/app/shared/components/forms/references-dropdown.component.ts b/frontend/app/shared/components/forms/references-dropdown.component.ts index 60d6a9122b..524bc216dd 100644 --- a/frontend/app/shared/components/forms/references-dropdown.component.ts +++ b/frontend/app/shared/components/forms/references-dropdown.component.ts @@ -16,7 +16,8 @@ import { LanguageDto, StatefulControlComponent, Types, - UIOptions + UIOptions, + value$ } from '@app/shared/internal'; export const SQX_REFERENCES_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { @@ -83,7 +84,7 @@ export class ReferencesDropdownComponent extends StatefulControlComponent { if (value && value.id) { this.callTouched(); @@ -119,19 +120,19 @@ export class ReferencesDropdownComponent extends StatefulControlComponent { - this.selectionControl.disable(); + this.selectionControl.disable(NO_EMIT); }); } else { - this.selectionControl.disable(); + this.selectionControl.disable(NO_EMIT); } } } public setDisabledState(isDisabled: boolean) { if (isDisabled) { - this.selectionControl.disable(); + this.selectionControl.disable(NO_EMIT); } else if (this.isValid) { - this.selectionControl.enable(); + this.selectionControl.enable(NO_EMIT); } super.setDisabledState(isDisabled); diff --git a/frontend/app/shared/components/forms/references-tags.component.ts b/frontend/app/shared/components/forms/references-tags.component.ts index 8bab938a49..c3cdf5afc3 100644 --- a/frontend/app/shared/components/forms/references-tags.component.ts +++ b/frontend/app/shared/components/forms/references-tags.component.ts @@ -24,8 +24,6 @@ export const SQX_REFERENCES_TAGS_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesTagsComponent), multi: true }; -const NO_EMIT = { emitEvent: false }; - class TagsConverter implements Converter { public suggestions: ReadonlyArray = []; @@ -70,6 +68,8 @@ interface State { converter: TagsConverter; } +const NO_EMIT = { emitEvent: false }; + @Component({ selector: 'sqx-references-tags', styleUrls: ['./references-tags.component.scss'], @@ -141,9 +141,9 @@ export class ReferencesTagsComponent extends StatefulControlComponent 0) { converter = new TagsConverter(this.language, this.contentItems); - this.selectionControl.enable(); + this.selectionControl.enable(NO_EMIT); } else { converter = new TagsConverter(null!, []); - this.selectionControl.disable(); + this.selectionControl.disable(NO_EMIT); } this.next({ converter }); diff --git a/frontend/app/shared/state/contents.forms.ts b/frontend/app/shared/state/contents.forms.ts index 20995de455..a698e29029 100644 --- a/frontend/app/shared/state/contents.forms.ts +++ b/frontend/app/shared/state/contents.forms.ts @@ -15,7 +15,8 @@ import { Form, formControls, Types, - ValidatorsEx + ValidatorsEx, + value$ } from '@app/framework'; import { AppLanguageDto } from './../services/app-languages.service'; @@ -468,7 +469,7 @@ export class EditContentForm extends Form { ) { super(new FormGroup({})); - this.form.valueChanges.subscribe(value => { + value$(this.form).subscribe(value => { this.value.next(value); });