Validate forms with validate.js

web
mobile

Topic: Validate forms with validate.js
Published 09.09.2019 Updated 13.09.2019

Piotr Obrebski posted 2 months ago

Introduction

In this tutorial, you will learn how to implement form validation using validate.js library.

You will learn how to add dependencies, build basic form structure and execute required functions to create fully validated form with custom error messages and dynamically assigned errors depending on which type of error user caused in your form.

Note: To understand this tutorial content you need to have some understanding about JavaScript and HTML's form structure. I will do my best to explain everything in depth but you need to have some basic knowledge about this topic to use this useful extension.

,,Why should I waste my time for this?"

Validate.js is a lightweight library that depends on object constraints and function validate(). This simple structure gives us easy to understand logic about our rules of validation inside the object and receiving errors depending on our custom made rules with a designated function. After this tutorial, you will understand everything you need to build your first validated form with this technology.


See how easily you can modify your design of the form keeping the same validation methods.


Installation

To use validate.js you actually don't need any external dependencies! However, you might want to add a way to validate date and DateTime with custom parse and format function. For that purpose creator of validate.js recommend using moment.js library.

Additionally, to show you we can use only clear javascript while using this library I will not use any jQuery code in this form aside initiation of our MDB components. To make this process easy I will use some basic functions from underscore.js to quickly operate on dom elements without building any external objects and functions.

My starting index.html file will look like this:

<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Validate.js example</title>
  <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
  <!-- Downloaded mdb.css file -->
  <link href="css/mdb.min.css" rel="stylesheet">
</head>

<body>
  <!-- Optional libraries I am using in this tutorial -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
  <!-- Core MDB elements -->
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"
    integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
    integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous">
  </script>
  <!-- Downloaded mdb.js file -->
  <script src="js/mdb.min.js"></script>
  <!-- Library we are trying to use today -->
  <script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
</body>

</html>

Note: You can use validate.js library without any other dependencies. I will use external dependencies to show how you can implement complex form easily by using these additions.


Build an MDB Form

To check if the form is properly filled by users first we need to have a ready form.

In this tutorial I will try to showcase all the possibilities of this validating library, so we will need a variety of input types.

This example showcase usage of basic input types like type="email", type="password", type="text", type="date" or type="number" and additionally, some custom made validation will check the validation of the select field.

To present how you can implement this validation in the real-life example I styled this form and prepared space in bootstrap layout for errors display.

Note: Elements with class .messages will contain error content.

The form that I will check with validate.js looks like this:

