@5minds/node-red-dashboard-2-processcube-dynamic-form 1.0.15 → 1.0.16
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.
- package/nodes/dynamic-form.html +184 -172
- package/package.json +81 -81
- package/resources/ui-dynamic-form.umd.js +2 -2
- package/ui/components/DynamicForm.vue +209 -194
package/nodes/dynamic-form.html
CHANGED
|
@@ -1,192 +1,204 @@
|
|
|
1
1
|
<script type="text/javascript">
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
2
|
+
RED.nodes.registerType('ui-dynamic-form', {
|
|
3
|
+
category: 'ProcessCube UI',
|
|
4
|
+
color: '#00aed7',
|
|
5
|
+
defaults: {
|
|
6
|
+
name: { value: '' },
|
|
7
|
+
group: { type: 'ui-group', required: true },
|
|
8
|
+
order: { value: 0 },
|
|
9
|
+
options: {
|
|
10
|
+
value: [{ label: '' }],
|
|
11
|
+
validate: function (v) {
|
|
12
|
+
const unique = new Set(
|
|
13
|
+
v.map(function (o) {
|
|
14
|
+
return o.label;
|
|
15
|
+
})
|
|
16
|
+
);
|
|
17
|
+
return v.length === unique.size;
|
|
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.',
|
|
23
|
+
},
|
|
24
|
+
width: {
|
|
25
|
+
value: 0,
|
|
26
|
+
validate: function (v) {
|
|
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
|
+
},
|
|
34
|
+
},
|
|
35
|
+
height: { value: 0 },
|
|
36
|
+
outputs: { value: 1 },
|
|
18
37
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
"Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist.",
|
|
24
|
-
},
|
|
25
|
-
width: {
|
|
26
|
-
value: 0,
|
|
27
|
-
validate: function (v) {
|
|
28
|
-
const width = v || 0;
|
|
29
|
-
const currentGroup = $("#node-input-group").val() || this.group;
|
|
30
|
-
const groupNode = RED.nodes.node(currentGroup);
|
|
31
|
-
const valid = !groupNode || +width <= +groupNode.width;
|
|
32
|
-
$("#node-input-size").toggleClass("input-error", !valid);
|
|
33
|
-
return valid;
|
|
38
|
+
inputs: 1,
|
|
39
|
+
outputs: 1,
|
|
40
|
+
outputLabels: function (index) {
|
|
41
|
+
return this.options[index].label;
|
|
34
42
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
width: "#node-input-width",
|
|
51
|
-
height: "#node-input-height",
|
|
52
|
-
group: "#node-input-group",
|
|
53
|
-
});
|
|
43
|
+
icon: 'file.svg',
|
|
44
|
+
label: function () {
|
|
45
|
+
return this.name || 'dynamic-form';
|
|
46
|
+
},
|
|
47
|
+
oneditprepare: function () {
|
|
48
|
+
$('#node-input-size').elementSizer({
|
|
49
|
+
width: '#node-input-width',
|
|
50
|
+
height: '#node-input-height',
|
|
51
|
+
group: '#node-input-group',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
function generateOption(i, option) {
|
|
55
|
+
const container = $('<li/>', {
|
|
56
|
+
style: 'background: var(--red-ui-secondary-background, #fff); margin:0; padding:8px 0px 0px;',
|
|
57
|
+
});
|
|
54
58
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// Create input fields for value and label
|
|
60
|
+
const row = $('<div/>').appendTo(container);
|
|
61
|
+
$('<input/>', {
|
|
62
|
+
class: 'node-input-option-label',
|
|
63
|
+
type: 'text',
|
|
64
|
+
style: 'margin-left:7px; width:calc(29%);',
|
|
65
|
+
placeholder: 'Label',
|
|
66
|
+
value: option.label,
|
|
67
|
+
})
|
|
68
|
+
.appendTo(row)
|
|
69
|
+
.typedInput({
|
|
70
|
+
type: 'str',
|
|
71
|
+
types: ['str'],
|
|
72
|
+
});
|
|
60
73
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
types: ["str"],
|
|
74
|
-
});
|
|
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
|
+
});
|
|
75
86
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
});
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
// Create delete button for the option
|
|
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);
|
|
109
|
+
$('<i/>', { class: 'fa fa-remove' }).appendTo(deleteButton);
|
|
96
110
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
111
|
+
deleteButton.click(function () {
|
|
112
|
+
container.css({
|
|
113
|
+
background: 'var(--red-ui-secondary-background-inactive, #fee)',
|
|
114
|
+
});
|
|
115
|
+
container.fadeOut(300, function () {
|
|
116
|
+
$(this).remove();
|
|
117
|
+
});
|
|
118
|
+
});
|
|
105
119
|
|
|
106
|
-
|
|
107
|
-
|
|
120
|
+
$('#node-input-option-container').append(container);
|
|
121
|
+
}
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
$("#node-input-option-container-div").get(0).scrollHeight
|
|
116
|
-
);
|
|
117
|
-
});
|
|
123
|
+
$('#node-input-add-option').click(function () {
|
|
124
|
+
generateOption($('#node-input-option-container').children().length + 1, {});
|
|
125
|
+
$('#node-input-option-container-div').scrollTop(
|
|
126
|
+
$('#node-input-option-container-div').get(0).scrollHeight
|
|
127
|
+
);
|
|
128
|
+
});
|
|
118
129
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
130
|
+
for (let i = 0; i < this.options.length; i++) {
|
|
131
|
+
const option = this.options[i];
|
|
132
|
+
generateOption(i + 1, option);
|
|
133
|
+
}
|
|
123
134
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
$('#node-input-option-container').sortable({
|
|
136
|
+
axis: 'y',
|
|
137
|
+
handle: '.node-input-option-handle',
|
|
138
|
+
cursor: 'move',
|
|
139
|
+
});
|
|
140
|
+
},
|
|
141
|
+
oneditsave: function () {
|
|
142
|
+
const options = $('#node-input-option-container').children();
|
|
143
|
+
const node = this;
|
|
144
|
+
node.options = [];
|
|
145
|
+
options.each(function (i) {
|
|
146
|
+
const option = $(this);
|
|
147
|
+
const o = {
|
|
148
|
+
label: option.find('.node-input-option-label').val(),
|
|
149
|
+
condition: option.find('.node-input-option-condition').val(),
|
|
150
|
+
errorMessage: option.find('.node-input-option-error').val(),
|
|
151
|
+
};
|
|
140
152
|
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
node.options.push(o);
|
|
154
|
+
});
|
|
143
155
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
156
|
+
this.outputs = node.options.length || 1;
|
|
157
|
+
},
|
|
158
|
+
});
|
|
147
159
|
</script>
|
|
148
160
|
|
|
149
161
|
<script type="text/html" data-template-name="ui-dynamic-form">
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
162
|
+
<div class="form-row">
|
|
163
|
+
<label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
164
|
+
<input type="text" id="node-input-name" placeholder="Name">
|
|
165
|
+
</div>
|
|
166
|
+
<div class="form-row">
|
|
167
|
+
<label for="node-input-group"><i class="fa fa-table"></i> Group</label>
|
|
168
|
+
<input type="text" id="node-input-group">
|
|
169
|
+
</div>
|
|
170
|
+
<div class="form-row">
|
|
171
|
+
<label><i class="fa fa-object-group"></i> <span data-i18n="ui-dynamic-form.label.size"></label>
|
|
172
|
+
<input type="hidden" id="node-input-width">
|
|
173
|
+
<input type="hidden" id="node-input-height">
|
|
174
|
+
<button class="editor-button" id="node-input-size"></button>
|
|
175
|
+
</div>
|
|
164
176
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
177
|
+
<div class="form-row form-row-flex node-input-option-container-row" style="margin-bottom: 0px;width: 100%">
|
|
178
|
+
<label for="node-input-width" style="vertical-align:top"><i class="fa fa-list-alt"></i> Actions</label>
|
|
179
|
+
<div id="node-input-option-container-div" style="box-sizing:border-box; border-radius:5px; height:257px; padding:5px; border:1px solid var(--red-ui-form-input-border-color, #ccc); overflow-y:scroll; display:inline-block; width: 70%;">
|
|
180
|
+
<span id="valWarning" style="color: var(--red-ui-text-color-error, #910000)"><b>All Values must be unique.</b></span>
|
|
181
|
+
<ol id="node-input-option-container" style="list-style-type:none; margin:0;"></ol>
|
|
182
|
+
</div>
|
|
183
|
+
<!-- <a
|
|
184
|
+
data-html="true"
|
|
185
|
+
title="Dynamic Property: Send 'msg.options' in order to override this property."
|
|
186
|
+
class="red-ui-button ui-node-popover-title"
|
|
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;">
|
|
188
|
+
<i style="font-family: ui-serif;">fx</i>
|
|
189
|
+
</a> -->
|
|
190
|
+
</div>
|
|
191
|
+
<!-- Add Option Button -->
|
|
192
|
+
<div class="form-row">
|
|
193
|
+
<a href="#" class="editor-button editor-button-small" id="node-input-add-option" style="margin-top:4px; margin-left:103px;"><i class="fa fa-plus"></i> <span>action</span></a>
|
|
194
|
+
</div>
|
|
183
195
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
<div class="form-row">
|
|
197
|
+
<label for="node-input-waiting_title"><i class="fa fa-hand"></i>Title for waiting text.</label>
|
|
198
|
+
<input type="text" id="node-input-waiting_title">
|
|
199
|
+
</div>
|
|
200
|
+
<div class="form-row">
|
|
201
|
+
<label for="node-input-waiting_info"><i class="fa fa-hand"></i>Text for waiting text.</label>
|
|
202
|
+
<input type="text" id="node-input-waiting_info">
|
|
203
|
+
</div>
|
|
192
204
|
</script>
|
package/package.json
CHANGED
|
@@ -1,85 +1,85 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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"
|
|
2
|
+
"name": "@5minds/node-red-dashboard-2-processcube-dynamic-form",
|
|
3
|
+
"version": "1.0.16",
|
|
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"
|
|
24
14
|
},
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
"
|
|
82
|
-
|
|
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
|
+
"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
|
+
}
|
|
83
|
+
}
|
|
83
84
|
}
|
|
84
|
-
}
|
|
85
85
|
}
|
|
@@ -1,2 +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-
|
|
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,
|
|
1
|
+
(function(){"use strict";try{if(typeof document<"u"){var e=document.createElement("style");e.appendChild(document.createTextNode(".dynamic-form-wrapper[data-v-9cd04719]{padding:10px;margin:10px;border:1px solid black}.dynamic-form-class[data-v-9cd04719]{color:green;font-weight:700}h1[data-v-9cd04719]{margin-bottom:10px}h2[data-v-9cd04719]{margin-top:1.5rem;margin-bottom:.75rem}h3[data-v-9cd04719]{margin-top:1rem}p[data-v-9cd04719]{margin-bottom:5px}ul li[data-v-9cd04719]{list-style-type:circle;list-style-position:inside;margin-left:15px}pre[data-v-9cd04719]{padding:12px;margin:12px;background-color:#eee}code[data-v-9cd04719]{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 u=(t,s)=>{const r=t.__vccOpts||t;for(const[d,i]of s)r[d]=i;return r},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:{},msg:{},taskInput:{},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(),this.msg=t,t.payload&&t.payload.userTask&&(this.taskInput=t.payload.userTask),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,s;(t=this.$socket)==null||t.off("widget-load"+this.id),(s=this.$socket)==null||s.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(r=>({...r,component:y(r.type),items:k(r.type,r)}))},hasFields(){return this.messages&&this.messages[this.id]&&this.messages[this.id].payload.userTask!==void 0},send(t,s){const r=[];r[s]=t,this.$socket.emit("widget-action",this.id,r)},init(){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(s=>s.label===t.label))):this.showError(!0,t.errorMessage)},checkCondition(t){try{return!!Function("fields","userTask","msg",'"use strict"; return ('+t+")")(this.formData,this.taskInput,this.msg)}catch(s){return console.error("Error while evaluating condition: "+s),!1}},showError(t,s){this.error=t,this.errorMsg=s}}};function k(t,s){return t==="enum"?s.enumValues.map(r=>({title:r.name,value:r.id})):null}function y(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"},g={key:0},x={key:1};function w(t,s,r,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",_,[a.hasFields()?(e.openBlock(),e.createElementBlock("p",g,[e.createVNode(C,{ref:"form",modelValue:i.form,"onUpdate:modelValue":s[0]||(s[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=u(f,[["render",w],["__scopeId","data-v-9cd04719"]]);n.DynamicForm=T,Object.defineProperty(n,Symbol.toStringTag,{value:"Module"})});
|
|
@@ -1,226 +1,241 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</v-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
41
|
-
import { mapState } from
|
|
37
|
+
import { markRaw } from 'vue';
|
|
38
|
+
import { mapState } from 'vuex';
|
|
42
39
|
|
|
43
40
|
export default {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
+
msg: {},
|
|
62
|
+
taskInput: {},
|
|
63
|
+
error: false,
|
|
64
|
+
errorMsg: '',
|
|
65
|
+
};
|
|
70
66
|
},
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
67
|
+
computed: {
|
|
68
|
+
...mapState('data', ['messages']),
|
|
69
|
+
waiting_title() {
|
|
70
|
+
return this.props.waiting_title || 'Warten auf den Usertask...';
|
|
71
|
+
},
|
|
72
|
+
waiting_info() {
|
|
73
|
+
return (
|
|
74
|
+
this.props.waiting_info ||
|
|
75
|
+
'Der Usertask wird automatisch angezeigt, wenn ein entsprechender Task vorhanden ist.'
|
|
76
|
+
);
|
|
77
|
+
},
|
|
76
78
|
},
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
this.init()
|
|
79
|
+
mounted() {
|
|
80
|
+
this.$socket.on('widget-load:' + this.id, (msg) => {
|
|
81
|
+
this.init();
|
|
81
82
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
83
|
+
this.$store.commit('data/bind', {
|
|
84
|
+
widgetId: this.id,
|
|
85
|
+
msg,
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
this.$socket.on('msg-input:' + this.id, (msg) => {
|
|
89
|
+
// store the latest message in our client-side vuex store when we receive a new message
|
|
90
|
+
this.init();
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
//this.formData = { ...msg.payload.userTask.startToken.formData };
|
|
93
|
-
this.formData = { ...msg.payload.userTask.startToken }
|
|
94
|
-
console.info(this.formData)
|
|
95
|
-
}
|
|
92
|
+
this.msg = msg;
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
);
|
|
117
|
-
},
|
|
118
|
-
userTask() {
|
|
119
|
-
return this.hasUserTask() ? this.messages[this.id].payload.userTask : {};
|
|
120
|
-
},
|
|
121
|
-
fields() {
|
|
122
|
-
const aFields = this.hasUserTask()
|
|
123
|
-
? this.userTask().userTaskConfig.formFields
|
|
124
|
-
: [];
|
|
94
|
+
if (msg.payload && msg.payload.userTask) {
|
|
95
|
+
this.taskInput = msg.payload.userTask;
|
|
96
|
+
}
|
|
125
97
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
98
|
+
if (msg.payload && msg.payload.userTask && msg.payload.userTask.startToken) {
|
|
99
|
+
//this.formData = { ...msg.payload.userTask.startToken.formData };
|
|
100
|
+
this.formData = { ...msg.payload.userTask.startToken };
|
|
101
|
+
console.info(this.formData);
|
|
102
|
+
}
|
|
131
103
|
|
|
132
|
-
|
|
104
|
+
this.$store.commit('data/bind', {
|
|
105
|
+
widgetId: this.id,
|
|
106
|
+
msg,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
// tell Node-RED that we're loading a new instance of this widget
|
|
110
|
+
this.$socket.emit('widget-load', this.id);
|
|
133
111
|
},
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
this.
|
|
137
|
-
this
|
|
138
|
-
this.messages[this.id].payload.userTask !== undefined
|
|
139
|
-
);
|
|
112
|
+
unmounted() {
|
|
113
|
+
/* Make sure, any events you subscribe to on SocketIO are unsubscribed to here */
|
|
114
|
+
this.$socket?.off('widget-load' + this.id);
|
|
115
|
+
this.$socket?.off('msg-input:' + this.id);
|
|
140
116
|
},
|
|
141
|
-
|
|
117
|
+
methods: {
|
|
118
|
+
hasUserTask() {
|
|
119
|
+
return this.messages && this.messages[this.id] && this.messages[this.id].payload.userTask;
|
|
120
|
+
},
|
|
121
|
+
userTask() {
|
|
122
|
+
return this.hasUserTask() ? this.messages[this.id].payload.userTask : {};
|
|
123
|
+
},
|
|
124
|
+
fields() {
|
|
125
|
+
const aFields = this.hasUserTask() ? this.userTask().userTaskConfig.formFields : [];
|
|
126
|
+
|
|
127
|
+
const fieldMap = aFields.map((field) => ({
|
|
128
|
+
...field,
|
|
129
|
+
component: mapFieldTypes(field.type),
|
|
130
|
+
items: mapItems(field.type, field),
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
return fieldMap;
|
|
134
|
+
},
|
|
135
|
+
hasFields() {
|
|
136
|
+
return this.messages && this.messages[this.id] && this.messages[this.id].payload.userTask !== undefined;
|
|
137
|
+
},
|
|
138
|
+
/*
|
|
142
139
|
widget-action just sends a msg to Node-RED, it does not store the msg state server-side
|
|
143
140
|
alternatively, you can use widget-change, which will also store the msg in the Node's datastore
|
|
144
141
|
*/
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
142
|
+
send(msg, index) {
|
|
143
|
+
const msgArr = [];
|
|
144
|
+
msgArr[index] = msg;
|
|
145
|
+
this.$socket.emit('widget-action', this.id, msgArr);
|
|
146
|
+
},
|
|
147
|
+
init() {
|
|
148
|
+
this.actions = this.props.options;
|
|
149
|
+
},
|
|
150
|
+
actionFn(action) {
|
|
151
|
+
if (this.checkCondition(action.condition)) {
|
|
152
|
+
this.showError(false, '');
|
|
153
|
+
this.send(
|
|
154
|
+
{ payload: { formData: this.formData, userTask: this.userTask() } },
|
|
155
|
+
this.actions.findIndex((element) => element.label === action.label)
|
|
156
|
+
);
|
|
157
|
+
} else {
|
|
158
|
+
this.showError(true, action.errorMessage);
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
checkCondition(condition) {
|
|
162
|
+
try {
|
|
163
|
+
const func = Function('fields', 'userTask', 'msg', '"use strict"; return (' + condition + ')');
|
|
164
|
+
const result = func(this.formData, this.taskInput, this.msg);
|
|
165
|
+
return Boolean(result);
|
|
166
|
+
} catch (err) {
|
|
167
|
+
console.error('Error while evaluating condition: ' + err);
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
showError(bool, errMsg) {
|
|
172
|
+
this.error = bool;
|
|
173
|
+
this.errorMsg = errMsg;
|
|
174
|
+
},
|
|
159
175
|
},
|
|
160
|
-
},
|
|
161
176
|
};
|
|
162
177
|
|
|
163
178
|
function mapItems(type, field) {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
179
|
+
if (type === 'enum') {
|
|
180
|
+
return field.enumValues.map((enumValue) => ({
|
|
181
|
+
title: enumValue.name,
|
|
182
|
+
value: enumValue.id,
|
|
183
|
+
}));
|
|
184
|
+
} else {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
172
187
|
}
|
|
173
188
|
|
|
174
189
|
function mapFieldTypes(fieldType) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
190
|
+
switch (fieldType) {
|
|
191
|
+
case 'string':
|
|
192
|
+
return 'v-text-field';
|
|
193
|
+
case 'long':
|
|
194
|
+
case 'date':
|
|
195
|
+
return 'v-text-field';
|
|
196
|
+
case 'enum':
|
|
197
|
+
return 'v-select';
|
|
198
|
+
case 'boolean':
|
|
199
|
+
return 'v-checkbox';
|
|
200
|
+
case 'text':
|
|
201
|
+
return 'v-text-field';
|
|
202
|
+
case 'select':
|
|
203
|
+
return 'v-select';
|
|
204
|
+
case 'checkbox':
|
|
205
|
+
return 'v-checkbox';
|
|
206
|
+
case 'radio':
|
|
207
|
+
return 'v-radio';
|
|
208
|
+
case 'switch':
|
|
209
|
+
return 'v-switch';
|
|
210
|
+
case 'slider':
|
|
211
|
+
return 'v-slider';
|
|
212
|
+
case 'time':
|
|
213
|
+
return 'v-time-picker';
|
|
214
|
+
case 'datetime':
|
|
215
|
+
return 'v-datetime-picker';
|
|
216
|
+
case 'color':
|
|
217
|
+
return 'v-color-picker';
|
|
218
|
+
case 'file':
|
|
219
|
+
return 'v-file-input';
|
|
220
|
+
case 'textarea':
|
|
221
|
+
return 'v-textarea';
|
|
222
|
+
case 'password':
|
|
223
|
+
return 'v-text-field';
|
|
224
|
+
case 'number':
|
|
225
|
+
return 'v-text-field';
|
|
226
|
+
case 'email':
|
|
227
|
+
return 'v-text-field';
|
|
228
|
+
case 'tel':
|
|
229
|
+
return 'v-text-field';
|
|
230
|
+
case 'url':
|
|
231
|
+
return 'v-text-field';
|
|
232
|
+
default:
|
|
233
|
+
return 'v-text-field';
|
|
234
|
+
}
|
|
220
235
|
}
|
|
221
236
|
</script>
|
|
222
237
|
|
|
223
238
|
<style scoped>
|
|
224
239
|
/* CSS is auto scoped, but using named classes is still recommended */
|
|
225
|
-
@import
|
|
240
|
+
@import '../stylesheets/dynamic-form.css';
|
|
226
241
|
</style>
|