I'm using the excellent jQuery Validate Plugin to validate some forms. On one form, I need to ensure that the user fills in at least one of a group of fields. I think I've got a pretty good solution, and wanted to share it. Please suggest any improvements you can think of.
Finding no built-in way to do this, I searched and found Rebecca Murphey's custom validation method, which was very helpful.
I improved this in three ways:
- To let you pass in a selector for the group of fields
- To let you specify how many of that group must be filled for validation to pass
- To show all inputs in the group as passing validation as soon as one of them passes validation. (See shout-out to Nick Craver at end.)
So you can say "at least X inputs that match selector Y must be filled."
The end result is a rule like this:
partnumber: {
require_from_group: [2,".productinfo"]
}
//The partnumber input will validate if
//at least 2 `.productinfo` inputs are filled
Item #3 assumes that you're adding a class of .checked
to your error messages upon successful validation. You can do this as follows, as demonstrated here.
success: function(label) {
label.html(" ").addClass("checked");
}
As in the demo linked above, I use CSS to give each span.error
an X image as its background, unless it has the class .checked
, in which case it gets a check mark image.
Here's my code so far:
jQuery.validator.addMethod("require_from_group", function(value, element, options) {
numberRequired = options[0];
selector = options[1];
//Look for our selector within the parent form
var validOrNot = $(selector, element.form).filter(function() {
// Each field is kept if it has a value
return $(this).val();
// Set to true if there are enough, else to false
}).length >= numberRequired;
//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);
//.valid() means "validate using all applicable rules" (which
//includes this one)
fields.data('being_validated', true).valid();
fields.data('being_validated', false);
}
return validOrNot;
// {0} below is the 0th item in the options field
}, jQuery.format("Please fill out at least {0} of these fields."));
Hooray!
Shout out
Now for that shout-out - originally, my code just blindly hid the error messages on the other matching fields instead of re-validating them, which meant that if there was another problem (like 'only numbers are allowed and you entered letters'), it got hidden until the user tried to submit. This was because I didn't know how to avoid the feedback loop mentioned in the comments above. I knew there must be a way, so I asked a question, and Nick Craver enlightened me. Thanks, Nick!
Question Solved
This was originally a "let me share this and see if anybody can suggest improvements" kind of question. While I'd still welcome feedback, I think it's pretty complete at this point. (It could be shorter, but I want it to be easy to read and not necessarily concise.) So just enjoy!