Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Using jQuery unobtrusive validation with Twitter Bootstrap

5.00/5 (4 votes)
7 Nov 2015CPOL3 min read 14.1K   324  
This is an alternative for Using the jQuery unobtrusive validator with Twitter Bootstrap tooltips

Introduction

When I was trying to find a good example of how to use twitter bootstrap's validation states with jquery's unobtrusive validation, I came across Cristian Moldovan great example of using bootstrap's tooltips to display validation errors. However, for my needs, I wanted to use bootstrap's default form validation states to style how my field errors would look like. So I decided to tweak Cristian's example a little to show how we can use bootstrap's input validation states along with jquery's unobtrusive validation.

Background

I will be just going over my tweaks to Cristian's project. If you want more details on what files to include, please refer to Cristian's example. I am also assuming that you have worked with jquery validation before and are also familiar with Bootstrap.

The reason I wanted to change jquery's default validation style was because in the application I was working on, we are using default bootstrap for our UI. jquery's validation was nice but I wanted to be consistent in how our UI looked so I decided to incorporate bootstrap's form validation states into our application. Here is an example of what the field validation would look like with bootstrap:

Image 1

Using the Code

Similar to Cristian's example, I created a JavaScript library to change the default styling of jquery's validation. Below is that library, jquery.validate.unobtrusive.bootstrap.js:

JavaScript
(function ($) {
    var classes = { groupIdentifier: '.form-group', 
    error: 'has-error', success: 'has-success', feedback: 'has-feedback' };
    function updateClasses(inputElement, toAdd, toRemove, addFeedback) {
        var group = inputElement.closest(classes.groupIdentifier);
        if (group.length > 0) {
            group.addClass(toAdd).removeClass(toRemove);
            if (addFeedback) {
                group.addClass(classes.feedback);
            }
        }
    }

    function onError(inputElement, message) {
        var group = inputElement.closest(classes.groupIdentifier);
        var inputGroup = inputElement.closest('.input-group');
        var helpBlock = '<span class="help-block">' + message + '</span>';

        group.find('.form-control-feedback').remove();
        if (group.find('.help-block').length > 0) {
            group.find('.help-block').text(message);
        } else {
            if (inputGroup.length > 0) {
                inputGroup.after(helpBlock);
            } else {
                inputElement.after(helpBlock);
            }
        }

        if (inputElement.context.type === 'text') {
            var errorIcon = '<span class="glyphicon glyphicon-remove 
            form-control-feedback" aria-hidden="true"></span>';
            if (inputGroup.length > 0) {
                inputGroup.after(errorIcon);
            } else {
                inputElement.after(errorIcon);
            }
            updateClasses(inputElement, classes.error, classes.success, true);
        } else {
            updateClasses(inputElement, classes.error, classes.success, false);
        }
    }

    function onSuccess(inputElement) {
        var group = inputElement.closest(classes.groupIdentifier);
        var inputGroup = inputElement.closest('.input-group');

        group.find('.form-control-feedback').remove();
        group.find('.help-block').remove();
        if (inputElement.context.type === 'text') {
            var successIcon = '<span class="glyphicon glyphicon-ok 
            form-control-feedback" aria-hidden="true"></span>';
            if (inputGroup.length > 0) {
                inputGroup.after(successIcon);
            } else {
                inputElement.after(successIcon);
            }
            updateClasses(inputElement, classes.success, classes.error, true);
        } else {
            updateClasses(inputElement, classes.success, classes.error, false);
        }
    }

    function onValidated(errorMap, errorList) {
        $.each(errorList, function () {
            onError($(this.element), this.message);
        });

        if (this.settings.success) {
            $.each(this.successList, function () {
                onSuccess($(this));
            });
        }
    }

    $(function () {
        $('form').each(function () {
            var validator = $(this).data('validator');
            validator.settings.showErrors = onValidated;
        });
    });
}(jQuery));

The main changes from Cristian's library are in the updateClasses, onError and onSuccess functions.

For the updateClasses function, I added an extra parameter called addFeedback. This determines if I want to add the bootstrap css class has-feedback to the form-group element. The has-feedback class is needed in form-group that contain an input element of type text. This helps in adding the little icon to the right of the input field.

JavaScript
function updateClasses(inputElement, toAdd, toRemove, addFeedback) {
    var group = inputElement.closest(classes.groupIdentifier);
    if (group.length > 0) {
        group.addClass(toAdd).removeClass(toRemove);
        if (addFeedback) {
            group.addClass(classes.feedback);
        }
    }
}

In the onError function, this is where I add a span with a class help-block that contains the error message that is returned to the user indicating what kind of error is on the field. I also check if the input element is inside of an input group. If it is, I want to append the help block after the input group. Otherwise, if it is appended after the input element, inside of the input group, it causes the add-on in the input group to stretch when the help-block message shows.

To get the x mark to show in the input element, I had to make sure that the input element is a text input because this will only work with text inputs. So after I made sure it was a text input, I append a span containing the x icon after the input element. I also do the same input group check as before and also determine if I want to add the has-feedback class.

JavaScript
function onError(inputElement, message) {
    var group = inputElement.closest(classes.groupIdentifier);
    var inputGroup = inputElement.closest('.input-group');
    var helpBlock = '<span class="help-block">' +
            message + '</span>';

    group.find('.form-control-feedback').remove(); //remove old icon span
    if (group.find('.help-block').length > 0) {
        group.find('.help-block').text(message);
    } else {
        if (inputGroup.length > 0) {
            inputGroup.after(helpBlock);
        } else {
            inputElement.after(helpBlock);
        }
    }

    if (inputElement.context.type === 'text') {
        // add new icon to text input
        var errorIcon = '<span class="glyphicon
        glyphicon-remove form-control-feedback" aria-hidden="true"></span>';
        if (inputGroup.length > 0) {
            inputGroup.after(errorIcon);
        } else {
            inputElement.after(errorIcon);
        }
        updateClasses(inputElement, classes.error, classes.success, true);
    } else {
        updateClasses(inputElement, classes.error, classes.success, false);
    }
}

Similar to the onError function, the onSuccess function also adds an icon to text inputs and checks when to add the has-feedback class to the form-group. The main difference is that we want to remove the help-block message if the field is in the success state.

JavaScript
function onSuccess(inputElement) {
    var group = inputElement.closest(classes.groupIdentifier);
    var inputGroup = inputElement.closest('.input-group');

    group.find('.form-control-feedback').remove(); //remove old icon span
    group.find('.help-block').remove();
    if (inputElement.context.type === 'text') {
        // add new icon to text input
        var successIcon = '<span class="glyphicon
        glyphicon-ok form-control-feedback" aria-hidden="true"></span>';
        if (inputGroup.length > 0) {
            inputGroup.after(successIcon);
        } else {
            inputElement.after(successIcon);
        }
        updateClasses(inputElement, classes.success, classes.error, true);
    } else {
        updateClasses(inputElement, classes.success, classes.error, false);
    }
}

Once all of these changes are made and you include the JavaScript library to your application, the end result will look something like this:

Image 2

Points of Interest

Going into this and before stumbling across Cristian's great example, I thought it was going to be difficult trying to change the default functionality of jquery's unobtrusive validation styles. I was pleasantly surprised at how easy it was to do and I am really happy with the result.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)