Browse Source

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.
pull/489/head
Sebastian Stehle 6 years ago
committed by GitHub
parent
commit
aa49450b8b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      frontend/app/features/content/pages/content/content-page.component.ts
  2. 6
      frontend/app/features/content/shared/forms/array-item.component.ts
  3. 4
      frontend/app/features/content/shared/forms/stock-photo-editor.component.html
  4. 34
      frontend/app/features/content/shared/forms/stock-photo-editor.component.ts
  5. 2
      frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html
  6. 2
      frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html
  7. 35
      frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.ts
  8. 4
      frontend/app/framework/angular/forms/editors/autocomplete.component.ts
  9. 2
      frontend/app/framework/angular/forms/editors/dropdown.component.html
  10. 13
      frontend/app/framework/angular/forms/editors/dropdown.component.ts
  11. 6
      frontend/app/framework/angular/forms/editors/tag-editor.component.ts
  12. 46
      frontend/app/framework/angular/forms/forms-helper.spec.ts
  13. 4
      frontend/app/framework/angular/forms/forms-helper.ts
  14. 13
      frontend/app/shared/components/forms/references-dropdown.component.ts
  15. 12
      frontend/app/shared/components/forms/references-tags.component.ts
  16. 5
      frontend/app/shared/state/contents.forms.ts

