@5minds/node-red-dashboard-2-processcube-dynamic-form 2.1.1 → 2.2.0-feature-ff91f8-me6xahkh

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,44 +1,126 @@
1
1
  <template>
2
- <div class="ui-dynamic-form-footer-actions-container">
3
- <div class="ui-dynamic-form-footer-actions-left">
4
- <div v-for="(action, index) in actions" :key="index">
5
- <v-btn v-if="action.alignment === 'left' || !action.alignment" :key="index"
6
- class="ui-dynamic-form-footer-action-button" :class="getActionButtonClass(action)"
7
- :disabled="formIsFinished" @click="actionCallback(action)">
8
- {{ action.label }}
9
- </v-btn>
10
- </div>
11
- </div>
12
- <div class="ui-dynamic-form-footer-actions-right">
13
- <div v-for="(action, index) in actions" :key="index">
14
- <v-btn v-if="action.alignment === 'right'" :key="index" class="ui-dynamic-form-footer-action-button"
15
- :class="getActionButtonClass(action)" :disabled="formIsFinished" @click="actionCallback(action)">
16
- {{ action.label }}
17
- </v-btn>
18
- </div>
19
- </div>
2
+ <div class="ui-dynamic-form-footer-actions-container">
3
+ <div class="ui-dynamic-form-footer-actions-left">
4
+ <div v-for="(action, index) in leftSideActions" :key="`left-${index}`">
5
+ <v-btn
6
+ class="ui-dynamic-form-footer-action-button"
7
+ :class="getActionButtonClass(action)"
8
+ :disabled="formIsFinished"
9
+ @click="actionCallback(action)"
10
+ >
11
+ {{ action.label }}
12
+ </v-btn>
13
+ </div>
14
+ <div>
15
+ <v-btn
16
+ class="ui-dynamic-form-footer-action-button ui-dynamic-form-footer-action-secondary"
17
+ :disabled="formIsFinished"
18
+ @click="handleSuspend"
19
+ >
20
+ Suspend
21
+ </v-btn>
22
+ </div>
23
+ <div>
24
+ <v-btn
25
+ class="ui-dynamic-form-footer-action-terminate"
26
+ :disabled="formIsFinished"
27
+ @click="handleTerminate"
28
+ variant="text"
29
+ size="small"
30
+ >
31
+ Terminate
32
+ </v-btn>
33
+ </div>
20
34
  </div>
35
+ <div class="ui-dynamic-form-footer-actions-right">
36
+ <div v-for="(action, index) in rightSideActions" :key="`right-${index}`">
37
+ <v-btn
38
+ class="ui-dynamic-form-footer-action-button"
39
+ :class="getActionButtonClass(action)"
40
+ :disabled="formIsFinished"
41
+ @click="actionCallback(action)"
42
+ >
43
+ {{ action.label }}
44
+ </v-btn>
45
+ </div>
46
+ </div>
47
+ </div>
21
48
  </template>
22
49
 
23
50
  <script>
