diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html index 17e31854cc..56a6ca46c0 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.html @@ -55,7 +55,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts index aadb96c142..dc13dac0df 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/add-attribute-dialog.component.ts @@ -72,6 +72,11 @@ export class AddAttributeDialogComponent extends DialogComponent diff --git a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts index 539c9930db..4ce038a4ed 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/attribute/edit-attribute-value-panel.component.ts @@ -62,6 +62,11 @@ export class EditAttributeValuePanelComponent extends PageComponent implements O return originalErrorState || customErrorState; } + invalid(): boolean { + const value = this.attributeFormGroup.get('value').value; + return !Array.isArray(value) && this.attributeFormGroup.invalid; + } + cancel(): void { this.overlayRef.dispose(); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html index 6e4178fccd..f3aefedbce 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-connectors.component.html @@ -179,7 +179,7 @@ diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.ts index 9840db6efe..0314120995 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-form.component.ts @@ -286,6 +286,7 @@ export class GatewayFormComponent extends PageComponent implements OnInit, OnDes panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { jsonValue: config, + required: true, title: this.translate.instant('gateway.title-connectors-json', {typeName: type}) } }).afterClosed().subscribe( diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts index dbda5f4db2..941731f7e0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/gateway/gateway-service-rpc.component.ts @@ -31,6 +31,7 @@ import { JsonObjectEditDialogComponent, JsonObjectEditDialogData } from '@shared/components/dialog/json-object-edit-dialog.component'; +import { jsonRequired } from '@shared/components/json-object-edit.component'; @Component({ @@ -77,7 +78,7 @@ export class GatewayServiceRPCComponent extends PageComponent implements AfterVi this.commandForm = this.fb.group({ command: [null,[Validators.required]], time: [60, [Validators.required, Validators.min(1)]], - params: ["{}", [Validators.required]], + params: [{}, [jsonRequired]], result: [null] }) @@ -114,7 +115,8 @@ export class GatewayServiceRPCComponent extends PageComponent implements AfterVi disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - jsonValue: JSON.parse(this.commandForm.get('params').value) + jsonValue: JSON.parse(this.commandForm.get('params').value), + required: true } }).afterClosed().subscribe( (res) => { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html index 24fe07d92b..40636a9d1b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/json-input-widget.component.html @@ -26,14 +26,14 @@ -
+
- - + {{ getErrorMessageText(key.settings,'required') }} @@ -193,7 +193,7 @@
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts index d4c0ad2630..eb3b0c9abf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/multiple-input-widget.component.ts @@ -604,7 +604,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } public inputChanged(source: MultipleInputWidgetSource, key: MultipleInputWidgetDataKey) { - if (!this.settings.showActionButtons && !this.isSavingInProgress && this.multipleInputFormGroup.get(key.formId).valid) { + const control = this.multipleInputFormGroup.get(key.formId); + if (!this.settings.showActionButtons && !this.isSavingInProgress && (Array.isArray(control.value) || control.valid)) { this.isSavingInProgress = true; const dataToSave: MultipleInputWidgetSource = { datasource: source.datasource, @@ -775,6 +776,7 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { jsonValue: formControl.value, + required: key.settings.required, title: key.settings.dialogTitle, saveLabel: key.settings.saveButtonLabel, cancelLabel: key.settings.cancelButtonLabel @@ -791,4 +793,16 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni } ); } + + invalid(): boolean { + for (const source of this.sources) { + for (const key of this.visibleKeys(source)) { + const control = this.multipleInputFormGroup.get(key.formId); + if (!Array.isArray(control.value) && control.invalid) { + return true; + } + } + } + return false; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index 732d6e3119..cdd1d8e2da 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -89,7 +89,6 @@ diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html index 97f2214f2e..6d059fddec 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-config.component.html @@ -22,7 +22,7 @@ class="tb-rule-node-configuration-json" formControlName="configuration" [label]="'rulenode.configuration' | translate" - [required]="required" + [jsonRequired]="required" [fillHeight]="true">
diff --git a/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html index 141979d8ff..3b216bda71 100644 --- a/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.html @@ -33,8 +33,7 @@ diff --git a/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.ts b/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.ts index e55431a327..4aeed2a360 100644 --- a/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.ts +++ b/ui-ngx/src/app/shared/components/dialog/json-object-edit-dialog.component.ts @@ -26,6 +26,7 @@ import { isNotEmptyStr } from '@core/utils'; export interface JsonObjectEditDialogData { jsonValue: object; + required?: boolean; title?: string; saveLabel?: string; cancelLabel?: string; @@ -43,6 +44,8 @@ export class JsonObjectEditDialogComponent extends DialogComponent, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: JsonObjectEditDialogData, diff --git a/ui-ngx/src/app/shared/components/directives/tb-json-to-string.directive.ts b/ui-ngx/src/app/shared/components/directives/tb-json-to-string.directive.ts index 8da5fb1396..c70de1ab48 100644 --- a/ui-ngx/src/app/shared/components/directives/tb-json-to-string.directive.ts +++ b/ui-ngx/src/app/shared/components/directives/tb-json-to-string.directive.ts @@ -17,19 +17,23 @@ import { Directive, ElementRef, forwardRef, HostListener, Renderer2, SkipSelf } from '@angular/core'; import { ControlValueAccessor, - UntypedFormControl, FormGroupDirective, NG_VALIDATORS, NG_VALUE_ACCESSOR, NgForm, + UntypedFormControl, ValidationErrors, Validator } from '@angular/forms'; import { ErrorStateMatcher } from '@angular/material/core'; -import { isObject } from "@core/utils"; +import { isObject } from '@core/utils'; @Directive({ selector: '[tb-json-to-string]', + // eslint-disable-next-line @angular-eslint/no-host-metadata-property + host: { + '(blur)': 'onTouched()' + }, providers: [{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => TbJsonToStringDirective), @@ -48,18 +52,26 @@ import { isObject } from "@core/utils"; export class TbJsonToStringDirective implements ControlValueAccessor, Validator, ErrorStateMatcher { private propagateChange = null; + public onTouched = () => {}; private parseError: boolean; private data: any; @HostListener('input', ['$event.target.value']) input(newValue: any): void { try { - this.data = JSON.parse(newValue); - if (isObject(this.data)) { - this.parseError = false; + if (newValue) { + this.data = JSON.parse(newValue); + if (isObject(this.data)) { + this.parseError = false; + } else { + this.data = null; + this.parseError = true; + } } else { - this.parseError = true; + this.data = null; + this.parseError = false; } } catch (e) { + this.data = null; this.parseError = true; } @@ -73,9 +85,7 @@ export class TbJsonToStringDirective implements ControlValueAccessor, Validator, } isErrorState(control: UntypedFormControl | null, form: FormGroupDirective | NgForm | null): boolean { - const originalErrorState = this.errorStateMatcher.isErrorState(control, form); - const customErrorState = !!(control && control.invalid && this.parseError); - return originalErrorState || customErrorState; + return !!(control && control.invalid && !Array.isArray(control.value) && control.touched); } validate(c: UntypedFormControl): ValidationErrors { @@ -87,11 +97,9 @@ export class TbJsonToStringDirective implements ControlValueAccessor, Validator, } writeValue(obj: any): void { - if (obj) { - this.data = obj; - this.parseError = false; - this.render.setProperty(this.element.nativeElement, 'value', JSON.stringify(obj)); - } + this.data = obj; + this.parseError = false; + this.render.setProperty(this.element.nativeElement, 'value', obj ? JSON.stringify(obj) : ''); } registerOnChange(fn: any): void { @@ -99,5 +107,6 @@ export class TbJsonToStringDirective implements ControlValueAccessor, Validator, } registerOnTouched(fn: any): void { + this.onTouched = fn; } } diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.html b/ui-ngx/src/app/shared/components/json-object-edit.component.html index c0024da9dd..3ed828e6cf 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.html +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.html @@ -20,7 +20,7 @@ [fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column">
diff --git a/ui-ngx/src/app/shared/components/json-object-edit.component.ts b/ui-ngx/src/app/shared/components/json-object-edit.component.ts index f7e3116b0f..f89430f98b 100644 --- a/ui-ngx/src/app/shared/components/json-object-edit.component.ts +++ b/ui-ngx/src/app/shared/components/json-object-edit.component.ts @@ -24,7 +24,14 @@ import { OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, UntypedFormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator } from '@angular/forms'; +import { + ControlValueAccessor, + UntypedFormControl, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + AbstractControl, ValidationErrors +} from '@angular/forms'; import { Ace } from 'ace-builds'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ActionNotificationHide, ActionNotificationShow } from '@core/notification/notification.actions'; @@ -34,6 +41,9 @@ import { CancelAnimationFrame, RafService } from '@core/services/raf.service'; import { guid, isDefinedAndNotNull, isObject, isUndefined } from '@core/utils'; import { ResizeObserver } from '@juggle/resize-observer'; import { getAce } from '@shared/models/ace/ace.models'; +import { coerceBoolean } from '@shared/decorators/coercion'; + +export const jsonRequired = (control: AbstractControl): ValidationErrors | null => !control.value ? {required: true} : null; @Component({ selector: 'tb-json-object-edit', @@ -73,27 +83,13 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va @Input() sort: (key: string, value: any) => any; - private requiredValue: boolean; - - get required(): boolean { - return this.requiredValue; - } - + @coerceBoolean() @Input() - set required(value: boolean) { - this.requiredValue = coerceBooleanProperty(value); - } - - private readonlyValue: boolean; - - get readonly(): boolean { - return this.readonlyValue; - } + jsonRequired: boolean; + @coerceBoolean() @Input() - set readonly(value: boolean) { - this.readonlyValue = coerceBooleanProperty(value); - } + readonly: boolean; fullscreen = false; @@ -245,12 +241,10 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va try { if (isDefinedAndNotNull(this.modelValue)) { this.contentValue = JSON.stringify(this.modelValue, isUndefined(this.sort) ? undefined : - (key, objectValue) => { - return this.sort(key, objectValue); - }, 2); + (key, objectValue) => this.sort(key, objectValue), 2); this.objectValid = true; } else { - this.objectValid = !this.required; + this.objectValid = !this.jsonRequired; this.validationError = 'Json object is required.'; } } catch (e) { @@ -288,8 +282,8 @@ export class JsonObjectEditComponent implements OnInit, ControlValueAccessor, Va this.validationError = errorInfo; } } else { - this.objectValid = !this.required; - this.validationError = this.required ? 'Json object is required.' : ''; + this.objectValid = !this.jsonRequired; + this.validationError = this.jsonRequired ? 'Json object is required.' : ''; } this.modelValue = data; this.propagateChange(data); diff --git a/ui-ngx/src/app/shared/components/value-input.component.html b/ui-ngx/src/app/shared/components/value-input.component.html index 57d6954699..9f7f9d0ffc 100644 --- a/ui-ngx/src/app/shared/components/value-input.component.html +++ b/ui-ngx/src/app/shared/components/value-input.component.html @@ -67,7 +67,7 @@ - + {{ (requiredText ? requiredText : 'value.json-value-required') | translate }} diff --git a/ui-ngx/src/app/shared/components/value-input.component.ts b/ui-ngx/src/app/shared/components/value-input.component.ts index 4ba7abf157..52828cec35 100644 --- a/ui-ngx/src/app/shared/components/value-input.component.ts +++ b/ui-ngx/src/app/shared/components/value-input.component.ts @@ -15,7 +15,7 @@ /// import { Component, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgForm } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgForm, NgModel } from '@angular/forms'; import { ValueType, valueTypesMap } from '@shared/models/constants'; import { isObject } from '@core/utils'; import { MatDialog } from '@angular/material/dialog'; @@ -23,6 +23,7 @@ import { JsonObjectEditDialogComponent, JsonObjectEditDialogData } from '@shared/components/dialog/json-object-edit-dialog.component'; +import { coerceBoolean } from '@shared/decorators/coercion'; @Component({ selector: 'tb-value-input', @@ -73,7 +74,8 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - jsonValue: this.modelValue + jsonValue: this.modelValue, + required: true } }).afterClosed().subscribe( (res) => { @@ -115,7 +117,8 @@ export class ValueInputComponent implements OnInit, ControlValueAccessor { } updateView() { - if (this.inputForm.valid || this.valueType === ValueType.BOOLEAN) { + if (this.inputForm.valid || this.valueType === ValueType.BOOLEAN || + (this.valueType === ValueType.JSON && Array.isArray(this.modelValue))) { this.propagateChange(this.modelValue); } else { this.propagateChange(null);