@5minds/node-red-dashboard-2-processcube-dynamic-form 2.1.0-feature-48cebe-mdiwg5tc → 2.1.0-feature-9b3262-mdyiezct

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,85 +1,127 @@
1
1
  <template>
2
- <div className="ui-dynamic-form-external-sizing-wrapper" :style="props.card_size_styling">
3
- <!-- Component must be wrapped in a block so props such as className and style can be passed in from parent -->
4
- <UIDynamicFormTitleText v-if="props.title_style === 'outside' && hasUserTask" :style="props.title_style"
5
- :title="effectiveTitle" :customStyles="props.title_custom_text_styling" :titleIcon="props.title_icon"
6
- :collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)" :collapsed="collapsed"
7
- :toggleCollapse="toggleCollapse" />
8
- <div className="ui-dynamic-form-wrapper">
9
- <p v-if="hasUserTask" style="margin-bottom: 0px">
10
- <v-form ref="form" v-model="form" :class="dynamicClass">
11
- <UIDynamicFormTitleText v-if="props.title_style != 'outside'" :style="props.title_style"
12
- :title="effectiveTitle" :customStyles="props.title_custom_text_styling"
13
- :titleIcon="props.title_icon"
14
- :collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)"
15
- :collapsed="collapsed" :toggleCollapse="toggleCollapse" />
16
- <Transition name="cardCollapse">
17
- <div v-if="!collapsed">
18
- <div className="ui-dynamic-form-formfield-positioner" :style="props.inner_card_styling"
19
- :data-columns="props.form_columns || 1">
20
- <FormKit id="form" type="group">
21
- <v-row v-for="(field, index) in fields()" :key="field"
22
- :class="field.type === 'header' ? 'ui-dynamic-form-header-row' : ''"
23
- :style="getRowWidthStyling(field, index)">
24
- <v-col cols="12">
25
- <component :is="createComponent(field).type"
26
- v-if="createComponent(field).innerHTML"
27
- v-bind="createComponent(field).props"
28
- :class="createComponent(field).class"
29
- v-html="createComponent(field).innerHTML" :ref="(el) => {
30
- if (index === 0) firstFormFieldRef = el;
31
- }
32
- " />
33
- <component :is="createComponent(field).type"
34
- v-else-if="createComponent(field).innerText"
35
- v-bind="createComponent(field).props" :ref="(el) => {
36
- if (index === 0) firstFormFieldRef = el;
37
- }
38
- " v-model="formData[field.id]">
39
- {{ createComponent(field).innerText }}
40
- </component>
41
- <div v-else-if="createComponent(field).type == 'v-slider'">
42
- <p class="formkit-label">{{ field.label }}</p>
43
- <component :is="createComponent(field).type"
44
- v-bind="createComponent(field).props" :ref="(el) => {
45
- if (index === 0) firstFormFieldRef = el;
46
- }
47
- " v-model="field.defaultValue" />
48
- <p class="formkit-help">
49
- {{ field.customForm ? JSON.parse(field.customForm).hint : undefined
50
- }}
51
- </p>
52
- </div>
53
- <component :is="createComponent(field).type" v-else
54
- v-bind="createComponent(field).props" :ref="(el) => {
55
- if (index === 0) firstFormFieldRef = el;
56
- }
57
- " v-model="formData[field.id]" />
58
- </v-col>
59
- </v-row>
60
- </FormKit>
61
- </div>
62
- <v-row :class="dynamicFooterClass">
63
- <v-row v-if="errorMsg.length > 0" style="padding: 12px">
64
- <v-alert type="error">Error: {{ errorMsg }}</v-alert>
65
- </v-row>
66
- <UIDynamicFormFooterAction v-if="props.actions_inside_card && actions.length > 0"
67
- :actions="actions" :actionCallback="actionFn" :formIsFinished="formIsFinished"
68
- style="padding: 16px; padding-top: 0px" />
69
- </v-row>
70
- </div>
71
- </Transition>
72
- </v-form>
73
- </p>
74
- <p v-else>
75
- <v-alert v-if="props.waiting_info.length > 0 || props.waiting_title.length > 0"
76
- :text="props.waiting_info" :title="props.waiting_title" />
77
- </p>
78
- </div>
79
- <div v-if="!props.actions_inside_card && actions.length > 0 && hasUserTask" style="padding-top: 32px">
80
- <UIDynamicFormFooterAction :actions="actions" :actionCallback="actionFn" />
81
- </div>
2
+ <div className="ui-dynamic-form-external-sizing-wrapper" :style="props.card_size_styling">
3
+ <UIDynamicFormTitleText
4
+ v-if="props.title_style === 'outside' && hasUserTask"
5
+ :style="props.title_style"
6
+ :title="effectiveTitle"
7
+ :customStyles="props.title_custom_text_styling"
8
+ :titleIcon="props.title_icon"
9
+ :collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)"
10
+ :collapsed="collapsed"
11
+ :toggleCollapse="toggleCollapse"
12
+ />
13
+ <div className="ui-dynamic-form-wrapper">
14
+ <p v-if="hasUserTask" style="margin-bottom: 0px">
15
+ <v-form ref="form" v-model="form" :class="dynamicClass">
16
+ <UIDynamicFormTitleText
17
+ v-if="props.title_style != 'outside'"
18
+ :style="props.title_style"
19
+ :title="effectiveTitle"
20
+ :customStyles="props.title_custom_text_styling"
21
+ :titleIcon="props.title_icon"
22
+ :collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)"
23
+ :collapsed="collapsed"
24
+ :toggleCollapse="toggleCollapse"
25
+ />
26
+ <Transition name="cardCollapse">
27
+ <div v-if="!collapsed">
28
+ <div
29
+ className="ui-dynamic-form-formfield-positioner"
30
+ :style="props.inner_card_styling"
31
+ :data-columns="props.form_columns || 1"
32
+ >
33
+ <FormKit id="form" type="group">
34
+ <v-row
35
+ v-for="(field, index) in fields()"
36
+ :key="field"
37
+ :class="field.type === 'header' ? 'ui-dynamic-form-header-row' : ''"
38
+ :style="getRowWidthStyling(field, index)"
39
+ >
40
+ <v-col cols="12">
41
+ <component
42
+ :is="createComponent(field).type"
43
+ v-if="createComponent(field).innerHTML"
44
+ v-bind="createComponent(field).props"
45
+ :class="createComponent(field).class"
46
+ v-html="createComponent(field).innerHTML"
47
+ :ref="
48
+ (el) => {
49
+ if (index === 0) firstFormFieldRef = el;
50
+ }
51
+ "
52
+ />
53
+ <component
54
+ :is="createComponent(field).type"
55
+ v-else-if="createComponent(field).innerText"
56
+ v-bind="createComponent(field).props"
57
+ :ref="
58
+ (el) => {
59
+ if (index === 0) firstFormFieldRef = el;
60
+ }
61
+ "
62
+ v-model="formData[field.id]"
63
+ >
64
+ {{ createComponent(field).innerText }}
65
+ </component>
66
+ <div v-else-if="createComponent(field).type == 'v-slider'">
67
+ <p class="formkit-label">{{ field.label }}</p>
68
+ <component
69
+ :is="createComponent(field).type"
70
+ v-bind="createComponent(field).props"
71
+ :ref="
72
+ (el) => {
73
+ if (index === 0) firstFormFieldRef = el;
74
+ }
75
+ "
76
+ v-model="field.defaultValue"
77
+ />
78
+ <p class="formkit-help">
79
+ {{ field.customForm ? JSON.parse(JSON.stringify(field.customForm)).hint : undefined }}
80
+ </p>
81
+ </div>
82
+ <component
83
+ :is="createComponent(field).type"
84
+ v-else
85
+ v-bind="createComponent(field).props"
86
+ :ref="
87
+ (el) => {
88
+ if (index === 0) firstFormFieldRef = el;
89
+ }
90
+ "
91
+ v-model="formData[field.id]"
92
+ />
93
+ </v-col>
94
+ </v-row>
95
+ </FormKit>
96
+ </div>
97
+ <v-row :class="dynamicFooterClass">
98
+ <v-row v-if="errorMsg.length > 0" style="padding: 12px">
99
+ <v-alert type="error">Error: {{ errorMsg }}</v-alert>
100
+ </v-row>
101
+ <UIDynamicFormFooterAction
102
+ v-if="props.actions_inside_card && hasUserTask"
103
+ :actions="actions"
104
+ :actionCallback="actionFn"
105
+ :formIsFinished="formIsFinished"
106
+ style="padding: 16px; padding-top: 0px"
107
+ />
108
+ </v-row>
109
+ </div>
110
+ </Transition>
111
+ </v-form>
112
+ </p>
113
+ <p v-else>
114
+ <v-alert
115
+ v-if="props.waiting_info.length > 0 || props.waiting_title.length > 0"
116
+ :text="props.waiting_info"
117
+ :title="props.waiting_title"
118
+ />
119
+ </p>
120
+ </div>
121
+ <div v-if="!props.actions_inside_card && hasUserTask" style="padding-top: 32px">
122
+ <UIDynamicFormFooterAction :actions="actions" :actionCallback="actionFn" />
82
123
  </div>
