UI: Improvement multiple attribute widget
This commit is contained in:
parent
783552f6c3
commit
49a9a3136f
File diff suppressed because one or more lines are too long
@ -114,6 +114,10 @@ export function isNumeric(value: any): boolean {
|
|||||||
return (value - parseFloat(value) + 1) >= 0;
|
return (value - parseFloat(value) + 1) >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isBoolean(value: any): boolean {
|
||||||
|
return typeof value === 'boolean';
|
||||||
|
}
|
||||||
|
|
||||||
export function isString(value: any): boolean {
|
export function isString(value: any): boolean {
|
||||||
return typeof value === 'string';
|
return typeof value === 'string';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,12 @@
|
|||||||
type="text"
|
type="text"
|
||||||
(focus)="key.isFocused = true; focusInputElement($event)"
|
(focus)="key.isFocused = true; focusInputElement($event)"
|
||||||
(blur)="key.isFocused = false; inputChanged(source, key)">
|
(blur)="key.isFocused = false; inputChanged(source, key)">
|
||||||
<mat-icon *ngIf="key.settings.icon" matPrefix>{{key.settings.icon}}</mat-icon>
|
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matPrefix>
|
||||||
|
<mat-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</mat-icon>
|
||||||
|
<ng-template #customToggleIcon>
|
||||||
|
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
|
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
|
||||||
{{ getErrorMessageText(key.settings, 'required') }}
|
{{ getErrorMessageText(key.settings, 'required') }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
@ -57,7 +62,12 @@
|
|||||||
max="{{key.settings.maxValue}}"
|
max="{{key.settings.maxValue}}"
|
||||||
(focus)="key.isFocused = true; focusInputElement($event)"
|
(focus)="key.isFocused = true; focusInputElement($event)"
|
||||||
(blur)="key.isFocused = false; inputChanged(source, key)">
|
(blur)="key.isFocused = false; inputChanged(source, key)">
|
||||||
<mat-icon *ngIf="key.settings.icon" matPrefix>{{key.settings.icon}}</mat-icon>
|
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon" matPrefix>
|
||||||
|
<mat-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</mat-icon>
|
||||||
|
<ng-template #customToggleIcon>
|
||||||
|
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
|
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')">
|
||||||
{{ getErrorMessageText(key.settings,'required') }}
|
{{ getErrorMessageText(key.settings,'required') }}
|
||||||
</mat-error>
|
</mat-error>
|
||||||
@ -77,7 +87,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="input-field mat-block" *ngIf="key.settings.dataKeyValueType === 'booleanSwitch'">
|
<div class="input-field mat-block" *ngIf="key.settings.dataKeyValueType === 'booleanSwitch'">
|
||||||
<mat-slide-toggle formControlName="{{key.formId}}"
|
<mat-slide-toggle formControlName="{{key.formId}}"
|
||||||
|
[labelPosition]="key.settings.slideToggleLabelPosition"
|
||||||
(change)="inputChanged(source, key)">
|
(change)="inputChanged(source, key)">
|
||||||
|
<ng-container *ngIf="key.settings.icon || key.settings.safeCustomIcon">
|
||||||
|
<mat-icon *ngIf="!key.settings.safeCustomIcon; else customToggleIcon">{{key.settings.icon}}</mat-icon>
|
||||||
|
<ng-template #customToggleIcon>
|
||||||
|
<img class="mat-icon" [src]="key.settings.safeCustomIcon" alt="icon">
|
||||||
|
</ng-template>
|
||||||
|
</ng-container>
|
||||||
<span class="label-wrapper">{{key.label}}</span>
|
<span class="label-wrapper">{{key.label}}</span>
|
||||||
</mat-slide-toggle>
|
</mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -64,3 +64,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:host ::ng-deep {
|
||||||
|
.tb-multiple-input {
|
||||||
|
.mat-slide-toggle-content {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.mat-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -24,7 +24,14 @@ import { UtilsService } from '@core/services/utils.service';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
|
import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models';
|
||||||
import { IWidgetSubscription } from '@core/api/widget-api.models';
|
import { IWidgetSubscription } from '@core/api/widget-api.models';
|
||||||
import { createLabelFromDatasource, isDefinedAndNotNull, isEqual, isNotEmptyStr, isUndefined } from '@core/utils';
|
import {
|
||||||
|
createLabelFromDatasource,
|
||||||
|
isBoolean, isDefined,
|
||||||
|
isDefinedAndNotNull,
|
||||||
|
isEqual,
|
||||||
|
isNotEmptyStr,
|
||||||
|
isUndefined
|
||||||
|
} from '@core/utils';
|
||||||
import { EntityType } from '@shared/models/entity-type.models';
|
import { EntityType } from '@shared/models/entity-type.models';
|
||||||
import * as _moment from 'moment';
|
import * as _moment from 'moment';
|
||||||
import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
|
import { FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms';
|
||||||
@ -35,6 +42,7 @@ import { forkJoin, Observable, Subject } from 'rxjs';
|
|||||||
import { EntityId } from '@shared/models/id/entity-id';
|
import { EntityId } from '@shared/models/id/entity-id';
|
||||||
import { ResizeObserver } from '@juggle/resize-observer';
|
import { ResizeObserver } from '@juggle/resize-observer';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||||
|
|
||||||
type FieldAlignment = 'row' | 'column';
|
type FieldAlignment = 'row' | 'column';
|
||||||
|
|
||||||
@ -44,6 +52,9 @@ type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' |
|
|||||||
'dateTime' | 'date' | 'time' | 'select';
|
'dateTime' | 'date' | 'time' | 'select';
|
||||||
type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly';
|
type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly';
|
||||||
|
|
||||||
|
type ConvertGetValueFunction = (value: any, ctx: WidgetContext) => any;
|
||||||
|
type ConvertSetValueFunction = (value: any, originValue: any, ctx: WidgetContext) => any;
|
||||||
|
|
||||||
interface MultipleInputWidgetSettings {
|
interface MultipleInputWidgetSettings {
|
||||||
widgetTitle: string;
|
widgetTitle: string;
|
||||||
showActionButtons: boolean;
|
showActionButtons: boolean;
|
||||||
@ -66,6 +77,7 @@ interface MultipleInputWidgetSelectOption {
|
|||||||
interface MultipleInputWidgetDataKeySettings {
|
interface MultipleInputWidgetDataKeySettings {
|
||||||
dataKeyType: MultipleInputWidgetDataKeyType;
|
dataKeyType: MultipleInputWidgetDataKeyType;
|
||||||
dataKeyValueType: MultipleInputWidgetDataKeyValueType;
|
dataKeyValueType: MultipleInputWidgetDataKeyValueType;
|
||||||
|
slideToggleLabelPosition?: 'after' | 'before';
|
||||||
selectOptions: MultipleInputWidgetSelectOption[];
|
selectOptions: MultipleInputWidgetSelectOption[];
|
||||||
required: boolean;
|
required: boolean;
|
||||||
isEditable: MultipleInputWidgetDataKeyEditableType;
|
isEditable: MultipleInputWidgetDataKeyEditableType;
|
||||||
@ -78,10 +90,19 @@ interface MultipleInputWidgetDataKeySettings {
|
|||||||
invalidDateErrorMessage?: string;
|
invalidDateErrorMessage?: string;
|
||||||
minValueErrorMessage?: string;
|
minValueErrorMessage?: string;
|
||||||
maxValueErrorMessage?: string;
|
maxValueErrorMessage?: string;
|
||||||
|
useCustomIcon: boolean;
|
||||||
icon: string;
|
icon: string;
|
||||||
|
customIcon: string ;
|
||||||
|
safeCustomIcon?: SafeUrl;
|
||||||
inputTypeNumber?: boolean;
|
inputTypeNumber?: boolean;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
disabledOnCondition?: boolean;
|
disabledOnCondition?: boolean;
|
||||||
|
useGetValueFunction?: boolean;
|
||||||
|
getValueFunctionBody?: string;
|
||||||
|
getValueFunction?: ConvertGetValueFunction;
|
||||||
|
useSetValueFunction?: boolean;
|
||||||
|
setValueFunctionBody?: string;
|
||||||
|
setValueFunction?: ConvertSetValueFunction;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MultipleInputWidgetDataKey extends DataKey {
|
interface MultipleInputWidgetDataKey extends DataKey {
|
||||||
@ -139,7 +160,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
|
|||||||
private utils: UtilsService,
|
private utils: UtilsService,
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
private attributeService: AttributeService,
|
private attributeService: AttributeService,
|
||||||
private translate: TranslateService) {
|
private translate: TranslateService,
|
||||||
|
private sanitizer: DomSanitizer) {
|
||||||
super(store);
|
super(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -265,6 +287,34 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dataKey.settings.dataKeyValueType === 'booleanSwitch' && isUndefined(dataKey.settings.slideToggleLabelPosition)) {
|
||||||
|
dataKey.settings.slideToggleLabelPosition = 'after';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataKey.settings.useCustomIcon && isDefinedAndNotNull(dataKey.settings.customIcon)) {
|
||||||
|
dataKey.settings.safeCustomIcon = this.sanitizer.bypassSecurityTrustUrl(dataKey.settings.customIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataKey.settings.useGetValueFunction && dataKey.settings.getValueFunctionBody.length) {
|
||||||
|
try {
|
||||||
|
dataKey.settings.getValueFunction =
|
||||||
|
new Function('value, ctx', dataKey.settings.getValueFunctionBody) as ConvertGetValueFunction;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Parse getValue function in key ${dataKey.label}`, e);
|
||||||
|
dataKey.settings.getValueFunction = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataKey.settings.useSetValueFunction && dataKey.settings.setValueFunctionBody.length) {
|
||||||
|
try {
|
||||||
|
dataKey.settings.setValueFunction =
|
||||||
|
new Function('value, originValue, ctx', dataKey.settings.setValueFunctionBody) as ConvertSetValueFunction;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Parse setValue function in key ${dataKey.label}`, e);
|
||||||
|
dataKey.settings.setValueFunction = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
source.keys.push(dataKey);
|
source.keys.push(dataKey);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -342,28 +392,41 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
|
|||||||
source.keys.forEach((key) => {
|
source.keys.forEach((key) => {
|
||||||
const keyData = data[dataIndex].data;
|
const keyData = data[dataIndex].data;
|
||||||
if (keyData && keyData.length) {
|
if (keyData && keyData.length) {
|
||||||
let value;
|
let value = keyData[0][1];
|
||||||
|
const keyValue = this.getKeyValue(value, key.settings);
|
||||||
switch (key.settings.dataKeyValueType) {
|
switch (key.settings.dataKeyValueType) {
|
||||||
case 'dateTime':
|
case 'dateTime':
|
||||||
case 'date':
|
case 'date':
|
||||||
if (isDefinedAndNotNull(keyData[0][1]) && keyData[0][1] !== '') {
|
if (isDefinedAndNotNull(keyValue) && keyValue !== '') {
|
||||||
value = _moment(keyData[0][1]).toDate();
|
if (keyValue instanceof Date) {
|
||||||
|
value = keyValue;
|
||||||
|
} else {
|
||||||
|
value = _moment(keyValue).toDate();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
value = null;
|
value = null;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'time':
|
case 'time':
|
||||||
value = _moment().startOf('day').add(keyData[0][1], 'ms').toDate();
|
if (keyValue instanceof Date) {
|
||||||
|
value = keyValue;
|
||||||
|
} else {
|
||||||
|
value = _moment().startOf('day').add(keyValue, 'ms').toDate();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'booleanCheckbox':
|
case 'booleanCheckbox':
|
||||||
case 'booleanSwitch':
|
case 'booleanSwitch':
|
||||||
value = (keyData[0][1] === 'true');
|
if (isBoolean(keyValue)) {
|
||||||
|
value = keyValue;
|
||||||
|
} else {
|
||||||
|
value = (keyValue === 'true');
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 'select':
|
case 'select':
|
||||||
value = keyData[0][1].toString();
|
value = keyValue.toString();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
value = keyData[0][1];
|
value = keyValue;
|
||||||
}
|
}
|
||||||
key.value = value;
|
key.value = value;
|
||||||
}
|
}
|
||||||
@ -399,6 +462,18 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getKeyValue(data: any, keySetting: MultipleInputWidgetDataKeySettings) {
|
||||||
|
if (isDefined(keySetting.getValueFunction)) {
|
||||||
|
try {
|
||||||
|
return keySetting.getValueFunction(data, this.ctx);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Call function getValue`, e);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
private updateWidgetDisplaying() {
|
private updateWidgetDisplaying() {
|
||||||
this.changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620);
|
this.changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620);
|
||||||
}
|
}
|
||||||
@ -493,7 +568,8 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
|
|||||||
if (!this.settings.showActionButtons && !this.isSavingInProgress) {
|
if (!this.settings.showActionButtons && !this.isSavingInProgress) {
|
||||||
this.isSavingInProgress = true;
|
this.isSavingInProgress = true;
|
||||||
const currentValue = this.multipleInputFormGroup.get(key.formId).value;
|
const currentValue = this.multipleInputFormGroup.get(key.formId).value;
|
||||||
if (!key.settings.required || (key.settings.required && isDefinedAndNotNull(currentValue) && isNotEmptyStr(currentValue.toString()))) {
|
if (!key.settings.required ||
|
||||||
|
(key.settings.required && isDefinedAndNotNull(currentValue) && isNotEmptyStr(currentValue.toString()))) {
|
||||||
const dataToSave: MultipleInputWidgetSource = {
|
const dataToSave: MultipleInputWidgetSource = {
|
||||||
datasource: source.datasource,
|
datasource: source.datasource,
|
||||||
keys: [key]
|
keys: [key]
|
||||||
@ -523,8 +599,9 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
|
|||||||
const sharedAttributes: AttributeData[] = [];
|
const sharedAttributes: AttributeData[] = [];
|
||||||
const telemetry: AttributeData[] = [];
|
const telemetry: AttributeData[] = [];
|
||||||
for (const key of toSave.keys) {
|
for (const key of toSave.keys) {
|
||||||
const currentValue = key.settings.dataKeyHidden ? key.value : this.multipleInputFormGroup.get(key.formId).value;
|
let currentValue = key.settings.dataKeyHidden ? key.value : this.multipleInputFormGroup.get(key.formId).value;
|
||||||
if (!isEqual(currentValue, key.value) || this.settings.updateAllValues) {
|
if (!isEqual(currentValue, key.value) || this.settings.updateAllValues) {
|
||||||
|
currentValue = this.setKeyValue(currentValue, key, toSave);
|
||||||
const attribute: AttributeData = {
|
const attribute: AttributeData = {
|
||||||
key: key.name,
|
key: key.name,
|
||||||
value: null
|
value: null
|
||||||
@ -615,6 +692,21 @@ export class MultipleInputWidgetComponent extends PageComponent implements OnIni
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private setKeyValue(value: any, key: MultipleInputWidgetDataKey, source: MultipleInputWidgetSource) {
|
||||||
|
if (isDefined(key.settings.setValueFunction)) {
|
||||||
|
const currentDatasourceData = this.subscription.data
|
||||||
|
.find(dsData => dsData.datasource === source.datasource && dsData.dataKey.name === key.name);
|
||||||
|
const originValue = currentDatasourceData.data[0][1];
|
||||||
|
try {
|
||||||
|
return key.settings.setValueFunction(value, originValue, this.ctx);
|
||||||
|
} catch (e) {
|
||||||
|
console.warn(`Call function setValue`, e);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
public discardAll() {
|
public discardAll() {
|
||||||
this.multipleInputFormGroup.reset(undefined, {emitEvent: false});
|
this.multipleInputFormGroup.reset(undefined, {emitEvent: false});
|
||||||
this.sources.forEach((source) => {
|
this.sources.forEach((source) => {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user