@5minds/node-red-dashboard-2-processcube-dynamic-form 1.0.13 → 1.0.15-feature-ca6857-lzbajzx1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,53 +1,59 @@
1
1
  <script type="text/javascript">
2
- RED.nodes.registerType('dynamic-form', {
2
+ RED.nodes.registerType('ui-dynamic-form', {
3
3
  category: 'ProcessCube UI',
4
4
  color: '#00aed7',
5
5
  defaults: {
6
- name: { value: "" },
6
+ name: { value: '' },
7
7
  group: { type: 'ui-group', required: true },
8
8
  order: { value: 0 },
9
9
  options: {
10
- value: [{ label: ''}],
10
+ value: [{ label: '' }],
11
11
  validate: function (v) {
12
- const unique = new Set(v.map(function (o) { return o.label }));
12
+ const unique = new Set(
13
+ v.map(function (o) {
14
+ return o.label;
15
+ })
16
+ );
13
17
  return v.length === unique.size;
14
- }
18
+ },
19
+ },
20
+ waiting_title: { value: 'Waiting for Warten auf den Usertask...' },
21
+ waiting_info: {
22
+ value: 'Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist.',
15
23
  },
16
- waiting_title: { value: "Waiting for Warten auf den Usertask..." },
17
- waiting_info: { value: "Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist." },
18
24
  width: {
19
25
  value: 0,
20
26
  validate: function (v) {
21
- const width = v || 0
22
- const currentGroup = $('#node-input-group').val() || this.group
23
- const groupNode = RED.nodes.node(currentGroup)
24
- const valid = !groupNode || +width <= +groupNode.width
25
- $('#node-input-size').toggleClass('input-error', !valid)
26
- return valid
27
- }
27
+ const width = v || 0;
28
+ const currentGroup = $('#node-input-group').val() || this.group;
29
+ const groupNode = RED.nodes.node(currentGroup);
30
+ const valid = !groupNode || +width <= +groupNode.width;
31
+ $('#node-input-size').toggleClass('input-error', !valid);
32
+ return valid;
33
+ },
28
34
  },
29
35
  height: { value: 0 },
30
- outputs: { value: 1}
36
+ outputs: { value: 1 },
31
37
  },
32
38
  inputs: 1,
33
39
  outputs: 1,
34
- outputLabels: function(index) {
35
- return this.options[index].label
40
+ outputLabels: function (index) {
41
+ return this.options[index].label;
36
42
  },
37
- icon: "file.svg",
38
- label: function() {
39
- return this.name || "dynamic-form";
43
+ icon: 'file.svg',
44
+ label: function () {
45
+ return this.name || 'dynamic-form';
40
46
  },
41
47
  oneditprepare: function () {
42
48
  $('#node-input-size').elementSizer({
43
49
  width: '#node-input-width',
44
50
  height: '#node-input-height',
45
- group: '#node-input-group'
51
+ group: '#node-input-group',
46
52
  });
47
53
 
48
54
  function generateOption(i, option) {
49
55
  const container = $('<li/>', {
50
- style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px;'
56
+ style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px;',
51
57
  });
52
58
 
53
59
  // Create input fields for value and label
@@ -55,30 +61,57 @@
55
61
  $('<input/>', {
56
62
  class: 'node-input-option-label',
57
63
  type: 'text',
58
- style: 'margin-left:7px; width:calc(100% - 48px);',
64
+ style: 'margin-left:7px; width:calc(29%);',
59
65
  placeholder: 'Label',
60
- value: option.label
61
- }).appendTo(row).typedInput({
62
- type: 'str', types: ['str']
63
- });
66
+ value: option.label,
67
+ })
68
+ .appendTo(row)
69
+ .typedInput({
70
+ type: 'str',
71
+ types: ['str'],
72
+ });
64
73
 
65
- // $('<input/>', {
66
- // class: 'node-input-option-condition',
67
- // type: 'text',
68
- // style: 'margin-left:7px; width:calc(50% - 32px);',
69
- // placeholder: 'Condition',
70
- // value: option.condition
71
- // }).appendTo(row).typedInput({
72
- // type: 'str', types: ['str']
73
- // });
74
+ $('<input/>', {
75
+ class: 'node-input-option-condition',
76
+ type: 'text',
77
+ style: 'margin-left:7px; width:calc(29%);',
78
+ placeholder: 'Condition',
79
+ value: option.condition,
80
+ })
81
+ .appendTo(row)
82
+ .typedInput({
83
+ type: 'str',
84
+ types: ['str'],
85
+ });
86
+
87
+ $('<input/>', {
88
+ class: 'node-input-option-error',
89
+ type: 'text',
90
+ style: 'margin-left:7px; width:calc(29%);',
91
+ placeholder: 'Error message',
92
+ value: option.errorMessage,
93
+ })
94
+ .appendTo(row)
95
+ .typedInput({
96
+ type: 'str',
97
+ types: ['str'],
98
+ });
74
99
 
75
100
  // Create delete button for the option
76
- const finalSpan = $('<span/>', { style: 'float:right; margin-right:8px;' }).appendTo(row);
77
- const deleteButton = $('<a/>', { href: '#', class: 'editor-button editor-button-small', style: 'margin-top:7px; margin-left:5px;' }).appendTo(finalSpan);
101
+ const finalSpan = $('<span/>', {
102
+ style: 'float:right; margin-right:8px;',
103
+ }).appendTo(row);
104
+ const deleteButton = $('<a/>', {
105
+ href: '#',
106
+ class: 'editor-button editor-button-small',
107
+ style: 'margin-top:7px; margin-left:5px;',
108
+ }).appendTo(finalSpan);
78
109
  $('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton);
79
110
 
80
111
  deleteButton.click(function () {
81
- container.css({ background: 'var(--red-ui-secondary-background-inactive, #fee)' });
112
+ container.css({
113
+ background: 'var(--red-ui-secondary-background-inactive, #fee)',
114
+ });
82
115
  container.fadeOut(300, function () {
83
116
  $(this).remove();
84
117
  });
@@ -89,7 +122,9 @@
89
122
 
90
123
  $('#node-input-add-option').click(function () {
91
124
  generateOption($('#node-input-option-container').children().length + 1, {});
92
- $('#node-input-option-container-div').scrollTop($('#node-input-option-container-div').get(0).scrollHeight);
125
+ $('#node-input-option-container-div').scrollTop(
126
+ $('#node-input-option-container-div').get(0).scrollHeight
127
+ );
93
128
  });
94
129
 
95
130
  for (let i = 0; i < this.options.length; i++) {
@@ -100,7 +135,7 @@
100
135
  $('#node-input-option-container').sortable({
101
136
  axis: 'y',
102
137
  handle: '.node-input-option-handle',
103
- cursor: 'move'
138
+ cursor: 'move',
104
139
  });
105
140
  },
106
141
  oneditsave: function () {
@@ -111,18 +146,19 @@
111
146
  const option = $(this);
112
147
  const o = {
113
148
  label: option.find('.node-input-option-label').val(),
114
- // condition: option.find('.node-input-option-condition').val()
149
+ condition: option.find('.node-input-option-condition').val(),
150
+ errorMessage: option.find('.node-input-option-error').val(),
115
151
  };
116
152
 
117
153
  node.options.push(o);
118
154
  });
119
155
 
120
- this.outputs = node.options.length || 1
156
+ this.outputs = node.options.length || 1;
121
157
  },
122
158
  });
123
159
  </script>
124
160
 
125
- <script type="text/html" data-template-name="dynamic-form">
161
+ <script type="text/html" data-template-name="ui-dynamic-form">
126
162
  <div class="form-row">
127
163
  <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
128
164
  <input type="text" id="node-input-name" placeholder="Name">
@@ -132,7 +168,7 @@
132
168
  <input type="text" id="node-input-group">
133
169
  </div>
134
170
  <div class="form-row">
135
- <label><i class="fa fa-object-group"></i> <span data-i18n="dynamic-form.label.size"></label>
171
+ <label><i class="fa fa-object-group"></i> <span data-i18n="ui-dynamic-form.label.size"></label>
136
172
  <input type="hidden" id="node-input-width">
137
173
  <input type="hidden" id="node-input-height">
138
174
  <button class="editor-button" id="node-input-size"></button>
@@ -144,13 +180,13 @@
144
180
  <span id="valWarning" style="color: var(--red-ui-text-color-error, #910000)"><b>All Values must be unique.</b></span>
145
181
  <ol id="node-input-option-container" style="list-style-type:none; margin:0;"></ol>
146
182
  </div>
147
- <a
183
+ <!-- <a
148
184
  data-html="true"
149
185
  title="Dynamic Property: Send 'msg.options' in order to override this property."
150
186
  class="red-ui-button ui-node-popover-title"
151
187
  style="margin-left: 4px; cursor: help; font-size: 0.625rem; border-radius: 50%; width: 24px; height: 24px; display: inline-flex; justify-content: center; align-items: center;">
152
188
  <i style="font-family: ui-serif;">fx</i>
153
- </a>
189
+ </a> -->
154
190
  </div>
155
191
  <!-- Add Option Button -->
156
192
  <div class="form-row">
@@ -1,48 +1,48 @@
1
1
  module.exports = function (RED) {
2
- function DynamicFormNode (config) {
3
- RED.nodes.createNode(this, config)
2
+ function DynamicFormNode(config) {
3
+ RED.nodes.createNode(this, config);
4
4
 
5
- const node = this
5
+ const node = this;
6
6
 
7
- // which group are we rendering this widget
8
- const group = RED.nodes.getNode(config.group)
7
+ // which group are we rendering this widget
8
+ const group = RED.nodes.getNode(config.group);
9
9
 
10
- const base = group.getBase()
10
+ const base = group.getBase();
11
11
 
12
- // server-side event handlers
13
- const evts = {
14
- onAction: true,
15
- onInput: function (msg, send, done) {
16
- // store the latest value in our Node-RED datastore
17
- base.stores.data.save(base, node, msg)
18
- },
19
- onSocket: {
20
- 'my-custom-event': function (conn, id, msg) {
21
- console.info('"my-custom-event" received:', conn.id, id, msg)
22
- console.info('conn.id:', conn.id)
23
- console.info('id:', id)
24
- console.info('msg:', msg)
25
- console.info('node.id:', node.id)
26
- // emit a msg in Node-RED from this node
27
- node.send(msg)
28
- }
29
- }
30
- }
12
+ // server-side event handlers
13
+ const evts = {
14
+ onAction: true,
15
+ onInput: function (msg, send, done) {
16
+ // store the latest value in our Node-RED datastore
17
+ base.stores.data.save(base, node, msg);
18
+ },
19
+ onSocket: {
20
+ "my-custom-event": function (conn, id, msg) {
21
+ console.info('"my-custom-event" received:', conn.id, id, msg);
22
+ console.info("conn.id:", conn.id);
23
+ console.info("id:", id);
24
+ console.info("msg:", msg);
25
+ console.info("node.id:", node.id);
26
+ // emit a msg in Node-RED from this node
27
+ node.send(msg);
28
+ },
29
+ },
30
+ };
31
31
 
32
- // inform the dashboard UI that we are adding this node
33
- if (group) {
34
- group.register(node, config, evts)
35
- } else {
36
- node.error('No group configured')
37
- }
32
+ // inform the dashboard UI that we are adding this node
33
+ if (group) {
34
+ group.register(node, config, evts);
35
+ } else {
36
+ node.error("No group configured");
38
37
  }
38
+ }
39
39
 
40
- RED.nodes.registerType('dynamic-form', DynamicFormNode, {
41
- defaults: {
42
- outputs: { value: 1 }
43
- },
44
- outputs: function(config) {
45
- return config.outputs || 1;
46
- }
47
- })
48
- }
40
+ RED.nodes.registerType("ui-dynamic-form", DynamicFormNode, {
41
+ defaults: {
42
+ outputs: { value: 1 },
43
+ },
44
+ outputs: function (config) {
45
+ return config.outputs || 1;
46
+ },
47
+ });
48
+ };
package/package.json CHANGED
@@ -1,85 +1,85 @@
1
1
  {
2
- "name": "@5minds/node-red-dashboard-2-processcube-dynamic-form",
3
- "version": "1.0.13",
4
- "description": "The ui component for the ProcessCube dynamic-form",
5
- "keywords": [
6
- "processcube",
7
- "dynamic-form",
8
- "node-red",
9
- "node-red-dashboard-2"
10
- ],
11
- "repository": {
12
- "type": "git",
13
- "url": "https://github.com/5minds/.gitnode-red-dashboard-2-processcube-dynamic-form"
2
+ "name": "@5minds/node-red-dashboard-2-processcube-dynamic-form",
3
+ "version": "1.0.15-feature-ca6857-lzbajzx1",
4
+ "description": "The ui component for the ProcessCube dynamic-form",
5
+ "keywords": [
6
+ "processcube",
7
+ "dynamic-form",
8
+ "node-red",
9
+ "node-red-dashboard-2"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/5minds/.gitnode-red-dashboard-2-processcube-dynamic-form"
14
+ },
15
+ "license": "Apache-2.0",
16
+ "author": {
17
+ "name": "Martin Moellenbeck",
18
+ "url": "https://github.com/moellenbeck"
19
+ },
20
+ "contributors": [
21
+ {
22
+ "name": "Robin Lenz",
23
+ "url": "https://github.com/roblen45"
14
24
  },
15
- "license": "Apache-2.0",
16
- "author": {
17
- "name": "Martin Moellenbeck",
18
- "url": "https://github.com/moellenbeck"
19
- },
20
- "contributors": [
21
- {
22
- "name": "Robin Lenz",
23
- "url": "https://github.com/roblen45"
24
- },
25
- {
26
- "name": "Luis Thieme",
27
- "url": "https://github.com/luisthieme"
28
- }
29
- ],
30
- "exports": {
31
- "import": "./resources/dynamic-form.esm.js",
32
- "require": "./resources/dynamic-form.umd.js"
33
- },
34
- "files": [
35
- "dist/*",
36
- "nodes/*",
37
- "ui/*",
38
- "resources/*"
39
- ],
40
- "scripts": {
41
- "build": "vite build",
42
- "build:dev": "NODE_ENV=development vite build",
43
- "dev": "NODE_ENV=development vite build --watch",
44
- "dev:prod": "vite build --watch",
45
- "lint": "npm run lint:js && npm run lint:package",
46
- "lint:fix": "npm run lint:js:fix && npm run lint:package:fix",
47
- "lint:js": "eslint --ext .js,.vue,.cjs,.mjs .",
48
- "lint:js:fix": "yarn lint:js --fix",
49
- "lint:package": "sort-package-json --check 'package.json'",
50
- "lint:package:fix": "sort-package-json 'package.json'"
51
- },
52
- "dependencies": {
53
- "to-title-case": "^1.0.0",
54
- "vue": "^3.3.8",
55
- "vuex": "^4.1.0"
56
- },
57
- "devDependencies": {
58
- "@vitejs/plugin-vue": "^4.5.0",
59
- "eslint": "^8.53.0",
60
- "eslint-config-standard": "^17.1.0",
61
- "eslint-plugin-import": "^2.29.0",
62
- "eslint-plugin-n": "^16.3.1",
63
- "eslint-plugin-vue": "^9.18.1",
64
- "vite": "^5.0.13",
65
- "vite-plugin-css-injected-by-js": "^3.3.0"
66
- },
67
- "engines": {
68
- "node": ">=14"
69
- },
70
- "node-red": {
71
- "version": ">=3.0.0",
72
- "nodes": {
73
- "dynamic-form": "nodes/dynamic-form.js"
74
- }
75
- },
76
- "node-red-dashboard-2": {
77
- "version": "1.0.0",
78
- "widgets": {
79
- "dynamic-form": {
80
- "output": "dynamic-form.umd.js",
81
- "component": "DynamicForm"
82
- }
83
- }
25
+ {
26
+ "name": "Luis Thieme",
27
+ "url": "https://github.com/luisthieme"
28
+ }
29
+ ],
30
+ "exports": {
31
+ "import": "./resources/dynamic-form.esm.js",
32
+ "require": "./resources/dynamic-form.umd.js"
33
+ },
34
+ "files": [
35
+ "dist/*",
36
+ "nodes/*",
37
+ "ui/*",
38
+ "resources/*"
39
+ ],
40
+ "scripts": {
41
+ "build": "vite build",
42
+ "build:dev": "NODE_ENV=development vite build",
43
+ "dev": "NODE_ENV=development vite build --watch",
44
+ "dev:prod": "vite build --watch",
45
+ "lint": "npm run lint:js && npm run lint:package",
46
+ "lint:fix": "npm run lint:js:fix && npm run lint:package:fix",
47
+ "lint:js": "eslint --ext .js,.vue,.cjs,.mjs .",
48
+ "lint:js:fix": "yarn lint:js --fix",
49
+ "lint:package": "sort-package-json --check 'package.json'",
50
+ "lint:package:fix": "sort-package-json 'package.json'"
51
+ },
52
+ "dependencies": {
53
+ "to-title-case": "^1.0.0",
54
+ "vue": "^3.3.8",
55
+ "vuex": "^4.1.0"
56
+ },
57
+ "devDependencies": {
58
+ "@vitejs/plugin-vue": "^4.5.0",
59
+ "eslint": "^8.53.0",
60
+ "eslint-config-standard": "^17.1.0",
61
+ "eslint-plugin-import": "^2.29.0",
62
+ "eslint-plugin-n": "^16.3.1",
63
+ "eslint-plugin-vue": "^9.18.1",
64
+ "vite": "^5.0.13",
65
+ "vite-plugin-css-injected-by-js": "^3.3.0"
66
+ },
67
+ "engines": {
68
+ "node": ">=14"
69
+ },
70
+ "node-red": {
71
+ "version": ">=3.0.0",
72
+ "nodes": {
73
+ "ui-dynamic-form": "nodes/dynamic-form.js"
74
+ }
75
+ },
76
+ "node-red-dashboard-2": {
77
+ "version": "1.0.0",
78
+ "widgets": {
79
+ "ui-dynamic-form": {
80
+ "output": "ui-dynamic-form.umd.js",
81
+ "component": "DynamicForm"
82
+ }
84
83
  }
84
+ }
85
85
  }
@@ -0,0 +1,2 @@
1
+ (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(".dynamic-form-wrapper[data-v-0d226515]{padding:10px;margin:10px;border:1px solid black}.dynamic-form-class[data-v-0d226515]{color:green;font-weight:700}h1[data-v-0d226515]{margin-bottom:10px}h2[data-v-0d226515]{margin-top:1.5rem;margin-bottom:.75rem}h3[data-v-0d226515]{margin-top:1rem}p[data-v-0d226515]{margin-bottom:5px}ul li[data-v-0d226515]{list-style-type:circle;list-style-position:inside;margin-left:15px}pre[data-v-0d226515]{padding:12px;margin:12px;background-color:#eee}code[data-v-0d226515]{font-size:.825rem;color:#ae0000}")),document.head.appendChild(e)}}catch(a){console.error("vite-plugin-css-injected-by-js",a)}})();
2
+ (function(n,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("vuex")):typeof define=="function"&&define.amd?define(["exports","vue","vuex"],e):(n=typeof globalThis<"u"?globalThis:n||self,e(n["ui-dynamic-form"]={},n.Vue,n.vuex))})(this,function(n,e,h){"use strict";const f=(t,r)=>{const s=t.__vccOpts||t;for(const[d,i]of r)s[d]=i;return s},u={name:"DynamicForm",inject:["$socket"],props:{id:{type:String,required:!0},props:{type:Object,default:()=>({})},state:{type:Object,default:()=>({enabled:!1,visible:!1})}},setup(t){console.info("DynamicForm setup with:",t),console.debug("Vue function loaded correctly",e.markRaw)},data(){return{actions:[],form:{},formData:{},error:!1,errorMsg:""}},computed:{...h.mapState("data",["messages"]),waiting_title(){return this.props.waiting_title||"Warten auf den Usertask..."},waiting_info(){return this.props.waiting_info||"Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist."}},mounted(){this.$socket.on("widget-load:"+this.id,t=>{this.init(),this.$store.commit("data/bind",{widgetId:this.id,msg:t})}),this.$socket.on("msg-input:"+this.id,t=>{this.init(),t.payload&&t.payload.userTask&&t.payload.userTask.startToken&&(this.formData={...t.payload.userTask.startToken},console.info(this.formData)),this.$store.commit("data/bind",{widgetId:this.id,msg:t})}),this.$socket.emit("widget-load",this.id)},unmounted(){var t,r;(t=this.$socket)==null||t.off("widget-load"+this.id),(r=this.$socket)==null||r.off("msg-input:"+this.id)},methods:{hasUserTask(){return this.messages&&this.messages[this.id]&&this.messages[this.id].payload.userTask},userTask(){return this.hasUserTask()?this.messages[this.id].payload.userTask:{}},fields(){return(this.hasUserTask()?this.userTask().userTaskConfig.formFields:[]).map(s=>({...s,component:_(s.type),items:k(s.type,s)}))},hasFields(){return this.messages&&this.messages[this.id]&&this.messages[this.id].payload.userTask!==void 0},send(t,r){const s=[];s[r]=t,console.info(s),this.$socket.emit("widget-action",this.id,s)},init(){console.info(this.props.options),this.actions=this.props.options},actionFn(t){this.checkCondition(t.condition)?(this.showError(!1,""),this.send({payload:{formData:this.formData,userTask:this.userTask()}},this.actions.findIndex(r=>r.label===t.label))):this.showError(!0,t.errorMessage)},checkCondition(t){try{return!!Function("fields",'"use strict"; return ('+t+")")(this.formData)}catch(r){return console.error("Error while evaluating condition: "+r),!1}},showError(t,r){this.error=t,this.errorMsg=r}}};function k(t,r){return t==="enum"?r.enumValues.map(s=>({title:s.name,value:s.id})):null}function _(t){switch(t){case"string":return"v-text-field";case"long":case"date":return"v-text-field";case"enum":return"v-select";case"boolean":return"v-checkbox";case"text":return"v-text-field";case"select":return"v-select";case"checkbox":return"v-checkbox";case"radio":return"v-radio";case"switch":return"v-switch";case"slider":return"v-slider";case"time":return"v-time-picker";case"datetime":return"v-datetime-picker";case"color":return"v-color-picker";case"file":return"v-file-input";case"textarea":return"v-textarea";case"password":return"v-text-field";case"number":return"v-text-field";case"email":return"v-text-field";case"tel":return"v-text-field";case"url":return"v-text-field";default:return"v-text-field"}}const y={className:"dynamic-form-wrapper"},g={key:0},x={key:1};function w(t,r,s,d,i,a){const b=e.resolveComponent("v-col"),l=e.resolveComponent("v-row"),m=e.resolveComponent("v-alert"),B=e.resolveComponent("v-btn"),C=e.resolveComponent("v-form");return e.openBlock(),e.createElementBlock("div",y,[a.hasFields()?(e.openBlock(),e.createElementBlock("p",g,[e.createVNode(C,{ref:"form",modelValue:i.form,"onUpdate:modelValue":r[0]||(r[0]=o=>i.form=o)},{default:e.withCtx(()=>[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(a.fields(),(o,c)=>(e.openBlock(),e.createBlock(l,{key:c},{default:e.withCtx(()=>[e.createVNode(b,{cols:"12"},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(o.component),{id:o.id,modelValue:i.formData[o.id],"onUpdate:modelValue":p=>i.formData[o.id]=p,required:o.required,items:o.items,label:o.label},null,8,["id","modelValue","onUpdate:modelValue","required","items","label"]))]),_:2},1024)]),_:2},1024))),128)),e.createVNode(l,{style:{padding:"12px"}},{default:e.withCtx(()=>[i.error?(e.openBlock(),e.createBlock(m,{key:0,type:"error"},{default:e.withCtx(()=>[e.createTextVNode("Error: "+e.toDisplayString(i.errorMsg),1)]),_:1})):e.createCommentVNode("",!0)]),_:1}),e.createVNode(l,{style:{display:"flex",gap:"8px",padding:"12px"}},{default:e.withCtx(()=>[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(i.actions,(o,c)=>(e.openBlock(),e.createElementBlock("div",{key:c,style:{"flex-grow":"1"}},[(e.openBlock(),e.createBlock(B,{key:c,style:{width:"100%"},onClick:p=>a.actionFn(o)},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(o.label),1)]),_:2},1032,["onClick"]))]))),128))]),_:1})]),_:1},8,["modelValue"])])):(e.openBlock(),e.createElementBlock("p",x,[e.createVNode(m,{text:a.waiting_info,title:a.waiting_title},null,8,["text","title"])]))])}const T=f(u,[["render",w],["__scopeId","data-v-0d226515"]]);n.DynamicForm=T,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})});
@@ -1,226 +1,233 @@
1
1
  <template>
