Wednesday, 22 February 2012

Checking All Checkboxes in a GridView Using jQuery

Creating a GridView with a CheckBox in Each Row


Before we examine how to provide functionality to check or uncheck each checkbox in a GridView, let's first concentrate on building a GridView control that includes a CheckBox for each row. A column of CheckBoxes can be added to the GridView using one of two approaches:
  • Using the CheckBoxField - the CheckBoxField is a built-in GridView field type that renders a column of CheckBoxes with each CheckBox's Checked property assigned to some data field in the grid's underlying data source.
  • Using a TemplateField - we can add a TemplateField and then, in the ItemTemplate, add a CheckBox Web control. If the CheckBox Web control's Checked property should be based on some underlying data field value, the Checked property can be set either declaratively (such as, <asp:CheckBox runat="server" ... Checked='<%# Eval("ColumnName") %>' ... />) or programmatically (such as in the RowDataBound event handler).
The second option - using a TemplateField - has a number of advantages. First, it's more customizable. With minimal effort we can include additional markup in each row and can provide a custom header, which is useful if we want to include a checkbox in the header row that allows the user to check or uncheck all checkboxes in the column. Moreover, if the CheckBox's Checked property is not dependent on some underlying data field, then you'll need to use the TemplateField option. This article, and the demo available for download at the end of the article, uses the TemplateField option. Imagine that we are building a web-based file management tool that lists the files in the current directory, allowing the user to select one or more files to delete. To select a file, we want to include a checkbox in each row; beneath the GridView there's a "Delete All Checked Files" Button that, when clicked, will delete the selected files. (The demo available for download at the end of this article doesn't actually delete the files; rather, it just prints a message indicating what files it would have deleted, had that functionality been implemented.)
The following GridView accomplishes the needed functionality. Note that it uses a TemplateField to house a CheckBox control for each GridView row. Also note that the GridView's DataKeyNames property is assigned FullName. Setting the DataKeyNames property prompts the GridView to record the value of each file's FullName property in its DataKeys collection; this information is used when deleting the selected files. (Some of the GridView's formatting-related settings have been removed for brevity.)
<asp:GridView ID="gvFileList" runat="server"
   AutoGenerateColumns="False" DataKeyNames="FullName">
   <Columns>
      <asp:TemplateField>
         <ItemTemplate>
            <asp:CheckBox runat="server" ID="chkSelected" />
         </ItemTemplate>
      </asp:TemplateField>
      <asp:BoundField DataField="Name" HeaderText="Name" />
      <asp:BoundField DataField="CreationTime" HeaderText="Created On" />
      <asp:BoundField DataField="Length" HeaderText="File Size" />
   </Columns>
</asp:GridView>
In the Page_Load event handler the files in the current directory are retrieved and bound to the GridView. (For more information on accessing and displaying the files in a directory, see Displaying Files and Folders in a GridView.)
protected void Page_Load(object sender, EventArgs e)
{
   if (!Page.IsPostBack)
   {
      var dirInfo = new DirectoryInfo(Request.PhysicalApplicationPath);

      gvFileList.DataSource = dirInfo.GetFiles();
      gvFileList.DataBind();
   }
}
Additionally, we need a "Delete All Checked Files" Button Web control that, when clicked, determines what CheckBox controls in the grid have been selected and then deletes those files. The demo includes a Button Web control named DeleteButton that carries out this purpose. In its Click event handler, the rows of the GridView are enumerated using a foreach loop over the GridView's Rows collection. For each GridViewRow instance, the chkSelected CheckBox is referenced and its Checked property is examined. If checked, the file corresponding to that row is deleted. Note that the file path for each GridView row is accessible via the GridView's DataKeys properties.
protected void DeleteButton_Click(object sender, EventArgs e)
{
   // Enumerate the GridViewRows
   foreach(GridViewRow row in gvFileList.Rows)
   {
      // Programmatically access the CheckBox from the TemplateField
      var cb = row.FindControl("chkSelected") as CheckBox;

      // If it's checked, delete it...
      if (cb.Checked)
      {
         var currentRowsFilePath = gvFileList.DataKeys[row.RowIndex].Value.ToString();

         // To actually delete the file, uncomment the below line of code...
         // File.Delete(currentRowsFilePath);
      }
   }
}
ow that we have the groundwork laid out, we're ready to examine how to implement functionality to check and uncheck all CheckBoxes in the GridView using client-side script. Let's start with a look at adding said functionality using "Check All" and "Uncheck All" buttons. Following that, we'll see how to implement the same functionality using a checkbox in the grid's header row.

Using "Check All" and "Uncheck All" Buttons


With the existing GridView in place, a user can check one or more checkboxes, click the "Delete All Checked Files" button, and have those selected files deleted. But what if there are a lot of files in the directory and they want to delete them all? As it stands now, they must check each checkbox, one at a time, until all are selected. We can improve the user experience by adding a mechanism for checking or unchecking all checkboxes with one click of the mouse. One way to let users check (or uncheck) all checkboxes is to add two buttons to the page titled "Check All" and "Uncheck All." The following markup adds two such buttons to the page. (In the screen shot below and in the demo available for download at the end of this article, these two buttons are positioned above the GridView, but you could place them anywhere on the page.)
<p>
   <input type="button" id="btnCheckAll" value="Check All" />
   <input type="button" id="btnUncheckAll" value="Uncheck All" />
</p>

The Check All and Uncheck All buttons appear above the GridView.
To have the "Check All" button cause all checkboxes to be clicked and to have the "Uncheck All" button cause all checkboxes to be unchecked, add the following JavaScript to your page. (This presumes that you also have a reference to the jQuery library.)
<script type="text/javascript">
   var checkBoxSelector = '#<%=gvFileList.ClientID%> input[id*="chkSelected"]:checkbox';

   $(document).ready(function () {
      $('#btnCheckAll').live('click', function () {
         $(checkBoxSelector).attr('checked', true);
      });

      $('#btnUncheckAll').live('click', function () {
         $(checkBoxSelector).attr('checked', false);
      });
   });
</script>
The above script starts by creating a variable named checkBoxSelector that defines the selector syntax for selecting all checkboxes in the GridView. Let's look at each part of the selector:
  • #<%=gvFileList.ClientID%> - gvFileList.ClientID is server-side code that returns the client-side id attribute for the HTML <table> rendered by the gvFileList GridView. While the GridView's server-side ID is gvFileList, the rendered id attribute sent down to the client may be modified if the GridView is within any naming containers. For my demo, the GridView's rendered client-side id is actually ContentPlaceHolder1_gvFileList, which is the value returned by gvFileList.ClientID. Consequently, the #<%=gvFileList.ClientID%> in the web page gets rendered to the client as #ContentPlaceHolder1_gvFileList, which tells jQuery to start its search by looking for an HTML element with the id ContentPlaceHolder1_gvFileList. (For more information on the ClientID property and related tips, refer to Take Control Of Web Control ClientID Values in ASP.NET 4.)
  • input[id*="chkSelected"] - returns all <input> elements whose id attribute contains the text chkSelected. (Note that each checkbox in the grid has an id of the form ContentPlaceHolder1_gvFileList_chkSelected_rowNumber.) Because this appears after the #<%=gvFileList.ClientID%> selector, it is limited in its search scope to within the gvFileList GridView. That is, if there is another <input> element on the page with a matching id it will not be returned.
  • :checkbox - this limits the set of elements returned by the previous selector to those that are also checkboxes.
Next, in the $(document).ready event handler we define click event handlers for the two buttons. Both buttons have similar event handlers. For the btnCheckAll button we set the checked attribute to true for all chkSelected checkboxes in the grid; the btnUncheckAll event handler sets the checked attribute to false. The event handlers here are defined using jQuery's live function (instead of click) to support the case where the GridView may be in an UpdatePanel. (See Rebinding Client-Side Events After a Partial Page Postback for more details on this concept.) With the above script in place, clicking the "Check All" button checks all checkboxes, and clicking "Uncheck All" unchecks all checkboxes. But notice that even after all checkboxes are checked, the "Check All" button remains clickable; ditto the "Uncheck All" checkbox when no checkboxes are checked. Ideally, these buttons would be disabled in these scenarios. To accomplish this we need to do three things:
  1. Create a function that determines if all the checkboxes are checked or if no checkboxes are checked. If all checkboxes are checked then the "Check All" button should be disabled. If no checkboxes are checked then "Uncheck All" should be disabled.
  2. Whenever a checkbox is checked or unchecked or whenever the "Check All" or "Uncheck All" buttons are clicked we need to call the function created in Step 1.
  3. When the page loads for the first time we need to call the function created in Step 1.
The following function, named ToggleCheckUncheckAllOptionAsNeeded, performs the task outlined in Step 1. It starts by getting a reference to the set of the chkSelected checkboxes in the grid as well as those that are checked. It then sets the disabled attribute based of the btnCheckAll button to true if the number of total checkboxes equals the number of checked checkboxes, false otherwise. Similarly, the btnUncheckAll button's disabled attribute is set to true if the number of checked checkboxes equals 0, otherwise it's set to false.
function ToggleCheckUncheckAllOptionAsNeeded() {
   var totalCheckboxes = $(checkBoxSelector),
      checkedCheckboxes = totalCheckboxes.filter(":checked"),
      noCheckboxesAreChecked = (checkedCheckboxes.length === 0),
      allCheckboxesAreChecked = (totalCheckboxes.length === checkedCheckboxes.length);

   $('#btnCheckAll').attr('disabled', allCheckboxesAreChecked);
   $('#btnUncheckAll').attr('disabled', noCheckboxesAreChecked);
}
In the $(document).ready event handler the chkSelected checkboxes have their click event wired up to the ToggleCheckUncheckAllOptionAsNeeded function. Likewise, the click event handlers for the "Check All" and "Uncheck All" buttons have been updated to call ToggleCheckUncheckAllOptionAsNeeded. And ToggleCheckUncheckAllOptionAsNeeded is called when the page first loads, as well.
$(document).ready(function () {
   $('#btnCheckAll').live('click', function () {
      $(checkBoxSelector).attr('checked', true);
      ToggleCheckUncheckAllOptionAsNeeded();
   });

   $('#btnUncheckAll').live('click', function () {
      $(checkBoxSelector).attr('checked', false);
      ToggleCheckUncheckAllOptionAsNeeded();
   });

   $(checkBoxSelector).live('click', ToggleCheckUncheckAllOptionAsNeeded);

   ToggleCheckUncheckAllOptionAsNeeded();
});
With this new script in place, the buttons are disabled when they would otherwise be redundant. The screen shot below shows an example. Here, no checkboxes are checked, so the "Uncheck All" button is disabled. Checking a single checkbox (or clicking the "Check All" button) causes the "Uncheck All" button to become enabled again.

The Uncheck All button is disabled because no checkboxes are checked.

Using a Checkbox in the GridView Header Row


In place of "Check All" and "Uncheck All" buttons we could opt to add a checkbox to the GridView's header row that serves the same purpose. To add such a checkbox we need to add a HeaderTemplate with its own CheckBox control to the TemplateField that contains the chkSelected CheckBox control:
<asp:GridView ID="gvFileList" runat="server"
   AutoGenerateColumns="False" DataKeyNames="FullName">
   <Columns>
      <asp:TemplateField>
         <HeaderTemplate>
            <asp:CheckBox runat="server" ID="chkAll" />
         </HeaderTemplate>
         <ItemTemplate>
            <asp:CheckBox runat="server" ID="chkSelected" />
         </ItemTemplate>
      </asp:TemplateField>
      ...
   </Columns>
</asp:GridView>
With this addition the GridView now renders a checkbox in the header row, as the following screen shot shows.

There's now a checkbox in the grid's header row.
Much like with the "Check All" and "Uncheck All" button demo, we need to have the header row checkbox check (or uncheck) all chkSelected checkboxes when clicked. And if all checkboxes are checked by the user, one at a time, we need to also check the header row checkbox. If there is at least one unchecked checkbox then the header row checkbox should be unchecked.
This can be accomplished in the same way as the button demo. In the $(document).ready event handler we need to create an event handler for the header row checkbox's click event that checks (or unchecks) all chkSelected checkboxes. We also need a ToggleCheckUncheckAllOptionAsNeeded function that checks (or unchecks) the header row checkbox based on whether all chkSelected checkboxes are checked, and ToggleCheckUncheckAllOptionAsNeeded needs to be called whenever a chkSelected checkbox is clicked, whenever the header row checkbox is clicked, and when the page first loads.
<script type="text/javascript">
   var allCheckBoxSelector = '#<%=gvFileList.ClientID%> input[id*="chkAll"]:checkbox';
   var checkBoxSelector = '#<%=gvFileList.ClientID%> input[id*="chkSelected"]:checkbox';

   function ToggleCheckUncheckAllOptionAsNeeded() {
      var totalCheckboxes = $(checkBoxSelector),
         checkedCheckboxes = totalCheckboxes.filter(":checked"),
         noCheckboxesAreChecked = (checkedCheckboxes.length === 0),
         allCheckboxesAreChecked = (totalCheckboxes.length === checkedCheckboxes.length);

      $(allCheckBoxSelector).attr('checked', allCheckboxesAreChecked);
   }

   $(document).ready(function () {
      $(allCheckBoxSelector).live('click', function () {
         $(checkBoxSelector).attr('checked', $(this).is(':checked'));

         ToggleCheckUncheckAllOptionAsNeeded();
      });

      $(checkBoxSelector).live('click', ToggleCheckUncheckAllOptionAsNeeded);
        
      ToggleCheckUncheckAllOptionAsNeeded();
   });
</script>
In the above script there are two selector variables, allCheckBoxSelector and checkBoxSelector, which are the jQuery selectors to retrieve the header row checkbox and the set of chkSelected checkboxes in the gvFileList grid. In the ToggleCheckUncheckAllOptionAsNeeded the header row checkbox is checked if all checkboxes in the grid are checked, otherwise it's unchecked.

No comments:

Post a Comment