Is summer 2020 and since we're coming back to quarantine in Barcelona Spain, I decided to take some time to prepare this article about dynamic forms in Vue meanwhile I imagine myself on a beach drinking a mojito π .
Usually, forms are pretty straightforward to implement especially in vue, you just need a pair of inputs inside of a <form>
element in your template and a method that will handle when your form is submitted.
That's ok, but what happens when the form gets bigger and bigger? Or what happens when your client needs to edit those inputs frequently to adapt to their current business model? For the second case that would imply that the developer needs to change the template every time the client needs, which ends up being a pretty mediocre user experience for the client, and for the developer as well.
I found myself in that situation a few months ago in a side-project, then I remembered the times that I was working mainly in Angular, there was a way to implement forms dynamically based on metadata that describes the business model, called Dynamic Forms.
That's it, that's the solution. So I started to search for an existing library in vue with a similar implementation, but to my surprise, there wasn't that much. In fact, most of them where over-complicated or part of an extensive library of UI components that I don't really need.
So I started doing a module from scratch following similar concepts from the angular implementation, a <form-component>
holding the form info, and a set of input-controls
that will iterate using v-for
to dynamically create the inputs based of a fields
prop.
All you will need to do is pass the <form-component />
the fields using an object array similar to this:
{
fields: [
{
label: 'Email',
name: 'email',
type: 'email',
},
{
label: 'Password',
name: 'password',
type: 'password',
},
],
}
Eventually, I found myself using this module in several projects, with little modifications in each one and the problem of having to update them all manually whenever I improved the module. So I decided to transform it into a library that I could install as a dependency using npm
.
That is how Vue Dynamic Forms was born.
In this article, we'll learn how to easily implement dynamic forms in vue using this library. The good thing? I already pass trough the difficult parts so you don't need to reinvent the wheel π.
Instalation
To install just run:
yarn add @asigloo/vue-dynamic-forms
# or, using NPM
npm install @asigloo/vue-dynamic-forms
Then, we can install it as a plugin between the vue app:
import Vue from 'vue';
import VueDynamicForms from '@asigloo/vue-dynamic-forms';
Vue.use(VueDynamicForms);
If you're using Nuxt.js you can install it as a module (Nuxt.js version >= 2.12.2
is recommended).
// nuxt.config.js
module.exports = {
modules: ['@asigloo/vue-dynamic-forms/nuxt'],
};
Form Composition
Let's start with the basics, a simple login form. Inside our page/component template add the <dynamic-form />
component
<dynamic-form
:id="loginForm.id"
:fields="loginForm.fields"
:options="loginForm.options"
@submit="handleSubmit"
/>
Just bellow we will create a call to action button for the form submitting. For it to work make sure it has the attribute submit
to true
and form
with the same id that you passed to the dynamic-form
component.
<button submit="true" :form="loginForm.id" class="btn btn-primary">
Login
</button>
We'll create a data object containing the form id
and the form fields, to create the fields we can import the factory function FormField
from the library core:
import { FormField } from '@asigloo/vue-dynamic-forms';
const loginPage = {
data() {
return {
loginForm: {
id: 'login-form',
fields: [
new FormField({ type: 'email', label: 'Email', name: 'email' }),
new FormField({
type: 'password',
label: 'Password',
name: 'password',
}),
],
},
};
},
};
export default loginPage;
With a little bit of CSS makeup you should end up to something similar to this:
Options
The main component comes with a set of default options you can override by passing an object using FormOptions
function trough the options
prop. There is more
info in the documentation here
import { FormField, FormOptions } from '@asigloo/vue-dynamic-forms';
const loginPage = {
data() {
return {
loginForm: {
id: 'login-form',
fields,
options: new FormOptions({
customClass = 'row',
netlify: true,
})
},
};
},
};
export default loginPage;
The next step is to handle the form's submission, for that, the library has a special called submit
event (documentation here) which will trigger after clicking the button and the form has no errors. Let's create a method on our loginPage
component called onLogInSubmit
import { FormField } from '@asigloo/vue-dynamic-forms';
const loginPage = {
data() {
return {
loginForm,
isLoggedIn: false
},
};
},
methods() {
onLogInSubmit() {
this.isLoggedIn = true;
}
}
};
export default loginPage;
Styling
The library is completely framework-agnostic in terms of UI, the components are unstyled by default, so you can customize them with your own. If you want a more "ready to go" solution you can import one of the themes we have included in src/styles/themes/
@import '~@asigloo/vue-dynamic-forms/src/styles/themes/default.scss';
or with Nuxt Module options in nuxt.config.js:
module.exports = {
modules: ['@asigloo/vue-dynamic-forms/nuxt'],
dynamicForms: {
theme: 'default',
},
};
For the moment there are two themes available:
- Bootstrap alike
themes/default.scss
- Material Design theme
themes/material.scss
I wanted the styling process to be as customizable as the client needed, you can add customClass
to both the main form trough the FormOptions
and each field trough the FormField
option. This is pretty handy for example if you want to have a row of fields each one in a column.
import { FormField, FormOptions } from '@asigloo/vue-dynamic-forms';
const loginPage = {
data() {
return {
loginForm: {
id: 'login-form',
fields: [
new FormField({
type: 'email',
label: 'Email',
name: 'email',
customClass: 'col-6'
}),
new FormField({
type: 'password',
label: 'Password',
name: 'password',
customClass: 'col-6'
}),
],
options: new FormOptions({
customClass = 'row',
})
},
};
},
};
export default loginPage;
Another way you can customize it is by overriding the scss
variables, you only need to set them just before importing the theme like this:
$input-bg: #e2eb5d52;
$input-border-color: #aec64c;
@import '~@asigloo/vue-dynamic-forms/src/styles/themes/default.scss';
Validations and error handling.
One of the things I missed in the majority of the libraries was a built-in validation to avoid having the necessity of adding an external library like VeeValidate into the bundle.
For this, I added to the library the ability to define an array of validations you want the field control to have and the error message in addition to a set of default validators (which are in their core just simple arrow functions) that you can import to your component as you need like this:
import {
FormField,
FormValidation,
required,
email,
} from '@asigloo/vue-dynamic-forms';
Then add the validations array to the FormField
of your choice, for this example, we are going to use email one. Each validation can be created using FormValidation
with the validator function and the message linked to it in case it fails.
loginForm: {
...
fields: [
new FormField({
type: 'email',
label: 'Email',
name: 'email',
validations: [
new FormValidation(required, 'This field is required'),
new FormValidation(email, 'Format of email is incorrect'),
],
}),
],
},
By default, Vue Dynamic Forms contains the following validations:
- required
- min
- max
- url
- minLength
- maxLength
- pattern
The complete documentation for this --> Validation
But... what about custom validations π€? Let's use the password field as an example:
new FormField({
type: 'password',
label: 'Password',
name: 'password',
validations: [
new FormValidation(
pattern(
'^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[#$^+=!*()@%&]).{8,10}$',
),
'Password must contain at least 1 Uppercase, 1 Lowercase, 1 number, 1 special character and min 8 characters max 10',
),
],
}),
Under the hood, pattern
validator uses regex to check if the value of the input matches the acceptance criteria. This is very powerful because it allows the user to create their custom validations outside of the library defaults.
Thanks for following along, I hope this tutorial helps you implement forms in a more comfortable way. If you have any question let's talk in the comments.
Like this article? Follow @alvarosaburido1 on Twitter.
Top comments (0)