2
- <!-- Component must be wrapped in a block so props such as className and style can be passed in from parent -->
3
- <div className="dynamic-form-wrapper">
4
- <p v-if="hasFields()">
5
- <v-form ref="form" v-model="form">
6
- <v-row v-for="(field, index) in fields()" :key="index">
7
- <v-col cols="12">
8
- <component
9
- :is="field.component"
10
- :id="field.id"
11
- v-model="formData[field.id]"
12
- :required="field.required"
13
- :items="field.items"
14
- :label="field.label" />
15
- </v-col>
16
- </v-row>
17
-
18
- <v-row style="display: flex; gap: 8px; padding: 12px">
19
- <div
20
- v-for="(action, index) in actions"
21
- :key="index"
22
- style="flex-grow: 1">
23
- <v-btn
24
- :key="index"
25
- style="width: 100%"
26
- @click="actionFn(action.label)">
27
- {{ action.label }}
28
- </v-btn>
29
- </div>
30
- </v-row>
31
- </v-form>
32
- </p>
33
- <p v-else>
34
- <v-alert :text="waiting_info" :title="waiting_title" />
35
- </p>
36
- </div>
2
+ <!-- Component must be wrapped in a block so props such as className and style can be passed in from parent -->
3
+ <div className="dynamic-form-wrapper">
4
+ <p v-if="hasFields()">
5
+ <v-form ref="form" v-model="form">
6
+ <v-row v-for="(field, index) in fields()" :key="index">
7
+ <v-col cols="12">
8
+ <component
9
+ :is="field.component"
10
+ :id="field.id"
11
+ v-model="formData[field.id]"
12
+ :required="field.required"
13
+ :items="field.items"
14
+ :label="field.label"
15
+ />
16
+ </v-col>
17
+ </v-row>
18
+ <v-row style="padding: 12px">
19
+ <v-alert v-if="error" type="error">Error: {{ errorMsg }}</v-alert>
20
+ </v-row>
21
+ <v-row style="display: flex; gap: 8px; padding: 12px">
22
+ <div v-for="(action, index) in actions" :key="index" style="flex-grow: 1">
23
+ <v-btn :key="index" style="width: 100%" @click="actionFn(action)">
24
+ {{ action.label }}
25
+ </v-btn>
26
+ </div>
27
+ </v-row>
28
+ </v-form>
29
+ </p>
30
+ <p v-else>
31
+ <v-alert :text="waiting_info" :title="waiting_title" />
32
+ </p>
33
+ </div>
37
34
  </template>
