To read the article online, visit http://www.4GuysFromRolla.com/articles/053106-1.aspx

Checking All CheckBoxes in a GridView Using Client-Side Script and a Check All CheckBox

By Scott Mitchell


An Updated Version Is Available!
In December 2010 I updated the code and concepts presented in this article to use unobtrusive JavaScript and the jQuery library. To learn more, read Checking All Checkboxes in a GridView Using jQuery.

Introduction


Last week I authored an article titled Checking All CheckBoxes in a GridView that illustrated how to add functionality to check all CheckBoxes in a GridView at the click of a button. Specifically, the article examined how to accomplish this using both server-side code and client-side script. In both versions, the page contained two buttons - "Check All" and "Uncheck All" - that, when clicked, caused all of the checkboxes to become checked or unchecked. In the server-side code version, clicking either of these buttons caused a postback and programmatically checked or unchecked all of the CheckBoxes. In the client-side version, the checking and unchecking was handled with a bit of JavaScript code.

In the code download that accompanied Checking All CheckBoxes in a GridView I included an example that improved the user interface with the client-side script approach to include a "Check/Uncheck All" checkbox in the header above the column of CheckBoxes in the GridView (see the screen shot to the right). Checking the header checkbox would check all checkboxes in the GridView, while unchecking the header checkbox would have the opposite effect. Adding such functionality was a bit more tricky than I had at first anticipated due to a couple of subtleties. In this article we'll explore these issues and the workarounds I employed to overcome them. Read on to learn more!

If you've not yet read the original article, Checking All CheckBoxes in a GridView, please take a moment to read that first before continuing on with this article...

Adding a "Check/Uncheck All" CheckBox to the Header


The first step in implementing this user experience is to add the "Check/Uncheck All" checkbox to the header of the column of CheckBoxes. Since we implemented the column of CheckBoxes in the GridView as a TemplateField, adding a header CheckBox control is as simple as adding a HeaderTemplate to the TemplateField, like so (some formatting-related GridView properties have been removed for brevity):

<asp:GridView ID="FileList" runat="server"
    AutoGenerateColumns="False" DataKeyNames="FullName">
    <Columns>
        <asp:TemplateField>
            <HeaderTemplate>
                <asp:CheckBox runat="server" ID="HeaderLevelCheckBox" />
            </HeaderTemplate>
            <ItemTemplate>
                <asp:CheckBox runat="server" ID="RowLevelCheckBox" />
            </ItemTemplate>
        </asp:TemplateField>
        
        <asp:BoundField DataField="Name" HeaderText="Name" />
        ... Remaining BoundFields ...
    </Columns>
</asp:GridView>

This addition puts a CheckBox in the header of the column of CheckBoxes; however, clicking the header CheckBox doesn't do anything. Upon having the header CheckBox checked we want to check all of the CheckBoxes in the grid; similarly, when unchecking the header CheckBox we want all of the CheckBoxes in the grid to be unchecked. Moreover, to provide a snappy user experience, all of this needs to be done on the client-side using JavaScript.

In Checking All CheckBoxes in a GridView we looked at a JavaScript function named ChangeAllCheckBoxStates(checkState) that checked (or unchecked) all of the CheckBoxes in the grid depending on the value of the checkState input parameter. In the client-side demo from that previous article, this function was called by the "Check All" and "Uncheck All" buttons passing in values of true and false for the checkState input parameter, respectively. My initial idea was to simply reuse this JavaScript function with the header CheckBox. Namely, the client-side onclick event for the header CheckBox would call ChangeAllCheckBoxStates passing in this.checked as the value of checkState.

To accomplish this I wired up the header CheckBox's client-side onclick event using the following server-side code in the GridView's DataBound event handler:

Protected Sub FileList_DataBound(ByVal sender As Object, ByVal e As System.EventArgs) Handles FileList.DataBound
   'Each time the data is bound to the grid we need to build up the CheckBoxIDs array

   'Get the header CheckBox
   Dim cbHeader As CheckBox = CType(FileList.HeaderRow.FindControl("HeaderLevelCheckBox"), CheckBox)

   'Run the ChangeCheckBoxState client-side function whenever the
   'header checkbox is checked/unchecked
   cbHeader.Attributes("onclick") = "ChangeAllCheckBoxStates(this.checked);"

   For Each gvr As GridViewRow In FileList.Rows
     'Get a programmatic reference to the CheckBox control
     Dim cb As CheckBox = CType(gvr.FindControl("RowLevelCheckBox"), CheckBox)

     'Add the CheckBox's ID to the client-side CheckBoxIDs array
     ClientScript.RegisterArrayDeclaration("CheckBoxIDs", String.Concat("'", cb.ClientID, "'"))
   Next
End Sub

