bekwam courses

Vuelidate Custom Validator

June 28, 2019

Vuelidate is a library that provides form validation to the Vue.js framework. This article shows the basic usage and demonstrates a custom validator. The custom validator provides a rich experience for the user as it hits the backened while the user manipulates the form. The user is steered into submitting valid data before proceeding to the next screen.

A working example and the full source is available as a CodePen.

See the Pen Vuelidate Example by Carl Walker (@walkerca) on CodePen.

Walkthough

The app starts with a form containing a text input for a Product Id. Initially, no Product Id has been entered, so the user is flagged to provide one.

Screenshot of Code Example With Required Error Message
Form Flagging Required Product Id Field

If the user enters a value into the text input, it is checked against the backened to see if the Product Id exists. In this screenshot, the user has entered "a". There is no record with a Product Id equal to "a", so the input is flagged. The button to Lookup a record is disabled.

Screenshot of Code Example With Not Found Error Message
Form Flagging Product Not Found

By providing a valid Product Id, in this case "101", the user has passed all validation and can proceed to the Lookup. All error messages are cleared and the Lookup button is enabled.

Screenshot of Code Example Of Valid Form
A Valid Form

The result of the Lookup is a detailed record.

Screenshot of Code Example Showing Lookup
Result of Lookup Operation

Code

This UML diagram shows the structure of the program. There is a main App instance configured with Vue Router. Two routes are provide which map to two Components: MyForm and MyHandler. MyForm uses both a standard Vuelidate validator (required) and a custom validator (found).

UML Class Diagram
UML Class Diagram of Vuelidate Example

Both components communicate with the backend. MyForm issues Axios calls in response to us er input from the Product Id text box. MyHandler will issue a call when the page is loaded based on the $route.params.id value set up in MyForm.

This is the first part of the code listing for MyForm. Vuelidate provides a comprehensive report on the state of validations in the $v object. In the template below, a pair of HTML <small> tags will communicate validation errors back to the user. "Field is required" will be displayed if the required check does not pass; this is when the control is left empty including initially. "Product not found" will be displayed after the user types if the contents of the text box -- a Product Id -- are not found on the backend.

There is a single data field "productId" which is a v-model for the text box. This will provide the lookup argument via the route parameters to MyHandler.

    
const MyForm = {
  template: `
  <div>
    <h2>Form</h2>
    <p>
      Enter a Product Id.  If that's found, you can look up the description.  Otherwise, the Lookup button will remain disabled. Product Ids 101-108 will produce a hit.
    </p>
    <hr />
    <label for="productId">Product Id</label><br />
    <input type="text" id="productId" v-model="productId"/>
    <small class="validation-err" v-if="!$v.productId.required">Field is required</small>
    <small class="validation-err" v-if="$v.productId.required && !$v.productId.found">Product not found</small>
    <br />
    <router-link :to="'/handler/' + productId" tag="button" :disabled="!$v.productId.found">Lookup</router-link>
  </div>
  `,    
    data() {
    return {
       productId: null
    };
  },
    

The second part of the MyForm code listing contains the validations object. This object contains a standard validator, required, and a custom validator, found. "required" is configured through a simple declaration.


  validations: {
    productId: {
      required,
      found(value) {
          return new Promise((resolve, reject) => {
            axios
              .get("https://www.bekwam.net/data/products.json")
              .then(response => {
                const products = response.data.filter(p => p.productId == value);
                resolve(products.length > 0);
              })
              .catch(() => reject(false));
          });
      }
    }
  }
};
    

"found" is a function that returns a Promise. If the service call is made successfully and the Product was found based on the Product Id passed-in by the text box, the Promise will resolve to true. Note that this is an asynchronous call. The asynchronous call is needed so that the UI remains responsive (no dimmed and disabled browser controls) during the typing. Poor network connectivity can produce a gap between the user's typing and the display of the error message. I've fortified the form by linking the disabled property of the Lookup button to a valid "found" response.

The CodePen embedded at the start of the page shows the full code including the App instance and MyHandler which don't interact with Vuelidate. The CodePen also is an example of how to use Vuelidate with a CDN. In most apps, you'll use import but need to work with a Vuelidate object tied to the window object which running from a CDN.

This article presented a custom Vuelidate validator. While the found function was implemented as a service call, you can add JavaScript-only validators say to check for particular input within the running app's data fields or against a string format. Vuelidate has been very helpful in the Vue.js products I've worked on by providing a standard way to check input prior to form submission.


Headshot of Carl Walker

By Carl Walker

President and Principal Consultant of Bekwam, Inc