PluginProbe
The Innovative Form Builder – IvyForms / 0.2
The Innovative Form Builder – IvyForms v0.2
1.5.1 1.5 1.4.1 1.4 trunk 0.1.2 0.2 0.2.1 0.3 0.3.1 0.4 0.5 0.6 0.6.1 0.6.1-backup 0.6.1.1 0.7 0.8 0.8.1 0.8.2 0.9 0.9.1 1.0 1.1 1.1.1 All 27 releases
ivyforms / frontend / src / views / public / FormRender.vue

FormRender.vue in The Innovative Form Builder – IvyForms 0.2, at frontend/src/views/public/FormRender.vue

317 lines 9.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div
3 ref="formWrapperRef"
4 :class="['ivyforms-form-wrapper', 'ivyforms-form-wrapper-' + formData.id]"
5 >
6 <div v-if="showSuccessMessage" class="ivyforms-success-message">
7 {{ successMessage || getLabel('thank_you') }}
8 </div>
9 <!-- Form title-->
10 <template v-else>
11 <div v-if="showSuccessMessageReset" class="ivyforms-success-message ivyforms-mb-24">
12 {{ successMessage || getLabel('thank_you') }}
13 </div>
14
15 <h2
16 v-if="formData.showTitle"
17 :class="['ivyforms-form-title', 'ivyforms-form-title-' + formData.id]"
18 >
19 {{ formData.name }}
20 </h2>
21 <!-- Form description-->
22 <p
23 v-if="formData.showDescription"
24 :class="['ivyforms-form-description', 'ivyforms-form-description-' + formData.id]"
25 >
26 {{ formData.description }}
27 </p>
28
29 <IvyForm
30 :id="'ivyforms-form-' + formData.id"
31 ref="formRef"
32 :class="'ivyforms-form ivyforms-form-' + formData.id"
33 :data-form-id="formData.id"
34 :rules="formRules"
35 :model="formValues"
36 @submit.prevent="submitForm"
37 >
38 <template v-for="(field, index) in formFields" :key="index">
39 <component
40 :is="formFieldsTemplates[field.type]"
41 ref="formFieldsCollectorRefs"
42 v-model="formValues[field.type + '_' + field.fieldIndex]"
43 :field="field"
44 :error="fieldErrors[field.type + '_' + field.fieldIndex]"
45 ></component>
46 </template>
47 <!-- Add hidden inputs fields for referer, post id ... -->
48 <div class="ivyforms-form-submit">
49 <IvyButtonAction
50 :id="'ivyforms-form-submit-button-' + formData.id"
51 priority="tertiary"
52 type="border"
53 @click.prevent.stop="validateAndSubmit"
54 >
55 {{ getLabel('submit') }}
56 </IvyButtonAction>
57 </div>
58 </IvyForm>
59 </template>
60 </div>
61 </template>
62
63 <script setup lang="ts">
64 import { ref, onMounted, markRaw, reactive } from 'vue'
65 import { useApiClient } from '@/composables/useApiClient'
66
67 // * Form Field templates
68 import IvyTextFormField from '../public/fields/TextFormField.vue'
69 import IvyParagraphFormField from '../public/fields/ParagraphFormField.vue'
70 import IvyEmailFormField from '../public/fields/EmailFormField.vue'
71 import IvyNumberFormField from '../public/fields/NumberFormField.vue'
72 import IvyPhoneFormField from '../public/fields/PhoneFormField.vue'
73 import type { Field } from '@/types/field'
74 import type { Confirmation } from '@/types/confirmations'
75 import { useLabels } from '@/composables/useLabels'
76 const { getLabel } = useLabels()
77 const { request } = useApiClient()
78
79 // Form data
80 const formFields = ref([])
81 const formValues = ref({})
82 const formRules = ref({})
83 const fieldErrors = ref({})
84 const isLoading = ref(true)
85 const formRef = ref(null)
86 const formWrapperRef = ref(null)
87 const formNonce = ref(null)
88 const formConfirmation = ref([])
89 const showSuccessMessage = ref(false)
90 const showSuccessMessageReset = ref(false)
91 const successMessage = ref('')
92
93 interface FormData {
94 id: string | number
95 name?: string
96 description?: string
97 showTitle?: boolean
98 showDescription?: boolean
99 defaultValue?: string
100 counter?: number
101 fields?: Field[]
102 confirmations?: Confirmation[]
103 }
104 // Update your refs with proper typing
105 const formData = ref<FormData>({
106 id: '',
107 name: '',
108 description: '',
109 showTitle: true,
110 showDescription: false,
111 defaultValue: '',
112 counter: 0,
113 fields: [],
114 confirmations: [],
115 })
116 // * Form Fields Object
117 const formFieldsTemplates = reactive({
118 text: markRaw(IvyTextFormField),
119 email: markRaw(IvyEmailFormField),
120 textarea: markRaw(IvyParagraphFormField),
121 number: markRaw(IvyNumberFormField),
122 phone: markRaw(IvyPhoneFormField),
123 })
124
125 // Initialize from global variable
126 onMounted(async () => {
127 if (typeof window !== 'undefined' && window.wpIvyFormDataList) {
128 const counter = formWrapperRef.value.closest('.ivyforms-frontend').dataset.counter
129 const formsData = window.wpIvyFormDataList[counter]
130 // Check if formData is an object and has the required properties
131 if (typeof formsData === 'object' && formsData !== null && formsData.id) {
132 // Assign the form data to the reactive variable
133 formData.value = formsData.form
134 formNonce.value = formsData.nonce
135 formFields.value = formsData.fields || []
136 formConfirmation.value = formsData.confirmations || []
137
138 // Initialize form values with defaults or empty strings
139 formFields.value.forEach((field) => {
140 // Initialize field errors
141 const key = field.type + '_' + field.fieldIndex
142 formValues.value[key] = field.defaultValue || ''
143
144 const rules = []
145
146 // Required rule
147 if (field.required) {
148 rules.push({
149 required: true,
150 message: getRequiredMessage(field),
151 trigger: 'submit',
152 })
153 }
154
155 // Format validations
156 if (field.type === 'email') {
157 rules.push({
158 type: 'email',
159 message: getLabel('enter_email_address'),
160 trigger: ['submit'],
161 })
162 }
163
164 if (field.type === 'phone') {
165 rules.push({
166 pattern: /^[+\d]?(?:[\d\s.-]{7,15})$/,
167 message: getLabel('enter_phone_number'),
168 trigger: ['submit'],
169 })
170 }
171
172 formRules.value[key] = rules
173 })
174 // Form is loaded, remove loader
175 isLoading.value = false
176 // add vanila js remove class from loader
177 const loader = document.querySelector('.ivyforms-skeleton-' + counter)
178 if (loader) {
179 loader.remove()
180 }
181 } else {
182 console.error(getLabel('invalid_form_data'), formData)
183 }
184 } else {
185 console.error(getLabel('form_data_not_found'))
186 // Even with error, don't show loader anymore
187 isLoading.value = false
188 }
189 })
190
191 function getRequiredMessage(field: Field): string {
192 return field.label?.trim() ? `${field.label} ${getLabel('this_field')}` : getLabel('required')
193 }
194
195 // Validate form and submit
196 const validateAndSubmit = async () => {
197 if (!formRef.value) return
198
199 await formRef.value.validate((valid: boolean, errors: Record<string, { message: string }[]>) => {
200 if (valid) {
201 submitForm()
202 } else {
203 fieldErrors.value = {} // Clear previous
204 Object.keys(errors).forEach((fieldKey) => {
205 fieldErrors.value[fieldKey] = errors[fieldKey][0]?.message || getLabel('invalid_input')
206 })
207 }
208 })
209 }
210
211 // Submit form
212 const submitForm = async () => {
213 try {
214 const { data, error, status } = await request('form/submission/', {
215 method: 'POST',
216 headers: {
217 'Content-Type': 'application/json',
218 },
219 data: {
220 formId: formData.value.id,
221 values: formValues.value,
222 nonce: formNonce.value,
223 },
224 })
225 // Handle response
226 if (status !== 200 || error) {
227 console.error(getLabel('error_submitting_form'), error || data)
228 }
229
230 // Reset form or show success message
231 formValues.value = {}
232 const type = formConfirmation.value[0].type
233 const enabled = formConfirmation.value[0].enabled
234 const showForm = formConfirmation.value[0].showForm
235
236 if (!enabled) return
237
238 if (type === 'successMessage') {
239 if (!showForm) {
240 showSuccessMessage.value = true
241 successMessage.value = formConfirmation.value[0].message || getLabel('thank_you')
242 return
243 } else {
244 showSuccessMessageReset.value = true
245 successMessage.value = formConfirmation.value[0].message || getLabel('thank_you')
246
247 // Hide the success message after 5 seconds
248 setTimeout(() => {
249 const messageElement = document.querySelector('.ivyforms-success-message')
250 if (messageElement) {
251 messageElement.classList.add('ivyforms-fade-out')
252 }
253 setTimeout(() => {
254 showSuccessMessageReset.value = false
255 }, 500) // Match the CSS transition duration
256 }, 5000)
257 }
258 } else if (type === 'redirectToPage' && formConfirmation.value[0].page) {
259 const pageId = formConfirmation.value[0].page
260 if (pageId) {
261 const pageUrl = `${window.location.origin}/?p=${pageId}`
262 window.location.href = pageUrl
263 }
264 } else if (type === 'redirectToCustomUrl' && formConfirmation.value[0].url) {
265 window.location.href = formConfirmation.value[0].url
266 }
267 } catch (error) {
268 console.error(getLabel('error_submitting_form'), error)
269 // Show error message
270 }
271 }
272 </script>
273
274 <style lang="scss" scoped>
275 .ivyforms-form-wrapper {
276 width: 100%;
277 max-width: 720px;
278 margin: 0 auto;
279
280 .ivyforms-form-title {
281 font-size: 24px;
282 font-weight: 600;
283 margin-bottom: 12px;
284 color: var(--map-base-text-0);
285 }
286
287 .ivyforms-form-description {
288 font-size: 16px;
289 margin-bottom: 24px;
290 color: var(--map-base-text-1);
291 }
292
293 .ivyforms-form-field {
294 margin-bottom: 16px;
295 }
296
297 .ivyforms-form-submit {
298 margin-top: 24px;
299 }
300 ::v-deep(.ivyforms-success-message) {
301 font-size: 20px;
302 color: var(--map-base-brand-symbol-0);
303 padding: 20px;
304 background-color: var(--map-base-brand-o05);
305 border: 1px solid var(--map-base-brand-symbol-0);
306 border-radius: 8px;
307 text-align: center;
308 transition: opacity 0.5s ease-in-out;
309 opacity: 1;
310
311 &.ivyforms-fade-out {
312 opacity: 0;
313 }
314 }
315 }
316 </style>
317