Last week, I went over some Techniques for Form Validation, including using CSS for visual feedback as well as the Constraint Validation API. I also discussed the benefits of validating inputs in realtime, and so this week I built out this simple registration form, demonstrating realtime validation.

Realtime Form Validation Demo

There are three key features of this form -

  1. The requirements for each input are clearly displayed even before the user starts typing
  2. As the user starts typing on the field and fulfils each requirement, they are given real time feedback on their success (or failings)
  3. The feedback is presented in such a way that the user will not submit the form without knowing of the errors in their input

If you'd like to see a walkthrough of how I built this out, you can watch the video below.

And here is the JavaScript behind the realtime validation -

/* ----------------------------

    CustomValidation prototype

    - Keeps track of the list of invalidity messages for this input
    - Keeps track of what validity checks need to be performed for this input
    - Performs the validity checks and sends feedback to the front end

---------------------------- */

function CustomValidation() {  
    this.invalidities = [];
    this.validityChecks = [];
}

CustomValidation.prototype = {  
    addInvalidity: function(message) {
        this.invalidities.push(message);
    },
    getInvalidities: function() {
        return this.invalidities.join('. \n');
    },
    checkValidity: function(input) {
        for ( var i = 0; i < this.validityChecks.length; i++ ) {

            var isInvalid = this.validityChecks[i].isInvalid(input);
            if (isInvalid) {
                this.addInvalidity(this.validityChecks[i].invalidityMessage);
            } 

            var requirementElement = this.validityChecks[i].element;
            if (requirementElement) {
                if (isInvalid) {
                    requirementElement.classList.add('invalid');
                    requirementElement.classList.remove('valid');
                } else {
                    requirementElement.classList.remove('invalid');
                    requirementElement.classList.add('valid');
                }

            } // end if requirementElement
        } // end for
    }
};



/* ----------------------------

    Validity Checks

    The arrays of validity checks for each input
    Comprised of three things
        1. isInvalid() - the function to determine if the input fulfills a particular requirement
        2. invalidityMessage - the error message to display if the field is invalid
        3. element - The element that states the requirement

---------------------------- */

var usernameValidityChecks = [  
    {
        isInvalid: function(input) {
            return input.value.length < 3;
        },
        invalidityMessage: 'This input needs to be at least 3 characters',
        element: document.querySelector('label[for="username"] .input-requirements li:nth-child(1)')
    },
    {
        isInvalid: function(input) {
            var illegalCharacters = input.value.match(/[^a-zA-Z0-9]/g);
            return illegalCharacters ? true : false;
        },
        invalidityMessage: 'Only letters and numbers are allowed',
        element: document.querySelector('label[for="username"] .input-requirements li:nth-child(2)')
    }
];

var passwordValidityChecks = [  
    {
        isInvalid: function(input) {
            return input.value.length < 8 | input.value.length > 100;
        },
        invalidityMessage: 'This input needs to be between 8 and 100 characters',
        element: document.querySelector('label[for="password"] .input-requirements li:nth-child(1)')
    },
    {
        isInvalid: function(input) {
            return !input.value.match(/[0-9]/g);
        },
        invalidityMessage: 'At least 1 number is required',
        element: document.querySelector('label[for="password"] .input-requirements li:nth-child(2)')
    },
    {
        isInvalid: function(input) {
            return !input.value.match(/[a-z]/g);
        },
        invalidityMessage: 'At least 1 lowercase letter is required',
        element: document.querySelector('label[for="password"] .input-requirements li:nth-child(3)')
    },
    {
        isInvalid: function(input) {
            return !input.value.match(/[A-Z]/g);
        },
        invalidityMessage: 'At least 1 uppercase letter is required',
        element: document.querySelector('label[for="password"] .input-requirements li:nth-child(4)')
    },
    {
        isInvalid: function(input) {
            return !input.value.match(/[\!\@\#\$\%\^\&\*]/g);
        },
        invalidityMessage: 'You need one of the required special characters',
        element: document.querySelector('label[for="password"] .input-requirements li:nth-child(5)')
    }
];

var passwordRepeatValidityChecks = [  
    {
        isInvalid: function() {
            return passwordRepeatInput.value != passwordInput.value;
        },
        invalidityMessage: 'This password needs to match the first one'
    }
];



/* ----------------------------

    Check this input

    Function to check this particular input
    If input is invalid, use setCustomValidity() to pass a message to be displayed

---------------------------- */

function checkInput(input) {

    input.CustomValidation.invalidities = [];
    input.CustomValidation.checkValidity(input);

    if ( input.CustomValidation.invalidities.length == 0 && input.value != '' ) {
        input.setCustomValidity('');
    } else {
        var message = input.CustomValidation.getInvalidities();
        input.setCustomValidity(message);
    }
}



/* ----------------------------

    Setup CustomValidation

    Setup the CustomValidation prototype for each input
    Also sets which array of validity checks to use for that input

---------------------------- */

var usernameInput = document.getElementById('username');  
var passwordInput = document.getElementById('password');  
var passwordRepeatInput = document.getElementById('password_repeat');

usernameInput.CustomValidation = new CustomValidation();  
usernameInput.CustomValidation.validityChecks = usernameValidityChecks;

passwordInput.CustomValidation = new CustomValidation();  
passwordInput.CustomValidation.validityChecks = passwordValidityChecks;

passwordRepeatInput.CustomValidation = new CustomValidation();  
passwordRepeatInput.CustomValidation.validityChecks = passwordRepeatValidityChecks;




/* ----------------------------

    Event Listeners

---------------------------- */

var inputs = document.querySelectorAll('input:not([type="submit"])');  
var submit = document.querySelector('input[type="submit"');

for (var i = 0; i < inputs.length; i++) {  
    inputs[i].addEventListener('keyup', function() {
        checkInput(this);
    });
}

submit.addEventListener('click', function() {  
    for (var i = 0; i < inputs.length; i++) {
        checkInput(inputs[i]);
    }
});

 

As always, there are improvements that can be made and since I built and recorded this I've already had some ideas for how to improve the form. For example -

  • Give feedback when a user focuses out of an invalid input
  • Use a custom tooltip in place of the in-built browser tooltips for more consistent and reliable behaviour across browsers

If you have more ideas, or feedback on the implementation, do leave a comment below.