Friday, August 27, 2010

jQuery Validate and Checkboxes

As I start this post, a quick aside.. I've had some discussions with a few devs lately on the use of the id attribute versus the name attribute on HTML input elements.  IMO, id is a unique identifier, whereas name is used to tie fields such as checkboxes together, which today's post deals with.  The result looks like this:


For what it's worth, I never use the name attribute on input elements except when it is of type checkbox, but I digress... ;)

I had the need to require two or more checkboxes to be checked on a form recently.  I am using jQuery Validate for the form validation logic.  This is the first time I've had to roll my own validator since starting with jQuery Validate as it's defaults are pretty inclusive.  Creating a custom validator is simple.  The code looks like this:
$.validator.addMethod(
 'multiplecheckboxchecked',
 function (value, element) {
  var aChecked = $('input[name='+$(element).attr('name')+']:checked');
  return ( aChecked.length > 1 ) ? true : false;
 }
);
Breaking it down,  use the addMethod() function to define a new rule in the form addMethod(name, callback, message).  In my validation logic I change the color of the container div so I do not need the message attribute.  The name attribute specifies the rule name when we assign it to an element when creating the validator.  The callback is the function we will use to validate the form field.  The callback method gets two parameters by default: value and element.  Value is the value of the form field being validated.  In the case of a checkbox this is a bit weird.  We get only one element passed through which has the name value we specify in our rules definition.  The element is the actual form element, and for a checkbox we can use this to our advantage by grabbing the name attribute to get all of the checkboxes bound by the same name.  Line 4 shows our selector: 'input[name='+$(element).attr('name')+']:checked'.  This tells jQuery to get all input controls with a name matching our checkbox group name that are also checked.  This selector returns an array of elements, and we use the length of that array to tell how many boxes have been checked.  In my case I needed at least two, so I return true if two or more are checked, false if one or less are checked.

Using the rule then looks like this:
rules: {
 frictiontypeid: 'required',
 checkboxgroupname: 'multiplecheckboxchecked'
}
I also had to use a custom highlighter which is simple to override for checkboxes:
highlight: function(element, errorClass, validclass) {
 // if the element is a checkbox, highlight the entire group
 if ( element.type == 'checkbox' ) {
     $(element).parents('.ctrlHolder').addClass('error');
 } else {
  $(element).parent().addClass('error');
 }
},

1 comment:

  1. As an update to this post. With PHP if you include [] at the end of your checkbox group name attribute value then php will pick up the list of checkboxes as an array implicitly. Which is quite cool.

    The repercussions for this script is that you then have to quote the element name in the validator rules list.

    Which then has the further repercussion of breaking your added method.

    The fix is to add another quote in the addmethod to explicitly pass it in as a string. You do this here:

    input[name="'+

    and end it here:

    +'"]:checked');




    //require at least one checked
    $.validator.addMethod( 'multiplecheckboxchecked', function (value, element)
    {
    var aChecked = $('input[name="'+$(element).attr('name')+'"]:checked'); return ( aChecked.length >= 1 ) ? true : false;
    } );


    And the rules for my form.

    $(document).ready(function() {
    $("#FIN_Email_Signup").validate({
    rules: {
    fname: "required",// simple rule, converted to {required:true}
    lname: "required",
    email: {// compound rule
    required: true,
    email: true
    },
    'subs[]': 'multiplecheckboxchecked'
    },
    messages: {
    fname: "Please enter a First name.",
    lname: "Please enter a Last name.",
    email: "You need to enter your email in the format me@email.com.",
    'subs[]': "You must select at least one newsletter to sign up for."

    }
    });
    });

    ReplyDelete