Skip to content

Form Component Bundle

Overview

The Form component bundle provides a comprehensive form building system with automatic validation, field management, and support for multiple input types. It consists of four components that work together to create flexible, maintainable form structures with built-in error handling and AJAX submission support.

Location: resources/components/form/

Components:

  • VueForm - Main form container managing submission and validation
  • VueField - Generic field wrapper supporting multiple input types
  • Checkbox - Checkbox input component
  • Radio - Radio button input component

Architecture

┌────────────────────────────────────────────────┐
│          VueForm                               │
│  (Form container, validation, submission)      │
│  Provides: labelSize, formOrientation, emitter │
│                                                │
│  ┌──────────────────────────────────────────┐  │
│  │ VueField (name, type: 'text')            │  │
│  │  ├─ input[type="text"]                   │  │
│  │  └─ error messages                       │  │
│  └──────────────────────────────────────────┘  │
│                                                │
│  ┌──────────────────────────────────────────┐  │
│  │ VueField (name, type: 'select')          │  │
│  │  ├─ <select> (or Select2)                │  │
│  │  └─ error messages                       │  │
│  └──────────────────────────────────────────┘  │
│                                                │
│  ┌──────────────────────────────────────────┐  │
│  │ VueField (name, type: 'checkbox')        │  │
│  │  ├─ Checkbox components                  │  │
│  │  └─ Checkbox components                  │  │
│  └──────────────────────────────────────────┘  │
│                                                │
└────────────────────────────────────────────────┘

Component Registration

The Form bundle is registered via an index file that exports a load function:

javascript
import {defineAsyncComponent} from "vue";

function load(app){
    app.component('VueForm', defineAsyncComponent(() => import('./form.vue')));
    app.component('VueField', defineAsyncComponent(() => import('./field.vue')));
    app.component('Checkbox', defineAsyncComponent(() => import('./checkbox.vue')));
    app.component('Radio', defineAsyncComponent(() => import('./radio.vue')));
}

export default {load}

Usage in Entry Point:

javascript
// resources/js/app.js
import {createApp} from "vue";
import Form from "../../components/form";

let app = createApp({
    // ... app config
});

// Load the Form bundle
Form.load(app);

app.mount('#app');

Components

VueForm

The main form container that manages submission, validation, field state, and event communication via an event emitter.

Props

PropTypeDefaultDescription
idString-Required. Unique ID for the form element
actionString-Form submission endpoint URL (defaults to current location)
verticalBooleanfalseIf true, renders fields in vertical layout mode
ajaxBooleantrueIf true, uses AJAX submission; if false, traditional form submission
smallNumber6Bootstrap column width for labels on small screens
mediumNumber3Bootstrap column width for labels on medium screens
largeNumber2Bootstrap column width for labels on large screens

Data

PropertyTypeDescription
loadingBooleanWhether form is currently processing submission
fieldsObjectCurrent form field values keyed by field name
validationObjectCurrent validation errors keyed by field name
hasFilesBooleanWhether form contains file inputs (auto-detects)
emitterEmitterEvent emitter for form-wide communication

Methods

MethodParametersDescription
setField(field, value)field: String, value: AnyUpdate a field value in the form
reset(defaults)defaults: ObjectReset form to initial state with optional default values
submit()-Submit the form via AJAX or traditional means

Provided Values (for child components)

  • labelSize - Bootstrap column sizes for responsive labels
  • formOrientation - Layout orientation (vertical/horizontal)
  • emitter - Event emitter for child communication
  • validation - Current validation errors
  • setField - Function to update field values

Encoding

The form automatically determines the encoding type:

  • application/x-www-form-urlencoded - For regular fields
  • multipart/form-data - When file inputs are present

Events (emitted)

EventPayloadDescription
init{defaults: Object}Initialize form with default values
init:subfieldName: StringInitialize sub-fields (checkboxes/radios)
destroyfieldName: StringClean up field resources

Example Usage

