Nearly 4 years ago, I showed how to "check all checkboxes" using jQuery 1.2.6. In this post, I'm going to show a more advanced, but still simple way to handle checkboxes with jQuery that accounts for changes made as of version 1.6.1. I'll even answer the bracketed field name question that haunts PHP developers.

jQuery version 1.6 introduced a new function, .prop(), for which changes were also made to the .attr() function. The changes to .attr() were reverted in version 1.6.1 due to a backlash related to backwards compatibility. Basically, it broke everything that was already using .attr(). You can read the documentation for the full details but it boils down to this:

  • Use .attr() to read the HTML attribute of an element.
  • Use .prop() to set and read a property of an element. "Properties generally affect the dynamic state of a DOM element without changing the serialized HTML attribute." (emphasis mine)

Here's an example, straight from the jQuery site:

jQuery .attr() vs. .prop()
.attr() Proper .prop() usage
$(window).attr... $(window).prop...
$(document).attr... $(document).prop...
$(":checkbox").attr("checked", true); $(":checkbox").prop("checked", true);
$("option").attr("selected", true); $("option").prop("selected", true);

In my previous post, I used .attr() and .is() as those were the best available functions at the time.

Old and busted
view plain print about
1$("form#" + id + " INPUT[@name=" + name + "][type='checkbox']").attr('checked', false);
2
3$('#checkAllAuto').is(':checked');

Specifications!

This post is going to cover the following:

  1. Check and Uncheck all checkboxes related to a particular "check all" checkbox.
  2. Manage the "check all" box
    1. Uncheck the "check all" box when a related checkbox is un-checked.
    2. Select the "check all" box when all related checkboxes are selected.
  3. Handle the PHP naming convention <input type="checkbox" name="foo[]" />

The end goal is to write generic process to handle ALL of the "check all" functionality throughout your application.

The examples in this post should run on any version of jQuery 1.7 or greater. Also, I tend to switch between "check all" and "select all" quite a bit. Deal with it. :)

Generating the HTML

Let's create a simple column with two columns of checkboxes and a "Select All" checkbox for each column. While this example uses ColdFusion to dynamically generate the HTML, it can be easily translated to PHP, .NET or any other server-side language.

view plain print about
1<html>
2    <head>
3        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
4        <script src="checkall.js"></script>
5    </head>
6    <body>
7        
8        <table border="1">
9            <thead>
10                <tr>
11                    <th>&nbsp;</th>
12                    <th>name="foo"</th>
13                    <th>name="bar"</th>
14                </tr>
15            </thead>
16            <tbody>
17                <cfoutput>
18                    <cfloop from="0" to="9" index="i">
19                        <tr>
20                            <td>#i#</td>
21                            <td><input type="checkbox" id="foo_#i#" name="foo" value="#i#"
22                                     data-select-all="sa_foo" class="checkme" />
</td>
23                            <td><input type="checkbox" id="bar_#i#" name="bar" value="#i#"
24                                     data-select-all="sa_bar" class="checkme" />
</td>
25                        </tr>
26                    </cfloop>
27                </cfoutput>
28            </tbody>
29            <tfoot>
30                <tr>
31                    <td>Select All</td>
32                    <td><input type="checkbox" id="sa_foo" name="sa_foo"
33                                data-checkbox-name="foo" class="selectall" />
</td>
34                    <td><input type="checkbox" id="sa_bar" name="sa_bar"
35                                data-checkbox-name="bar" class="selectall" />
</td>
36                </tr>
37            </tfoot>
38            
39        </table>
40        
41    </body>
42</html>

Here's the generated table:

Doing the "check all" thing
  name="foo" name="bar"
0
1
2
3
4
5
6
7
8
9
Select All

Avoid inline event handlers.

We all learned to use onclick="this();" and onchange="that();" in form fields to call some JavaScript function or other. In my opinion, we should stop doing this when possible. I build a lot of applications where the goal is reuse of code and if you write everything inline, you can't reuse a group of form fields across multiple forms.