The For Each loop builds up the CheckBoxIDs client-side array, which is iterated through in the ChangeAllCheckBoxStates function to check or uncheck all of the grid's CheckBoxes. Note that I grab a reference to the header CheckBox by searching the GridView's HeaderRow's control collection for the control with ID HeaderLevelCheckBox. (This was the value of the ID property of the header CheckBox in the declarative syntax examined earlier in this article.)

Once I get a reference to the header CheckBox I indicate that the client-side onclick event handler should invoke the ChangeAllCheckBoxStates passing in this.checked as the value of checkState by assigning the JavaScript to invoke to the CheckBox's Attributes collection's onclick setting. The Attributes collection provides a way to specify additional attribute values for the control's rendered HTML; see Working with Client-Side Script for more information on working with client-side HTML attributes and events using server-side ASP.NET code.

With this addition, checking the header CheckBox calls ChangeAllCheckBoxStates(true), checking all of the CheckBoxes in the grid; conversely, unchecking the header CheckBox calls ChangeAllCheckBoxStates(false), unchecking all of the CheckBoxes.

Problem #1: The "Check All" and "Uncheck All" Buttons Don't Check/Uncheck the Header CheckBox


After making this change, the first issue I found was that checking the "Check All" and "Uncheck All" buttons on the page checked (or unchecked) all of the CheckBoxes in the grid except for the header CheckBox. This is because these two buttons call ChangeAllCheckBoxStates and ChangeAllCheckBoxStates simply iterates through the CheckBoxIDs array, checking or unchecking all of the CheckBoxes registered in that array. Since the header CheckBox is not included in that array, it's not affected by the ChangeAllCheckBoxStates function. (Of course this isn't an issue when directly checking or unchecking the header CheckBox since the user-based action of checking or unchecking the header CheckBox handles this.)

To remedy this I simply added the header CheckBox to the list of IDs in the CheckBoxIDs array. This was accomplished by adding the following line of code to the GridView's DataBound event handler immediately after setting the header CheckBox's Attributes("onclick") property:

'Add the CheckBox's ID to the client-side CheckBoxIDs array
ClientScript.RegisterArrayDeclaration("CheckBoxIDs", String.Concat("'", cbHeader.ClientID, "'"))

By adding the header CheckBox's ID to the array, clicking the "Check All" or "Uncheck All" buttons correctly check or uncheck the header CheckBox along with the row-level CheckBoxes in the grid.

Problem #2: Checking All CheckBoxes Manually or Unchecking a CheckBox Does Not Update the "Check/Uncheck All" Header CheckBox


Since the "Check/Uncheck All" header CheckBox checks all of the CheckBoxes in the grid I initially thought of the header CheckBox as a user interface item that solely performs some action - specifically, if you check the header CheckBox, all row-level CheckBoxes in the grid are checked. However, the header CheckBox really represents information about state of the CheckBoxes in the grid. That is, if the header CheckBox is checked, it indicates that all of the other CheckBoxes in the grid are also checked and if the header CheckBox is not checked then one or more of the other CheckBoxes in the grid must be unchecked.

Thinking of the header CheckBox in these terms, it became clear to me that the header CheckBox need to automatically be checked if all of the other CheckBoxes are manually checked; conversely, if all of the other CheckBoxes are checked and then the user manually unchecks one of them, the header CheckBox needs to be automatically unchecked. To accomplish this I decided to do the following:

  1. Add a JavaScript function named ChangeHeaderAsNeeded() that checks the header CheckBox if all of the other CheckBoxes in the grid are checked and unchecks the header CheckBox if there exists an unchecked CheckBox
  2. Wire up each row-level CheckBox's client-side onclick event to call the ChangeHeaderAsNeeded() function
Let's first look at the ChangeHeaderAsNeeded() function. This function assumes that the first CheckBoxIDs array element is the ID of the header CheckBox, and that the remaining elements are the IDs of the row-level CheckBoxes. The function loops through the row-level CheckBoxes and, for each CheckBox, determines if it's checked. If it is not, it unchecks the header CheckBox and exits the function. If the loop finishes without finding an unchecked row-level CheckBox, the header CheckBox is checked.

function ChangeHeaderAsNeeded()
{
    // Whenever a checkbox in the GridView is toggled, we need to
    // check the Header checkbox if ALL of the GridView checkboxes are
    // checked, and uncheck it otherwise
    if (CheckBoxIDs != null)
    {
        // check to see if all other checkboxes are checked
        for (var i = 1; i < CheckBoxIDs.length; i++)
        {
            var cb = document.getElementById(CheckBoxIDs[i]);
            if (!cb.checked)
            {
                // Whoops, there is an unchecked checkbox, make sure
                // that the header checkbox is unchecked
                ChangeCheckBoxState(CheckBoxIDs[0], false);
                return;
            }
        }
        
        // If we reach here, ALL GridView checkboxes are checked
        ChangeCheckBoxState(CheckBoxIDs[0], true);
    }
}

