Creating a Javascript form validation plugin8 min read

After my HTML Form designs are complete the next problem was to validate the input data before sending it to the servers. I don’t use Jquery or any other javascript frameworks. Also i did not find the inbuilt browser validation working well with the user interface i had in mind. So i had to come up with a new “Javascript form validation plugin” in pure vanilla javascript.

OK maybe i should have named it better.

Let’s get started

So where do i start ?

Here are the thing i wanted it to do

  • Set min, max and other properties on the input element and the plugin should check if the input text satisfies the conditions
  • Show an error message with a user recognizable field name and reason why it failed the test.
  • Scroll to the element which broke the rule on submit.
  • Do not submit data if no changes are made in an edit data form.
  • Generate a JSON string from the form data.

I am no expert in javascript but it seemed simple and i had to give it a try.

So i decided to add 2 custom attributes to my input elements.

  • data-sname: A replacement for the name attribute for inputs. This is for the servers to understand what field the data should be inserted to. Ex – If it is the username field data-sname attribute would be “username”.
    <input data-sname="username" />
    
  • data-lname: A field name that the end user would understand. Ex – If any required field was left blank, the error message would be {data-lname of elem} is required.
    <input data-lname="username" data-sname="username" />
    

To start with let us add 3 rules

  1. min – Min value of the input.
  2. max – Max value of the input.
  3. required – whether the field is mandatory.

More rules can be added to improve upon our “Javascript form validation plugin”.  Yup SEO.

Here is the form we have created in this article with our new found attributes added.

<form id="add_form" class="fiform" novalidate >
  <h1>New User</h1>
  <div class="fi" >
    <label>
      <span>User Type</span>
      <select id="user_type" data-lname="User Type" required data-sname="user_type" >
        <option value="1" >Single</option>
        <option value="1" >Team</option>
      </select>
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Username</span>
    <input id="username" required min="2" max="50" data-sname="username" data-lname="Username" />
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Password</span>
    <input id="password" type="password" required min="6" max="50" data-sname="pass" data-lname="Password" />
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Address</span>
    <textarea id="add" required min="6" max="50" data-sname="addr" data-lname="Address" ></textarea>
    </label>
  </div>
  <div class="fi" >
    <label>
    <input type="checkbox" checked data-sname="notify" />
    <span>Recieve Notifications</span>
    </label>
  </div>
  <div class="fi" >
    <label>
    <input type="submit" value="Add" id="submit" />
    </label>
  </div>
</form>

Javascript

Let us create a validator object.

var validator = function(elem) {
    this.form = elem;
    this.error = [false];
    this.qs = '';
    this.result = {};
    this.changed = false;
    this.changes = [];
    this.check_changes = false;
}

It takes the form element which has to be validated as input.

Now let us add the functions that would validate the input.

var validator = function(elem) {
  this.form = elem;
  this.error = [false];
  this.qs = '';
  this.result = {};
  this.changed = false;
  this.changes = [];
  this.check_changes = false;
  this.valid_input = {
    'min': function(input, val, type) {
      if (type == 'number') {
        if (parseFloat(input) < val) {
          return [false, 'Min value of {fieldname} is ' + val];
        }
      } else {
        if (input.length < val) {
          return [false, 'Min length of {fieldname} is ' + val];
        }
      }
      return [true];
    },
    'max': function(input, val, type) {
      if (type == 'number') {
        if (parseFloat(input) > val) {
          return [false, 'Max value of {fieldname} is ' + val];
        }
      } else {
        if (input.length > val) {
          return [false, 'Max length of {fieldname} is ' + val];
        }
      }
      return [true];
    },
    'required': function(input, val, type) {
      if (!input.length) {
        return [false, '{fieldname} is required'];
      } else {
        return [true];
      }
    }
  };
}

The validation functions take 3 inputs

  1. The inputted value.
  2. The value against it has to be checked.
  3. The type of the input element.

The functions return an array. Array key position 1 gives the validity of the input against the condition provided as Boolean. Position 2 gives the error message if any as string.

Now lets add a message box to show the error message.

if (document.getElementById('form_err')) {
  this.emsg = document.getElementById('form_err');
} else {
  this.emsg = document.createElement('div');
  this.emsg.style.padding = '10px 20px';
  this.emsg.id = 'form_err';
  this.emsg.style.background = 'rgb(0, 0, 16)';
  this.emsg.style.color = 'white';
  this.emsg.style.fontSize = '16px';
  this.emsg.style.position = 'absolute';
  this.emsg.style.zIndex = '100';
  this.emsg.style.display = 'none';
  this.emsg.style.top = '10px';
  this.emsg.style.left = '10px';
  this.emsg.style.opacity = '1';
  this.emsg.style.transition = 'opacity 1s';
  this.emsg.style.height = 'auto';
  this.emsg.style.borderRadius = '3px';
  this.emsg.style.boxSizing = 'border-box';
  var pointer = document.createElement('div');
  document.body.appendChild(this.emsg);
}