<form id="main" class="form-horizontal" action="/example" method="post" novalidate>
  <div class="form-group row mb-0">
    <div class="col-sm-8 md-form px-0">
      <input id="email" class="form-control" type="email" name="email">
      <label for="email" class="control-label">Email</label>
    </div>
    <div class="col-sm-4 messages"></div>
  </div>
  <div class="form-group row mb-0">
    <div class="col-sm-8 md-form px-0">
      <input id="password" class="form-control" type="password" name="password">
      <label for="password" class="control-label">Pasword</label>
    </div>
    <div class="col-sm-4 messages"></div>
  </div>
  <div class="form-group row mb-0">
    <div class="col-sm-8 md-form px-0">
      <input id="confirm-password" class="form-control" type="password" name="confirm-password">
      <label class="control-label" for="confirm-password">Confirm password</label>
    </div>
    <div class="col-sm-4 messages"></div>
  </div>
  <div class="form-group row mb-0">
    <div class="col-sm-8 md-form px-0">
      <input id="username" class="form-control" type="text" name="username">
      <label class="control-label" for="username">Username</label>
    </div>
    <div class="col-sm-4 messages">
    </div>
  </div>
  <div class="form-group row mb-0">
    <div class="col-sm-8 md-form px-0">
      <input id="birthdate" class="form-control" type="date" placeholder="YYYY-MM-DD" name="birthdate">
      <label class="control-label" for="birthdate">Birthdate</label>
    </div>
    <div class="col-sm-4 messages"></div>
  </div>
  <div class="form-group row mb-0">
    <div class="col-sm-8 px-0">
      <select id="country" class="mdb-select md-form" name="country">
        <option value="">Country</option>
        <option value="BE">Belgium</option>
        <option value="CA">Canada</option>
        <option value="CN">China</option>
        <option value="CZ">Czech Republic</option>
        <option value="DK">Denmark</option>
        <option value="EG">Egypt</option>
        <option value="FR">France</option>
        <option value="GR">Greece</option>
        <option value="IN">India</option>
        <option value="JP">Japan</option>
        <option value="KE">Kenya</option>
        <option value="PL">Poland</option>
        <option value="PT">Portugal</option>
        <option value="SE">Sweden</option>
        <option value="CH">Switzerland</option>
        <option value="VE">Venezuela</option>
      </select>
    </div>
    <div class="col-sm-4 messages"></div>
  </div>
  <div class="form-group row mb-0">
    <div class="col-sm-8 md-form px-0">
      <input id="zip" class="form-control" type="text" name="zip">
      <label class="control-label" for="zip">ZIP Code</label>
    </div>
    <div class="col-sm-4 messages"></div>
  </div>
  <div class="form-group row mb-0">
    <div class="col-sm-8 md-form px-0">
      <input id="number-of-children" class="form-control" type="number" name="number-of-children">
      <label class="control-label" for="number-of-children">Number of children</label>
    </div>
    <div class="col-sm-4 messages"></div>
  </div>
  <div class="form-group">
    <div class="text-center pt-3">
      <button type="submit" class="btn btn-dark">Submit</button>
    </div>
  </div>
</form>
.card{
min-height: 82vh;
}
.form-group{
  margin: 0 0;
}
.md-form{
margin: 16px 0;
}
.help-block.error {
  margin-bottom: 5px;
}
.has-error label{
  color: #ff3547 !important;
}
.has-error input.form-control{
  border-bottom-color: #ff3547 !important;
}

Let's get started!

Step 1 - create constraints object

When working with validate.js this object is essential. Inside this structure, you define what attribute of input you want to check, what input types you want to be checking, check your custom validator options and many more like defining error messages.

We will use object constraints inside basic validate function of this library but let's not overtake the topic. First, we need our rules.

The basic structure of constraints object looks like this:

var constraints = {
  <attribute>: {
    <validator name>: <validator options>
  }
}

To create complex constraints object you have to consider all use cases of your form.

In my example form, we have all sort of different input types so we need to create constraints rules for all of them.

Note: Element attribute is, in this example, the name tag inside our input declaration. Validator name is one of the predefined names given by the library. Validator options are one of the given values that given Validator name accepts.


Basic elements

Checking email

Validate.js is created by making our development easy in mind. Creators made commonly used features available to use by default. For example to create constraints for basic email checking you have to create only these lines of code:

var constraints = {
  email: {
    // If email is required
    presence: true,
    // and you want to check if this is an email
    email: true
  },
}

Checking password

Checking if the password is enough long for our standards is simple and easy to:

var constraints = {
  email: {
    // ...
  },
  password: {
    // Is password required?
    presence: true,
    // Is password at least 5 characters long
    length: {
      minimum: 5
    }
  },
}

Confirm password

This sometimes causes troubles during development but with validate.js you simply use default options given by the library. We have the attribute equality which gives every possible feature you might need in checking equality of your input fields.

To check if this element value is equal with another input type we need to create a structure like this:

var constraints = {
  email: {
    // ...
  },
  password: {
    // Is password required?
    presence: true,
    // Is password at least 5 characters long
    length: {
      minimum: 5
    }
  },
  "confirm-password": {
    // Do ou need to confirm your password?
    presence: true,
    // It needs to be equal to the other password
    equality: {
      // Input we want it to be equal to
      attribute: "password",
      // Error message if passwords don't match
      message: "^The passwords does not match"
    }
  },
}