24
-
25
51
  export default {
26
- name: 'UIDynamicFormFooterAction',
27
- props: {
28
- actions: { type: Array, default() { return [] } },
29
- actionCallback: { type: Function, default(action) { } },
30
- formIsFinished: { type: Boolean, default() { return false } }
52
+ name: 'UIDynamicFormFooterAction',
53
+ props: {
54
+ actions: {
55
+ type: Array,
56
+ default() {
57
+ return [];
58
+ },
59
+ },
60
+ actionCallback: { type: Function, default(action) {} },
61
+ formIsFinished: {
62
+ type: Boolean,
63
+ default() {
64
+ return false;
65
+ },
31
66
  },
32
- methods: {
33
- getActionButtonClass(action) {
34
- return action.primary === 'true' ? 'ui-dynamic-form-footer-action-primary' : 'ui-dynamic-form-footer-action-secondary'
35
- }
36
- }
37
- }
67
+ },
68
+ computed: {
69
+ leftSideActions() {
70
+ const leftActions = this.actions.filter((action) => action.alignment === 'left' || !action.alignment);
71
+ const sortPrimaryToLeft = (a, b) => {
72
+ const aPrimary = a.primary === 'true';
73
+ const bPrimary = b.primary === 'true';
74
+ if (aPrimary && !bPrimary) return -1;
75
+ if (!aPrimary && bPrimary) return 1;
76
+ return 0;
77
+ };
38
78
 
79
+ return leftActions.sort(sortPrimaryToLeft);
80
+ },
81
+ rightSideActions() {
82
+ const rightActions = this.actions.filter((action) => action.alignment === 'right');
83
+ const sortPrimaryToRight = (a, b) => {
84
+ const aPrimary = a.primary === 'true';
85
+ const bPrimary = b.primary === 'true';
86
+ if (!aPrimary && bPrimary) return -1;
87
+ if (aPrimary && !bPrimary) return 1;
88
+ return 0;
89
+ };
90
+
91
+ return rightActions.sort(sortPrimaryToRight);
92
+ },
93
+ },
94
+ methods: {
95
+ getActionButtonClass(action) {
96
+ return action.primary === 'true'
97
+ ? 'ui-dynamic-form-footer-action-primary'
98
+ : 'ui-dynamic-form-footer-action-secondary';
99
+ },
100
+ handleTerminate() {
101
+ const terminateAction = {
102
+ label: 'Terminate',
103
+ alignment: 'left',
104
+ primary: 'false',
105
+ isTerminate: true,
106
+ };
107
+
108
+ this.actionCallback(terminateAction);
109
+ },
110
+ handleSuspend() {
111
+ const suspendAction = {
112
+ label: 'Suspend',
113
+ alignment: 'left',
114
+ primary: 'false',
115
+ isSuspend: true,
116
+ };
117
+
118
+ this.actionCallback(suspendAction);
119
+ },
120
+ },
121
+ };
39
122
  </script>
40
123
 
41
124
  <style>
42
- /* CSS is auto scoped, but using named classes is still recommended */
43
125
  @import '../stylesheets/ui-dynamic-form.css';
44
126
  </style>
@@ -1,6 +1,5 @@
1
1
  <template>
2
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
3
  <UIDynamicFormTitleText
5
4
  v-if="props.title_style === 'outside' && hasUserTask"
6
5
  :style="props.title_style"
@@ -77,7 +76,7 @@
77
76
  v-model="field.defaultValue"
78
77
  />
79
78
  <p class="formkit-help">
80
- {{ field.customForm ? JSON.parse(field.customForm).hint : undefined }}
79
+ {{ field.customForm ? field.customForm.hint : undefined }}
81
80
  </p>
82
81
  </div>
83
82
  <component
@@ -100,7 +99,7 @@
100
99
  <v-alert type="error">Error: {{ errorMsg }}</v-alert>
101
100
  </v-row>
102
101
  <UIDynamicFormFooterAction
103
- v-if="props.actions_inside_card && actions.length > 0"
102
+ v-if="props.actions_inside_card && hasUserTask && actions.length > 0"
104
103
  :actions="actions"
105
104
  :actionCallback="actionFn"
106
105
  :formIsFinished="formIsFinished"
@@ -119,7 +118,7 @@
119
118
  />
120
119
  </p>
121
120
  </div>
122
- <div v-if="!props.actions_inside_card && actions.length > 0 && hasUserTask" style="padding-top: 32px">
121
+ <div v-if="!props.actions_inside_card && hasUserTask && actions.length > 0" style="padding-top: 32px">
123
122
  <UIDynamicFormFooterAction :actions="actions" :actionCallback="actionFn" />
124
123
  </div>
125
124
  </div>
@@ -190,7 +189,6 @@ export default {
190
189
  },
191
190
  inject: ['$socket'],