We also need a function to show this message box.

this.showerr = function(msg, input) {
  this.emsg.innerHTML = msg;
  this.emsg.style.display = 'inline-block';
  this.emsg.style.position = 'fixed'
  this.emsg.style.opacity = '1';
  setTimeout(function() {
    this.emsg.style.opacity = 0;
  }.bind(this), 5000);
  setTimeout(function() {
    this.emsg.style.display = 'none';
    this.emsg.style.position = 'absolute'
  }.bind(this), 6000);
};

And a scroll to element function

this.scrollt = function() {
  var scroll_speed = 15;
  if (this.left != this.leftlimit) {
    if (this.left < this.leftlimit) {
      this.left += scroll_speed;
    } else {
      this.left -= scroll_speed;
    }
  }
  if (this.top != this.toplimit) {
    if (this.top < this.toplimit) {
      this.top += scroll_speed;
    } else {
      this.top -= scroll_speed;
    }
  }
  window.scrollTo(this.left, this.top);
  this.animator = requestAnimationFrame(this.scrollt.bind(this));
  if ((this.top - this.toplimit) > -scroll_speed && (this.top - this.toplimit) < scroll_speed) {
    cancelAnimationFrame(this.animator);
  }
};

Before that we need a function that would return the value of a given element regardless of its type.

function multi_select(sel, string) {
  var opts = [],
    opt;
  for (var i = 0, len = sel.options.length; i < len; i++) {
    opt = sel.options[i];
    if (opt.selected) {
      opts.push(opt.value);
    }
  }
  return (string) ? opts.join() : opts;
}
get_val = function(el) {
  var node_type = el.nodeName.toLowerCase();
  if (node_type != 'input' && node_type != 'select' && node_type != 'textarea') {
    return el.innerHTML;
  }
  if (node_type == 'select' || (node_type == 'input' && el.type != 'checkbox') || node_type == 'textarea') {
    if (el.hasAttribute('multiple')) {
      return multi_select(el, true);
    }
    return el.value.trim();
  } else if (el.type == 'checkbox') {
    return (el.checked) ? 1 : 0;
  }
  return '';
}

Now the validate function

this.validate = function(no_same) {
  var input = [];
  var inputlen = 0;
  var childrenall = this.form.getElementsByTagName('*');
  for (var i = 0, q = childrenall.length; i < q; i++) {
    if (childrenall[i].hasAttribute('data-sname')) {
      if (childrenall[i].hasAttribute('data-active')) {
        if (childrenall[i].getAttribute('data-active') == 'true') {
          input.push(childrenall[i]);
          inputlen++;
        }
      } else {
        input.push(childrenall[i]);
        inputlen++;
      }
      if (childrenall[i].hasAttribute('data-init')) {
        this.check_changes = true;
        if (childrenall[i].getAttribute('multiple') != null) {
          if (childrenall[i].getAttribute('data-init') != multi_select(childrenall[i]).join()) {
            if (!this.changed) {
              this.changed = true;
            }
            this.changes.push(childrenall[i].getAttribute('data-lname').toLowerCase());
          }
        } else {
          if (childrenall[i].getAttribute('data-init') != get_val(childrenall[i])) {
            if (!this.changed) {
              this.changed = true;
            }
            this.changes.push(childrenall[i].getAttribute('data-lname').toLowerCase());
          }
        }
      }
    }
  }
  if ((this.check_changes && !this.changed) || inputlen == 0) {
    this.showerr('No changes made');
    return [false];
  }
  for (var i = 0, q = inputlen; i < q; i++) {
    for (rule in this.valid_input) {
      if (this.valid_input.hasOwnProperty(rule)) {
        if (input[i].hasAttribute(rule) && (input[i].required || input[i].value != '')) {
          var er_check = this.valid_input[rule](get_val(input[i]), input[i].getAttribute(rule), input[i].type);
          if (!er_check[0]) {
            this.left = this.scrollp.scrollLeft;
            this.top = this.scrollp.scrollTop;
            this.toplimit = input[i].offsetTop - 50;
            this.leftlimit = input[i].offsetLeft - 50;
            this.animator = requestAnimationFrame(this.scrollt.bind(this));
            input[i].focus();
            this.showerr(er_check[1].replace('{fieldname}', input[i].getAttribute('data-lname')), input[i]);
            return [false, input[i]];
          }
        }
      }
    }
    this.result[input[i].getAttribute('data-sname')] = encodeURIComponent(get_val(input[i]));
    if (this.changes.length) {
      this.result['changes'] = this.changes.join();
    }
  }
  return [true, this.result];
}

The function loops through all elements of the given form and creates an array of elements which has an attribute of data-sname.

As you can see if an attribute of data-init is set to an element, the current value of the element is checked against the value of this attribute to see if any changes were made.

On error our validator shows a message and focuses on the element which broke the rule. Finally if no errors are found it returns an object with data-sname as key and value of element as value.

