@processmaker/screen-builder 3.8.12 → 3.8.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/vue-form-builder.css +1 -1
- package/dist/vue-form-builder.es.js +1938 -1867
- package/dist/vue-form-builder.es.js.map +1 -1
- package/dist/vue-form-builder.umd.js +47 -47
- package/dist/vue-form-builder.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/bootstrap.js +7 -1
- package/src/components/inspector/button/handler-event-property.js +18 -6
- package/src/components/renderer/form-button.vue +146 -61
- package/src/components/vue-form-builder.vue +14 -0
- package/src/form-builder-controls.js +3 -1
- package/src/main.js +8 -1
- package/src/mixins/extensions/LoadFieldComponents.js +21 -8
package/package.json
CHANGED
package/src/bootstrap.js
CHANGED
|
@@ -50,6 +50,9 @@ const cacheEnabled = document.head.querySelector(
|
|
|
50
50
|
const cacheTimeout = document.head.querySelector(
|
|
51
51
|
"meta[name='screen-cache-timeout']"
|
|
52
52
|
);
|
|
53
|
+
const secureHandlerToggleVisibleMeta = document.head.querySelector(
|
|
54
|
+
"meta[name='screen-secure-handler-toggle-visible']"
|
|
55
|
+
);
|
|
53
56
|
|
|
54
57
|
// Get the current protocol, hostname, and port
|
|
55
58
|
const { protocol, hostname, port } = window.location;
|
|
@@ -73,7 +76,10 @@ window.ProcessMaker = {
|
|
|
73
76
|
alert(message, variant) {},
|
|
74
77
|
screen: {
|
|
75
78
|
cacheEnabled: cacheEnabled ? cacheEnabled.content === "true" : false,
|
|
76
|
-
cacheTimeout: cacheTimeout ? Number(cacheTimeout.content) : 0
|
|
79
|
+
cacheTimeout: cacheTimeout ? Number(cacheTimeout.content) : 0,
|
|
80
|
+
secureHandlerToggleVisible: !!Number(
|
|
81
|
+
secureHandlerToggleVisibleMeta?.content
|
|
82
|
+
)
|
|
77
83
|
}
|
|
78
84
|
};
|
|
79
85
|
window.Echo = {
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
export const handlerEventProperty = {
|
|
2
|
-
type:
|
|
3
|
-
field:
|
|
2
|
+
type: "CodeEditor",
|
|
3
|
+
field: "handler",
|
|
4
4
|
config: {
|
|
5
|
-
label:
|
|
6
|
-
helper:
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
label: "Click Handler",
|
|
6
|
+
helper:
|
|
7
|
+
"The handler is a JavaScript function that will be executed when the button is clicked.",
|
|
8
|
+
dataFeature: "i1177"
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const handlerSecurityProperty = {
|
|
13
|
+
type: "FormCheckbox",
|
|
14
|
+
field: "handlerSecurityEnabled",
|
|
15
|
+
config: {
|
|
16
|
+
label: "Secure Handler Execution",
|
|
17
|
+
toggle: true,
|
|
18
|
+
helper:
|
|
19
|
+
"When enabled, the handler runs inside a sandboxed worker. Disable to allow full JavaScript access."
|
|
20
|
+
}
|
|
9
21
|
};
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
<div class="form-group" style="overflow-x: hidden">
|
|
3
3
|
<button
|
|
4
4
|
v-b-tooltip="options"
|
|
5
|
-
@click="click"
|
|
6
5
|
:class="classList"
|
|
7
6
|
:name="name"
|
|
8
7
|
:aria-label="$attrs['aria-label']"
|
|
9
8
|
:tabindex="$attrs['tabindex']"
|
|
10
9
|
:disabled="showSpinner"
|
|
10
|
+
@click="click"
|
|
11
11
|
>
|
|
12
12
|
<b-spinner v-if="showSpinner" small></b-spinner>
|
|
13
13
|
{{ showSpinner ? (!loadingLabel ? "Loading..." : loadingLabel) : label }}
|
|
@@ -15,13 +15,16 @@
|
|
|
15
15
|
</div>
|
|
16
16
|
</template>
|
|
17
17
|
|
|
18
|
+
<!-- eslint-disable import/no-extraneous-dependencies -->
|
|
19
|
+
<!-- eslint-disable import/no-unresolved -->
|
|
20
|
+
<!-- eslint-disable import/extensions -->
|
|
18
21
|
<script>
|
|
19
|
-
import Mustache from
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
+
import Mustache from "mustache";
|
|
23
|
+
import { mapState } from "vuex";
|
|
24
|
+
import { stringify } from "flatted";
|
|
25
|
+
import { getValidPath } from "@/mixins";
|
|
22
26
|
import Worker from "@/workers/worker.js?worker&inline";
|
|
23
27
|
import { findRootScreen } from "@/mixins/DataReference";
|
|
24
|
-
import { stringify } from 'flatted';
|
|
25
28
|
|
|
26
29
|
export default {
|
|
27
30
|
mixins: [getValidPath],
|
|
@@ -37,7 +40,8 @@ export default {
|
|
|
37
40
|
"transientData",
|
|
38
41
|
"loading",
|
|
39
42
|
"loadingLabel",
|
|
40
|
-
"handler"
|
|
43
|
+
"handler",
|
|
44
|
+
"handlerSecurityEnabled"
|
|
41
45
|
],
|
|
42
46
|
data() {
|
|
43
47
|
return {
|
|
@@ -47,30 +51,35 @@ export default {
|
|
|
47
51
|
computed: {
|
|
48
52
|
...mapState("globalErrorsModule", ["valid"]),
|
|
49
53
|
classList() {
|
|
50
|
-
|
|
54
|
+
const variant = this.variant || "primary";
|
|
51
55
|
return {
|
|
52
56
|
btn: true,
|
|
53
|
-
[
|
|
54
|
-
disabled: this.event ===
|
|
57
|
+
[`btn-${variant}`]: true,
|
|
58
|
+
disabled: this.event === "submit" && !this.valid
|
|
55
59
|
};
|
|
56
60
|
},
|
|
57
61
|
options() {
|
|
58
|
-
if (!this.tooltip || this.event ===
|
|
62
|
+
if (!this.tooltip || this.event === "submit") {
|
|
59
63
|
return {};
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
let content =
|
|
66
|
+
let content = "";
|
|
63
67
|
try {
|
|
64
|
-
content = Mustache.render(
|
|
65
|
-
|
|
68
|
+
content = Mustache.render(
|
|
69
|
+
this.tooltip.content || "",
|
|
70
|
+
this.transientData
|
|
71
|
+
);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(error);
|
|
74
|
+
}
|
|
66
75
|
|
|
67
76
|
return {
|
|
68
77
|
title: content,
|
|
69
78
|
html: true,
|
|
70
|
-
placement: this.tooltip.position ||
|
|
71
|
-
trigger:
|
|
72
|
-
variant: this.tooltip.variant ||
|
|
73
|
-
boundary:
|
|
79
|
+
placement: this.tooltip.position || "",
|
|
80
|
+
trigger: "hover",
|
|
81
|
+
variant: this.tooltip.variant || "",
|
|
82
|
+
boundary: "window"
|
|
74
83
|
};
|
|
75
84
|
},
|
|
76
85
|
buttonInfo() {
|
|
@@ -92,74 +101,150 @@ export default {
|
|
|
92
101
|
}
|
|
93
102
|
},
|
|
94
103
|
async click() {
|
|
95
|
-
if (this.event ===
|
|
96
|
-
const trueValue = this.fieldValue ||
|
|
97
|
-
|
|
98
|
-
this
|
|
104
|
+
if (this.event === "script") {
|
|
105
|
+
const trueValue = this.fieldValue || "1";
|
|
106
|
+
// eslint-disable-next-line eqeqeq
|
|
107
|
+
const value = this.value == trueValue ? null : trueValue;
|
|
108
|
+
this.$emit("input", value);
|
|
99
109
|
// Run handler after setting the value
|
|
100
110
|
await this.runHandler();
|
|
101
111
|
}
|
|
102
|
-
if (this.event !==
|
|
112
|
+
if (this.event !== "pageNavigate" && this.name) {
|
|
103
113
|
this.setValue(this.$parent, this.name, this.fieldValue);
|
|
104
114
|
}
|
|
105
|
-
if (this.event ===
|
|
115
|
+
if (this.event === "submit") {
|
|
106
116
|
if (this.loading && this.valid) {
|
|
107
117
|
this.showSpinner = true;
|
|
108
118
|
}
|
|
109
|
-
this.$emit(
|
|
119
|
+
this.$emit("input", this.fieldValue);
|
|
110
120
|
// Run handler after setting the value
|
|
111
121
|
await this.runHandler();
|
|
112
122
|
this.$nextTick(() => {
|
|
113
|
-
this.$emit(
|
|
123
|
+
this.$emit("submit", this.eventData, this.loading, this.buttonInfo);
|
|
114
124
|
});
|
|
115
125
|
return;
|
|
116
126
|
}
|
|
117
|
-
if (this.event ===
|
|
127
|
+
if (this.event === "pageNavigate") {
|
|
118
128
|
// Run handler for page navigate
|
|
119
129
|
await this.runHandler();
|
|
120
130
|
}
|
|
121
131
|
this.$emit(this.event, this.eventData);
|
|
122
|
-
if (this.event ===
|
|
123
|
-
this.$emit(
|
|
132
|
+
if (this.event === "pageNavigate") {
|
|
133
|
+
this.$emit("page-navigate", this.eventData);
|
|
124
134
|
}
|
|
125
135
|
},
|
|
126
136
|
runHandler() {
|
|
127
|
-
if (this.handler) {
|
|
128
|
-
return
|
|
129
|
-
|
|
130
|
-
const rootScreen = findRootScreen(this);
|
|
131
|
-
const data = rootScreen.vdata;
|
|
132
|
-
const scope = this.transientData;
|
|
137
|
+
if (!this.handler) {
|
|
138
|
+
return Promise.resolve();
|
|
139
|
+
}
|
|
133
140
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
fn: this.handler,
|
|
138
|
-
dataRefs: stringify({data, scope}),
|
|
139
|
-
});
|
|
141
|
+
const rootScreen = findRootScreen(this);
|
|
142
|
+
const data = rootScreen.vdata;
|
|
143
|
+
const scope = this.transientData;
|
|
140
144
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (e.data.error) {
|
|
144
|
-
reject(e.data.error);
|
|
145
|
-
} else if (e.data.result) {
|
|
146
|
-
// Update the data with the result
|
|
147
|
-
Object.keys(e.data.result).forEach(key => {
|
|
148
|
-
if (key === '_root') {
|
|
149
|
-
Object.assign(data, e.data.result[key]);
|
|
150
|
-
} else {
|
|
151
|
-
scope[key] = e.data.result[key];
|
|
152
|
-
}
|
|
153
|
-
});
|
|
154
|
-
resolve();
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
} catch (error) {
|
|
158
|
-
console.error("❌ There is an error in the button handler", error);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
145
|
+
if (this.handlerSecurityEnabled === false) {
|
|
146
|
+
return this.executeHandlerWithoutWorker(data, scope);
|
|
161
147
|
}
|
|
148
|
+
|
|
149
|
+
return this.executeHandlerWithWorker(data, scope);
|
|
150
|
+
},
|
|
151
|
+
executeHandlerWithWorker(data, scope) {
|
|
152
|
+
return new Promise((resolve, reject) => {
|
|
153
|
+
try {
|
|
154
|
+
const worker = new Worker();
|
|
155
|
+
worker.postMessage({
|
|
156
|
+
fn: this.handler,
|
|
157
|
+
dataRefs: stringify({ data, scope })
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
worker.onmessage = (e) => {
|
|
161
|
+
worker.terminate();
|
|
162
|
+
if (e.data.error) {
|
|
163
|
+
console.error(
|
|
164
|
+
"There is an error in the button handler",
|
|
165
|
+
e.data.error
|
|
166
|
+
);
|
|
167
|
+
reject(e.data.error);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
this.applyHandlerResult(e.data.result, data, scope);
|
|
171
|
+
resolve();
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
worker.onerror = (errorEvent) => {
|
|
175
|
+
worker.terminate();
|
|
176
|
+
console.error(
|
|
177
|
+
"There is an error in the button handler",
|
|
178
|
+
errorEvent
|
|
179
|
+
);
|
|
180
|
+
reject(errorEvent);
|
|
181
|
+
};
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error("There is an error in the button handler", error);
|
|
184
|
+
reject(error);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
executeHandlerWithoutWorker(data, scope) {
|
|
189
|
+
const hasDataReferenceHelper =
|
|
190
|
+
typeof this.getScreenDataReference === "function";
|
|
191
|
+
const dataReference = hasDataReferenceHelper
|
|
192
|
+
? this.getScreenDataReference(null, (screen, name, value) => {
|
|
193
|
+
screen.$set(screen.vdata, name, value);
|
|
194
|
+
})
|
|
195
|
+
: data;
|
|
196
|
+
const parentReference =
|
|
197
|
+
hasDataReferenceHelper && dataReference
|
|
198
|
+
? dataReference._parent
|
|
199
|
+
: undefined;
|
|
200
|
+
const context = scope || dataReference;
|
|
201
|
+
const toRaw = (item) => (item && item[Symbol.for("__v_raw")]) || item;
|
|
202
|
+
const functionBody = `return (async () => { ${this.handler} })();`;
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
// eslint-disable-next-line no-new-func, max-len
|
|
206
|
+
const userFunc = new Function("data", "parent", "toRaw", functionBody); // NOSONAR. This dynamic code execution is safe because it only occurs when the user has explicitly disabled the security worker.
|
|
207
|
+
const result = userFunc.apply(context, [
|
|
208
|
+
dataReference,
|
|
209
|
+
parentReference,
|
|
210
|
+
toRaw
|
|
211
|
+
]);
|
|
212
|
+
return this.resolveHandlerResult(result, data, scope);
|
|
213
|
+
} catch (error) {
|
|
214
|
+
console.error("There is an error in the button handler", error);
|
|
215
|
+
return Promise.reject(error);
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
resolveHandlerResult(result, data, scope) {
|
|
219
|
+
if (result && typeof result.then === "function") {
|
|
220
|
+
return result
|
|
221
|
+
.then((resolved) => {
|
|
222
|
+
this.applyHandlerResult(resolved, data, scope);
|
|
223
|
+
})
|
|
224
|
+
.catch((error) => {
|
|
225
|
+
console.error("There is an error in the button handler", error);
|
|
226
|
+
throw error;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
this.applyHandlerResult(result, data, scope);
|
|
231
|
+
return Promise.resolve();
|
|
232
|
+
},
|
|
233
|
+
applyHandlerResult(result, data, scope) {
|
|
234
|
+
if (!result || typeof result !== "object") {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const targetScope = scope || this.transientData || {};
|
|
239
|
+
|
|
240
|
+
Object.keys(result).forEach((key) => {
|
|
241
|
+
if (key === "_root") {
|
|
242
|
+
Object.assign(data, result[key]);
|
|
243
|
+
} else {
|
|
244
|
+
this.$set(targetScope, key, result[key]);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
162
247
|
}
|
|
163
|
-
}
|
|
248
|
+
}
|
|
164
249
|
};
|
|
165
250
|
</script>
|
|
@@ -810,6 +810,13 @@ export default {
|
|
|
810
810
|
},
|
|
811
811
|
showToolbar() {
|
|
812
812
|
return this.screenType === formTypes.form;
|
|
813
|
+
},
|
|
814
|
+
secureHandlerToggleVisible() {
|
|
815
|
+
return _.get(
|
|
816
|
+
globalObject,
|
|
817
|
+
"ProcessMaker.screen.secureHandlerToggleVisible",
|
|
818
|
+
false
|
|
819
|
+
);
|
|
813
820
|
}
|
|
814
821
|
},
|
|
815
822
|
watch: {
|
|
@@ -1220,6 +1227,13 @@ export default {
|
|
|
1220
1227
|
(control) => control.component === this.inspection.component
|
|
1221
1228
|
) || { inspector: [] };
|
|
1222
1229
|
return control.inspector.filter((input) => {
|
|
1230
|
+
if (
|
|
1231
|
+
!this.secureHandlerToggleVisible &&
|
|
1232
|
+
typeof input === "object" &&
|
|
1233
|
+
input.field === "handlerSecurityEnabled"
|
|
1234
|
+
) {
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
1223
1237
|
if (accordionFields.includes(input.field)) {
|
|
1224
1238
|
return true;
|
|
1225
1239
|
}
|
|
@@ -14,7 +14,7 @@ import FormListTable from './components/renderer/form-list-table';
|
|
|
14
14
|
import FormAnalyticsChart from "./components/renderer/form-analytics-chart";
|
|
15
15
|
import FormCollectionRecordControl from './components/renderer/form-collection-record-control.vue';
|
|
16
16
|
import FormCollectionViewControl from './components/renderer/form-collection-view-control.vue';
|
|
17
|
-
import { handlerEventProperty } from './components/inspector/button/handler-event-property';
|
|
17
|
+
import { handlerEventProperty, handlerSecurityProperty } from './components/inspector/button/handler-event-property';
|
|
18
18
|
import {DataTypeProperty, DataFormatProperty, DataTypeDateTimeProperty} from './VariableDataTypeProperties';
|
|
19
19
|
import {
|
|
20
20
|
FormInput,
|
|
@@ -763,6 +763,7 @@ export default [
|
|
|
763
763
|
fieldValue: null,
|
|
764
764
|
tooltip: {},
|
|
765
765
|
handler: '',
|
|
766
|
+
handlerSecurityEnabled: true,
|
|
766
767
|
},
|
|
767
768
|
inspector: [
|
|
768
769
|
{
|
|
@@ -786,6 +787,7 @@ export default [
|
|
|
786
787
|
},
|
|
787
788
|
buttonTypeEvent,
|
|
788
789
|
handlerEventProperty,
|
|
790
|
+
handlerSecurityProperty,
|
|
789
791
|
LoadingSubmitButtonProperty,
|
|
790
792
|
LabelSubmitButtonProperty,
|
|
791
793
|
tooltipProperty,
|
package/src/main.js
CHANGED
|
@@ -152,6 +152,10 @@ const cacheEnabled = document.head.querySelector(
|
|
|
152
152
|
const cacheTimeout = document.head.querySelector(
|
|
153
153
|
"meta[name='screen-cache-timeout']"
|
|
154
154
|
);
|
|
155
|
+
const secureHandlerToggleVisibleMeta = document.head.querySelector(
|
|
156
|
+
"meta[name='screen-secure-handler-toggle-visible']"
|
|
157
|
+
);
|
|
158
|
+
|
|
155
159
|
// Get the current protocol, hostname, and port
|
|
156
160
|
const { protocol, hostname, port } = window.location;
|
|
157
161
|
window.ProcessMaker = {
|
|
@@ -302,7 +306,10 @@ window.ProcessMaker = {
|
|
|
302
306
|
},
|
|
303
307
|
screen: {
|
|
304
308
|
cacheEnabled: cacheEnabled ? cacheEnabled.content === "true" : false,
|
|
305
|
-
cacheTimeout: cacheTimeout ? Number(cacheTimeout.content) : 0
|
|
309
|
+
cacheTimeout: cacheTimeout ? Number(cacheTimeout.content) : 0,
|
|
310
|
+
secureHandlerToggleVisible: !!Number(
|
|
311
|
+
secureHandlerToggleVisibleMeta?.content
|
|
312
|
+
)
|
|
306
313
|
}
|
|
307
314
|
};
|
|
308
315
|
window.Echo = {
|
|
@@ -55,11 +55,19 @@ export default {
|
|
|
55
55
|
componentName === "FormTextArea" ||
|
|
56
56
|
componentName === "FormInput"
|
|
57
57
|
) {
|
|
58
|
-
properties[
|
|
59
|
-
|
|
58
|
+
properties[
|
|
59
|
+
"@input"
|
|
60
|
+
] = `updateScreenData('${safeDotName}', '${element.config.name}')`;
|
|
61
|
+
properties[
|
|
62
|
+
"@change"
|
|
63
|
+
] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`;
|
|
60
64
|
} else {
|
|
61
|
-
properties[
|
|
62
|
-
|
|
65
|
+
properties[
|
|
66
|
+
"@input"
|
|
67
|
+
] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`;
|
|
68
|
+
properties[
|
|
69
|
+
"@change"
|
|
70
|
+
] = `updateScreenDataNow('${safeDotName}', '${element.config.name}')`;
|
|
63
71
|
}
|
|
64
72
|
// Process the FormSelectList@reset event
|
|
65
73
|
properties[
|
|
@@ -100,12 +108,17 @@ export default {
|
|
|
100
108
|
properties[":readonly"] = isCalcProp || element.config.readonly;
|
|
101
109
|
properties[":disabled"] = isCalcProp || element.config.disabled;
|
|
102
110
|
// Events
|
|
103
|
-
properties[
|
|
111
|
+
properties["@submit"] = "submitForm";
|
|
104
112
|
// Add handler event if Button
|
|
105
|
-
if(componentName ===
|
|
106
|
-
properties[
|
|
113
|
+
if (componentName === "FormButton") {
|
|
114
|
+
properties[":handler"] = this.byRef(element.config.handler);
|
|
115
|
+
const handlerSecurity =
|
|
116
|
+
element.config.handlerSecurityEnabled === undefined
|
|
117
|
+
? true
|
|
118
|
+
: element.config.handlerSecurityEnabled;
|
|
119
|
+
properties[":handler-security-enabled"] = this.byRef(handlerSecurity);
|
|
107
120
|
}
|
|
108
|
-
}
|
|
121
|
+
}
|
|
109
122
|
},
|
|
110
123
|
mounted() {
|
|
111
124
|
this.extensions.push({
|