PluginProbe
The Innovative Form Builder – IvyForms / 0.4
The Innovative Form Builder – IvyForms v0.4
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 / fields / AddressFormField.vue

AddressFormField.vue in The Innovative Form Builder – IvyForms 0.4, at frontend/src/views/public/fields/AddressFormField.vue

324 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <template>
2 <div
3 :class="[
4 'ivyforms-field',
5 'ivyforms-field__address',
6 'ivyforms-field__address--' + field.id,
7 field.cssClasses,
8 {
9 'ivyforms-field__address--main-label-visible': !field.hideLabel,
10 'ivyforms-field__address--readonly': !!field.readOnly,
11 },
12 ]"
13 >
14 <div
15 v-if="!field.hideLabel"
16 class="regular-16 ivyforms-mb-16 ivyforms-flex ivyforms-align-items-center"
17 >
18 {{ field.label }}
19 </div>
20 <div class="ivyforms-flex ivyforms-flex-direction-column ivyforms-gap-16">
21 <!-- Row 1: Street Address (full width) -->
22 <div class="ivyforms-flex ivyforms-flex-direction-row">
23 <div v-if="getSubVisible(0)" class="ivyforms-width-100">
24 <IvyFormItem
25 :required="isSubRequired(0)"
26 :prop="`${field.type}_${field.fieldIndex}.${addressFields[0].type}`"
27 :show-info="!!getSubDescription(0)"
28 :info-text="getSubDescription(0) || ''"
29 :error="getSubfieldError(addressFields[0].type)"
30 >
31 <template #label>
32 <div
33 v-if="!addressFields[0].hide"
34 class="ivyforms-field__address-label medium-14 ivyforms-mb-6 ivyforms-flex ivyforms-align-items-center"
35 >
36 <span class="ivyforms-nowrap">{{ addressFields[0].label }}</span>
37 </div>
38 <div
39 v-else
40 class="ivyforms-mb-6"
41 style="height: 20px; visibility: hidden; display: block"
42 aria-hidden="true"
43 ></div>
44 </template>
45 <IvyTextInput
46 :id="addressFields[0].fieldID"
47 :model-value="getFieldValue(addressFields[0].type)"
48 class="ivyforms-width-100"
49 type="text"
50 :placeholder="addressFields[0].placeholder"
51 :disabled="isDisabled"
52 :required="isSubRequired(0)"
53 :aria-required="isSubRequired(0) ? 'true' : 'false'"
54 @input="updateFieldValue(addressFields[0].type, $event)"
55 />
56 </IvyFormItem>
57 </div>
58 </div>
59 <!-- Row 2: Address Line 2 (full width) -->
60 <div class="ivyforms-flex ivyforms-flex-direction-row">
61 <div v-if="getSubVisible(1)" class="ivyforms-width-100">
62 <IvyFormItem
63 :required="isSubRequired(1)"
64 :prop="`${field.type}_${field.fieldIndex}.${addressFields[1].type}`"
65 :show-info="!!getSubDescription(1)"
66 :info-text="getSubDescription(1) || ''"
67 :error="getSubfieldError(addressFields[1].type)"
68 >
69 <template #label>
70 <div
71 v-if="!addressFields[1].hide"
72 class="ivyforms-field__address-label medium-14 ivyforms-mb-6 ivyforms-flex ivyforms-align-items-center"
73 >
74 <span class="ivyforms-nowrap">{{ addressFields[1].label }}</span>
75 </div>
76 <div
77 v-else
78 class="ivyforms-mb-6"
79 style="height: 20px; visibility: hidden; display: block"
80 aria-hidden="true"
81 ></div>
82 </template>
83 <IvyTextInput
84 :id="addressFields[1].fieldID"
85 :model-value="getFieldValue(addressFields[1].type)"
86 class="ivyforms-width-100"
87 type="text"
88 :placeholder="addressFields[1].placeholder"
89 :disabled="isDisabled"
90 :required="isSubRequired(1)"
91 :aria-required="isSubRequired(1) ? 'true' : 'false'"
92 @input="updateFieldValue(addressFields[1].type, $event)"
93 />
94 </IvyFormItem>
95 </div>
96 </div>
97 <!-- Row 3: State (full width) -->
98 <div class="ivyforms-flex ivyforms-flex-direction-row">
99 <div v-if="getSubVisible(3)" class="ivyforms-width-100">
100 <IvyFormItem
101 :required="isSubRequired(3)"
102 :prop="`${field.type}_${field.fieldIndex}.${addressFields[3].type}`"
103 :show-info="!!getSubDescription(3)"
104 :info-text="getSubDescription(3) || ''"
105 :error="getSubfieldError(addressFields[3].type)"
106 >
107 <template #label>
108 <div
109 v-if="!addressFields[3].hide"
110 class="ivyforms-field__address-label medium-14 ivyforms-mb-6 ivyforms-flex ivyforms-align-items-center"
111 >
112 <span class="ivyforms-nowrap">{{ addressFields[3].label }}</span>
113 </div>
114 <div
115 v-else
116 class="ivyforms-mb-6"
117 style="height: 20px; visibility: hidden; display: block"
118 aria-hidden="true"
119 ></div>
120 </template>
121 <IvyTextInput
122 :id="addressFields[3].fieldID"
123 :model-value="getFieldValue(addressFields[3].type)"
124 class="ivyforms-width-100"
125 type="text"
126 :placeholder="addressFields[3].placeholder"
127 :disabled="isDisabled"
128 :required="isSubRequired(3)"
129 :aria-required="isSubRequired(3) ? 'true' : 'false'"
130 @input="updateFieldValue(addressFields[3].type, $event)"
131 />
132 </IvyFormItem>
133 </div>
134 </div>
135 <!-- Row 4: City, Zip, Country (three fields in one row) -->
136 <div class="ivyforms-flex ivyforms-flex-direction-row ivyforms-gap-16">
137 <template
138 v-for="(fieldData, idx) in [addressFields[2], addressFields[4], addressFields[5]]"
139 :key="fieldData.fieldID"
140 >
141 <div v-if="getSubVisible(idx === 0 ? 2 : idx === 1 ? 4 : 5)" class="ivyforms-width-100">
142 <IvyFormItem
143 :required="isSubRequired(idx === 0 ? 2 : idx === 1 ? 4 : 5)"
144 :prop="`${field.type}_${field.fieldIndex}.${fieldData.type}`"
145 :show-info="!!getSubDescription(idx === 0 ? 2 : idx === 1 ? 4 : 5)"
146 :info-text="getSubDescription(idx === 0 ? 2 : idx === 1 ? 4 : 5) || ''"
147 :error="getSubfieldError(fieldData.type)"
148 >
149 <template #label>
150 <div
151 v-if="!fieldData.hide"
152 class="ivyforms-field__address-label medium-14 ivyforms-mb-6 ivyforms-flex ivyforms-align-items-center"
153 >
154 <span class="ivyforms-nowrap">{{ fieldData.label }}</span>
155 </div>
156 <div
157 v-else
158 class="ivyforms-mb-6"
159 style="height: 20px; visibility: hidden; display: block"
160 aria-hidden="true"
161 ></div>
162 </template>
163 <template v-if="fieldData.type === 'country'">
164 <IvySelectInput
165 :id="fieldData.fieldID"
166 v-model="countryValue"
167 :placeholder="getLabel('select_country')"
168 :disabled="isDisabled"
169 :required="isSubRequired(idx === 0 ? 2 : idx === 1 ? 4 : 5)"
170 :aria-required="
171 isSubRequired(idx === 0 ? 2 : idx === 1 ? 4 : 5) ? 'true' : 'false'
172 "
173 @update:model-value="(val) => updateFieldValue('country', val)"
174 >
175 <IvySelectOption value="">{{ getLabel('select_country') }}</IvySelectOption>
176 <IvySelectOption
177 v-for="country in countryOptions"
178 :key="country"
179 :value="country"
180 >{{ country }}</IvySelectOption
181 >
182 </IvySelectInput>
183 </template>
184 <template v-else>
185 <IvyTextInput
186 :id="fieldData.fieldID"
187 :model-value="getFieldValue(fieldData.type)"
188 class="ivyforms-width-100"
189 type="text"
190 :placeholder="fieldData.placeholder"
191 :disabled="isDisabled"
192 :required="isSubRequired(idx === 0 ? 2 : idx === 1 ? 4 : 5)"
193 :aria-required="
194 isSubRequired(idx === 0 ? 2 : idx === 1 ? 4 : 5) ? 'true' : 'false'
195 "
196 @input="updateFieldValue(fieldData.type, $event)"
197 />
198 </template>
199 </IvyFormItem>
200 </div>
201 </template>
202 </div>
203 </div>
204 </div>
205 </template>
206
207 <script setup lang="ts">
208 import { computed } from 'vue'
209 import { useLabels } from '@/composables/useLabels'
210 import { countryList } from '@/constants/countries'
211 import IvySelectInput from '@/views/_components/input/IvySelectInput.vue'
212 import IvySelectOption from '@/views/_components/input/IvySelectOption.vue'
213 import type { Field } from '@/types/field'
214 const { getLabel } = useLabels()
215 interface AddressFieldValue {
216 [key: string]: string | undefined
217 fullAddress?: string
218 }
219 interface FieldProps {
220 modelValue: AddressFieldValue
221 field: Field
222 disabled?: boolean
223 error?: string
224 fieldErrors?: Record<string, string>
225 }
226
227 const props = withDefaults(defineProps<FieldProps>(), {
228 modelValue: () => ({}),
229 disabled: false,
230 error: '',
231 fieldErrors: () => ({}),
232 })
233 const emit = defineEmits(['update:modelValue'])
234
235 const isDisabled = computed(() => !!props.disabled || !!props.field.readOnly)
236
237 // Always use the correct order for address fields
238 const addressFieldOrder = ['streetAddress', 'addressLine2', 'city', 'state', 'zip', 'country']
239 const addressFieldTypes = computed(() => {
240 // If the field has addressFieldTypes, reorder to match the required order
241 const types = props.field.addressFieldTypes || [
242 'streetAddress',
243 'addressLine2',
244 'city',
245 'state',
246 'zip',
247 'country',
248 ]
249 // Map to required order, filter out missing, and fill with empty if needed
250 return addressFieldOrder.map((t) => types.find((x) => x === t) || t)
251 })
252
253 const countryOptions = countryList.map((c) => c.name)
254
255 const addressFields = computed(() => {
256 return addressFieldTypes.value.map((fieldType) => {
257 const rawLabel = ((props.field as Record<string, string>)[`${fieldType}Label`] ?? '') as string
258 const isCleared = rawLabel.trim() === ''
259 const explicitHide = (props.field as Record<string, boolean>)[`${fieldType}HideLabel`] || false
260 const hide = explicitHide || isCleared
261 return {
262 type: fieldType,
263 label: rawLabel,
264 placeholder: (props.field as Record<string, string>)[`${fieldType}Placeholder`] || '',
265 fieldID: `ivyforms-field__address-${fieldType}_${(props.field.formId || '').toString()}_${(props.field.fieldIndex || '').toString()}`,
266 hide,
267 }
268 })
269 })
270
271 const isSubRequired = (idx: number): boolean => {
272 const t = addressFields.value[idx]?.type
273 return !!(props.field as Record<string, unknown>)[`${t}Required`]
274 }
275 const getSubDescription = (idx: number): string => {
276 const t = addressFields.value[idx]?.type
277 return ((props.field as Record<string, string>)[`${t}Description`] || '') as string
278 }
279 const getSubfieldError = (fieldType: string): string => {
280 const subKey = `${props.field.type}_${props.field.fieldIndex}.${fieldType}`
281 return props.fieldErrors?.[subKey] || ''
282 }
283 const getFieldValue = (fieldType: string) =>
284 props.modelValue?.[fieldType] ||
285 (fieldType === 'country' ? props.field.countryDefaultValue || '' : '')
286 const updateFieldValue = (fieldType: string, value: string) => {
287 const newModelValue: AddressFieldValue = { ...props.modelValue, [fieldType]: value }
288 emit('update:modelValue', newModelValue)
289 }
290
291 const countryValue = computed({
292 get: () => getFieldValue('country'),
293 set: (val: string) => updateFieldValue('country', val),
294 })
295
296 const getSubVisible = (idx: number): boolean => {
297 const t = addressFields.value[idx]?.type
298 return (props.field as Record<string, boolean>)[`${t}Visible`] !== false
299 }
300 </script>
301
302 <style lang="scss" scoped>
303 .ivyforms-field__address {
304 :deep(.el-form-item__label) {
305 justify-content: flex-start;
306 }
307 &-label {
308 color: var(--map-base-text-0);
309 }
310 &--readonly {
311 opacity: 0.7;
312 :deep(.el-input__wrapper),
313 :deep(.el-input__wrapper:hover),
314 :deep(.el-input__wrapper.is-focus) {
315 border-color: var(--map-base-dusk-stroke--2) !important;
316 box-shadow: none !important;
317 }
318 :deep(input[disabled]]) {
319 cursor: not-allowed;
320 }
321 }
322 }
323 </style>
324