38
35
 
39
36
  <script>
40
- import { markRaw } from "vue";
41
- import { mapState } from "vuex";
37
+ import { markRaw } from 'vue';
38
+ import { mapState } from 'vuex';
42
39
 
43
40
  export default {
44
- name: "DynamicForm",
45
- inject: ["$socket"],
46
- props: {
47
- /* do not remove entries from this - Dashboard's Layout Manager's will pass this data to your component */
48
- id: { type: String, required: true },
49
- props: { type: Object, default: () => ({}) },
50
- state: {
51
- type: Object,
52
- default: () => ({ enabled: false, visible: false }),
41
+ name: 'DynamicForm',
42
+ inject: ['$socket'],
43
+ props: {
44
+ /* do not remove entries from this - Dashboard's Layout Manager's will pass this data to your component */
45
+ id: { type: String, required: true },
46
+ props: { type: Object, default: () => ({}) },
47
+ state: {
48
+ type: Object,
49
+ default: () => ({ enabled: false, visible: false }),
50
+ },
51
+ },
52
+ setup(props) {
53
+ console.info('DynamicForm setup with:', props);
54
+ console.debug('Vue function loaded correctly', markRaw);
53
55
  },
54
- },
55
- setup(props) {
56
- console.info("DynamicForm setup with:", props);
57
- console.debug("Vue function loaded correctly", markRaw);
58
- },
59
- data() {
60
- return {
61
- actions: [],
62
- form: {},
63
- formData: {},
64
- };
65
- },
66
- computed: {
67
- ...mapState("data", ["messages"]),
68
- waiting_title() {
69
- return this.props.waiting_title || "Warten auf den Usertask..."
56
+ data() {
57
+ return {
58
+ actions: [],
59
+ form: {},
60
+ formData: {},
61
+ error: false,
62
+ errorMsg: '',
63
+ };
70
64
  },
71
- waiting_info() {
72
- return (
73
- this.props.waiting_info ||
74
- "Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist."
75
- );
65
+ computed: {
66
+ ...mapState('data', ['messages']),
67
+ waiting_title() {
68
+ return this.props.waiting_title || 'Warten auf den Usertask...';
69
+ },
70
+ waiting_info() {
71
+ return (
72
+ this.props.waiting_info ||
73
+ 'Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist.'
74
+ );
75
+ },
76
76
  },
77
- },
78
- mounted() {
79
- this.$socket.on("widget-load:" + this.id, (msg) => {
80
- this.init()
77
+ mounted() {
78
+ this.$socket.on('widget-load:' + this.id, (msg) => {
79
+ this.init();
81
80
 
82
- this.$store.commit("data/bind", {
83
- widgetId: this.id,
84
- msg,
85
- });
86
- });
87
- this.$socket.on("msg-input:" + this.id, (msg) => {
88
- // store the latest message in our client-side vuex store when we receive a new message
89
- this.init()
81
+ this.$store.commit('data/bind', {
82
+ widgetId: this.id,
83
+ msg,
84
+ });
85
+ });
86
+ this.$socket.on('msg-input:' + this.id, (msg) => {
87
+ // store the latest message in our client-side vuex store when we receive a new message
88
+ this.init();
90
89
 
91
- if (msg.payload && msg.payload.userTask && msg.payload.userTask.startToken) {
92
- //this.formData = { ...msg.payload.userTask.startToken.formData };
93
- this.formData = { ...msg.payload.userTask.startToken }
94
- console.info(this.formData)
95
- }
90
+ if (msg.payload && msg.payload.userTask && msg.payload.userTask.startToken) {
91
+ //this.formData = { ...msg.payload.userTask.startToken.formData };
92
+ this.formData = { ...msg.payload.userTask.startToken };
93
+ console.info(this.formData);
94
+ }
96
95
 
97
- this.$store.commit("data/bind", {
98
- widgetId: this.id,
99
- msg,
100
- });
101
- });
102
- // tell Node-RED that we're loading a new instance of this widget
103
- this.$socket.emit("widget-load", this.id);
104
- },
105
- unmounted() {
106
- /* Make sure, any events you subscribe to on SocketIO are unsubscribed to here */
107
- this.$socket?.off("widget-load" + this.id);
108
- this.$socket?.off("msg-input:" + this.id);
109
- },
110
- methods: {
111
- hasUserTask() {
112
- return (
113
- this.messages &&
114
- this.messages[this.id] &&
115
- this.messages[this.id].payload.userTask
116
- );
96
+ this.$store.commit('data/bind', {
97
+ widgetId: this.id,
98
+ msg,
99
+ });
100
+ });
101
+ // tell Node-RED that we're loading a new instance of this widget
102
+ this.$socket.emit('widget-load', this.id);
117
103
  },
118
- userTask() {
119
- return this.hasUserTask() ? this.messages[this.id].payload.userTask : {};
104
+ unmounted() {
105
+ /* Make sure, any events you subscribe to on SocketIO are unsubscribed to here */
106
+ this.$socket?.off('widget-load' + this.id);
107
+ this.$socket?.off('msg-input:' + this.id);
120
108
  },
121
- fields() {
122
- const aFields = this.hasUserTask()
123
- ? this.userTask().userTaskConfig.formFields
124
- : [];
109
+ methods: {
110
+ hasUserTask() {
111
+ return this.messages && this.messages[this.id] && this.messages[this.id].payload.userTask;
112
+ },
113
+ userTask() {
114
+ return this.hasUserTask() ? this.messages[this.id].payload.userTask : {};
115
+ },
116
+ fields() {
117
+ const aFields = this.hasUserTask() ? this.userTask().userTaskConfig.formFields : [];
125
118
 
126
- const fieldMap = aFields.map((field) => ({
127
- ...field,
128
- component: mapFieldTypes(field.type),
129
- items: mapItems(field.type, field),
130
- }));
119
+ const fieldMap = aFields.map((field) => ({
120
+ ...field,
121
+ component: mapFieldTypes(field.type),
122
+ items: mapItems(field.type, field),
123
+ }));
131
124
 
132
- return fieldMap;
133
- },
134
- hasFields() {
135
- return (
136
- this.messages &&
137
- this.messages[this.id] &&
138
- this.messages[this.id].payload.userTask !== undefined
139
- );
140
- },
141
- /*
125
+ return fieldMap;
126
+ },
127
+ hasFields() {
128
+ return this.messages && this.messages[this.id] && this.messages[this.id].payload.userTask !== undefined;
129
+ },
130
+ /*
142
131
  widget-action just sends a msg to Node-RED, it does not store the msg state server-side
143
132
  alternatively, you can use widget-change, which will also store the msg in the Node's datastore
144
133
  */
145
- send(msg, index) {
146
- const msgArr = [];
147
- msgArr[index] = msg;
148
- console.info(msgArr);
149
- this.$socket.emit("widget-action", this.id, msgArr);
150
- },
151
- init() {
152
- this.actions = this.props.options;
153
- },
154
- actionFn(action) {
155
- this.send(
156
- { payload: { formData: this.formData, userTask: this.userTask() } },
157
- this.actions.findIndex((element) => element.label === action)
158
- );
134
+ send(msg, index) {
135
+ const msgArr = [];
136
+ msgArr[index] = msg;
137
+ this.$socket.emit('widget-action', this.id, msgArr);
138
+ },
139
+ init() {
140
+ this.actions = this.props.options;
141
+ },
142
+ actionFn(action) {
143
+ if (this.checkCondition(action.condition)) {
144
+ this.showError(false, '');
145
+ this.send(
146
+ { payload: { formData: this.formData, userTask: this.userTask() } },
147
+ this.actions.findIndex((element) => element.label === action.label)
148
+ );
149
+ } else {
150
+ this.showError(true, action.errorMessage);
151
+ }
152
+ },
153
+ checkCondition(condition) {
154
+ try {
155
+ const func = Function('fields', '"use strict"; return (' + condition + ')');
156
+ const result = func(this.formData);
157
+ return Boolean(result);
158
+ } catch (err) {
159
+ console.error('Error while evaluating condition: ' + err);
160
+ return false;
161
+ }
162
+ },
163
+ showError(bool, errMsg) {
164
+ this.error = bool;
165
+ this.errorMsg = errMsg;
166
+ },
159
167
  },
160
- },
161
168
  };
162
169
 
163
170
  function mapItems(type, field) {
164
- if (type === "enum") {
165
- return field.enumValues.map((enumValue) => ({
166
- title: enumValue.name,
167
- value: enumValue.id,
168
- }));
169
- } else {
170
- return null;
171
- }
171
+ if (type === 'enum') {
172
+ return field.enumValues.map((enumValue) => ({
173
+ title: enumValue.name,
174
+ value: enumValue.id,
175
+ }));
176
+ } else {
177
+ return null;
178
+ }
172
179
  }
173
180
 
174
181
  function mapFieldTypes(fieldType) {
175
- switch (fieldType) {
176
- case "string":
177
- return "v-text-field";
178
- case "long":
179
- case "date":
180
- return "v-text-field";
181
- case "enum":
182
- return "v-select";
183
- case "boolean":
184
- return "v-checkbox";
185
- case "text":
186
- return "v-text-field";
187
- case "select":
188
- return "v-select";
189
- case "checkbox":
190
- return "v-checkbox";
191
- case "radio":
192
- return "v-radio";
193
- case "switch":
194
- return "v-switch";
195
- case "slider":
196
- return "v-slider";
197
- case "time":
198
- return "v-time-picker";
199
- case "datetime":
200
- return "v-datetime-picker";
201
- case "color":
202
- return "v-color-picker";
203
- case "file":
204
- return "v-file-input";
205
- case "textarea":
206
- return "v-textarea";
207
- case "password":
208
- return "v-text-field";
209
- case "number":
210
- return "v-text-field";
211
- case "email":
212
- return "v-text-field";
213
- case "tel":
214
- return "v-text-field";
215
- case "url":
216
- return "v-text-field";
217
- default:
218
- return "v-text-field";
219
- }
182
+ switch (fieldType) {
183
+ case 'string':
184
+ return 'v-text-field';
185
+ case 'long':
186
+ case 'date':
187
+ return 'v-text-field';
188
+ case 'enum':
189
+ return 'v-select';
190
+ case 'boolean':
191
+ return 'v-checkbox';
192
+ case 'text':
193
+ return 'v-text-field';
194
+ case 'select':
195
+ return 'v-select';
196
+ case 'checkbox':
197
+ return 'v-checkbox';
198
+ case 'radio':
199
+ return 'v-radio';
200
+ case 'switch':
201
+ return 'v-switch';
202
+ case 'slider':
203
+ return 'v-slider';
204
+ case 'time':
205
+ return 'v-time-picker';
206
+ case 'datetime':
207
+ return 'v-datetime-picker';
208
+ case 'color':
209
+ return 'v-color-picker';
210
+ case 'file':
211
+ return 'v-file-input';
212
+ case 'textarea':
213
+ return 'v-textarea';
214
+ case 'password':
215
+ return 'v-text-field';
216
+ case 'number':
217
+ return 'v-text-field';
218
+ case 'email':
219
+ return 'v-text-field';
220
+ case 'tel':
221
+ return 'v-text-field';
222
+ case 'url':
223
+ return 'v-text-field';
224
+ default:
225
+ return 'v-text-field';
226
+ }
220
227
  }
221
228
  </script>
222
229
 
223
230
  <style scoped>
224
231
  /* CSS is auto scoped, but using named classes is still recommended */
225
- @import "../stylesheets/dynamic-form.css";
232
+ @import '../stylesheets/dynamic-form.css';
226
233
  </style>
@@ -1,44 +1,44 @@
1
1
  .dynamic-form-wrapper {
2
- padding: 10px;
3
- margin: 10px;
4
- border: 1px solid black;
2
+ padding: 10px;
3
+ margin: 10px;
4
+ border: 1px solid black;
5
5
  }
6
6
 
7
7
  .dynamic-form-class {
8
- color: green;
9
- font-weight: bold;
8
+ color: green;
9
+ font-weight: bold;
10
10
  }
11
11
 
12
12
  h1 {
13
- margin-bottom: 10px
13
+ margin-bottom: 10px;
14
14
  }
15
15
 
16
16
  h2 {
17
- margin-top: 1.5rem;
18
- margin-bottom: 0.75rem;
17
+ margin-top: 1.5rem;
18
+ margin-bottom: 0.75rem;
19
19
  }
20
20
 
21
21
  h3 {
22
- margin-top: 1rem;
22
+ margin-top: 1rem;
23
23
  }
24
24
 
25
25
  p {
26
- margin-bottom: 5px;
26
+ margin-bottom: 5px;
27
27
  }
28
28
 
29
29
  ul li {
30
- list-style-type: circle;
31
- list-style-position: inside;
32
- margin-left: 15px;
30
+ list-style-type: circle;
31
+ list-style-position: inside;
32
+ margin-left: 15px;
33
33
  }
34
34
 
35
35
  pre {
36
- padding: 12px;
37
- margin: 12px;
38
- background-color: #eee;
36
+ padding: 12px;
37
+ margin: 12px;
38
+ background-color: #eee;
39
39
  }
40
40
 
41
41
  code {
42
- font-size: 0.825rem;
43
- color: #ae0000;
44
- }
42
+ font-size: 0.825rem;
43
+ color: #ae0000;
44
+ }
@@ -1,2 +0,0 @@
1
- (function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(".dynamic-form-wrapper[data-v-37f46fd0]{padding:10px;margin:10px;border:1px solid black}.dynamic-form-class[data-v-37f46fd0]{color:green;font-weight:700}h1[data-v-37f46fd0]{margin-bottom:10px}h2[data-v-37f46fd0]{margin-top:1.5rem;margin-bottom:.75rem}h3[data-v-37f46fd0]{margin-top:1rem}p[data-v-37f46fd0]{margin-bottom:5px}ul li[data-v-37f46fd0]{list-style-type:circle;list-style-position:inside;margin-left:15px}pre[data-v-37f46fd0]{padding:12px;margin:12px;background-color:#eee}code[data-v-37f46fd0]{font-size:.825rem;color:#ae0000}")),document.head.appendChild(e)}}catch(a){console.error("vite-plugin-css-injected-by-js",a)}})();
2
- (function(n,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("vuex")):typeof define=="function"&&define.amd?define(["exports","vue","vuex"],e):(n=typeof globalThis<"u"?globalThis:n||self,e(n["dynamic-form"]={},n.Vue,n.vuex))})(this,function(n,e,p){"use strict";const u=(t,o)=>{const s=t.__vccOpts||t;for(const[l,r]of o)s[l]=r;return s},f={name:"DynamicForm",inject:["$socket"],props:{id:{type:String,required:!0},props:{type:Object,default:()=>({})},state:{type:Object,default:()=>({enabled:!1,visible:!1})}},setup(t){console.info("DynamicForm setup with:",t),console.debug("Vue function loaded correctly",e.markRaw)},data(){return{actions:[],form:{},formData:{}}},computed:{...p.mapState("data",["messages"]),waiting_title(){return this.props.waiting_title||"Warten auf den Usertask..."},waiting_info(){return this.props.waiting_info||"Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist."}},mounted(){this.$socket.on("widget-load:"+this.id,t=>{this.init(),this.$store.commit("data/bind",{widgetId:this.id,msg:t})}),this.$socket.on("msg-input:"+this.id,t=>{this.init(),t.payload&&t.payload.userTask&&t.payload.userTask.startToken&&(this.formData={...t.payload.userTask.startToken},console.info(this.formData)),this.$store.commit("data/bind",{widgetId:this.id,msg:t})}),this.$socket.emit("widget-load",this.id)},unmounted(){var t,o;(t=this.$socket)==null||t.off("widget-load"+this.id),(o=this.$socket)==null||o.off("msg-input:"+this.id)},methods:{hasUserTask(){return this.messages&&this.messages[this.id]&&this.messages[this.id].payload.userTask},userTask(){return this.hasUserTask()?this.messages[this.id].payload.userTask:{}},fields(){return(this.hasUserTask()?this.userTask().userTaskConfig.formFields:[]).map(s=>({...s,component:k(s.type),items:h(s.type,s)}))},hasFields(){return this.messages&&this.messages[this.id]&&this.messages[this.id].payload.userTask!==void 0},send(t,o){const s=[];s[o]=t,console.info(s),this.$socket.emit("widget-action",this.id,s)},init(){this.actions=this.props.options},actionFn(t){this.send({payload:{formData:this.formData,userTask:this.userTask()}},this.actions.findIndex(o=>o.label===t))}}};function h(t,o){return t==="enum"?o.enumValues.map(s=>({title:s.name,value:s.id})):null}function k(t){switch(t){case"string":return"v-text-field";case"long":case"date":return"v-text-field";case"enum":return"v-select";case"boolean":return"v-checkbox";case"text":return"v-text-field";case"select":return"v-select";case"checkbox":return"v-checkbox";case"radio":return"v-radio";case"switch":return"v-switch";case"slider":return"v-slider";case"time":return"v-time-picker";case"datetime":return"v-datetime-picker";case"color":return"v-color-picker";case"file":return"v-file-input";case"textarea":return"v-textarea";case"password":return"v-text-field";case"number":return"v-text-field";case"email":return"v-text-field";case"tel":return"v-text-field";case"url":return"v-text-field";default:return"v-text-field"}}const _={className:"dynamic-form-wrapper"},y={key:0},x={key:1};function g(t,o,s,l,r,a){const T=e.resolveComponent("v-col"),d=e.resolveComponent("v-row"),b=e.resolveComponent("v-btn"),B=e.resolveComponent("v-form"),C=e.resolveComponent("v-alert");return e.openBlock(),e.createElementBlock("div",_,[a.hasFields()?(e.openBlock(),e.createElementBlock("p",y,[e.createVNode(B,{ref:"form",modelValue:r.form,"onUpdate:modelValue":o[0]||(o[0]=i=>r.form=i)},{default:e.withCtx(()=>[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(a.fields(),(i,c)=>(e.openBlock(),e.createBlock(d,{key:c},{default:e.withCtx(()=>[e.createVNode(T,{cols:"12"},{default:e.withCtx(()=>[(e.openBlock(),e.createBlock(e.resolveDynamicComponent(i.component),{id:i.id,modelValue:r.formData[i.id],"onUpdate:modelValue":m=>r.formData[i.id]=m,required:i.required,items:i.items,label:i.label},null,8,["id","modelValue","onUpdate:modelValue","required","items","label"]))]),_:2},1024)]),_:2},1024))),128)),e.createVNode(d,{style:{display:"flex",gap:"8px",padding:"12px"}},{default:e.withCtx(()=>[(e.openBlock(!0),e.createElementBlock(e.Fragment,null,e.renderList(r.actions,(i,c)=>(e.openBlock(),e.createElementBlock("div",{key:c,style:{"flex-grow":"1"}},[(e.openBlock(),e.createBlock(b,{key:c,style:{width:"100%"},onClick:m=>a.actionFn(i.label)},{default:e.withCtx(()=>[e.createTextVNode(e.toDisplayString(i.label),1)]),_:2},1032,["onClick"]))]))),128))]),_:1})]),_:1},8,["modelValue"])])):(e.openBlock(),e.createElementBlock("p",x,[e.createVNode(C,{text:a.waiting_info,title:a.waiting_title},null,8,["text","title"])]))])}const w=u(f,[["render",g],["__scopeId","data-v-37f46fd0"]]);n.DynamicForm=w,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})});