@5minds/node-red-dashboard-2-processcube-dynamic-form 2.0.3-feature-80b521-mbt3b80e → 2.0.3-feature-6a41d1-mcdc8slf
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.
|
@@ -2,39 +2,37 @@
|
|
|
2
2
|
<div className="ui-dynamic-form-external-sizing-wrapper" :style="props.card_size_styling">
|
|
3
3
|
<!-- Component must be wrapped in a block so props such as className and style can be passed in from parent -->
|
|
4
4
|
<UIDynamicFormTitleText
|
|
5
|
-
v-if="props.title_style === 'outside' && hasUserTask"
|
|
6
|
-
:
|
|
7
|
-
:
|
|
8
|
-
:customStyles="props.title_custom_text_styling"
|
|
9
|
-
:titleIcon="props.title_icon"
|
|
10
|
-
:collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)"
|
|
11
|
-
:collapsed="collapsed"
|
|
5
|
+
v-if="props.title_style === 'outside' && hasUserTask" :style="props.title_style"
|
|
6
|
+
:title="props.title_text" :customStyles="props.title_custom_text_styling" :titleIcon="props.title_icon"
|
|
7
|
+
:collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)" :collapsed="collapsed"
|
|
12
8
|
:toggleCollapse="toggleCollapse"
|
|
13
9
|
/>
|
|
14
10
|
<div className="ui-dynamic-form-wrapper">
|
|
15
|
-
<p v-if="hasUserTask" style="margin-bottom: 0px
|
|
11
|
+
<p v-if="hasUserTask" style="margin-bottom: 0px">
|
|
16
12
|
<v-form ref="form" v-model="form" :class="dynamicClass">
|
|
17
13
|
<UIDynamicFormTitleText
|
|
18
|
-
v-if="props.title_style != 'outside'"
|
|
19
|
-
:
|
|
20
|
-
:title="props.title_text"
|
|
21
|
-
:customStyles="props.title_custom_text_styling"
|
|
14
|
+
v-if="props.title_style != 'outside'" :style="props.title_style"
|
|
15
|
+
:title="props.title_text" :customStyles="props.title_custom_text_styling"
|
|
22
16
|
:titleIcon="props.title_icon"
|
|
23
17
|
:collapsible="props.collapsible || (props.collapse_when_finished && formIsFinished)"
|
|
24
|
-
:collapsed="collapsed"
|
|
25
|
-
:toggleCollapse="toggleCollapse"
|
|
18
|
+
:collapsed="collapsed" :toggleCollapse="toggleCollapse"
|
|
26
19
|
/>
|
|
27
20
|
<Transition name="cardCollapse">
|
|
28
21
|
<div v-if="!collapsed">
|
|
29
|
-
<div className="ui-dynamic-form-formfield-positioner">
|
|
22
|
+
<div className="ui-dynamic-form-formfield-positioner" :style="props.inner_card_styling">
|
|
30
23
|
<FormKit id="form" type="group">
|
|
31
|
-
<v-row
|
|
24
|
+
<v-row
|
|
25
|
+
v-for="(field, index) in fields()" :key="field"
|
|
26
|
+
:style="getRowWidthStyling(field, index)"
|
|
27
|
+
>
|
|
32
28
|
<v-col cols="12">
|
|
33
29
|
<component
|
|
34
30
|
:is="createComponent(field).type"
|
|
35
31
|
v-if="createComponent(field).innerText"
|
|
36
|
-
v-bind="createComponent(field).props"
|
|
37
|
-
|
|
32
|
+
v-bind="createComponent(field).props" :ref="(el) => {
|
|
33
|
+
if (index === 0) firstFormFieldRef = el;
|
|
34
|
+
}
|
|
35
|
+
" v-model="formData[field.id]"
|
|
38
36
|
>
|
|
39
37
|
{{ createComponent(field).innerText }}
|
|
40
38
|
</component>
|
|
@@ -42,18 +40,22 @@
|
|
|
42
40
|
<p class="formkit-label">{{ field.label }}</p>
|
|
43
41
|
<component
|
|
44
42
|
:is="createComponent(field).type"
|
|
45
|
-
v-bind="createComponent(field).props"
|
|
46
|
-
|
|
43
|
+
v-bind="createComponent(field).props" :ref="(el) => {
|
|
44
|
+
if (index === 0) firstFormFieldRef = el;
|
|
45
|
+
}
|
|
46
|
+
" v-model="field.defaultValue"
|
|
47
47
|
/>
|
|
48
48
|
<p class="formkit-help">
|
|
49
|
-
{{ field.customForm ? JSON.parse(field.customForm).hint : undefined
|
|
49
|
+
{{ field.customForm ? JSON.parse(field.customForm).hint : undefined
|
|
50
|
+
}}
|
|
50
51
|
</p>
|
|
51
52
|
</div>
|
|
52
53
|
<component
|
|
53
|
-
:is="createComponent(field).type"
|
|
54
|
-
v-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
:is="createComponent(field).type" v-else
|
|
55
|
+
v-bind="createComponent(field).props" :ref="(el) => {
|
|
56
|
+
if (index === 0) firstFormFieldRef = el;
|
|
57
|
+
}
|
|
58
|
+
" v-model="formData[field.id]"
|
|
57
59
|
/>
|
|
58
60
|
</v-col>
|
|
59
61
|
</v-row>
|
|
@@ -63,17 +65,24 @@
|
|
|
63
65
|
<v-row v-if="errorMsg.length > 0" style="padding: 12px">
|
|
64
66
|
<v-alert type="error">Error: {{ errorMsg }}</v-alert>
|
|
65
67
|
</v-row>
|
|
66
|
-
<UIDynamicFormFooterAction
|
|
68
|
+
<UIDynamicFormFooterAction
|
|
69
|
+
v-if="props.actions_inside_card && actions.length > 0"
|
|
70
|
+
:actions="actions" :actionCallback="actionFn" :formIsFinished="formIsFinished"
|
|
71
|
+
style="padding: 16px; padding-top: 0px"
|
|
72
|
+
/>
|
|
67
73
|
</v-row>
|
|
68
74
|
</div>
|
|
69
75
|
</Transition>
|
|
70
76
|
</v-form>
|
|
71
77
|
</p>
|
|
72
78
|
<p v-else>
|
|
73
|
-
<v-alert
|
|
79
|
+
<v-alert
|
|
80
|
+
v-if="props.waiting_info.length > 0 || props.waiting_title.length > 0"
|
|
81
|
+
:text="props.waiting_info" :title="props.waiting_title"
|
|
82
|
+
/>
|
|
74
83
|
</p>
|
|
75
84
|
</div>
|
|
76
|
-
<div v-if="!props.actions_inside_card && actions.length > 0 && hasUserTask" style="padding-top: 32px
|
|
85
|
+
<div v-if="!props.actions_inside_card && actions.length > 0 && hasUserTask" style="padding-top: 32px">
|
|
77
86
|
<UIDynamicFormFooterAction :actions="actions" :actionCallback="actionFn" />
|
|
78
87
|
</div>
|
|
79
88
|
</div>
|
|
@@ -81,22 +90,39 @@
|
|
|
81
90
|
|
|
82
91
|
<!-- eslint-disable no-case-declarations -->
|
|
83
92
|
<script>
|
|
93
|
+
import { de } from '@formkit/i18n'
|
|
84
94
|
import { FormKit, defaultConfig, plugin } from '@formkit/vue'
|
|
85
|
-
import { getCurrentInstance, markRaw } from 'vue'
|
|
95
|
+
import { getCurrentInstance, markRaw, nextTick } from 'vue'
|
|
86
96
|
|
|
87
97
|
// eslint-disable-next-line import/no-unresolved
|
|
88
98
|
import '@formkit/themes/genesis'
|
|
89
99
|
import UIDynamicFormFooterAction from './FooterActions.vue'
|
|
90
100
|
import UIDynamicFormTitleText from './TitleText.vue'
|
|
91
101
|
|
|
102
|
+
// eslint-disable-next-line no-unused-vars
|
|
103
|
+
function requiredIf ({ value }, [targetField, expectedValue], node) {
|
|
104
|
+
console.debug(arguments)
|
|
105
|
+
|
|
106
|
+
const actual = node?.root?.value?.[targetField]
|
|
107
|
+
const isEmpty = value === '' || value === null || value === undefined
|
|
108
|
+
|
|
109
|
+
if (actual === expectedValue && isEmpty) {
|
|
110
|
+
return false // oder: `return "Dieses Feld ist erforderlich."`
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
|
|
92
116
|
export default {
|
|
93
117
|
name: 'UIDynamicForm',
|
|
94
118
|
components: {
|
|
95
|
-
FormKit,
|
|
119
|
+
FormKit,
|
|
120
|
+
UIDynamicFormFooterAction,
|
|
121
|
+
UIDynamicFormTitleText
|
|
96
122
|
},
|
|
97
123
|
inject: ['$socket'],
|
|
98
124
|
props: {
|
|
99
|
-
|
|
125
|
+
/* do not remove entries from this - Dashboard's Layout Manager's will pass this data to your component */
|
|
100
126
|
id: { type: String, required: true },
|
|
101
127
|
props: { type: Object, default: () => ({}) },
|
|
102
128
|
state: {
|
|
@@ -107,10 +133,16 @@ export default {
|
|
|
107
133
|
setup (props) {
|
|
108
134
|
console.info('UIDynamicForm setup with:', props)
|
|
109
135
|
console.debug('Vue function loaded correctly', markRaw)
|
|
136
|
+
|
|
110
137
|
const instance = getCurrentInstance()
|
|
111
138
|
const app = instance.appContext.app
|
|
139
|
+
|
|
112
140
|
const formkitConfig = defaultConfig({
|
|
113
|
-
theme: 'genesis'
|
|
141
|
+
theme: 'genesis',
|
|
142
|
+
locales: { de },
|
|
143
|
+
locale: 'de',
|
|
144
|
+
// eslint-disable-next-line object-shorthand
|
|
145
|
+
rules: { requiredIf: requiredIf }
|
|
114
146
|
})
|
|
115
147
|
app.use(plugin, formkitConfig)
|
|
116
148
|
},
|
|
@@ -123,7 +155,8 @@ export default {
|
|
|
123
155
|
errorMsg: '',
|
|
124
156
|
formIsFinished: false,
|
|
125
157
|
msg: null,
|
|
126
|
-
collapsed: false
|
|
158
|
+
collapsed: false,
|
|
159
|
+
firstFormFieldRef: null
|
|
127
160
|
}
|
|
128
161
|
},
|
|
129
162
|
computed: {
|
|
@@ -137,10 +170,14 @@ export default {
|
|
|
137
170
|
return !!this.userTask
|
|
138
171
|
},
|
|
139
172
|
totalOutputs () {
|
|
140
|
-
return
|
|
173
|
+
return (
|
|
174
|
+
this.props.options.length +
|
|
175
|
+
(this.props.handle_confirmation_dialogs ? 2 : 0) +
|
|
176
|
+
(this.props.trigger_on_change ? 1 : 0)
|
|
177
|
+
)
|
|
141
178
|
},
|
|
142
179
|
isConfirmDialog () {
|
|
143
|
-
return this.userTask.userTaskConfig.formFields.some(field => field.type === 'confirm')
|
|
180
|
+
return this.userTask.userTaskConfig.formFields.some((field) => field.type === 'confirm')
|
|
144
181
|
}
|
|
145
182
|
},
|
|
146
183
|
watch: {
|
|
@@ -151,6 +188,20 @@ export default {
|
|
|
151
188
|
this.send(res, this.totalOutputs - 1)
|
|
152
189
|
}
|
|
153
190
|
},
|
|
191
|
+
collapsed (newVal) {
|
|
192
|
+
if (!newVal && this.hasUserTask) {
|
|
193
|
+
nextTick(() => {
|
|
194
|
+
this.focusFirstFormField()
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
userTask (newVal) {
|
|
199
|
+
if (newVal && !this.collapsed) {
|
|
200
|
+
nextTick(() => {
|
|
201
|
+
this.focusFirstFormField()
|
|
202
|
+
})
|
|
203
|
+
}
|
|
204
|
+
},
|
|
154
205
|
deep: true
|
|
155
206
|
}
|
|
156
207
|
},
|
|
@@ -204,10 +255,12 @@ export default {
|
|
|
204
255
|
const validation = customForm.validation
|
|
205
256
|
const name = field.id
|
|
206
257
|
const customProperties = customForm.customProperties ?? []
|
|
207
|
-
const isReadOnly =
|
|
208
|
-
this.props.readonly ||
|
|
209
|
-
|
|
210
|
-
|
|
258
|
+
const isReadOnly =
|
|
259
|
+
this.props.readonly ||
|
|
260
|
+
this.formIsFinished ||
|
|
261
|
+
customProperties.find((entry) => ['readOnly', 'readonly'].includes(entry.name) && entry.value === 'true')
|
|
262
|
+
? 'true'
|
|
263
|
+
: undefined
|
|
211
264
|
switch (field.type) {
|
|
212
265
|
case 'long':
|
|
213
266
|
return {
|
|
@@ -731,7 +784,7 @@ export default {
|
|
|
731
784
|
if (field.type === 'header') {
|
|
732
785
|
style += 'flex-basis: 100%;'
|
|
733
786
|
} else {
|
|
734
|
-
style += `flex-basis: ${1 / this.props.form_columns * 100}%;`
|
|
787
|
+
style += `flex-basis: ${(1 / this.props.form_columns) * 100}%;`
|
|
735
788
|
}
|
|
736
789
|
return style
|
|
737
790
|
},
|
|
@@ -745,9 +798,9 @@ export default {
|
|
|
745
798
|
return fieldMap
|
|
746
799
|
},
|
|
747
800
|
/*
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
801
|
+
widget-action just sends a msg to Node-RED, it does not store the msg state server-side
|
|
802
|
+
alternatively, you can use widget-change, which will also store the msg in the Node's datastore
|
|
803
|
+
*/
|
|
751
804
|
send (msg, index) {
|
|
752
805
|
const msgArr = []
|
|
753
806
|
msgArr[index] = msg
|
|
@@ -772,7 +825,7 @@ export default {
|
|
|
772
825
|
}
|
|
773
826
|
|
|
774
827
|
const formFields = this.userTask.userTaskConfig.formFields
|
|
775
|
-
const formFieldIds = formFields.map(ff => ff.id)
|
|
828
|
+
const formFieldIds = formFields.map((ff) => ff.id)
|
|
776
829
|
const initialValues = this.userTask.startToken
|
|
777
830
|
const finishedFormData = msg.payload.formData
|
|
778
831
|
this.formIsFinished = !!msg.payload.formData
|
|
@@ -788,32 +841,43 @@ export default {
|
|
|
788
841
|
const customForm = field.customForm ? JSON.parse(field.customForm) : {}
|
|
789
842
|
const confirmText = customForm.confirmButtonText ?? 'Confirm'
|
|
790
843
|
const declineText = customForm.declineButtonText ?? 'Decline'
|
|
791
|
-
this.actions = [
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
844
|
+
this.actions = [
|
|
845
|
+
{
|
|
846
|
+
alignment: 'right',
|
|
847
|
+
primary: 'false',
|
|
848
|
+
label: declineText,
|
|
849
|
+
condition: ''
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
alignment: 'right',
|
|
853
|
+
primary: 'true',
|
|
854
|
+
label: confirmText,
|
|
855
|
+
condition: ''
|
|
856
|
+
}
|
|
857
|
+
]
|
|
802
858
|
}
|
|
803
859
|
})
|
|
804
860
|
}
|
|
805
861
|
|
|
806
862
|
if (initialValues) {
|
|
807
|
-
Object.keys(initialValues)
|
|
808
|
-
|
|
809
|
-
|
|
863
|
+
Object.keys(initialValues)
|
|
864
|
+
.filter((key) => formFieldIds.includes(key))
|
|
865
|
+
.forEach((key) => {
|
|
866
|
+
this.formData[key] = initialValues[key]
|
|
867
|
+
})
|
|
810
868
|
}
|
|
811
869
|
|
|
812
870
|
if (this.formIsFinished) {
|
|
813
|
-
Object.keys(finishedFormData)
|
|
814
|
-
|
|
815
|
-
|
|
871
|
+
Object.keys(finishedFormData)
|
|
872
|
+
.filter((key) => formFieldIds.includes(key))
|
|
873
|
+
.forEach((key) => {
|
|
874
|
+
this.formData[key] = finishedFormData[key]
|
|
875
|
+
})
|
|
816
876
|
}
|
|
877
|
+
|
|
878
|
+
nextTick(() => {
|
|
879
|
+
this.focusFirstFormField()
|
|
880
|
+
})
|
|
817
881
|
},
|
|
818
882
|
actionFn (action) {
|
|
819
883
|
if (action.label === 'Speichern' || action.label === 'Speichern und nächster') {
|
|
@@ -843,7 +907,8 @@ export default {
|
|
|
843
907
|
msg.payload = { formData: this.formData, userTask: this.userTask }
|
|
844
908
|
this.send(
|
|
845
909
|
msg,
|
|
846
|
-
this.actions.findIndex((element) => element.label === action.label) +
|
|
910
|
+
this.actions.findIndex((element) => element.label === action.label) +
|
|
911
|
+
(this.isConfirmDialog ? this.props.options.length : 0)
|
|
847
912
|
)
|
|
848
913
|
// TODO: mm - end
|
|
849
914
|
} else {
|
|
@@ -864,6 +929,31 @@ export default {
|
|
|
864
929
|
},
|
|
865
930
|
showError (errMsg) {
|
|
866
931
|
this.errorMsg = errMsg
|
|
932
|
+
},
|
|
933
|
+
focusFirstFormField () {
|
|
934
|
+
if (this.collapsed || !this.hasUserTask) {
|
|
935
|
+
return
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
if (this.firstFormFieldRef) {
|
|
939
|
+
let inputElement = null
|
|
940
|
+
|
|
941
|
+
if (this.firstFormFieldRef.node && this.firstFormFieldRef.node.input instanceof HTMLElement) {
|
|
942
|
+
inputElement = this.firstFormFieldRef.node.input
|
|
943
|
+
} else if (this.firstFormFieldRef.$el instanceof HTMLElement) {
|
|
944
|
+
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(this.firstFormFieldRef.$el.tagName)) {
|
|
945
|
+
inputElement = this.firstFormFieldRef.$el
|
|
946
|
+
} else {
|
|
947
|
+
inputElement = this.firstFormFieldRef.$el.querySelector('input:not([type="hidden"]), textarea, select')
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
if (inputElement) {
|
|
952
|
+
inputElement.focus()
|
|
953
|
+
} else {
|
|
954
|
+
console.warn('Could not find a focusable input element for the first form field.')
|
|
955
|
+
}
|
|
956
|
+
}
|
|
867
957
|
}
|
|
868
958
|
}
|
|
869
959
|
}
|