124
+ </div>
83
125
  </template>
84
126
 
85
127
  <script>
@@ -94,938 +136,970 @@ import UIDynamicFormFooterAction from './FooterActions.vue';
94
136
  import UIDynamicFormTitleText from './TitleText.vue';
95
137
 
96
138
  function requiredIf({ value }, [targetField, expectedValue], node) {
97
- console.debug(arguments);
139
+ console.debug(arguments);
98
140
 
99
- const actual = node?.root?.value?.[targetField];
100
- const isEmpty = value === '' || value === null || value === undefined;
141
+ const actual = node?.root?.value?.[targetField];
142
+ const isEmpty = value === '' || value === null || value === undefined;
101
143
 
102
- if (actual === expectedValue && isEmpty) {
103
- return false;
104
- }
144
+ if (actual === expectedValue && isEmpty) {
145
+ return false;
146
+ }
105
147
 
106
- return true;
148
+ return true;
107
149
  }
108
150
 
109
151
  class MarkdownRenderer extends marked.Renderer {
110
- link(params) {
111
- const link = super.link(params);
112
- return link.replace('<a', "<a target='_blank'");
113
- }
152
+ link(params) {
153
+ const link = super.link(params);
154
+ return link.replace('<a', "<a target='_blank'");
155
+ }
114
156
 
115
- html(params) {
116
- const result = super.html(params);
117
- if (result.startsWith('<a ') && !result.includes('target=')) {
118
- return result.replace('<a ', `<a target="_blank" `);
119
- }
120
- return result;
157
+ html(params) {
158
+ const result = super.html(params);
159
+ if (result.startsWith('<a ') && !result.includes('target=')) {
160
+ return result.replace('<a ', `<a target="_blank" `);
121
161
  }
162
+ return result;
163
+ }
122
164
  }
123
165
 
124
166
  class MarkedHooks extends marked.Hooks {
125
- postprocess(html) {
126
- return DOMPurify.sanitize(html, { ADD_ATTR: ['target'] });
127
- }
167
+ postprocess(html) {
168
+ return DOMPurify.sanitize(html, { ADD_ATTR: ['target'] });
169
+ }
128
170
  }
129
171
 
130
172
  function processMarkdown(content) {
131
- if (!content) return '';
173
+ if (!content) return '';
132
174
 
133
- const html = marked.parse(content.toString(), {
134
- renderer: new MarkdownRenderer(),
135
- hooks: new MarkedHooks(),
136
- });
175
+ const html = marked.parse(content.toString(), {
176
+ renderer: new MarkdownRenderer(),
177
+ hooks: new MarkedHooks(),
178
+ });
137
179
 
138
- return html;
180
+ return html;
139
181
  }
140
182
 
141
183
  export default {
142
- name: 'UIDynamicForm',
143
- components: {
144
- FormKit,
145
- UIDynamicFormFooterAction,
146
- UIDynamicFormTitleText,
147
- },
148
- inject: ['$socket'],
149
- props: {
150
- /* do not remove entries from this - Dashboard's Layout Manager's will pass this data to your component */
151
- id: { type: String, required: true },
152
- props: { type: Object, default: () => ({}) },
153
- state: {
154
- type: Object,
155
- default: () => ({ enabled: false, visible: false }),
156
- },
184
+ name: 'UIDynamicForm',
185
+ components: {
186
+ FormKit,
187
+ UIDynamicFormFooterAction,
188
+ UIDynamicFormTitleText,
189
+ },
190
+ inject: ['$socket'],
191
+ props: {
192
+ id: { type: String, required: true },
193
+ props: { type: Object, default: () => ({}) },
194
+ state: {
195
+ type: Object,
196
+ default: () => ({ enabled: false, visible: false }),
157
197
  },
158
- setup(props) {
159
- console.info('UIDynamicForm setup with:', props);
160
- console.debug('Vue function loaded correctly', markRaw);
198
+ },
199
+ setup(props) {
200
+ console.info('UIDynamicForm setup with:', props);
201
+ console.debug('Vue function loaded correctly', markRaw);
161
202
 
162
- const instance = getCurrentInstance();
163
- const app = instance.appContext.app;
203
+ const instance = getCurrentInstance();
204
+ const app = instance.appContext.app;
164
205
 
165
- const formkitConfig = defaultConfig({
166
- theme: 'genesis',
167
- locales: { de },
168
- locale: 'de',
169
- // eslint-disable-next-line object-shorthand
170
- rules: { requiredIf: requiredIf },
171
- });
172
- app.use(plugin, formkitConfig);
206
+ const formkitConfig = defaultConfig({
207
+ theme: 'genesis',
208
+ locales: { de },
209
+ locale: 'de',
210
+ rules: { requiredIf: requiredIf },
211
+ });
212
+ app.use(plugin, formkitConfig);
213
+ },
214
+ data() {
215
+ return {
216
+ actions: [],
217
+ formData: {},
218
+ userTask: null,
219
+ theme: '',
220
+ errorMsg: '',
221
+ formIsFinished: false,
222
+ msg: null,
223
+ collapsed: false,
224
+ firstFormFieldRef: null,
225
+ };
226
+ },
227
+ computed: {
228
+ dynamicClass() {
229
+ return `ui-dynamic-form-${this.theme} ui-dynamic-form-common`;
173
230
  },
174
- data() {
175
- return {
176
- actions: [],
177
- formData: {},
178
- userTask: null,
179
- theme: '',
180
- errorMsg: '',
181
- formIsFinished: false,
182
- msg: null,
183
- collapsed: false,
184
- firstFormFieldRef: null,
185
- };
231
+ dynamicFooterClass() {
232
+ return `ui-dynamic-form-footer-${this.theme} ui-dynamic-form-footer-common`;
186
233
  },
187
- computed: {
188
- dynamicClass() {
189
- return `ui-dynamic-form-${this.theme} ui-dynamic-form-common`;
190
- },
191
- dynamicFooterClass() {
192
- return `ui-dynamic-form-footer-${this.theme} ui-dynamic-form-footer-common`;
193
- },
194
- hasUserTask() {
195
- return !!this.userTask;
196
- },
197
- totalOutputs() {
198
- return (
199
- this.props.options.length +
200
- (this.props.handle_confirmation_dialogs ? 2 : 0) +
201
- (this.props.trigger_on_change ? 1 : 0)
202
- );
203
- },
204
- isConfirmDialog() {
205
- return this.userTask.userTaskConfig.formFields.some((field) => field.type === 'confirm');
206
- },
207
- effectiveTitle() {
208
- if (this.props.title_text_type === 'str') {
209
- return this.props.title_text;
210
- } else if (this.props.title_text_type === 'msg') {
211
- return this.msg.dynamicTitle;
212
- } else {
213
- return '';
214
- }
215
- },
234
+ hasUserTask() {
235
+ return !!this.userTask;
216
236
  },
217
- watch: {
218
- formData: {
219
- handler(newData, oldData) {
220
- if (this.props.trigger_on_change) {
221
- const res = { payload: { formData: newData, userTask: this.userTask } };
222
- this.send(res, this.totalOutputs - 1);
223
- }
224
- },
225
- collapsed(newVal) {
226
- if (!newVal && this.hasUserTask) {
227
- nextTick(() => {
228
- this.focusFirstFormField();
229
- });
230
- }
231
- },
232
- userTask(newVal) {
233
- if (newVal && !this.collapsed) {
234
- nextTick(() => {
235
- this.focusFirstFormField();
236
- });
237
- }
238
- },
239
- deep: true,
240
- },
237
+ totalOutputs() {
238
+ const outputsConfirmTerminate = 2;
239
+ return (
240
+ this.props.options.length +
241
+ (this.props.handle_confirmation_dialogs ? 2 : 0) +
242
+ (this.props.trigger_on_change ? 1 : 0) +
243
+ outputsConfirmTerminate
244
+ );
245
+ },
246
+ isConfirmDialog() {
247
+ return this.userTask.userTaskConfig.formFields.some((field) => field.type === 'confirm');
248
+ },
249
+ effectiveTitle() {
250
+ if (this.props.title_text_type === 'str') {
251
+ return this.props.title_text;
252
+ } else if (this.props.title_text_type === 'msg') {
253
+ return this.msg.dynamicTitle;
254
+ } else {
255
+ return '';
256
+ }
257
+ },
258
+ },
259
+ watch: {
260
+ formData: {
261
+ handler(newData, oldData) {
262
+ if (this.props.trigger_on_change) {
263
+ const res = { payload: { formData: newData, userTask: this.userTask } };
264
+ this.send(res, this.totalOutputs - 1);
265
+ }
266
+ },
267
+ collapsed(newVal) {
268
+ if (!newVal && this.hasUserTask) {
269
+ nextTick(() => {
270
+ this.focusFirstFormField();
271
+ });
272
+ }
273
+ },
274
+ userTask(newVal) {
275
+ if (newVal && !this.collapsed) {
276
+ nextTick(() => {
277
+ this.focusFirstFormField();
278
+ });
279
+ }
280
+ },
281
+ deep: true,
241
282
  },
242
- created() {
243
- const currentPath = window.location.pathname;
244
- const lastPart = currentPath.substring(currentPath.lastIndexOf('/'));
283
+ },
284
+ created() {
285
+ const currentPath = window.location.pathname;
286
+ const lastPart = currentPath.substring(currentPath.lastIndexOf('/'));
245
287
 
246
- const store = this.$store.state;
288
+ const store = this.$store.state;
247
289
 
248
- for (const key in store.ui.pages) {
249
- if (store.ui.pages[key].path === lastPart) {
250
- const theme = store.ui.pages[key].theme;
251
- if (store.ui.themes[theme].name === 'ProcessCube Lightmode') {
252
- this.theme = 'light';
253
- } else if (store.ui.themes[theme].name === 'ProcessCube Darkmode') {
254
- this.theme = 'dark';
255
- } else {
256
- this.theme = 'default';
257
- }
258
- break;
259
- }
290
+ for (const key in store.ui.pages) {
291
+ if (store.ui.pages[key].path === lastPart) {
292
+ const theme = store.ui.pages[key].theme;
293
+ if (store.ui.themes[theme].name === 'ProcessCube Lightmode') {
294
+ this.theme = 'light';
295
+ } else if (store.ui.themes[theme].name === 'ProcessCube Darkmode') {
296
+ this.theme = 'dark';
297
+ } else {
298
+ this.theme = 'default';
260
299
  }
261
- },
262
- mounted() {
263
- const elements = document.querySelectorAll('.formkit-input');
300
+ break;
301
+ }
302
+ }
303
+ },
304
+ mounted() {
305
+ console.log('UIDynamicForm mounted with ID:', this.id);
306
+ const elements = document.querySelectorAll('.formkit-input');
264
307
 
265
- elements.forEach((element) => {
266
- element.classList.add('test');
267
- });
308
+ elements.forEach((element) => {
309
+ element.classList.add('test');
310
+ });
268
311
 
269
- this.$socket.on('widget-load:' + this.id, (msg) => {
270
- this.init(msg);
271
- });
272
- this.$socket.on('msg-input:' + this.id, (msg) => {
273
- // store the latest message in our client-side vuex store when we receive a new message
274
- this.init(msg);
275
- });
276
- // tell Node-RED that we're loading a new instance of this widget
277
- this.$socket.emit('widget-load', this.id);
312
+ this.$socket.on('widget-load:' + this.id, (msg) => {
313
+ this.init(msg);
314
+ });
315
+ this.$socket.on('msg-input:' + this.id, (msg) => {
316
+ this.init(msg);
317
+ });
318
+ this.$socket.emit('widget-load', this.id);
319
+ },
320
+ unmounted() {
321
+ this.$socket?.off('widget-load' + this.id);
322
+ this.$socket?.off('msg-input:' + this.id);
323
+ },
324
+ methods: {
325
+ createComponent(field) {
326
+ const customForm = field.customForm ? JSON.parse(JSON.stringify(field.customForm)) : {};
327
+ const hint = customForm.hint;
328
+ const placeholder = customForm.placeholder;
329
+ const validation = customForm.validation;
330
+ const name = field.id;
331
+ const customProperties = customForm.customProperties ?? [];
332
+ const isReadOnly =
333
+ this.props.readonly ||
334
+ this.formIsFinished ||
335
+ customProperties.find((entry) => ['readOnly', 'readonly'].includes(entry.name) && entry.value === 'true')
336
+ ? 'true'
337
+ : undefined;
338
+ switch (field.type) {
339
+ case 'long':
340
+ return {
341
+ type: 'FormKit',
342
+ props: {
343
+ type: 'number',
344
+ id: field.id,
345
+ name,
346
+ label: field.label,
347
+ required: field.required,
348
+ value: this.formData[field.id],
349
+ number: 'integer',
350
+ min: 0,
351
+ validation: validation ? `${validation}|number` : 'number',
352
+ help: hint,
353
+ wrapperClass: '$remove:formkit-wrapper',
354
+ labelClass: 'ui-dynamic-form-input-label',
355
+ inputClass: `input-${this.theme}`,
356
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
357
+ readonly: isReadOnly,
358
+ validationVisibility: 'live',
359
+ },
360
+ };
361
+ case 'number':
362
+ const step = field.customForm ? JSON.parse(JSON.stringify(field.customForm)).step : undefined;
363
+ return {
364
+ type: 'FormKit',
365
+ props: {
366
+ type: 'number',
367
+ id: field.id,
368
+ name,
369
+ label: field.label,
370
+ required: field.required,
371
+ value: this.formData[field.id],
372
+ step,
373
+ number: 'float',
374
+ validation: validation ? `${validation}|number` : 'number',
375
+ help: hint,
376
+ wrapperClass: '$remove:formkit-wrapper',
377
+ labelClass: 'ui-dynamic-form-input-label',
378
+ inputClass: `input-${this.theme}`,
379
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
380
+ readonly: isReadOnly,
381
+ validationVisibility: 'live',
382
+ },
383
+ };
384
+ case 'date':
385
+ return {
386
+ type: 'FormKit',
387
+ props: {
388
+ type: 'date',
389
+ id: field.id,
390
+ name,
391
+ label: field.label,
392
+ required: field.required,
393
+ value: this.formData[field.id],
394
+ help: hint,
395
+ wrapperClass: '$remove:formkit-wrapper',
396
+ labelClass: 'ui-dynamic-form-input-label',
397
+ inputClass: `input-${this.theme}`,
398
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
399
+ readonly: isReadOnly,
400
+ validation,
401
+ validationVisibility: 'live',
402
+ },
403
+ };
404
+ case 'enum':
405
+ const enums = field.enumValues.map((obj) => {
406
+ return { value: obj.id, label: obj.name };
407
+ });
408
+ return {
409
+ type: 'FormKit',
410
+ props: {
411
+ type: 'select',
412
+ id: field.id,
413
+ name,
414
+ label: field.label,
415
+ required: field.required,
416
+ value: this.formData[field.id],
417
+ options: enums,
418
+ help: hint,
419
+ wrapperClass: '$remove:formkit-wrapper',
420
+ labelClass: 'ui-dynamic-form-input-label',
421
+ inputClass: `input-${this.theme}`,
422
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
423
+ readonly: isReadOnly,
424
+ disabled: isReadOnly,
425
+ validation,
426
+ validationVisibility: 'live',
427
+ },
428
+ };
429
+ case 'select':
430
+ const selections = JSON.parse(JSON.stringify(field.customForm)).entries.map((obj) => {
431
+ return { value: obj.key, label: obj.value };
432
+ });
433
+ return {
434
+ type: 'FormKit',
435
+ props: {
436
+ type: 'select',
437
+ id: field.id,
438
+ name,
439
+ label: field.label,
440
+ required: field.required,
441
+ value: this.formData[field.id],
442
+ options: selections,
443
+ placeholder,
444
+ help: hint,
445
+ wrapperClass: '$remove:formkit-wrapper',
446
+ labelClass: 'ui-dynamic-form-input-label',
447
+ inputClass: `input-${this.theme}`,
448
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
449
+ readonly: isReadOnly,
450
+ disabled: isReadOnly,
451
+ validation,
452
+ validationVisibility: 'live',
453
+ },
454
+ };
455
+ case 'string':
456
+ return {
457
+ type: 'FormKit',
458
+ props: {
459
+ type: 'text',
460
+ id: field.id,
461
+ name,
462
+ label: field.label,
463
+ required: field.required,
464
+ value: this.formData[field.id],
465
+ help: hint,
466
+ placeholder,
467
+ wrapperClass: '$remove:formkit-wrapper',
468
+ labelClass: 'ui-dynamic-form-input-label',
469
+ inputClass: `input-${this.theme}`,
470
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
471
+ readonly: isReadOnly,
472
+ validation,
473
+ validationVisibility: 'live',
474
+ },
475
+ };
476
+ case 'confirm':
477
+ return {
478
+ type: 'h3',
479
+ innerText: field.label,
480
+ };
481
+ case 'boolean':
482
+ return {
483
+ type: 'FormKit',
484
+ props: {
485
+ type: 'checkbox',
486
+ id: field.id,
487
+ name,
488
+ label: field.label,
489
+ required: field.required,
490
+ value: this.formData[field.id],
491
+ help: hint,
492
+ labelClass: 'ui-dynamic-form-input-label',
493
+ inputClass: `input-${this.theme}`,
494
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
495
+ readonly: isReadOnly,
496
+ disabled: isReadOnly,
497
+ validation,
498
+ validationVisibility: 'live',
499
+ },
500
+ };
501
+ case 'file':
502
+ const multiple = field.customForm ? JSON.parse(JSON.stringify(field.customForm)).multiple === 'true' : false;
503
+ return {
504
+ type: 'FormKit',
505
+ props: {
506
+ type: 'file',
507
+ id: field.id,
508
+ name,
509
+ label: field.label,
510
+ required: field.required,
511
+ value: this.formData[field.id],
512
+ help: hint,
513
+ innerClass: 'reset-background',
514
+ wrapperClass: '$remove:formkit-wrapper',
515
+ labelClass: 'ui-dynamic-form-input-label',
516
+ inputClass: `input-${this.theme}`,
517
+ readonly: isReadOnly,
518
+ disabled: isReadOnly,
519
+ multiple,
520
+ validation,
521
+ validationVisibility: 'live',
522
+ },
523
+ };
524
+ case 'checkbox':
525
+ const options = JSON.parse(JSON.stringify(field.customForm)).entries.map((obj) => {
526
+ return { value: obj.key, label: obj.value };
527
+ });
528
+ return {
529
+ type: 'FormKit',
530
+ props: {
531
+ type: 'checkbox',
532
+ id: field.id,
533
+ name,
534
+ label: field.label,
535
+ required: field.required,
536
+ value: this.formData[field.id],
537
+ options,
538
+ help: hint,
539
+ fieldsetClass: 'custom-fieldset',
540
+ labelClass: 'ui-dynamic-form-input-label',
541
+ inputClass: `input-${this.theme}`,
542
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
543
+ readonly: isReadOnly,
544
+ disabled: isReadOnly,
545
+ validation,
546
+ validationVisibility: 'live',
547
+ },
548
+ };
549
+ case 'color':
550
+ return {
551
+ type: 'FormKit',
552
+ props: {
553
+ type: 'color',
554
+ id: field.id,
555
+ name,
556
+ label: field.label,
557
+ required: field.required,
558
+ value: this.formData[field.id],
559
+ help: hint,
560
+ readonly: isReadOnly,
561
+ disabled: isReadOnly,
562
+ validation,
563
+ validationVisibility: 'live',
564
+ },
565
+ };
566
+ case 'datetime-local':
567
+ return {
568
+ type: 'FormKit',
569
+ props: {
570
+ type: 'datetime-local',
571
+ id: field.id,
572
+ name,
573
+ label: field.label,
574
+ required: field.required,
575
+ value: this.formData[field.id],
576
+ help: hint,
577
+ wrapperClass: '$remove:formkit-wrapper',
578
+ labelClass: 'ui-dynamic-form-input-label',
579
+ inputClass: `input-${this.theme}`,
580
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
581
+ readonly: isReadOnly,
582
+ validation,
583
+ validationVisibility: 'live',
584
+ },
585
+ };
586
+ case 'email':
587
+ return {
588
+ type: 'FormKit',
589
+ props: {
590
+ type: 'email',
591
+ id: field.id,
592
+ name,
593
+ label: field.label,
594
+ required: field.required,
595
+ value: this.formData[field.id],
596
+ help: hint,
597
+ placeholder,
598
+ wrapperClass: '$remove:formkit-wrapper',
599
+ labelClass: 'ui-dynamic-form-input-label',
600
+ inputClass: `input-${this.theme}`,
601
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
602
+ readonly: isReadOnly,
603
+ validation,
604
+ validationVisibility: 'live',
605
+ },
606
+ };
607
+ case 'header':
608
+ let typeToUse = 'h1';
609
+ if (field.customForm && JSON.parse(JSON.stringify(field.customForm)).style === 'heading_2') {
610
+ typeToUse = 'h2';
611
+ }
612
+ if (field.customForm && JSON.parse(JSON.stringify(field.customForm)).style === 'heading_3') {
613
+ typeToUse = 'h3';
614
+ }
615
+ return {
616
+ type: typeToUse,
617
+ innerText: this.formData[field.id],
618
+ };
619
+ case 'hidden':
620
+ return {
621
+ type: 'input',
622
+ props: {
623
+ type: 'hidden',
624
+ value: this.formData[field.id],
625
+ },
626
+ };
627
+ case 'month':
628
+ return {
629
+ type: 'FormKit',
630
+ props: {
631
+ type: 'month',
632
+ id: field.id,
633
+ name,
634
+ label: field.label,
635
+ required: field.required,
636
+ value: this.formData[field.id],
637
+ help: hint,
638
+ wrapperClass: '$remove:formkit-wrapper',
639
+ labelClass: 'ui-dynamic-form-input-label',
640
+ inputClass: `input-${this.theme}`,
641
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
642
+ readonly: isReadOnly,
643
+ validation,
644
+ validationVisibility: 'live',
645
+ },
646
+ };
647
+ case 'paragraph':
648
+ const paragraphContent = this.formData[field.id] || field.defaultValue || field.label || '';
649
+ const processedHtml = processMarkdown(paragraphContent);
650
+ return {
651
+ type: 'div',
652
+ innerHTML: processedHtml,
653
+ class: 'ui-dynamic-form-paragraph',
654
+ };
655
+ case 'password':
656
+ return {
657
+ type: 'FormKit',
658
+ props: {
659
+ type: 'password',
660
+ id: field.id,
661
+ name,
662
+ label: field.label,
663
+ required: field.required,
664
+ value: this.formData[field.id],
665
+ help: hint,
666
+ placeholder,
667
+ wrapperClass: '$remove:formkit-wrapper',
668
+ labelClass: 'ui-dynamic-form-input-label',
669
+ inputClass: `input-${this.theme}`,
670
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
671
+ readonly: isReadOnly,
672
+ validation,
673
+ validationVisibility: 'live',
674
+ },
675
+ };
676
+ case 'radio':
677
+ const radioOptions = JSON.parse(JSON.stringify(field.customForm)).entries.map((obj) => {
678
+ return { value: obj.key, label: obj.value };
679
+ });
680
+ return {
681
+ type: 'FormKit',
682
+ props: {
683
+ type: 'radio',
684
+ id: field.id,
685
+ name,
686
+ label: field.label,
687
+ required: field.required,
688
+ value: this.formData[field.id],
689
+ options: radioOptions,
690
+ help: hint,
691
+ fieldsetClass: 'custom-fieldset',
692
+ labelClass: 'ui-dynamic-form-input-label',
693
+ inputClass: `input-${this.theme}`,
694
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
695
+ readonly: isReadOnly,
696
+ disabled: isReadOnly,
697
+ validation,
698
+ validationVisibility: 'live',
699
+ },
700
+ };
701
+ case 'range':
702
+ const customForm = JSON.parse(JSON.stringify(field.customForm));
703
+ return {
704
+ type: 'v-slider',
705
+ props: {
706
+ id: field.id,
707
+ name,
708
+ required: field.required,
709
+ min: customForm.min,
710
+ max: customForm.max,
711
+ step: customForm.step,
712
+ thumbLabel: true,
713
+ labelClass: 'ui-dynamic-form-input-label',
714
+ readonly: isReadOnly,
715
+ disabled: isReadOnly,
716
+ validation,
717
+ validationVisibility: 'live',
718
+ },
719
+ };
720
+ case 'tel':
721
+ return {
722
+ type: 'FormKit',
723
+ props: {
724
+ type: 'tel',
725
+ id: field.id,
726
+ name,
727
+ label: field.label,
728
+ required: field.required,
729
+ value: this.formData[field.id],
730
+ help: hint,
731
+ placeholder,
732
+ wrapperClass: '$remove:formkit-wrapper',
733
+ labelClass: 'ui-dynamic-form-input-label',
734
+ inputClass: `input-${this.theme}`,
735
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
736
+ readonly: isReadOnly,
737
+ validation,
738
+ validationVisibility: 'live',
739
+ },
740
+ };
741
+ case 'textarea':
742
+ const rows = field.customForm ? JSON.parse(JSON.stringify(field.customForm)).rows : undefined;
743
+ return {
744
+ type: 'FormKit',
745
+ props: {
746
+ type: 'textarea',
747
+ id: field.id,
748
+ name,
749
+ label: field.label,
750
+ required: field.required,
751
+ value: this.formData[field.id],
752
+ rows,
753
+ help: hint,
754
+ placeholder,
755
+ wrapperClass: '$remove:formkit-wrapper',
756
+ labelClass: 'ui-dynamic-form-input-label',
757
+ inputClass: `input-${this.theme}`,
758
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
759
+ readonly: isReadOnly,
760
+ validation,
761
+ validationVisibility: 'live',
762
+ },
763
+ };
764
+ case 'time':
765
+ return {
766
+ type: 'FormKit',
767
+ props: {
768
+ type: 'time',
769
+ id: field.id,
770
+ name,
771
+ label: field.label,
772
+ required: field.required,
773
+ value: this.formData[field.id],
774
+ help: hint,
775
+ placeholder,
776
+ wrapperClass: '$remove:formkit-wrapper',
777
+ labelClass: 'ui-dynamic-form-input-label',
778
+ inputClass: `input-${this.theme}`,
779
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
780
+ readonly: isReadOnly,
781
+ validation,
782
+ validationVisibility: 'live',
783
+ },
784
+ };
785
+ case 'url':
786
+ return {
787
+ type: 'FormKit',
788
+ props: {
789
+ type: 'url',
790
+ id: field.id,
791
+ name,
792
+ label: field.label,
793
+ required: field.required,
794
+ value: this.formData[field.id],
795
+ help: hint,
796
+ placeholder,
797
+ wrapperClass: '$remove:formkit-wrapper',
798
+ labelClass: 'ui-dynamic-form-input-label',
799
+ inputClass: `input-${this.theme}`,
800
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
801
+ readonly: isReadOnly,
802
+ validation,
803
+ validationVisibility: 'live',
804
+ },
805
+ };
806
+ case 'week':
807
+ return {
808
+ type: 'FormKit',
809
+ props: {
810
+ type: 'week',
811
+ id: field.id,
812
+ name,
813
+ label: field.label,
814
+ required: field.required,
815
+ value: this.formData[field.id],
816
+ help: hint,
817
+ placeholder,
818
+ wrapperClass: '$remove:formkit-wrapper',
819
+ labelClass: 'ui-dynamic-form-input-label',
820
+ inputClass: `input-${this.theme}`,
821
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
822
+ readonly: isReadOnly,
823
+ validation,
824
+ validationVisibility: 'live',
825
+ },
826
+ };
827
+ default:
828
+ return {
829
+ type: 'FormKit',
830
+ props: {
831
+ type: field.type,
832
+ id: field.id,
833
+ name,
834
+ label: field.label,
835
+ required: field.required,
836
+ value: this.formData[field.id],
837
+ help: hint,
838
+ labelClass: 'ui-dynamic-form-input-label',
839
+ inputClass: `input-${this.theme}`,
840
+ innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
841
+ readonly: isReadOnly,
842
+ validation,
843
+ validationVisibility: 'live',
844
+ },
845
+ };
846
+ }
278
847
  },
279
- unmounted() {
280
- /* Make sure, any events you subscribe to on SocketIO are unsubscribed to here */
281
- this.$socket?.off('widget-load' + this.id);
282
- this.$socket?.off('msg-input:' + this.id);
848
+ toggleCollapse() {
849
+ this.collapsed = !this.collapsed;
283
850
  },
284
- methods: {
285
- createComponent(field) {
286
- console.debug('Creating component for field:', field);
287
- const customForm = field.customForm ? JSON.parse(field.customForm) : {};
288
- const hint = customForm.hint;
289
- const placeholder = customForm.placeholder;
290
- const validation = customForm.validation;
291
- const name = field.id;
292
- const customProperties = customForm.customProperties ?? [];
293
- const isReadOnly =
294
- this.props.readonly ||
295
- this.formIsFinished ||
296
- customProperties.find((entry) => ['readOnly', 'readonly'].includes(entry.name) && entry.value === 'true')
297
- ? 'true'
298
- : undefined;
299
- switch (field.type) {
300
- case 'long':
301
- return {
302
- type: 'FormKit',
303
- props: {
304
- type: 'number',
305
- id: field.id,
306
- name,
307
- label: field.label,
308
- required: field.required,
309
- value: this.formData[field.id],
310
- number: 'integer',
311
- min: 0,
312
- validation: validation ? `${validation}|number` : 'number',
313
- help: hint,
314
- wrapperClass: '$remove:formkit-wrapper',
315
- labelClass: 'ui-dynamic-form-input-label',
316
- inputClass: `input-${this.theme}`,
317
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
318
- readonly: isReadOnly,
319
- validationVisibility: 'live',
320
- },
321
- };
322
- case 'number':
323
- const step = field.customForm ? JSON.parse(field.customForm).step : undefined;
324
- return {
325
- type: 'FormKit',
326
- props: {
327
- type: 'number',
328
- id: field.id,
329
- name,
330
- label: field.label,
331
- required: field.required,
332
- value: this.formData[field.id],
333
- step,
334
- number: 'float',
335
- validation: validation ? `${validation}|number` : 'number',
336
- help: hint,
337
- wrapperClass: '$remove:formkit-wrapper',
338
- labelClass: 'ui-dynamic-form-input-label',
339
- inputClass: `input-${this.theme}`,
340
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
341
- readonly: isReadOnly,
342
- validationVisibility: 'live',
343
- },
344
- };
345
- case 'date':
346
- return {
347
- type: 'FormKit',
348
- props: {
349
- type: 'date',
350
- id: field.id,
351
- name,
352
- label: field.label,
353
- required: field.required,
354
- value: this.formData[field.id],
355
- help: hint,
356
- wrapperClass: '$remove:formkit-wrapper',
357
- labelClass: 'ui-dynamic-form-input-label',
358
- inputClass: `input-${this.theme}`,
359
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
360
- readonly: isReadOnly,
361
- validation,
362
- validationVisibility: 'live',
363
- },
364
- };
365
- case 'enum':
366
- const enums = field.enumValues.map((obj) => {
367
- return { value: obj.id, label: obj.name };
368
- });
369
- return {
370
- type: 'FormKit',
371
- props: {
372
- type: 'select', // JSON.parse(field.customForm).displayAs
373
- id: field.id,
374
- name,
375
- label: field.label,
376
- required: field.required,
377
- value: this.formData[field.id],
378
- options: enums,
379
- help: hint,
380
- wrapperClass: '$remove:formkit-wrapper',
381
- labelClass: 'ui-dynamic-form-input-label',
382
- inputClass: `input-${this.theme}`,
383
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
384
- readonly: isReadOnly,
385
- disabled: isReadOnly,
386
- validation,
387
- validationVisibility: 'live',
388
- },
389
- };
390
- case 'select':
391
- const selections = JSON.parse(field.customForm).entries.map((obj) => {
392
- return { value: obj.key, label: obj.value };
393
- });
394
- return {
395
- type: 'FormKit',
396
- props: {
397
- type: 'select', // JSON.parse(field.customForm).displayAs
398
- id: field.id,
399
- name,
400
- label: field.label,
401
- required: field.required,
402
- value: this.formData[field.id],
403
- options: selections,
404
- placeholder,
405
- help: hint,
406
- wrapperClass: '$remove:formkit-wrapper',
407
- labelClass: 'ui-dynamic-form-input-label',
408
- inputClass: `input-${this.theme}`,
409
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
410
- readonly: isReadOnly,
411
- disabled: isReadOnly,
412
- validation,
413
- validationVisibility: 'live',
414
- },
415
- };
416
- case 'string':
417
- return {
418
- type: 'FormKit',
419
- props: {
420
- type: 'text',
421
- id: field.id,
422
- name,
423
- label: field.label,
424
- required: field.required,
425
- value: this.formData[field.id],
426
- help: hint,
427
- placeholder,
428
- wrapperClass: '$remove:formkit-wrapper',
429
- labelClass: 'ui-dynamic-form-input-label',
430
- inputClass: `input-${this.theme}`,
431
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
432
- readonly: isReadOnly,
433
- validation,
434
- validationVisibility: 'live',
435
- },
436
- };
437
- case 'confirm':
438
- return {
439
- type: 'h3',
440
- innerText: field.label,
441
- };
442
- case 'boolean':
443
- return {
444
- type: 'FormKit',
445
- props: {
446
- type: 'checkbox',
447
- id: field.id,
448
- name,
449
- label: field.label,
450
- required: field.required,
451
- value: this.formData[field.id],
452
- help: hint,
453
- labelClass: 'ui-dynamic-form-input-label',
454
- inputClass: `input-${this.theme}`,
455
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
456
- readonly: isReadOnly,
457
- disabled: isReadOnly,
458
- validation,
459
- validationVisibility: 'live',
460
- },
461
- };
462
- case 'file':
463
- const multiple = field.customForm ? JSON.parse(field.customForm).multiple === 'true' : false;
464
- return {
465
- type: 'FormKit',
466
- props: {
467
- type: 'file',
468
- id: field.id,
469
- name,
470
- label: field.label,
471
- required: field.required,
472
- value: this.formData[field.id],
473
- help: hint,
474
- innerClass: 'reset-background',
475
- wrapperClass: '$remove:formkit-wrapper',
476
- labelClass: 'ui-dynamic-form-input-label',
477
- inputClass: `input-${this.theme}`,
478
- // innerClass: ui-dynamic-form-input-outlines `${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
479
- readonly: isReadOnly,
480
- disabled: isReadOnly,
481
- multiple,
482
- validation,
483
- validationVisibility: 'live',
484
- },
485
- };
486
- case 'checkbox':
487
- const options = JSON.parse(field.customForm).entries.map((obj) => {
488
- return { value: obj.key, label: obj.value };
489
- });
490
- return {
491
- type: 'FormKit',
492
- props: {
493
- type: 'checkbox',
494
- id: field.id,
495
- name,
496
- label: field.label,
497
- required: field.required,
498
- value: this.formData[field.id],
499
- options,
500
- help: hint,
501
- fieldsetClass: 'custom-fieldset',
502
- labelClass: 'ui-dynamic-form-input-label',
503
- inputClass: `input-${this.theme}`,
504
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
505
- readonly: isReadOnly,
506
- disabled: isReadOnly,
507
- validation,
508
- validationVisibility: 'live',
509
- },
510
- };
511
- case 'color':
512
- return {
513
- type: 'FormKit',
514
- props: {
515
- type: 'color',
516
- id: field.id,
517
- name,
518
- label: field.label,
519
- required: field.required,
520
- value: this.formData[field.id],
521
- help: hint,
522
- readonly: isReadOnly,
523
- disabled: isReadOnly,
524
- validation,
525
- validationVisibility: 'live',
526
- },
527
- };
528
- case 'datetime-local':
529
- return {
530
- type: 'FormKit',
531
- props: {
532
- type: 'datetime-local',
533
- id: field.id,
534
- name,
535
- label: field.label,
536
- required: field.required,
537
- value: this.formData[field.id],
538
- help: hint,
539
- wrapperClass: '$remove:formkit-wrapper',
540
- labelClass: 'ui-dynamic-form-input-label',
541
- inputClass: `input-${this.theme}`,
542
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
543
- readonly: isReadOnly,
544
- validation,
545
- validationVisibility: 'live',
546
- },
547
- };
548
- case 'email':
549
- return {
550
- type: 'FormKit',
551
- props: {
552
- type: 'email',
553
- id: field.id,
554
- name,
555
- label: field.label,
556
- required: field.required,
557
- value: this.formData[field.id],
558
- help: hint,
559
- placeholder,
560
- wrapperClass: '$remove:formkit-wrapper',
561
- labelClass: 'ui-dynamic-form-input-label',
562
- inputClass: `input-${this.theme}`,
563
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
564
- readonly: isReadOnly,
565
- validation,
566
- validationVisibility: 'live',
567
- },
568
- };
569
- case 'header':
570
- let typeToUse = 'h1';
571
- if (field.customForm && JSON.parse(field.customForm).style === 'heading_2') {
572
- typeToUse = 'h2';
573
- }
574
- if (field.customForm && JSON.parse(field.customForm).style === 'heading_3') {
575
- typeToUse = 'h3';
576
- }
577
- return {
578
- type: typeToUse,
579
- innerText: this.formData[field.id],
580
- };
581
- case 'hidden':
582
- return {
583
- type: 'input',
584
- props: {
585
- type: 'hidden',
586
- value: this.formData[field.id],
587
- },
588
- };
589
- case 'month':
590
- return {
591
- type: 'FormKit',
592
- props: {
593
- type: 'month',
594
- id: field.id,
595
- name,
596
- label: field.label,
597
- required: field.required,
598
- value: this.formData[field.id],
599
- help: hint,
600
- wrapperClass: '$remove:formkit-wrapper',
601
- labelClass: 'ui-dynamic-form-input-label',
602
- inputClass: `input-${this.theme}`,
603
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
604
- readonly: isReadOnly,
605
- validation,
606
- validationVisibility: 'live',
607
- },
608
- };
609
- case 'paragraph':
610
- const paragraphContent = this.formData[field.id] || field.defaultValue || field.label || '';
611
- const processedHtml = processMarkdown(paragraphContent);
612
- return {
613
- type: 'div',
614
- innerHTML: processedHtml,
615
- class: 'ui-dynamic-form-paragraph',
616
- };
617
- case 'password':
618
- return {
619
- type: 'FormKit',
620
- props: {
621
- type: 'password',
622
- id: field.id,
623
- name,
624
- label: field.label,
625
- required: field.required,
626
- value: this.formData[field.id],
627
- help: hint,
628
- placeholder,
629
- wrapperClass: '$remove:formkit-wrapper',
630
- labelClass: 'ui-dynamic-form-input-label',
631
- inputClass: `input-${this.theme}`,
632
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
633
- readonly: isReadOnly,
634
- validation,
635
- validationVisibility: 'live',
636
- },
637
- };
638
- case 'radio':
639
- const radioOptions = JSON.parse(field.customForm).entries.map((obj) => {
640
- return { value: obj.key, label: obj.value };
641
- });
642
- return {
643
- type: 'FormKit',
644
- props: {
645
- type: 'radio',
646
- id: field.id,
647
- name,
648
- label: field.label,
649
- required: field.required,
650
- value: this.formData[field.id],
651
- options: radioOptions,
652
- help: hint,
653
- fieldsetClass: 'custom-fieldset',
654
- labelClass: 'ui-dynamic-form-input-label',
655
- inputClass: `input-${this.theme}`,
656
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
657
- readonly: isReadOnly,
658
- disabled: isReadOnly,
659
- validation,
660
- validationVisibility: 'live',
661
- },
662
- };
663
- case 'range':
664
- const customForm = JSON.parse(field.customForm);
665
- return {
666
- type: 'v-slider',
667
- props: {
668
- id: field.id,
669
- name,
670
- // label: field.label,
671
- required: field.required,
672
- // value: this.formData[field.id],
673
- // help: hint,
674
- min: customForm.min,
675
- max: customForm.max,
676
- step: customForm.step,
677
- thumbLabel: true,
678
- // wrapperClass: '$remove:formkit-wrapper',
679
- labelClass: 'ui-dynamic-form-input-label',
680
- // inputClass: `input-${this.theme}`,
681
- // innerClass: ui-dynamic-form-input-outlines `${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
682
- readonly: isReadOnly,
683
- disabled: isReadOnly,
684
- validation,
685
- validationVisibility: 'live',
686
- },
687
- };
688
- case 'tel':
689
- return {
690
- type: 'FormKit',
691
- props: {
692
- type: 'tel' /* with pro component mask more good */,
693
- id: field.id,
694
- name,
695
- label: field.label,
696
- required: field.required,
697
- value: this.formData[field.id],
698
- help: hint,
699
- placeholder,
700
- wrapperClass: '$remove:formkit-wrapper',
701
- labelClass: 'ui-dynamic-form-input-label',
702
- inputClass: `input-${this.theme}`,
703
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
704
- readonly: isReadOnly,
705
- validation,
706
- validationVisibility: 'live',
707
- },
708
- };
709
- case 'textarea':
710
- const rows = field.customForm ? JSON.parse(field.customForm).rows : undefined;
711
- return {
712
- type: 'FormKit',
713
- props: {
714
- type: 'textarea' /* with pro component mask more good */,
715
- id: field.id,
716
- name,
717
- label: field.label,
718
- required: field.required,
719
- value: this.formData[field.id],
720
- rows,
721
- help: hint,
722
- placeholder,
723
- wrapperClass: '$remove:formkit-wrapper',
724
- labelClass: 'ui-dynamic-form-input-label',
725
- inputClass: `input-${this.theme}`,
726
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
727
- readonly: isReadOnly,
728
- validation,
729
- validationVisibility: 'live',
730
- },
731
- };
732
- case 'time':
733
- return {
734
- type: 'FormKit',
735
- props: {
736
- type: 'time' /* with pro component mask more good */,
737
- id: field.id,
738
- name,
739
- label: field.label,
740
- required: field.required,
741
- value: this.formData[field.id],
742
- help: hint,
743
- placeholder,
744
- wrapperClass: '$remove:formkit-wrapper',
745
- labelClass: 'ui-dynamic-form-input-label',
746
- inputClass: `input-${this.theme}`,
747
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
748
- readonly: isReadOnly,
749
- validation,
750
- validationVisibility: 'live',
751
- },
752
- };
753
- case 'url':
754
- return {
755
- type: 'FormKit',
756
- props: {
757
- type: 'url',
758
- id: field.id,
759
- name,
760
- label: field.label,
761
- required: field.required,
762
- value: this.formData[field.id],
763
- help: hint,
764
- placeholder,
765
- wrapperClass: '$remove:formkit-wrapper',
766
- labelClass: 'ui-dynamic-form-input-label',
767
- inputClass: `input-${this.theme}`,
768
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
769
- readonly: isReadOnly,
770
- validation,
771
- validationVisibility: 'live',
772
- },
773
- };
774
- case 'week':
775
- return {
776
- type: 'FormKit',
777
- props: {
778
- type: 'week',
779
- id: field.id,
780
- name,
781
- label: field.label,
782
- required: field.required,
783
- value: this.formData[field.id],
784
- help: hint,
785
- placeholder,
786
- wrapperClass: '$remove:formkit-wrapper',
787
- labelClass: 'ui-dynamic-form-input-label',
788
- inputClass: `input-${this.theme}`,
789
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
790
- readonly: isReadOnly,
791
- validation,
792
- validationVisibility: 'live',
793
- },
794
- };
795
- default:
796
- return {
797
- type: 'FormKit',
798
- props: {
799
- type: field.type,
800
- id: field.id,
801
- name,
802
- label: field.label,
803
- required: field.required,
804
- value: this.formData[field.id],
805
- help: hint,
806
- labelClass: 'ui-dynamic-form-input-label',
807
- inputClass: `input-${this.theme}`,
808
- innerClass: `ui-dynamic-form-input-outlines ${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
809
- readonly: isReadOnly,
810
- validation,
811
- validationVisibility: 'live',
812
- },
813
- };
814
- }
815
- },
816
- toggleCollapse() {
817
- this.collapsed = !this.collapsed;
818
- },
819
- getRowWidthStyling(field, index) {
820
- let style = '';
821
- if (index === 0) {
822
- style += 'margin-top: 12px;';
823
- }
824
- if (field.type === 'header') {
825
- style += 'flex-basis: 100%;';
826
- } else {
827
- style += `flex-basis: 100%;`;
828
- }
829
- return style;
830
- },
831
- fields() {
832
- const aFields = this.userTask.userTaskConfig?.formFields ?? [];
833
- const fieldMap = aFields.map((field) => ({
834
- ...field,
835
- items: mapItems(field.type, field),
836
- }));
851
+ getRowWidthStyling(field, index) {
852
+ let style = '';
853
+ if (index === 0) {
854
+ style += 'margin-top: 12px;';
855
+ }
856
+ if (field.type === 'header') {
857
+ style += 'flex-basis: 100%;';
858
+ } else {
859
+ style += `flex-basis: 100%;`;
860
+ }
861
+ return style;
862
+ },
863
+ fields() {
864
+ const aFields = this.userTask.userTaskConfig?.formFields ?? [];
865
+ const fieldMap = aFields.map((field) => ({
866
+ ...field,
867
+ items: mapItems(field.type, field),
868
+ }));
837
869
 
838
- return fieldMap;
839
- },
840
- /*
841
- widget-action just sends a msg to Node-RED, it does not store the msg state server-side
842
- alternatively, you can use widget-change, which will also store the msg in the Node's datastore
843
- */
844
- send(msg, index) {
845
- const msgArr = [];
846
- msgArr[index] = msg;
847
- this.$socket.emit('widget-action', this.id, msgArr);
848
- },
849
- init(msg) {
850
- this.msg = msg;
851
- if (!msg) {
852
- return;
853
- }
870
+ return fieldMap;
871
+ },
872
+ send(msg, index) {
873
+ const msgArr = [];
874
+ msgArr[index] = msg;
875
+ this.$socket.emit('widget-action', this.id, msgArr);
876
+ },
877
+ init(msg) {
878
+ this.msg = msg;
879
+ if (!msg) {
880
+ return;
881
+ }
882
+
883
+ this.actions = this.props.options;
884
+
885
+ const hasTask = msg.payload && msg.payload.userTask;
886
+
887
+ if (hasTask) {
888
+ this.userTask = msg.payload.userTask;
889
+ } else {
890
+ this.userTask = null;
891
+ this.formData = {};
892
+ return;
893
+ }
854
894
 
855
- this.actions = this.props.options;
895
+ const formFields = this.userTask.userTaskConfig.formFields;
896
+ const formFieldIds = formFields.map((ff) => ff.id);
897
+ const initialValues = this.userTask.startToken;
898
+ const finishedFormData = msg.payload.formData;
899
+ this.formIsFinished = !!msg.payload.formData;
900
+ if (this.formIsFinished) {
901
+ this.collapsed = this.props.collapse_when_finished;
902
+ }
856
903
 
857
- const hasTask = msg.payload && msg.payload.userTask;
904
+ if (formFields) {
905
+ console.log('Form fields:', formFields);
906
+ formFields.forEach((field) => {
907
+ this.formData[field.id] = field.defaultValue;
858
908
 
859
- if (hasTask) {
860
- this.userTask = msg.payload.userTask;
909
+ if (field.type === 'confirm') {
910
+ const customForm = field.customForm ? JSON.parse(JSON.stringify(field.customForm)) : {};
911
+ const confirmText = customForm.confirmButtonText ?? 'Confirm';
912
+ const declineText = customForm.declineButtonText ?? 'Decline';
913
+ const confirmActions = [
914
+ {
915
+ alignment: 'left',
916
+ primary: 'true',
917
+ label: confirmText,
918
+ condition: '',
919
+ isConfirmAction: true,
920
+ confirmFieldId: field.id,
921
+ confirmValue: true,
922
+ },
923
+ {
924
+ alignment: 'left',
925
+ primary: 'true',
926
+ label: declineText,
927
+ condition: '',
928
+ isConfirmAction: true,
929
+ confirmFieldId: field.id,
930
+ confirmValue: false,
931
+ },
932
+ ];
933
+ if (this.props.handle_confirmation_dialogs) {
934
+ this.actions = confirmActions;
861
935
  } else {
862
- this.userTask = null;
863
- this.formData = {};
864
- return;
936
+ this.actions = [...this.actions, ...confirmActions];
865
937
  }
938
+ }
939
+ });
940
+ }
866
941
 
867
- const formFields = this.userTask.userTaskConfig.formFields;
868
- const formFieldIds = formFields.map((ff) => ff.id);
869
- const initialValues = this.userTask.startToken;
870
- const finishedFormData = msg.payload.formData;
871
- this.formIsFinished = !!msg.payload.formData;
872
- if (this.formIsFinished) {
873
- this.collapsed = this.props.collapse_when_finished;
874
- }
942
+ if (initialValues) {
943
+ Object.keys(initialValues)
944
+ .filter((key) => formFieldIds.includes(key))
945
+ .forEach((key) => {
946
+ this.formData[key] = initialValues[key];
947
+ });
948
+ }
875
949
 
876
- if (formFields) {
877
- formFields.forEach((field) => {
878
- this.formData[field.id] = field.defaultValue;
950
+ if (this.formIsFinished) {
951
+ Object.keys(finishedFormData)
952
+ .filter((key) => formFieldIds.includes(key))
953
+ .forEach((key) => {
954
+ this.formData[key] = finishedFormData[key];
955
+ });
956
+ }
879
957
 
880
- if (field.type === 'confirm') {
881
- const customForm = field.customForm ? JSON.parse(field.customForm) : {};
882
- const confirmText = customForm.confirmButtonText ?? 'Confirm';
883
- const declineText = customForm.declineButtonText ?? 'Decline';
884
- this.actions = [
885
- {
886
- alignment: 'right',
887
- primary: 'false',
888
- label: declineText,
889
- condition: '',
890
- },
891
- {
892
- alignment: 'right',
893
- primary: 'true',
894
- label: confirmText,
895
- condition: '',
896
- },
897
- ];
898
- }
899
- });
900
- }
958
+ nextTick(() => {
959
+ this.focusFirstFormField();
960
+ });
961
+ },
962
+ actionFn(action) {
963
+ if (action.isTerminate) {
964
+ this.showError('');
901
965
 
902
- if (initialValues) {
903
- Object.keys(initialValues)
904
- .filter((key) => formFieldIds.includes(key))
905
- .forEach((key) => {
906
- this.formData[key] = initialValues[key];
907
- });
908
- }
966
+ const msg = this.msg ?? {};
967
+ msg.payload = {
968
+ formData: this.formData,
969
+ userTask: this.userTask,
970
+ isTerminate: true,
971
+ };
909
972
 
910
- if (this.formIsFinished) {
911
- Object.keys(finishedFormData)
912
- .filter((key) => formFieldIds.includes(key))
913
- .forEach((key) => {
914
- this.formData[key] = finishedFormData[key];
915
- });
916
- }
973
+ const terminateOutputIndex = this.totalOutputs - 1;
917
974
 
918
- nextTick(() => {
919
- this.focusFirstFormField();
920
- });
921
- },
922
- actionFn(action) {
923
- if (action.label === 'Speichern' || action.label === 'Speichern und nächster') {
924
- const formkitInputs = this.$refs.form.$el.querySelectorAll('.formkit-outer');
925
- let allComplete = true;
975
+ this.send(msg, terminateOutputIndex);
976
+ return;
977
+ }
926
978
 
927
- formkitInputs.forEach((input) => {
928
- const dataComplete = input.getAttribute('data-complete');
929
- const dataInvalid = input.getAttribute('data-invalid');
979
+ if (action.isSuspend) {
980
+ this.showError('');
930
981
 
931
- if (dataComplete == null && dataInvalid === 'true') {
932
- allComplete = false;
933
- }
934
- });
982
+ const msg = this.msg ?? {};
983
+ msg.payload = {
984
+ formData: this.formData,
985
+ userTask: this.userTask,
986
+ isSuspend: true,
987
+ };
935
988
 
936
- if (!allComplete) return;
937
- }
989
+ const suspendOutputIndex = this.totalOutputs - 2;
938
990
 
939
- if (this.checkCondition(action.condition)) {
940
- this.showError('');
991
+ this.send(msg, suspendOutputIndex);
992
+ return;
993
+ }
941
994
 
942
- const processedFormData = { ...this.formData };
943
- const formFields = this.userTask.userTaskConfig.formFields;
995
+ if (action.isConfirmAction && action.confirmFieldId) {
996
+ this.formData[action.confirmFieldId] = action.confirmValue;
997
+ }
944
998
 
945
- formFields.forEach((field) => {
946
- const fieldValue = processedFormData[field.id];
999
+ if (action.label === 'Speichern' || action.label === 'Speichern und nächster') {
1000
+ const formkitInputs = this.$refs.form.$el.querySelectorAll('.formkit-outer');
1001
+ let allComplete = true;
947
1002
 
948
- if (field.type === 'number' || field.type === 'long') {
949
- if (fieldValue !== null && fieldValue !== undefined && fieldValue !== '') {
950
- if (field.type === 'long') {
951
- const intValue = Number.parseInt(fieldValue, 10);
952
- if (!isNaN(intValue)) {
953
- processedFormData[field.id] = intValue;
954
- }
955
- } else {
956
- const numValue = Number.parseFloat(fieldValue);
957
- if (!isNaN(numValue)) {
958
- processedFormData[field.id] = numValue;
959
- }
960
- }
961
- }
962
- }
963
- });
1003
+ formkitInputs.forEach((input) => {
1004
+ const dataComplete = input.getAttribute('data-complete');
1005
+ const dataInvalid = input.getAttribute('data-invalid');
964
1006
 
965
- const msg = this.msg ?? {};
966
- msg.payload = { formData: processedFormData, userTask: this.userTask };
967
- this.send(
968
- msg,
969
- this.actions.findIndex((element) => element.label === action.label) +
970
- (this.isConfirmDialog ? this.props.options.length : 0)
971
- );
972
- // TODO: mm - end
973
- } else {
974
- this.showError(action.errorMessage);
975
- }
976
- },
977
- checkCondition(condition) {
978
- if (condition === '') return true;
979
- try {
980
- // eslint-disable-next-line no-new-func
981
- const func = Function('fields', 'userTask', 'msg', '"use strict"; return (' + condition + ')');
982
- const result = func(this.formData, this.userTask, this.msg);
983
- return Boolean(result);
984
- } catch (err) {
985
- console.error('Error while evaluating condition: ' + err);
986
- return false;
987
- }
988
- },
989
- showError(errMsg) {
990
- this.errorMsg = errMsg;
991
- },
992
- focusFirstFormField() {
993
- if (this.collapsed || !this.hasUserTask) {
994
- return;
995
- }
1007
+ if (dataComplete == null && dataInvalid === 'true') {
1008
+ allComplete = false;
1009
+ }
1010
+ });
996
1011
 
997
- if (this.firstFormFieldRef) {
998
- let inputElement = null;
1012
+ if (!allComplete) return;
1013
+ }
999
1014
 
1000
- if (this.firstFormFieldRef.node && this.firstFormFieldRef.node.input instanceof HTMLElement) {
1001
- inputElement = this.firstFormFieldRef.node.input;
1002
- } else if (this.firstFormFieldRef.$el instanceof HTMLElement) {
1003
- if (['INPUT', 'TEXTAREA', 'SELECT'].includes(this.firstFormFieldRef.$el.tagName)) {
1004
- inputElement = this.firstFormFieldRef.$el;
1005
- } else {
1006
- inputElement = this.firstFormFieldRef.$el.querySelector('input:not([type="hidden"]), textarea, select');
1007
- }
1008
- }
1015
+ if (this.checkCondition(action.condition)) {
1016
+ this.showError('');
1009
1017
 
1010
- if (inputElement) {
1011
- inputElement.focus();
1012
- } else {
1013
- console.warn('Could not find a focusable input element for the first form field.');
1018
+ const processedFormData = { ...this.formData };
1019
+ const formFields = this.userTask.userTaskConfig.formFields;
1020
+
1021
+ formFields.forEach((field) => {
1022
+ const fieldValue = processedFormData[field.id];
1023
+
1024
+ if (field.type === 'number' || field.type === 'long') {
1025
+ if (fieldValue !== null && fieldValue !== undefined && fieldValue !== '') {
1026
+ if (field.type === 'long') {
1027
+ const intValue = Number.parseInt(fieldValue, 10);
1028
+ if (!isNaN(intValue)) {
1029
+ processedFormData[field.id] = intValue;
1030
+ }
1031
+ } else {
1032
+ const numValue = Number.parseFloat(fieldValue);
1033
+ if (!isNaN(numValue)) {
1034
+ processedFormData[field.id] = numValue;
1014
1035
  }
1036
+ }
1015
1037
  }
1016
- },
1038
+ }
1039
+ });
1040
+
1041
+ const msg = this.msg ?? {};
1042
+ msg.payload = { formData: processedFormData, userTask: this.userTask };
1043
+ this.send(
1044
+ msg,
1045
+ this.actions.findIndex((element) => element.label === action.label) +
1046
+ (this.isConfirmDialog ? this.props.options.length : 0)
1047
+ );
1048
+ } else {
1049
+ this.showError(action.errorMessage);
1050
+ }
1051
+ },
1052
+ checkCondition(condition) {
1053
+ if (condition === '') return true;
1054
+ try {
1055
+ const func = Function('fields', 'userTask', 'msg', '"use strict"; return (' + condition + ')');
1056
+ const result = func(this.formData, this.userTask, this.msg);
1057
+ return Boolean(result);
1058
+ } catch (err) {
1059
+ console.error('Error while evaluating condition: ' + err);
1060
+ return false;
1061
+ }
1062
+ },
1063
+ showError(errMsg) {
1064
+ this.errorMsg = errMsg;
1065
+ },
1066
+ focusFirstFormField() {
1067
+ if (this.collapsed || !this.hasUserTask) {
1068
+ return;
1069
+ }
1070
+
1071
+ if (this.firstFormFieldRef) {
1072
+ let inputElement = null;
1073
+
1074
+ if (this.firstFormFieldRef.node && this.firstFormFieldRef.node.input instanceof HTMLElement) {
1075
+ inputElement = this.firstFormFieldRef.node.input;
1076
+ } else if (this.firstFormFieldRef.$el instanceof HTMLElement) {
1077
+ if (['INPUT', 'TEXTAREA', 'SELECT'].includes(this.firstFormFieldRef.$el.tagName)) {
1078
+ inputElement = this.firstFormFieldRef.$el;
1079
+ } else {
1080
+ inputElement = this.firstFormFieldRef.$el.querySelector('input:not([type="hidden"]), textarea, select');
1081
+ }
1082
+ }
1083
+
1084
+ if (inputElement) {
1085
+ inputElement.focus();
1086
+ } else {
1087
+ console.warn('Could not find a focusable input element for the first form field.');
1088
+ }
1089
+ }
1017
1090
  },
1091
+ },
1018
1092
  };
1019
1093
 
1020
1094
  function mapItems(type, field) {
1021
- if (type === 'enum') {
1022
- return field.enumValues.map((enumValue) => ({
1023
- title: enumValue.name,
1024
- value: enumValue.id,
1025
- }));
1026
- } else {
1027
- return null;
1028
- }
1095
+ if (type === 'enum') {
1096
+ return field.enumValues.map((enumValue) => ({
1097
+ title: enumValue.name,
1098
+ value: enumValue.id,
1099
+ }));
1100
+ } else {
1101
+ return null;
1102
+ }
1029
1103
  }
1030
1104
  </script>
1031
1105