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.