In the HTML form I used the intuitive name for input that will require confirmation of the password but this name contained symbol '-'. Because of that, we had to show the object that we still work with the name string. To accomplish that I simply wrapped the name of this validate attribute inside a string apostrophe's like this:

"confirm-password":

In this example, we created a custom message for our new constraints rule which is 'equality'. For email and password validation we have predefined messages that we can use if we want to but for our custom rules, we have to create our messages that we want to display in case of an error.

Note: Creating custom messages might be useful to create different language versions for your form validation.


Check the input content - Username

In our services we want our users to have standardized usernames. We can create this sort of in-depth checking with validate.js. In my example, I will require username to exist, have a length between 3 and 20 string length, contain only letters and digits but we don't want to check if letters are upper or lower case.

With standard validation options, we most likely would create a complex function that would check all these elements by parsing our input value. With validate.js you can accomplish that by simple object structure:

var constraints = {
  email: {
    // ...
  },
  password: {
    // ...
  },
  "confirm-password": {
    // ...
  },
  username: {
    // You need to pick a username
    presence: true,
    // It must be between 3 and 20 characters long
    length: {
      minimum: 3,
      maximum: 20
    },
    format: {
      // We only a-z and 0-9
      pattern: "[a-z0-9]+",
      // We don't care if the username is uppercase or lowercase
      flags: "i",
      message: "can only contain a-z and 0-9"
    }
  },
}

In the example above I tried to explain every element used by adding comments. Because of this object great structure, every part of the required code is easy to understand and after the first implementation, everything seems to be intuitive.

Note: To find out every possible predefined attribute you can check their list in full documentation on the creator's site.


Age restriction - Date

This kind of verification can be achieved by a countless amount of ways. In this example, I will use the library that I mentioned at the beginning moment.js. This addon makes my birthdate verification 4 lines long:

var constraints = {
  email: {
    // ...
  },
  password: {
    // ...
  },
  "confirm-password": {
    // ...
  },
  username: {
    // ...
  },
  birthdate: {
    // The user needs to give a birthday
    presence: true,
    // Must be born at least 18 years ago
    date: {
      latest: moment().subtract(18, "years"),
      message: "^You must be at least 18 years old to use this service"
    }
  },
}

Like always we check if the element exists with presence: true,. This time we need to use default date attribute and check it with the latest value to ensure the data we get in the date input is not later than the declared value.

This may be not intuitive but using the function moment().subtract(18, "years") I check what moment it is right now and what is the year someone had to be born to be at least 18 years old.

Because I declared new validation rule I declared additional error message if this rule is not fulfilled.

This is that simple!


Checking select field - Country

This part will demonstrate how to implement validation with our mdb material select. This component causes many troubles in the past with validation implementation but with validate.js it is much easier.

In the form I created to demonstrate this type of validation you can see that the list of options is quite large but every single one has an attribute: value="" with country code in it. Because of this addition, we can check what option is picked more intuitive.

var constraints = {
  email: {
    // ...
  },
  password: {
    // ...
  },
  "confirm-password": {
    // ...
  },
  username: {
    // ...
  },
  birthdate: {
    // ...
  },
  country: {
    // You also need to input where you live
    presence: true,
    // And we restrict the countries supported to Sweden
    inclusion: {
      within: ["SE"],
      // The ^ prevents the field name from being prepended to the error
      message: "^Sorry, this service is for Sweden only"
    }
  }
}

In this example, we used two new attributes: inclusion and within.

Inclusion checks if the input value is the same as any element from the "within" table. In this example, I declared only a select option with attribute value="SE" which is Sweden.

Note: Inclusion gives you the opportunity to check if the input value matches one of many options you declare as valid. Simply extend it within the table with more strings separated by a comma.

We almost finished, let's understand the last two types of constraints declared in my example.


Checking optional field - Zip Code

