views:

489

answers:

1

I'm using the jQuery Validation plugin on my forms. I have some groups of fields that are optional, but need to be either "all or nothing" - if you fill one input in a group, you must fill all of them.

For example, imagine the user can input one location - street, city, state, and zip - or multiple locations. You don't have to enter a second location, but if you do, it's not OK to just give the city; I need the state and zip for that one too.

To solve this problem, I wrote a custom rule - really just a tiny tweak of my previous rule, require_from_group. This one is called skip_or_fill_minimum. Here's how you'd use it:

var validationrules = {
  rules: {
    location2address: {
    skip_or_fill_minimum: [4,'.location2']
  }
  //This input will validate if all 4 `.location2` inputs are filled,
  //or if all of them are left blank
}

var validator = $("#formtovalidate").validate(validationrules);  

Here's the code for the custom rule:

jQuery.validator.addMethod("skip_or_fill_minimum", function(value, element, options) {
    numberRequired = options[0];
    selector = options[1];
    //Look for our selector within the parent form
    var numberFilled = $(selector, element.form).filter(function() {
         // Each field is kept if it has a value
         return $(this).val();
      }).length;
    var valid;
    (numberFilled >= numberRequired || numberFilled == 0) ? valid = true: valid = false;

    //The elegent part - this element needs to check the others that match the
    //selector, but we don't want to set off a feedback loop where all the
    //elements check all the others which check all the others which
    //check all the others...
    //So instead we
    //  1) Flag all matching elements as 'currently being validated'
    //  using jQuery's .data()
    //  2) Re-run validation on each of them. Since the others are now
    //     flagged as being in the process, they will skip this section,
    //     and therefore won't turn around and validate everything else
    //  3) Once that's done, we remove the 'currently being validated' flag
    //     from all the elements
    if(!$(element).data('being_validated')) {
      var fields = $(selector, element.form);
      //.validate() means "validate using all applicable rules" (which includes this one)
      fields.data('being_validated', true).validate();
      fields.data('being_validated', false);
    }
    return valid;
    // {0} below is the 0th item in the options field
    }, jQuery.format("Please either skip these fields or fill at least {0} of them."));

I'm primarily posting this so that others who search the web for a solution will find it. Still - does anyone see a way to improve this?

+3  A: 

My shortened and "optimized" version (more chaining + use of validator custom provided selector). And hopefully makes less selection operations.

Still suffers from the same bugs your rule does -- what I mean is that the removing of the error class and the error labels on all elems which match our selector is not really correct. Just imagine you require 4 fields to be set but there are 5. User fills 4 fields. The fifth he leaves unfilled but on that field you additionally set required and email. This way the error-message for the email too gets removed although he didn't resolve it yet.

jQuery.validator.addMethod("skip_or_fill_minimum", function(value, element, options) {
    var elems = $(element).parents('form').find(options[1]);
    var numberFilled = elems.filter(':filled').size();
    if (numberFilled >= options[0] || numberFilled == 0) {
        elems.removeClass('error');
        elems.next('label.error').not('.checked').text('').addClass('checked');
        return true;
    } else {
        elems.addClass('error');
    }
}, jQuery.format("Please either skip these fields or fill at least {0} of them."));
jitter
Good suggestions. To some extent, my code was verbose on purpose in order to be a clear example, but I can learn a couple of things from your code. Thank you! Also - you're right about the incorrect behavior removing the errors. Any ideas for a workaround?
Nathan Long
Finally have a workaround - see updated code.
Nathan Long