Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

One size fits all solution for freezing a Grid's header rows, why not?

4.91/5 (11 votes)
23 Jan 2013CPOL8 min read 45.6K   1.2K  
Enhance usability and look-and-feel of table/grid with freezing header rows. Let's discover it.

Introduction    

Table structure in HTML and GridView in ASP.NET has brought about various smart ways of rendering data.  But there is a big question I've been looking for the answer for quite some time ago; that is how to freeze header rows to track data easily when scrolling page. Unfortunately, the GridView control doesn't provide such a feature. I searched on Internet for the solution but no one makes me satisfied. Therefore, I decided to develop a library to support this feature.  Here is such the result: 

Image 1

Image 2
Background  

Hooking up the "scrolling" event in client side lies at the heart of this technique. When the header row is getting out of sight while scrolling the page, the entire row disconnects itself from the grid and then floats on the very top of browser. On the contrary, when the header row is back under the screen view, it rejoins the grid as though nothing occurs.  

Here's how it works: 

Image 3 

And here it explains how the header row disconnects from grid:

Image 4

Using the code  

Freezing header rows is a tricky thing. It's pretty straight forward. Use JQuery to hook up the event and keep eyes out on changes of header row's position. Here's how it looks:  

C++
<script type="text/javascript">
    var gridview_header_top_position = 0;
    $(document).ready(function() {
        $(window).scroll(function() 
        {
            if ($(window).scrollTop() >= top_position_of_header_row) 
            {
                //Disconnect the header row from grid and push it up on top of browser:        
                //For example: jHeaderRow.css('position', 'fixed');
            }
            else
            {
                //Put it back where the orginal position belongs
                //For example: jHeaderRow.css('position', '');
                ...
            }
        });
    }); 
</script>  

The above codesnippet doesn't incur the processing overhead of a server round trip. But please be careful when writing script if you don't expect the Web page get sluggish responding to each scroll event. To optimize performance, define some variables to hold the unchanged or rarely changed data, such as: position of grid view, the clone of header rows... 

Please note that the style of each header cell loses after disconnecting them from grid. Therefore, the "viewable" properties (border, padding, font...) should be persisted when disconnecting header cells.  

Browser Compatibility        

One of the daunting tasks is to make your Web page run smooth and compatible in all sorts of browsers in order to make it look and feel like you’re using the same browser. Obviously, to make it jump through all the hoops, we have to fine-tune the code a lot.   

The above solution runs quite well in Firefox, Opera and Chrome. For Safari and Flock, the layout is broken after rejoining the broken pieces (header rows and data/footer rows). For IE browsers, it runs fine in version greater than or equal 8.0. For IE 7.0, 6.0 or lower than that, it looks bad. IE 6 doesn't support the "fixed" position. It has to be done with some CSS hacks. But CSS hacks isn't a good thing because other unrelated parts might be affected. So why do we go through the hard time for such problems? The advice is, for IE 6.0 and 7.0, leaving the grid header unfrozen if you don't want to bear the huge cost.   

Test Cases   

There are 3 main scenarios that might not be noticed early on: 

Test Case #1: When the grid header is frozen, the cells in the header rows and data rows are out of proportion. Just like below image:  

Image 5

This bug occurs when setting cell style in GridView control. It is a problem of frozen header itself when the entire header is taken out and positioned to be fixed on top of the browser (or dock to top of any pre-defined frame). Then the columns lose the "leading style setting" (stipulated by header cell) and the ItemStyle-Width will be applied instead.  

XML
<asp:TemplateField ItemStyle-Width="80px" HeaderText="Customer ID">
    <ItemTemplate>
        <asp:Label ID="lblCustID" runat="server" Text='<%# Eval("CustomerID")%>'></asp:Label>
    </ItemTemplate>
</asp:TemplateField> 

It's not easy to give a perfect fix for this bug. You'll probably have to fine-tune the disconnected header case-by-case to make sure it has a very tight fit with the grid. If you don't want to suffer from headache with all those things, let's go with the complete solution explained at the end of the article. 

Test Case #2:  The disconnected header doesn't move along with the underlying table/grid when scroll page horizontally. Why? Because we set them fixed (position = 'fixed') so it doesn't move.

Image 6

To resolve the above bugs, I come up with the completely new solution. I clone the whole table and put it in a new layer (div). This layer is docking on top but moves when scrolling the page horizontally. Let's see how the below picture describes: 

