How to build reusable forms using vue-form-generator
Tags: vue, tutorial, webdev
Published: 2020-05-01 18:01:45

---
title: How to build reusable forms using vue-form-generator
published: true
date: 2020-04-02 16:00:35 UTC
tags: vue, tutorial, webdev 
canonical_url: https://blog.logrocket.com/how-to-build-reusable-forms-using-vue-form-generator/
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/5ch90x1tad9fb99ijsva.png
---
**Written by [Nkere-Awaji Inwan](https://blog.logrocket.com/author/enkaypeter/)**✏️

When developing applications either small or large scale, form inputs are the primary source of collecting data from users.

As our application grows larger we find the need to reuse forms that capture similar data instead of creating redundant files by repeating the forms going against the [DRY principle](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself). To handle this growth or keep our codebase clean, we will be taking advantage of the [vue-form-generato](https://github.com/vue-generators/vue-form-generator)r package.

vue-form-generator is a schema-based form generator component for vue.js, which can be used to create reactive forms based on data models or schema. It supports 21 field types, has a built-in form validator, can be easily customized, highly extensible, and lightweight in size.

To take this library for a spin, we are going to create a simple user management app that will demonstrate how we can perform CRUD operations on user records. We will also explore some fundamental Vue-form-generator features such as customization, validation, extension e.t.c. These fundamentals will enable us to learn how to better manage forms by generating reusable form components based on data models.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Prerequisites

To follow along with this tutorial the following are required:

1. An IDE of your choice e.g [Visual Studio Code](https://code.visualstudio.com/)
2. [Node.js](https://nodejs.org/en/) (minimum Node v10x) installed on your PC
3. Basic understanding of JavaScript and [Vue](https://vuejs.org/)
4. [Vue CLI](https://cli.vuejs.org/) installed on your PC

## Installation

If not already installed, you can use this command to install the Vue CLI on your PC:

```jsx
npm install @vue/cli -g
```

Now create a new Vue project with this command:

```jsx
vue create user-management-app
```

Next, change the directory into your newly created project:

```jsx
cd user-management-app
```

Install vue-form-generator:

```jsx
npm install vue-form-generator
```

Let’s install additional packages we’ll need to complete the implementation of our project.

- `axios` to make API calls
- `vue-router` to handle route functionality in our app
- `vue-tel-input` to get the feature country code and phone number input

Install these dependencies by running the command below:

```jsx
npm install axios vue-router vue-tel-input
```

The `dependencies` object in our `package.json` should look like this:

```jsx
"dependencies": {
  "axios": "^0.19.2",
  "core-js": "^3.6.4",
  "vue": "^2.6.11",
  "vue-form-generator": "^2.3.4",
  "vue-router": "^3.1.6",
  "vue-tel-input": "^4.3.0"
}
```

## Getting started

For demonstration purposes, a [node-express backend](https://repl.it/@enkaypeter/user-management-api) was built for this article. This express based server will help us perform CRUD operations.

By the end of this tutorial we will have built an application that looks like the GIF below:

![finished tutorial vue forms](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/demo-1.gif?resize=730%2C539&ssl=1)

## Setup

Let us add the package into our application, there are two ways of adding the package into our project it can be added manually or by importing it directly inside the project.

Doing it manually will require you to download zipped package, unpack it and add the `vfg.css` and `vfg.js` file to your project from `/dist` folder.

For this project, we’ll be using the import option. To do this, you will add the package to your `main.js` file, this way the form component can be reused across our application:

```jsx
// src/main.js

import Vue from 'vue'
import App from './App.vue'
import VueFormGenerator from 'vue-form-generator'

Vue.use(VueFormGenerator);

Vue.config.productionTip = false;

new Vue({
  render: h => h(App),
}).$mount('#app');
```

## Add Bootstrap

To get to some predefined styling in our application we’ll be adding Bootstrap via a content delivery network(CDN) like so:

```jsx
// public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet">
    ...
  </head>
</html>
```

Next up, we’ll add the navbar to our app by updating the `src/App.vue` as seen below:

```jsx
// src/App.js

<template>
  <div id="app">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container">
        <a class="navbar-brand" href="#">VFG</a>
        <button class="navbar-toggler" type="button"
                data-toggle="collapse"
                data-target="#navbarNavDropdown"
                aria-controls="navbarNavDropdown"
                aria-expanded="false"
                aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarNavDropdown">
          <ul class="navbar-nav">
            <li class="nav-item active">
              <a class="nav-link" href="#">Manage Users <span class="sr-only">(current)</span></a>
            </li>
          </ul>
        </div>
      </div>
    </nav>
    <div class="container my-5">
      <router-view/>
    </div>
  </div>
</template>

...
```

## Configuring Vue router

In this section, we’ll configure the routes for our application by creating a `router` folder in our `src` directory like so:

```jsx
$ mkdir src/router && touch src/router/index.js
```

The `index.js` file will contain all our routes logic and configuration. We will create the routes for our app like this:

```jsx
// src/router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import Users from '../pages/Users'
import CreateUser from '../pages/CreateUser'
import EditUser from '../pages/EditUser'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Users',
      component: Users
    },
    {
      path: '/create',
      name: 'CreateUser',
      component: CreateUser
    },
    {
      path: '/edit/:id',
      name: 'EditUser',
      component: EditUser
    }
  ]
})
```

> In the snippet above, we’ve imported a couple of components that we’ve yet to create. These components will be created as we progress.

To bootstrap our routes to our application, we will add it to the Vue instance as seen in the snippet below:

```jsx
// src/main.js

...

import router from './router'

...

new Vue({
  render: h => h(App),
  router, // add the router config to vue instance
}).$mount('#app');
```

In the coming sections, we will be creating the components imported earlier in our `src/router/index.js` file by creating a `pages` folder inside the `src` directory. This is where we will store all our router views.

## User list view

For our user’s list view, we’ll create a file named `Users.vue` and add the snippet below:

```jsx
// src/pages/Users.vue

<template>
    <div class="container">
        <div class="d-flex justify-content-between align-items-center">
            <div class="d-flex flex-column align-items-start">
                <h2>All users</h2><span class="text-muted">list of all users</span>
            </div>
            <router-link to="/create" class="btn btn-primary">Create new user</router-link>
        </div>
        <div class="table-responsive mt-5 bg-white">
            <div class="text-center" v-if="isLoading">Fetching Users</div>
            <table v-if="!isLoading && users.length" class="table">
                <thead>
                <tr>
                    <th>SN</th>
                    <th>First Name</th>
                    <th>Last Name</th>
                    <th>Email</th>
                    <th>Phone Number</th>
                    <th></th>
                </tr>
                </thead>
                <tbody>
                <tr v-for="(user, index) in users" :key="index">
                    <td>{{index+1}}</td>
                    <td>{{user.first_name}}</td>
                    <td>{{user.last_name}}</td>
                    <td>{{user.email}}</td>
                    <td>{{user.phone_number}}</td>
                    <td>
                        <div class="button-group">
                            <button class="btn btn-sm">view</button>
                            <router-link :to="`/edit/${user.id}`" class="btn btn-sm btn-secondary">edit</router-link>
                        </div>
                    </td>
                </tr>
                </tbody>
            </table>
            <div class="text-center" v-if="!isLoading && !users.length">No users found</div>
        </div>
    </div>
</template>

<script>
import axios from 'axios'

export default {
  name: 'Users',
  mounted () {
    this.fetchUsers()
  },
  data () {
    return {
      users: [],
      isLoading: true
    }
  },
  methods: {
    async fetchUsers () {
      try {
        this.users = (await axios.get('https://user-management-api.enkaypeter.repl.co/users')).data
      } catch (e) {
        console.log(e)
        alert(e.message)
      }
      this.isLoading = false
    }
  }

}
</script>

<style scoped></style>
```

In the snippet above, we’re creating a list view that displays the recorded details of our users. We are also creating a `Create new user` button which links to the `create users` form.

## User creation form

Before we proceed to the user form creation, let’s go over some properties of the `vue-form-generator` component which we’ll be using in our form implementation.

**Schema**: The schema object is where we define our form fields. These form fields can either be grouped or ungrouped. For our `create user form` we will be using grouped form fields. To know more about the different schema formats available you can visit the [documentation](https://vue-generators.gitbook.io/vue-generators/component/schema).

**Options**: Options for the Vue form component, these options can be used to define how validation is being run on the form.

**Model**: The model is a native JSON object. `VueFormGenerator` will modify this model if the values of the fields change.

**Tag**: This property can be used to change the main HTML element wrapper for fields and groups, the default value is a `fieldset`.

Now that we’re familiar with some fundamental `vue-form-generator` properties, let’s create our user form schema. We will do so by creating a folder named `forms` which will house our form schema `userFromSchema.js` like so:

```jsx
$ mkdir src/forms && touch src/forms/userFromSchema.js
```

After that is done, we’ll import vue-form-generator into a schema file like this:

```jsx
// src/forms/userFormSchema.js

import VueFormGenerator from 'vue-form-generator'

export default {
  groups: []
}

...
```

Our form will be made of four groups namely personal info, contact details, profile, and login details as defined in the sub-sections below.

## Personal info

This group contains form fields which will be used to collect the user’s personal information with basic validation on each field as seen below:

```jsx
// src/forms/userFormSchema.js
...

  {
      legend: 'Personal Info',
      fields: [
        {
          type: 'input',
          inputType: 'text',
          label: 'First Name',
          model: 'first_name',
          required: true,
          validator: ['string', 'required']
        },
        {
          type: 'input',
          inputType: 'text',
          label: 'Last Name',
          model: 'last_name',
          required: true,
          validator: ['string', 'required']
        },
        {
          type: 'select',
          label: 'Gender',
          model: 'gender',
          values: [
            {id: 'male', name: 'Male'},
            {id: 'female', name: 'Female'}
          ],
          selectOptions: {
            noneSelectedText: 'Choose One'
          },
          required: true,
          validator: ['string', 'required']
        },
        {
          type: 'input',
          inputType: 'number',
          label: 'Age',
          model: 'age',
          required: true,
          hint: 'Age is required & must be a between 18 and 35.',
          validator: ['number', 'required'],
          min: 18,
          max: 35
        },
        {
          type: 'input',
          inputType: 'text',
          label: 'City',
          model: 'city',
          required: true,
          validator: ['string', 'required']
        }
      ]
    }

...
```

## Contact details

This section contains form fields to collect the user’s email address and phone number. This section contains a custom input field called `tel-input`. We will talk more about how this was defined in the “how to add custom input fields” section in this post.

```jsx
// src/forms/userFormSchema.js
...

  {
      legend: 'Contact Details',
      fields: [
        {
          type: 'input',
          inputType: 'email',
          label: 'Email',
          model: 'email',
          required: true,
          validator: VueFormGenerator.validators.email
        },
        {
          type: 'tel-input',
          label: 'Phone Number',
          model: 'phone_number'
        }
      ]
    }
...
```

## Profile

This group, as the name implies, contains form fields which we’ll use to collect the user’s profile information. A text area is used to collect information about the user and a checklist for collecting the user’s skills.

```jsx
// src/forms/userFormSchema.js
...

  {
      legend: 'Profile',
      fields: [
        {
          type: 'textArea',
          inputType: 'textArea',
          rows: 4,
          label: 'About',
          model: 'about',
          required: true,
          validator: ['string', 'required']
        },
        {
          type: 'checklist',
          label: 'Skills',
          model: 'skills',
          values: ['Javascript', 'VueJS', 'CSS3', 'HTML5'],
          multiSelect: true,
          multi: true,
          required: true,
          validator: ['array', 'required'],
          selectOptions: {
            noneSelectedText: 'Choose One'
          }
        }
      ]
    }
...
```

## Login details

This group is used to collect the proposed username and password of the user. This group also contains custom validation rules which will be expatiated on in “how to add custom validation rules” section in this post.

```jsx
// src/forms/userFormSchema.js
...
  {
      legend: 'Login Details',
      fields: [
        {
          type: 'input',
          inputType: 'text',
          label: 'Username',
          model: 'username',
          required: true,
          validator: ['string', 'required']
        },
        {
          type: 'input',
          inputType: 'password',
          label: 'Password',
          model: 'password',
          required: true,
          validator: ['strongPassword', 'required']
        }
      ]
    }
...
```

These four groups make up our `create user form` schema and will determine how our form fields will be rendered. To see how they all come together, you can check out the [project repository](https://github.com/enkaypeter/user-management-app/blob/master/src/forms/userFormSchema.js) on GitHub.

## Customizing vue-form-generator

Predefined input types and fields sometimes won’t cut it as not every form is generic. As such, vue-form-generator makes it possible to add custom input types as well as built-in custom features.

In this section, we will take a look at how we can add custom input types and also add custom validation rules for our form fields.

### How to add custom input fields

In the `Contact Details` group located in `src/forms/userFormSchema.js`, we’re adding the custom field `tel-input` as seen below:

```jsx
...

{
          type: 'tel-input',
          label: 'Phone Number',
          model: 'phone_number'
}

...
```

To create this field, we will create a file `tel-input.vue` inside the `components` folder and add the snippet below:

```jsx
// src/components/tel-input.vue

<template>
    <vue-tel-input v-model="value"></vue-tel-input>
</template>

<script>
import { VueTelInput } from 'vue-tel-input'
import { abstractField } from 'vue-form-generator'

export default {
  name: 'TelephoneInput',
  mixins: [abstractField],
  components: {VueTelInput}
}
</script>
```

After this is done, we need to register the `tel-input` component as a global component in the `src/main.js` file like this:

```jsx
// src/main.js
...
Vue.component('field-tel-input', TelInput)
...
```

## How to add custom validation rules

As seen in the `Login Details` group, we added a `strongPassword` validation rule. This is because we want to define custom password validation rules which suits our applications requirements:

```jsx
...

{
          type: 'input',
          inputType: 'password',
          label: 'Password',
          model: 'password',
          required: true,
          validator: ['strongPassword', 'required']
}

...
```

To create this rule, we will open up our `main.js` file and update `Vue.use(VueFormGenerator);` like so:

```jsx
// src/main.js
...

Vue.use(VueFormGenerator, {
  validators: {
    strongPassword: (value, field, model) => {
      let regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{5,}$/

      if (!regex.test(value)) {
        return [ 'Password should be a minimum five characters, at least one uppercase letter, ' +
        'one lowercase letter and one number']
      } else {
        return []
      }
    }
  }
})

...
```

In the snippet above, we use regex to check if our password has a minimum of five characters, at least one uppercase letter, one lowercase letter, and one number.

## CreateUser components

The next step in our implementation will be to add our user creation page which contains the create form. We will do that by creating a file named `CreateUser.vue` inside the `pages` folder with the snippet below:

```jsx
<template>
    <div class="container">
        <div class="d-flex justify-content-start     mb-3">
            <router-link to="/" class="btn btn-sm btn-outline-secondary">back</router-link>
        </div>
        <div class="d-flex justify-content-between align-items-center">
            <div class="d-flex flex-column align-items-start">
                <h2>Create User</h2><span class="text-muted">Add new user record</span>
            </div>
        </div>
        <div class="card pl-4 pt-5 pb-5 pr-4 mt-5">
            <form action="" @submit.prevent="submitUserDetails">
                <vue-form-generator tag="div" :schema="schema" :options="formOptions" :model="model" />
                <div class="d-flex justify-content-end mt-3 pr-4">
                    <button type="submit" class="btn btn-primary btn-lg">
                        {{ isSaving ? 'Saving...' : 'Submit'}}
                    </button>
                </div>
            </form>
        </div>
    </div>
</template>

<script>
import userFormSchema from '../forms/userFormSchema'
import axios from 'axios'

export default {
  name: 'CreateUser',
  mounted () {
  },
  data () {
    return {
      model: {
        first_name: '',
        last_name: '',
        gender: '',
        skills: ['Javascript', 'VueJS'],
        email: '',
        age: '',
        phone_number: '',
        city: '',
        about: '',
        username: '',
        password: ''
      },
      schema: userFormSchema,
      formOptions: {
        validateAfterChanged: true
      },
      isSaving: false
    }
  },
  methods: {
    async submitUserDetails () {
      try {
        await axios.post('https://user-management-api.enkaypeter.repl.co/users', {
          ...this.model
        })
        alert('Saved Successfully')
      } catch (e) {greate
        console.log(e)
        alert(e.message)
      }
    }
  }
}
</script>

<style></style>
```

As seen in the snippet above, we are importing the `userFormSchema` which we created earlier to help determine how our form fields will be presented as well as `axios` to help us make API calls.

We then added a `model` property in the `data` method of our component which we’re using to get input data from our form.

Lastly, we added the `formOptions` property to our data method. Setting `validateAfterChanged` to `true` will instruct vue-form-generator to validate our form on every field change.

## User edit form

This file is very similar to our user creation form, except that the fields are pre-filled based on the user record selected from the user’s list view.

We will create our `EditUser` view by creating a new file named `EditUser.vue` in the `pages` folder like so:

```jsx
<template>
    <div class="container">
        <div class="d-flex justify-content-start     mb-3">
            <router-link to="/" class="btn btn-sm btn-outline-secondary">back</router-link>
        </div>
        <div class="d-flex justify-content-between align-items-center">
            <div class="d-flex flex-column align-items-start">
                <h2>Edit User</h2><span class="text-muted">Edit new user record</span>
            </div>
        </div>
        <div class="card pl-4 pt-5 pb-5 pr-4 mt-5">
            <form action="" @submit.prevent="updateUserDetails">
                <vue-form-generator tag="div" :schema="schema" :model="model" />
                <div class="d-flex justify-content-end mt-3 pr-4">
                    <button class="btn btn-primary btn-lg">
                        {{ isSaving ? 'Saving...' : 'Update'}}
                    </button>
                </div>
            </form>
        </div>
    </div>
</template>

<script>
    import userFormSchema from '../forms/userFormSchema'
    import axios from 'axios'

    export default {
        name: 'EditUser',
        mounted () {
            this.fetchUser(this.$route.params.id)
        },
        data () {
            return {
                model: {},
                schema: userFormSchema,
                id: this.$route.params.id,
                isSaving: false
            }
        },
        methods: {
            async fetchUser () {
                try {
                    this.model = (await axios.get(`https://user-management-api.enkaypeter.repl.co/users/${this.id}`)).data
                } catch (e) {
                    console.log(e);
                    alert(e.message)
                }
            },
            async updateUserDetails () {
                this.isSaving = true;
                try {
                    await axios.put(`https://user-management-api.enkaypeter.repl.co/users/${this.id}`, this.model);
                    alert('User details updated')
                } catch (e) {
                    console.log(e);
                    alert(e.message)
                }
                this.isSaving = false
            }
        }
    }
</script>

<style></style>
```

The component above contains two methods; `fetchUse()` and `updateUserDetails()`.

The `fetchUser` method fetches a user’s record based on their `id` whilst `updateUserDetails` updates the user’s details.

Let’s take a break and test our application to see what it looks like. To do that let’s run the code below in our project directory:

```jsx
$ npm run serve
```

If all goes well, you should see a page that looks like this when you visit the displayed URL:

![user management app created page](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/usermanagementdev.png?resize=1188%2C988&ssl=1)

Now that we have a running app, the next thing for us is to add aesthetics to our form by styling it.

## Styling vue-form-generator

vue-form-generator is Bootstrap friendly and has some default styling out of the box. To use its default styling you can import this file `vue-form-generator/dist/vfg.css` into your app. You can also use this file as a starting point to modify the look and feel of your form.

For this tutorial, we’ll be writing our custom style. This will help us better understand how to style the component.

Replace the style section in `src/App.vue` with the styles below:

```jsx
<style>
  body {
    background-color: #fafafa !important;
  }

  #app {
    font-family: 'Avenir', Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
  }

  .vue-form-generator > div{
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
    flex-grow: 1;
  }

  .form-group{
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    padding: 0 2%;
    width: 50%;
  }

  .field-wrap, .wrapper{
    width: 100%;
  }

  .dropList{
    z-index: 10;
    background-color: #FFF;
    position: relative;
    width: 40%;
    top: 5px;
    right: 12px;
  }

  legend{
    margin: 10px 0 20px 18px;
    font-size: 16px;
    font-weight: bold;
    text-align: left;
  }

  .hint{
    font-size: 10px;
    font-style: italic;
    color: purple;
  }

  .help-block{
    color: red;
  }
</style>
```

After we apply the styles, we should see something that looks like the image below:  
 ![User create page](https://i1.wp.com/blog.logrocket.com/wp-content/uploads/2020/03/User-create-page.png?resize=1140%2C991&ssl=1)

## Conclusion

In this article, we have learned how to create and manage reusable forms in our Vue application by generating form components based on their data model which can be reused on different occasions using the [vue-form-generator](https://github.com/vue-generators/vue-form-generator) package.

The source code for each of the following snippets is available on [GitHub](https://github.com/enkaypeter/user-management-app). Feel free to clone it, fork it, or submit an issue.

Here’s a link to the documentation of [the vue-form-generator](https://vue-generators.gitbook.io/vue-generators/) which has more examples and extensive explanation.

* * *
## Experience your Vue apps exactly how a user does 

Debugging Vue.js applications can be difficult, especially when there are dozens, if not hundreds of mutations during a user session. If you’re interested in monitoring and tracking Vue mutations for all of your users in production, [try LogRocket.](https://www2.logrocket.com/vue-performance-monitoring)

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/t4ni6gomxqbkbxygmco0.png)

[LogRocket](https://www2.logrocket.com/vue-performance-monitoring) is like a DVR for web apps, recording literally everything that happens in your Vue apps including network requests, JavaScript errors, performance problems, and much more. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred.

The LogRocket Vuex plugin logs Vuex mutations to the LogRocket console, giving you context around what led to an error, and what state the application was in when an issue occurred.

Modernize how you debug your Vue apps - [Start monitoring for free.](https://www2.logrocket.com/vue-performance-monitoring)
* * *

The post [How to build reusable forms using vue-form-generator](https://blog.logrocket.com/how-to-build-reusable-forms-using-vue-form-generator/) appeared first on [LogRocket Blog](https://blog.logrocket.com).