# Forms
While you’re free to use FormulateInput
elements as stand-alone elements, it’s
often useful to group them into more traditional forms. Doing this is
as simple as wrapping your FormulateInput
fields in a FormulateForm
component.
The FormulateForm
will actively collect all the values from the
FormulateInput
fields it is wrapping, and use the name
prop of each as the
property name in the object. You can read and write to form values using
v-model
just as you would on an input. Let’s make a registration form as an
example:
View source code
<template>
<FormulateForm
class="login-form"
v-model="formValues"
>
<h2 class="form-title">Register</h2>
<p>
You can place any elements you want inside a form. The inputs themselves
can even be deeply nested.
</p>
<FormulateInput
name="name"
type="text"
label="Your name"
placeholder="Your name"
validation="required"
/>
<FormulateInput
name="email"
type="email"
label="Email address"
placeholder="Email address"
validation="required|email"
/>
<div class="double-wide">
<FormulateInput
name="password"
type="password"
label="Password"
placeholder="Your password"
validation="required"
/>
<FormulateInput
name="password_confirm"
type="password"
label="Confirm your password"
placeholder="Confirm password"
validation="required|confirm"
validation-name="Confirmation"
/>
</div>
<FormulateInput
type="submit"
label="Register"
/>
<pre
class="code"
v-text="formValues"
/>
</FormulateForm>
</template>
<script>
export default {
data () {
return {
formValues: {}
}
}
}
</script>
<style scoped>
.login-form {
padding: 2em;
border: 1px solid #a8a8a8;
border-radius: .5em;
max-width: 500px;
box-sizing: border-box;
}
.form-title {
margin-top: 0;
}
.login-form::v-deep .formulate-input .formulate-input-element {
max-width: none;
}
@media (min-width: 420px) {
.double-wide {
display: flex;
}
.double-wide .formulate-input {
flex-grow: 1;
width: calc(50% - .5em);
}
.double-wide .formulate-input:first-child {
margin-right: .5em;
}
.double-wide .formulate-input:last-child {
margin-left: .5em;
}
}
</style>
# Setting initial values
To re-populate an entire form of data, you can set the values
prop of the
form. This makes it easy to create “update” forms like account pages:
<template>
<FormulateForm
:values="formValues"
>
<FormulateInput
type="text"
name="name"
label="Your name"
/>
<FormulateInput
type="email"
name="email"
label="Your email"
/>
<FormulateInput
type="submit"
label="Save account"
/>
</FormulateForm>
</template>
<script>
export default {
data () {
return {
formValues: {
name: 'Jenny Taylor',
email: 'jenny.taylor@example.com'
}
}
}
}
</script>
# Model binding
In the above example, formValues
is used to initially set the field values,
but then it is never changed after that. Often it’s helpful to use the values of
the form reactively. To do this use v-model
instead of values
to
bi-directionally bind to the form values — if you change the values of that
object, the form fields will update and if you type into one of the text fields
the object will update!
<FormulateForm
v-model="formValues"
>
...
</FormulateForm>
Note
It is not recommended to use your v-model
data in your form submit handler.
Instead, use the data passed to your submit handler. Read Submitting forms for more info.
# Submitting forms
While it’s easy to use v-model
to get and set form values, the @submit
event
is the preferred way to retrieve the final values from a form for processing or
submitting to a backend. There are a number of reasons for this:
- The
@submit
event will not fire until all validation rules (including async validation rules) are passing. - Data emitted in the
@submit
event is deeply cloned and can be safely mutated without side effects. - The
@submit
event ensures all form uploads are finished before completing.
You can listen for the @submit
event just as you would on a standard <form>
element. If you return a Promise
from your submit handler FormulateForm
will
automatically expose an isLoading
property on the context object.
Form Submission Control Flow Diagram
<template>
<FormulateForm
@submit="submitHandler"
#default="{ isLoading }"
>
<FormulateInput
label="What is your name?"
name="name"
help="Please enter your name"
validation="required"
/>
<FormulateInput
type="submit"
:disabled="isLoading"
:label="isLoading ? 'Loading...' : 'Submit this form'"
/>
</FormulateForm>
</template>
<script>
export default {
methods: {
async submitHandler (data) {
await this.$axios.post('/my/api', data)
alert(`Thank you, ${data.name}`)
}
}
}
</script>
Outputs:
Notice how the above form does not trigger the alert dialog until the fields it contains pass validation. Neat-o.
Note
Because validation rules are asynchronous, and file uploads, will wait to resolve the @submit
event is also asynchronous relative to when the form was submitted.
# Non-validated submit handler
There are times where you may not want to opt-in to the default behavior of the
@submit
event, and would rather be notified synchronously on every attempt to
submit a form. For these edge cases, you can bind to the @submit-raw
event.
This event is triggered on all submission attempts, even if the inputs do not
pass validation. It’s up to you to determine how you want to handle it. The
payload of the event is a FormSubmission
instance (opens new window).
# Form validation 2.5
The <FormulateForm>
component is always aware of the validation state for
each of it’s inputs. In addition to the @submit
handler not being called
unless every nested FormulateInput
is valid, the validation state of the form
is also made available to your template via the default
slot. In this example
we only enable the submit button when all the fields pass validation:
<FormulateForm
#default="{ hasErrors }"
>
<FormulateInput
type="email"
label="Please enter a superhero email address"
validation="required|email"
validation-behavior="live"
/>
<FormulateInput
type="checkbox"
label="Select some of your favorite superheros"
validation="required|min:2"
:options="{
batman: 'Batman',
blackpanther: 'Black Panther'
captainamerica: 'Captain America,
catwoman: 'Catwoman',
hulk: 'Hulk,
superman: 'Superman',
wonderwoman: 'Wonder woman',
}"
validation-behavior="live"
/>
<FormulateInput
type="submit"
:disabled="hasErrors"
/>
</FormulateForm>
# Validation failed message 2.5
On long forms it can be helpful to display an error message near the submit
button when submitting a form that contains invalid fields, since the validation
errors may be out of the viewport. To do this, use the invalid-message
prop.
<FormulateForm
invalid-message="Not all fields were filled out properly"
>
<FormulateInput
label="Email"
type="email"
name="email"
validation="required|email"
/>
<FormulateInput
label="First name"
name="first_name"
validation="required"
/>
<FormulateInput
label="Last name"
name="first_name"
validation="required"
/>
<!-- Form errors will show here -->
<FormulateErrors />
<FormulateInput type="submit" />
</FormulateForm>
The invalid-message
prop can be a String
, Array
, or a Function
.
Functions are passed an object of the invalid inputs, and you are expected to
return a String
or Array
of strings. A slight tweak to the above example
allows us to output the names of the fields that are failing:
<FormulateForm
:invalid-message="invalidMessage"
>
...
<script>
export default {
methods: {
invalidMessage(fields) {
const fieldNames = Object.keys(fields)
const listOfNames = fieldNames.map(fieldName => fieldName.replace('_', ' '))
return `Invalid fields: ${listOfNames}`
}
}
}
</script>
Note
For more information on the FormulateErrors
component please read about
Form errors.
# Conditional fields
To make fields conditional use simple Vue directives such as v-if
.
<FormulateForm v-model="values">
<FormulateInput
type="select"
name="planet"
label="What is your favorite rocky planet?"
:options="{ mercury: 'Mercury', venus: 'Venus', earth: 'Earth', mars: 'Mars' }"
/>
<FormulateInput
v-if="values.planet === 'earth'"
key="earth"
name="earth_moon"
label="What is the name of earth’s moon?"
/>
<FormulateInput
v-if="values.planet === 'mars'"
key="mars"
name="mars_sunset"
label="What color is the Martian sunset?"
/>
</FormulateForm>
{}
Conditional fields and keys
Due to the way Vue patches the DOM, it is generally a best practice to put
a key
on each FormulateInput
that can be dynamically swapped out. This ensures that
Vue does not re-use the DOM element when patching. For more information read the
about reusing elements in the Vue Docs (opens new window).
# Preserving conditional values
Did you notice in the example above that values were removed from the form's
data when the corresponding FormulateInput
was removed? If you need to keep
those values in the form data set the keep-model-data
prop to true
. If you
only want one or two fields to keep their data you can set the keep-model-data
prop directly on <FormulateInput>
as well.
<FormulateForm
v-model="values"
:keep-model-data="true"
>
<FormulateInput
type="select"
name="planet"
label="What is your favorite rocky planet?"
:options="{ mercury: 'Mercury', venus: 'Venus', earth: 'Earth', mars: 'Mars' }"
/>
<FormulateInput
v-if="values.planet === 'earth'"
key="earth"
name="earth_moon"
label="What is the name of earth’s moon?"
/>
<FormulateInput
v-if="values.planet === 'mars'"
key="mars"
name="mars_sunset"
label="What color is the Martian sunset?"
/>
</FormulateForm>
{}
# Ignoring inputs 2.5+
Complex forms often have inputs that do not need to be submitted to the server,
for example inputs that are only used to control the display of the form. These
inputs can opt-out of form participation by adding an ignored
prop:
<FormulateForm
v-model="values"
>
<FormulateInput
label="Select your meal"
v-model="meal"
type="select"
:options="{burger: 'Hamburger', pasta: 'Pasta'}"
ignored
/>
<FormulateInput
v-if="meal === 'burger'"
label="Build your own burger"
type="checkbox"
name="burger"
:options="{
meat: 'Meat',
lettuce: 'Lettuce',
tomato: 'Tomato',
cheese: 'Cheese'
}"
/>
<FormulateInput
label="Select a pasta sauce"
v-if="meal === 'pasta'"
name="sauce"
type="radio"
:options="{
bolognese: 'Bolognese',
carbonara: 'Carbonara',
tortellini: 'Tortellini'
}"
/>
</FormulateForm>
{}
# Named forms
Vue Formulate introduces the concept of "named forms" as a mechanism for
globally accessing and manipulating your forms through the $formulate
plugin.
To leverage named forms, simply supply a unique name
prop to any
<FormulateForm>
component. The names should be unique among any currently
mounted forms. After naming a form, you can easily call a number of named form
methods.
Method | Description |
---|---|
handle(err, formName) | Used to set error messages on a form, typically from a backend server. Read more about error handling. |
reset(formName, values) | Reset the form's values, validation messages, and error messages. |
resetValidation(formName) | Reset all validation and error messages. |
setValues(formName, values) | Set the value of the form's model with values (even if no v-model is defined). |
submit(formName) | Used to submit a form programmatically. |
View source code
<template>
<FormulateForm
name="login"
@submit="setSomeErrors"
v-model="formData"
class="login-form"
>
<h2 class="form-title">Login</h2>
<FormulateInput
name="email"
label="Email"
validation="required|email"
/>
<FormulateInput
name="password"
label="Password"
validation="required"
/>
<FormulateErrors />
<div class="actions">
<FormulateInput
type="submit"
/>
<FormulateInput
type="button"
label="Reset"
data-ghost
@click="reset"
/>
</div>
<code class="code code--block">{{ formData }}</code>
</FormulateForm>
</template>
<script>
export default {
data () {
return {
formData: {}
}
},
methods: {
setSomeErrors () {
// do some processing...
const errors = {
fieldErrors: { username: 'Sorry, no such username exists!' },
formErrors: ['Incorrect login, please try again.']
}
this.$formulate.handle(errors, 'login')
},
reset () {
this.$formulate.reset('login')
}
}
}
</script>
<style>
.actions {
display: flex;
margin-bottom: 1em;
}
.actions .formulate-input {
margin-right: 1em;
margin-bottom: 0;
}
.login-form {
padding: 2em;
border: 1px solid #a8a8a8;
border-radius: .5em;
max-width: 500px;
box-sizing: border-box;
}
.form-title {
margin-top: 0;
}
</style>
# Events
Event | Description |
---|---|
created | Emitted in from the form's created lifecycle hook after it has applied default values. |
failed-validation | Emitted when form submission fails due to validation, passed an object with field names as properties and component instances as values. |
input | Emitted when any values in the form change. |
submit-raw | Emitted on any form submission attempt, even with invalid fields. |
submit | Emitted by any standard form submission events if all fields are passing validation |
# Props
Name | Description |
---|---|
debounce | Amount of time (in milliseconds) to debounce all inputs in the form. Note: this is not reactive, it only applies to inputs when they register |
invalid-message | String , Array , or Function , error message to show when a form is submitted with invalid fields. |
# Context object 2.5
Forms contain a single slot default
, which is passed a form context object.
This object is similar to the input context object,
albeit much simpler.
Property | Description |
---|---|
errors | An array of explicit form errors (not validation errors) assigned via the error handling features. |
hasErrors | Boolean indicating if the form has validation errors |
hasValue | Boolean indicating if the form has any values at all |
isValid | Inverse of hasErrors |
isLoading | If the form is currently loading. This is automatically managed by returning a promise from your `@submit handler. |
values | The values of the inputs in the form itself. |