9 changed files with 397 additions and 7 deletions
@ -0,0 +1,259 @@ |
|||||
|
/* |
||||
|
* Copyright © 2016-2019 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 './multiple-input-widget.scss'; |
||||
|
|
||||
|
/* eslint-disable import/no-unresolved, import/default */ |
||||
|
|
||||
|
import multipleInputWidgetTemplate from './multiple-input-widget.tpl.html'; |
||||
|
|
||||
|
/* eslint-enable import/no-unresolved, import/default */ |
||||
|
|
||||
|
export default angular.module('thingsboard.widgets.multipleInputWidget', []) |
||||
|
.directive('tbMultipleInputWidget', MultipleInputWidget) |
||||
|
.name; |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
function MultipleInputWidget() { |
||||
|
return { |
||||
|
restrict: "E", |
||||
|
scope: true, |
||||
|
bindToController: { |
||||
|
formId: '=', |
||||
|
ctx: '=' |
||||
|
}, |
||||
|
controller: MultipleInputWidgetController, |
||||
|
controllerAs: 'vm', |
||||
|
templateUrl: multipleInputWidgetTemplate |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
/*@ngInject*/ |
||||
|
function MultipleInputWidgetController($q, $scope, attributeService, toast, types, utils) { |
||||
|
var vm = this; |
||||
|
|
||||
|
vm.dataKeyDetected = false; |
||||
|
vm.hasAnyChange = false; |
||||
|
vm.entityDetected = false; |
||||
|
vm.isValidParameter = true; |
||||
|
vm.message = 'No entity selected'; |
||||
|
|
||||
|
vm.rows = []; |
||||
|
vm.rowIndex = 0; |
||||
|
|
||||
|
vm.datasources = null; |
||||
|
|
||||
|
vm.cellStyle = cellStyle; |
||||
|
vm.discardAll = discardAll; |
||||
|
vm.inputChanged = inputChanged; |
||||
|
vm.postData = postData; |
||||
|
|
||||
|
$scope.$watch('vm.ctx', function() { |
||||
|
if (vm.ctx && vm.ctx.defaultSubscription) { |
||||
|
vm.settings = vm.ctx.settings; |
||||
|
vm.widgetConfig = vm.ctx.widgetConfig; |
||||
|
vm.subscription = vm.ctx.defaultSubscription; |
||||
|
vm.datasources = vm.subscription.datasources; |
||||
|
initializeConfig(); |
||||
|
updateDatasources(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
$scope.$on('multiple-input-data-updated', function(event, formId) { |
||||
|
if (vm.formId == formId) { |
||||
|
updateRowData(vm.subscription.data); |
||||
|
$scope.$digest(); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
function defaultStyle() { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
function cellStyle(key) { |
||||
|
var style = {}; |
||||
|
if (key) { |
||||
|
var styleInfo = vm.stylesInfo[key.label]; |
||||
|
var value = key.currentValue; |
||||
|
if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { |
||||
|
try { |
||||
|
style = styleInfo.cellStyleFunction(value); |
||||
|
} catch (e) { |
||||
|
style = {}; |
||||
|
} |
||||
|
} else { |
||||
|
style = defaultStyle(); |
||||
|
} |
||||
|
} |
||||
|
return style; |
||||
|
} |
||||
|
|
||||
|
function discardAll() { |
||||
|
for (var r = 0; r < vm.rows.length; r++) { |
||||
|
var row = vm.rows[r]; |
||||
|
for (var d = 0; d < row.data.length; d++ ) { |
||||
|
row.data[d].currentValue = row.data[d].originalValue; |
||||
|
} |
||||
|
} |
||||
|
vm.hasAnyChange = false; |
||||
|
} |
||||
|
|
||||
|
function inputChanged() { |
||||
|
var newValue = false; |
||||
|
for (var r = 0; r < vm.rows.length; r++) { |
||||
|
var row = vm.rows[r]; |
||||
|
for (var d = 0; d < row.data.length; d++ ) { |
||||
|
if (!row.data[d].currentValue) { |
||||
|
return; |
||||
|
} |
||||
|
if (row.data[d].currentValue !== row.data[d].originalValue) { |
||||
|
newValue = true; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
vm.hasAnyChange = newValue; |
||||
|
} |
||||
|
|
||||
|
function postData() { |
||||
|
var promises = []; |
||||
|
for (var r = 0; r < vm.rows.length; r++) { |
||||
|
var row = vm.rows[r]; |
||||
|
var datasource = row.datasource; |
||||
|
var attributes = []; |
||||
|
var newValues = false; |
||||
|
|
||||
|
for (var d = 0; d < row.data.length; d++ ) { |
||||
|
if (row.data[d].currentValue !== row.data[d].originalValue) { |
||||
|
attributes.push({ |
||||
|
key : row.data[d].name, |
||||
|
value : row.data[d].currentValue, |
||||
|
}); |
||||
|
newValues = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (newValues) { |
||||
|
promises.push(attributeService.saveEntityAttributes( |
||||
|
datasource.entityType, |
||||
|
datasource.entityId, |
||||
|
vm.attributeScope, |
||||
|
attributes)); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (promises.length) { |
||||
|
$q.all(promises).then( |
||||
|
function success() { |
||||
|
for (var d = 0; d < row.data.length; d++ ) { |
||||
|
row.data[d].originalValue = row.data[d].currentValue; |
||||
|
} |
||||
|
vm.hasAnyChange = false; |
||||
|
if (vm.settings.showResultMessage) { |
||||
|
toast.showSuccess('Update successful', 1000, angular.element(vm.ctx.$container), 'bottom left'); |
||||
|
} |
||||
|
}, |
||||
|
function fail() { |
||||
|
if (vm.settings.showResultMessage) { |
||||
|
toast.showError('Update failed', angular.element(vm.ctx.$container), 'bottom left'); |
||||
|
} |
||||
|
} |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function initializeConfig() { |
||||
|
|
||||
|
if (vm.settings.widgetTitle && vm.settings.widgetTitle.length) { |
||||
|
vm.widgetTitle = utils.customTranslation(vm.settings.widgetTitle, vm.settings.widgetTitle); |
||||
|
} else { |
||||
|
vm.widgetTitle = vm.ctx.widgetConfig.title; |
||||
|
} |
||||
|
|
||||
|
vm.ctx.widgetTitle = vm.widgetTitle; |
||||
|
|
||||
|
vm.attributeScope = vm.settings.attributesShared ? types.attributesScope.shared.value : types.attributesScope.server.value; |
||||
|
} |
||||
|
|
||||
|
function updateDatasources() { |
||||
|
|
||||
|
vm.stylesInfo = {}; |
||||
|
vm.rows = []; |
||||
|
vm.rowIndex = 0; |
||||
|
|
||||
|
if (vm.datasources) { |
||||
|
vm.entityDetected = true; |
||||
|
for (var ds = 0; ds < vm.datasources.length; ds++) { |
||||
|
var row = {}; |
||||
|
var datasource = vm.datasources[ds]; |
||||
|
row.datasource = datasource; |
||||
|
row.data = []; |
||||
|
if (datasource.dataKeys) { |
||||
|
vm.dataKeyDetected = true; |
||||
|
for (var a = 0; a < datasource.dataKeys.length; a++ ) { |
||||
|
var dataKey = datasource.dataKeys[a]; |
||||
|
|
||||
|
if (dataKey.units) { |
||||
|
dataKey.label += ' (' + dataKey.units + ')'; |
||||
|
} |
||||
|
|
||||
|
var keySettings = dataKey.settings; |
||||
|
if (keySettings.inputTypeNumber) { |
||||
|
keySettings.inputType = 'number'; |
||||
|
} else { |
||||
|
keySettings.inputType = 'text'; |
||||
|
} |
||||
|
|
||||
|
var cellStyleFunction = null; |
||||
|
var useCellStyleFunction = false; |
||||
|
|
||||
|
if (keySettings.useCellStyleFunction === true) { |
||||
|
if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) { |
||||
|
try { |
||||
|
cellStyleFunction = new Function('value', keySettings.cellStyleFunction); |
||||
|
useCellStyleFunction = true; |
||||
|
} catch (e) { |
||||
|
cellStyleFunction = null; |
||||
|
useCellStyleFunction = false; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
vm.stylesInfo[dataKey.label] = { |
||||
|
useCellStyleFunction: useCellStyleFunction, |
||||
|
cellStyleFunction: cellStyleFunction |
||||
|
}; |
||||
|
|
||||
|
row.data.push(dataKey); |
||||
|
} |
||||
|
vm.rows.push(row); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function updateRowData(data) { |
||||
|
var dataIndex = 0; |
||||
|
for (var r = 0; r < vm.rows.length; r++) { |
||||
|
var row = vm.rows[r]; |
||||
|
for (var d = 0; d < row.data.length; d++ ) { |
||||
|
var keyData = data[dataIndex++].data; |
||||
|
if (keyData && keyData.length && keyData[0].length > 1) { |
||||
|
row.data[d].currentValue = row.data[d].originalValue = keyData[0][1]; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2019 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. |
||||
|
*/ |
||||
|
.tb-multiple-input { |
||||
|
height: 100%; |
||||
|
|
||||
|
.md-button.md-icon-button { |
||||
|
width: 32px; |
||||
|
min-width: 32px; |
||||
|
height: 32px; |
||||
|
min-height: 32px; |
||||
|
padding: 0 !important; |
||||
|
margin: 0; |
||||
|
line-height: 20px; |
||||
|
} |
||||
|
|
||||
|
.md-icon-button md-icon { |
||||
|
width: 20px; |
||||
|
min-width: 20px; |
||||
|
height: 20px; |
||||
|
min-height: 20px; |
||||
|
font-size: 20px; |
||||
|
|
||||
|
&:not([disabled]) { |
||||
|
color: #f66; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
md-toast { |
||||
|
min-width: 0; |
||||
|
|
||||
|
.md-toast-content { |
||||
|
font-size: 14px !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.footer { |
||||
|
position: absolute; |
||||
|
bottom: 0; |
||||
|
left: 0; |
||||
|
width: 100%; |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
<!-- |
||||
|
|
||||
|
Copyright © 2016-2019 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 class="tb-multiple-input" name="multipleInputForm" ng-submit="vm.postData($event)"> |
||||
|
<div style="padding: 0 8px; margin: auto 0;"> |
||||
|
<div ng-show="vm.entityDetected" layout="row" flex ng-repeat="row in vm.rows track by $index"> |
||||
|
<div layout="column" flex ng-repeat="key in row.data track by $index"> |
||||
|
<md-input-container class="md-icon-float" ng-style="vm.cellStyle(key)"> |
||||
|
<label>{{key.label}}</label> |
||||
|
<md-icon class="material-icons" ng-if="key.settings.icon"> |
||||
|
{{key.settings.icon}} |
||||
|
</md-icon> |
||||
|
<input name="key.name" |
||||
|
ng-model="key.currentValue" |
||||
|
type="{{key.settings.inputType}}" |
||||
|
step="{{key.settings.step}}" |
||||
|
md-select-on-focus |
||||
|
ng-change="vm.inputChanged()"> |
||||
|
</md-input-container> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div style="text-align: center; font-size: 18px; color: #a0a0a0;" ng-hide="vm.entityDetected" ng-bind="vm.message" |
||||
|
></div> |
||||
|
<div style="text-align: center; font-size: 18px; color: #a0a0a0;" ng-show="vm.entityDetected && !vm.dataKeyDetected"> |
||||
|
No attribute is selected |
||||
|
</div> |
||||
|
<div style="text-align: center; font-size: 18px; color: #a0a0a0;" ng-show="vm.entityDetected && !vm.isValidParameter"> |
||||
|
Timeseries parameter cannot be used in this widget |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="footer md-padding" layout="row" layout-align="end center" ng-show="vm.entityDetected && vm.dataKeyDetected"> |
||||
|
<md-button class="md-primary" ng-click="vm.discardAll()" style="max-height: 50px;margin-right:20px;" ng-disabled="!vm.hasAnyChange"> |
||||
|
{{ 'action.discard-changes' | translate }} |
||||
|
</md-button> |
||||
|
<md-button class="md-raised md-primary" type="submit" value="Submit" style="max-height: 50px;margin-right:20px;" |
||||
|
ng-disabled="!vm.hasAnyChange" ng-click="vm.isFocused = false"> |
||||
|
{{ 'action.save' | translate }} |
||||
|
</md-button> |
||||
|
</div> |
||||
|
</form> |
||||
Loading…
Reference in new issue