9 changed files with 743 additions and 22 deletions
@ -0,0 +1,126 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2020 The Thingsboard Authors |
||||
|
|
||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
you may not use this file except in compliance with the License. |
||||
|
You may obtain a copy of the License at |
||||
|
|
||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
|
||||
|
Unless required by applicable law or agreed to in writing, software |
||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
See the License for the specific language governing permissions and |
||||
|
limitations under the License. |
||||
|
|
||||
|
--> |
||||
|
<form #formContainer class="tb-multiple-input" |
||||
|
#multipleInputForm="ngForm" |
||||
|
[formGroup]="multipleInputFormGroup" |
||||
|
tb-toast toastTarget="{{ toastTargetId }}" |
||||
|
(ngSubmit)="save()" novalidate autocomplete="off"> |
||||
|
<div style="padding: 0 8px;" *ngIf="entityDetected && isAllParametersValid"> |
||||
|
<fieldset *ngFor="let source of sources" [ngClass]="{'fields-group': settings.showGroupTitle}"> |
||||
|
<legend class="group-title" *ngIf="settings.showGroupTitle">{{ getGroupTitle(source.datasource) }} |
||||
|
</legend> |
||||
|
<div fxLayout="row" class="layout-wrap" |
||||
|
[ngClass]="{'vertical-alignment': isVerticalAlignment || changeAlignment}"> |
||||
|
<div *ngFor="let key of visibleKeys(source)" |
||||
|
[ngStyle]="{width: (isVerticalAlignment || changeAlignment) ? '100%' : inputWidthSettings}"> |
||||
|
<div class="input-field" *ngIf="key.settings.dataKeyValueType === 'string'"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label>{{key.label}}</mat-label> |
||||
|
<input matInput |
||||
|
formControlName="{{key.formId}}" |
||||
|
[required]="key.settings.required" |
||||
|
[readonly]="key.settings.isEditable === 'readonly'" |
||||
|
type="text" |
||||
|
(focus)="key.isFocused = true; focusInputElement($event)" |
||||
|
(blur)="key.isFocused = false; inputChanged(source, key)"> |
||||
|
<mat-icon *ngIf="key.settings.icon" matPrefix>{{key.settings.icon}}</mat-icon> |
||||
|
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')"> |
||||
|
{{key.settings.requiredErrorMessage}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div class="input-field" *ngIf="key.settings.dataKeyValueType === 'double' || |
||||
|
key.settings.dataKeyValueType === 'integer'"> |
||||
|
<mat-form-field class="mat-block"> |
||||
|
<mat-label>{{key.label}}</mat-label> |
||||
|
<input matInput |
||||
|
formControlName="{{key.formId}}" |
||||
|
[required]="key.settings.required" |
||||
|
[readonly]="key.settings.isEditable === 'readonly'" |
||||
|
type="number" |
||||
|
step="{{key.settings.step}}" |
||||
|
(focus)="key.isFocused = true; focusInputElement($event)" |
||||
|
(blur)="key.isFocused = false; inputChanged(source, key)"> |
||||
|
<mat-icon *ngIf="key.settings.icon" matPrefix>{{key.settings.icon}}</mat-icon> |
||||
|
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')"> |
||||
|
{{key.settings.requiredErrorMessage}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
<div class="input-field mat-block" *ngIf="key.settings.dataKeyValueType === 'booleanCheckbox'"> |
||||
|
<mat-checkbox formControlName="{{key.formId}}" |
||||
|
(change)="inputChanged(source, key)"> |
||||
|
{{key.label}} |
||||
|
</mat-checkbox> |
||||
|
</div> |
||||
|
<div class="input-field mat-block" *ngIf="key.settings.dataKeyValueType === 'booleanSwitch'"> |
||||
|
<mat-slide-toggle formControlName="{{key.formId}}" |
||||
|
(change)="inputChanged(source, key)"> |
||||
|
{{key.label}} |
||||
|
</mat-slide-toggle> |
||||
|
</div> |
||||
|
<div class="input-field mat-block date-time-input" *ngIf="(key.settings.dataKeyValueType === 'dateTime') || |
||||
|
(key.settings.dataKeyValueType === 'date') || |
||||
|
(key.settings.dataKeyValueType === 'time')" fxLayout="column"> |
||||
|
<div fxLayout="row" [ngClass]="{'vertically-aligned': smallWidthContainer}" fxLayoutGap="16px"> |
||||
|
<mat-form-field> |
||||
|
<mat-placeholder>{{key.label}}</mat-placeholder> |
||||
|
<mat-datetimepicker-toggle [for]="datePicker" matPrefix></mat-datetimepicker-toggle> |
||||
|
<mat-datetimepicker #datePicker type="{{datePickerType(key.settings.dataKeyValueType)}}" |
||||
|
openOnFocus="true"></mat-datetimepicker> |
||||
|
<input matInput formControlName="{{key.formId}}" |
||||
|
[required]="key.settings.required" |
||||
|
[readonly]="key.settings.isEditable === 'readonly'" |
||||
|
[matDatetimepicker]="datePicker" |
||||
|
(focus)="key.isFocused = true;" |
||||
|
(blur)="key.isFocused = false;" |
||||
|
(dateChange)="inputChanged(source, key)"> |
||||
|
<mat-error *ngIf="multipleInputFormGroup.get(key.formId).hasError('required')"> |
||||
|
{{key.settings.requiredErrorMessage}} |
||||
|
</mat-error> |
||||
|
</mat-form-field> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</fieldset> |
||||
|
<div class="mat-padding" fxLayout="row" fxLayoutAlign="end center" |
||||
|
*ngIf="entityDetected && settings.showActionButtons"> |
||||
|
<button mat-button color="primary" type="button" |
||||
|
(click)="discardAll()" style="max-height: 50px; margin-right:20px;" |
||||
|
[disabled]="!multipleInputForm.dirty"> |
||||
|
{{ 'action.undo' | translate }} |
||||
|
</button> |
||||
|
<button mat-button mat-raised-button color="primary" type="submit" |
||||
|
style="max-height: 50px; margin-right:20px;" |
||||
|
[disabled]="!multipleInputForm.dirty || multipleInputForm.invalid"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="tb-multiple-input__errors" fxLayout="column" fxLayoutAlign="center center" style="height: 100%;" |
||||
|
*ngIf="!entityDetected || !isAllParametersValid"> |
||||
|
<div style="text-align: center; font-size: 18px; color: #a0a0a0;" [fxHide]="entityDetected"> |
||||
|
{{ 'widgets.input-widgets.no-entity-selected' | translate }} |
||||
|
</div> |
||||
|
<div style="text-align: center; font-size: 18px; color: #a0a0a0;" |
||||
|
[fxShow]="entityDetected && !isAllParametersValid"> |
||||
|
{{ 'widgets.input-widgets.not-allowed-entity' | translate }} |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
@ -0,0 +1,75 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2020 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
|
||||
|
:host { |
||||
|
|
||||
|
.tb-multiple-input { |
||||
|
height: 100%; |
||||
|
overflow-x: hidden; |
||||
|
overflow-y: auto; |
||||
|
|
||||
|
.fields-group { |
||||
|
padding: 0 8px; |
||||
|
margin: 10px 0; |
||||
|
border: 1px groove rgba(0, 0, 0, .25); |
||||
|
|
||||
|
legend { |
||||
|
color: rgba(0, 0, 0, .7); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.input-field { |
||||
|
padding-right: 10px; |
||||
|
|
||||
|
mat-form-field { |
||||
|
margin-bottom: 5px; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
mat-checkbox, |
||||
|
mat-slide-toggle { |
||||
|
display: block; |
||||
|
margin-top: 20px; |
||||
|
margin-bottom: 16px; |
||||
|
white-space: normal; |
||||
|
} |
||||
|
|
||||
|
.date-time-input { |
||||
|
mat-form-field { |
||||
|
width: 100%; |
||||
|
margin: 2px 0; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.vertical-alignment { |
||||
|
flex-direction: column; |
||||
|
|
||||
|
mat-checkbox, |
||||
|
mat-slide-toggle { |
||||
|
margin-top: 18px; |
||||
|
} |
||||
|
|
||||
|
mat-slide-toggle { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.vertically-aligned { |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,483 @@ |
|||||
|
///
|
||||
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
||||
|
///
|
||||
|
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
/// you may not use this file except in compliance with the License.
|
||||
|
/// You may obtain a copy of the License at
|
||||
|
///
|
||||
|
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
///
|
||||
|
/// Unless required by applicable law or agreed to in writing, software
|
||||
|
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
|
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
|
/// See the License for the specific language governing permissions and
|
||||
|
/// limitations under the License.
|
||||
|
///
|
||||
|
|
||||
|
import { Component, ElementRef, Input, NgZone, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; |
||||
|
import { PageComponent } from '@shared/components/page.component'; |
||||
|
import { WidgetContext } from '@home/models/widget-component.models'; |
||||
|
import { Store } from '@ngrx/store'; |
||||
|
import { AppState } from '@core/core.state'; |
||||
|
import { Overlay } from '@angular/cdk/overlay'; |
||||
|
import { UtilsService } from '@core/services/utils.service'; |
||||
|
import { TranslateService } from '@ngx-translate/core'; |
||||
|
import { DataKey, Datasource, DatasourceData, DatasourceType, WidgetConfig } from '@shared/models/widget.models'; |
||||
|
import { IWidgetSubscription } from '@core/api/widget-api.models'; |
||||
|
import { isDefined, isEqual, isUndefined } from '@core/utils'; |
||||
|
import { EntityType } from '@shared/models/entity-type.models'; |
||||
|
import * as _moment from 'moment'; |
||||
|
import { FormBuilder, FormGroup, NgForm, ValidatorFn, Validators } from '@angular/forms'; |
||||
|
import { RequestConfig } from '@core/http/http-utils'; |
||||
|
import { AttributeService } from '@core/http/attribute.service'; |
||||
|
import { AttributeData, AttributeScope, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; |
||||
|
import { forkJoin, Observable } from 'rxjs'; |
||||
|
import { EntityId } from '@shared/models/id/entity-id'; |
||||
|
|
||||
|
type FieldAlignment = 'row' | 'column'; |
||||
|
|
||||
|
type MultipleInputWidgetDataKeyType = 'server' | 'shared' | 'timeseries'; |
||||
|
type MultipleInputWidgetDataKeyValueType = 'string' | 'double' | 'integer' | |
||||
|
'booleanCheckbox' | 'booleanSwitch' | |
||||
|
'dateTime' | 'date' | 'time'; |
||||
|
type MultipleInputWidgetDataKeyEditableType = 'editable' | 'disabled' | 'readonly'; |
||||
|
|
||||
|
interface MultipleInputWidgetSettings { |
||||
|
widgetTitle: string; |
||||
|
showActionButtons: boolean; |
||||
|
updateAllValues: boolean; |
||||
|
showResultMessage: boolean; |
||||
|
showGroupTitle: boolean; |
||||
|
groupTitle: string; |
||||
|
fieldsAlignment: FieldAlignment; |
||||
|
fieldsInRow: number; |
||||
|
attributesShared?: boolean; |
||||
|
} |
||||
|
|
||||
|
interface MultipleInputWidgetDataKeySettings { |
||||
|
dataKeyType: MultipleInputWidgetDataKeyType; |
||||
|
dataKeyValueType: MultipleInputWidgetDataKeyValueType; |
||||
|
required: boolean; |
||||
|
isEditable: MultipleInputWidgetDataKeyEditableType; |
||||
|
disabledOnDataKey: string; |
||||
|
dataKeyHidden: boolean; |
||||
|
step: number; |
||||
|
requiredErrorMessage: string; |
||||
|
icon: string; |
||||
|
inputTypeNumber?: boolean; |
||||
|
readOnly?: boolean; |
||||
|
disabledOnCondition?: boolean; |
||||
|
} |
||||
|
|
||||
|
interface MultipleInputWidgetDataKey extends DataKey { |
||||
|
formId?: string; |
||||
|
settings: MultipleInputWidgetDataKeySettings; |
||||
|
isFocused: boolean; |
||||
|
value?: any; |
||||
|
} |
||||
|
|
||||
|
interface MultipleInputWidgetSource { |
||||
|
datasource: Datasource; |
||||
|
keys: MultipleInputWidgetDataKey[]; |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'tb-multiple-input-widget ', |
||||
|
templateUrl: './multiple-input-widget.component.html', |
||||
|
styleUrls: ['./multiple-input-widget.component.scss'] |
||||
|
}) |
||||
|
export class MultipleInputWidgetComponent extends PageComponent implements OnInit, OnDestroy { |
||||
|
|
||||
|
@ViewChild('formContainer', {static: true}) formContainerRef: ElementRef<HTMLElement>; |
||||
|
@ViewChild('multipleInputForm', {static: true}) multipleInputForm: NgForm; |
||||
|
|
||||
|
@Input() |
||||
|
ctx: WidgetContext; |
||||
|
|
||||
|
private formResizeListener: any; |
||||
|
private settings: MultipleInputWidgetSettings; |
||||
|
private widgetConfig: WidgetConfig; |
||||
|
private subscription: IWidgetSubscription; |
||||
|
private datasources: Array<Datasource>; |
||||
|
private sources: Array<MultipleInputWidgetSource> = []; |
||||
|
|
||||
|
isVerticalAlignment: boolean; |
||||
|
inputWidthSettings: string; |
||||
|
changeAlignment: boolean; |
||||
|
smallWidthContainer: boolean; |
||||
|
|
||||
|
entityDetected = false; |
||||
|
isAllParametersValid = true; |
||||
|
|
||||
|
multipleInputFormGroup: FormGroup; |
||||
|
|
||||
|
toastTargetId = 'multiple-input-widget' + this.utils.guid(); |
||||
|
|
||||
|
constructor(protected store: Store<AppState>, |
||||
|
private elementRef: ElementRef, |
||||
|
private ngZone: NgZone, |
||||
|
private overlay: Overlay, |
||||
|
private viewContainerRef: ViewContainerRef, |
||||
|
private utils: UtilsService, |
||||
|
private fb: FormBuilder, |
||||
|
private attributeService: AttributeService, |
||||
|
private translate: TranslateService) { |
||||
|
super(store); |
||||
|
} |
||||
|
|
||||
|
ngOnInit(): void { |
||||
|
this.ctx.$scope.multipleInputWidget = this; |
||||
|
this.settings = this.ctx.settings; |
||||
|
this.widgetConfig = this.ctx.widgetConfig; |
||||
|
this.subscription = this.ctx.defaultSubscription; |
||||
|
this.datasources = this.subscription.datasources; |
||||
|
this.initializeConfig(); |
||||
|
this.updateDatasources(); |
||||
|
this.buildForm(); |
||||
|
this.ctx.updateWidgetParams(); |
||||
|
this.formResizeListener = this.resize.bind(this); |
||||
|
// @ts-ignore
|
||||
|
addResizeListener(this.formContainerRef.nativeElement, this.formResizeListener); |
||||
|
} |
||||
|
|
||||
|
ngOnDestroy(): void { |
||||
|
if (this.formResizeListener) { |
||||
|
// @ts-ignore
|
||||
|
removeResizeListener(this.formContainerRef.nativeElement, this.formResizeListener); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private initializeConfig() { |
||||
|
|
||||
|
if (this.settings.widgetTitle && this.settings.widgetTitle.length) { |
||||
|
this.ctx.widgetTitle = this.utils.customTranslation(this.settings.widgetTitle, this.settings.widgetTitle); |
||||
|
} else { |
||||
|
this.ctx.widgetTitle = this.ctx.widgetConfig.title; |
||||
|
} |
||||
|
|
||||
|
this.settings.groupTitle = this.settings.groupTitle || '${entityName}'; |
||||
|
|
||||
|
// For backward compatibility
|
||||
|
if (isUndefined(this.settings.showActionButtons)) { |
||||
|
this.settings.showActionButtons = true; |
||||
|
} |
||||
|
if (isUndefined(this.settings.fieldsAlignment)) { |
||||
|
this.settings.fieldsAlignment = 'row'; |
||||
|
} |
||||
|
if (isUndefined(this.settings.fieldsInRow)) { |
||||
|
this.settings.fieldsInRow = 2; |
||||
|
} |
||||
|
// For backward compatibility
|
||||
|
|
||||
|
this.isVerticalAlignment = !(this.settings.fieldsAlignment === 'row'); |
||||
|
|
||||
|
if (!this.isVerticalAlignment && this.settings.fieldsInRow) { |
||||
|
this.inputWidthSettings = 100 / this.settings.fieldsInRow + '%'; |
||||
|
} |
||||
|
|
||||
|
this.updateWidgetDisplaying(); |
||||
|
} |
||||
|
|
||||
|
private updateDatasources() { |
||||
|
if (this.datasources && this.datasources.length) { |
||||
|
this.entityDetected = true; |
||||
|
let keyIndex = 0; |
||||
|
this.datasources.forEach((datasource) => { |
||||
|
const source: MultipleInputWidgetSource = { |
||||
|
datasource, |
||||
|
keys: [] |
||||
|
}; |
||||
|
if (datasource.type === DatasourceType.entity) { |
||||
|
datasource.dataKeys.forEach((dataKey: MultipleInputWidgetDataKey) => { |
||||
|
if ((datasource.entityType !== EntityType.DEVICE) && (dataKey.settings.dataKeyType === 'shared')) { |
||||
|
this.isAllParametersValid = false; |
||||
|
} |
||||
|
if (dataKey.units) { |
||||
|
dataKey.label += ' (' + dataKey.units + ')'; |
||||
|
} |
||||
|
dataKey.formId = (++keyIndex)+''; |
||||
|
dataKey.isFocused = false; |
||||
|
|
||||
|
// For backward compatibility
|
||||
|
if (isUndefined(dataKey.settings.dataKeyType)) { |
||||
|
if (this.settings.attributesShared) { |
||||
|
dataKey.settings.dataKeyType = 'shared'; |
||||
|
} else { |
||||
|
dataKey.settings.dataKeyType = 'server'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isUndefined(dataKey.settings.dataKeyValueType)) { |
||||
|
if (dataKey.settings.inputTypeNumber) { |
||||
|
dataKey.settings.dataKeyValueType = 'double'; |
||||
|
} else { |
||||
|
dataKey.settings.dataKeyValueType = 'string'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (isUndefined(dataKey.settings.isEditable)) { |
||||
|
if (dataKey.settings.readOnly) { |
||||
|
dataKey.settings.isEditable = 'readonly'; |
||||
|
} else { |
||||
|
dataKey.settings.isEditable = 'editable'; |
||||
|
} |
||||
|
} |
||||
|
// For backward compatibility
|
||||
|
|
||||
|
source.keys.push(dataKey); |
||||
|
}); |
||||
|
} else { |
||||
|
this.entityDetected = false; |
||||
|
} |
||||
|
this.sources.push(source); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private buildForm() { |
||||
|
this.multipleInputFormGroup = this.fb.group({}); |
||||
|
this.sources.forEach((source) => { |
||||
|
for (const key of this.visibleKeys(source)) { |
||||
|
const validators: ValidatorFn[] = []; |
||||
|
if (key.settings.required) { |
||||
|
validators.push(Validators.required); |
||||
|
} |
||||
|
if (key.settings.dataKeyValueType === 'integer') { |
||||
|
validators.push(Validators.pattern(/^-?[0-9]+$/)); |
||||
|
} |
||||
|
const formControl = this.fb.control( |
||||
|
{ value: key.value, |
||||
|
disabled: key.settings.isEditable === 'disabled' || key.settings.disabledOnCondition}, |
||||
|
validators |
||||
|
); |
||||
|
this.multipleInputFormGroup.addControl(key.formId, formControl); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private updateWidgetData(data: Array<DatasourceData>) { |
||||
|
let dataIndex = 0; |
||||
|
this.sources.forEach((source) => { |
||||
|
source.keys.forEach((key) => { |
||||
|
const keyData = data[dataIndex].data; |
||||
|
if (keyData && keyData.length) { |
||||
|
let value; |
||||
|
switch (key.settings.dataKeyValueType) { |
||||
|
case 'dateTime': |
||||
|
case 'date': |
||||
|
value = _moment(keyData[0][1]).toDate(); |
||||
|
break; |
||||
|
case 'time': |
||||
|
value = _moment().startOf('day').add(keyData[0][1], 'ms').toDate(); |
||||
|
break; |
||||
|
case 'booleanCheckbox': |
||||
|
case 'booleanSwitch': |
||||
|
value = (keyData[0][1] === 'true'); |
||||
|
break; |
||||
|
default: |
||||
|
value = keyData[0][1]; |
||||
|
} |
||||
|
key.value = value; |
||||
|
} |
||||
|
|
||||
|
if (key.settings.isEditable === 'editable' && key.settings.disabledOnDataKey) { |
||||
|
const conditions = data.filter((item) => { |
||||
|
return source.datasource === item.datasource && item.dataKey.name === key.settings.disabledOnDataKey; |
||||
|
}); |
||||
|
if (conditions && conditions.length) { |
||||
|
if (conditions[0].data.length) { |
||||
|
if (conditions[0].data[0][1] === 'false') { |
||||
|
key.settings.disabledOnCondition = true; |
||||
|
} else { |
||||
|
key.settings.disabledOnCondition = !conditions[0].data[0][1]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (!key.settings.dataKeyHidden) { |
||||
|
if (key.settings.isEditable === 'disabled' || key.settings.disabledOnCondition) { |
||||
|
this.multipleInputFormGroup.get(key.formId).disable({emitEvent: false}); |
||||
|
} else { |
||||
|
this.multipleInputFormGroup.get(key.formId).enable({emitEvent: false}); |
||||
|
} |
||||
|
const dirty = this.multipleInputFormGroup.get(key.formId).dirty; |
||||
|
if (!key.isFocused && !dirty) { |
||||
|
this.multipleInputFormGroup.get(key.formId).patchValue(key.value, {emitEvent: false}); |
||||
|
} |
||||
|
} |
||||
|
dataIndex++; |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private updateWidgetDisplaying() { |
||||
|
this.changeAlignment = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 620); |
||||
|
this.smallWidthContainer = (this.ctx.$container && this.ctx.$container[0].offsetWidth < 420); |
||||
|
} |
||||
|
|
||||
|
public onDataUpdated() { |
||||
|
this.ngZone.run(() => { |
||||
|
this.updateWidgetData(this.subscription.data); |
||||
|
this.ctx.detectChanges(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private resize() { |
||||
|
this.ngZone.run(() => { |
||||
|
this.updateWidgetDisplaying(); |
||||
|
this.ctx.detectChanges(); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public getGroupTitle(datasource: Datasource): string { |
||||
|
return this.utils.createLabelFromDatasource(datasource, this.settings.groupTitle); |
||||
|
} |
||||
|
|
||||
|
public visibleKeys(source: MultipleInputWidgetSource): MultipleInputWidgetDataKey[] { |
||||
|
return source.keys.filter(key => !key.settings.dataKeyHidden); |
||||
|
} |
||||
|
|
||||
|
public datePickerType(keyType: MultipleInputWidgetDataKeyValueType): string { |
||||
|
switch (keyType) { |
||||
|
case 'dateTime': |
||||
|
return 'datetime'; |
||||
|
case 'date': |
||||
|
return 'date'; |
||||
|
case 'time': |
||||
|
return 'time'; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public focusInputElement($event: Event) { |
||||
|
($event.target as HTMLInputElement).select(); |
||||
|
} |
||||
|
|
||||
|
public inputChanged(source: MultipleInputWidgetSource, key: MultipleInputWidgetDataKey) { |
||||
|
if (!this.settings.showActionButtons) { |
||||
|
const currentValue = this.multipleInputFormGroup.get(key.formId).value; |
||||
|
if (!key.settings.required || (key.settings.required && isDefined(currentValue))) { |
||||
|
const dataToSave: MultipleInputWidgetSource = { |
||||
|
datasource: source.datasource, |
||||
|
keys: [key] |
||||
|
}; |
||||
|
this.save(dataToSave); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public save(dataToSave?: MultipleInputWidgetSource) { |
||||
|
const config: RequestConfig = { |
||||
|
ignoreLoading: !this.settings.showActionButtons |
||||
|
}; |
||||
|
let data: Array<MultipleInputWidgetSource>; |
||||
|
if (dataToSave) { |
||||
|
data = [dataToSave]; |
||||
|
} else { |
||||
|
data = this.sources; |
||||
|
} |
||||
|
const tasks: Observable<any>[] = []; |
||||
|
data.forEach((toSave) => { |
||||
|
const serverAttributes: AttributeData[] = []; |
||||
|
const sharedAttributes: AttributeData[] = []; |
||||
|
const telemetry: AttributeData[] = []; |
||||
|
for (const key of this.visibleKeys(toSave)) { |
||||
|
const currentValue = this.multipleInputFormGroup.get(key.formId).value; |
||||
|
if (!isEqual(currentValue, key.value) || this.settings.updateAllValues) { |
||||
|
const attribute: AttributeData = { |
||||
|
key: key.name, |
||||
|
value: null |
||||
|
}; |
||||
|
if (currentValue) { |
||||
|
switch (key.settings.dataKeyValueType) { |
||||
|
case 'dateTime': |
||||
|
case 'date': |
||||
|
attribute.value = currentValue.getTime(); |
||||
|
break; |
||||
|
case 'time': |
||||
|
attribute.value = currentValue.getTime() - _moment().startOf('day').valueOf(); |
||||
|
break; |
||||
|
default: |
||||
|
attribute.value = currentValue; |
||||
|
} |
||||
|
} else { |
||||
|
if (currentValue === '') { |
||||
|
attribute.value = null; |
||||
|
} else { |
||||
|
attribute.value = currentValue; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
switch (key.settings.dataKeyType) { |
||||
|
case 'shared': |
||||
|
sharedAttributes.push(attribute); |
||||
|
break; |
||||
|
case 'timeseries': |
||||
|
telemetry.push(attribute); |
||||
|
break; |
||||
|
default: |
||||
|
serverAttributes.push(attribute); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
const entityId: EntityId = { |
||||
|
entityType: toSave.datasource.entityType, |
||||
|
id: toSave.datasource.entityId |
||||
|
}; |
||||
|
if (serverAttributes.length) { |
||||
|
tasks.push(this.attributeService.saveEntityAttributes( |
||||
|
entityId, |
||||
|
AttributeScope.SERVER_SCOPE, |
||||
|
serverAttributes, |
||||
|
config |
||||
|
)); |
||||
|
} |
||||
|
if (sharedAttributes.length) { |
||||
|
tasks.push(this.attributeService.saveEntityAttributes( |
||||
|
entityId, |
||||
|
AttributeScope.SHARED_SCOPE, |
||||
|
sharedAttributes, |
||||
|
config |
||||
|
)); |
||||
|
} |
||||
|
if (telemetry.length) { |
||||
|
tasks.push(this.attributeService.saveEntityTimeseries( |
||||
|
entityId, |
||||
|
LatestTelemetry.LATEST_TELEMETRY, |
||||
|
telemetry, |
||||
|
config |
||||
|
)); |
||||
|
} |
||||
|
}); |
||||
|
if (tasks.length) { |
||||
|
forkJoin(tasks).subscribe( |
||||
|
() => { |
||||
|
this.multipleInputForm.resetForm(); |
||||
|
this.multipleInputFormGroup.markAsPristine(); |
||||
|
if (this.settings.showResultMessage) { |
||||
|
this.ctx.showSuccessToast(this.translate.instant('widgets.input-widgets.update-successful'), |
||||
|
1000, 'bottom', 'left', this.toastTargetId); |
||||
|
} |
||||
|
}, |
||||
|
() => { |
||||
|
if (this.settings.showResultMessage) { |
||||
|
this.ctx.showErrorToast(this.translate.instant('widgets.input-widgets.update-failed'), |
||||
|
'bottom', 'left', this.toastTargetId); |
||||
|
} |
||||
|
}); |
||||
|
} else { |
||||
|
this.multipleInputForm.resetForm(); |
||||
|
this.multipleInputFormGroup.markAsPristine(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public discardAll() { |
||||
|
this.multipleInputForm.resetForm(); |
||||
|
this.sources.forEach((source) => { |
||||
|
for (const key of this.visibleKeys(source)) { |
||||
|
this.multipleInputFormGroup.get(key.formId).patchValue(key.value, {emitEvent: false}); |
||||
|
} |
||||
|
}); |
||||
|
this.multipleInputFormGroup.markAsPristine(); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue