I was recently working on a simple feature in our web client at work. Part of the feature required that the user be able to select one to many items from a multiple select list. My first stab at this worked great; I added the required
class to the select
element and voila, the user was limited to at least one item. However, I thought it would be nice to give a custom error message with a little more context than just "this field is required" and perhaps provide the ability to limit the selection to a variable minimum and maximum length.
I quickly discovered that I don't understand the jQuery Validation plug-in and the documentation seems to be written by someone who thinks I might know way more than I actually do1. My next step was to find examples but they were all related to comparing element values on more standard input
elements, which just served to confuse me further, so I embarked on a quest to find out how to solve my problem and write a blog about it.
Now, I'm sure that what I am about to document will seem like child's play to the jQuery ninjas out there, but for every ninja equipped with the skills to silently dispatch every henchman in the room, there's someone like me who just wants to get out of the room alive. So, please bear that in mind as you read on and whether a ninja or just a survivor, please comment.
<!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta charset="utf-8" /> <title>My jQuery real good fun time yeah page</title> <script src="http://code.jquery.com/jquery-1.9.1.js" type="text/javascript"></script> <script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.11.1/jquery.validate.js" type="text/javascript"></script> <script type="text/javascript"> /* Placeholder for my amazing coding skills. */ </script> </head> <body> <form id="mySuperCoolForm"> <fieldset> <legend>Select some stuff</legend> <select name="things" multiple="multiple"> <option>thing 1</option> <option>thing 2</option> <option>thing 3</option> <option>thing 4</option> <option>thing 5</option> </select> <input type="submit" /> </fieldset> </form> </body> </html>
This is the simple HTML page that I'm going to work with. There's nothing complex here, just a form
with a select
and a submit input
. I have also added some script imports for jQuery and the jQuery validate plug-in and an empty script element for our validation definitions to live.
To this, we want to add some validation to make sure that at least one element in the list is selected when the form is submitted. First, just to check things are wired up correctly, we will just specify the select
named "things" as being required. Rather than do this by just adding the required
class, let's use some scripting, that way we can manipulate the script as we go.
$(function () { $('#mySuperCoolForm').validate({ rules:{ things:{ required:true } } }); });
This code is not doing much. It is telling the jQuery validate plug-in to attach its validation to our form, mySuperCoolForm
with the rule that our field, things
is required. If you try this out and click the submit button without selecting anything, you'll get a message stating, "This field is required." Not a very descriptive message, so let's fix that by adding a message for our "things are required" rule.
$(function () { $('#mySuperCoolForm').validate({ rules: { things: { required: true } }, messages: { things: { required: 'Please select at least one thing.' } } }); });
Again, not a very complex change. I have added a messages
field to the JSON object being passed to the validate
method. This field provides messages that should be displayed when validation rules fail. In this case, we are stating we want at least one thing to be selected whenever our required
rule fails. You should note the correlation here between the name of our field, things
and the rules and messages that are attached to it. This is important to understanding how to manipulate the jQuery validation. When a rule fails to validate for a named field, the message under the same named field with the matching name to that failed rule will be displayed.
That works nicely so it's job done, right? Not quite. You see, while this works for the cases where one to many items need selecting, it feels a little hacky. I'd rather say explicitly "I want x items selected" rather than rely on the required rule working the way it does for multiple select scenarios. I like being explicit. So, let's get explicit2.
Validating with minlength
It just so happens that the jQuery validation plug-in has another rule, minlength
that appears to be exactly what we want. Thinking this, I naively wrote some code to require at least two items. I also included some string formatting for the error message so that the number of things in the message matched the number required by the rule.
$(function () { $('#mySuperCoolForm').validate({ rules: { things: { minlength: 2 } }, messages: { things: { minlength: $.format('Please select at least {0} things.') } } }); });
Now, if we select two things, we can submit just fine, but if we select just one thing, we get told we need at least two. So far so good. However, if we select nothing, the form passes validation and submits fine! What? We stated a minimum of two and yet zero passes. That makes no sense. It made even less sense when I set my minlength
to 1
and it didn't appear to do any validation at all. Everything was feeling so intuitive, what happened? By looking at the code, it becomes clearer.
The minlength
rule looks like this:
minlength: function( value, element, param ) { var length = $.isArray( value ) ? value.length : this.getLength($.trim(value), element); return this.optional(element) || length >= param; }
If you put a breakpoint here, you'll discover that when nothing is selected, value
is null
, but that's okay because length
becomes `0` in that circumstance anyway. The problem is on the second line of the function, which I have highlighted. It checks to see if the element is optional or not and if it is optional, the element passes validation regardless. A quick look at the optional
function shows that it calls the required
rule and returns the inverse of its result. When we have no items selected, required
returns false
which optional
turns to true
which incorrectly says our data is valid. However, when only one item is selected, required
returns true
which means the `length >= param` check occurs and our validation fails as we would like. While this behaviour is probably intuitive for say, string input
fields where no value and values of a certain minimum length make sense, this is confusing when using minlength
with select
fields.
To get our validation working as we would like, we have to apply both the required
and minlength
rules as follows:
$(function () { $('#mySuperCoolForm').validate({ rules: { things: { required: true, minlength: 2 } }, messages: { things: { required: 'Please select at least 2 things.' minlength: $.format('Please select at least {0} things.') } } }); });
Unfortunately, now we've lost the nice dynamic nature of our error messages. If we change the minlength
rule to 3
, we have to remember to edit the message for required
. It seems we have gone from "feels hacky" to "feels hackier".
What I really want is for required
to behave differently when I have the minlength
rule applied to a select
element. When using minlength
on a select
element, I want required
to default to true
but my error message to come from the minlength
message format. This would feel far more intuitive for this use case than the current behaviour does. So, given that the plug-in does not do this, what can we do to fix it?
Implementing an intuitive minlength
The jQuery Validation plug-in provides the ability to add custom validation methods. Not only does this allow the addition of new methods, but it also allows for the replacement of existing methods. This gives us two options:
- Create a new
minlengthofselection
or similar that follows the rules we want. - Override the existing
minlength
that does things the way we want.
While the first option is probably safest for those already familiar with the behaviour as it stands, option two is more fun. Guess which one I did3.
$(function () { $.validator.addMethod('minlength', function (value, element, param) { var length = $.isArray(value) ? value.length : this.getLength($.trim(value), element); if (element.nodeName.toLowerCase() === 'select' && this.settings.rules[$(element).attr('name')].required !== false) { // could be an array for select-multiple or a string, both are fine this way return length >= param; } return this.optional(element) || length >= param; }, $.format('Please select at least {0} things.')); $('#mySuperCoolForm').validate({ rules: { things: { minlength: 2 } }, messages: { things: { minlength: $.format('Please select at least {0} things.') } } }); });
In this final update to the code, I provided a new implementation to the validator for minlength
. This new version is almost identical to the default except that when checking a select
element, it looks to see if the field is explicitly not required, thereby assuming that we want something to be selected. Finally, we have a minlength
validation check that works intuitively for our scenario as well as the more common scenarios surround string lengths and we don't have to hard code numbers into the error message.
Conclusion
In conclusion, I ran into what is probably a corner case when it comes to using jQuery Validation where things were not as intuitive as I had hoped. In doing so, I was able to learn more about the plug-in and eventually, resolve my specific issue.
- Have you had a similar problem?
- Do you have improvements to suggest?
- Should I submit this minor change for inclusion in the jQuery Validation plug-in?
Please, comment and let me know.
Acknowledgements
I would like to thank, alen, the author of this tutorial which gave me a foundation to work from when investigating and resolving this issue.