In declaring constraints I used presence: true in all examples above but this element is not required to exist in every single validated attribute. This time I present you simple constraints example of how you can check if the validated input fits your rules.

This time my declaration want this input to contain 5 digits. I achieve this validation this way:

var constraints = {
  email: {
    // ...
  },
  password: {
    // ...
  },
  "confirm-password": {
    // ...
  },
  username: {
    // ...
  },
  birthdate: {
    // ...
  },
  country: {
    // ...
  },
  zip: {
    // Zip is optional but if specified it must be a 5 digit long number
    format: {
      pattern: "\\d{5}"
    }
  }
}

As you may notice in format attribute we use pattern with value wrote using regExp to declare what type of string we accept.

Note: To read more about this element visit this documentation site

This input will not be validated unless someone types something in it. If at the moment of checking the form value will be empty there will be no verification of this input but if the content of the input exists and doesn't match our pattern rules the user will receive an error message.


Checking number field

The last element we want to validate is number input with restriction to accept only an integer value equal to or greater than zero.

Achieving this with validate.js is simple:

var constraints = {
  email: {
    // ...
  },
  password: {
    // ...
  },
  "confirm-password": {
    // ...
  },
  username: {
    // ...
  },
  birthdate: {
    // ...
  },
  country: {
    // ...
  },
  zip: {
    // ...
  },
  "number-of-children": {
    presence: true,
    // Number of children has to be an integer >= 0
    numericality: {
      onlyInteger: true,
      greaterThanOrEqualTo: 0
    }
  }
}

Code in this example basically explains itself. After watching previous examples this one is trivial, isn't it?

To check numerical values we use to attribute numericality with some basic expressions like "only integer" and "greaterThanOrEqualTo".

Note: To read more about numerical validation rules check this documentation site

And... We did it! Our Constraints are ready to be validated.


Validation in action

Like in every form we need to add some basic js functions before we start creating our features. In this example, I create an extended validation rule for date checking to accept the desired format.

// Before using validate.js we must add the parse and format functions
// Here is a sample implementation using moment.js
validate.extend(validate.validators.datetime, {
  // The value is not null or undefined but otherwise it could be anything.
  parse: function(value, options) {
    return +moment.utc(value);
  },
  // Input is a unix timestamp
  format: function(value, options) {
    var format = options.dateOnly ? "YYYY-MM-DD" : "YYYY-MM-DD hh:mm:ss";
    return moment.utc(value).format(format);
  }
});

After all my rules are ready to be used I hooked up the form to prevent it from being posted and instead execute a custom function.

var form = document.querySelector("form#main");
form.addEventListener("submit", function(ev) {
  ev.preventDefault();
  handleFormSubmit(form);
});

After the form submits event my handleFormSubmit() function will be executed. Let's create this function now!

function handleFormSubmit(form, input) {
  // validate the form against the constraints
  var errors = validate(form, constraints);
  // then we update the form to reflect the results
  showErrors(form, errors || {});
  if (!errors) {
    showSuccess();
  }
}

This simple function checks if validating function after checking our form returns any errors according to our predefined constraints. We execute function showErrors() if any error exists or showSuccess() if there are none.

Let's create both functions:

function showSuccess() {
  // Action to execute if the form is valid.
  alert("Success!");
}

function showErrors(form, errors) {
  // We loop through all the inputs and show the errors for that input
  _.each(form.querySelectorAll("input[name], select[name]"), function(input) {
    // Since the errors can be null if no errors were found we need to handle that
    showErrorsForInput(input, errors && errors[input.name]);
  });
}

Inside our loop, we execute the function for every single input to show it's errors and as explained in comments, we have to handle errors that contain null. To achieve that we create the function showErrorsForInput() that will do exactly that.

This function will be more complex so let's read this carefully with my comments:

