37 KiB
37 KiB
# Form Component Documentation
## Overview
A highly flexible and configurable dynamic form component built with **PrimeVue**. This component generates forms based on field configuration objects and supports various input types including **AutoComplete with custom values**, validation, responsive layouts, and both controlled and uncontrolled form state management.
## ✨ New Features (PrimeVue Migration)
- **AutoComplete component** - Users can select from suggestions OR enter completely custom values
- **Enhanced Date/Time Pickers** - Comprehensive date handling with multiple formats, time selection, constraints, and smart defaults
- **Better accessibility** with ARIA support
- **More flexible styling** with CSS custom properties
- **Enhanced mobile responsiveness** with CSS Grid
## Basic Usage
```vue
<template>
<Form
:fields="formFields"
:form-data="formData"
@submit="handleSubmit"
@change="handleFieldChange"
/>
</template>
<script setup>
import { ref } from "vue";
import Form from "./components/common/Form.vue";
const formData = ref({});
const formFields = [
{
name: "firstName",
label: "First Name",
type: "text",
required: true,
cols: 6,
},
{
name: "email",
label: "Email",
type: "text",
format: "email",
required: true,
cols: 6,
},
// NEW: AutoComplete with custom values!
{
name: "country",
label: "Country",
type: "autocomplete",
placeholder: "Type or select a country",
options: [
{ label: "United States", value: "US" },
{ label: "Canada", value: "CA" },
{ label: "United Kingdom", value: "UK" },
],
optionLabel: "label",
optionValue: "value",
forceSelection: false, // Allows custom entries!
cols: 6,
helpText: "You can select from the list or enter a custom country",
},
{
name: "bio",
label: "Biography",
type: "textarea",
rows: 4,
cols: 12,
},
];
const handleSubmit = (data) => {
console.log("Form submitted:", data);
};
const handleFieldChange = (event) => {
console.log("Field changed:", event.fieldName, event.value);
};
</script>
```
## Props
### `fields` (Array) - Required
- **Description:** Array of field configuration objects that define the form structure
- **Type:** `Array<Object>`
- **Required:** `true`
### `formData` (Object)
- **Description:** External form data object for controlled form state
- **Type:** `Object`
- **Default:** `null`
- **Note:** When provided, the form operates in controlled mode. When null, uses internal state.
### `onChange` (Function)
- **Description:** Global change handler function called when any field changes
- **Type:** `Function`
- **Signature:** `(fieldName: string, value: any, formData: Object) => void`
### `onSubmit` (Function)
- **Description:** Submit handler function called when form is submitted
- **Type:** `Function`
- **Signature:** `(formData: Object) => Promise<void> | void`
### `showSubmitButton` (Boolean)
- **Description:** Controls visibility of the submit button
- **Type:** `Boolean`
- **Default:** `true`
### `showCancelButton` (Boolean)
- **Description:** Controls visibility of the cancel button
- **Type:** `Boolean`
- **Default:** `false`
### `submitButtonText` (String)
- **Description:** Text displayed on the submit button
- **Type:** `String`
- **Default:** `'Submit'`
### `cancelButtonText` (String)
- **Description:** Text displayed on the cancel button
- **Type:** `String`
- **Default:** `'Cancel'`
### `validateOnChange` (Boolean)
- **Description:** Enables real-time validation as fields change
- **Type:** `Boolean`
- **Default:** `true`
## Field Configuration
Each field object in the `fields` array supports the following properties:
### Basic Properties
- **`name`** (String, required) - Unique identifier for the field
- **`label`** (String, required) - Display label for the field
- **`type`** (String, required) - Field input type
- **`required`** (Boolean, default: `false`) - Makes the field mandatory
- **`disabled`** (Boolean, default: `false`) - Disables the field
- **`readonly`** (Boolean, default: `false`) - Makes the field read-only
- **`placeholder`** (String) - Placeholder text for input fields
- **`helpText`** (String) - Help text displayed below the field
- **`defaultValue`** (Any) - Initial value for the field
### Layout Properties
- **`cols`** (Number, default: `12`) - Column width on extra small screens
- **`sm`** (Number, default: `12`) - Column width on small screens
- **`md`** (Number, default: `6`) - Column width on medium screens
- **`lg`** (Number, default: `6`) - Column width on large screens
### Validation Properties
- **`validate`** (Function) - Custom validation function
- **Signature:** `(value: any) => string | null`
- **Returns:** Error message string or null if valid
### Field-Specific Properties
- **`onChangeOverride`** (Function) - Field-specific change handler that overrides global onChange
- **Signature:** `(value: any, fieldName: string, formData: Object) => void`
## Field Types
### Text Input (`type: 'text'`)
Standard text input field with optional format validation.
```javascript
{
name: 'username',
label: 'Username',
type: 'text',
required: true,
placeholder: 'Enter your username'
}
```
**Additional Properties:**
- **`format`** (String) - Input format validation (`'email'` for email validation)
### Number Input (`type: 'number'`)
Numeric input field with optional min/max constraints.
```javascript
{
name: 'age',
label: 'Age',
type: 'number',
min: 0,
max: 120,
step: 1
}
```
**Additional Properties:**
- **`min`** (Number) - Minimum allowed value
- **`max`** (Number) - Maximum allowed value
- **`step`** (Number) - Step increment for the input
### Textarea (`type: 'textarea'`)
Multi-line text input for longer content.
```javascript
{
name: 'description',
label: 'Description',
type: 'textarea',
rows: 4,
placeholder: 'Enter description...'
}
```
**Additional Properties:**
- **`rows`** (Number, default: `3`) - Number of visible text lines
### Select Dropdown (`type: 'select'`)
Dropdown selection field with predefined options and built-in filtering.
```javascript
{
name: 'country',
label: 'Country',
type: 'select',
required: true,
options: [
{ label: 'United States', value: 'us' },
{ label: 'Canada', value: 'ca' },
{ label: 'Mexico', value: 'mx' }
],
filter: true, // Enable search filtering
showClear: true // Allow clearing selection
}
```
**Additional Properties:**
- **`options`** (Array, required) - Array of option objects with `label` and `value` properties
- **`optionLabel`** (String, default: `'label'`) - Property name for display text
- **`optionValue`** (String, default: `'value'`) - Property name for value
- **`filter`** (Boolean, default: `true`) - Enable filtering/search
- **`showClear`** (Boolean, default: `true`) - Show clear button
### ⭐ AutoComplete (`type: 'autocomplete'`) - NEW!
**The game-changer!** AutoComplete field that allows users to select from suggestions OR enter completely custom values not in the predefined list.
```javascript
// Basic AutoComplete - allows custom entries
{
name: 'skills',
label: 'Skills',
type: 'autocomplete',
placeholder: 'Add your skills',
options: [
{ label: 'JavaScript', value: 'js' },
{ label: 'Python', value: 'python' },
{ label: 'Vue.js', value: 'vue' }
],
optionLabel: 'label',
optionValue: 'value',
forceSelection: false, // KEY: Allows custom values!
multiple: true, // Multiple selection
helpText: 'Select existing skills or add your own custom skills'
}
// Strict selection (only predefined options)
{
name: 'department',
label: 'Department',
type: 'autocomplete',
options: [
{ label: 'Engineering', value: 'eng' },
{ label: 'Marketing', value: 'mkt' },
{ label: 'Sales', value: 'sales' }
],
forceSelection: true, // Only allows predefined options
dropdown: true, // Show dropdown button
required: true
}
// Dynamic/Remote data loading
{
name: 'users',
label: 'Select Users',
type: 'autocomplete',
options: [], // Initially empty
onSearch: async (query, callback) => {
if (query.length > 2) {
const response = await fetch(`/api/users?search=${query}`);
const users = await response.json();
callback(users); // Update options dynamically
}
}
}
```
**Additional Properties:**
- **`options`** (Array) - Array of suggestion objects
- **`optionLabel`** (String, default: `'label'`) - Property for display text
- **`optionValue`** (String, default: `'value'`) - Property for value
- **`forceSelection`** (Boolean, default: `false`) - If true, only allows predefined options. If false, allows custom values!
- **`multiple`** (Boolean, default: `false`) - Allow multiple selections
- **`dropdown`** (Boolean, default: `true`) - Show dropdown button
- **`onSearch`** (Function) - Custom search function for dynamic data loading
- **Signature:** `(query: string, callback: Function) => void`
### Checkbox (`type: 'checkbox'`)
Boolean checkbox input.
```javascript
{
name: 'subscribe',
label: 'Subscribe to newsletter',
type: 'checkbox',
defaultValue: false
}
```
### Radio Group (`type: 'radio'`)
Radio button group for single selection from multiple options.
```javascript
{
name: 'gender',
label: 'Gender',
type: 'radio',
required: true,
options: [
{ label: 'Male', value: 'male' },
{ label: 'Female', value: 'female' },
{ label: 'Other', value: 'other' }
]
}
```
**Additional Properties:**
- **`options`** (Array, required) - Array of option objects with `label` and `value` properties
### Date Input (`type: 'date'`)
Enhanced date picker input field with comprehensive formatting and configuration options.
```javascript
// Basic date input
{
name: 'birthDate',
label: 'Birth Date',
type: 'date',
required: true,
}
// Date with custom format
{
name: 'eventDate',
label: 'Event Date',
type: 'date',
format: 'YYYY-MM-DD', // or 'mm/dd/yyyy', 'dd/mm/yyyy', etc.
required: true,
placeholder: 'Select event date'
}
// Date with time picker
{
name: 'appointmentDateTime',
label: 'Appointment Date & Time',
type: 'date',
showTime: true,
hourFormat: '12', // '12' or '24'
required: true,
defaultToNow: true, // Set to current date/time by default
}
// Time-only picker
{
name: 'preferredTime',
label: 'Preferred Time',
type: 'date',
timeOnly: true,
hourFormat: '12',
stepMinute: 15, // 15-minute intervals
defaultValue: 'now'
}
// Advanced date configuration
{
name: 'projectDeadline',
label: 'Project Deadline',
type: 'date',
format: 'dd/mm/yyyy',
minDate: 'today', // Can't select past dates
maxDate: '2025-12-31', // Maximum date
defaultToToday: true,
showButtonBar: true,
yearNavigator: true,
monthNavigator: true,
yearRange: '2024:2030',
helpText: 'Select a deadline for the project completion'
}
// Inline date picker (always visible)
{
name: 'calendarDate',
label: 'Calendar',
type: 'date',
inline: true,
view: 'date', // 'date', 'month', 'year'
showWeek: true,
defaultValue: 'today'
}
```
**Additional Properties:**
- **`format`** (String) - Date format: `'YYYY-MM-DD'`, `'mm/dd/yyyy'`, `'dd/mm/yyyy'`, `'dd-mm-yyyy'`, `'mm-dd-yyyy'`
- **`dateFormat`** (String) - PrimeVue-specific format string (overrides `format`)
- **`showTime`** (Boolean, default: `false`) - Include time picker
- **`timeOnly`** (Boolean, default: `false`) - Show only time picker (no date)
- **`hourFormat`** (String, default: `'24'`) - Hour format: `'12'` or `'24'`
- **`stepHour`** (Number, default: `1`) - Hour step increment
- **`stepMinute`** (Number, default: `1`) - Minute step increment
- **`showSeconds`** (Boolean, default: `false`) - Show seconds in time picker
- **`stepSecond`** (Number, default: `1`) - Second step increment
- **`minDate`** (String|Date) - Minimum selectable date
- **`maxDate`** (String|Date) - Maximum selectable date
- **`defaultToToday`** (Boolean, default: `false`) - Set default to today's date
- **`defaultToNow`** (Boolean, default: `false`) - Set default to current date/time
- **`showButtonBar`** (Boolean, default: `true`) - Show today/clear buttons
- **`todayButtonLabel`** (String, default: `'Today'`) - Today button text
- **`clearButtonLabel`** (String, default: `'Clear'`) - Clear button text
- **`showWeek`** (Boolean, default: `false`) - Show week numbers
- **`manualInput`** (Boolean, default: `true`) - Allow manual date entry
- **`yearNavigator`** (Boolean, default: `false`) - Show year dropdown
- **`monthNavigator`** (Boolean, default: `false`) - Show month dropdown
- **`yearRange`** (String, default: `'1900:2100'`) - Available year range
- **`inline`** (Boolean, default: `false`) - Display picker inline (always visible)
- **`view`** (String, default: `'date'`) - Default view: `'date'`, `'month'`, `'year'`
- **`touchUI`** (Boolean, default: `false`) - Optimize for touch devices
- **`onDateChange`** (Function) - Custom date change handler
- **Signature:** `(dateValue: Date) => any`
**Default Value Options:**
```javascript
// String values
defaultValue: "today"; // Set to today's date
defaultValue: "now"; // Set to current date/time
defaultValue: "2024-12-25"; // Specific date string
// Boolean flags
defaultToToday: true; // Set to today (date only)
defaultToNow: true; // Set to current date/time
// Date object
defaultValue: new Date(); // Specific Date object
```
### DateTime Input (`type: 'datetime'`) - LEGACY
**⚠️ DEPRECATED:** Use `type: 'date'` with `showTime: true` instead.
Legacy date and time picker input field. This is maintained for backward compatibility.
```javascript
// LEGACY - Use date with showTime instead
{
name: 'appointmentTime',
label: 'Appointment Time',
type: 'datetime',
required: true
}
// RECOMMENDED - Use this instead
{
name: 'appointmentTime',
label: 'Appointment Time',
type: 'date',
showTime: true,
required: true
}
```
**Additional Properties:**
- **`min`** (String) - Minimum allowed datetime
- **`max`** (String) - Maximum allowed datetime
- **`hourFormat`** (String, default: `'24'`) - Hour format: `'12'` or `'24'`
### File Input (`type: 'file'`)
File upload input field.
```javascript
{
name: 'resume',
label: 'Resume',
type: 'file',
accept: '.pdf,.doc,.docx',
multiple: false
}
```
**Additional Properties:**
- **`accept`** (String) - File types to accept (MIME types or file extensions)
- **`multiple`** (Boolean, default: `false`) - Allow multiple file selection
## Events
### `update:formData`
- **Description:** Emitted when form data changes (controlled mode only)
- **Payload:** Updated form data object
- **Usage:** `@update:formData="handleFormDataUpdate"`
### `submit`
- **Description:** Emitted when form is successfully submitted
- **Payload:** Form data object
- **Usage:** `@submit="handleSubmit"`
### `cancel`
- **Description:** Emitted when cancel button is clicked
- **Usage:** `@cancel="handleCancel"`
### `change`
- **Description:** Emitted when any field value changes
- **Payload:** Object with `fieldName`, `value`, and `formData` properties
- **Usage:** `@change="handleFieldChange"`
## Exposed Methods
The component exposes several methods that can be accessed via template refs:
```vue
<template>
<Form ref="formRef" :fields="fields" />
</template>
<script setup>
import { ref } from "vue";
const formRef = ref(null);
// Access exposed methods
const validateForm = () => formRef.value.validateForm();
const resetForm = () => formRef.value.resetForm();
</script>
```
### `validateForm()`
- **Description:** Validates the entire form and returns validation status
- **Returns:** `Boolean` - `true` if valid, `false` if invalid
- **Side Effect:** Updates form error state
### `getCurrentFormData()`
- **Description:** Gets the current form data object
- **Returns:** `Object` - Current form data
### `resetForm()`
- **Description:** Resets form to initial state and clears all errors
- **Returns:** `void`
### `setFieldError(fieldName, error)`
- **Description:** Sets an error message for a specific field
- **Parameters:**
- `fieldName` (String) - The field name
- `error` (String) - Error message to display
### `clearFieldError(fieldName)`
- **Description:** Clears the error for a specific field
- **Parameters:**
- `fieldName` (String) - The field name to clear
## Usage Examples
### Basic Contact Form
```vue
<script setup>
import { ref } from "vue";
const formData = ref({});
const contactFields = [
{
name: "firstName",
label: "First Name",
type: "text",
required: true,
cols: 12,
md: 6,
},
{
name: "lastName",
label: "Last Name",
type: "text",
required: true,
cols: 12,
md: 6,
},
{
name: "email",
label: "Email",
type: "text",
format: "email",
required: true,
cols: 12,
md: 6,
},
{
name: "preferredContactDate",
label: "Preferred Contact Date",
type: "date",
format: "mm/dd/yyyy",
minDate: "today",
defaultToToday: true,
cols: 12,
md: 6,
helpText: "When would you like us to contact you?",
},
{
name: "message",
label: "Message",
type: "textarea",
rows: 5,
required: true,
cols: 12,
},
];
const handleSubmit = async (data) => {
try {
await sendContactForm(data);
alert("Form submitted successfully!");
} catch (error) {
console.error("Submission failed:", error);
}
};
</script>
<template>
<Form
:fields="contactFields"
v-model:form-data="formData"
@submit="handleSubmit"
submit-button-text="Send Message"
/>
</template>
```
### AutoComplete Examples
```vue
<script setup>
import { ref } from "vue";
const formData = ref({});
// Sample data
const countries = [
{ label: "United States", value: "US" },
{ label: "Canada", value: "CA" },
{ label: "United Kingdom", value: "UK" },
{ label: "Germany", value: "DE" },
{ label: "France", value: "FR" },
];
const skills = [
{ label: "JavaScript", value: "javascript" },
{ label: "Python", value: "python" },
{ label: "Vue.js", value: "vue" },
{ label: "React", value: "react" },
{ label: "Node.js", value: "node" },
];
const autoCompleteFields = [
// Basic AutoComplete - allows custom countries
{
name: "country",
label: "Country",
type: "autocomplete",
placeholder: "Type or select a country",
options: countries,
optionLabel: "label",
optionValue: "value",
forceSelection: false, // Users can enter "Custom Country"
required: true,
cols: 6,
helpText: "Select from list or enter any country name",
},
// Multiple selection with custom values
{
name: "skills",
label: "Technical Skills",
type: "autocomplete",
placeholder: "Add your skills",
options: skills,
optionLabel: "label",
optionValue: "value",
multiple: true,
forceSelection: false, // Users can add custom skills like "Machine Learning"
cols: 6,
helpText: "Select existing skills or add custom ones",
},
// Strict selection - only predefined options
{
name: "department",
label: "Department",
type: "autocomplete",
options: [
{ label: "Engineering", value: "eng" },
{ label: "Marketing", value: "mkt" },
{ label: "Sales", value: "sales" },
{ label: "HR", value: "hr" },
],
forceSelection: true, // Only allows company departments
dropdown: true,
required: true,
cols: 6,
},
// Dynamic search with API calls
{
name: "users",
label: "Assign to User",
type: "autocomplete",
placeholder: "Search users...",
options: [],
optionLabel: "name",
optionValue: "id",
onSearch: async (query, callback) => {
if (query.length > 2) {
try {
const response = await fetch(`/api/users?search=${query}`);
const users = await response.json();
callback(users);
} catch (error) {
console.error("Search failed:", error);
callback([]);
}
} else {
callback([]);
}
},
cols: 6,
helpText: "Type at least 3 characters to search",
},
];
</script>
<template>
<Form
:fields="autoCompleteFields"
v-model:form-data="formData"
@submit="handleSubmit"
submit-button-text="Save"
/>
<!-- Show current values -->
<div v-if="Object.keys(formData).length" class="mt-4">
<h3>Current Values:</h3>
<pre>{{ JSON.stringify(formData, null, 2) }}</pre>
</div>
</template>
```
### Enhanced Date Picker Examples
```vue
<script setup>
import { ref } from "vue";
const formData = ref({});
const dateFields = [
// Basic date picker
{
name: "birthDate",
label: "Birth Date",
type: "date",
format: "mm/dd/yyyy",
required: true,
cols: 6,
},
// Date with time - appointment scheduling
{
name: "appointmentDateTime",
label: "Appointment Date & Time",
type: "date",
showTime: true,
hourFormat: "12",
stepMinute: 15,
defaultToNow: true,
minDate: "today",
cols: 6,
helpText: "Select appointment date and time",
},
// Project deadline with constraints
{
name: "projectDeadline",
label: "Project Deadline",
type: "date",
format: "YYYY-MM-DD",
minDate: "today",
maxDate: "2025-12-31",
defaultToToday: true,
yearNavigator: true,
monthNavigator: true,
required: true,
cols: 6,
helpText: "Deadline cannot be in the past",
},
// Time-only picker for preferences
{
name: "preferredTime",
label: "Preferred Contact Time",
type: "date",
timeOnly: true,
hourFormat: "12",
stepMinute: 30,
defaultValue: "09:00",
cols: 6,
helpText: "Best time to contact you",
},
// Event date with week numbers
{
name: "eventDate",
label: "Event Date",
type: "date",
format: "dd/mm/yyyy",
showWeek: true,
yearNavigator: true,
monthNavigator: true,
yearRange: "2024:2026",
cols: 6,
},
// Inline calendar for scheduling
{
name: "availableDate",
label: "Available Dates",
type: "date",
inline: true,
multiple: true, // Note: This would require custom implementation
showButtonBar: true,
cols: 12,
helpText: "Click dates when you are available",
},
];
const handleSubmit = (data) => {
console.log("Date form submitted:", data);
// Example of working with date values
if (data.appointmentDateTime) {
console.log(
"Appointment scheduled for:",
data.appointmentDateTime.toLocaleString(),
);
}
if (data.projectDeadline) {
const deadline = new Date(data.projectDeadline);
const daysUntilDeadline = Math.ceil(
(deadline - new Date()) / (1000 * 60 * 60 * 24),
);
console.log(`Days until deadline: ${daysUntilDeadline}`);
}
};
</script>
<template>
<Form
:fields="dateFields"
v-model:form-data="formData"
@submit="handleSubmit"
submit-button-text="Schedule"
/>
<!-- Display current values -->
<div v-if="Object.keys(formData).length" class="mt-4">
<h3>Selected Dates:</h3>
<div v-for="(value, key) in formData" :key="key" class="mb-2">
<strong>{{ key }}:</strong>
<span v-if="value instanceof Date">
{{ value.toLocaleString() }}
</span>
<span v-else>{{ value }}</span>
</div>
</div>
</template>
```
### Real-World Date Picker Scenarios
```vue
<script setup>
import { ref } from "vue";
const bookingData = ref({});
// Hotel booking form with date constraints
const hotelBookingFields = [
{
name: "checkIn",
label: "Check-in Date",
type: "date",
format: "mm/dd/yyyy",
minDate: "today",
defaultToToday: true,
required: true,
cols: 6,
onDateChange: (date) => {
// Auto-update checkout date to be at least 1 day later
if (bookingData.value.checkOut && bookingData.value.checkOut <= date) {
const nextDay = new Date(date);
nextDay.setDate(nextDay.getDate() + 1);
bookingData.value.checkOut = nextDay;
}
return date;
},
},
{
name: "checkOut",
label: "Check-out Date",
type: "date",
format: "mm/dd/yyyy",
minDate: "today",
required: true,
cols: 6,
validate: (value) => {
if (
value &&
bookingData.value.checkIn &&
value <= bookingData.value.checkIn
) {
return "Check-out date must be after check-in date";
}
return null;
},
},
];
// Meeting scheduler with business hours
const meetingFields = [
{
name: "meetingDate",
label: "Meeting Date",
type: "date",
format: "dd/mm/yyyy",
minDate: "today",
// Exclude weekends
validate: (value) => {
if (value) {
const day = value.getDay();
if (day === 0 || day === 6) {
return "Please select a weekday";
}
}
return null;
},
cols: 6,
},
{
name: "meetingTime",
label: "Meeting Time",
type: "date",
timeOnly: true,
hourFormat: "12",
stepMinute: 30,
// Business hours only: 9 AM to 5 PM
validate: (value) => {
if (value) {
const hours = value.getHours();
if (hours < 9 || hours >= 17) {
return "Please select a time between 9:00 AM and 5:00 PM";
}
}
return null;
},
cols: 6,
},
];
// Birth date with age calculation
const personalInfoFields = [
{
name: "birthDate",
label: "Date of Birth",
type: "date",
format: "mm/dd/yyyy",
maxDate: "today",
yearNavigator: true,
yearRange: "1920:2010",
required: true,
onDateChange: (date) => {
// Calculate and display age
if (date) {
const today = new Date();
const age = today.getFullYear() - date.getFullYear();
const monthDiff = today.getMonth() - date.getMonth();
if (
monthDiff < 0 ||
(monthDiff === 0 && today.getDate() < date.getDate())
) {
age--;
}
console.log(`Age: ${age} years`);
}
return date;
},
cols: 12,
},
];
</script>
<template>
<div>
<h3>Hotel Booking</h3>
<Form :fields="hotelBookingFields" v-model:form-data="bookingData" />
<h3 class="mt-6">Meeting Scheduler</h3>
<Form :fields="meetingFields" />
<h3 class="mt-6">Personal Information</h3>
<Form :fields="personalInfoFields" />
</div>
</template>
```
```vue
```
### User Registration Form
```vue
<script setup>
import { ref } from "vue";
const registrationData = ref({});
const registrationFields = [
{
name: "username",
label: "Username",
type: "text",
required: true,
validate: (value) => {
if (value && value.length < 3) {
return "Username must be at least 3 characters";
}
return null;
},
},
{
name: "email",
label: "Email",
type: "text",
format: "email",
required: true,
},
{
name: "age",
label: "Age",
type: "number",
min: 18,
max: 100,
required: true,
},
// UPDATED: Using AutoComplete instead of Select
{
name: "country",
label: "Country",
type: "autocomplete", // Changed from 'select' to 'autocomplete'
placeholder: "Type or select your country",
required: true,
options: [
{ label: "United States", value: "us" },
{ label: "Canada", value: "ca" },
{ label: "United Kingdom", value: "uk" },
{ label: "Australia", value: "au" },
{ label: "Germany", value: "de" },
],
optionLabel: "label",
optionValue: "value",
forceSelection: false, // Allows users to enter unlisted countries
helpText: "Select from common countries or type your own",
},
{
name: "terms",
label: "I agree to the terms and conditions",
type: "checkbox",
required: true,
validate: (value) => {
if (!value) {
return "You must agree to the terms and conditions";
}
return null;
},
},
];
</script>
<template>
<Form
:fields="registrationFields"
v-model:form-data="registrationData"
@submit="handleRegistration"
submit-button-text="Register"
show-cancel-button
@cancel="handleCancel"
/>
</template>
```
### Survey Form with Custom Validation
```vue
<script setup>
import { ref } from "vue";
const surveyData = ref({});
const surveyFields = [
{
name: "satisfaction",
label: "How satisfied are you with our service?",
type: "radio",
required: true,
options: [
{ label: "Very Satisfied", value: "5" },
{ label: "Satisfied", value: "4" },
{ label: "Neutral", value: "3" },
{ label: "Dissatisfied", value: "2" },
{ label: "Very Dissatisfied", value: "1" },
],
cols: 12,
},
{
name: "feedback",
label: "Additional Feedback",
type: "textarea",
rows: 4,
placeholder: "Please share your thoughts...",
cols: 12,
onChangeOverride: (value, fieldName, formData) => {
// Custom logic for this field only
console.log(`Feedback length: ${value?.length || 0} characters`);
},
},
{
name: "recommend",
label: "Would you recommend us to others?",
type: "checkbox",
cols: 12,
},
];
const handleSurveySubmit = (data) => {
console.log("Survey submitted:", data);
};
const handleFieldChange = (event) => {
console.log("Global change handler:", event);
};
</script>
<template>
<Form
:fields="surveyFields"
v-model:form-data="surveyData"
@submit="handleSurveySubmit"
@change="handleFieldChange"
submit-button-text="Submit Survey"
/>
</template>
```
### File Upload Form
```vue
<script setup>
import { ref } from "vue";
const uploadData = ref({});
const uploadFields = [
{
name: "title",
label: "Document Title",
type: "text",
required: true,
cols: 12,
},
{
name: "category",
label: "Category",
type: "select",
required: true,
options: [
{ label: "Reports", value: "reports" },
{ label: "Presentations", value: "presentations" },
{ label: "Documents", value: "documents" },
],
cols: 12,
md: 6,
},
{
name: "uploadDate",
label: "Upload Date",
type: "date",
required: true,
cols: 12,
md: 6,
},
{
name: "files",
label: "Select Files",
type: "file",
accept: ".pdf,.doc,.docx,.ppt,.pptx",
multiple: true,
required: true,
cols: 12,
},
{
name: "description",
label: "Description",
type: "textarea",
rows: 3,
helpText: "Optional description of the uploaded files",
cols: 12,
},
];
</script>
<template>
<Form
:fields="uploadFields"
v-model:form-data="uploadData"
@submit="handleFileUpload"
submit-button-text="Upload Files"
/>
</template>
```
## Form State Management
### Controlled Mode (External Form Data)
When you provide a `formData` prop, the component operates in controlled mode:
```vue
<script setup>
import { ref } from "vue";
// External form state
const formData = ref({
name: "John Doe",
email: "john@example.com",
});
</script>
<template>
<Form :fields="fields" v-model:form-data="formData" @submit="handleSubmit" />
</template>
```
### Uncontrolled Mode (Internal Form Data)
When no `formData` is provided, the component manages its own internal state:
```vue
<template>
<Form :fields="fields" @submit="handleSubmit" />
</template>
<script setup>
const handleSubmit = (data) => {
// Data is passed to the submit handler
console.log("Form data:", data);
};
</script>
```
## Validation
### Built-in Validation
- **Required fields** - Validates that required fields are not empty
- **Email format** - Validates email format when `format: 'email'` is used
- **Number ranges** - Validates min/max values for number fields
### Custom Validation
Each field can have a custom validation function:
```javascript
{
name: 'password',
label: 'Password',
type: 'text',
required: true,
validate: (value) => {
if (value && value.length < 8) {
return 'Password must be at least 8 characters long'
}
if (value && !/(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/.test(value)) {
return 'Password must contain at least one lowercase letter, one uppercase letter, and one number'
}
return null // Valid
}
}
```
### Validation Timing
- **On change** - When `validateOnChange` is `true` (default)
- **On submit** - Always validates on form submission
- **Manual** - Using the exposed `validateForm()` method
## Responsive Layout
The component uses Vuetify's grid system for responsive layouts:
```javascript
{
name: 'field',
label: 'Field',
type: 'text',
cols: 12, // Full width on extra small screens
sm: 12, // Full width on small screens
md: 6, // Half width on medium screens
lg: 4 // One-third width on large screens
}
```
## Styling
The component uses Vuetify's design system with:
- **Outlined variants** for consistent appearance
- **Comfortable density** for optimal spacing
- **Error state styling** for validation feedback
- **Required field indicators** with red asterisks
- **Responsive design** that adapts to screen size
## Best Practices
1. **Use meaningful field names** that reflect the data structure
2. **Provide clear labels and help text** for better user experience
3. **Implement proper validation** for data integrity
4. **Consider responsive layout** for different screen sizes
5. **Handle form submission errors** gracefully
6. **Use controlled mode** when form data needs to be managed externally
7. **Leverage custom validation** for business-specific rules
8. **Test with various field combinations** to ensure proper behavior
9. **Use appropriate field types** for better user experience
10. **Provide meaningful default values** when appropriate
## Accessibility
The component includes:
- **Proper form semantics** with native HTML form elements
- **Label associations** for screen readers
- **Error message announcements** for validation feedback
- **Keyboard navigation** support throughout the form
- **Focus management** for better usability
- **Required field indicators** for clarity
## Browser Support
Compatible with all modern browsers that support:
- Vue 3 Composition API
- Vuetify 3 components
- ES6+ features
- CSS Grid and Flexbox
## Migration from Vuetify
The component has been completely migrated from Vuetify to PrimeVue. Here's what you need to know:
### ✨ What's New
- **AutoComplete component** - The star feature! Users can select from suggestions OR enter completely custom values
- **Enhanced Date/Time Pickers** - Multiple format support (`YYYY-MM-DD`, `mm/dd/yyyy`, etc.), smart defaults (`today`, `now`), time-only mode, inline calendars, business hour constraints, and comprehensive validation
- **Better file uploads** - Drag & drop, better validation, file preview
- **Improved accessibility** - Full ARIA support, better keyboard navigation
- **Flexible styling** - CSS custom properties, easier theming
- **Mobile-first responsive** - Better grid system, improved mobile UX
### 🔄 Migration Changes
#### Layout System
```javascript
// OLD (Vuetify):
{ cols: 12, sm: 12, md: 6, lg: 6 }
// NEW (CSS Grid): Same properties, better responsive behavior
{ cols: 12, md: 6, lg: 4 }
```
#### Component Mapping
| Vuetify | PrimeVue | Notes |
| --------------- | -------------- | ------------------------ |
| `v-text-field` | `InputText` | Same functionality |
| `v-textarea` | `Textarea` | Added autoResize |
| `v-select` | `Select` | Added filtering |
| `v-checkbox` | `Checkbox` | Improved styling |
| `v-radio-group` | `RadioButton` | Better layout |
| `v-file-input` | `FileUpload` | Enhanced features |
| `v-btn` | `Button` | Same functionality |
| **New!** | `AutoComplete` | 🚀 Custom values support |
| **New!** | `DatePicker` | Calendar popup |
#### Event Changes
```javascript
// OLD (Vuetify):
@update:model-value="handleChange"
// NEW (PrimeVue):
@update:model-value="handleChange" // Same!
// But also enhanced with better event data
```
#### Styling Changes
```css
/* OLD: Vuetify classes */
.v-form { ... }
.v-text-field { ... }
/* NEW: Custom CSS with variables */
.dynamic-form { ... }
.field-wrapper { ... }
/* Theming with CSS custom properties */
:root {
--text-color: #374151;
--red-500: #ef4444;
}
```
### 🚀 Upgrade Benefits
1. **AutoComplete with Custom Values** - Users are no longer limited to predefined options
2. **Better Performance** - PrimeVue components are more lightweight
3. **Enhanced Accessibility** - Full WCAG compliance out of the box
4. **Improved Developer Experience** - Better TypeScript support, clearer APIs
5. **Modern Styling** - CSS Grid, custom properties, better mobile support
6. **Rich Components** - Better date pickers, file uploads, and form controls
### 📝 Quick Migration Checklist
- [ ] Update imports from Vuetify to PrimeVue components
- [ ] Replace `v-select` with `AutoComplete` where custom values are needed
- [ ] Update CSS classes from `v-*` to custom classes
- [ ] Test responsive layout (should work better!)
- [ ] Enjoy the new AutoComplete functionality! 🎉
## Dependencies
- **Vue 3** with Composition API
- **PrimeVue 4.4+** components (InputText, InputNumber, Textarea, Select, AutoComplete, Checkbox, RadioButton, DatePicker, FileUpload, Button, Message)
- **Modern JavaScript** features (ES6+)
- **CSS Grid** and **Flexbox** support