Link

Usage

There are a few steps to getting going with Formival, but it’s worth it. You might not see the benefit when designing a couple of simple forms, but once you’ve got more than a few forms, you will start to see the benefits.

Initialise

To start using Formival, the plugin is registered with Vue using Vue.use() as well as an instance being created with the options and passed to the Vue constructor:

import Vue from 'vue';
import Formival from 'formival';

Vue.use(Formival);
const formival = new Formival({
  // options, including:
  //     types,
  //     wrappers,
  //     validationMessages
});

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

This is similar to how you might initialise Vuex or Vue Router.

Formival will not do anything without defining field types. You will also probably want to provide at least one wrapper, and some validation message templates.

Add the Formival Form Component

Then you can make use of the formival-form component to embed a form anywhere:

<form @submit.prevent="onSubmit" novalidate autocomplete="off">
  <formival-form v-model="model" :validation="$v.model" :fields="fields"/>
  <button type="submit">Submit</button>
</form>

The :fields binding defines the structure of the form. Each field has a unique key that defines which bit of the model should be bound to this field. The type defines which input component is used. And templateOptions defines any extra options that are passed along to the input and wrapper components:

      fields: [
        {
          key: 'email',
          type: 'input',
          templateOptions: {
            label: 'Email',
            type: 'email'
          }
        },
        {
          key: 'password',
          type: 'input',
          templateOptions: {
            label: 'Password',
            type: 'password',
            description: 'Please enter your password here'
          }
        }
      ]
    }
  }

The model is an object who’s entries will be bound to the form fields as defined in fields. The model is bound using the v-model directive so that it is a two-way data binding.

  data() {
    return {
      model: {
        email: 'npm@daz.is',
        password: ''
      },
    ...
  }

The :validation binding is passed a Vuelidate validation object. You create this by adding validations to the component options, then accessing it via $v.

import {required, minLength, email} from "vuelidate/lib/validators";
...
    validations: {
        model: {
            email: {
                required,
                email
            },
          password: {
                required,
                minLength: minLength(8)
          }
    }
...

Configuration

Formival has no opinions about what your form markup should look like. You need to provide your own components for the input form controls. Formival will take care of binding them to the appropriate parts of the model.

Define types for input field types. You can use wrappers which are wrapped around the input fields to provide labels and validation error messages etc. The use of wrappers means that you can keep it DRY and not duplicate the same elements in each of the form controls. Validation messages use Lodash template strings and have access to the current value of the errored field, the field config, and the key of the validator that failed.

const types = [
  {
    name: 'input',
    component: SimpleInput,
    wrappers: ['field-wrapper']
  }
];

const wrappers = [
  {
    name: 'field-wrapper',
    component: FieldWrapper
  }
];

const validationMessages = {
  required: "{{field.templateOptions.label}} is required",
  email: "{{value}} is not a valid email address"
};

const formival = new Formival({
  types,
  wrappers,
  validationMessages
});

A Simple Input

See input types for more details.

<template>
    <input :value="value" :id="id" :type="inputType"
           @change="$emit('touched')"
           @focus="$emit('reset')"
           @input="$emit('input', $event.target.value)">
</template>

<script>
  export default {
    name: "SimpleInput",
    props: ['value', 'field', 'id'],
    computed: {
      inputType() {
        return this.field.templateOptions.type || 'text';
      }
    }
  };
</script>

A Simple Wrapper

See wrappers for more details.

<template>
    <div>
        <label :for="id"></label>
        <slot></slot>
        <p v-if="hasError" class="error">{{ errorMessage }}</p>
    </div>
</template>

<script>
  export default {
    name: "FieldWrapper",
    props: ['field', 'id', 'hasError', 'errorMessage']
  };
</script>