Skip to content

Form Using Schema

This example is using the PrimeVue preset

Source code

vue
<template>
  <Enforma
    :data="data"
    :validator="validator"
    :submit-handler="submitHandler"
    :schema="schema"
  >
  </Enforma>
</template>

<script setup>
import { Enforma } from '@'
import SalaryField from './SalaryField.vue'
import EndDateField from './EndDateField.vue'
import useFormConfig from '../headless/useFormConfig'

const {data, validator, submitHandler} = useFormConfig()

// Define the entire form structure using schema
const schema = {
  section_top: {
    type: 'section',
    class: 'grid grid-cols-2 gap-4',
  },
  name: {
    type: 'field',
    section: 'section_top',
    class: 'col-start-1 col-end-3',
    label: 'Name',
    required: true,
    inputProps: { class: 'w-full' },
  },
  email: {
    type: 'field',
    section: 'section_top',
    class: 'col-start-1 col-end-3',
    label: 'Email',
    required: true,
    inputProps: { class: 'w-full' },
  },
  'address.country': {
    type: 'field',
    section: 'section_top',
    class: 'col-start-1 col-end-2',
    label: 'Country',
    required: true,
    inputProps: { class: 'w-full' },
  },
  'address.city': {
    type: 'field',
    section: 'section_top',
    class: 'col-start-2 col-end-3',
    label: 'City',
    required: true,
    inputProps: { class: 'w-full' },
  },
  willing_to_relocate: {
    type: 'field',
    section: 'section_top',
    class: 'col-start-1 col-end-3 toggle-field',
    label: 'Willing to relocate',
    showLabelNextToInput: true,
    inputComponent: 'toggle',
  },
  salary: {
    type: 'field',
    section: 'section_top',
    class: 'col-start-1 col-end-2',
    label: 'Salary',
    inputComponent: SalaryField,
    inputProps: { class: 'w-full' },
  },
  available_date: {
    type: 'field',
    section: 'section_top',
    class: 'col-start-2 col-end-3',
    label: 'Available date',
    useModelValue: true,
    inputComponent: 'datepicker',
    inputProps: { class: 'w-full', dateFormat: 'yy-mm-dd', fluid: true },
  },
  linkedin_profile: {
    type: 'field',
    section: 'section_top',
    label: 'Linkedin Profile',
    inputProps: { class: 'w-full' },
  },
  personal_site: {
    type: 'field',
    section: 'section_top',
    label: 'Personal site',
    inputProps: { class: 'w-full' },
  },
  skills_section: {
    type: 'section',
    title: 'Skills',
    titleComponent: 'h3',
    titleProps: { class: 'w-full' },
  },
  skills: {
    type: 'repeatable_table',
    section: 'skills_section',
    class: 'mb-4',
    subfields: {
      name: {
        label: 'Skill',
        inputComponent: 'input',
        inputProps: { fluid: true },
      },
      level: {
        label: 'Level',
        inputComponent: 'select',
        inputProps: {
          fluid: true,
          options: ['Beginner', 'Intermediate', 'Advanced', 'Expert'],
        },
      },
    },
  },
  experience_section: {
    type: 'section',
    title: 'Experience',
    titleComponent: 'h3',
    titleProps: { class: 'w-full' },
  },
  experience: {
    type: 'repeatable',
    section: 'experience_section',
    class: 'mb-4 form-repeatable-experience',
    subfields: {
      company: {
        label: 'Company',
        wrapperProps: { class: 'col-start-1 col-end-2' },
        inputProps: { fluid: true },
      },
      position: {
        label: 'Position',
        wrapperProps: { class: 'col-start-2 col-end-3' },
        inputProps: { fluid: true },
      },
      start: {
        label: 'Start',
        useModelValue: true,
        inputComponent: 'datepicker',
        inputProps: {
          dateFormat: 'yy-mm-dd',
          fluid: true,
        },
      },
      end: {
        label: 'End',
        useModelValue: true,
        inputComponent: EndDateField,
        inputProps: { fluid: true },
      },
    },
  }
}
</script>
ts
import { createEncolaValidator } from '../../../src/validators/encolaValidator'

// this file contains a composable function to be reused for the examples
function getData() {
  const data = {
    name: "",
    email: '',
    address: {
      country: '',
      city: '',
    },
    available_date: null,
    willing_to_relocate: false,
    skills: [],
    experience: [],
  }
  for (let i = 0; i < 3; i++) {
    const level = ['Beginner', 'Intermediate', 'Advanced', 'Expert'].sort(() => Math.random() - 0.5)[0]
    data.skills.push({ name: `Skill ${i + 1}`, level })
  }
  for (let i = 0; i < 2; i++) {
    const level = ['Beginner', 'Intermediate', 'Advanced', 'Expert'].sort(() => Math.random() - 0.5)[0]
    data.experience.push({
      company: `Company ${i + 1}`,
      position: `Job title ${i + 1}`,
      start: new Date(`202${5 - i}-01-01`),
      end: i === 0 ? null : new Date(`202${5 - 1}-12-31`),
      current: i === 0
    })
  }
  return data
}

// this validator is based on Encola Validator
// but it can be replaced with a validator that uses Zod, Valibot or Yup
const validator = createEncolaValidator(
  {
    name: 'required',
    email: 'required|email',
    'salary.min': 'number',
    'salary.max': 'number|gt:@salary.min',
    'available_date': 'required|date:yy-mm-dd|date_after:' + (new Date().toISOString().split('T')[0]),
    'address.city': 'required',
    'address.country': 'required',
    'linkedin_profile': 'required|url',
    'personal_site': 'url',
    'skills.*.name': 'required',
    'skills.*.level': 'required',
    'experience.*.company': 'required',
    'experience.*.position': 'required',
    'experience.*.start': 'required|date:yy-mm-dd',
    'experience.*.end': 'required_when:@experience.*.current,false|date:yy-mm-dd',
  },
  {
    'name:required': 'You gotta have a name',
    'salary.max:gt': 'The max should be greater than the min'
  }
)

// For backwards compatibility, also export rules and messages separately
const rules = {
  name: 'required',
  email: {
    'required|email': true
  }
}

const messages = {
  email: {
    'required': 'Email is required',
    'email': 'Please enter a valid email'
  }
}

const submitHandler = (formData) => {
  return new Promise((resolve) => {
    setTimeout(() => {
      alert('Data sent to server: ' + JSON.stringify(formData))
      resolve(true)
    }, 2000)
  })
}

export default function () {
  return {
    data: getData(),
    validator,
    submitHandler
  }
}

Released under the MIT License