From 3ab6b0e2b7a9216a52b5344954d015af2084080d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 10 Oct 2023 17:23:12 +0300 Subject: [PATCH] UI: Fix data keys chips validation. --- .../widget/config/data-keys.component.ts | 70 ++++++++++++++----- .../widget/widget-config.component.ts | 1 + 2 files changed, 55 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts index 02060db364..24b2f6345b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/config/data-keys.component.ts @@ -33,13 +33,13 @@ import { import { AbstractControl, ControlValueAccessor, - FormGroupDirective, + FormGroupDirective, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgForm, UntypedFormBuilder, UntypedFormControl, UntypedFormGroup, - ValidationErrors + ValidationErrors, Validator } from '@angular/forms'; import { Observable, of } from 'rxjs'; import { filter, map, mergeMap, publishReplay, refCount, share, tap } from 'rxjs/operators'; @@ -62,7 +62,7 @@ import { DataKeyConfigDialogComponent, DataKeyConfigDialogData } from '@home/components/widget/config/data-key-config-dialog.component'; -import { deepClone, guid, isDefinedAndNotNull, isUndefined } from '@core/utils'; +import { deepClone, guid, isDefinedAndNotNull, isObject, isUndefined } from '@core/utils'; import { Dashboard } from '@shared/models/dashboard.models'; import { AggregationType } from '@shared/models/time/time.models'; import { DndDropEvent } from 'ngx-drag-drop/lib/dnd-dropzone.directive'; @@ -81,15 +81,20 @@ import { TbPopoverService } from '@shared/components/popover.service'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DataKeysComponent), multi: true - } /*, + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DataKeysComponent), + multi: true, + }, { provide: ErrorStateMatcher, useExisting: DataKeysComponent - } */ + } ], encapsulation: ViewEncapsulation.None }) -export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChanges, ErrorStateMatcher { +export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChanges, ErrorStateMatcher, Validator { public get hideDataKeyLabel(): boolean { return this.datasourceComponent.hideDataKeyLabel; @@ -205,6 +210,9 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange private propagateChange = (v: any) => { }; + private keysRequired = this._keysRequired.bind(this); + private keysValidator = this._keysValidator.bind(this); + constructor(private store: Store, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, private datasourceComponent: DatasourceComponent, @@ -220,12 +228,16 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange } updateValidators() { - this.keysListFormGroup.get('keys').setValidators(this.required ? [this.keysRequired] : []); + if (this.required) { + this.keysListFormGroup.get('keys').addValidators(this.keysRequired); + } else { + this.keysListFormGroup.get('keys').removeValidators(this.keysRequired); + } this.keysListFormGroup.get('keys').updateValueAndValidity(); } - keysRequired(control: AbstractControl): ValidationErrors | null { - const value = control.value; + private _keysRequired(control: AbstractControl): ValidationErrors | null { + const value = this.modelValue; if (value && Array.isArray(value) && value.length) { return null; } else { @@ -233,8 +245,23 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange } } + private _keysValidator(control: AbstractControl): ValidationErrors | null { + const value = this.modelValue; + if (value && Array.isArray(value)) { + if (value.some(v => isObject(v) && (!v.type || !v.name))) { + return { + dataKey: true + }; + } + } + return null; + } + registerOnChange(fn: any): void { this.propagateChange = fn; + if (!this.keysListFormGroup.valid) { + this.propagateChange(this.modelValue); + } } registerOnTouched(fn: any): void { @@ -242,9 +269,12 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange ngOnInit() { this.keysListFormGroup = this.fb.group({ - keys: [null, this.required ? [this.keysRequired] : []], + keys: [null, [this.keysValidator]], key: [null] }); + if (this.required) { + this.keysListFormGroup.get('keys').addValidators(this.keysRequired); + } this.alarmKeys = []; for (const name of Object.keys(alarmFields)) { this.alarmKeys.push({ @@ -335,8 +365,8 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange } else { this.keys = []; } - this.keysListFormGroup.get('keys').setValue(this.keys); this.modelValue = this.keys.length ? [...this.keys] : null; + this.keysListFormGroup.get('keys').setValue(this.keys); if (this.keyInput) { this.keyInput.nativeElement.value = ''; } @@ -374,7 +404,7 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { const originalErrorState = this.errorStateMatcher.isErrorState(control, form); - const customErrorState = this.required && (!this.modelValue || !this.modelValue.length); + const customErrorState = this.keysListFormGroup.get('keys').hasError('dataKey'); return originalErrorState || customErrorState; } @@ -395,12 +425,20 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange this.keysListFormGroup.get('keys').setValue(this.keys); } else { this.keys = []; - this.keysListFormGroup.get('keys').setValue(this.keys); this.modelValue = null; + this.keysListFormGroup.get('keys').setValue(this.keys); } this.dirty = true; } + validate(c: UntypedFormControl) { + return (this.keysListFormGroup.get('keys').hasError('dataKey')) ? { + dataKeys: { + valid: false, + }, + } : null; + } + onFocus() { if (this.dirty) { this.keysListFormGroup.get('key').updateValueAndValidity({onlySelf: true, emitEvent: true}); @@ -441,8 +479,8 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange const index = this.keys.indexOf(key); if (index >= 0) { this.keys.splice(index, 1); - this.keysListFormGroup.get('keys').setValue(this.keys); this.modelValue.splice(index, 1); + this.keysListFormGroup.get('keys').setValue(this.keys); if (!this.modelValue.length) { this.modelValue = null; } @@ -468,8 +506,8 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange index = this.keys.length; } moveItemInArray(this.keys, this.dragIndex, index); - this.keysListFormGroup.get('keys').setValue(this.keys); moveItemInArray(this.modelValue, this.dragIndex, index); + this.keysListFormGroup.get('keys').setValue(this.keys); this.dragIndex = -1; this.propagateChange(this.modelValue); } @@ -525,8 +563,8 @@ export class DataKeysComponent implements ControlValueAccessor, OnInit, OnChange }).afterClosed().subscribe((updatedDataKey) => { if (updatedDataKey) { this.keys[index] = updatedDataKey; - this.keysListFormGroup.get('keys').setValue(this.keys); this.modelValue[index] = updatedDataKey; + this.keysListFormGroup.get('keys').setValue(this.keys); this.propagateChange(this.modelValue); } }); diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index b4495a2b95..0f569c5ce9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -435,6 +435,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe this.basicModeComponentChangeSubscription = this.basicModeComponent.widgetConfigChanged.subscribe((data) => { this.modelValue = data; this.propagateChange(this.modelValue); + this.cd.markForCheck(); }); this.cd.markForCheck(); }, 0);