Now let’s put our validator to work

document.getElementById('add_form').onsubmit = function(e) {
  e.preventDefault();
  var val = new validator(this).validate();
  if (!val[0]) {
    return;
  }
  alert(JSON.stringify(val[1]));
}

The returned object is converted to a JSON string, and is ready to be send to our servers for further validation.

Download validator.js

You can download the js file from the above link and use it as shown in  code above.

Here is a working example

See the Pen XjzRrg by hrishikesh mk (@avastava) on CodePen.0

Styling HTML Forms4 min read

I wanted to create a simple but good looking HTML Form for my applications. After searching the internet for inspiration this is what i came up with –

See the Pen HTML forms by hrishikesh mk (@avastava) on CodePen.0

Code

HTML

Inside the form (.fiform) the input elements are enclosed inside a container (div .fi). Inside the container comes the label element which encloses the input text and input elements. This is done so that the input element gets focus when the user clicks anywhere inside the label.

<form id="add_form" class="fiform" novalidate >
  <h1>New User</h1>
  <div class="fi" >
    <label>
      <span>User Type</span>
      <select id="user_type" data-lname="User Type" required data-sname="user_type" >
        <option value="1" >Single</option>
        <option value="1" >Team</option>
      </select>
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Username</span>
    <input id="username" required min="2" max="50" />
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Password</span>
    <input id="password" type="password" required min="6" max="50" />
    </label>
  </div>
  <div class="fi" >
    <label>
    <span>Address</span>
    <textarea id="add" required min="6" max="50" ></textarea>
    </label>
  </div>
  <div class="fi" >
    <label>
    <input type="checkbox" checked />
    <span>Recieve Notifications</span>
    </label>
  </div>
  <div class="fi" >
    <label>
    <input type="submit" value="Add" id="submit" />
    </label>
  </div>
</form>

 

CSS

The form is given a display of table. Inside the form the input fields with labels are put in a container with a display of table-row. The labels are given a display of table-cell so that they fill the parent and its contents can be vertically aligned.

* {
    margin: 0;
    padding: 0;
    font-family: Avant Garde, Avantgarde, Century Gothic, CenturyGothic, AppleGothic, sans-serif;
}
body {
    padding-top: 55px;
    background-color: #f8f8f8;
}
.fiform {
    display: table;
    width: 90%;
    max-width: 400px;
    min-width: 300px;
    margin: auto;
    padding: 30px 0;
    padding-top: 0;
}
.fiform h1 {
    text-align: center;
}
.fi {
    display: table-row;
    text-align: left;
    height: 100px;
}
label {
    display: table-cell;
    vertical-align: middle;
    font-size: 18px;
}
label span {
    font-size: 17px;
    font-weight: 600;
    color: #000;
}

The input elements are given a width of 100% so that they fill the form width. Also let’s give the inputs the same background as the body. Input auto-fill color is also changed in webkit browsers using -webkit-autofill property.

input,
select,
textarea {
    display: block;
    margin: auto;
    width: 100%;
    height: 35px;
    border: 0;
    border-bottom: solid 1px;
    font-size: 18px;
    background-color: #f8f8f8;
}
input[type="submit"] {
    height: 70px;
}
input:disabled {
    background-color: #f0f0f0;
}
button,
input[type="submit"] {
    border: 0;
    cursor: pointer;
    background-color: #dddddd;
}
input:focus,
input:active,
select:focus,
select:active,
textarea:focus,
textarea:active {
    outline: 0;
    border-bottom: solid 1px rgb(0, 188, 215);
}
input:-webkit-autofill {
    box-shadow: 0 0 0px 1000px #f8f8f8 inset;
}
textarea {
    height: 100px;
}
select[multiple] {
    height: auto !important;
    margin-bottom: 20px;
    border-bottom: solid 1px #000;
}
select {
    text-transform: capitalize;
}

Everything looked good until the time came to add check-boxes to the form. So i started my search for cool check box styles.

Fortunately I came across a youtube channel called Devtips. So i just stole the idea and got a good checkbox style for my form.

Here is the link to their tutorial – How to make Custom Animated Checkboxes with CSS .

Javascript

Now the only thing left was to highlight the label text when the input element is in focus. So let’s bring in some javascript.

var inputfade = document.querySelectorAll('.fi input,.fi select,.fi textarea');
var ifln = inputfade.length;

function input_fade(elem, on) {
  var span_elem = elem.parentNode.getElementsByTagName('span');
  if (span_elem.length) {
    span_elem[0].style.color = on ? 'rgb(0, 188, 215)' : '#000';
  }
}
for (var i = 0; i < ifln; i++) {
  inputfade[i].onfocus = function() {
    input_fade(this, true);
  }
  inputfade[i].onblur = function() {
    input_fade(this, false);
  }
}

And the form is ready.

Link to part two of the tutorial – Styling HTML Forms 2 .