192
191
  props: {
193
- /* do not remove entries from this - Dashboard's Layout Manager's will pass this data to your component */
194
192
  id: { type: String, required: true },
195
193
  props: { type: Object, default: () => ({}) },
196
194
  state: {
@@ -209,7 +207,6 @@ export default {
209
207
  theme: 'genesis',
210
208
  locales: { de },
211
209
  locale: 'de',
212
- // eslint-disable-next-line object-shorthand
213
210
  rules: { requiredIf: requiredIf },
214
211
  });
215
212
  app.use(plugin, formkitConfig);
@@ -238,10 +235,12 @@ export default {
238
235
  return !!this.userTask;
239
236
  },
240
237
  totalOutputs() {
238
+ const outputsConfirmTerminate = 2;
241
239
  return (
242
240
  this.props.options.length +
243
241
  (this.props.handle_confirmation_dialogs ? 2 : 0) +
244
- (this.props.trigger_on_change ? 1 : 0)
242
+ (this.props.trigger_on_change ? 1 : 0) +
243
+ outputsConfirmTerminate
245
244
  );
246
245
  },
247
246
  isConfirmDialog() {
@@ -309,25 +308,18 @@ export default {
309
308
  element.classList.add('test');
310
309
  });
311
310
 
312
- this.$socket.on('widget-load:' + this.id, (msg) => {
313
- this.init(msg);
314
- });
315
311
  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
312
  this.init(msg);
318
313
  });
319
- // tell Node-RED that we're loading a new instance of this widget
320
314
  this.$socket.emit('widget-load', this.id);
321
315
  },
322
316
  unmounted() {
323
- /* Make sure, any events you subscribe to on SocketIO are unsubscribed to here */
324
- this.$socket?.off('widget-load' + this.id);
317
+ this.$socket?.off('widget-load', this.id);
325
318
  this.$socket?.off('msg-input:' + this.id);
326
319
  },