Next, we need to configure each row-level CheckBox so that when its client-side onclick event is fired the ChangeHeaderAsNeeded() function is invoked. This can be accomplished by adding the following client-side code to the For Each loop in the GridView's DataBound event handler:

... Code omitted for brevity ...
For Each gvr As GridViewRow In FileList.Rows
    'Get a programmatic reference to the CheckBox control
    Dim cb As CheckBox = CType(gvr.FindControl("RowLevelCheckBox"), CheckBox)

   'If the checkbox is unchecked, ensure that the Header CheckBox is unchecked
   cb.Attributes("onclick") = "ChangeHeaderAsNeeded();"

    'Add the CheckBox's ID to the client-side CheckBoxIDs array
    ClientScript.RegisterArrayDeclaration("CheckBoxIDs", String.Concat("'", cb.ClientID, "'"))
Next

That's all there is to it! With that change, checking all of the CheckBoxes manually will cause the header CheckBox to become checked; similarly, when all row-level CheckBoxes are checked, unchecking a single one will uncheck the header CheckBox.

Problem #3: When Doing a Postback that Doesn't Rebind the Data to the GridView, the "Check/Uncheck All" Header CheckBox No Longer Works


The code in the DataBound event handler assigns the client-side attributes to the checkboxes in the GridView and also specifies the elements in the client-side CheckBoxIDs array. The Web controls' Attributes collection is stored in view state, but the script emitted using the ClientScriptManager class is not. This distinction is important because the DataBound event handler only executes when the data is explicitly bound to the GridView. If the GridView's view state is enabled (the default), on a non-grid related postback the GridView is reconstructed from view state and does not explicitly rebind to its data source. Consequently, the DataBound event handler does not run.

The net result of this behavior is that if the GridView's view state is enabled and a non-grid related postback occurs, the client-side CheckBoxIDs array is lost and the Check All / Uncheck All logic no longer works. To remedy this, we need to forgo using the ClientScriptManager and instead craft the necessary JavaScript for the array and store it in a Literal Web control. Since the Literal control's Text property is stored in view state, the client-side array will remain regardless of whether the DataBound event handler executes.

The following code achieves this by using a List of type String named ArrayValues to hold the array elements. Every place ClientScript.RegisterArrayDeclaration("CheckBoxIDs", value) used to be is replaced by ArrayValues.Add(value). Then, after all of the values have been added to the List, the Literal control's Text property is assigned the appropriate JavaScript syntax for creating this array. The bold sections highlight the changes from the previous code snippets.

Protected Sub FileList_DataBound(ByVal sender As Object, ByVal e As System.EventArgs) Handles FileList.DataBound
   ... Code omitted for brevity ...

   'Add the CheckBox's ID to the client-side CheckBoxIDs array
   Dim ArrayValues As New List(Of String)
   ArrayValues.Add(String.Concat("'", cbHeader.ClientID, "'"))


   For Each gvr As GridViewRow In FileList.Rows
     'Get a programmatic reference to the CheckBox control
     Dim cb As CheckBox = CType(gvr.FindControl("RowLevelCheckBox"), CheckBox)

     'If the checkbox is unchecked, ensure that the Header CheckBox is unchecked
     cb.Attributes("onclick") = "ChangeHeaderAsNeeded();"

     'Add the CheckBox's ID to the client-side CheckBoxIDs array
    

ArrayValues.Add(String.Concat("'", cb.ClientID, "'"))


   Next

   'Output the array to the Literal control (CheckBoxIDsArray)
   CheckBoxIDsArray.Text = "<script type=""text/javascript"">" & vbCrLf & _
                   "<!--" & vbCrLf & _
                   String.Concat("var CheckBoxIDs = new Array(", String.Join(",", ArrayValues.ToArray()), ");") & vbCrLf & _
                   "// -->" & vbCrLf & _
                   "</script>"
End Sub

Conclusion


In this article we saw how to add a "Check/Uncheck All" header CheckBox to the GridView that, using client-side techniques, provides a means to quickly check or uncheck all row-level CheckBoxes. The header CheckBox, along with the "Check All" and "Uncheck All" buttons, provide the user with multiple ways for checking or unchecking all of the grid's CheckBoxes. Since client-side script is used to handle checking and unchecking the CheckBoxes, the interface provides a snappy user experience.

Happy Programming!

  • By Scott Mitchell


    Attachments


  • Download the code used in this article

    Further Reading


  • Checking All Checkboxes in a GridView Using jQuery

  • Article Information
    Article Title: ASP.NET.Checking All CheckBoxes in a GridView Using Client-Side Script and a Check All CheckBox
    Article Author: Scott Mitchell
    Published Date: May 31, 2006
    Article URL: http://www.4GuysFromRolla.com/articles/053106-1.aspx


    Copyright 2014 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers