@5minds/node-red-dashboard-2-processcube-dynamic-form 2.1.0 → 2.1.1

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