// Function that shows the errors for a specific input
function showErrorsForInput(input, errors) {
  // This is the root of the input
  var formGroup = closestParent(input.parentNode, "form-group")
    // Find where the error messages will be insert into
    , messages = formGroup.querySelector(".messages");
  // First we remove any old messages and resets the classes
  resetFormGroup(formGroup);
  // If we have errors
  if (errors) {
    // we first mark the group has having errors
    formGroup.classList.add("has-error");
    // then we append all the errors
    _.each(errors, function(error) {
      addError(messages, error);
    });
  } else {
    // otherwise we simply mark it as success
    formGroup.classList.add("has-success");
  }
}

In the function above we use functions closestParent, resetFormGroup, addError and element item but we didn't declare them before. Let's fix it now!

First, we create the input variable:

// Hook up the inputs to validate on the fly
var inputs = document.querySelectorAll("input, textarea, select")
for (var i = 0; i < inputs.length; ++i) {
  inputs.item(i).addEventListener("change", function(ev) {
    var errors = validate(form, constraints) || {};
    showErrorsForInput(this, errors[this.name])
  });
}

In this code, I created not only an input element but additionally a loop that will iterate through all inputs that we find and proceed with some intentional methods.

We want to know if any input was changed and if that is a truth we execute code inside this event function. Generate new errors depending on this state of form and execute a function that will show this direct input new error.


Now I will declare a function closestParent(). We will use this function to find a place for our error messages. My structure of HTML assumes that my errors will be displayed inside of a grid structure so I just need to reach a div that is a sibling to our inputs div with class .md-form.

This function will look like this:

// Recusively finds the closest parent that has the specified class
function closestParent(child, className) {
  if (!child || child == document) {
    return null;
  }
  if (child.classList.contains(className)) {
    return child;
  } else {
    return closestParent(child.parentNode, className);
  }
}

We just check if the attribute child received an element that can actually be a nested element and if that is true, checks if this element contains the class that we want to actually find. If not we reach .parentNode element and execute the function once more. So the only expected outputs are null if we reach document without finding the desired class or actually an element that we are looking for.

Our form validation is almost ready. The only missing parts are functions resetFormGroup and addError.

To achieve reset of a form group we just clear previously added classes and remove elements containing errors.

function resetFormGroup(formGroup) {
  // Remove the success and error classes
  formGroup.classList.remove("has-error");
  formGroup.classList.remove("has-success");
  // and remove any old messages
  _.each(formGroup.querySelectorAll(".help-block.error"), function(el) {
    el.parentNode.removeChild(el);
  });
}

So, for now, we check our form and take care of all the possible outcomes but we didn't add the main part of this whole event. How actually we add our errors to the form?

Check out this function:

// Adds the specified error with the planned markup
// [message]
function addError(messages, error) {
  // Create error message container
  var block = document.createElement("p");
  block.classList.add("help-block");
  block.classList.add("error");
  // You can add what ever styling classes you want to your errors
  block.classList.add("text-danger");
  // Assign error message
  block.innerText = error;
  // Adds our ready error block to the desired location
  messages.appendChild(block);
}

And that's it. We just have to assemble all the pieces and execute them in (function() { })( ); object.

I added one mdb element to the mix so we have to initialize MDB material select with this code:

// Material Select Initialization
$(document).ready(function() {
  $('.mdb-select').materialSelect();
});

It is done!

We created a fully functional front end form with various validation methods and our custom set of rules. Our final result should contain this code:

<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Validate.js example</title>
  <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
  <!-- Downloaded mdb.css file -->
  <link href="css/mdb.min.css" rel="stylesheet">
</head>