vue
<vue-form id="contact-form" action="/api/contacts" large="3" ajax ref="form">
  <vue-field name="name" type="text" id="name">Full Name</vue-field>
  <vue-field name="email" type="email" id="email">Email Address</vue-field>
  <vue-field name="message" type="textarea" id="message">Message</vue-field>
  
  <button @click="$refs.form.submit()" class="btn btn-primary">Submit</button>
</vue-form>

VueField

Flexible field wrapper component supporting multiple input types with automatic validation display and field management.

Props

PropTypeDefaultDescription
idString-Unique ID for the input element
nameString-Form field name (used for submission and validation)
typeString'text'Input type: text, number, password, email, tel, daterange, file, select, select2, checkbox, radio, autocomplete
autocompleteBooleanfalseEnable/disable HTML autocomplete attribute
multipleBooleanfalseAllow multiple selections (for select/select2)
rangedBoolean-Enable date range picker mode (for daterange type)
urlString-Server endpoint for dynamic data (select2/autocomplete)
limitNumber-Limit results for select2/autocomplete

Slots

SlotDescription
DefaultField label text
optionsOption elements for select/checkbox/radio fields

Computed Properties

PropertyDescription
labelClassBootstrap classes for responsive label layout
inputClassBootstrap classes for responsive input container
elementClassCSS classes for the input element (includes validation state)
inputTypeInternal type identifier for the field
textTypeHTML input type attribute

Methods

MethodParametersDescription
setValue(value)value: AnyUpdate field value and trigger change
onChange()-Handle field value changes
construct(defaultValue)defaultValue: AnyInitialize field with default value
destroy()-Clean up field resources (select2, daterange)
initDatePicker(defaultValue)defaultValue: StringInitialize date range picker
initSelect(defaultValue)defaultValue: ObjectInitialize Select2 enhanced select

Supported Field Types

TypeHTML ElementFeatures
text<input type="text">Standard text input
number<input type="number">Numeric input
password<input type="password">Password input (hidden)
email<input type="email">Email validation
tel<input type="tel">Telephone input
file<input type="file">File upload (sets form encoding to multipart)
daterange<input type="text">Date or date range picker
select<select>Native HTML select dropdown
select2<select>Enhanced Select2 dropdown with search
checkboxCheckbox componentsCheckbox input group
radioRadio componentsRadio button group
autocompleteAutocomplete componentServer-side autocomplete

Example Usage

Text Input:

vue
<vue-field name="username" type="text" id="username">Username</vue-field>

Email Input:

vue
<vue-field name="email" type="email" id="email">Email Address</vue-field>

Select Dropdown:

vue
<vue-field name="country" type="select" id="country">Country</vue-field>
  <template #options>
    <option value="us">United States</option>
    <option value="uk">United Kingdom</option>
    <option value="ca">Canada</option>
  </template>
</vue-field>

Select2 (Enhanced Select):

vue
<vue-field name="department" type="select2" id="department" url="/api/departments" :limit="5">
  Select Department
</vue-field>

Date Range Picker:

vue
<vue-field name="date_range" type="daterange" id="date-range" ranged>
  Date Range
</vue-field>

File Upload:

vue
<vue-field name="document" type="file" id="document">Upload Document</vue-field>

Checkbox Group:

vue
<vue-field name="interests" type="checkbox" id="interests">Interests</vue-field>
  <template #options>
    <checkbox id="interest-1" name="interests" value="sports">Sports</checkbox>
    <checkbox id="interest-2" name="interests" value="music">Music</checkbox>
    <checkbox id="interest-3" name="interests" value="art">Art</checkbox>
  </template>
</vue-field>

Autocomplete:

vue
<vue-field name="contact_id" type="autocomplete" id="contact" url="/api/contacts" :limit="10">
  Select Contact
</vue-field>

Checkbox

Individual checkbox input component for use within VueField checkbox groups.

Props

