UI: Allow empty array in json object edit form.
This commit is contained in:
parent
94dbb1a682
commit
ece3540f96
@ -55,7 +55,7 @@
|
||||
</button>
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty">
|
||||
[disabled]="(isLoading$ | async) || invalid() || !attributeFormGroup.dirty">
|
||||
{{ 'action.add' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -72,6 +72,11 @@ export class AddAttributeDialogComponent extends DialogComponent<AddAttributeDia
|
||||
return originalErrorState || customErrorState;
|
||||
}
|
||||
|
||||
invalid(): boolean {
|
||||
const value = this.attributeFormGroup.get('value').value;
|
||||
return !Array.isArray(value) && this.attributeFormGroup.invalid;
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@
|
||||
</button>
|
||||
<button mat-button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || attributeFormGroup.invalid || !attributeFormGroup.dirty">
|
||||
[disabled]="(isLoading$ | async) || invalid() || !attributeFormGroup.dirty">
|
||||
{{ 'action.update' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -179,7 +179,7 @@
|
||||
<tb-json-object-edit
|
||||
fxFlex
|
||||
fxLayout="column"
|
||||
required
|
||||
jsonRequired
|
||||
label="{{ 'gateway.configuration' | translate }}"
|
||||
formControlName="configurationJson">
|
||||
</tb-json-object-edit>
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -26,14 +26,14 @@
|
||||
<tb-json-object-edit
|
||||
[editorStyle]="{minHeight: '100px'}"
|
||||
fillHeight="true"
|
||||
[required]="settings.attributeRequired"
|
||||
[jsonRequired]="settings.attributeRequired"
|
||||
label="{{ settings.showLabel ? labelValue : '' }}"
|
||||
formControlName="currentValue"
|
||||
(focusin)="isFocused = true;"
|
||||
(focusout)="isFocused = false;"
|
||||
></tb-json-object-edit>
|
||||
</fieldset>
|
||||
<div class="tb-json-input-form__actions" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="20px">
|
||||
<div class="tb-json-input__actions" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="20px">
|
||||
<button mat-button color="primary"
|
||||
type="button"
|
||||
[disabled]="!attributeUpdateFormGroup.dirty"
|
||||
@ -42,7 +42,7 @@
|
||||
matTooltipPosition="above">
|
||||
{{ "action.undo" | translate }}
|
||||
</button>
|
||||
<button mat-button mat-raised-button color="primary"
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="attributeUpdateFormGroup.invalid || !attributeUpdateFormGroup.dirty">
|
||||
{{ "action.save" | translate }}
|
||||
|
||||
@ -27,6 +27,10 @@
|
||||
font-size: 18px;
|
||||
color: #a0a0a0;
|
||||
}
|
||||
|
||||
&__actions {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.tb-toast {
|
||||
|
||||
@ -23,13 +23,14 @@ import { UtilsService } from '@core/services/utils.service';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
|
||||
import { IWidgetSubscription } from '@core/api/widget-api.models';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, ValidatorFn, Validators } from '@angular/forms';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, ValidatorFn } from '@angular/forms';
|
||||
import { AttributeService } from '@core/http/attribute.service';
|
||||
import { AttributeData, AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
|
||||
import { EntityId } from '@shared/models/id/entity-id';
|
||||
import { EntityType } from '@shared/models/entity-type.models';
|
||||
import { createLabelFromDatasource, isDefinedAndNotNull } from '@core/utils';
|
||||
import { Observable } from 'rxjs';
|
||||
import { jsonRequired } from '@shared/components/json-object-edit.component';
|
||||
|
||||
enum JsonInputWidgetMode {
|
||||
ATTRIBUTE = 'ATTRIBUTE',
|
||||
@ -131,7 +132,7 @@ export class JsonInputWidgetComponent extends PageComponent implements OnInit {
|
||||
private buildForm() {
|
||||
const validators: ValidatorFn[] = [];
|
||||
if (this.settings.attributeRequired) {
|
||||
validators.push(Validators.required);
|
||||
validators.push(jsonRequired);
|
||||
}
|
||||
this.attributeUpdateFormGroup = this.fb.group({
|
||||
currentValue: [{}, validators]
|
||||
@ -143,7 +144,7 @@ export class JsonInputWidgetComponent extends PageComponent implements OnInit {
|
||||
|
||||
private updateWidgetData(data: Array<DatasourceData>) {
|
||||
if (!this.errorMessage) {
|
||||
let value = {};
|
||||
let value = null;
|
||||
if (data[0].data[0][1] !== '') {
|
||||
try {
|
||||
value = JSON.parse(data[0].data[0][1]);
|
||||
|
||||
@ -91,7 +91,7 @@
|
||||
(click)="openEditJSONDialog($event, key, source)">
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
</button>
|
||||
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
|
||||
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required') && !multipleInputFormGroup.get(key.formId).hasError('invalidJSON')">
|
||||
{{ getErrorMessageText(key.settings,'required') }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('invalidJSON')">
|
||||
@ -193,7 +193,7 @@
|
||||
</button>
|
||||
<button mat-button mat-raised-button color="primary" type="submit"
|
||||
class="tb-multiple-input--buttons-container__button"
|
||||
[disabled]="!multipleInputFormGroup.dirty || multipleInputFormGroup.invalid">
|
||||
[disabled]="!multipleInputFormGroup.dirty || invalid()">
|
||||
{{ saveButtonLabel }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,7 +89,6 @@
|
||||
<ng-template matExpansionPanelContent>
|
||||
<tb-json-object-edit
|
||||
[editorStyle]="{minHeight: '100px'}"
|
||||
required
|
||||
label="{{ 'widget-config.title-style' | translate }}"
|
||||
formControlName="titleStyle"
|
||||
></tb-json-object-edit>
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
class="tb-rule-node-configuration-json"
|
||||
formControlName="configuration"
|
||||
[label]="'rulenode.configuration' | translate"
|
||||
[required]="required"
|
||||
[jsonRequired]="required"
|
||||
[fillHeight]="true">
|
||||
</tb-json-object-edit>
|
||||
</div>
|
||||
|
||||
@ -33,8 +33,7 @@
|
||||
<tb-json-object-edit
|
||||
formControlName="json"
|
||||
label="{{ 'value.json-value' | translate }}"
|
||||
validateContent="true"
|
||||
[required]="true"
|
||||
[jsonRequired]="required"
|
||||
[fillHeight]="false">
|
||||
</tb-json-object-edit>
|
||||
</fieldset>
|
||||
|
||||
@ -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<JsonObjectEdi
|
||||
saveButtonLabel = this.translate.instant('action.save');
|
||||
cancelButtonLabel = this.translate.instant('action.cancel');
|
||||
|
||||
required = this.data.required === true;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
protected router: Router,
|
||||
@Inject(MAT_DIALOG_DATA) public data: JsonObjectEditDialogData,
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,7 @@
|
||||
[fullscreen]="fullscreen" (fullscreenChanged)="onFullscreen()" fxLayout="column">
|
||||
<div fxLayout="row" fxLayoutAlign="start center" class="tb-json-object-toolbar">
|
||||
<label class="tb-title no-padding"
|
||||
[ngClass]="{'tb-required': required,
|
||||
[ngClass]="{'tb-required': jsonRequired,
|
||||
'tb-readonly': readonly,
|
||||
'tb-error': !objectValid}">{{ label }}</label>
|
||||
<span fxFlex></span>
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -67,7 +67,7 @@
|
||||
<button matSuffix mat-icon-button (click)="openEditJSONDialog($event)">
|
||||
<mat-icon>open_in_new</mat-icon>
|
||||
</button>
|
||||
<mat-error *ngIf="value.hasError('required')">
|
||||
<mat-error *ngIf="value.hasError('required') && !value.hasError('invalidJSON')">
|
||||
{{ (requiredText ? requiredText : 'value.json-value-required') | translate }}
|
||||
</mat-error>
|
||||
<mat-error *ngIf="value.hasError('invalidJSON')">
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user