Image 7

The same problem occurs when resizing browser in real-time, then as usual, the underlying table is auto resized (i.e. width='80%') or squeezed into the most compact shape (if width='auto'). In this case, the cloned table's width must be re-calculated to sync with the underlying table. 

XML
<script type="text/javascript">
   $(window).bind('resize', function () { 
        //Your code goes here to re-calculate the width of the cloned table to be same with new width value of the underlying table.
   });
</script> 

Note that this solution might make the data-heavy page run a bit slow and browser get sluggish. You can see it clearly in the low-configuration PC clients. The other thing you might ask about is that why don't we create a new empty table and then copy the whole structure (include both inline style and external css) of the main table (use the window.getComputedStyle() to get this done)? It'll be running faster because we don't copy the whole data-heavy table. Yes, I feel it right too. But unfortunately, there are still many unsolved problems with this proposal. Furthermore, browser compatibility is still a drawback to this proposal. 

Test Case 3: As explained above, cloning the whole table is the perfect solution. But there is still a minor drawback like the below illustration: 

Image 8

The hot fix to this is to make sure that all the "viewable" properties (font, border, padding...) are set, not leave "inherited" nor not set by default.

Weighing the Options

All in all, there are 3 solutions to build up, depending on the complexity of each problem. If your grid is simple or you don't need to support every browsers, let's use the first solution in order to make your Web page run faster. On the other hand, if you don't care much about the performance while Usability is the most important concern, you can go with the 3rd solution.  

Solution How-toAdvantagesDisadvantages 
1. Detach header row. Detach cells one by one. Set their position as "fixed".   Simple and easy to customize.
Suitable for simple table design.
Not compatible in every browsers. 
2. Clone header row.  Clone a new DIV element that has same structure with the header row.Resolve UI issues of Safari/Flock browsers.  Doesn't work well when table's style is too complicated. 
3. Clone the entire table. Clone entire table in a DIV element (upmost layer) that has same height with overall header rows.Ensure the highest precision and bug-free in all cases. Compatible in every browsers except IE6. Leave the heavy processing burden to the client side when the table's data is too large.  
Sneak Preview

The above look-and-feel is really intriguing, isn't it? I find it particularly helpful for the long data list which is easy to lose sight when viewing. Especially the 3rd solution is "one size fits all" approach for almost every browsers.  Now let's build the library to support main UI functions from behind. It would be a good thing if we can wrap all the above things up and put them in an utility function that's loosely coupled with UI. In this article, I implement the function FreezeTableHeader() and put it in a class named "GridHelper" (should be placed in an assembly too). The remaining job of the developer is to provide parameters like gridview ID or CSS class and further optional variables to control freezing behaviors. By doing so, the main developing source code is lean.  

Now, let's jump straight to the live demo: http://phamdinhtruong.7hostfree.com/FreezingTable.aspx 

You can also have sneak preview with the animated demo which demonstrates freezing the grid header while scrolling vertilcally/horizontally. Even beyond that, the grid header can dock within a certain container. In this demo, you can see that the header of the nested table is docking within its container which is a certain row ifself of the parent table.

Image 9 

Conclusion  

The above solutions has helped me to build freezing header rows not only within a browser window but also a certain pre-defined area. Especially, the complete solution (3rd solution) now can help freeze even "multiple-rows" header section (in which cells are merged). You can also use the same technique for freezing footer rows. I would really be interested in knowing if this code could help you. Please put in comments to let me know how you feel about the article. I'd be ready to help you on any suggestions/issues.   

History   

  1. Version 1.0 (20 July, 2012) - Initial release
  2. Version 2.0 (10 Sept, 2012) - Fixes for Safari and Flock
  3. Version 3.0 (25 Sept, 2012) - Fixes for multiple gridviews and nested gridviews in the same page.
  4. Version 4.0 (07 Nov, 2012) - Fixes and fine-tuning for precisely freezing header row.
  5. Version 5.0 (14 Jan, 2013) - Implement the complete solution that clones the table and creates an upmost layer to fit in the cloned header. 

References  

I want to thank Mr. Amauri Rodrigues for the good article named "Expandable panel inside a GridView". It was used in my example as a real good Test Case for solving the problems relative to nested grids.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)