PropTypeRequiredDescription
idStringYesUnique ID for the checkbox
nameStringYesField name (must match parent field name)
valueString/BooleanNoValue to submit when checked

Slots

SlotDescription
DefaultCheckbox label text

Example Usage

vue
<checkbox id="terms" name="agreement" value="true">
  I agree to the terms and conditions
</checkbox>

Radio

Individual radio button component for use within VueField radio groups.

Props

PropTypeRequiredDescription
idStringYesUnique ID for the radio button
nameStringYesField name (all radios in a group share the same name)
valueStringYesValue to submit when selected

Slots

SlotDescription
DefaultRadio button label text

Events

EventPayloadDescription
change-Emitted when radio button is selected

Example Usage

vue
<radio id="option-1" name="choice" value="option1">Option 1</radio>
<radio id="option-2" name="choice" value="option2">Option 2</radio>
<radio id="option-3" name="choice" value="option3">Option 3</radio>

Complete Example

Here's a complete example showing the Form component with various field types:

Blade View

blade
<div class="card">
  <div class="card-header">
    <h5 class="card-title">Contact Form</h5>
  </div>
  <div class="card-body">
    <div id="app">
      <vue-form id="contact-form" action="/api/contacts" large="3" ajax ref="contactForm">
        <vue-field name="first_name" type="text" id="first-name">
          First Name
        </vue-field>
        
        <vue-field name="last_name" type="text" id="last-name">
          Last Name
        </vue-field>
        
        <vue-field name="email" type="email" id="email">
          Email Address
        </vue-field>
        
        <vue-field name="phone" type="tel" id="phone">
          Phone Number
        </vue-field>
        
        <vue-field name="country" type="select" id="country">
          Country
          <template #options>
            <option value="">-- Select a country --</option>
            <option value="us">United States</option>
            <option value="uk">United Kingdom</option>
            <option value="ca">Canada</option>
          </template>
        </vue-field>
        
        <vue-field name="department" type="select2" id="department" url="/api/departments" :limit="5">
          Department
        </vue-field>
        
        <vue-field name="interests" type="checkbox" id="interests">
          Interests
          <template #options>
            <checkbox id="interest-sports" name="interests" value="sports">Sports</checkbox>
            <checkbox id="interest-music" name="interests" value="music">Music</checkbox>
            <checkbox id="interest-art" name="interests" value="art">Art</checkbox>
          </template>
        </vue-field>
        
        <vue-field name="experience" type="radio" id="experience">
          Experience Level
          <template #options>
            <radio id="exp-beginner" name="experience" value="beginner">Beginner</radio>
            <radio id="exp-intermediate" name="experience" value="intermediate">Intermediate</radio>
            <radio id="exp-advanced" name="experience" value="advanced">Advanced</radio>
          </template>
        </vue-field>
      </vue-form>
      
      <div class="mt-3">
        <button @click="$refs.contactForm.submit()" class="btn btn-primary">
          Submit
        </button>
        <button @click="$refs.contactForm.reset()" class="btn btn-secondary ml-2">
          Reset
        </button>
      </div>
    </div>
  </div>
</div>

JavaScript Entry Point

javascript
import {createApp} from "vue";
import Form from "../../components/form";

let app = createApp({
    methods: {
        handleFormSuccess(response) {
            console.log('Form submitted successfully', response);
            alert('Your contact form has been submitted!');
        }
    }
});

Form.load(app);
app.mount('#app');

Features

  • ✅ Multiple input types (text, email, select, checkbox, radio, file, daterange, autocomplete)
  • ✅ Built-in AJAX form submission
  • ✅ Automatic validation error display
  • ✅ Responsive label sizing (small, medium, large)
  • ✅ Select2 integration for enhanced selects
  • ✅ Date range picker support
  • ✅ File upload with automatic multipart encoding
  • ✅ Server-side autocomplete
  • ✅ Event-based field communication via emitter
  • ✅ Field reset functionality with defaults
  • ✅ Bootstrap form styling

