Skip to content

Address Picker

This recipe shows how you can go about implementing an address selector with add and edit functionality

Source code

vue
<template>
  <div class="flex gap-2">
    <Select
      class="flex-grow-1"
      v-model="selectedAddress"
      :options="selectOptions"
      placeholder="Select address."
      @focus="emit('focus')"
      @blur="emit('blur')"
      @update:modelValue="emit('change', {value: selectedAddress})">
      <template #value="{value, placeholder}">
        {{getAddressAsText(value) || placeholder}}
      </template>
      <template #option="{option}">
        {{getAddressAsText(option)}}
      </template>
    </Select>
    <Button
      severity="secondary"
      label="New address"
      @click="showAddressModal = true"
    />
  </div>
  <Dialog
    v-model:visible="showAddressModal"
    modal
    header="Add New Address" :style="{ width: '25rem' }">
    <Enforma
      ref="modalForm"
      :data="newAddress"
      :schema="addressSchema"
      :validator="addressValidator"
      :show-submit-button="false"
      :submit-handler="addAddress"
      >
    </Enforma>
    <div class="flex justify-end gap-2">
      <Button type="button" label="Cancel" severity="secondary" @click="showAddressModal = false"></Button>
      <Button type="button" label="Add address" @click="modalForm?.submit"></Button>
    </div>
  </Dialog>
</template>

<script setup>
import { Select, Dialog, Button } from 'primevue'
import { Enforma } from '../../../src/'
import { createEncolaValidator } from '../../../src/validators/encolaValidator'
import { defineProps, defineEmits, ref } from 'vue'

const props = defineProps({
  value: {type: Object, required: false, default: null},
  options: { type: Array, default: [] },
})

/**
 * - `change` and `input` are implemented because
 *   we try to keep close to the browser events
 * - `focus` and `blur` are implemented because
 *   they are attached to by default be Enforma
 * - `new_address` is a custom event that you might hook into
 *   to update the list of addresses inside the form
 */
const emit = defineEmits('change', 'input', 'blur', 'focus', 'new_address')

// internal model
const selectedAddress = ref(props.value)

/**
 * internal options for the address Select component
 * alternatively you could use the @new_address event to update the address list
 * inside the form (for example if the address list is reused by another component)
 */
const selectOptions = ref([...props.options])

const showAddressModal = ref(false)

function getAddressAsText(address) {
  if (!address || !address.address) {
    return null
  }

  return Object.values(address).filter((i) => i).join(', ')
}

const addressSchema = {
  address: {
    label: 'Address (street, building)',
    inputProps: {
      class: 'w-full',
    }
  },
  city: {
    label: 'City',
    inputProps: {
      class: 'w-full',
    }
  },
  state: {
    label: 'State',
    inputComponent: 'select',
    inputProps: {
      class: 'w-full',
      placeholder: 'Select...',
      options: ['AB', 'CA', 'WA']
    }
  },
  zip: {
    label: 'Zip',
    inputProps: {
      type: 'number',
      class: 'w-full',
    }
  },
}

// reference for the form inside the modal
const modalForm = ref(null)

// initial data for the form
let newAddress = {}

// validation rules
const addressValidator = createEncolaValidator({
  address: 'required',
  city: 'required',
  state: 'required',
  zip: 'required',
})

// submit handler called if validation passes
function addAddress(address) {
  address.country = 'USA'
  selectOptions.value.push(address)
  // emit `new_address` event
  emit('new_address', address)
  // change value in <Select>
  selectedAddress.value = address
  // this is needed to communicate the value has changed
  // as the previous line is not enough
  emit('change', {value: selectedAddress})
  // hide the modal
  showAddressModal.value = false
  // reset form data
  newAddress = {}
}

</script>

<style>
.enforma-field-wrapper {
  margin-bottom: 0.5rem;

}
</style>
vue
<template>
  <Enforma
    ref="formRef"
    :data="data"
    :validator="validator"
    :submit-handler="submitHandler"
  >
    <EnformaField
      class="col-start-1 col-end-3 mb-4"
      name="name"
      required
      label="Name"
      :input-props="{class: 'w-full'}"
    />
    <EnformaField
      class="col-start-1 col-end-3 mb-4"
      name="shipping_address"
      required
      label="Shipping Address"
      :input-component="AddressPicker"
      :input-props="{class: 'w-full', options: addresses}"
    />
  </Enforma>
</template>

<script setup>
import { Enforma, EnformaField } from '@'
import { createEncolaValidator } from '@/validators/encolaValidator'
import AddressPicker from './AddressPicker.vue'

const addresses = [
  {
    address: "123 Maple Street",
    city: "Springfield",
    state: "IL",
    zip: "62704",
    country: "USA"
  },
  {
    address: "456 Oak Avenue",
    city: "Boulder",
    state: "CO",
    zip: "80302",
    country: "USA"
  },
  {
    address: "789 Pine Road",
    city: "Orlando",
    state: "FL",
    zip: "32801",
    country: "USA"
  }
]

const data = {
  name: null,
  shipping_address: null
}

const validator = createEncolaValidator({
  name: 'required',
  shipping_address: 'required'
})

function submitHandler(formData) {
  alert('Form data sent to server: ' + JSON.stringify(formData))
}


</script>

Released under the MIT License