PluginProbe
The Innovative Form Builder – IvyForms / 0.1.2
The Innovative Form Builder – IvyForms v0.1.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 / stores / useFormBuilder.ts

useFormBuilder.ts in The Innovative Form Builder – IvyForms 0.1.2, at frontend/src/stores/useFormBuilder.ts

423 lines 13.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import { defineStore } from 'pinia'
2 import { ref } from 'vue'
3 import type { Field } from '@/types/field'
4 import { useLabels } from '@/composables/useLabels'
5 import IvyMessage from '@/views/_components/message/ivyMessage.ts'
6 import { useRouter } from 'vue-router'
7
8 export const useFormBuilder = defineStore('formBuilderData', () => {
9 const formId = ref<string | null>(null)
10 const isEditing = ref(false)
11 const fields = ref([])
12 const counterFields = ref<number>(0)
13 const selectedField = ref<Field | null>(null)
14 const activeTab = ref<string>('addField')
15 const description = ref<string | null>(null)
16 const name = ref<string | null>('Blank form')
17 const published = ref(true)
18 const showTitle = ref(true)
19 const showDescription = ref(false)
20 const isEditingSettingGeneral = ref(false)
21 const confirmationId = ref<string | null>(null)
22 const router = useRouter()
23
24 const { getLabel } = useLabels()
25 // Set selected field and update the active tab
26 const selectField = (fieldIndex: number) => {
27 selectedField.value =
28 fields.value.find((field: Field) => field.fieldIndex === fieldIndex) || null
29 activeTab.value = 'options'
30 }
31
32 // Deselect field when clicking outside of it
33 function deselectField(event: Event) {
34 event.stopPropagation() // Prevent event from bubbling up to the document
35 const clickedElement = event.target as HTMLElement
36 const fieldElements = document.querySelectorAll('.ivyforms-form-builder__list-group__item')
37
38 let clickedInsideField = false
39 fieldElements.forEach((el) => {
40 if (el.contains(clickedElement)) {
41 clickedInsideField = true
42 }
43 })
44
45 // Only deselect if clicking outside a field
46 if (!clickedInsideField) {
47 selectedField.value = null
48 }
49 }
50
51 // Check if the field is selected based on its index
52 const isSelected = (fieldIndex: number) => {
53 return selectedField.value?.fieldIndex === fieldIndex
54 }
55
56 // Update the selected field and the store
57 const updateSelectedField = (key: string, value: number) => {
58 // Find the index of the field in the store and update it
59 const index = fields.value.findIndex((f) => f.fieldIndex === selectedField.value?.fieldIndex)
60 if (index !== -1) {
61 fields.value[index][key] = value
62 }
63 }
64
65 // Add a new field when an item is dropped or clicked
66 const addField = (fieldData: {
67 id: number
68 fieldIndex: number
69 type: string
70 label: string
71 defaultValue?: string
72 required?: boolean
73 placeholder?: string | number
74 position?: number
75 }) => {
76 const newPosition = fieldData.position ?? fields.value.length + 1
77
78 fields.value.push({
79 id: fieldData.id,
80 fieldIndex: fieldData.fieldIndex,
81 type: fieldData.type,
82 label: fieldData.label,
83 defaultValue: '',
84 required: fieldData.required || false,
85 placeholder: fieldData.placeholder || '',
86 position: newPosition,
87 })
88
89 updateFieldPositions() // Ensure positions remain sequential
90 }
91
92 // Function to update positions after reordering
93 const updateFieldPositions = () => {
94 fields.value.forEach((field, fieldIndex) => {
95 field.position = fieldIndex + 1
96 })
97 }
98
99 // Increase the counter for new fields
100 const increaseCounter = () => {
101 counterFields.value++
102 return counterFields.value
103 }
104 // TODO add Internalization for all strings in notifications
105 // Load existing form data if formId is provided
106 const loadForm = async (id: string) => {
107 try {
108 const response = await fetch(`/wp-json/ivyforms/v1/form/${id}/`, {
109 method: 'GET',
110 headers: {
111 'Content-Type': 'application/json',
112 // eslint-disable-next-line no-undef
113 'X-WP-Nonce': wpIvyApiSettings.nonce, // Add nonce for authentication
114 },
115 })
116 const result = await response.json()
117
118 if (response.ok && response.status == 200 && result.data.data) {
119 formId.value = id
120 isEditing.value = true
121 isEditingSettingGeneral.value = true
122 name.value = result.data.data.name || 'Blank form'
123 description.value = result.data.data.description || ''
124 published.value = result.data.data.published || false
125 showTitle.value = result.data.data.showTitle || false
126 showDescription.value = result.data.data.showDescription || false
127 confirmationId.value = result.data.data.confirmationId || null
128 const fieldsArray = result.data.data.fields
129 fields.value = fieldsArray.map((field: Field, fieldIndex: number) => ({
130 id: field.id,
131 fieldIndex: field.fieldIndex,
132 type: field.type,
133 label: field.label,
134 defaultValue: field.defaultValue || '',
135 required: field.required || false,
136 placeholder: field.placeholder || '',
137 position: field.position || fieldIndex + 1,
138 }))
139
140 counterFields.value = 0
141 // Find the highest key in all fields if they exist
142 if (fieldsArray.length)
143 counterFields.value = Math.max(...fieldsArray.map((field: Field) => field.fieldIndex))
144 } else {
145 IvyMessage({
146 message: getLabel('failed_to_tab_form'),
147 type: 'error',
148 })
149 console.error(getLabel('failed_to_tab_form'), result.data.message)
150 }
151 } catch (error) {
152 IvyMessage({
153 message: getLabel('error_loading_form'),
154 type: 'error',
155 })
156 console.error(getLabel('error_loading_form'), error)
157 }
158 }
159
160 // Update form after clicking update button
161 const updateForm = async () => {
162 if (!formId.value) return
163 // TODO Refactor for name/desc after templates
164 const formData = {
165 name: name.value || 'Updated form',
166 description: description.value || '',
167 published: published.value || false,
168 showTitle: showTitle.value || false,
169 showDescription: showDescription.value || false,
170 fields: fields.value ? fields.value : [],
171 }
172
173 try {
174 const response = await fetch(`/wp-json/ivyforms/v1/form/update/${formId.value}/`, {
175 method: 'POST',
176 headers: {
177 'Content-Type': 'application/json',
178 // eslint-disable-next-line no-undef
179 'X-WP-Nonce': wpIvyApiSettings.nonce, // Add nonce for authentication
180 },
181 body: JSON.stringify(formData),
182 })
183
184 const result = await response.json()
185
186 if (response.ok) {
187 IvyMessage({
188 message: getLabel('successfully_updated_form'),
189 type: 'success',
190 })
191 console.log(getLabel('successfully_updated_form'), result.data.message)
192 } else {
193 IvyMessage({
194 message: getLabel('failed_to_update_form'),
195 type: 'error',
196 })
197 console.error(getLabel('failed_to_update_form'), result.data.message)
198 }
199 } catch (error) {
200 IvyMessage({
201 message: getLabel('error_updating_form'),
202 type: 'error',
203 })
204 console.error(getLabel('error_updating_form'), error)
205 }
206 }
207
208 // Save form using WordPress REST API
209 const saveForm = async () => {
210 const formData = {
211 // TODO make form name dynamic
212 name: name.value || 'Blank form',
213 description: description.value || '',
214 showTitle: showTitle.value || false,
215 published: published.value || false,
216 showDescription: showDescription.value || false,
217 fields: fields.value.map((field) => ({
218 id: field.id,
219 fieldIndex: field.fieldIndex,
220 type: field.type,
221 label: field.label,
222 required: field.required,
223 defaultValue: field.defaultValue,
224 placeholder: field.placeholder,
225 position: field.position,
226 })),
227 }
228
229 try {
230 const response = await fetch('/wp-json/ivyforms/v1/form/add/', {
231 method: 'POST',
232 headers: {
233 'Content-Type': 'application/json',
234 // eslint-disable-next-line no-undef
235 'X-WP-Nonce': wpIvyApiSettings.nonce, // Add nonce for authentication
236 },
237 body: JSON.stringify(formData),
238 })
239
240 const result = await response.json()
241
242 if (response.ok && result.data.data?.id) {
243 formId.value = result.data.data.id // Store new form ID
244 confirmationId.value = result.data.data.confirmationId
245 isEditing.value = true // Switch to update mode
246 isEditingSettingGeneral.value = true // Switch to update mode
247 await router.push({ hash: `manage/${formId.value}` })
248 IvyMessage({
249 message: getLabel('successfully_created_form'),
250 type: 'success',
251 })
252 } else {
253 IvyMessage({
254 message: getLabel('failed_save_form'),
255 type: 'error',
256 })
257 }
258 } catch (error) {
259 IvyMessage({
260 message: `${getLabel('error_save_form')} ${error}`,
261 type: 'error',
262 })
263 }
264 }
265 // Save form using WordPress REST API
266 // TODO Refactor save logic after templates (for fields)
267 const saveFormSettings = async () => {
268 // TODO Refactor for name/desc after templates
269 const formData = {
270 // TODO make form name dynamic
271 name: name.value || getLabel('blank_form'),
272 description: description.value || '',
273 published: published.value || false,
274 showTitle: showTitle.value || false,
275 showDescription: showDescription.value || false,
276 fields: fields.value.map((field) => ({
277 id: field.id,
278 fieldIndex: field.fieldIndex,
279 type: field.type,
280 label: field.label,
281 required: field.required,
282 defaultValue: field.defaultValue,
283 placeholder: field.placeholder,
284 position: field.position,
285 })),
286 }
287
288 try {
289 const response = await fetch('/wp-json/ivyforms/v1/form/add/', {
290 method: 'POST',
291 headers: {
292 'Content-Type': 'application/json',
293 // eslint-disable-next-line no-undef
294 'X-WP-Nonce': wpIvyApiSettings.nonce, // Add nonce for authentication
295 },
296 body: JSON.stringify(formData),
297 })
298
299 const result = await response.json()
300
301 if (response.ok && result.data.data?.id) {
302 formId.value = result.data.data.id // Store new form ID
303 isEditing.value = true // Switch to update mode
304 isEditingSettingGeneral.value = true // Switch to update mode
305 await router.push({ hash: `/settings/general/manage/${formId.value}` })
306 IvyMessage({
307 message: getLabel('successfully_created_form'),
308 type: 'success',
309 })
310 } else {
311 IvyMessage({
312 message: getLabel('failed_save_form'),
313 type: 'error',
314 })
315 }
316 } catch (error) {
317 IvyMessage({
318 message: `${getLabel('error_save_form')} ${error}`,
319 type: 'error',
320 })
321 }
322 }
323 const moveFieldUp = (fieldIndex: number) => {
324 const currentIndex = fields.value.findIndex((f) => f.fieldIndex === fieldIndex)
325 if (currentIndex > 0) {
326 const temp = fields.value[currentIndex]
327 fields.value[currentIndex] = fields.value[currentIndex - 1]
328 fields.value[currentIndex - 1] = temp
329 updateFieldPositions()
330 }
331 }
332
333 const moveFieldDown = (fieldIndex: number) => {
334 const currentIndex = fields.value.findIndex((f) => f.fieldIndex === fieldIndex)
335 if (currentIndex < fields.value.length - 1) {
336 const temp = fields.value[currentIndex]
337 fields.value[currentIndex] = fields.value[currentIndex + 1]
338 fields.value[currentIndex + 1] = temp
339 updateFieldPositions()
340 }
341 }
342
343 // Delete a field by index
344 const deleteField = (fieldIndex: number) => {
345 const i = fields.value.findIndex((f) => f.fieldIndex === fieldIndex)
346 if (i !== -1) {
347 fields.value.splice(i, 1)
348 if (selectedField.value?.fieldIndex === fieldIndex) {
349 selectedField.value = null
350 }
351 updateFieldPositions()
352 }
353 }
354
355 // Duplicate a field
356 const duplicateField = (fieldIndex: number) => {
357 const original = fields.value.find((f) => f.fieldIndex === fieldIndex)
358 if (!original) return
359
360 const newIndex = increaseCounter()
361
362 const copy = {
363 ...JSON.parse(JSON.stringify(original)),
364 id: 0,
365 fieldIndex: newIndex,
366 label: original.label + ' (copy)',
367 }
368
369 const insertAt = fields.value.findIndex((f) => f.fieldIndex === fieldIndex)
370 fields.value.splice(insertAt + 1, 0, copy)
371 updateFieldPositions()
372 }
373
374 // Reset form to default values
375 const resetForm = () => {
376 formId.value = null
377 isEditing.value = false
378 fields.value = []
379 counterFields.value = 0
380 selectedField.value = null
381 activeTab.value = 'addField'
382 description.value = null
383 name.value = 'Blank form'
384 published.value = true
385 showTitle.value = true
386 showDescription.value = false
387 isEditingSettingGeneral.value = false
388 confirmationId.value = null
389 }
390
391 return {
392 formId,
393 fields,
394 isEditing,
395 selectedField,
396 activeTab,
397 counterFields,
398 description,
399 name,
400 published,
401 showTitle,
402 showDescription,
403 isEditingSettingGeneral,
404 confirmationId,
405 saveForm,
406 loadForm,
407 updateForm,
408 increaseCounter,
409 addField,
410 selectField,
411 deselectField,
412 isSelected,
413 updateSelectedField,
414 updateFieldPositions,
415 moveFieldUp,
416 moveFieldDown,
417 deleteField,
418 duplicateField,
419 saveFormSettings,
420 resetForm,
421 }
422 })
423