Adding proper THEAD sections to a GridView
I’m working on some legacy code for a customer today and dealing with a page that has my favorite ‘friend’ on it: A GridView control. The ASP.NET GridView control (and also the older DataGrid control) creates some pretty messed up HTML. One of the more annoying things it does is to generate all rows including the header into the page in the <tbody> section of the document rather than in a properly separated <thead> section.
Here’s is typical GridView generated HTML output:
<table class="tablesorter blackborder" cellspacing="0" rules="all" border="1"
id="Table1" style="border-collapse:collapse;"> <tr> <th scope="col">Name</th>
<th scope="col">Company</th>
<th scope="col">Entered</th><th scope="col">Balance</th> </tr>
<tr> <td>Frank Hobson</td><td>Hobson Inc.</td>
<td>10/20/2010 12:00:00 AM</td><td>240.00</td> </tr> ... </table>
Notice that all content – both the headers and the body of the table – are generated directly under the <table> tag and there’s no explicit use of <tbody> or <thead> (or <tfooter> for that matter).
When the browser renders this the document some default settings kick in and the DOM tree turns into something like this:
<table> <tbody> <tr> <-- header <tr> <—detail row <tr> <—detail row </tbody> </table>
Now if you’re just rendering the Grid server side and you’re applying all your styles through CssClass assignments this isn’t much of a problem. However, if you want to style your grid more generically using hierarchical CSS selectors it gets a lot more tricky to format tables that don’t properly delineate headers and body content. Also many plug-ins and other JavaScript utilities that work on tables require a properly formed table layout, and many of these simple won’t work out of the box with a GridView. For example, one of the things I wanted to do for this app is use the jQuery TableSorter plug-in which – not surprisingly – requires to work of table headers in the DOM document. Out of the box, the TableSorter plug-in doesn’t work with GridView controls, because the lack of a <thead> section to work on.
Luckily with a little help of some jQuery scripting there’s a real easy fix to this problem. Basically, if we know the GridView generated table has a header in it, code like the following will move the headers from <tbody> to <thead>:
<script type="text/javascript"> $(document).ready(function () { // Fix up GridView to support THEAD tags $("#gvCustomers tbody").before("<thead><tr></tr></thead>"); $("#gvCustomers thead tr").append($("#gvCustomers th")); $("#gvCustomers tbody tr:first").remove(); $("#gvCustomers").tablesorter({ sortList: [[1, 0]] }); }); </script>
And voila you have a table that now works with the TableSorter plug-in.
If you use GridView’s a lot you might want something a little more generic so the following does the same thing but should work more generically on any GridView/DataGrid missing its <thead> tag:
function fixGridView(tableEl) {
var jTbl = $(tableEl);
if(jTbl.find("tbody>tr>th").length > 0) {
jTbl.find("tbody").before("<thead><tr></tr></thead>");
jTbl.find("thead tr").append(jTbl.find("th"));
jTbl.find("tbody tr:first").remove();
}
}
which you can call like this:
$(document).ready(function () { fixGridView( $("#gvCustomers") ); $("#gvCustomers").tablesorter({ sortList: [[1, 0]] }); });
Server Side THEAD Rendering
[updated from comments 11/21/2010]
Several commenters pointed out that you can also do this on the server side by using the GridView.HeaderRow.TableSection property to force rendering with a proper table header. I was unaware of this option actually – not exactly an easy one to discover. One issue here is that timing of this needs to happen during the databinding process so you need to use an event handler:
this.gvCustomers.DataBound += (object o, EventArgs ev) => { gvCustomers.HeaderRow.TableSection = TableRowSection.TableHeader; }; this.gvCustomers.DataSource = custList; this.gvCustomers.DataBind();
You can apply the same logic for the FooterRow. It’s beyond me why this rendering mode isn’t the default for a GridView – why would you ever want to have a table that doesn’t use a THEAD section??? But I disgress :-)
I don’t use GridViews much anymore – opting for more flexible approaches using ListViews or even plain code based views or other custom displays that allow more control over layout, but I still see a lot of old code that does use them old clunkers including my own :) (gulp) and this does make life a little bit easier especially if you’re working with any of the jQuery table related plug-ins that expect a proper table structure.
The Voices of Reason
# re: Adding proper THEAD sections to a GridView
if (HeaderRow != null) { HeaderRow.TableSection = TableRowSection.TableHeader; } if (FooterRow != null && ShowFooter) { FooterRow.TableSection = TableRowSection.TableFooter; }
I've used this many times due to the jQuery tableSorter plugin.
# re: Adding proper THEAD sections to a GridView
http://www.devcurry.com/2009/02/make-aspnet-gridview-generate-thead-and.html
# re: Adding proper THEAD sections to a GridView
# re: Adding proper THEAD sections to a GridView
# re: Adding proper THEAD sections to a GridView
Have you tried the JQGrid plugin for .NET? If you (or other readers) have any thoughts to share on it, I would appreciate it. I have done some work converting a GridView to this JQueryUI plugin and, while it seems capable, I am not sure it has saved me any work and the documentation is spotty. I usually check code from their demo pages because they are more complete than the Wiki. Thanks, Don
# re: Adding proper THEAD sections to a GridView
FWIW, I have a Web Toolkit sample for jqGrid here:http://www.west-wind.com/westwindwebtoolkit/samples/Ajax/jqGrid.aspx which uses the Toolkits AJAX backend functions to retrieve the data for the grid. I've been thinking about building a control that wraps the grid setup and AJAX callback automatically to make this easier, but haven't found the time to do this yet.
# re: Adding proper THEAD sections to a GridView
# re: Adding proper THEAD sections to a GridView
Wouldn't it be better to hook up to the PreRender event so that the fix will occur on post backs where the data is not re-bound?
gvCustomers.PreRender += new EventHandler(GridViewHelper.GridViewTagFix);
Rick, thanks for all the posts! There is some amazing content here.
# re: Adding proper THEAD sections to a GridView
I should modify a little the code that you propose to work with a GridView with paging enabled. The problem is that the paging is rendered as a table like the main GridView, and your current code put the THEAD with the header columns to both tables, the main GridView and the paging. The fix consists in take only the first THEAD found to append the headers. Here the code:
$("table.grid").each(function () { var jTbl = $(this); if (jTbl.find("tbody>tr>th").length > 0) { jTbl.find("tbody").before("<thead><tr></tr></thead>"); jTbl.find("thead:first tr").append(jTbl.find("th")); jTbl.find("tbody tr:first").remove(); } });
# re: Adding proper THEAD sections to a GridView