Styling

Default Styling

The Form component uses Bootstrap form classes:

  • .form-group - Field container
  • .form-control - Input element
  • .form-check - Checkbox/radio container
  • .form-check-input - Checkbox/radio input
  • .form-check-label - Checkbox/radio label
  • .text-danger - Error message color

Bootstrap Responsive Column Grid

The form automatically uses Bootstrap's 12-column grid:

vue
<!-- Label/Input split varies by screen size -->
<!-- Small: 6 cols label, 6 cols input -->
<!-- Medium: 3 cols label, 9 cols input -->
<!-- Large: 2 cols label, 10 cols input -->
<vue-form id="form" small="6" medium="3" large="2">

Custom Styling

Customize through Bootstrap utility classes or custom CSS:

vue
<vue-field name="email" type="email" id="email" class="mb-4">
  Email Address
</vue-field>

Best Practices

  1. Always Set Unique IDs: Required for form element targeting

    vue
    ✅ GOOD
    <vue-form id="my-form">
  2. Use Semantic Field Names: Match backend expected names

    vue
    ✅ GOOD
    <vue-field name="first_name" id="first-name">First Name</vue-field>
    
    ❌ BAD
    <vue-field name="fname" id="first-name">First Name</vue-field>
  3. Provide Clear Labels: Use field labels in the default slot

    vue
    ✅ GOOD
    <vue-field name="email" id="email">Email Address</vue-field>
  4. Handle Validation Errors: Check response status in submission handler

    javascript
    this.$refs.form.submit().done(() => {
        // Success - validation passed
    }).fail((xhr) => {
        if (xhr.status === 422) {
            // Validation errors - form displays them
        }
    });
  5. Set Appropriate Field Types: Use correct input type for better UX

    vue
    ✅ GOOD
    <vue-field name="email" type="email" id="email">Email</vue-field>
    <vue-field name="phone" type="tel" id="phone">Phone</vue-field>
    <vue-field name="quantity" type="number" id="quantity">Quantity</vue-field>

Common Patterns

Contact Form with Validation

vue
<vue-form id="contact-form" action="/api/contact" ajax ref="form">
  <vue-field name="name" type="text" id="name">Full Name</vue-field>
  <vue-field name="email" type="email" id="email">Email Address</vue-field>
  <vue-field name="subject" type="text" id="subject">Subject</vue-field>
  <vue-field name="message" type="textarea" id="message">Message</vue-field>
</vue-form>

<button @click="submitForm" class="btn btn-primary">Send Message</button>

<script>
export default {
  methods: {
    submitForm() {
      this.$refs.form.submit()
        .done(() => alert('Message sent!'))
        .fail(xhr => {
          if (xhr.status === 422) {
            console.log('Validation errors:', this.$refs.form.validation);
          }
        });
    }
  }
};
</script>

Form with Dynamic Select Options

vue
<vue-form id="product-form" action="/api/products" ajax ref="form">
  <vue-field name="category" type="select" id="category">
    Category
    <template #options>
      <option v-for="cat in categories" :key="cat.id" :value="cat.id">
        {{ cat.name }}
      </option>
    </template>
  </vue-field>
</vue-form>

<script>
export default {
  data() {
    return {
      categories: [
        { id: 1, name: 'Electronics' },
        { id: 2, name: 'Clothing' },
        { id: 3, name: 'Books' }
      ]
    };
  }
};
</script>

User Preference Form with Checkboxes

vue
<vue-form id="preferences-form" action="/api/preferences" ajax ref="form">
  <vue-field name="notifications" type="checkbox" id="notifications">
    Notification Preferences
    <template #options>
      <checkbox id="email-notify" name="notifications" value="email">
        Email Notifications
      </checkbox>
      <checkbox id="sms-notify" name="notifications" value="sms">
        SMS Notifications
      </checkbox>
      <checkbox id="push-notify" name="notifications" value="push">
        Push Notifications
      </checkbox>
    </template>
  </vue-field>
