Showing
19 changed files
with
336 additions
and
31 deletions
... | ... | @@ -192,6 +192,8 @@ public class AnnotationComponentDiscoveryService implements ComponentDiscoverySe |
192 | 192 | NodeConfiguration config = configClazz.newInstance(); |
193 | 193 | NodeConfiguration defaultConfiguration = config.defaultConfiguration(); |
194 | 194 | nodeDefinition.setDefaultConfiguration(mapper.valueToTree(defaultConfiguration)); |
195 | + nodeDefinition.setUiResources(nodeAnnotation.uiResources()); | |
196 | + nodeDefinition.setConfigDirective(nodeAnnotation.configDirective()); | |
195 | 197 | return nodeDefinition; |
196 | 198 | } |
197 | 199 | ... | ... |
... | ... | @@ -284,6 +284,7 @@ |
284 | 284 | <exclude>src/sh/**</exclude> |
285 | 285 | <exclude>src/main/scripts/control/**</exclude> |
286 | 286 | <exclude>src/main/scripts/windows/**</exclude> |
287 | + <exclude>src/main/resources/public/static/rulenode/**</exclude> | |
287 | 288 | </excludes> |
288 | 289 | <mapping> |
289 | 290 | <proto>JAVADOC_STYLE</proto> | ... | ... |
... | ... | @@ -35,7 +35,10 @@ import static org.thingsboard.rule.engine.DonAsynchron.withCallback; |
35 | 35 | nodeDetails = "Evaluate incoming Message with configured JS condition. " + |
36 | 36 | "If <b>True</b> - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used." + |
37 | 37 | "Message payload can be accessed via <code>msg</code> property. For example <code>msg.temperature < 10;</code>" + |
38 | - "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>") | |
38 | + "Message metadata can be accessed via <code>meta</code> property. For example <code>meta.customerName === 'John';</code>", | |
39 | + uiResources = {"static/rulenode/rulenode-core-config.js"}, | |
40 | + configDirective = "tbFilterNodeScriptConfig") | |
41 | + | |
39 | 42 | public class TbJsFilterNode implements TbNode { |
40 | 43 | |
41 | 44 | private TbJsFilterNodeConfiguration config; | ... | ... |
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
0 → 100644
1 | +!function(e){function t(r){if(n[r])return n[r].exports;var u=n[r]={exports:{},id:r,loaded:!1};return e[r].call(u.exports,u,u.exports,t),u.loaded=!0,u.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}([function(e,t,n){e.exports=n(3)},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args="{{ [\'msg\'] }}" no-validate=true> </tb-js-func> </section> '},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function u(e){var t=function(t,n,r,u){var o=i.default;n.html(o),t.$watch("configuration",function(e,n){angular.equals(e,n)||u.$setViewValue(t.configuration)}),u.$render=function(){t.configuration=u.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}u.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=u;var o=n(1),i=r(o)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var u=n(2),o=r(u),i=n(5),a=r(i);t.default=angular.module("thingsboard.ruleChain.config",[]).directive("tbFilterNodeScriptConfig",o.default).config(a.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter"}}};angular.merge(e.en_US,t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function u(e,t){(0,i.default)(t);for(var n in t){var r=t[n];e.translations(n,r)}}u.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=u;var o=n(4),i=r(o)}]); | |
2 | +//# sourceMappingURL=rulenode-core-config.js.map | |
\ No newline at end of file | ... | ... |
... | ... | @@ -30,6 +30,10 @@ const httpProxy = require('http-proxy'); |
30 | 30 | const forwardHost = 'localhost'; |
31 | 31 | const forwardPort = 8080; |
32 | 32 | |
33 | +const ruleNodeUiforwardHost = 'localhost'; | |
34 | +const ruleNodeUiforwardPort = 8080; | |
35 | +//const ruleNodeUiforwardPort = 5000; | |
36 | + | |
33 | 37 | const app = express(); |
34 | 38 | const server = http.createServer(app); |
35 | 39 | |
... | ... | @@ -52,17 +56,34 @@ const apiProxy = httpProxy.createProxyServer({ |
52 | 56 | } |
53 | 57 | }); |
54 | 58 | |
59 | +const ruleNodeUiApiProxy = httpProxy.createProxyServer({ | |
60 | + target: { | |
61 | + host: ruleNodeUiforwardHost, | |
62 | + port: ruleNodeUiforwardPort | |
63 | + } | |
64 | +}); | |
65 | + | |
55 | 66 | apiProxy.on('error', function (err, req, res) { |
56 | 67 | console.warn('API proxy error: ' + err); |
57 | 68 | res.end('Error.'); |
58 | 69 | }); |
59 | 70 | |
71 | +ruleNodeUiApiProxy.on('error', function (err, req, res) { | |
72 | + console.warn('RuleNode UI API proxy error: ' + err); | |
73 | + res.end('Error.'); | |
74 | +}); | |
75 | + | |
60 | 76 | console.info(`Forwarding API requests to http://${forwardHost}:${forwardPort}`); |
77 | +console.info(`Forwarding Rule Node UI requests to http://${ruleNodeUiforwardHost}:${ruleNodeUiforwardPort}`); | |
61 | 78 | |
62 | 79 | app.all('/api/*', (req, res) => { |
63 | 80 | apiProxy.web(req, res); |
64 | 81 | }); |
65 | 82 | |
83 | +app.all('/static/rulenode/*', (req, res) => { | |
84 | + ruleNodeUiApiProxy.web(req, res); | |
85 | +}); | |
86 | + | |
66 | 87 | app.get('*', function(req, res) { |
67 | 88 | res.sendFile(path.join(__dirname, 'src/index.html')); |
68 | 89 | }); | ... | ... |
... | ... | @@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.ruleChain', []) |
17 | 17 | .factory('ruleChainService', RuleChainService).name; |
18 | 18 | |
19 | 19 | /*@ngInject*/ |
20 | -function RuleChainService($http, $q, $filter, types, componentDescriptorService) { | |
20 | +function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, componentDescriptorService) { | |
21 | 21 | |
22 | 22 | var ruleNodeComponents = null; |
23 | 23 | |
... | ... | @@ -177,11 +177,18 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService) |
177 | 177 | } else { |
178 | 178 | loadRuleNodeComponents().then( |
179 | 179 | (components) => { |
180 | - ruleNodeComponents = components; | |
181 | - ruleNodeComponents.push( | |
182 | - types.ruleChainNodeComponent | |
180 | + resolveRuleNodeComponentsUiResources(components).then( | |
181 | + (components) => { | |
182 | + ruleNodeComponents = components; | |
183 | + ruleNodeComponents.push( | |
184 | + types.ruleChainNodeComponent | |
185 | + ); | |
186 | + deferred.resolve(ruleNodeComponents); | |
187 | + }, | |
188 | + () => { | |
189 | + deferred.reject(); | |
190 | + } | |
183 | 191 | ); |
184 | - deferred.resolve(ruleNodeComponents); | |
185 | 192 | }, |
186 | 193 | () => { |
187 | 194 | deferred.reject(); |
... | ... | @@ -191,6 +198,48 @@ function RuleChainService($http, $q, $filter, types, componentDescriptorService) |
191 | 198 | return deferred.promise; |
192 | 199 | } |
193 | 200 | |
201 | + function resolveRuleNodeComponentsUiResources(components) { | |
202 | + var deferred = $q.defer(); | |
203 | + var tasks = []; | |
204 | + for (var i=0;i<components.length;i++) { | |
205 | + var component = components[i]; | |
206 | + tasks.push(resolveRuleNodeComponentUiResources(component)); | |
207 | + } | |
208 | + $q.all(tasks).then( | |
209 | + (components) => { | |
210 | + deferred.resolve(components); | |
211 | + }, | |
212 | + () => { | |
213 | + deferred.resolve(components); | |
214 | + } | |
215 | + ); | |
216 | + return deferred.promise; | |
217 | + } | |
218 | + | |
219 | + function resolveRuleNodeComponentUiResources(component) { | |
220 | + var deferred = $q.defer(); | |
221 | + var uiResources = component.configurationDescriptor.nodeDefinition.uiResources; | |
222 | + if (uiResources && uiResources.length) { | |
223 | + var tasks = []; | |
224 | + for (var i=0;i<uiResources.length;i++) { | |
225 | + var uiResource = uiResources[i]; | |
226 | + tasks.push($ocLazyLoad.load(uiResource)); | |
227 | + } | |
228 | + $q.all(tasks).then( | |
229 | + () => { | |
230 | + deferred.resolve(component); | |
231 | + }, | |
232 | + () => { | |
233 | + component.configurationDescriptor.nodeDefinition.uiResourceLoadError = $translate.instant('rulenode.ui-resources-load-error'); | |
234 | + deferred.resolve(component); | |
235 | + } | |
236 | + ) | |
237 | + } else { | |
238 | + deferred.resolve(component); | |
239 | + } | |
240 | + return deferred.promise; | |
241 | + } | |
242 | + | |
194 | 243 | function getRuleNodeComponentByClazz(clazz) { |
195 | 244 | var res = $filter('filter')(ruleNodeComponents, {clazz: clazz}, true); |
196 | 245 | if (res && res.length) { | ... | ... |
... | ... | @@ -43,6 +43,7 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { |
43 | 43 | var template = $templateCache.get(jsFuncTemplate); |
44 | 44 | element.html(template); |
45 | 45 | |
46 | + scope.functionName = attrs.functionName; | |
46 | 47 | scope.functionArgs = scope.$eval(attrs.functionArgs); |
47 | 48 | scope.validationArgs = scope.$eval(attrs.validationArgs); |
48 | 49 | scope.resultType = attrs.resultType; |
... | ... | @@ -50,6 +51,8 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { |
50 | 51 | scope.resultType = "nocheck"; |
51 | 52 | } |
52 | 53 | |
54 | + scope.validationTriggerArg = attrs.validationTriggerArg; | |
55 | + | |
53 | 56 | scope.functionValid = true; |
54 | 57 | |
55 | 58 | var Range = ace.acequire("ace/range").Range; |
... | ... | @@ -66,11 +69,15 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { |
66 | 69 | } |
67 | 70 | |
68 | 71 | scope.onFullscreenChanged = function () { |
72 | + updateEditorSize(); | |
73 | + }; | |
74 | + | |
75 | + function updateEditorSize() { | |
69 | 76 | if (scope.js_editor) { |
70 | 77 | scope.js_editor.resize(); |
71 | 78 | scope.js_editor.renderer.updateFull(); |
72 | 79 | } |
73 | - }; | |
80 | + } | |
74 | 81 | |
75 | 82 | scope.jsEditorOptions = { |
76 | 83 | useWrapMode: true, |
... | ... | @@ -131,6 +138,9 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { |
131 | 138 | scope.validate = function () { |
132 | 139 | try { |
133 | 140 | var toValidate = new Function(scope.functionArgsString, scope.functionBody); |
141 | + if (scope.noValidate) { | |
142 | + return true; | |
143 | + } | |
134 | 144 | var res; |
135 | 145 | var validationError; |
136 | 146 | for (var i=0;i<scope.validationArgs.length;i++) { |
... | ... | @@ -200,9 +210,19 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { |
200 | 210 | } |
201 | 211 | }; |
202 | 212 | |
203 | - scope.$on('form-submit', function () { | |
204 | - scope.functionValid = scope.validate(); | |
205 | - scope.updateValidity(); | |
213 | + scope.$on('form-submit', function (event, args) { | |
214 | + if (!args || scope.validationTriggerArg && scope.validationTriggerArg == args) { | |
215 | + scope.validationArgs = scope.$eval(attrs.validationArgs); | |
216 | + scope.cleanupJsErrors(); | |
217 | + scope.functionValid = true; | |
218 | + scope.updateValidity(); | |
219 | + scope.functionValid = scope.validate(); | |
220 | + scope.updateValidity(); | |
221 | + } | |
222 | + }); | |
223 | + | |
224 | + scope.$on('update-ace-editor-size', function () { | |
225 | + updateEditorSize(); | |
206 | 226 | }); |
207 | 227 | |
208 | 228 | $compile(element.contents())(scope); |
... | ... | @@ -211,7 +231,11 @@ function JsFunc($compile, $templateCache, toast, utils, $translate) { |
211 | 231 | return { |
212 | 232 | restrict: "E", |
213 | 233 | require: "^ngModel", |
214 | - scope: {}, | |
234 | + scope: { | |
235 | + disabled:'=ngDisabled', | |
236 | + noValidate: '=?', | |
237 | + fillHeight:'=?' | |
238 | + }, | |
215 | 239 | link: linker |
216 | 240 | }; |
217 | 241 | } | ... | ... |
... | ... | @@ -15,6 +15,12 @@ |
15 | 15 | */ |
16 | 16 | tb-js-func { |
17 | 17 | position: relative; |
18 | + .tb-disabled { | |
19 | + color: rgba(0,0,0,0.38); | |
20 | + } | |
21 | + .fill-height { | |
22 | + height: 100%; | |
23 | + } | |
18 | 24 | } |
19 | 25 | |
20 | 26 | .tb-js-func-panel { |
... | ... | @@ -23,8 +29,10 @@ tb-js-func { |
23 | 29 | height: 100%; |
24 | 30 | #tb-javascript-input { |
25 | 31 | min-width: 200px; |
26 | - min-height: 200px; | |
27 | 32 | width: 100%; |
28 | 33 | height: 100%; |
34 | + &:not(.fill-height) { | |
35 | + min-height: 200px; | |
36 | + } | |
29 | 37 | } |
30 | 38 | } | ... | ... |
... | ... | @@ -15,19 +15,20 @@ |
15 | 15 | limitations under the License. |
16 | 16 | |
17 | 17 | --> |
18 | -<div style="background: #fff;" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column"> | |
18 | +<div style="background: #fff;" ng-class="{'tb-disabled': disabled, 'fill-height': fillHeight}" tb-expand-fullscreen fullscreen-zindex="100" expand-button-id="expand-button" on-fullscreen-changed="onFullscreenChanged()" layout="column"> | |
19 | 19 | <div layout="row" layout-align="start center" style="height: 40px;"> |
20 | - <span style="font-style: italic;">function({{ functionArgsString }}) {</span> | |
20 | + <label class="tb-title no-padding">function {{ functionName }}({{ functionArgsString }}) {</label> | |
21 | 21 | <span flex></span> |
22 | 22 | <div id="expand-button" layout="column" aria-label="Fullscreen" class="md-button md-icon-button tb-md-32 tb-fullscreen-button-style"></div> |
23 | 23 | </div> |
24 | 24 | <div flex id="tb-javascript-panel" class="tb-js-func-panel" layout="column"> |
25 | - <div flex id="tb-javascript-input" | |
26 | - ui-ace="jsEditorOptions" | |
25 | + <div flex id="tb-javascript-input" ng-class="{'fill-height': fillHeight}" | |
26 | + ui-ace="jsEditorOptions" | |
27 | + ng-readonly="disabled" | |
27 | 28 | ng-model="functionBody"> |
28 | 29 | </div> |
29 | 30 | </div> |
30 | 31 | <div layout="row" layout-align="start center" style="height: 40px;"> |
31 | - <span style="font-style: italic;">}</span> | |
32 | - </div> | |
33 | -</div> | |
\ No newline at end of file | ||
32 | + <label class="tb-title no-padding">}</label> | |
33 | + </div> | |
34 | +</div> | ... | ... |
... | ... | @@ -1198,7 +1198,9 @@ export default angular.module('thingsboard.locale', []) |
1198 | 1198 | "type-action": "Action", |
1199 | 1199 | "type-action-details": "Perform special action", |
1200 | 1200 | "type-rule-chain": "Rule Chain", |
1201 | - "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain" | |
1201 | + "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain", | |
1202 | + "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.", | |
1203 | + "ui-resources-load-error": "Failed to load configuration ui resources." | |
1202 | 1204 | }, |
1203 | 1205 | "rule-plugin": { |
1204 | 1206 | "management": "Rules and plugins management" | ... | ... |
... | ... | @@ -18,6 +18,8 @@ import RuleChainRoutes from './rulechain.routes'; |
18 | 18 | import RuleChainsController from './rulechains.controller'; |
19 | 19 | import {RuleChainController, AddRuleNodeController, AddRuleNodeLinkController} from './rulechain.controller'; |
20 | 20 | import RuleChainDirective from './rulechain.directive'; |
21 | +import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive'; | |
22 | +import RuleNodeConfigDirective from './rulenode-config.directive'; | |
21 | 23 | import RuleNodeDirective from './rulenode.directive'; |
22 | 24 | import LinkDirective from './link.directive'; |
23 | 25 | |
... | ... | @@ -28,6 +30,8 @@ export default angular.module('thingsboard.ruleChain', []) |
28 | 30 | .controller('AddRuleNodeController', AddRuleNodeController) |
29 | 31 | .controller('AddRuleNodeLinkController', AddRuleNodeLinkController) |
30 | 32 | .directive('tbRuleChain', RuleChainDirective) |
33 | + .directive('tbRuleNodeDefinedConfig', RuleNodeDefinedConfigDirective) | |
34 | + .directive('tbRuleNodeConfig', RuleNodeConfigDirective) | |
31 | 35 | .directive('tbRuleNode', RuleNodeDirective) |
32 | 36 | .directive('tbRuleNodeLink', LinkDirective) |
33 | 37 | .name; | ... | ... |
... | ... | @@ -137,10 +137,13 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
137 | 137 | }; |
138 | 138 | |
139 | 139 | vm.saveRuleNode = function(theForm) { |
140 | - theForm.$setPristine(); | |
141 | - vm.isEditingRuleNode = false; | |
142 | - vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; | |
143 | - vm.editingRuleNode = angular.copy(vm.editingRuleNode); | |
140 | + $scope.$broadcast('form-submit'); | |
141 | + if (theForm.$valid) { | |
142 | + theForm.$setPristine(); | |
143 | + vm.isEditingRuleNode = false; | |
144 | + vm.ruleChainModel.nodes[vm.editingRuleNodeIndex] = vm.editingRuleNode; | |
145 | + vm.editingRuleNode = angular.copy(vm.editingRuleNode); | |
146 | + } | |
144 | 147 | }; |
145 | 148 | |
146 | 149 | vm.saveRuleNodeLink = function(theForm) { |
... | ... | @@ -309,7 +312,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
309 | 312 | var componentType = ruleNodeComponent.type; |
310 | 313 | var model = vm.ruleNodeTypesModel[componentType].model; |
311 | 314 | var node = { |
312 | - id: model.nodes.length, | |
315 | + id: 'node-lib-' + componentType + '-' + model.nodes.length, | |
313 | 316 | component: ruleNodeComponent, |
314 | 317 | name: '', |
315 | 318 | nodeClass: vm.types.ruleNodeType[componentType].nodeClass, |
... | ... | @@ -358,7 +361,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
358 | 361 | |
359 | 362 | vm.ruleChainModel.nodes.push( |
360 | 363 | { |
361 | - id: vm.nextNodeID++, | |
364 | + id: 'rule-chain-node-' + vm.nextNodeID++, | |
362 | 365 | component: types.inputNodeComponent, |
363 | 366 | name: "", |
364 | 367 | nodeClass: types.ruleNodeType.INPUT.nodeClass, |
... | ... | @@ -389,7 +392,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
389 | 392 | var component = ruleChainService.getRuleNodeComponentByClazz(ruleNode.type); |
390 | 393 | if (component) { |
391 | 394 | var node = { |
392 | - id: vm.nextNodeID++, | |
395 | + id: 'rule-chain-node-' + vm.nextNodeID++, | |
393 | 396 | ruleNodeId: ruleNode.id, |
394 | 397 | additionalInfo: ruleNode.additionalInfo, |
395 | 398 | configuration: ruleNode.configuration, |
... | ... | @@ -466,7 +469,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
466 | 469 | var ruleChainNode = ruleChainNodesMap[ruleChainConnection.additionalInfo.ruleChainNodeId]; |
467 | 470 | if (!ruleChainNode) { |
468 | 471 | ruleChainNode = { |
469 | - id: vm.nextNodeID++, | |
472 | + id: 'rule-chain-node-' + vm.nextNodeID++, | |
470 | 473 | additionalInfo: ruleChainConnection.additionalInfo, |
471 | 474 | targetRuleChainId: ruleChainConnection.targetRuleChainId.id, |
472 | 475 | x: ruleChainConnection.additionalInfo.layoutX, |
... | ... | @@ -611,7 +614,7 @@ export function RuleChainController($stateParams, $scope, $compile, $q, $mdUtil, |
611 | 614 | fullscreen: true, |
612 | 615 | targetEvent: $event |
613 | 616 | }).then(function (ruleNode) { |
614 | - ruleNode.id = vm.nextNodeID++; | |
617 | + ruleNode.id = 'rule-chain-node-' + vm.nextNodeID++; | |
615 | 618 | ruleNode.connectors = []; |
616 | 619 | if (ruleNode.component.configurationDescriptor.nodeDefinition.inEnabled) { |
617 | 620 | ruleNode.connectors.push( | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +/* eslint-disable import/no-unresolved, import/default */ | |
18 | + | |
19 | +import ruleNodeConfigTemplate from './rulenode-config.tpl.html'; | |
20 | + | |
21 | +/* eslint-enable import/no-unresolved, import/default */ | |
22 | + | |
23 | +/*@ngInject*/ | |
24 | +export default function RuleNodeConfigDirective($compile, $templateCache, $injector, $translate) { | |
25 | + | |
26 | + var linker = function (scope, element, attrs, ngModelCtrl) { | |
27 | + var template = $templateCache.get(ruleNodeConfigTemplate); | |
28 | + element.html(template); | |
29 | + | |
30 | + scope.$watch('configuration', function (newVal, prevVal) { | |
31 | + if (!angular.equals(newVal, prevVal)) { | |
32 | + ngModelCtrl.$setViewValue(scope.configuration); | |
33 | + } | |
34 | + }); | |
35 | + | |
36 | + ngModelCtrl.$render = function () { | |
37 | + scope.configuration = ngModelCtrl.$viewValue; | |
38 | + }; | |
39 | + | |
40 | + scope.useDefinedDirective = function() { | |
41 | + return scope.nodeDefinition.configDirective && !scope.definedDirectiveError; | |
42 | + }; | |
43 | + | |
44 | + validateDefinedDirective(); | |
45 | + | |
46 | + function validateDefinedDirective() { | |
47 | + if (scope.nodeDefinition.uiResourceLoadError && scope.nodeDefinition.uiResourceLoadError.length) { | |
48 | + scope.definedDirectiveError = scope.nodeDefinition.uiResourceLoadError; | |
49 | + } else { | |
50 | + var definedDirective = scope.nodeDefinition.configDirective; | |
51 | + if (definedDirective && definedDirective.length) { | |
52 | + if (!$injector.has(definedDirective + 'Directive')) { | |
53 | + scope.definedDirectiveError = $translate.instant('rulenode.directive-is-not-loaded', {directiveName: definedDirective}); | |
54 | + } | |
55 | + } | |
56 | + } | |
57 | + } | |
58 | + | |
59 | + $compile(element.contents())(scope); | |
60 | + }; | |
61 | + | |
62 | + return { | |
63 | + restrict: "E", | |
64 | + require: "^ngModel", | |
65 | + scope: { | |
66 | + nodeDefinition:'=', | |
67 | + required:'=ngRequired', | |
68 | + readonly:'=ngReadonly' | |
69 | + }, | |
70 | + link: linker | |
71 | + }; | |
72 | + | |
73 | +} | ... | ... |
1 | +<!-- | |
2 | + | |
3 | + Copyright © 2016-2018 The Thingsboard Authors | |
4 | + | |
5 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
6 | + you may not use this file except in compliance with the License. | |
7 | + You may obtain a copy of the License at | |
8 | + | |
9 | + http://www.apache.org/licenses/LICENSE-2.0 | |
10 | + | |
11 | + Unless required by applicable law or agreed to in writing, software | |
12 | + distributed under the License is distributed on an "AS IS" BASIS, | |
13 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
14 | + See the License for the specific language governing permissions and | |
15 | + limitations under the License. | |
16 | + | |
17 | +--> | |
18 | + | |
19 | +<tb-rule-node-defined-config ng-if="useDefinedDirective()" | |
20 | + ng-model="configuration" | |
21 | + rule-node-directive="{{nodeDefinition.configDirective}}" | |
22 | + ng-required="required" | |
23 | + ng-readonly="readonly"> | |
24 | +</tb-rule-node-defined-config> | |
25 | +<div class="tb-rulenode-directive-error" ng-if="definedDirectiveError">{{definedDirectiveError}}</div> | |
26 | +<tb-json-object-edit ng-if="!useDefinedDirective()" | |
27 | + class="tb-rule-node-configuration-json" | |
28 | + ng-model="configuration" | |
29 | + label="{{ 'rulenode.configuration' | translate }}" | |
30 | + ng-required="required" | |
31 | + fill-height="true"> | |
32 | +</tb-json-object-edit> | ... | ... |
1 | +/* | |
2 | + * Copyright © 2016-2018 The Thingsboard Authors | |
3 | + * | |
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | + * you may not use this file except in compliance with the License. | |
6 | + * You may obtain a copy of the License at | |
7 | + * | |
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | + * | |
10 | + * Unless required by applicable law or agreed to in writing, software | |
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | + * See the License for the specific language governing permissions and | |
14 | + * limitations under the License. | |
15 | + */ | |
16 | + | |
17 | +const SNAKE_CASE_REGEXP = /[A-Z]/g; | |
18 | + | |
19 | +/*@ngInject*/ | |
20 | +export default function RuleNodeDefinedConfigDirective($compile) { | |
21 | + | |
22 | + var linker = function (scope, element, attrs, ngModelCtrl) { | |
23 | + | |
24 | + attrs.$observe('ruleNodeDirective', function() { | |
25 | + loadTemplate(); | |
26 | + }); | |
27 | + | |
28 | + scope.$watch('configuration', function (newVal, prevVal) { | |
29 | + if (!angular.equals(newVal, prevVal)) { | |
30 | + ngModelCtrl.$setViewValue(scope.configuration); | |
31 | + } | |
32 | + }); | |
33 | + | |
34 | + ngModelCtrl.$render = function () { | |
35 | + scope.configuration = ngModelCtrl.$viewValue; | |
36 | + }; | |
37 | + | |
38 | + function loadTemplate() { | |
39 | + var directive = snake_case(attrs.ruleNodeDirective, '-'); | |
40 | + var template = `<${directive} ng-model="configuration" ng-required="required" ng-readonly="readonly"></${directive}>`; | |
41 | + element.html(template); | |
42 | + $compile(element.contents())(scope); | |
43 | + } | |
44 | + | |
45 | + function snake_case(name, separator) { | |
46 | + separator = separator || '_'; | |
47 | + return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) { | |
48 | + return (pos ? separator : '') + letter.toLowerCase(); | |
49 | + }); | |
50 | + } | |
51 | + }; | |
52 | + | |
53 | + return { | |
54 | + restrict: "E", | |
55 | + require: "^ngModel", | |
56 | + scope: { | |
57 | + required:'=ngRequired', | |
58 | + readonly:'=ngReadonly' | |
59 | + }, | |
60 | + link: linker | |
61 | + }; | |
62 | + | |
63 | +} | ... | ... |
... | ... | @@ -38,11 +38,16 @@ |
38 | 38 | ng-model="ruleNode.debugMode">{{ 'rulenode.debug-mode' | translate }} |
39 | 39 | </md-checkbox> |
40 | 40 | </md-input-container> |
41 | - <tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration" | |
41 | + <tb-rule-node-config ng-model="ruleNode.configuration" | |
42 | + ng-required="true" | |
43 | + node-definition="ruleNode.component.configurationDescriptor.nodeDefinition" | |
44 | + ng-readonly="$root.loading || !isEdit || isReadOnly"> | |
45 | + </tb-rule-node-config> | |
46 | + <!--tb-json-object-edit class="tb-rule-node-configuration-json" ng-model="ruleNode.configuration" | |
42 | 47 | label="{{ 'rulenode.configuration' | translate }}" |
43 | 48 | ng-required="true" |
44 | 49 | fill-height="true"> |
45 | - </tb-json-object-edit> | |
50 | + </tb-json-object-edit--> | |
46 | 51 | <md-input-container class="md-block"> |
47 | 52 | <label translate>rulenode.description</label> |
48 | 53 | <textarea ng-model="ruleNode.additionalInfo.description" rows="2"></textarea> | ... | ... |