Working with CheckBoxes within DataGrid Control
Add Comment<span class=wboxheado>Introduction</span><br> Recently I have seen a lot of queries on the newsgroups where people want to create Hotmail like functionality with CheckBoxes within their DataGrid control's. This article should help you sort out these queries.<p>The problem is that there is a CheckBox in the Header of the DataGrid, just above a column of Checkboxes. Now the functionality required is that when the header CheckBox is selected / deselected relevantly all the CheckBoxes in the column should be selected / deselected. Actually, selection / deselection of CheckBoxes is a client-side scripting activity hence we will have to generate our server-side code in such a way that it can be easily manipulated by client-side scripting.<br> Another functionality required is that when an external button is clicked we should be able to retrieve all the checked rows from the DataGrid on the server. </p> <p><span class=wboxheado>Part 1- CheckBox Header</span></p> <p>Let's start with the first part of the problem i.e. to deal with working of CheckBox in the header of a CheckBox column. To straight jump to the required functionality, I have used the <b>Pubs</b> database, that's installed with the .NET SDK and MSDE. </p> <table cellpadding="2" cellspacing="1" width="100%" class="code"> <tr> <td width="100%"> <pre><%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <html> <script language="C#" runat="server"> SqlConnection myConnection; protected void Page_Load(Object Src, EventArgs E) { myConnection = new SqlConnection("server=(local)\\NetSDK; database=pubs;Trusted_Connection=yes"); if (!IsPostBack) BindGrid(); } public void BindGrid() { SqlDataAdapter myCommand = new SqlDataAdapter("select * from Authors", myConnection); DataSet ds = new DataSet(); myCommand.Fill(ds, "Authors"); MyDataGrid.DataSource=ds.Tables["Authors"].DefaultView; MyDataGrid.DataBind(); } </script> <body style="font: 10pt verdana"> <form runat="server" id="form1" name="form1" > <h3><font face="Verdana">Working with Checkboxes within a DataGrid</font></h3> <ASP:DataGrid id="MyDataGrid" runat="server" Width="800" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" AutoGenerateColumns="false" > <Columns> <asp:BoundColumn HeaderText="au_id" DataField="au_id" /> <asp:BoundColumn HeaderText="au_lname" DataField="au_lname" /> <asp:TemplateColumn HeaderText="au_fname" > <ItemTemplate> <asp:Label id="au_fname" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>' runat="server" /> </ItemTemplate> </asp:TemplateColumn> <asp:BoundColumn HeaderText="city" DataField="city" /> <asp:TemplateColumn HeaderText="contract" > <HeaderTemplate> <input type=checkbox id="checkAll" runat="server"> Contracts </HeaderTemplate> <ItemTemplate> <input type=checkbox runat="server" id="contract" checked='<%# DataBinder.Eval(Container.DataItem, "contract") %>'/> </ItemTemplate> </asp:TemplateColumn> </Columns> </ASP:DataGrid> </form> </body> </html> </pre> </td> </tr> </table> <p>The above code snip should be very familiar to you all by now, I am simply data binding the <b>Authors</b> table from the <i>Pubs</i> database to a DataGrid.<br> The only advanced functionality here is the definition of custom templates in the DataGrid. Since I want to display the <b>Contract</b> column as a CheckBox column, I use a custom <i>TemplateColumn</i> definition and bind the <i>Boolean</i> database field <i>Contract</i> with the <b>checked</b> property of the <b>HtmlInputCheckBox</b> control defined in the <i>ItemTemplate</i>. Also within the <i>TemplateColumn</i> I have defined a <i>HeaderItem</i> template where I have placed a normal <i>HtmlInputCheckBox</i> control.</p> <p>A point to note here is that if you want to perform any client-side scripting with your server controls, HtmlControls are the easiest to use, hence I have used the <i>HtmlInputCheckBox</i> control. Also I have data-bound the <i>au_fname</i> field within the ItemTemplate, since I want to retrieve the value this field upon postback in part-2 of this article, but generally you might prefer using the identity column of the table, so that it help's you perform further operations on the selected records.</p> <p>Next, I change the definition of the HtmlInputCheckBox control in the Header so that it's <b>onclick</b> <i>client-side</i> event calls a JavaScript function <b>CheckAll()</b>. The updated definition of the HtmlInputCheckbox is shown below :</p> <table cellpadding="2" cellspacing="1" width="100%" class="code"> <tr> <td width="100%"><br> <input type=checkbox id="checkAll" onclick="CheckAll(this);" runat="server"><br> </td> </tr> </table> <p>Now there are many ways you can handle this kind of interaction in JavaScript depending on your solution. Since there can be a lot of permutations of the JavaScript functionality wanted, I will be targeting a common functionality i.e. when the CheckBox in the header is selected / deselected all the CheckBoxes in the column below should check / uncheck respectively. Add the following JavaScript below the DataGrid definition in your code.<p>Note: I have checked this JavaScript in IE 6.0 and Mozilla 1.0.<br> <table cellpadding="2" cellspacing="1" width="100%" class="code"> <tr> <td width="100%"> <pre><script > var frm = document.form1 ; function CheckAll( checkAllBox ) { var actVar = checkAllBox.checked ; for(i=0;i< frm.length;i++) { e=frm.elements[i]; if ( e.type=='checkbox' && e.name.indexOf("contract") != -1 ) e.checked= actVar ; } } </script></pre> </td> </tr> </table> <p>In the above JavaScript, I first check the state of the CheckBox that fired the event i.e. the header CheckBox. Next, I loop through the form elements searching for all CheckBox controls which have the word "<i>contract</i>" in their name. Always remember that ASP.NET runtime allocates <i>unique</i> names for its controls on the client side, hence you cannot directly use the value of the <b>name</b> attribute you have used while coding your page. Although, the value you provide to the <i>name</i> attribute while defining the control will feature in the client side generated <i>unique id</i> for the control. Hence I am using the <b>indexof</b> function to match the client-side name of the CheckBoxes I want to select. Finally, I set the value of the CheckBoxes to match the value of the Header CheckBox.<p>This code works correctly, with one minor glitch being that after the Header CheckBox has been selected, which in turn selects all the CheckBoxes in the column and if any CheckBox in the column is later unchecked, the Header CheckBox remains checked! ( Confusing? ) <br> Ideally, the Header CheckBox should only remain checked only if all the CheckBoxes in the column are checked. <br> <br> To counter this we need to wire-up the column CheckBoxes with another JavaScript function as shown below:<br> <table cellpadding="2" cellspacing="1" width="100%" class="code"> <tr> <td width="100%"><br> <input type=checkbox runat="server" id="contract" onclick="UnCheck();" <br> checked='<%# DataBinder.Eval(Container.DataItem, "contract") %>'/><br> </td> </tr> </table> <p>Also add the new JavaScript function UnCheck as shown below: <br> <table cellpadding="2" cellspacing="1" width="100%" class="code"> <tr> <td width="100%"> <pre>function UnCheck() { for(i=0;i< frm.length;i++) { e=frm.elements[i]; if ( e.type=='checkbox' && e.name.indexOf("checkAll") != -1 ) { e.checked= false ; break; } } }</pre> </td> </tr> </table> <p>In the above snip of code I just look through the form elements to find the CheckBox in the header and uncheck it.</p> <p>This finishes the implementation of the first part of this tutorial, now you can save and test the page.</p> <p><span class=wboxheado>Part 2 - List Checked Rows</span></p> <p>Coming to the second part of the article, I will discuss that after the relevant CheckBoxes have been selected there has to be a mechanism, so that on the server-side we know which rows were checked, hence we can perform the relevant operations, usually delete or select. </p> <p>First we add a <i>Button</i> control to the page as shown below </p> <table cellpadding="2" cellspacing="1" width="100%" class="code"> <tr> <td width="100%"><br> <asp:button Text="List Selected" runat="server" OnClick="Show_Selected" /><br> </td> </tr> </table> <p>And then define its server side event handler as shown below: <br> <table cellpadding="2" cellspacing="1" width="100%" class="code" > <tr> <td width="100%"> <pre>public void Show_Selected( object sender, EventArgs e) { foreach( DataGridItem di in MyDataGrid.Items ) { HtmlInputCheckBox cb = (HtmlInputCheckBox)di.FindControl("contract") ; if( cb !=null && cb.Checked ) { Label lb = (Label)di.FindControl("au_fname"); Response.Write( lb.Text +"<br>" ); } } }</pre> </td> </tr> </table> <p>In the above method, I loop through each <b>DataGridItem</b> contained within the DataGrid and search for the <i>HtmlInputCheckBox</i> control and then check its value. Later the first names (au_fname) of the Checked rows is printed out using <b>Response.Write</b>. You would be ideally performing some other action based on this selected rows. Two things I would like to instruct at this point, <br> 1) I am printing the values of the <i>au_fname</i> field, since if you remember in the first part I had defined a <i>ItemTemplate</i> for the field. You could also use the <i>DataGridItem</i>'s <i>ControlCollection</i> indexer to find the relevant values.<br> 2) The above logic will not work is you have paging enabled in your DataGrid. In that case you will have to check the <b>PageSize </b>and <b>CurrentPage</b> properties of the DataGrid first and only then retrieve values from rows shown on that page.</p> <p>For your convince I am listing the completely modified source code once again below </p> <table cellpadding="2" cellspacing="1" width="100%" class="code"> <tr> <td width="100%"> <pre><%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.SqlClient" %> <html> <script language="C#" runat="server"> SqlConnection myConnection; protected void Page_Load(Object Src, EventArgs E) { myConnection = new SqlConnection("server=(local)\\NetSDK; database=pubs;Trusted_Connection=yes"); if (!IsPostBack) BindGrid(); } public void BindGrid() { SqlDataAdapter myCommand = new SqlDataAdapter("select * from Authors", myConnection); DataSet ds = new DataSet(); myCommand.Fill(ds, "Authors"); MyDataGrid.DataSource=ds.Tables["Authors"].DefaultView; MyDataGrid.DataBind(); } public void Show_Selected( object sender, EventArgs e) { foreach( DataGridItem di in MyDataGrid.Items ) { HtmlInputCheckBox cb = (HtmlInputCheckBox)di.FindControl("contract") ; if( cb !=null && cb.Checked ) { Label lb = (Label)di.FindControl("au_fname"); Response.Write( lb.Text +"<br>" ); } } } </script> <body style="font: 10pt verdana"> <form runat="server" id="form1" name="form1" > <h3><font face="Verdana">Working with Checkboxes within a DataGrid</font></h3> <ASP:DataGrid id="MyDataGrid" runat="server" Width="800" BackColor="#ccccff" BorderColor="black" ShowFooter="false" CellPadding=3 CellSpacing="0" Font-Name="Verdana" Font-Size="8pt" HeaderStyle-BackColor="#aaaadd" AutoGenerateColumns="false" > <Columns> <asp:BoundColumn HeaderText="au_id" DataField="au_id" /> <asp:BoundColumn HeaderText="au_lname" DataField="au_lname" /> <asp:TemplateColumn HeaderText="au_fname" > <ItemTemplate> <asp:Label id="au_fname" Text='<%# DataBinder.Eval(Container.DataItem, "au_fname") %>' runat="server" /> </ItemTemplate> </asp:TemplateColumn> <asp:BoundColumn HeaderText="city" DataField="city" /> <asp:TemplateColumn HeaderText="contract" > <HeaderTemplate> <input type=checkbox id="checkAll" onclick="CheckAll(this);" runat="server"> Contracts </HeaderTemplate> <ItemTemplate> <input type=checkbox runat="server" id="contract" onclick="UnCheck();" checked='<%# DataBinder.Eval(Container.DataItem, "contract") %>'/> </ItemTemplate> </asp:TemplateColumn> </Columns> </ASP:DataGrid> <asp:button Text="List Selected" runat="server" OnClick="Show_Selected" /> <script > var frm = document.form1 ; function CheckAll( checkAllBox ) { var actVar = checkAllBox.checked ; for(i=0;i< frm.length;i++) { e=frm.elements[i]; if ( e.type=='checkbox' && e.name.indexOf("contract") != -1 ) e.checked= actVar ; } } function UnCheck() { for(i=0;i< frm.length;i++) { e=frm.elements[i]; if ( e.type=='checkbox' && e.name.indexOf("checkAll") != -1 ) { e.checked= false ; break; } } } </script> </form> </body> </html></pre> </td> </tr> </table> <p><span class=wboxheado>Conclusion</span><br> In this article we saw how to enable client-side JavaScript interaction with CheckBox controls nested within a DataGrid. I hope this article will help you open a few more ideas with the DataGrid control.</p>