Deep dive into Vue Dynamic Forms.
Tags: vue, forms, dynamic, webdev
Published: 2020-07-20 14:15:52

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.

![Frustration](https://media.giphy.com/media/YUG7qlbCeRutqL1jPU/giphy.gif)

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](https://angular.io/guide/dynamic-form).

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:

```javascript
{
  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](https://github.com/alvarosaburido/vue-dynamic-forms) was born.

![it's alive](https://media.giphy.com/media/11RwocOdukxqN2/source.gif)

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:

```bash
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:

```javascript
import Vue from 'vue';
import VueDynamicForms from '@asigloo/vue-dynamic-forms';

Vue.use(VueDynamicForms);
```

If you're using [Nuxt.js](https://nuxtjs.org/) you can install it as a module (Nuxt.js version `>= 2.12.2` is recommended).

```javascript
// 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

```vue
<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.

```vue
<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:

```javascript
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:

![Login Form](https://res.cloudinary.com/alvarosaburido/image/upload/v1595173107/blog/Deep%20dive%20into%20Vue%20Dynamic%20Forms/login-form_zt5hy1.png)

## 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](https://vue-dynamic-forms.netlify.app/guide/props.html#options)

```javascript
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](https://vue-dynamic-forms.netlify.app/guide/events.html#submit)) which will trigger after clicking the button and the form has no errors. Let's create a method on our `loginPage` component called `onLogInSubmit`

```javascript
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/`

```scss
@import '~@asigloo/vue-dynamic-forms/src/styles/themes/default.scss';
```

or with Nuxt Module options in nuxt.config.js:

```javascript
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`

![Vue dynamic forms themes](https://res.cloudinary.com/alvarosaburido/image/upload/v1595222662/blog/Deep%20dive%20into%20Vue%20Dynamic%20Forms/vue-dynamic-forms-themes_aqfz0u.png)

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.

```javascript
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;
```

![Inline custom classes](https://res.cloudinary.com/alvarosaburido/image/upload/v1595223955/blog/Deep%20dive%20into%20Vue%20Dynamic%20Forms/form-inline-example_k4dvkn.png)

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:

```scss
$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](https://logaretm.github.io/vee-validate/) 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:

```javascript
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.

```javascript
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'),
      ],
    }),
  ],
},
```

![email-validation](https://res.cloudinary.com/alvarosaburido/image/upload/v1595227885/blog/Deep%20dive%20into%20Vue%20Dynamic%20Forms/email-validation_a1k6kb.png)

By default, Vue Dynamic Forms contains the following validations:

- required
- min
- max
- email
- url
- minLength
- maxLength
- pattern

The complete documentation for this --> [Validation](https://vue-dynamic-forms.netlify.app/guide/validation.html#required)

But... what about custom validations 🤔? Let's use the **password field** as an example:

```javascript
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](https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/RegExp) 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](https://twitter.com/alvarosaburido1) on Twitter.