diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index 51717b12ec..7328dc7fb3 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -319,9 +319,9 @@ "resources": [], "templateHtml": "\n", "templateCss": "", - "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n var scope = self.ctx.$scope;\r\n var id = self.ctx.$scope.$injector.get('utils').guid();\r\n scope.formId = \"form-\"+id;\r\n scope.ctx = self.ctx;\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-data-updated', self.ctx.$scope.formId);\r\n}\r\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Multiple input title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"attributesShared\": {\n \"title\": \"Attributes are 'shared' (default value is 'server')\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\":true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"attributesShared\",\n \"showResultMessage\"\n ]\n}", - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"readOnly\": {\n \"title\": \"Value is read only\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"inputTypeNumber\": {\n \"title\": \"Datakey is a number\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between valid values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"readOnly\",\n \"inputTypeNumber\",\n \"step\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t},\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n }\n ]\n}\n", + "controllerScript": "let $scope;\r\nlet settings;\r\nlet attributeService;\r\nlet toast;\r\nlet utils;\r\nlet types;\r\n\r\nself.onInit = function() {\r\n var scope = self.ctx.$scope;\r\n var id = self.ctx.$scope.$injector.get('utils').guid();\r\n scope.formId = \"form-\"+id;\r\n scope.ctx = self.ctx;\r\n}\r\n\r\nself.onDataUpdated = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-data-updated', self.ctx.$scope.formId);\r\n}\r\n\r\nself.typeParameters = function() {\r\n return {\r\n maxDatasources: 1\r\n }\r\n}\r\n\r\nself.onResize = function() {\r\n self.ctx.$scope.$broadcast('multiple-input-resize', self.ctx.$scope.formId);\r\n}\r\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"MultipleInput\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"showActionButtons\":{\n \"title\":\"Show action buttons\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"showResultMessage\":{\n \"title\":\"Show result message\",\n \"type\":\"boolean\",\n \"default\": true\n },\n \"fieldsAlignment\": {\n \"title\": \"Fields alignment\",\n \"type\": \"string\",\n \"default\": \"row\"\n },\n \"fieldsInRow\": {\n \"title\": \"Number of fields in the row\",\n \"type\": \"number\",\n \"default\": \"2\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n \"showActionButtons\",\n \"showResultMessage\",\n {\n \"key\": \"fieldsAlignment\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"row\",\n \"label\": \"Row (default)\"\n },\n {\n \"value\": \"column\",\n \"label\": \"Column\"\n }\n ]\n },\n \"fieldsInRow\"\n ]\n}", + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"dataKeyType\": {\n \"title\": \"Datakey type\",\n \"type\": \"string\",\n \"default\": \"server\"\n },\n \"dataKeyValueType\": {\n \"title\": \"Datakey value type\",\n \"type\": \"string\",\n \"default\": \"string\"\n },\n \"required\": {\n \"title\": \"Value is required\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"isEditable\": {\n \"title\": \"Ability to edit attribute\",\n \"type\": \"string\",\n \"default\": \"editable\"\n },\n \"disabledOnDataKey\": {\n \"title\": \"Disable on false value of another datakey (specify datakey name)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"dataKeyHidden\": {\n \"title\": \"Hide input field\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"step\": {\n \"title\": \"Step interval between values (only for numbers)\",\n \"type\": \"number\",\n \"default\": \"1\"\n },\n \"requiredErrorMessage\": {\n \"title\": \"'Required' error message\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"icon\": {\n \"title\": \"Icon to show before input cell\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"dataKeyType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"server\",\n \"label\": \"Server attribute (default)\"\n },\n {\n \"value\": \"shared\",\n \"label\": \"Shared attribute\"\n },\n {\n \"value\": \"timeseries\",\n \"label\": \"Timeseries\"\n }\n ]\n },\n {\n \"key\": \"dataKeyValueType\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"string\",\n \"label\": \"String\"\n },\n {\n \"value\": \"double\",\n \"label\": \"Double\"\n },\n {\n \"value\": \"integer\",\n \"label\": \"Integer\"\n },\n {\n \"value\": \"booleanCheckbox\",\n \"label\": \"Boolean (Checkbox)\"\n },\n {\n \"value\": \"booleanSwitch\",\n \"label\": \"Boolean (Switch)\"\n },\n {\n \"value\": \"dateTime\",\n \"label\": \"Date & Time\"\n },\n {\n \"value\": \"date\",\n \"label\": \"Date\"\n },\n {\n \"value\": \"time\",\n \"label\": \"Time\"\n }\n ]\n },\n \"required\",\n {\n \"key\": \"isEditable\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"editable\",\n \"label\": \"Editable (default)\"\n },\n {\n \"value\": \"disabled\",\n \"label\": \"Disabled\"\n },\n {\n \"value\": \"readonly\",\n \"label\": \"Read-only\"\n }\n ]\n },\n \"disabledOnDataKey\",\n \"dataKeyHidden\",\n \"step\",\n \"requiredErrorMessage\",\n\t\t{\n \t\t\"key\": \"icon\",\n\t\t\t\"type\": \"icon\"\n\t\t}\n ]\n}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.23592248334107624,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Update Multiple Attributes\",\"dropShadow\":true,\"enableFullscreen\":false,\"enableDataExport\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } }, diff --git a/ui/src/app/widget/lib/multiple-input-widget.js b/ui/src/app/widget/lib/multiple-input-widget.js index ad8972ff7d..c68c22704e 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.js +++ b/ui/src/app/widget/lib/multiple-input-widget.js @@ -41,25 +41,18 @@ function MultipleInputWidget() { } /*@ngInject*/ -function MultipleInputWidgetController($q, $scope, attributeService, toast, types, utils) { +function MultipleInputWidgetController($q, $scope, $translate, 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.isAllParametersValid = true; + vm.data = []; vm.datasources = null; - vm.cellStyle = cellStyle; - vm.textColor = textColor; vm.discardAll = discardAll; vm.inputChanged = inputChanged; - vm.postData = postData; + vm.save = save; $scope.$watch('vm.ctx', function() { if (vm.ctx && vm.ctx.defaultSubscription) { @@ -74,127 +67,109 @@ function MultipleInputWidgetController($q, $scope, attributeService, toast, type $scope.$on('multiple-input-data-updated', function(event, formId) { if (vm.formId == formId) { - updateRowData(vm.subscription.data); + updateWidgetData(vm.subscription.data); $scope.$digest(); } }); - function defaultStyle() { - return {}; - } - - function cellStyle(key, rowIndex, firstKey, lastKey) { - 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(); - } - } - if (vm.settings.rowMargin) { - if (angular.isUndefined(style.marginTop) && rowIndex != 0) { - style.marginTop = (vm.settings.rowMargin / 2) + 'px'; - } - if (angular.isUndefined(style.marginBottom)) { - style.marginBottom = (vm.settings.rowMargin / 2) + 'px'; - } - } - if (vm.settings.columnMargin) { - if (angular.isUndefined(style.marginLeft) && !firstKey) { - style.marginLeft = (vm.settings.columnMargin / 2) + 'px'; - } - if (angular.isUndefined(style.marginRight) && !lastKey) { - style.marginRight = (vm.settings.columnMargin / 2) + 'px'; - } - } - return style; - } - - function textColor(key) { - var style = {}; - if (key) { - var styleInfo = vm.stylesInfo[key.label]; - if (styleInfo.color) { - style = { color: styleInfo.color }; - } + $scope.$on('multiple-input-resize', function(event, formId) { + if (vm.formId == formId) { + updateWidgetDisplaying(); } - 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; - } + for (var i = 0; i < vm.data.length; i++) { + vm.data[i].data.currentValue = vm.data[i].data.originalValue; } - vm.hasAnyChange = false; + $scope.multipleInputForm.$setPristine(); } - 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; - } + function inputChanged(key) { + if (!vm.settings.showActionButtons) { + if (!key.settings.required || (key.settings.required && key.data && angular.isDefined(key.data.currentValue))) { + vm.save(key); } } - 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; + function save(key) { + var tasks = []; + var serverAttributes = [], sharedAttributes = [], telemetry = []; + var config = { + ignoreLoading: !vm.settings.showActionButtons + }; + var data; + if (key) { + data = [key]; + } else { + data = vm.data; + } + for (let i = 0; i < data.length; i++) { + var item = data[i]; + if (item.data.currentValue !== item.data.originalValue) { + var attribute = { + key: item.name + }; + switch (item.settings.dataKeyValueType) { + case 'dateTime': + case 'date': + attribute.value = item.data.currentValue.getTime(); + break; + case 'time': + attribute.value = item.data.currentValue.getTime() - moment().startOf('day').valueOf();//eslint-disable-line + break; + default: + attribute.value = item.data.currentValue; } - } - if (newValues) { - promises.push(attributeService.saveEntityAttributes( - datasource.entityType, - datasource.entityId, - vm.attributeScope, - attributes)); + switch (item.settings.dataKeyType) { + case 'shared': + sharedAttributes.push(attribute); + break; + case 'timeseries': + telemetry.push(attribute); + break; + default: + serverAttributes.push(attribute); + } } } - - if (promises.length) { - $q.all(promises).then( + for (let i = 0; i < serverAttributes.length; i++) { + tasks.push(attributeService.saveEntityAttributes( + vm.datasources[0].entityType, + vm.datasources[0].entityId, + types.attributesScope.server.value, + serverAttributes, + config)); + } + for (let i = 0; i < sharedAttributes.length; i++) { + tasks.push(attributeService.saveEntityAttributes( + vm.datasources[0].entityType, + vm.datasources[0].entityId, + types.attributesScope.shared.value, + sharedAttributes, + config)); + } + for (let i = 0; i < telemetry.length; i++) { + tasks.push(attributeService.saveEntityTimeseries( + vm.datasources[0].entityType, + vm.datasources[0].entityId, + types.latestTelemetry.value, + telemetry, + config)); + } + if (tasks.length) { + $q.all(tasks).then( function success() { - for (var d = 0; d < row.data.length; d++ ) { - row.data[d].originalValue = row.data[d].currentValue; - } - vm.hasAnyChange = false; + $scope.multipleInputForm.$setPristine(); if (vm.settings.showResultMessage) { - toast.showSuccess('Update successful', 1000, angular.element(vm.ctx.$container), 'bottom left'); + toast.showSuccess($translate.instant('widgets.input-widgets.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'); + toast.showError($translate.instant('widgets.input-widgets.update-failed'), angular.element(vm.ctx.$container), 'bottom left'); } } ); @@ -211,78 +186,75 @@ function MultipleInputWidgetController($q, $scope, attributeService, toast, type vm.ctx.widgetTitle = vm.widgetTitle; - vm.attributeScope = vm.settings.attributesShared ? types.attributesScope.shared.value : types.attributesScope.server.value; + vm.isVerticalAlignment = !(vm.settings.fieldsAlignment === 'row'); + + if (!vm.isVerticalAlignment && vm.settings.fieldsInRow) { + vm.inputWidthSettings = 100 / vm.settings.fieldsInRow + '%'; + } } function updateDatasources() { + if (vm.datasources && vm.datasources.length) { + var datasource = vm.datasources[0]; + if (datasource.type === types.datasourceType.entity) { + for (var i = 0; i < datasource.dataKeys.length; i++) { + if ((datasource.entityType !== types.entityType.device) && (datasource.dataKeys[i].settings.dataKeyType !== 'server')) { + vm.isAllParametersValid = false; + } + vm.data.push(datasource.dataKeys[i]); + vm.data[i].data = {}; + } + vm.entityDetected = true; + } + } + } - 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'; - } + function updateWidgetData(data) { + for (var i = 0; i < vm.data.length; i++) { + var keyData = data[i].data; + if (keyData && keyData.length) { + var value; + switch (vm.data[i].settings.dataKeyValueType) { + case 'dateTime': + case 'date': + value = moment(keyData[0][1]).toDate(); // eslint-disable-line + break; + case 'time': + value = moment().startOf('day').add(keyData[0][1], 'ms').toDate(); // eslint-disable-line + break; + case 'booleanCheckbox': + case 'booleanSwitch': + value = (keyData[0][1] === 'true'); + break; + default: + value = keyData[0][1]; + } - var cellStyleFunction = null; - var useCellStyleFunction = false; + vm.data[i].data = { + currentValue: value, + originalValue: value + }; - 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; - } + if (vm.data[i].settings.isEditable === 'editable' && vm.data[i].settings.disabledOnDataKey) { + var conditions = data.filter((item) => { + return item.dataKey.name === vm.data[i].settings.disabledOnDataKey; + }); + if (conditions && conditions.length) { + if (conditions[0].data.length) { + if (conditions[0].data[0][1] === 'false') { + vm.data[i].settings.disabledOnCondition = true; + } else { + vm.data[i].settings.disabledOnCondition = !conditions[0].data[0][1]; } } - - vm.stylesInfo[dataKey.label] = { - useCellStyleFunction: useCellStyleFunction, - cellStyleFunction: cellStyleFunction, - color: keySettings.color - }; - - 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]; - } - } - } + function updateWidgetDisplaying() { + vm.changeAlignment = (vm.ctx.$container[0].offsetWidth < 620); + vm.smallWidthContainer = (vm.ctx.$container[0].offsetWidth < 420); } - } diff --git a/ui/src/app/widget/lib/multiple-input-widget.scss b/ui/src/app/widget/lib/multiple-input-widget.scss index 7e16b3f967..ea5cf019fc 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.scss +++ b/ui/src/app/widget/lib/multiple-input-widget.scss @@ -15,15 +15,25 @@ */ .tb-multiple-input { height: 100%; + overflow-x: hidden; + overflow-y: auto; - .md-button.md-icon-button { - width: 32px; - min-width: 32px; - height: 32px; - min-height: 32px; - padding: 0 !important; - margin: 0; - line-height: 20px; + .input-field { + padding-right: 10px; + + md-input-container, + md-checkbox, + md-switch { + margin-bottom: 5px; + } + } + + md-checkbox { + margin-top: 22px; + } + + md-switch { + margin-top: 20px; } .md-icon-button md-icon { @@ -32,10 +42,56 @@ height: 20px; min-height: 20px; font-size: 20px; + } + + .date-time-input { + &__label { + margin-left: 36px; + font-size: 12px; + color: rgba(0, 0, 0, .54); + } + + mdp-date-picker, + mdp-time-picker { + width: 100%; + + .md-button.md-icon-button { + margin: 5px 0 0; + } - &:not([disabled]) { - color: #f66; + md-input-container { + width: 100%; + margin: 2px 0; + + label { + display: none; + } + } + } + } + + .vertical-alignment { + flex-direction: column; + + md-checkbox, + md-switch { + margin-top: 18px; } + + md-switch { + display: flex; + justify-content: space-between; + } + + .date-time-input { + &__label { + margin-top: 10px; + } + } + } + + .vertically-aligned { + flex-direction: column; } } diff --git a/ui/src/app/widget/lib/multiple-input-widget.tpl.html b/ui/src/app/widget/lib/multiple-input-widget.tpl.html index 1263919825..145b90a32d 100644 --- a/ui/src/app/widget/lib/multiple-input-widget.tpl.html +++ b/ui/src/app/widget/lib/multiple-input-widget.tpl.html @@ -15,53 +15,134 @@ limitations under the License. --> -
+
-
-
- - - - - - - {{key.settings.icon}} - - -
-
Value must be greater than {{key.settings.min}}
-
Value must be lower than {{key.settings.max}}
-
This field is required
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
value.invalid-integer-value
+
+
+
+
+ + {{key.label}} + +
+
+ + {{key.label}} + +
+
+ +
+ +
+
{{ key.settings.requiredErrorMessage }}
+
+
+ +
+
{{ key.settings.requiredErrorMessage }}
+
+
- +
-
-
- No attribute is selected +
+ {{ 'widgets.input-widgets.no-entity-selected' | translate }}
-
- Timeseries parameter cannot be used in this widget +
+ {{ 'widgets.input-widgets.not-allowed-entity' | translate }}
-
- +
+ {{ 'action.undo' | translate }} - + {{ 'action.save' | translate }}