I believe that the rules of how a form functions (and that's what we're doing here, writing rules) should be moved to an external JavaScript file. Doing so means that you don't have to go digging into the final HTML to figure out how a form works. This also lets you reuse groups of fields across multiple forms, but gives you the flexibility to easily change how they function.

*steps off the soapbox*

Specification 1: The "check all" checkbox

Let's take a look at the checkboxes in the name="foo" column:

view plain print about
1<input type="checkbox" id="foo_#i#" name="foo" value="#i#" data-select-all="sa_foo" class="checkme" />

Using "i" as the index of my loop,

  1. Each checkbox has a unique ID.
  2. Each has the same NAME.
  3. The unique ID is tied to the VALUE.
  4. The related "check all" checkbox is specified using and HTML "data-" attribute. In this case data-select-all="sa_foo".
  5. Every non-"check all" checkbox has the CLASS "checkme".

As of jQuery 1.4.3, we can read the value of any "data-" attribute using the function .data(). Yes, "data-" is an HTML5 attribute, but this works for non-HTML5 browsers (although your validator might pitch a fit).

Now here's the "check all" checkbox for the name="foo" column:

view plain print about
1<input type="checkbox" id="sa_foo" name="sa_foo" data-checkbox-name="foo" class="selectall" />

If you look at the name="bar" column, you'll see the same setup using "bar" instead of "foo".

checkall.js (v1)

view plain print about
1$(document).ready(function(){ // 1
2    // 2
3    $(':checkbox.selectall').on('click', function(){
4        // 3
5        $(':checkbox[name=' + $(this).data('checkbox-name') + ']').prop("checked", $(this).prop("checked"));
6        
7    });
8    
9});

This code

  1. Does not run until the document is ready to go ( using $(document).ready(); ).
     
  2. Uses the jQuery ":checkbox" selector, restricted to those with the class of "selectall" and adds a CLICK event to them using the jQuery 1.7 .on() function. (Previous to 1.7, you can use .click().)
     
  3. The closure (anonymous function) assigned to the "select all" checkbox
    1. Selects all of the ":checkbox" form elements with a NAME matching its "data-checkbox-name" attribute,
    2. then calls the .prop() function and assigns to each of them the same "checked" property it has.

So if the "check all" checkbox has a property of checked="true", then so do all of its related checkboxes. If it's "false", they're all checked="false".

Specification 2: Manage the "check all" checkbox.

Now that you can check all of the individual checkboxes at once, what happens when one of them is unchecked? The "check all" box should be unchecked too. Also, if all of the individual checkboxes are manually checked, then the "check all" should be checked as well.

By relating the individual checkboxes to the "check all" checkbox using a "data-" attribute, this is also pretty straight forward:

checkall.js (v2)
view plain print about
1$(document).ready(function(){
2    
3    $(':checkbox.selectall').on('click', function(){
4        $(':checkbox[name=' + $(this).data('checkbox-name') + ']').prop("checked", $(this).prop("checked"));
5    });
6    // Individual checkboxes
7    $(':checkbox.checkme').on('click', function(){ // 1
8
9        var _this = $(this); // 2
10        var _selectall = _this.prop("checked"); //3
11
12        if ( _selectall ) { // 4
13            // 5
14            $( ':checkbox[name=' + _this.attr('name') + ']' ).each(function(i){
15                // 6
16                _selectall = $(this).prop("checked");
17                return _selectall; // 7
18            });
19
20        }
21        
22        // 8
23        $(':checkbox[name=' + $(this).data('select-all') + ']').prop("checked", _selectall);
24    });
25    
26});

