@processmaker/screen-builder 3.8.3 → 3.8.5
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 +4013 -3904
- package/dist/vue-form-builder.es.js.map +1 -1
- package/dist/vue-form-builder.umd.js +38 -38
- package/dist/vue-form-builder.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/components/inspector/button/handler-event-property.js +9 -0
- package/src/components/inspector/code-editor.vue +127 -0
- package/src/components/inspector/index.js +1 -0
- package/src/components/renderer/form-button.vue +20 -1
- package/src/components/renderer/form-record-list.vue +12 -10
- package/src/components/vue-form-builder.vue +2 -0
- package/src/form-builder-controls.js +3 -0
- package/src/mixins/DataReference.js +4 -0
- package/src/mixins/extensions/LoadFieldComponents.js +4 -0
- package/src/stories/CodeEditor.stories.js +112 -0
package/package.json
CHANGED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<label :for="`${dataFeature}-code-input`">{{ $t(label) }} </label>
|
|
4
|
+
<div class="float-right buttons">
|
|
5
|
+
<b-button
|
|
6
|
+
:title="$t('Expand')"
|
|
7
|
+
variant="light"
|
|
8
|
+
size="sm"
|
|
9
|
+
:data-test="`${dataFeature}-expand-button`"
|
|
10
|
+
@click="expand"
|
|
11
|
+
><i class="fas fa-expand"></i></b-button>
|
|
12
|
+
</div>
|
|
13
|
+
<MonacoEditor
|
|
14
|
+
:id="`${dataFeature}-code-input`"
|
|
15
|
+
v-model="configValue"
|
|
16
|
+
:options="smallMonacoOptions"
|
|
17
|
+
class="editor"
|
|
18
|
+
language="javascript"
|
|
19
|
+
:data-test="`${dataFeature}-code-input`"
|
|
20
|
+
@change="updateValue"
|
|
21
|
+
/>
|
|
22
|
+
<small v-if="helper" class="form-text text-muted mt-2">{{ $t(helper) }}</small>
|
|
23
|
+
<b-modal
|
|
24
|
+
ref="codeEditorModal"
|
|
25
|
+
:title="$t(label)"
|
|
26
|
+
size="xl"
|
|
27
|
+
dialog-class="modal-dialog-fullscreen"
|
|
28
|
+
>
|
|
29
|
+
<MonacoEditor
|
|
30
|
+
v-model="configValue"
|
|
31
|
+
:options="largeMonacoOptions"
|
|
32
|
+
class="editor large-editor"
|
|
33
|
+
language="javascript"
|
|
34
|
+
:data-test="`${dataFeature}-code-input`"
|
|
35
|
+
@change="updateValue"
|
|
36
|
+
/>
|
|
37
|
+
<template #modal-footer="{ ok }">
|
|
38
|
+
<b-button @click="ok" class="btn btn-secondary text-uppercase">
|
|
39
|
+
{{ $t('Close') }}
|
|
40
|
+
</b-button>
|
|
41
|
+
</template>
|
|
42
|
+
</b-modal>
|
|
43
|
+
</div>
|
|
44
|
+
</template>
|
|
45
|
+
|
|
46
|
+
<script>
|
|
47
|
+
import MonacoEditor from 'vue-monaco';
|
|
48
|
+
|
|
49
|
+
export default {
|
|
50
|
+
name: 'CodeEditor',
|
|
51
|
+
props: {
|
|
52
|
+
value: {
|
|
53
|
+
type: String,
|
|
54
|
+
required: false,
|
|
55
|
+
},
|
|
56
|
+
label: {
|
|
57
|
+
type: String,
|
|
58
|
+
default: '',
|
|
59
|
+
},
|
|
60
|
+
helper: {
|
|
61
|
+
type: String,
|
|
62
|
+
required: false,
|
|
63
|
+
},
|
|
64
|
+
dataFeature: {
|
|
65
|
+
type: String,
|
|
66
|
+
required: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
components: {
|
|
70
|
+
MonacoEditor,
|
|
71
|
+
},
|
|
72
|
+
data() {
|
|
73
|
+
return {
|
|
74
|
+
configValue: this.value || '',
|
|
75
|
+
smallMonacoOptions: {
|
|
76
|
+
lineNumbers: 'off',
|
|
77
|
+
lineDecorationsWidth: 0,
|
|
78
|
+
lineNumbersMinChars: 0,
|
|
79
|
+
minimap: { enabled: false },
|
|
80
|
+
fixedOverflowWidgets: true,
|
|
81
|
+
automaticLayout: true,
|
|
82
|
+
renderLineHighlight: 'none',
|
|
83
|
+
overviewRulerLanes: 0,
|
|
84
|
+
},
|
|
85
|
+
largeMonacoOptions: {
|
|
86
|
+
lineNumbers: 'on',
|
|
87
|
+
lineDecorationsWidth: 0,
|
|
88
|
+
minimap: { enabled: false },
|
|
89
|
+
automaticLayout: true,
|
|
90
|
+
overviewRulerLanes: 0,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
methods: {
|
|
95
|
+
updateValue() {
|
|
96
|
+
this.$emit('input', this.configValue);
|
|
97
|
+
},
|
|
98
|
+
expand() {
|
|
99
|
+
this.$refs.codeEditorModal.show();
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
watch: {
|
|
103
|
+
value(newVal) {
|
|
104
|
+
this.configValue = newVal;
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
</script>
|
|
109
|
+
|
|
110
|
+
<style scoped>
|
|
111
|
+
.buttons button {
|
|
112
|
+
min-width: 2.2em;
|
|
113
|
+
margin-left: 0.5em;
|
|
114
|
+
margin-bottom: 0.5em;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.editor {
|
|
118
|
+
width: 100%;
|
|
119
|
+
height: 10em;
|
|
120
|
+
border: 1px solid var(--gray);
|
|
121
|
+
overflow: hidden;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.large-editor {
|
|
125
|
+
height: 25em;
|
|
126
|
+
}
|
|
127
|
+
</style>
|
|
@@ -30,3 +30,4 @@ export { default as LoadingSubmitButton } from "./loading-submit-button.vue";
|
|
|
30
30
|
export { default as LabelSubmitButton } from "./label-submit-button.vue";
|
|
31
31
|
export { default as AnalyticsSelector } from "./analytics-selector.vue";
|
|
32
32
|
export { default as EncryptedConfig } from "./encrypted-config.vue";
|
|
33
|
+
export { default as CodeEditor } from "./code-editor.vue";
|
|
@@ -22,7 +22,7 @@ import { getValidPath } from '@/mixins';
|
|
|
22
22
|
|
|
23
23
|
export default {
|
|
24
24
|
mixins: [getValidPath],
|
|
25
|
-
props: ['variant', 'label', 'event', 'eventData', 'name', 'fieldValue', 'value', 'tooltip', 'transientData', 'loading', 'loadingLabel'],
|
|
25
|
+
props: ['variant', 'label', 'event', 'eventData', 'name', 'fieldValue', 'value', 'tooltip', 'transientData', 'loading', 'loadingLabel', 'handler'],
|
|
26
26
|
data() {
|
|
27
27
|
return {
|
|
28
28
|
showSpinner: false
|
|
@@ -80,6 +80,8 @@ export default {
|
|
|
80
80
|
const trueValue = this.fieldValue || '1';
|
|
81
81
|
const value = (this.value == trueValue) ? null : trueValue;
|
|
82
82
|
this.$emit('input', value);
|
|
83
|
+
// Run handler after setting the value
|
|
84
|
+
await this.runHandler();
|
|
83
85
|
}
|
|
84
86
|
if (this.event !== 'pageNavigate' && this.name) {
|
|
85
87
|
this.setValue(this.$parent, this.name, this.fieldValue);
|
|
@@ -89,6 +91,8 @@ export default {
|
|
|
89
91
|
this.showSpinner = true;
|
|
90
92
|
}
|
|
91
93
|
this.$emit('input', this.fieldValue);
|
|
94
|
+
// Run handler after setting the value
|
|
95
|
+
await this.runHandler();
|
|
92
96
|
this.$nextTick(() => {
|
|
93
97
|
this.$emit('submit', this.eventData, this.loading, this.buttonInfo);
|
|
94
98
|
});
|
|
@@ -99,6 +103,21 @@ export default {
|
|
|
99
103
|
this.$emit('page-navigate', this.eventData);
|
|
100
104
|
}
|
|
101
105
|
},
|
|
106
|
+
async runHandler() {
|
|
107
|
+
if (this.handler) {
|
|
108
|
+
try {
|
|
109
|
+
const data = this.getScreenDataReference(null, (screen, name, value) => {
|
|
110
|
+
// Enable the data reference to be updated by the handler
|
|
111
|
+
screen.$set(screen.vdata, name, value);
|
|
112
|
+
});
|
|
113
|
+
await new Function(['toRaw'], this.handler).apply(data, [(item) => {
|
|
114
|
+
return item[Symbol.for('__v_raw')];
|
|
115
|
+
}]);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
console.error('❌ There is an error in the button handler', error);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
102
121
|
},
|
|
103
122
|
};
|
|
104
123
|
</script>
|
|
@@ -469,9 +469,9 @@ export default {
|
|
|
469
469
|
this.currentPage = this.currentPage == 0 ? 1 : this.currentPage;
|
|
470
470
|
}
|
|
471
471
|
},
|
|
472
|
-
// Watch for changes in
|
|
473
|
-
|
|
474
|
-
handler(newValue) {
|
|
472
|
+
// Watch for changes in validationData to handle any Mustache variable changes
|
|
473
|
+
validationData: {
|
|
474
|
+
handler(newValue, oldValue) {
|
|
475
475
|
if (this.source?.sourceOptions === "Collection" && this.source?.collectionFields?.pmql) {
|
|
476
476
|
this.onCollectionChange(
|
|
477
477
|
this.source?.collectionFields?.collectionId,
|
|
@@ -479,6 +479,7 @@ export default {
|
|
|
479
479
|
);
|
|
480
480
|
}
|
|
481
481
|
},
|
|
482
|
+
deep: true,
|
|
482
483
|
immediate: false
|
|
483
484
|
}
|
|
484
485
|
},
|
|
@@ -614,12 +615,9 @@ export default {
|
|
|
614
615
|
// Get data from validationData
|
|
615
616
|
const data = this.validationData || {};
|
|
616
617
|
|
|
617
|
-
//
|
|
618
|
-
let processedPmql =
|
|
619
|
-
|
|
620
|
-
// Process Mustache variables
|
|
621
|
-
processedPmql = Mustache.render(processedPmql, data);
|
|
622
|
-
|
|
618
|
+
// First, process all Mustache variables (both quoted and unquoted)
|
|
619
|
+
let processedPmql = Mustache.render(pmql, data);
|
|
620
|
+
|
|
623
621
|
// Check if the processed PMQL has empty values
|
|
624
622
|
if (this.hasEmptyValues(processedPmql)) {
|
|
625
623
|
this.collectionData = [];
|
|
@@ -627,7 +625,11 @@ export default {
|
|
|
627
625
|
}
|
|
628
626
|
|
|
629
627
|
// Add quotes around string values in PMQL if they don't have them
|
|
630
|
-
|
|
628
|
+
// This regex now properly handles values with spaces by looking for the end of the comparison
|
|
629
|
+
processedPmql = processedPmql.replace(
|
|
630
|
+
/= ([^"'\s][^"']*[^"'\s]|[^"'\s]+)(?=\s|$)/g,
|
|
631
|
+
'= "$1"'
|
|
632
|
+
);
|
|
631
633
|
|
|
632
634
|
return processedPmql;
|
|
633
635
|
} catch (error) {
|
|
@@ -562,6 +562,7 @@ import TabsBar from "./TabsBar.vue";
|
|
|
562
562
|
import Sortable from './sortable/Sortable.vue';
|
|
563
563
|
import ClipboardButton from './ClipboardButton.vue';
|
|
564
564
|
import ScreenTemplates from './ScreenTemplates.vue';
|
|
565
|
+
import CodeEditor from "./inspector/code-editor.vue";
|
|
565
566
|
|
|
566
567
|
// To include another language in the Validator with variable processmaker
|
|
567
568
|
const globalObject = typeof window === "undefined" ? global : window;
|
|
@@ -633,6 +634,7 @@ export default {
|
|
|
633
634
|
Sortable,
|
|
634
635
|
ClipboardButton,
|
|
635
636
|
ScreenTemplates,
|
|
637
|
+
CodeEditor,
|
|
636
638
|
},
|
|
637
639
|
mixins: [HasColorProperty, testing, Clipboard],
|
|
638
640
|
props: {
|
|
@@ -13,6 +13,7 @@ import FormListTable from './components/renderer/form-list-table';
|
|
|
13
13
|
import FormAnalyticsChart from "./components/renderer/form-analytics-chart";
|
|
14
14
|
import FormCollectionRecordControl from './components/renderer/form-collection-record-control.vue';
|
|
15
15
|
import FormCollectionViewControl from './components/renderer/form-collection-view-control.vue';
|
|
16
|
+
import { handlerEventProperty } from './components/inspector/button/handler-event-property';
|
|
16
17
|
import {DataTypeProperty, DataFormatProperty, DataTypeDateTimeProperty} from './VariableDataTypeProperties';
|
|
17
18
|
import {
|
|
18
19
|
FormInput,
|
|
@@ -720,6 +721,7 @@ export default [
|
|
|
720
721
|
name: null,
|
|
721
722
|
fieldValue: null,
|
|
722
723
|
tooltip: {},
|
|
724
|
+
handler: '',
|
|
723
725
|
},
|
|
724
726
|
inspector: [
|
|
725
727
|
{
|
|
@@ -742,6 +744,7 @@ export default [
|
|
|
742
744
|
},
|
|
743
745
|
},
|
|
744
746
|
buttonTypeEvent,
|
|
747
|
+
handlerEventProperty,
|
|
745
748
|
LoadingSubmitButtonProperty,
|
|
746
749
|
LabelSubmitButtonProperty,
|
|
747
750
|
tooltipProperty,
|
|
@@ -28,6 +28,7 @@ function findScreenOwner(control, lastScreenContentIfNull = false) {
|
|
|
28
28
|
* @return {object} proxy
|
|
29
29
|
*/
|
|
30
30
|
function wrapScreenData(screen, customProperties = null, setter = null) {
|
|
31
|
+
const RAW = Symbol.for('__v_raw');
|
|
31
32
|
const handler = {
|
|
32
33
|
get: (target, name) => {
|
|
33
34
|
if (customProperties && customProperties[name]) {
|
|
@@ -44,6 +45,9 @@ function wrapScreenData(screen, customProperties = null, setter = null) {
|
|
|
44
45
|
}
|
|
45
46
|
return undefined;
|
|
46
47
|
}
|
|
48
|
+
if (name === RAW) {
|
|
49
|
+
return screen.vdata;
|
|
50
|
+
}
|
|
47
51
|
// Check if vdata exists
|
|
48
52
|
if (screen.vdata !== undefined && screen.vdata !== null) {
|
|
49
53
|
return screen.vdata[name];
|
|
@@ -101,6 +101,10 @@ export default {
|
|
|
101
101
|
properties[":disabled"] = isCalcProp || element.config.disabled;
|
|
102
102
|
// Events
|
|
103
103
|
properties['@submit'] = 'submitForm';
|
|
104
|
+
// Add handler event if Button
|
|
105
|
+
if(componentName === 'FormButton') {
|
|
106
|
+
properties[':handler'] = this.byRef(element.config.handler);
|
|
107
|
+
}
|
|
104
108
|
},
|
|
105
109
|
},
|
|
106
110
|
mounted() {
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/* eslint-disable import/no-extraneous-dependencies */
|
|
2
|
+
import { userEvent, expect, within } from "@storybook/test";
|
|
3
|
+
import "../bootstrap";
|
|
4
|
+
import CodeEditor from "../components/inspector/code-editor.vue";
|
|
5
|
+
|
|
6
|
+
export default {
|
|
7
|
+
title: "Components/CodeEditor",
|
|
8
|
+
component: CodeEditor,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
argTypes: {
|
|
11
|
+
value: {
|
|
12
|
+
control: { type: 'text' },
|
|
13
|
+
description: 'The code value to display in the editor'
|
|
14
|
+
},
|
|
15
|
+
helper: {
|
|
16
|
+
control: { type: 'text' },
|
|
17
|
+
description: 'Helper text displayed below the editor'
|
|
18
|
+
},
|
|
19
|
+
dataFeature: {
|
|
20
|
+
control: { type: 'text' },
|
|
21
|
+
description: 'Data test attribute prefix for testing'
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
render: (args, { argTypes }) => ({
|
|
25
|
+
props: Object.keys(argTypes),
|
|
26
|
+
components: { CodeEditor },
|
|
27
|
+
template: '<code-editor v-bind="$props" v-model="inputValue" @input="handleInput" />',
|
|
28
|
+
data() {
|
|
29
|
+
return { inputValue: args.value };
|
|
30
|
+
},
|
|
31
|
+
methods: {
|
|
32
|
+
handleInput(value) {
|
|
33
|
+
this.inputValue = value;
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
watch: {
|
|
37
|
+
// Updates the value when the property changes in storybook controls
|
|
38
|
+
value(value) {
|
|
39
|
+
this.inputValue = value;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Stories of the component
|
|
47
|
+
*/
|
|
48
|
+
// Preview the component with basic JavaScript code
|
|
49
|
+
export const Preview = {
|
|
50
|
+
args: {
|
|
51
|
+
label: "Click Handler",
|
|
52
|
+
helper: "Enter your JavaScript code here",
|
|
53
|
+
dataFeature: "code-editor",
|
|
54
|
+
value: "console.log('Hello, World!');\n\nfunction greet(name) {\n return `Hello, ${name}!`;\n}"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Story with empty value
|
|
59
|
+
export const EmptyEditor = {
|
|
60
|
+
args: {
|
|
61
|
+
label: "Empty Editor",
|
|
62
|
+
helper: "Start typing your code...",
|
|
63
|
+
dataFeature: "code-editor-empty",
|
|
64
|
+
value: ""
|
|
65
|
+
},
|
|
66
|
+
play: async ({ canvasElement }) => {
|
|
67
|
+
const canvas = within(canvasElement);
|
|
68
|
+
await userEvent.type(canvas.getByRole('textbox'), 'console.log("Hello, World!");');
|
|
69
|
+
// Check if the code is displayed
|
|
70
|
+
expect(canvas.getByRole('textbox')).toHaveValue('console.log("Hello, World!");');
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// Story with long code
|
|
75
|
+
export const LongCode = {
|
|
76
|
+
args: {
|
|
77
|
+
label: "Long Code Example",
|
|
78
|
+
helper: "This editor contains a longer piece of code",
|
|
79
|
+
dataFeature: "code-editor-long",
|
|
80
|
+
value: `// This is a longer code example
|
|
81
|
+
function processUserData(users) {
|
|
82
|
+
return users
|
|
83
|
+
.filter(user => user.active)
|
|
84
|
+
.map(user => ({
|
|
85
|
+
id: user.id,
|
|
86
|
+
name: user.name,
|
|
87
|
+
email: user.email,
|
|
88
|
+
role: user.role,
|
|
89
|
+
lastLogin: user.lastLogin,
|
|
90
|
+
permissions: user.permissions || []
|
|
91
|
+
}))
|
|
92
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
|
93
|
+
.reduce((acc, user) => {
|
|
94
|
+
if (!acc[user.role]) {
|
|
95
|
+
acc[user.role] = [];
|
|
96
|
+
}
|
|
97
|
+
acc[user.role].push(user);
|
|
98
|
+
return acc;
|
|
99
|
+
}, {});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Example usage
|
|
103
|
+
const users = [
|
|
104
|
+
{ id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin', active: true, lastLogin: new Date() },
|
|
105
|
+
{ id: 2, name: 'Bob', email: 'bob@example.com', role: 'user', active: true, lastLogin: new Date() },
|
|
106
|
+
{ id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'admin', active: false, lastLogin: new Date() }
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
const processedUsers = processUserData(users);
|
|
110
|
+
console.log(processedUsers);`
|
|
111
|
+
}
|
|
112
|
+
};
|