<body>
  <div class="card mx-5 my-5 px-5 py-5">
    <h1>Validate.js example</h1>
    <form id="main" class="form-horizontal" action="/example" method="post" novalidate>
      <div class="form-group row mb-0">
          <div class="col-sm-8 md-form px-0">
            <input id="email" class="form-control" type="email" name="email">
            <label for="email" class="control-label">Email</label>
          </div>
          <div class="col-sm-4 messages"></div>
      </div>
      <div class="form-group row mb-0">
        <div class="col-sm-8 md-form px-0">
          <input id="password" class="form-control" type="password" name="password">
          <label for="password" class="control-label">Pasword</label>
        </div>
        <div class="col-sm-4 messages"></div>
      </div>
      <div class="form-group row mb-0">
        <div class="col-sm-8 md-form px-0">
          <input id="confirm-password" class="form-control" type="password" name="confirm-password">
          <label class="control-label" for="confirm-password">Confirm password</label>
        </div>
        <div class="col-sm-4 messages"></div>
      </div>
      <div class="form-group row mb-0">
        <div class="col-sm-8 md-form px-0">
          <input id="username" class="form-control" type="text" name="username">
          <label class="control-label" for="username">Username</label>
        </div>
        <div class="col-sm-4 messages">
        </div>
      </div>
      <div class="form-group row mb-0">
        <div class="col-sm-8 md-form px-0">
          <input id="birthdate" class="form-control" type="date" placeholder="YYYY-MM-DD" name="birthdate">
          <label class="control-label" for="birthdate">Birthdate</label>
        </div>
        <div class="col-sm-4 messages"></div>
      </div>
      <div class="form-group row mb-0">
        <div class="col-sm-8 px-0">
          <select id="country" class="mdb-select md-form" name="country">
            <option value="">Country</option>
            <option value="BE">Belgium</option>
            <option value="CA">Canada</option>
            <option value="CN">China</option>
            <option value="CZ">Czech Republic</option>
            <option value="DK">Denmark</option>
            <option value="EG">Egypt</option>
            <option value="FR">France</option>
            <option value="GR">Greece</option>
            <option value="IN">India</option>
            <option value="JP">Japan</option>
            <option value="KE">Kenya</option>
            <option value="PL">Poland</option>
            <option value="PT">Portugal</option>
            <option value="SE">Sweden</option>
            <option value="CH">Switzerland</option>
            <option value="VE">Venezuela</option>
          </select>
        </div>
        <div class="col-sm-4 messages"></div>
      </div>
      <div class="form-group row mb-0">
        <div class="col-sm-8 md-form px-0">

          <label class="control-label" for="zip">ZIP Code</label>
        </div>
        <div class="col-sm-4 messages"></div>
      </div>
      <div class="form-group row mb-0">
        <div class="col-sm-8 md-form px-0">
          <input id="number-of-children" class="form-control" type="number" name="number-of-children">
          <label class="control-label" for="number-of-children">Number of children</label>
        </div>
        <div class="col-sm-4 messages"></div>
      </div>
      <div class="form-group">
        <div class="text-center pt-3">
          <button type="submit" class="btn btn-dark">Submit</button>
        </div>
      </div>
    </form>
  </div>

  <!-- Optional libraries I am using in this tutorial -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/moment.min.js"></script>
  <!-- Core MDB elements -->
  <script src="https://code.jquery.com/jquery-3.4.1.min.js"
    integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
  <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
    integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous">
  </script>
  <!-- Downloaded mdb.js file -->
  <script src="js/mdb.min.js"></script>
  <!-- Library we are trying to use today -->
  <script src="//cdnjs.cloudflare.com/ajax/libs/validate.js/0.13.1/validate.min.js"></script>
</body>

</html>
.card{
min-height: 82vh;
}
.form-group{
  margin: 0 0;
}
.md-form{
margin: 16px 0;
}
.help-block.error {
  margin-bottom: 5px;
}
.has-error label{
  color: #ff3547 !important;
}
.has-error input.form-control{
  border-bottom-color: #ff3547 !important;
}
$(document).ready(function() {
  $('.mdb-select').materialSelect();
});