4
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) { private checkPendingChanges(action: string) {
if (this.content && !this.content.canUpdateAny) {
return of(true);
}
return this.contentForm.hasChanged() ? 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?**`) : 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); of(true);

6
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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnChanges, OnDestroy, Output, QueryList, SimpleChanges, ViewChildren } from '@angular/core';
import { AbstractControl, FormGroup } from '@angular/forms'; import { AbstractControl, FormGroup } from '@angular/forms';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { startWith } from 'rxjs/operators';
import { import {
AppLanguageDto, AppLanguageDto,
@ -16,7 +15,8 @@ import {
FieldDto, FieldDto,
FieldFormatter, FieldFormatter,
invalid$, invalid$,
RootFieldDto RootFieldDto,
value$
} from '@app/shared'; } from '@app/shared';
import { FieldEditorComponent } from './field-editor.component'; import { FieldEditorComponent } from './field-editor.component';
@ -103,7 +103,7 @@ export class ArrayItemComponent implements OnChanges, OnDestroy {
this.unsubscribeFromForm(); this.unsubscribeFromForm();
this.subscription = this.subscription =
this.itemForm.valueChanges.pipe(startWith(this.itemForm.value)) value$(this.itemForm)
.subscribe(() => { .subscribe(() => {
this.updateTitle(); this.updateTitle();
}); });

4
frontend/app/features/content/shared/forms/stock-photo-editor.component.html

@ -10,8 +10,8 @@
<i class="icon-close"></i> <i class="icon-close"></i>
</button> </button>
<div *ngIf="valueThumb | async; let thumbUrl; else noThumb" class="preview"> <div *ngIf="stockPhotoThumbnail | async; let url; else noThumb" class="preview">
<img [src]="thumbUrl" /> <img [src]="url" />
</div> </div>
<ng-template #noThumb> <ng-template #noThumb>

34
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 { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, OnInit } from '@angular/core';
import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, shareReplay, startWith, switchMap, tap } from 'rxjs/operators'; import { debounceTime, map, switchMap, tap } from 'rxjs/operators';
import { import {
StatefulControlComponent, StatefulControlComponent,
StockPhotoDto, StockPhotoDto,
StockPhotoService, StockPhotoService,
thumbnail, thumbnail,
Types Types,
value$
} from '@app/shared'; } from '@app/shared';
interface State { 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 provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StockPhotoEditorComponent), multi: true
}; };
const NO_EMIT = { emitEvent: false };
@Component({ @Component({
selector: 'sqx-stock-photo-editor', selector: 'sqx-stock-photo-editor',
styleUrls: ['./stock-photo-editor.component.scss'], 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<State, string> implements OnInit { export class StockPhotoEditorComponent extends StatefulControlComponent<State, string> implements OnInit {
public valueControl = new FormControl(''); public valueControl = new FormControl('');
public valueThumb = public stockPhotoThumbnail = value$(this.valueControl).pipe(map(v => thumbnail(v, 400) || v));
this.valueControl.valueChanges.pipe(
startWith(this.valueControl.value),
shareReplay(1),
map(value => thumbnail(value, 400) || value));
public stockPhotoSearch = new FormControl(''); public stockPhotoSearch = new FormControl('');
public stockPhotos = public stockPhotos =
this.stockPhotoSearch.valueChanges.pipe( value$(this.stockPhotoSearch).pipe(
startWith(this.stockPhotoSearch.value),
distinctUntilChanged(),
debounceTime(500), debounceTime(500),
tap(query => { tap(query => {
if (query && query.length > 0) { if (query && query.length > 0) {
@ -78,8 +74,6 @@ export class StockPhotoEditorComponent extends StatefulControlComponent<State, s
} }
public ngOnInit() { public ngOnInit() {
this.own(this.valueThumb);
this.own( this.own(
this.valueControl.valueChanges this.valueControl.valueChanges
.subscribe(value => { .subscribe(value => {
@ -89,9 +83,19 @@ export class StockPhotoEditorComponent extends StatefulControlComponent<State, s
public writeValue(obj: string) { public writeValue(obj: string) {
if (Types.isString(obj)) { if (Types.isString(obj)) {
this.valueControl.setValue(obj, { emitEvent: true }); this.valueControl.setValue(obj);
} else {
this.valueControl.setValue('');
}
}
public setDisabledState(isDisabled: boolean): void {
super.setDisabledState(isDisabled);
if (isDisabled) {
this.stockPhotoSearch.disable(NO_EMIT);
} else { } else {
this.valueControl.setValue('', { emitEvent: true }); this.stockPhotoSearch.enable(NO_EMIT);
} }
} }

2
frontend/app/features/schemas/pages/schema/fields/types/assets-validation.component.html

@ -111,7 +111,7 @@
<div class="form-group2 row"> <div class="form-group2 row">
<label class="col-3 col-form-label"> <label class="col-3 col-form-label">
Allowed Extensions File Extensions
</label> </label>
<div class="col-6"> <div class="col-6">

2
frontend/app/features/schemas/pages/schema/fields/types/string-validation.component.html

@ -61,7 +61,7 @@
{{patternName}} {{patternName}}
</small> </small>
</div> </div>
<div class="form-group row" *ngIf="showPatternMessage"> <div class="form-group row" *ngIf="showPatternMessage | async">
<label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPatternMessage">Pattern Message</label> <label class="col-3 col-form-label" for="{{field.fieldId}}_fieldPatternMessage">Pattern Message</label>
<div class="col-6"> <div class="col-6">

35
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. * 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 { FormControl, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@ -13,12 +13,14 @@ import {
fadeAnimation, fadeAnimation,
FieldDto, FieldDto,
hasNoValue$, hasNoValue$,
hasValue$,
ModalModel, ModalModel,
PatternDto, PatternDto,
ResourceOwner, ResourceOwner,
RootFieldDto, RootFieldDto,
StringFieldPropertiesDto, StringFieldPropertiesDto,
Types Types,
value$
} from '@app/shared'; } from '@app/shared';
@Component({ @Component({
@ -29,7 +31,7 @@ import {
fadeAnimation fadeAnimation
] ]
}) })
export class StringValidationComponent extends ResourceOwner implements OnInit { export class StringValidationComponent extends ResourceOwner implements OnChanges, OnInit {
@Input() @Input()
public editForm: FormGroup; public editForm: FormGroup;
@ -43,7 +45,7 @@ export class StringValidationComponent extends ResourceOwner implements OnInit {
public patterns: ReadonlyArray<PatternDto>; public patterns: ReadonlyArray<PatternDto>;
public showDefaultValue: Observable<boolean>; public showDefaultValue: Observable<boolean>;
public showPatternMessage: boolean; public showPatternMessage: Observable<boolean>;
public showPatternSuggestions: Observable<boolean>; public showPatternSuggestions: Observable<boolean>;
public patternName: string; public patternName: string;
@ -80,13 +82,16 @@ export class StringValidationComponent extends ResourceOwner implements OnInit {
this.showPatternSuggestions = this.showPatternSuggestions =
hasNoValue$(this.editForm.controls['pattern']); hasNoValue$(this.editForm.controls['pattern']);
this.showPatternSuggestions =
hasNoValue$(this.editForm.controls['pattern']);
this.showPatternMessage = this.showPatternMessage =
this.editForm.controls['pattern'].value && this.editForm.controls['pattern'].value.trim().length > 0; hasValue$(this.editForm.controls['pattern']);
this.own( this.own(
this.editForm.controls['pattern'].valueChanges value$(this.editForm.controls['pattern'])
.subscribe((value: string) => { .subscribe((value: string) => {
if (!value || value.length === 0) { if (!value) {
this.editForm.controls['patternMessage'].setValue(undefined); this.editForm.controls['patternMessage'].setValue(undefined);
} }
@ -96,22 +101,28 @@ export class StringValidationComponent extends ResourceOwner implements OnInit {
this.setPatternName(); this.setPatternName();
} }
public ngOnChanges() {
this.setPatternName();
}
public setPattern(pattern: PatternDto) { public setPattern(pattern: PatternDto) {
this.patternName = pattern.name;
this.editForm.controls['pattern'].setValue(pattern.pattern); this.editForm.controls['pattern'].setValue(pattern.pattern);
this.editForm.controls['patternMessage'].setValue(pattern.message); this.editForm.controls['patternMessage'].setValue(pattern.message);
this.showPatternMessage = true;
} }
private setPatternName() { private setPatternName() {
const value = this.editForm.controls['pattern'].value;
if (!value) {
this.patternName = '';
} else {
const matchingPattern = this.patterns.find(x => x.pattern === this.editForm.controls['pattern'].value); const matchingPattern = this.patterns.find(x => x.pattern === this.editForm.controls['pattern'].value);
if (matchingPattern) { if (matchingPattern) {
this.patternName = matchingPattern.name; this.patternName = matchingPattern.name;
} else if (this.editForm.controls['pattern'].value && this.editForm.controls['pattern'].value.trim() !== '') {
this.patternName = 'Advanced';
} else { } else {
this.patternName = ''; this.patternName = 'Advanced';
}
} }
} }
} }

4
frontend/app/framework/angular/forms/editors/autocomplete.component.ts

@ -20,8 +20,6 @@ export interface AutocompleteSource {
find(query: string): Observable<ReadonlyArray<any>>; find(query: string): Observable<ReadonlyArray<any>>;
} }
const NO_EMIT = { emitEvent: false };
export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = { export const SQX_AUTOCOMPLETE_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => AutocompleteComponent), multi: true
}; };
@ -34,6 +32,8 @@ interface State {
suggestedIndex: number; suggestedIndex: number;
} }
const NO_EMIT = { emitEvent: false };
@Component({ @Component({
selector: 'sqx-autocomplete', selector: 'sqx-autocomplete',
styleUrls: ['./autocomplete.component.scss'], styleUrls: ['./autocomplete.component.scss'],

2
frontend/app/framework/angular/forms/editors/dropdown.component.html

@ -17,7 +17,7 @@
<ng-container *sqxModal="dropdown"> <ng-container *sqxModal="dropdown">
<div class="control-dropdown" [sqxAnchoredTo]="input" position="bottom-left"> <div class="control-dropdown" [sqxAnchoredTo]="input" position="bottom-left">
<div *ngIf="canSearch" class="search-form"> <div *ngIf="canSearch" class="search-form">
<input class="form-control search" [formControl]="queryInput" [disabled]="snapshot.isDisabled" placeholder="Search" (keydown)="onKeyDown($event)" sqxFocusOnInit /> <input class="form-control search" [formControl]="queryInput" placeholder="Search" (keydown)="onKeyDown($event)" sqxFocusOnInit />
</div> </div>
<div class="control-dropdown-items" #container> <div class="control-dropdown-items" #container>

13
frontend/app/framework/angular/forms/editors/dropdown.component.ts

@ -34,6 +34,8 @@ interface State {
query?: RegExp; query?: RegExp;
} }
const NO_EMIT = { emitEvent: false };
@Component({ @Component({
selector: 'sqx-dropdown', selector: 'sqx-dropdown',
styleUrls: ['./dropdown.component.scss'], styleUrls: ['./dropdown.component.scss'],
@ -134,6 +136,16 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
this.selectIndex(this.items && obj ? this.items.indexOf(obj) : 0, false); this.selectIndex(this.items && obj ? this.items.indexOf(obj) : 0, false);
} }
public setDisabledState(isDisabled: boolean): void {
super.setDisabledState(isDisabled);
if (isDisabled) {
this.queryInput.disable(NO_EMIT);
} else {
this.queryInput.enable(NO_EMIT);
}
}
public onKeyDown(event: KeyboardEvent) { public onKeyDown(event: KeyboardEvent) {
switch (event.keyCode) { switch (event.keyCode) {
case Keys.UP: case Keys.UP:
@ -208,6 +220,5 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
this.next(s => ({ ...s, selectedIndex, selectedItem: value })); this.next(s => ({ ...s, selectedIndex, selectedItem: value }));
} }
} }
} }

6
frontend/app/framework/angular/forms/editors/tag-editor.component.ts

@ -139,6 +139,8 @@ interface State {
items: ReadonlyArray<TagValue>; items: ReadonlyArray<TagValue>;
} }
const NO_EMIT = { emitEvent: false };
@Component({ @Component({
selector: 'sqx-tag-editor', selector: 'sqx-tag-editor',
styleUrls: ['./tag-editor.component.scss'], styleUrls: ['./tag-editor.component.scss'],
@ -302,9 +304,9 @@ export class TagEditorComponent extends StatefulControlComponent<State, Readonly
super.setDisabledState(isDisabled); super.setDisabledState(isDisabled);
if (isDisabled) { if (isDisabled) {
this.addInput.disable(); this.addInput.disable(NO_EMIT);
} else { } else {
this.addInput.enable(); this.addInput.enable(NO_EMIT);
} }
} }

46
frontend/app/framework/angular/forms/forms-helper.spec.ts

@ -0,0 +1,46 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { FormControl, Validators } from '@angular/forms';
import { value$ } from './forms-helper';
describe('FormHelpers', () => {
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']);
});
});
});

4
frontend/app/framework/angular/forms/forms-helper.ts

@ -7,7 +7,7 @@
import { AbstractControl, FormArray, FormGroup } from '@angular/forms'; import { AbstractControl, FormArray, FormGroup } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { distinctUntilChanged, map, startWith } from 'rxjs/operators'; import { distinctUntilChanged, filter, map, startWith } from 'rxjs/operators';
import { Types } from './../../utils/types'; import { Types } from './../../utils/types';
@ -26,7 +26,7 @@ export function invalid$(form: AbstractControl): Observable<boolean> {
} }
export function value$<T = any>(form: AbstractControl): Observable<T> { export function value$<T = any>(form: AbstractControl): Observable<T> {
return form.valueChanges.pipe(startWith(form.value)); return form.valueChanges.pipe(startWith(form.value), filter(_ => form.enabled), distinctUntilChanged());
} }
export function hasValue$(form: AbstractControl): Observable<boolean> { export function hasValue$(form: AbstractControl): Observable<boolean> {

13
frontend/app/shared/components/forms/references-dropdown.component.ts

@ -16,7 +16,8 @@ import {
LanguageDto, LanguageDto,
StatefulControlComponent, StatefulControlComponent,
Types, Types,
UIOptions UIOptions,
value$
} from '@app/shared/internal'; } from '@app/shared/internal';
export const SQX_REFERENCES_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { export const SQX_REFERENCES_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
@ -83,7 +84,7 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
this.itemCount = uiOptions.get('referencesDropdownItemCount'); this.itemCount = uiOptions.get('referencesDropdownItemCount');
this.own( this.own(
this.selectionControl.valueChanges value$(this.selectionControl)
.subscribe((value: ContentName) => { .subscribe((value: ContentName) => {
if (value && value.id) { if (value && value.id) {
this.callTouched(); this.callTouched();
@ -119,19 +120,19 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
this.selectContent(); this.selectContent();
}, () => { }, () => {
this.selectionControl.disable(); this.selectionControl.disable(NO_EMIT);
}); });
} else { } else {
this.selectionControl.disable(); this.selectionControl.disable(NO_EMIT);
} }
} }
} }
public setDisabledState(isDisabled: boolean) { public setDisabledState(isDisabled: boolean) {
if (isDisabled) { if (isDisabled) {
this.selectionControl.disable(); this.selectionControl.disable(NO_EMIT);
} else if (this.isValid) { } else if (this.isValid) {
this.selectionControl.enable(); this.selectionControl.enable(NO_EMIT);
} }
super.setDisabledState(isDisabled); super.setDisabledState(isDisabled);

12
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 provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesTagsComponent), multi: true
}; };
const NO_EMIT = { emitEvent: false };
class TagsConverter implements Converter { class TagsConverter implements Converter {
public suggestions: ReadonlyArray<TagValue> = []; public suggestions: ReadonlyArray<TagValue> = [];
@ -70,6 +68,8 @@ interface State {
converter: TagsConverter; converter: TagsConverter;
} }
const NO_EMIT = { emitEvent: false };
@Component({ @Component({
selector: 'sqx-references-tags', selector: 'sqx-references-tags',
styleUrls: ['./references-tags.component.scss'], styleUrls: ['./references-tags.component.scss'],
@ -141,9 +141,9 @@ export class ReferencesTagsComponent extends StatefulControlComponent<State, Rea
public setDisabledState(isDisabled: boolean) { public setDisabledState(isDisabled: boolean) {
if (isDisabled) { if (isDisabled) {
this.selectionControl.disable(); this.selectionControl.disable(NO_EMIT);
} else if (this.isValid) { } else if (this.isValid) {
this.selectionControl.enable(); this.selectionControl.enable(NO_EMIT);
} }
super.setDisabledState(isDisabled); super.setDisabledState(isDisabled);
@ -159,11 +159,11 @@ export class ReferencesTagsComponent extends StatefulControlComponent<State, Rea
if (this.isValid && this.contentItems && this.contentItems.length > 0) { if (this.isValid && this.contentItems && this.contentItems.length > 0) {
converter = new TagsConverter(this.language, this.contentItems); converter = new TagsConverter(this.language, this.contentItems);
this.selectionControl.enable(); this.selectionControl.enable(NO_EMIT);
} else { } else {
converter = new TagsConverter(null!, []); converter = new TagsConverter(null!, []);
this.selectionControl.disable(); this.selectionControl.disable(NO_EMIT);
} }
this.next({ converter }); this.next({ converter });

5
frontend/app/shared/state/contents.forms.ts

@ -15,7 +15,8 @@ import {
Form, Form,
formControls, formControls,
Types, Types,
ValidatorsEx ValidatorsEx,
value$
} from '@app/framework'; } from '@app/framework';
import { AppLanguageDto } from './../services/app-languages.service'; import { AppLanguageDto } from './../services/app-languages.service';
@ -468,7 +469,7 @@ export class EditContentForm extends Form<FormGroup, any> {
) { ) {
super(new FormGroup({})); super(new FormGroup({}));
this.form.valueChanges.subscribe(value => { value$(this.form).subscribe(value => {
this.value.next(value); this.value.next(value);
}); });

Loading…
Cancel
Save