327
320
  methods: {
328
321
  createComponent(field) {
329
- console.debug('Creating component for field:', field);
330
- const customForm = field.customForm ? JSON.parse(field.customForm) : {};
322
+ const customForm = field.customForm ? JSON.parse(JSON.stringify(field.customForm)) : {};
331
323
  const hint = customForm.hint;
332
324
  const placeholder = customForm.placeholder;
333
325
  const validation = customForm.validation;
@@ -363,7 +355,7 @@ export default {
363
355
  },
364
356
  };
365
357
  case 'number':
366
- const step = field.customForm ? JSON.parse(field.customForm).step : undefined;
358
+ const step = field.customForm ? JSON.parse(JSON.stringify(field.customForm)).step : undefined;
367
359
  return {
368
360
  type: 'FormKit',
369
361
  props: {
@@ -412,7 +404,7 @@ export default {
412
404
  return {
413
405
  type: 'FormKit',
414
406
  props: {
415
- type: 'select', // JSON.parse(field.customForm).displayAs
407
+ type: 'select',
416
408
  id: field.id,
417
409
  name,
418
410
  label: field.label,
@@ -431,13 +423,13 @@ export default {
431
423
  },
432
424
  };
433
425
  case 'select':
434
- const selections = JSON.parse(field.customForm).entries.map((obj) => {
426
+ const selections = JSON.parse(JSON.stringify(field.customForm)).entries.map((obj) => {
435
427
  return { value: obj.key, label: obj.value };
436
428
  });
437
429
  return {
438
430
  type: 'FormKit',
439
431
  props: {
440
- type: 'select', // JSON.parse(field.customForm).displayAs
432
+ type: 'select',
441
433
  id: field.id,
442
434
  name,
443
435
  label: field.label,
@@ -503,7 +495,7 @@ export default {
503
495
  },
504
496
  };
505
497
  case 'file':
506
- const multiple = field.customForm ? JSON.parse(field.customForm).multiple === 'true' : false;
498
+ const multiple = field.customForm ? JSON.parse(JSON.stringify(field.customForm)).multiple === 'true' : false;
507
499
  return {
508
500
  type: 'FormKit',
509
501
  props: {
@@ -518,7 +510,6 @@ export default {
518
510
  wrapperClass: '$remove:formkit-wrapper',
519
511
  labelClass: 'ui-dynamic-form-input-label',
520
512
  inputClass: `input-${this.theme}`,
521
- // innerClass: ui-dynamic-form-input-outlines `${this.theme === 'dark' ? '$remove:formkit-inner' : ''}`,
522
513
  readonly: isReadOnly,
523
514
  disabled: isReadOnly,
524
515
  multiple,
@@ -527,7 +518,7 @@ export default {
527
518
  },
528
519
  };
529
520
  case 'checkbox':
530
- const options = JSON.parse(field.customForm).entries.map((obj) => {
521
+ const options = JSON.parse(JSON.stringify(field.customForm)).entries.map((obj) => {
531
522
  return { value: obj.key, label: obj.value };
532
523
  });
533
524
  return {
@@ -611,10 +602,10 @@ export default {
611
602
  };
612
603
  case 'header':
613
604
  let typeToUse = 'h1';
614
- if (field.customForm && JSON.parse(field.customForm).style === 'heading_2') {
605
+ if (field.customForm && JSON.parse(JSON.stringify(field.customForm)).style === 'heading_2') {
615
606
  typeToUse = 'h2';
616
607
  }
617
- if (field.customForm && JSON.parse(field.customForm).style === 'heading_3') {
608
+ if (field.customForm && JSON.parse(JSON.stringify(field.customForm)).style === 'heading_3') {
618
609
  typeToUse = 'h3';
619
610
  }
620
611
  return {
@@ -679,7 +670,7 @@ export default {
679
670
  },
680
671
  };
681
672
  case 'radio':
682
- const radioOptions = JSON.parse(field.customForm).entries.map((obj) => {
673
+ const radioOptions = JSON.parse(JSON.stringify(field.customForm)).entries.map((obj) => {
683
674
  return { value: obj.key, label: obj.value };
684
675
  });
685
676
  return {
@@ -704,24 +695,18 @@ export default {
704
695
  },
705
696
  };
706
697
  case 'range':
707
- const customForm = JSON.parse(field.customForm);
698
+ const customForm = JSON.parse(JSON.stringify(field.customForm));
708
699
  return {
709
700
  type: 'v-slider',
710
701
  props: {
711
702
  id: field.id,
712
703
  name,
713
- // label: field.label,
714
704
  required: field.required,
715
- // value: this.formData[field.id],
716
- // help: hint,
717
705
  min: customForm.min,
718
706
  max: customForm.max,
719
707
  step: customForm.step,
720
708
  thumbLabel: true,
721
- // wrapperClass: '$remove:formkit-wrapper',
722
709
  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
710
  readonly: isReadOnly,
726
711
  disabled: isReadOnly,
727
712
  validation,
@@ -732,7 +717,7 @@ export default {
732
717
  return {
733
718
  type: 'FormKit',
734
719
  props: {
735
- type: 'tel' /* with pro component mask more good */,
720
+ type: 'tel',
736
721
  id: field.id,
737
722
  name,
738
723
  label: field.label,
@@ -750,11 +735,11 @@ export default {
750
735
  },
751
736
  };
752
737
  case 'textarea':
753
- const rows = field.customForm ? JSON.parse(field.customForm).rows : undefined;
738
+ const rows = field.customForm ? JSON.parse(JSON.stringify(field.customForm)).rows : undefined;
754
739
  return {
755
740
  type: 'FormKit',
756
741
  props: {
757
- type: 'textarea' /* with pro component mask more good */,
742
+ type: 'textarea',
758
743
  id: field.id,
759
744
  name,
760
745
  label: field.label,
@@ -776,7 +761,7 @@ export default {
776
761
  return {
777
762
  type: 'FormKit',
778
763
  props: {
779
- type: 'time' /* with pro component mask more good */,
764
+ type: 'time',
780
765
  id: field.id,
781
766
  name,
782
767
  label: field.label,
@@ -880,10 +865,6 @@ export default {
880
865
 
881
866
  return fieldMap;
882
867
  },
883
- /*
884
- widget-action just sends a msg to Node-RED, it does not store the msg state server-side
885
- alternatively, you can use widget-change, which will also store the msg in the Node's datastore
886
- */
887
868
  send(msg, index) {
888
869
  const msgArr = [];
889
870
  msgArr[index] = msg;
@@ -921,23 +902,34 @@ export default {
921
902
  this.formData[field.id] = field.defaultValue;
922
903
 
923
904
  if (field.type === 'confirm') {
924
- const customForm = field.customForm;
905
+ const customForm = field.customForm ? JSON.parse(JSON.stringify(field.customForm)) : {};
925
906
  const confirmText = customForm.confirmButtonText ?? 'Confirm';
926
907
  const declineText = customForm.declineButtonText ?? 'Decline';
927
- this.actions = [
908
+ const confirmActions = [
928
909
  {
929
- alignment: 'right',
930
- primary: 'false',
931
- label: declineText,
910
+ alignment: 'left',
911
+ primary: 'true',
912
+ label: confirmText,
932
913
  condition: '',
914
+ isConfirmAction: true,
915
+ confirmFieldId: field.id,
916
+ confirmValue: true,
933
917
  },
934
918
  {
935
- alignment: 'right',
919
+ alignment: 'left',
936
920
  primary: 'true',
937
- label: confirmText,
921
+ label: declineText,
938
922
  condition: '',
923
+ isConfirmAction: true,
924
+ confirmFieldId: field.id,
925
+ confirmValue: false,
939
926
  },
940
927
  ];
928
+ if (this.props.handle_confirmation_dialogs) {
929
+ this.actions = confirmActions;
930
+ } else {
931
+ this.actions = [...this.actions, ...confirmActions];
932
+ }
941
933
  }
942
934
  });
943
935
  }
@@ -963,6 +955,42 @@ export default {
963
955
  });
964
956
  },
965
957
  actionFn(action) {
958
+ if (action.isTerminate) {
959
+ this.showError('');
960
+
961
+ const msg = this.msg ?? {};
962
+ msg.payload = {
963
+ formData: this.formData,
964
+ userTask: this.userTask,
965
+ isTerminate: true,
966
+ };
967
+
968
+ const terminateOutputIndex = this.totalOutputs - 1;
969
+
970
+ this.send(msg, terminateOutputIndex);
971
+ return;
972
+ }
973
+
974
+ if (action.isSuspend) {
975
+ this.showError('');
976
+
977
+ const msg = this.msg ?? {};
978
+ msg.payload = {
979
+ formData: this.formData,
980
+ userTask: this.userTask,
981
+ isSuspend: true,
982
+ };
983
+
984
+ const suspendOutputIndex = this.totalOutputs - 2;
985
+
986
+ this.send(msg, suspendOutputIndex);
987
+ return;
988
+ }
989
+
990
+ if (action.isConfirmAction && action.confirmFieldId) {
991
+ this.formData[action.confirmFieldId] = action.confirmValue;
992
+ }
993
+
966
994
  if (action.label === 'Speichern' || action.label === 'Speichern und nächster') {
967
995
  const formkitInputs = this.$refs.form.$el.querySelectorAll('.formkit-outer');
968
996
  let allComplete = true;
@@ -1012,7 +1040,6 @@ export default {
1012
1040
  this.actions.findIndex((element) => element.label === action.label) +
1013
1041
  (this.isConfirmDialog ? this.props.options.length : 0)
1014
1042
  );
1015
- // TODO: mm - end
1016
1043
  } else {
1017
1044
  this.showError(action.errorMessage);
1018
1045
  }
@@ -1020,7 +1047,6 @@ export default {
1020
1047
  checkCondition(condition) {
1021
1048
  if (condition === '') return true;
1022
1049
  try {
1023
- // eslint-disable-next-line no-new-func
1024
1050
  const func = Function('fields', 'userTask', 'msg', '"use strict"; return (' + condition + ')');
1025
1051
  const result = func(this.formData, this.userTask, this.msg);
1026
1052
  return Boolean(result);
@@ -130,6 +130,24 @@ code {
130
130
  padding: 8px;
131
131
  }
132
132
 
133
+ .ui-dynamic-form-footer-action-terminate {
134
+ background-color: transparent !important;
135
+ border: none !important;
136
+ color: #dc3545 !important;
137
+ text-decoration: none;
138
+ padding: 4px 8px !important;
139
+ min-height: auto !important;
140
+ font-size: 0.875rem;
141
+ cursor: pointer;
142
+ box-shadow: none !important;
143
+ }
144
+
145
+ .ui-dynamic-form-footer-action-terminate:hover {
146
+ background-color: transparent !important;
147
+ color: #b02a37 !important;
148
+ text-decoration: underline;
149
+ }
150
+
133
151
  .ui-dynamic-form-footer-actions-container {
134
152
  display: flex;
135
153
  justify-content: space-between;