(function() {
  validate.extend(validate.validators.datetime, {
    parse: function(value, options) {
      return +moment.utc(value);
    },
    format: function(value, options) {
      var format = options.dateOnly ? "YYYY-MM-DD" : "YYYY-MM-DD hh:mm:ss";
      return moment.utc(value).format(format);
    }
  });

  var constraints = {
    email: {
      presence: true,
      email: true
    },
    password: {
      presence: true,
      length: {
        minimum: 5
      }
    },
    "confirm-password": {
      presence: true,
      equality: {
        attribute: "password",
        message: "^The passwords does not match"
      }
    },
    username: {
      presence: true,
      length: {
        minimum: 3,
        maximum: 20
      },
      format: {
        pattern: "[a-z0-9]+",
        flags: "i",
        message: "can only contain a-z and 0-9"
      }
    },
    birthdate: {
      presence: true,
      date: {
        latest: moment().subtract(18, "years"),
        message: "^You must be at least 18 years old to use this service"
      }
    },
    country: {
      presence: true,
      inclusion: {
        within: ["SE"],
        message: "^Sorry, this service is for Sweden only"
      }
    },
    zip: {
      format: {
        pattern: "\\d{5}"
      }
    },
    "number-of-children": {
      presence: true,
      numericality: {
        onlyInteger: true,
        greaterThanOrEqualTo: 0
      }
    }
  };

  var form = document.querySelector("form#main");
  form.addEventListener("submit", function(ev) {
    ev.preventDefault();
    handleFormSubmit(form);
  });

  var inputs = document.querySelectorAll("input, textarea, select")
  for (var i = 0; i < inputs.length; ++i) {
    inputs.item(i).addEventListener("change", function(ev) {
      var errors = validate(form, constraints) || {};
      showErrorsForInput(this, errors[this.name])
    });
  }

  function handleFormSubmit(form, input) {
    var errors = validate(form, constraints);
    showErrors(form, errors || {});
    if (!errors) {
      showSuccess();
    }
  }

  function showErrors(form, errors) {
    _.each(form.querySelectorAll("input[name], select[name]"), function(input) {
      showErrorsForInput(input, errors && errors[input.name]);
    });
  }

  function showErrorsForInput(input, errors) {
    var formGroup = closestParent(input.parentNode, "form-group")
      , messages = formGroup.querySelector(".messages");
    resetFormGroup(formGroup);
    if (errors) {
      formGroup.classList.add("has-error");
      _.each(errors, function(error) {
        addError(messages, error);
      });
    } else {
      formGroup.classList.add("has-success");
    }
  }

  function closestParent(child, className) {
    if (!child || child == document) {
      return null;
    }
    if (child.classList.contains(className)) {
      return child;
    } else {
      return closestParent(child.parentNode, className);
    }
  }

  function resetFormGroup(formGroup) {
    formGroup.classList.remove("has-error");
    formGroup.classList.remove("has-success");
    _.each(formGroup.querySelectorAll(".help-block.error"), function(el) {
      el.parentNode.removeChild(el);
    });
  }

  function addError(messages, error) {
    var block = document.createElement("p");
    block.classList.add("help-block");
    block.classList.add("error");
    block.classList.add("text-danger");
    block.innerText = error;
    messages.appendChild(block);
  }

  function showSuccess() {
    alert("Success!");
  }
})();

Conclusion

Validate.js is helpful library to create all sort of validation rules and complex solutions. You can customize every single point of your form without hesitation. If you don't want to build jQ dependant form validation with our MDB Package I highly recommend using this library to build your own form constraints.


Note: Don't forget to show us your form with custom validation in our snippets

Poul Eker commented 2 months ago

Hi Piotr,

I'm familiar with Bootstrap & JavaScript, but do not know much about MDB yet. So please tell me which role MDB is playing in this sample and what's the adventage of using it?

Best regards

Poul Eker


Piotr Obrebski commented 2 months ago

With MDB package you can create fully styled form. This is the main purpose of MDB package. Our in package validations are prepared for most of usecases but it doesn't let you customize your rules of validation. This tutorial explains how to use fully styled ready to validate form from MDB  and prepare your own set of rules to validate the inputs before taking action.


Write
Please insert min. 20 characters.