Refactor Form Submission and Field Management Logic
- Simplified the constructor in StoreFormSubmissionJob for improved readability. - Enhanced the storeFile method to handle null and empty values more robustly, ensuring better validation of file names. - Updated StorageFileNameParser to return null for empty file names, improving error handling. - Refactored OpenForm.vue to optimize form initialization and prevent unnecessary reinitializations, enhancing performance. - Introduced new methods for managing field groups and preventing recursive updates in OpenForm.vue, improving the overall user experience. - Enhanced FormEditorSidebar.vue to utilize Pinia store for better state management and clarity. - Improved FormFieldEdit.vue to prevent page jumps during field editing, ensuring a smoother user experience. - Added new getters in working_form.js for better page management and navigation within forms. These changes aim to enhance the maintainability and performance of the form handling logic, providing a more efficient and user-friendly experience.
This commit is contained in:
@@ -200,11 +200,10 @@ export default {
|
||||
|
||||
data() {
|
||||
return {
|
||||
// Page index
|
||||
/**
|
||||
* Used to force refresh components by changing their keys
|
||||
*/
|
||||
isAutoSubmit: false,
|
||||
isInitialLoad: true,
|
||||
// Flag to prevent recursion in field group updates
|
||||
isUpdatingFieldGroups: false,
|
||||
}
|
||||
},
|
||||
|
||||
@@ -337,16 +336,24 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
form: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.initForm()
|
||||
}
|
||||
// Monitor only critical changes that require full reinitialization
|
||||
'form.database_id': function() {
|
||||
// Only reinitialize when database changes
|
||||
this.initForm()
|
||||
},
|
||||
fields: {
|
||||
'fields.length': function() {
|
||||
// Only reinitialize when fields are added or removed
|
||||
this.updateFieldGroupsSafely()
|
||||
},
|
||||
// Watch for changes to individual field properties
|
||||
'fields': {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.initForm()
|
||||
// Skip update if only triggered by internal fieldGroups changes
|
||||
if (this.isUpdatingFieldGroups) return
|
||||
|
||||
// Safely update field groups without causing recursive updates
|
||||
this.updateFieldGroupsSafely()
|
||||
}
|
||||
},
|
||||
dataFormValue: {
|
||||
@@ -460,21 +467,48 @@ export default {
|
||||
* Form initialization
|
||||
*/
|
||||
async initForm() {
|
||||
// Only do a full initialization when necessary
|
||||
// Store current page index and form data to avoid overwriting existing values
|
||||
const currentFormData = this.dataForm ? clonedeep(this.dataForm.data()) : {}
|
||||
|
||||
// Handle special cases first
|
||||
if (this.defaultDataForm) {
|
||||
// If we have default data form, initialize with that
|
||||
await nextTick(() => {
|
||||
this.dataForm.resetAndFill(this.defaultDataForm)
|
||||
})
|
||||
this.updateFieldGroupsSafely()
|
||||
return
|
||||
}
|
||||
|
||||
if (await this.tryInitFormFromEditableSubmission()) return
|
||||
if (this.tryInitFormFromPendingSubmission()) return
|
||||
|
||||
await nextTick(() => {
|
||||
this.formPageIndex = 0
|
||||
this.initFormWithDefaultValues()
|
||||
})
|
||||
// Initialize the field groups without resetting form data
|
||||
this.updateFieldGroupsSafely()
|
||||
|
||||
// Check if we need to handle form submission states
|
||||
if (await this.checkForEditableSubmission()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.checkForPendingSubmission()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Standard initialization with default values
|
||||
this.initFormWithDefaultValues(currentFormData)
|
||||
},
|
||||
|
||||
checkForEditableSubmission() {
|
||||
return this.tryInitFormFromEditableSubmission()
|
||||
},
|
||||
|
||||
checkForPendingSubmission() {
|
||||
if (this.tryInitFormFromPendingSubmission()) {
|
||||
this.updateFieldGroupsSafely()
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
async tryInitFormFromEditableSubmission() {
|
||||
if (this.isPublicFormPage && this.form.editable_submissions) {
|
||||
const submissionId = useRoute().query?.submission_id
|
||||
@@ -489,6 +523,7 @@ export default {
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
tryInitFormFromPendingSubmission() {
|
||||
if (this.isPublicFormPage && this.form.auto_save) {
|
||||
const pendingData = this.pendingSubmission.get()
|
||||
@@ -500,6 +535,7 @@ export default {
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
updatePendingDataFields(pendingData) {
|
||||
this.fields.forEach(field => {
|
||||
if (field.type === 'date' && field.prefill_today) {
|
||||
@@ -507,17 +543,26 @@ export default {
|
||||
}
|
||||
})
|
||||
},
|
||||
initFormWithDefaultValues() {
|
||||
const formData = {}
|
||||
|
||||
initFormWithDefaultValues(currentFormData = {}) {
|
||||
// Only set page 0 on first load, otherwise maintain current position
|
||||
if (this.formPageIndex === undefined || this.isInitialLoad) {
|
||||
this.formPageIndex = 0
|
||||
this.isInitialLoad = false
|
||||
}
|
||||
|
||||
// Initialize form data with default values
|
||||
const formData = { ...currentFormData }
|
||||
const urlPrefill = this.isPublicFormPage ? new URLSearchParams(window.location.search) : null
|
||||
|
||||
this.fields.forEach(field => {
|
||||
if (field.type.startsWith('nf-')) return
|
||||
if (field.type.startsWith('nf-') && !['nf-page-body-input', 'nf-page-logo', 'nf-page-cover'].includes(field.type)) return
|
||||
|
||||
this.handleUrlPrefill(field, formData, urlPrefill)
|
||||
this.handleDefaultPrefill(field, formData)
|
||||
})
|
||||
|
||||
|
||||
// Reset form with new data
|
||||
this.dataForm.resetAndFill(formData)
|
||||
},
|
||||
handleUrlPrefill(field, formData, urlPrefill) {
|
||||
@@ -620,6 +665,59 @@ export default {
|
||||
}
|
||||
this.formPageIndex = this.fieldGroups.length - 1
|
||||
},
|
||||
|
||||
// New method for updating field groups
|
||||
updateFieldGroups() {
|
||||
if (!this.fields || this.fields.length === 0) return
|
||||
|
||||
// Preserve the current page index if possible
|
||||
const currentPageIndex = this.formPageIndex
|
||||
|
||||
// Use a local variable instead of directly modifying computed property
|
||||
// We'll use this to determine totalPages and currentPageIndex
|
||||
const calculatedGroups = this.fields.reduce((groups, field, index) => {
|
||||
// If the field is a page break, start a new group
|
||||
if (field.type === 'nf-page-break' && index !== 0) {
|
||||
groups.push([])
|
||||
}
|
||||
// Add the field to the current group
|
||||
if (groups.length === 0) groups.push([])
|
||||
groups[groups.length - 1].push(field)
|
||||
return groups
|
||||
}, [])
|
||||
|
||||
// If we don't have any groups (shouldn't happen), create a default group
|
||||
if (calculatedGroups.length === 0) {
|
||||
calculatedGroups.push([])
|
||||
}
|
||||
|
||||
// Update page navigation
|
||||
const totalPages = calculatedGroups.length
|
||||
|
||||
// Try to maintain the current page index if valid
|
||||
if (currentPageIndex !== undefined && currentPageIndex < totalPages) {
|
||||
this.formPageIndex = currentPageIndex
|
||||
} else {
|
||||
this.formPageIndex = 0
|
||||
}
|
||||
|
||||
// Force a re-render of the component, which will update fieldGroups computed property
|
||||
this.$forceUpdate()
|
||||
},
|
||||
|
||||
// Helper method to prevent recursive updates
|
||||
updateFieldGroupsSafely() {
|
||||
// Set flag to prevent recursive updates
|
||||
this.isUpdatingFieldGroups = true
|
||||
|
||||
// Call the actual update method
|
||||
this.updateFieldGroups()
|
||||
|
||||
// Clear the flag after a short delay to allow Vue to process the update
|
||||
this.$nextTick(() => {
|
||||
this.isUpdatingFieldGroups = false
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -107,7 +107,6 @@
|
||||
import { computed, ref, watch, onMounted } from 'vue'
|
||||
import OpenCompleteForm from '../../OpenCompleteForm.vue'
|
||||
import {handleDarkMode, useDarkMode} from "~/lib/forms/public-page.js"
|
||||
import { default as _has } from 'lodash/has'
|
||||
import { useRecordsStore } from '~/stores/records'
|
||||
import { useWorkingFormStore } from '~/stores/working_form'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<script>
|
||||
import { computed } from "vue"
|
||||
import { useWorkingFormStore } from "../../../../../stores/working_form"
|
||||
import { storeToRefs } from 'pinia'
|
||||
import EditorRightSidebar from "../../../editors/EditorRightSidebar.vue"
|
||||
import FormFieldEdit from "../../fields/FormFieldEdit.vue"
|
||||
import AddFormBlock from "./AddFormBlock.vue"
|
||||
@@ -35,13 +36,20 @@ export default {
|
||||
props: {},
|
||||
setup() {
|
||||
const workingFormStore = useWorkingFormStore()
|
||||
|
||||
const editFieldIndex = computed(() => workingFormStore.selectedFieldIndex)
|
||||
const form = storeToRefs(workingFormStore).content
|
||||
const showEditFieldSidebar = computed(() => workingFormStore.showEditFieldSidebar)
|
||||
const showAddFieldSidebar = computed(() => workingFormStore.showAddFieldSidebar)
|
||||
|
||||
// The store now handles setting the page automatically in openSettingsForField
|
||||
|
||||
return {
|
||||
workingFormStore,
|
||||
editFieldIndex: computed(() => workingFormStore.selectedFieldIndex),
|
||||
showEditFieldSidebar: computed(
|
||||
() => workingFormStore.showEditFieldSidebar,
|
||||
),
|
||||
showAddFieldSidebar: computed(() => workingFormStore.showAddFieldSidebar),
|
||||
editFieldIndex,
|
||||
form,
|
||||
showEditFieldSidebar,
|
||||
showAddFieldSidebar,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -51,15 +59,6 @@ export default {
|
||||
isOpen() {
|
||||
return this.form !== null && (this.showEditFieldSidebar || this.showAddFieldSidebar)
|
||||
},
|
||||
form: {
|
||||
get() {
|
||||
return this.workingFormStore.content
|
||||
},
|
||||
/* We add a setter */
|
||||
set(value) {
|
||||
this.workingFormStore.set(value)
|
||||
},
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
mounted() {},
|
||||
|
||||
@@ -104,11 +104,11 @@ export default {
|
||||
},
|
||||
operators() {
|
||||
return Object.entries(this.available_filters[this.property.type].comparators)
|
||||
.filter(([key, value]) => this.customValidation || (!this.customValidation && !value.custom_validation_only))
|
||||
.map(([key]) => {
|
||||
.filter(([filterKey, value]) => this.customValidation || (!this.customValidation && !value.custom_validation_only))
|
||||
.map(([filterKey]) => {
|
||||
return {
|
||||
value: key,
|
||||
name: this.optionFilterNames(key),
|
||||
value: filterKey,
|
||||
name: this.optionFilterNames(filterKey),
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
@@ -124,6 +124,14 @@ const field = computed(() => {
|
||||
: null
|
||||
})
|
||||
|
||||
// Only set the page once when the component is mounted
|
||||
// This prevents page jumps when editing field properties
|
||||
onMounted(() => {
|
||||
if (selectedFieldIndex.value !== null) {
|
||||
workingFormStore.setPageForField(selectedFieldIndex.value)
|
||||
}
|
||||
})
|
||||
|
||||
const isBlockField = computed(() => {
|
||||
return field.value && field.value.type.startsWith('nf')
|
||||
})
|
||||
@@ -147,6 +155,8 @@ function removeBlock() {
|
||||
}
|
||||
|
||||
function closeSidebar() {
|
||||
// Explicitly clear the selected field index to prevent issues with subsequent block additions
|
||||
workingFormStore.selectedFieldIndex = null
|
||||
workingFormStore.closeEditFieldSidebar()
|
||||
}
|
||||
|
||||
|
||||
@@ -46,19 +46,28 @@
|
||||
<ul class="mt-4 space-y-2 text-sm">
|
||||
<li>
|
||||
Do you want to know why users choose OpnForm?
|
||||
<NuxtLink class="text-blue-500 hover:text-blue-600" :to="{ name: 'ai-form-builder' }">
|
||||
<NuxtLink
|
||||
class="text-blue-500 hover:text-blue-600"
|
||||
:to="{ name: 'ai-form-builder' }"
|
||||
>
|
||||
Learn more here!
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
Browse Templates
|
||||
<NuxtLink class="text-blue-500 hover:text-blue-600" :to="{ name: 'templates' }">
|
||||
<NuxtLink
|
||||
class="text-blue-500 hover:text-blue-600"
|
||||
:to="{ name: 'templates' }"
|
||||
>
|
||||
Get inspired with ready-to-use form templates!
|
||||
</NuxtLink>
|
||||
</li>
|
||||
<li>
|
||||
Check Out Our Help Center
|
||||
<NuxtLink class="text-blue-500 hover:text-blue-600" to="https://help.opnform.com/en/">
|
||||
<NuxtLink
|
||||
class="text-blue-500 hover:text-blue-600"
|
||||
to="https://help.opnform.com/en/"
|
||||
>
|
||||
Find quick answers to common questions.
|
||||
</NuxtLink>
|
||||
</li>
|
||||
@@ -69,7 +78,6 @@
|
||||
<div class="mt-20 border-t">
|
||||
<OpenFormFooter />
|
||||
</div>
|
||||
|
||||
</section>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
@@ -108,12 +108,6 @@ import RegisterForm from "./RegisterForm.vue"
|
||||
import { WindowMessageTypes } from "~/composables/useWindowMessage"
|
||||
|
||||
const appStore = useAppStore()
|
||||
const props = defineProps({
|
||||
redirectUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
|
||||
// Define emits for component interactions
|
||||
const emit = defineEmits(['close', 'reopen'])
|
||||
|
||||
Reference in New Issue
Block a user