Validator Plugin for Bootstrap #202

Closed
Tornth opened this Issue Sep 13, 2011 · 18 comments

Projects

None yet
@Tornth
Tornth commented Sep 13, 2011

What is the best jquery validator plugin to be used with Bootstrap. I personally like the error style that the CSS output but it cannot be done via Jquery Validation - http://docs.jquery.com/Plugins/Validation

@fat
Member
fat commented Sep 13, 2011

Could you ask this on the mailing list? Thanks! :)

@fat fat closed this Sep 13, 2011
@anyulled

it can be done this way

$('form').validate({
    errorClass:'error',
    validClass:'success',
    errorElement:'span',
    highlight: function (element, errorClass, validClass) { 
        $(element).parents("div[class='clearfix']").addClass(errorClass).removeClass(validClass); 
    }, 
    unhighlight: function (element, errorClass, validClass) { 
        $(element).parents(".error").removeClass(errorClass).addClass(validClass); 
    }
});
@arathael
arathael commented Feb 6, 2012

Do we have any changes with bootstrap 2.0?

@AndroBrgo

This is how I made it happen. But it involves making 2 changes to the validate script. (I got the code for bootstrap 1.4 here and then modified it - http://mihirchitnis.net/2012/01/customizing-error-messages-using-jquery-validate-plugin-for-twitter-bootstrap/)

My call to validate:

    $("#loginForm").validate({
  errorClass: "control-group error",
  validClass: "control-group success",
  errorElement: "span", // class='help-inline'
  highlight: function(element, errorClass, validClass) {
    if (element.type === 'radio') {
        this.findByName(element.name).parent("div").parent("div").removeClass(validClass).addClass(errorClass);
    } else {
        $(element).parent("div").parent("div").removeClass(validClass).addClass(errorClass);
    }
  },
  unhighlight: function(element, errorClass, validClass) {
    if (element.type === 'radio') {
        this.findByName(element.name).parent("div").parent("div").removeClass(errorClass).addClass(validClass);
    } else {
        $(element).parent("div").parent("div").removeClass(errorClass).addClass(validClass);
    }
  }
});

Then you need to change 2 things in jquery.validate.js

  1. apply this fix - bsrykt/jquery-validation@6c3f53e
  2. After line 647 (in the showLabel function, create label part) after line .addClass(this.settings.errorClass) add line: .addClass("help-inline")

    Someone can maybe find a way to apply the second fix in the validate function, but I havent found a way, since showLabel is called after highlight.
@tonycoco
tonycoco commented Feb 7, 2012

Put this in to override your JQuery validator...

$.extend($.validator.messages, {
  required: "can't be blank",
  remote: 'needs to get fixed',
  email: 'is an invalid email address',
  url: 'is not a valid URL',
  date: 'is not a valid date',
  dateISO: 'is not a valid date (ISO)',
  number: 'is not a valid number',
  digits: 'needs to be digits',
  creditcard: 'is not a valid credit card number',
  equalTo: 'is not the same value again',
  accept: 'is not a value with a valid extension',
  maxlength: jQuery.validator.format('needs to be more than {0} characters'),
  minlength: jQuery.validator.format('needs to be at least {0} characters'),
  rangelength: jQuery.validator.format('needs to be a value between {0} and {1} characters long'),
  range: jQuery.validator.format('needs to be a value between {0} and {1}'),
  max: jQuery.validator.format('needs to be a value less than or equal to {0}'),
  min: jQuery.validator.format('needs to be a value greater than or equal to {0}')
});

$.extend($.validator.prototype, {
  showLabel: function(element, message) {
    var label = this.errorsFor( element );

    if (label.length == 0) {
      var railsGenerated = $(element).next('span.help-inline');
      if (railsGenerated.length) {
        railsGenerated.attr('for', this.idOrName(element))
        railsGenerated.attr('generated', 'true');
        label = railsGenerated;
      }
    }

    if (label.length) {
      // refresh error/success class
      label.removeClass(this.settings.validClass).addClass(this.settings.errorClass);
      // check if we have a generated label, replace the message then
      label.attr('generated') && label.html(message);
    } else {
      // create label
      label = $('<' + this.settings.errorElement + '/>')
        .attr({'for':  this.idOrName(element), generated: true})
        .addClass(this.settings.errorClass)
        .addClass('help-inline')
        .html(message || '');
      if (this.settings.wrapper) {
        // make sure the element is visible, even in IE
        // actually showing the wrapped element is handled elsewhere
        label = label.hide().show().wrap('<' + this.settings.wrapper + '/>').parent();
      }
      if (!this.labelContainer.append(label).length)
        this.settings.errorPlacement
          ? this.settings.errorPlacement(label, $(element))
          : label.insertAfter(element);
    }
    if (!message && this.settings.success) {
      label.text('');
      typeof this.settings.success == 'string'
        ? label.addClass(this.settings.success)
        : this.settings.success(label);
    }
    this.toShow = this.toShow.add(label);
  }
});

$(document).ready(function() {
  $('form.validate').validate({
    errorClass: 'error',
    validClass: 'success',
    errorElement: 'span',
    highlight: function(element, errorClass, validClass) {
      if (element.type === 'radio') {
        this.findByName(element.name).parent('div').parent('div').removeClass(validClass).addClass(errorClass);
      } else {
        $(element).parent('div').parent('div').removeClass(validClass).addClass(errorClass);
      }
    },
    unhighlight: function(element, errorClass, validClass) {
      if (element.type === 'radio') {
        this.findByName(element.name).parent('div').parent('div').removeClass(errorClass).addClass(validClass);
      } else {
        $(element).parent('div').parent('div').removeClass(errorClass).addClass(validClass);
      }
    }
  });
});

Then on your forms just add a class of "validate".

@imom0
imom0 commented Feb 17, 2012
  unhighlight: function(element, errorClass, validClass) {
      if (element.type === 'radio') {
        this.findByName(element.name).parent('div').parent('div').removeClass(errorClass).addClass(validClass);
      } else {
        $(element).parent('div').parent('div').removeClass(errorClass).addClass(validClass);
        $(element).next('span.help-inline').text('');
      }
    }

add a line to make previous wrong messages not display when field is corrected.

@endel
endel commented Feb 24, 2012

Thanks guys!
Just to complement what @tonycoco said, it's possible to override the defaults validator configuration:

$.extend($.validator.defaults, {
    errorClass: 'error',
    validClass: 'success',
    errorElement: 'span',
...

So you can just call $('#awesome-form').validate() for any form in your app

@madebyreformat

Has anyone tried this from forms within a modal, seems to just close the window for some reason

@nfaiz
nfaiz commented Mar 10, 2012

does not work with prepended and appended text

@lstone
lstone commented Apr 6, 2012

Here's everything together with fixed code for prepend and append text:

$.extend($.validator.messages, {
required: "can't be blank",
remote: 'needs to get fixed',
email: 'is an invalid email address',
url: 'is not a valid URL',
date: 'is not a valid date',
dateISO: 'is not a valid date (ISO)',
number: 'is not a valid number',
digits: 'needs to be digits',
creditcard: 'is not a valid credit card number',
equalTo: 'is not the same value again',
accept: 'is not a value with a valid extension',
maxlength: jQuery.validator.format('needs to be more than {0} characters'),
minlength: jQuery.validator.format('needs to be at least {0} characters'),
rangelength: jQuery.validator.format('needs to be a value between {0} and {1} characters long'),
range: jQuery.validator.format('needs to be a value between {0} and {1}'),
max: jQuery.validator.format('needs to be a value less than or equal to {0}'),
min: jQuery.validator.format('needs to be a value greater than or equal to {0}')
});

/**

  • Adapt validator plugin to work with twitter bootstrap
    */
    $.extend($.validator.prototype, {
    showLabel: function(element, message) {
    var label = this.errorsFor( element );

    if (label.length == 0) {
        var railsGenerated = $(element).next('span.help-inline');
        if (railsGenerated.length) {
            railsGenerated.attr('for', this.idOrName(element))
            railsGenerated.attr('generated', 'true');
            label = railsGenerated;
        }
    }
    
    if (label.length) {
        // refresh error/success class
        label.removeClass(this.settings.validClass).addClass(this.settings.errorClass);
        // check if we have a generated label, replace the message then
        label.attr('generated') && label.html(message);
    } else {
        // create label
        label = $('<' + this.settings.errorElement + '/>')
            .attr({'for':  this.idOrName(element), generated: true})
            .addClass(this.settings.errorClass)
            .addClass('help-inline')
            .html(message || '');
        if (this.settings.wrapper) {
            // make sure the element is visible, even in IE
            // actually showing the wrapped element is handled elsewhere
            label = label.hide().show().wrap('<' + this.settings.wrapper + '/>').parent();
        }
        if (!this.labelContainer.append(label).length)
            this.settings.errorPlacement
                ? this.settings.errorPlacement(label, $(element))
                : label.insertAfter(element);
    }
    if (!message && this.settings.success) {
        label.text('');
        typeof this.settings.success == 'string'
            ? label.addClass(this.settings.success)
            : this.settings.success(label);
    }
    this.toShow = this.toShow.add(label);
    

    }
    });

/**

  • Setting custom validator defaults to work with twitter bootstrap
    */
    $.extend($.validator.defaults, {
    errorClass: 'error',
    validClass: 'success',
    errorElement: 'span',
    highlight: function(element, errorClass, validClass) {
    if (element.type === 'radio') {
    this.findByName(element.name).parent('div').parent('div').removeClass(validClass).addClass(errorClass);
    } else if($(element).parent('div.input-prepend, div.input-append').length > 0) {
    $(element).parent('div').parent('div').parent('div').removeClass(validClass).addClass(errorClass);
    } else {
    $(element).parent('div').parent('div').removeClass(validClass).addClass(errorClass);
    }
    },
    unhighlight: function(element, errorClass, validClass) {
    if (element.type === 'radio') {
    this.findByName(element.name).parent('div').parent('div').removeClass(errorClass).addClass(validClass);
    } else if($(element).parent('div.input-prepend, div.input-append').length > 0) {
    $(element).parent('div').parent('div').parent('div').removeClass(errorClass).addClass(validClass);
    $(element).next('span.help-inline').text('');
    } else {
    $(element).parent('div').parent('div').removeClass(errorClass).addClass(validClass);
    $(element).next('span.help-inline').text('');
    }
    }
    });
@kwilliams

The version above gave me some problems with input-append markup. The error was being shoved in between the add-on and the form element.

$.extend($.validator.prototype, {
    showLabel: function(element, message) {
        var label = this.errorsFor( element );

        if (label.length == 0) {
            var railsGenerated = $(element).next('span.help-inline');
            if (railsGenerated.length) {
                railsGenerated.attr('for', this.idOrName(element))
                railsGenerated.attr('generated', 'true');
                label = railsGenerated;
            }
        }

        if (label.length) {
            // refresh error/success class
            label.removeClass(this.settings.validClass).addClass(this.settings.errorClass);
            // check if we have a generated label, replace the message then
            label.attr('generated') && label.html(message);
        } else {
            // create label
            label = $('<' + this.settings.errorElement + '/>')
                  .attr({'for':  this.idOrName(element), generated: true})
                  .addClass(this.settings.errorClass)
                  .addClass('help-inline')
                  .html(message || '');
            if (this.settings.wrapper) {
                // make sure the element is visible, even in IE
                // actually showing the wrapped element is handled elsewhere
                label = label.hide().show().wrap('<' + this.settings.wrapper + '/>').parent();
            }
            if (!this.labelContainer.append(label).length) {
                this.settings.errorPlacement
                    ? this.settings.errorPlacement(label, $(element))
                    : label.insertAfter(element);
            }
        }
        if (!message && this.settings.success) {
            label.text('');
            typeof this.settings.success == 'string'
                ? label.addClass(this.settings.success)
                : this.settings.success(label);
        }
        this.toShow = this.toShow.add(label);
    }
});

/**

Setting custom validator defaults to work with twitter bootstrap */ 
$.extend($.validator.defaults, {
    errorClass: 'error',
    validClass: 'success',
    errorElement: 'span',
    highlight: function (element, errorClass, validClass) {
        if (element.type === 'radio') {
            this.findByName(element.name).closest('div.control-group').removeClass(validClass).addClass(errorClass);
        }else {
            $(element).closest('div.control-group').removeClass(validClass).addClass(errorClass);
        }
    },
    unhighlight: function (element, errorClass, validClass) {
        var addon = $(element).parent('div.input-prepend, div.input-append');

        if (element.type === 'radio') {
            this.findByName(element.name).parent('div').parent('div').removeClass(errorClass).addClass(validClass);
        } else {
            $(element).closest('div.control-group').removeClass(errorClass).addClass(validClass);
            $(element).next('span.help-inline').text('');
        }
    },
    errorPlacement: function(error, element) {
        var isInputAppend = ($(element).parent('div.input-append').length > 0);
        if (isInputAppend) {
            appendElement = $(element).next('span.add-on');
            error.insertAfter(appendElement);
        }else {
            error.insertAfter(element);
        }
    },
});
@ScottPhillips

Anyone get this to work with placeholder? Seems to screw up the validation.

@chillenious

@kwilliams code works good, except not when using input-prepend. I'm using the following errorPlacement instead, and that seems to work well (not actually tested with input-append):

    errorPlacement: function(error, element) {
        var wrapperElement = $(element).parent('div.input-append');
        if (wrapperElement.length == 0) {
            wrapperElement = $(element).parent('div.input-prepend');
        }
        if (wrapperElement.length > 0) {
            error.insertAfter(wrapperElement);
        } else {
            error.insertAfter(element);
        }
    }
@Seanom
Seanom commented Aug 28, 2012

I struggled with the placement for the Radio button error using the bootstrap style, and a horizontal-form so used the following to place the error after the radio buttons, using the methods above the error was placed after the first radio input

      errorPlacement: function(error, element) {
            var isInputAppend = ($(element).parent('div.input-append').length > 0);
            var isRadio = ($(element).attr('type') == 'radio');
            if(isRadio){
                afterElement = $(element).closest('div.controls').children("label").filter(":last");
                error.insertAfter(afterElement);
            }else if (isInputAppend) {
                appendElement = $(element).next('span.add-on');
                error.insertAfter(appendElement);
            }else {
                error.insertAfter(element);
            }
        }
@simonox
simonox commented Sep 13, 2012

@kwilliams: Thank you, works very nice :-)

@neutralbay

What about specifically in a modal (using .get)? Can't get it to work. I know I can when adding a query.validate to the head of the remote file with fancybox, but when using bootstrap modal functionality, nothing works. I read about binding, but can't get to the answer. Thanks... For now I have a "fake" bootstrap modal - looks like bootstrap, but actually is fancy box, purely so I can validate my modal forms.

@pauldotknopf

I have created a single javascript file that you reference after jquery.validation.js. Simple adding this reference will make your website (with no code change) validate with twitter bootstrap markup/css. Very unobtrusive and easy to use.

http://pknopf.com/blog/twitter-bootstrap-clientside-validation-with-jquery-validation-js

@cvrebert cvrebert locked and limited conversation to collaborators Jul 28, 2014
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.