Enhance Form Builder Submit Button Configuration and Variable Handling
- Introduced a new submit button configuration in the form builder, allowing users to enable/disable the default submit button and customize its label, category, and color. - Updated VariableBrowser.vue to support object property path input for variables, including validation and error handling for property paths. - Enhanced ApiNodeConfiguration.vue to prevent object path creation for output and error variables. - Improved workflow page to respect form builder submit button settings, ensuring consistent behavior across the application. - Added helper functions for managing submit button styles and variants, enhancing the overall user experience.
This commit is contained in:
parent
707d8fe3b0
commit
8f84b00a9e
@ -172,6 +172,7 @@
|
||||
v-model="localNodeData.outputVariable"
|
||||
:availableVariables="availableVariables"
|
||||
:allowCreate="true"
|
||||
:allowObjectPath="false"
|
||||
@change="saveChanges"
|
||||
/>
|
||||
</div>
|
||||
@ -188,6 +189,7 @@
|
||||
v-model="localNodeData.errorVariable"
|
||||
:availableVariables="availableVariables"
|
||||
:allowCreate="true"
|
||||
:allowObjectPath="false"
|
||||
@change="saveChanges"
|
||||
/>
|
||||
</div>
|
||||
|
@ -4,7 +4,7 @@
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="flex-1">
|
||||
<select
|
||||
:value="modelValue"
|
||||
:value="baseVariableName"
|
||||
@change="handleVariableSelect"
|
||||
:class="[
|
||||
'form-select w-full',
|
||||
@ -47,6 +47,63 @@
|
||||
</RsButton>
|
||||
</div>
|
||||
|
||||
<!-- Object Property Path Input (for object/array variables) -->
|
||||
<div v-if="selectedVariable && isObjectType(selectedVariable.type) && allowObjectPath" class="mt-2">
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">
|
||||
Object Property Path (optional)
|
||||
</label>
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="flex-1 relative">
|
||||
<div class="flex items-center border rounded-md focus-within:ring-2 focus-within:ring-purple-500 focus-within:border-purple-500" :class="propertyPathError ? 'border-red-500' : 'border-gray-300'">
|
||||
<span class="px-3 py-2 text-sm text-gray-600 bg-gray-50 border-r border-gray-300 rounded-l-md">
|
||||
{{ baseVariableName }}.
|
||||
</span>
|
||||
<input
|
||||
v-model="propertyPath"
|
||||
type="text"
|
||||
placeholder="data.user.name or [0].title"
|
||||
class="flex-1 px-3 py-2 text-sm border-0 rounded-r-md focus:outline-none focus:ring-0"
|
||||
@input="handlePropertyPathChange"
|
||||
@blur="validatePropertyPath"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-if="propertyPath"
|
||||
@click="clearPropertyPath"
|
||||
class="p-2 text-gray-400 hover:text-gray-600 transition-colors"
|
||||
title="Clear property path"
|
||||
>
|
||||
<Icon name="material-symbols:close" class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Property Path Examples -->
|
||||
<div class="mt-1 text-xs text-gray-500">
|
||||
<details class="cursor-pointer">
|
||||
<summary class="hover:text-gray-700">Examples</summary>
|
||||
<div class="mt-1 space-y-1 pl-2 border-l-2 border-gray-200">
|
||||
<div><code class="bg-gray-100 px-1">data.user.name</code> - Access nested object property</div>
|
||||
<div><code class="bg-gray-100 px-1">items[0].title</code> - Access first array item property</div>
|
||||
<div><code class="bg-gray-100 px-1">results.users[2].email</code> - Complex nested access</div>
|
||||
<div><code class="bg-gray-100 px-1">response.data.attributes.value</code> - Deep nesting</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<!-- Property Path Error -->
|
||||
<div v-if="propertyPathError" class="mt-1 text-red-600 text-xs flex items-center">
|
||||
<Icon name="material-symbols:error" class="w-3 h-3 mr-1" />
|
||||
{{ propertyPathError }}
|
||||
</div>
|
||||
|
||||
<!-- Final Variable Path Preview -->
|
||||
<div v-if="finalVariablePath && finalVariablePath !== baseVariableName" class="mt-2 p-2 bg-blue-50 border border-blue-200 rounded-md">
|
||||
<div class="text-xs font-medium text-blue-800 mb-1">Full Variable Path:</div>
|
||||
<code class="text-sm text-blue-700 bg-blue-100 px-2 py-1 rounded">{{ finalVariablePath }}</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Variable Info Display -->
|
||||
<div v-if="selectedVariable" class="mt-2 p-2 bg-gray-50 rounded-md border text-sm">
|
||||
<div class="flex items-center justify-between">
|
||||
@ -224,6 +281,10 @@ const props = defineProps({
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
allowObjectPath: {
|
||||
type: Boolean,
|
||||
default: true // Enable object property path input
|
||||
}
|
||||
});
|
||||
|
||||
@ -239,14 +300,32 @@ const newVariableDefaultValue = ref('');
|
||||
const newVariableDescription = ref('');
|
||||
const nameValidationError = ref('');
|
||||
|
||||
// Object property path state
|
||||
const propertyPath = ref('');
|
||||
const propertyPathError = ref('');
|
||||
|
||||
// Computed properties
|
||||
const baseVariableName = computed(() => {
|
||||
// Extract base variable name from modelValue (strip property path)
|
||||
const value = props.modelValue || '';
|
||||
const dotIndex = value.indexOf('.');
|
||||
return dotIndex > -1 ? value.substring(0, dotIndex) : value;
|
||||
});
|
||||
|
||||
const selectedVariable = computed(() => {
|
||||
return props.availableVariables.find(v => v.name === props.modelValue);
|
||||
return props.availableVariables.find(v => v.name === baseVariableName.value);
|
||||
});
|
||||
|
||||
const finalVariablePath = computed(() => {
|
||||
if (!baseVariableName.value) return '';
|
||||
if (!propertyPath.value) return baseVariableName.value;
|
||||
return `${baseVariableName.value}.${propertyPath.value}`;
|
||||
});
|
||||
|
||||
const hasError = computed(() => {
|
||||
if (!props.required && !props.modelValue) return false;
|
||||
if (props.modelValue && !selectedVariable.value) return true;
|
||||
if (propertyPathError.value) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
@ -255,7 +334,10 @@ const errorMessage = computed(() => {
|
||||
return 'Variable selection is required';
|
||||
}
|
||||
if (props.modelValue && !selectedVariable.value) {
|
||||
return `Variable "${props.modelValue}" not found`;
|
||||
return `Variable "${baseVariableName.value}" not found`;
|
||||
}
|
||||
if (propertyPathError.value) {
|
||||
return propertyPathError.value;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
@ -305,9 +387,74 @@ const canCreateVariable = computed(() => {
|
||||
|
||||
// Methods
|
||||
const handleVariableSelect = (event) => {
|
||||
emit('update:modelValue', event.target.value);
|
||||
const selectedVar = event.target.value;
|
||||
propertyPath.value = ''; // Reset property path when changing variable
|
||||
propertyPathError.value = '';
|
||||
emit('update:modelValue', selectedVar);
|
||||
};
|
||||
|
||||
const handlePropertyPathChange = () => {
|
||||
propertyPathError.value = ''; // Clear error on input
|
||||
emit('update:modelValue', finalVariablePath.value);
|
||||
};
|
||||
|
||||
const validatePropertyPath = () => {
|
||||
if (!propertyPath.value) {
|
||||
propertyPathError.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic validation for property path syntax
|
||||
const path = propertyPath.value.trim();
|
||||
|
||||
// Check for invalid characters or patterns
|
||||
if (path.includes('..') || path.startsWith('.') || path.endsWith('.')) {
|
||||
propertyPathError.value = 'Invalid property path format';
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for balanced brackets
|
||||
const openBrackets = (path.match(/\[/g) || []).length;
|
||||
const closeBrackets = (path.match(/\]/g) || []).length;
|
||||
if (openBrackets !== closeBrackets) {
|
||||
propertyPathError.value = 'Unmatched brackets in property path';
|
||||
return;
|
||||
}
|
||||
|
||||
propertyPathError.value = '';
|
||||
};
|
||||
|
||||
const clearPropertyPath = () => {
|
||||
propertyPath.value = '';
|
||||
propertyPathError.value = '';
|
||||
emit('update:modelValue', baseVariableName.value);
|
||||
};
|
||||
|
||||
const isObjectType = (type) => {
|
||||
return ['object', 'array', 'map', 'set'].includes(type);
|
||||
};
|
||||
|
||||
// Watch for external changes to modelValue to sync property path
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (!newValue) {
|
||||
propertyPath.value = '';
|
||||
propertyPathError.value = '';
|
||||
return;
|
||||
}
|
||||
|
||||
const dotIndex = newValue.indexOf('.');
|
||||
if (dotIndex > -1) {
|
||||
// modelValue contains a property path
|
||||
const baseName = newValue.substring(0, dotIndex);
|
||||
const path = newValue.substring(dotIndex + 1);
|
||||
propertyPath.value = path;
|
||||
} else {
|
||||
// No property path in modelValue
|
||||
propertyPath.value = '';
|
||||
}
|
||||
propertyPathError.value = '';
|
||||
}, { immediate: true });
|
||||
|
||||
const openCreateVariable = () => {
|
||||
showCreateVariable.value = true;
|
||||
resetCreateForm();
|
||||
|
@ -295,16 +295,20 @@
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Only show submit button if no submit button components exist in the form -->
|
||||
<!-- Only show submit button if enabled and no submit button components exist in the form -->
|
||||
<FormKit
|
||||
v-if="!formStore.formComponents.some(comp => comp.type === 'button' && comp.props.buttonType === 'submit')"
|
||||
v-if="formStore.submitButton.enabled && !formStore.formComponents.some(comp => comp.type === 'button' && comp.props.buttonType === 'submit')"
|
||||
type="submit"
|
||||
label="Submit"
|
||||
:label="formStore.submitButton.label || 'Submit'"
|
||||
class="form-submit mt-6"
|
||||
:class="{
|
||||
'mx-4 mb-4': selectedDevice !== 'Desktop',
|
||||
'mx-0 mb-0': selectedDevice === 'Desktop'
|
||||
}"
|
||||
:classes="{
|
||||
input: getSubmitButtonStyles(formStore.submitButton.category, formStore.submitButton.color).classes
|
||||
}"
|
||||
:style="getSubmitButtonStyles(formStore.submitButton.category, formStore.submitButton.color).style"
|
||||
/>
|
||||
</FormKit>
|
||||
</div>
|
||||
@ -576,6 +580,102 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Submit Button Settings Tab -->
|
||||
<template #submit>
|
||||
<div class="p-4 space-y-4">
|
||||
<div class="bg-blue-50 border border-blue-200 text-blue-800 p-3 rounded mb-4">
|
||||
<div class="flex items-start">
|
||||
<Icon name="material-symbols:info" class="w-5 h-5 mr-2 mt-0.5" />
|
||||
<div>
|
||||
<h4 class="font-medium text-sm">Submit Button Configuration</h4>
|
||||
<p class="text-xs mt-1">
|
||||
Configure the default submit button that appears at the bottom of your form.
|
||||
Note: If you add a custom submit button component to your form, the default button will be hidden automatically.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enable/Disable Submit Button -->
|
||||
<div class="space-y-2">
|
||||
<label class="flex items-center">
|
||||
<input
|
||||
type="checkbox"
|
||||
v-model="formStore.submitButton.enabled"
|
||||
class="form-checkbox h-4 w-4 text-blue-600 focus:ring-blue-500"
|
||||
/>
|
||||
<span class="ml-2 text-sm font-medium text-gray-700">Show default submit button</span>
|
||||
</label>
|
||||
<p class="text-xs text-gray-500 ml-6">
|
||||
When enabled, a submit button will appear at the bottom of your form unless you add a custom submit button component.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button Label -->
|
||||
<div v-if="formStore.submitButton.enabled">
|
||||
<FormKit
|
||||
type="text"
|
||||
label="Submit Button Text"
|
||||
v-model="formStore.submitButton.label"
|
||||
help="The text that appears on the submit button"
|
||||
validation="required"
|
||||
placeholder="Submit"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button Category -->
|
||||
<div v-if="formStore.submitButton.enabled">
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Button Style Category"
|
||||
v-model="formStore.submitButton.category"
|
||||
help="Primary buttons are more prominent, secondary are more subtle"
|
||||
:options="[
|
||||
{ label: 'Primary', value: 'primary' },
|
||||
{ label: 'Secondary', value: 'secondary' }
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button Color -->
|
||||
<div v-if="formStore.submitButton.enabled">
|
||||
<FormKit
|
||||
type="select"
|
||||
label="Button Color"
|
||||
v-model="formStore.submitButton.color"
|
||||
help="Choose the color theme for the submit button"
|
||||
:options="[
|
||||
{ label: 'Primary', value: 'primary' },
|
||||
{ label: 'Secondary', value: 'secondary' },
|
||||
{ label: 'Success', value: 'success' },
|
||||
{ label: 'Info', value: 'info' },
|
||||
{ label: 'Warning', value: 'warning' },
|
||||
{ label: 'Danger', value: 'danger' }
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Preview Section -->
|
||||
<div v-if="formStore.submitButton.enabled" class="border-t pt-4">
|
||||
<h4 class="text-sm font-medium text-gray-700 mb-2">Preview</h4>
|
||||
<div class="bg-gray-50 p-4 rounded border">
|
||||
<FormKit
|
||||
type="submit"
|
||||
:label="formStore.submitButton.label || 'Submit'"
|
||||
:classes="{
|
||||
input: getSubmitButtonStyles(formStore.submitButton.category, formStore.submitButton.color).classes
|
||||
}"
|
||||
:style="getSubmitButtonStyles(formStore.submitButton.category, formStore.submitButton.color).style"
|
||||
disabled
|
||||
/>
|
||||
<p class="text-xs text-gray-500 mt-2">
|
||||
This is how your submit button will appear in the form.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Custom JavaScript Tab -->
|
||||
<template #javascript>
|
||||
<div class="p-4">
|
||||
@ -995,6 +1095,23 @@ const pendingNavigation = ref(null);
|
||||
const navigationTarget = ref(null);
|
||||
const activeSettingsTab = ref('info');
|
||||
|
||||
// Helper function to get submit button CSS classes and styles based on category and color
|
||||
const getSubmitButtonStyles = (category, color) => {
|
||||
const baseClasses = 'px-4 py-2 rounded font-medium transition-all duration-200 text-white border-0';
|
||||
|
||||
// Use CSS custom properties from theme.css with opacity based on category
|
||||
const opacity = category === 'primary' ? '1' : '0.8';
|
||||
const hoverOpacity = category === 'primary' ? '0.9' : '0.7';
|
||||
|
||||
return {
|
||||
classes: baseClasses,
|
||||
style: {
|
||||
backgroundColor: `rgba(var(--color-${color}), ${opacity})`,
|
||||
'--hover-bg': `rgba(var(--color-${color}), ${hoverOpacity})`
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Responsive device preview state
|
||||
const selectedDevice = ref('Desktop');
|
||||
const deviceSizes = ref([
|
||||
@ -1032,6 +1149,7 @@ const conditionalLogicEngine = ref(null);
|
||||
// Settings tabs configuration
|
||||
const settingsTabs = [
|
||||
{ key: 'info', label: 'Form Info', icon: 'material-symbols:info-outline' },
|
||||
{ key: 'submit', label: 'Submit Button', icon: 'material-symbols:play-arrow' },
|
||||
{ key: 'javascript', label: 'JavaScript', icon: 'material-symbols:code' },
|
||||
{ key: 'css', label: 'CSS', icon: 'material-symbols:palette-outline' },
|
||||
{ key: 'events', label: 'Events', icon: 'material-symbols:event-outline' },
|
||||
@ -1062,7 +1180,8 @@ const formJson = computed(() => {
|
||||
customScript: formStore.formCustomScript,
|
||||
customCSS: formStore.formCustomCSS,
|
||||
formEvents: formStore.formEvents,
|
||||
scriptMode: formStore.scriptMode
|
||||
scriptMode: formStore.scriptMode,
|
||||
submitButton: formStore.submitButton
|
||||
};
|
||||
});
|
||||
|
||||
@ -1269,6 +1388,30 @@ const applyJsonChanges = () => {
|
||||
formStore.scriptMode = importedJson.scriptMode;
|
||||
}
|
||||
|
||||
// Import submit button settings if available
|
||||
if (importedJson.submitButton) {
|
||||
formStore.submitButton = {
|
||||
enabled: importedJson.submitButton.enabled !== undefined ? importedJson.submitButton.enabled : true,
|
||||
label: importedJson.submitButton.label || 'Submit',
|
||||
category: importedJson.submitButton.category || 'primary',
|
||||
color: importedJson.submitButton.color || 'primary'
|
||||
};
|
||||
|
||||
// Handle backward compatibility with old variant format
|
||||
if (importedJson.submitButton.variant && !importedJson.submitButton.category && !importedJson.submitButton.color) {
|
||||
const variantMapping = {
|
||||
'primary': { category: 'primary', color: 'primary' },
|
||||
'secondary': { category: 'secondary', color: 'secondary' },
|
||||
'success': { category: 'primary', color: 'success' },
|
||||
'warning': { category: 'primary', color: 'warning' },
|
||||
'danger': { category: 'primary', color: 'danger' }
|
||||
};
|
||||
const mapping = variantMapping[importedJson.submitButton.variant] || { category: 'primary', color: 'primary' };
|
||||
formStore.submitButton.category = mapping.category;
|
||||
formStore.submitButton.color = mapping.color;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as having unsaved changes
|
||||
formStore.hasUnsavedChanges = true;
|
||||
|
||||
@ -2634,6 +2777,10 @@ const handleFormRestored = (restoredForm) => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Submit button hover effects using CSS custom properties */
|
||||
:deep(.formkit-input[type="submit"]:hover) {
|
||||
background-color: var(--hover-bg) !important;
|
||||
}
|
||||
.form-name-input {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
|
@ -1488,6 +1488,39 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
finalResult: evaluateConditionGroup(conditionGroup, variables)
|
||||
};
|
||||
}
|
||||
|
||||
// Helper: Get submit button variant for RsButton
|
||||
const getSubmitButtonVariant = () => {
|
||||
// If form has submit button configuration, use it
|
||||
if (currentForm.value?.submitButton) {
|
||||
const { category } = currentForm.value.submitButton;
|
||||
return category || 'primary';
|
||||
}
|
||||
// Fallback to primary
|
||||
return 'primary';
|
||||
};
|
||||
|
||||
// Helper: Get submit button styles using theme colors
|
||||
const getWorkflowSubmitButtonStyle = () => {
|
||||
// If form has submit button configuration, use it
|
||||
if (currentForm.value?.submitButton) {
|
||||
const { category = 'primary', color = 'primary' } = currentForm.value.submitButton;
|
||||
|
||||
// Use CSS custom properties from theme.css with opacity based on category
|
||||
const opacity = category === 'primary' ? '1' : '0.8';
|
||||
const hoverOpacity = category === 'primary' ? '0.9' : '0.7';
|
||||
|
||||
return {
|
||||
backgroundColor: `rgba(var(--color-${color}), ${opacity})`,
|
||||
'--hover-bg': `rgba(var(--color-${color}), ${hoverOpacity})`,
|
||||
color: 'white',
|
||||
border: 'none'
|
||||
};
|
||||
}
|
||||
|
||||
// Fallback to default styling
|
||||
return {};
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -1662,16 +1695,17 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
:field-states="fieldStates"
|
||||
/>
|
||||
</template>
|
||||
<!-- Place submit button in a full-width row at the end of the grid -->
|
||||
<div class="col-span-12 flex justify-start pt-6 border-t border-gray-200">
|
||||
<!-- Submit button - respects form builder configuration -->
|
||||
<div v-if="currentForm?.submitButton?.enabled !== false" class="col-span-12 flex justify-start pt-6 border-t border-gray-200">
|
||||
<RsButton
|
||||
@click="validateAndSubmit"
|
||||
:disabled="stepLoading"
|
||||
variant="primary"
|
||||
:variant="getSubmitButtonVariant()"
|
||||
:style="getWorkflowSubmitButtonStyle()"
|
||||
class="col-span-12"
|
||||
>
|
||||
<Icon v-if="stepLoading" name="material-symbols:progress-activity" class="w-4 h-4 animate-spin mr-2" />
|
||||
{{ stepLoading ? 'Processing...' : 'Submit' }}
|
||||
{{ stepLoading ? 'Processing...' : (currentForm?.submitButton?.label || 'Submit') }}
|
||||
</RsButton>
|
||||
</div>
|
||||
</div>
|
||||
@ -1964,4 +1998,9 @@ function getConditionGroupResult(conditionGroup, variables) {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
/* Submit button hover effects using CSS custom properties */
|
||||
:deep(.rs-button:hover) {
|
||||
background-color: var(--hover-bg) !important;
|
||||
}
|
||||
</style>
|
@ -198,6 +198,16 @@
|
||||
],
|
||||
"default": "safe"
|
||||
},
|
||||
"submitButton": {
|
||||
"type": [
|
||||
"number",
|
||||
"string",
|
||||
"boolean",
|
||||
"object",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"creator": {
|
||||
"anyOf": [
|
||||
{
|
||||
@ -281,6 +291,16 @@
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"submitButton": {
|
||||
"type": [
|
||||
"number",
|
||||
"string",
|
||||
"boolean",
|
||||
"object",
|
||||
"array",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"versionNumber": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -64,6 +64,7 @@ model form {
|
||||
customScript String? @db.LongText
|
||||
formEvents Json?
|
||||
scriptMode String? @default("safe") @db.VarChar(20)
|
||||
submitButton Json?
|
||||
creator user? @relation(fields: [formCreatedBy], references: [userID])
|
||||
formHistory formHistory[]
|
||||
task task[]
|
||||
@ -83,6 +84,7 @@ model formHistory {
|
||||
customScript String? @db.LongText
|
||||
formEvents Json?
|
||||
scriptMode String? @db.VarChar(20)
|
||||
submitButton Json?
|
||||
versionNumber Int
|
||||
changeDescription String? @db.Text
|
||||
savedBy Int?
|
||||
|
@ -58,6 +58,7 @@ export default defineEventHandler(async (event) => {
|
||||
customScript: currentForm.customScript,
|
||||
formEvents: currentForm.formEvents,
|
||||
scriptMode: currentForm.scriptMode,
|
||||
submitButton: currentForm.submitButton,
|
||||
versionNumber: nextVersionNumber,
|
||||
changeDescription: body.changeDescription || null,
|
||||
savedBy: body.savedBy || currentForm.formCreatedBy,
|
||||
@ -104,6 +105,10 @@ export default defineEventHandler(async (event) => {
|
||||
if (body.scriptMode !== undefined) {
|
||||
updateData.scriptMode = body.scriptMode;
|
||||
}
|
||||
|
||||
if (body.submitButton !== undefined) {
|
||||
updateData.submitButton = body.submitButton;
|
||||
}
|
||||
|
||||
// Try to update by UUID first
|
||||
let form;
|
||||
|
@ -29,7 +29,8 @@ export default defineEventHandler(async (event) => {
|
||||
customScript: body.customScript || null,
|
||||
customCSS: body.customCSS || null,
|
||||
formEvents: body.formEvents || null,
|
||||
scriptMode: body.scriptMode || 'safe'
|
||||
scriptMode: body.scriptMode || 'safe',
|
||||
submitButton: body.submitButton || null
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -26,6 +26,14 @@ export const useFormBuilderStore = defineStore('formBuilder', {
|
||||
},
|
||||
scriptMode: 'safe', // 'safe' or 'advanced'
|
||||
|
||||
// Submit button configuration
|
||||
submitButton: {
|
||||
enabled: true,
|
||||
label: 'Submit',
|
||||
category: 'primary', // primary, secondary
|
||||
color: 'primary' // primary, secondary, success, info, warning, danger
|
||||
},
|
||||
|
||||
// Form preview data
|
||||
previewFormData: {},
|
||||
|
||||
@ -670,6 +678,7 @@ export const useFormBuilderStore = defineStore('formBuilder', {
|
||||
customCSS: this.formCustomCSS,
|
||||
formEvents: this.formEvents,
|
||||
scriptMode: this.scriptMode,
|
||||
submitButton: this.submitButton,
|
||||
// Add user info and change description for history tracking
|
||||
savedBy: 1, // TODO: Get from authenticated user
|
||||
changeDescription: this.lastChangeDescription || null
|
||||
@ -761,6 +770,12 @@ export const useFormBuilderStore = defineStore('formBuilder', {
|
||||
onValidation: false
|
||||
};
|
||||
this.scriptMode = result.form.scriptMode || 'safe';
|
||||
this.submitButton = result.form.submitButton || {
|
||||
enabled: true,
|
||||
label: 'Submit',
|
||||
category: 'primary',
|
||||
color: 'primary'
|
||||
};
|
||||
|
||||
// Transform components from DB format to store format
|
||||
if (Array.isArray(result.form.formComponents)) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user