This code also waits until the document is ready to go. Then,

  1. It uses the jQuery ":checkbox" selector, restricted to those with the class of "checkme" and adds a CLICK event to them using the .on() function.
  2. Inside the closure, for sake of this example, I've created a private variable "_this" equal to $(this), which is a pointer to the current $(':checkbox') object.
  3. Next, create a private variable "_selectall" and assign it the value of the "checked" property (true or false) of the current checkbox.
  4. If this checkbox has a property of "checked" equal to true
  5. then loop through all checkboxes with the same NAME (using another closure; within this closure, $(this) refers to the $(':checkbox') object on the previous line.)
  6. Set "_selectall" to the "checked" property of the current checkbox.
  7. By returning "_selectall" (true or false) in each iteration of the .each() loop, if the current individual checkbox has a property of "checked" that is false, we break out of the loop and continue
  8. Finally, use the value of "data-select-all" in the current "checkme" checkbox to find its related "check all" checkbox and set its "checked" property. If any or the related individual checkboxes is unchecked, then so should be the "check all" box.

Specification 3: PHP and form field names using brackets.

If a form has a group of fields with the same name, when it is submitted, the values of those fields are sent as multiple parameters to the server. For example, if we selected all name="foo" fields and submitted them to foo.htm using a GET, this is the result:

view plain print about
1foo.htm?foo=0&foo=1&foo=2&foo=3&foo=4&foo=5&foo=6&foo=7&foo=8&foo=9&sa_foo=on

If the ACTION on the form was foo.cfm, the query string would be the same, but the ColdFusion parser can reference a URL variable "foo" whose value would be "0,1,2,3,4,5,6,7,8,9".

Now, if the ACTION was foo.php, the PHP parser would set the value of the variable "foo" to that the last instance of "foo" in the query string. So in this case, PHP would say that foo = 9.

HOWEVER, if we changed the NAME of the individual checkboxes from name="foo" to name="foo[]", the square brackets tell PHP that foo is an ARRAY with 10 elements in it, ranging from values 0 through 9.

view plain print about
1<input type="checkbox" id="foo_#i#" name="foo[]" value="#i#" data-select-all="sa_foo" class="checkme" />
2
3<input type="checkbox" id="sa_foo" name="sa_foo" data-checkbox-name="foo[]" class="selectall" />

But this causes a problem with JavaScript and in this case jQuery. Defining a jQuery selector using name="foo[]" causes an error (message from Chrome):

view plain print about
1$(':checkbox[name=' + $(this).data('checkbox-name') + ']');

Uncaught Error: Syntax error, unrecognized expression: [name=foo[]]

The above code is equivalent to this:

view plain print about
1$(':checkbox[name=foo[]]');

which throws an "unrecognized expression" error because the JavaScript parser thinks that foo[] is a JavaScript variable of type array which has not yet been defined.

So this is an easy fix:

view plain print about
1$(':checkbox[name="' + $(this).data('checkbox-name') + '"]');

can you see that? It's fixed by using double quotes around your attribute value. The code is now equivalent to this:

view plain print about
1$(':checkbox[name="foo[]"]');

to which the JavaScript parser says (in a very "Alan Rickman as Marvin the Robot" style voice), "Wow. Another string value. How very boring."

Final checkall.js

Here's the final version of checkall.js with "_this" and blank lines removed.

view plain print about
1$(document).ready(function(){
2    $(':checkbox.selectall').on('click', function(){
3        $(':checkbox[name="' + $(this).data('checkbox-name') + '"]').prop("checked", $(this).prop("checked"));
4    });
5    $(':checkbox.checkme').on('click', function(){
6        var _selectall = $(this).prop("checked");
7        if ( _selectall ) {
8            $( ':checkbox[name="' + $(this).attr('name') + '"]' ).each(function(i){
9                _selectall = $(this).prop("checked");
10                return _selectall;
11            });
12        }
13        $(':checkbox[name="' + $(this).data('select-all') + '"]').prop("checked", _selectall);
14    });
15});

In conclusion . . .

The "check all" checkbox is a standard user interface widget. It is easy to define similar widgets using jQuery to access standard attributes & properties, along with common (if not yet standard) HTML5-style data attributes, of common elements throughout your application. By defining an application-wide library of widgets, it is possible to shorten your development time lines.

I'll be posting more jQuery examples soon. If there's anything you'd like to see, please let me know via the Contact page.