</vue-form>

Multi-Step Form in Modal

blade
<modal id="contact-modal" ref="contactModal" size="lg">
    <template #header>Contact Form</template>
    <vue-form id="contact-form" ref="contactForm" large="3" ajax action="/api/contacts">
        <tabs active-tab="#basic">
            <template #navigation>
                <tab target="#basic">Basic Info</tab>
                <tab target="#contact">Contact Details</tab>
            </template>
            
            <pane id="basic">
                <vue-field name="name" type="text" id="name">Name</vue-field>
            </pane>
            
            <pane id="contact">
                <vue-field name="email" type="email" id="email">Email</vue-field>
                <vue-field name="phone" type="tel" id="phone">Phone</vue-field>
            </pane>
        </tabs>
    </vue-form>
    <template #footer>
        <button class="btn btn-secondary" data-dismiss="modal">Cancel</button>
        <button class="btn btn-primary" @click="submitForm">Save</button>
    </template>
</modal>

Validation

Server-Side Validation

The form supports Laravel-style validation error responses (422 status code):

json
{
    "errors": {
        "email": ["The email field is required"],
        "phone": ["The phone must be a valid phone number"],
        "address.city": ["The city is required"]
    }
}

Error Display

Errors are automatically displayed below fields with the .text-danger class. Access validation state:

javascript
console.log(this.$refs.form.validation);
// { email: ['The email field is required'], phone: [...] }

Troubleshooting

Form Not Submitting

  • Verify VueForm is registered via the bundle's load() function
  • Check that the action prop is set or form will submit to current URL
  • Ensure ajax prop is correctly set (default is true)
  • Verify id prop is unique and set

Validation Errors Not Showing

  • Check server is returning 422 status with proper error format
  • Verify field name props match error keys in response
  • Check browser console for JavaScript errors
  • Ensure validation object is being populated correctly

Select2 or DatePicker Not Initializing

  • Verify required libraries are loaded (Select2, daterangepicker)
  • Check that field type is correct (select2, daterange)
  • Ensure DOM element has been rendered before initialization
  • Clear any previous initialization with destroy() on field change

Field Values Not Persisting

  • Ensure each field has a unique id and name
  • Verify field onChange is being called
  • Check that form is not being reset unexpectedly
  • Confirm setField method is being called correctly

Browser Compatibility

BrowserSupport
Chrome✅ Full support
Firefox✅ Full support
Safari✅ Full support
Edge✅ Full support
IE 11⚠️ Requires Vue 3 and Bootstrap 4 polyfills

Dependencies

  • Vue 3
  • Bootstrap 4 or 5 (for form styling)
  • jQuery (for AJAX and DOM manipulation)
  • Select2 (optional, for enhanced select fields)
  • daterangepicker (optional, for date range fields)
  • mitt (event emitter for component communication)
  • Autocomplete component (for autocomplete fields)

Real-World Usage

This component is used extensively in the OATERS application for creating forms in modals:

blade
<vue-form id="{{$id}}-form" ref="{{$ref}}Form" large="3" ajax action="{{url('r/contacts/'.($edit? 'update' : 'create'))}}">
    @if($edit)
        <input type="hidden" name="id" :value="openContact.id">
    @endif
    <tabs active-tab="#user">
        <template #navigation>
            <tab target="#user">{{trans('ruby::contacts.user')}}</tab>
            <tab target="#contact">{{trans('ruby::contacts.contact')}}</tab>
        </template>
        <pane id="user">
            <vue-field name="username" type="text" id="username">Username</vue-field>
            <vue-field name="password" type="password" id="password">Password</vue-field>
        </pane>
        <!-- ... more tabs and fields ... -->
    </tabs>
</vue-form>

See Also

  • Modal - For forms in modal dialogs
  • Tab - For multi-step forms
  • Card - For grouping form sections
  • Loader - For displaying loading state during submission

Built with ❤️ using Laravel and VitePress