Improving the Presentation of an Extra-Wide GridView

The codebase for the web application I am currently working on is pretty new.  We ported our legacy application from .Net 1.1 to .Net 2.0 and in the process we did a complete redesign of our presentation and business logic layers.  However, we did not do the same with our data model.  We upgraded from SQL Server 2000 to 2005, but for the most part we kept the exact same datamodel.  So while from a technology standpoint we have a completely new platform, our data model is about 5 years old.  Over those 5 years most of datamodel changes have been the addition of new columns to existing tables.  As a result, a number of our core tables have become quite wide.  And as the tables in our underlying datamodel get wider over time, so do the grids that present the data.        

Live Demo (FF, IE6, IE7) | Download

So during a recent visit to the Ajax Rain site, I came across a sample that had a few features that I thought would be nice to add to our extra-wide grids.

1. Provide row and column mouseover state

2. Provide row and column selected states

 

If you like the demo and are curious how you can add it to your existing site, you can visit the Tablecloth home page and download the code for it.  The JavaScript source is well written and very easy to understand.  Because there is a good chance we will be incorporating a similar approach into our web application, I refactored the Tablecloth example to use the ASP.NET AJAX client side API.  If we do decide to take this approach, I know there are other client-side GridView extension features we would like to roll into a single extender.  This is probably the starting point for this work ...     

<mb:GridViewExtender 
    runat="server" TargetControlID="gvCustomers"
    MouseOverCssClass="over" MouseDownCssClass="down" SelectedCssClass="selected" 
/>

Define the CSS Classes

For this sample. the following 3 CSS classes are used: .over, .selected, .down.  When the mouse is hovered over a cell, the .over class is applied to all of cells in the current cell's row and column.  When the users clicks in a cell to select it, the .selected class is applied to all of the cells in the row/column and finally, when the cells mousedown event fires, the .down class is applied to these cells as well.

For this sample, here are all the stylesheet rules that I used ...

/* table style */
table
{
    font-family:Arial,Helvetica,sans-serif;
    color:#555555;
    border-collapse:collapse;
    border-style:none;
    border-width:0px;
}
/* the sort header link */
th a 
{
    display:block;
    text-decoration:none;
    color:#FFFFFF;
}
/* common cell styles */
th, td
{
    border:1px solid #FFFFFF;
    padding:0.5em;
    text-align:left;
    font-size: 9.7pt;
}
td { cursor:default; }
/* alternating row style */
.alt { background-color:#E5F1F4; }
/* mouseover cell style */
.over { background-color:#ECFBD4; }
/* selected cell style */
.selected
{
    background:#BCE774;
    color:#555;
}
/* mouse-down cell style */
.down
{
    background:#BCE774;
    color:#FFFFFF;
}
/* header cell style */
th, th.over, th.selected, th.down
{
    background:#328AA4 url(img/tr_back.gif) repeat-x;
    color:#FFFFFF;    
}

Create the JavaScript Functions

After the classes were defined, I went ahead and created the JavaScript that would dynamically add and remove these classes at the appropriate time.  To help me with this, I created 2 helper JavaScript functions that given a row or column index, would visit every cell in the row or column.  The second parameter to these functions is a callback that would be invoked that would pass along the cell that was currently being visited.  Here are the listing for these functions ...

function visitColumn(rows, index, callback){
    //  visit all cells in the specified column
    for(var i = 0; i < rows.length; i++){
        callback(rows[i].cells[index]);
    }        
}        

function visitRow(rows, index, callback){
    //  visit all cells in the specified row
    for(var i = 0; i < rows[index].cells.length; i++){
        callback(rows[index].cells[i]);
    }       
} 

Once, these functions were created I added a pageLoad event handler to the page.  During the pageLoad event I enumerate all of the cells in the table and attach my event handlers so I can modify the CSS classes as needed ...

function pageLoad(args){

    //  get the elements
    var rows = $get('gvCustomers').getElementsByTagName("tr");
    
    //  attach our event handlers to all non header cells
    for(var i = 0; i < rows.length; i++){
        for(var j = 0; j < rows[i].cells.length; j++){
            if(rows[i].cells[j].tagName != 'TH'){
                
                var td = rows[i].cells[j];
                
                //  on mouseup, remove the 'down' class
                $addHandler(td, 'mouseup', 
                    function(sender){
                        Sys.UI.DomElement.removeCssClass(sender.target, 'down');                            
                    }
                );
                
                //  on mousedown, add the 'down' class
                $addHandler(td, 'mousedown',
                    function(sender){
                        Sys.UI.DomElement.addCssClass(sender.target, 'down');
                    }                        
                );
                
                //  on mouseover, add the 'over' class
                $addHandler(td, 'mouseover',
                    function(sender){
                        visitColumn(rows, sender.target.cellIndex, 
                            function(cell){ Sys.UI.DomElement.addCssClass(cell, 'over'); }
                        );
                        visitRow(rows, sender.target.parentNode.rowIndex, 
                            function(cell){ Sys.UI.DomElement.addCssClass(cell, 'over'); }
                        );            
                    }                        
                );
                
                //  on mouseout, remove the 'over' class
                $addHandler(td, 'mouseout',
                    function(sender){
                        visitColumn(rows, sender.target.cellIndex, 
                            function(cell){ Sys.UI.DomElement.removeCssClass(cell, 'over'); }
                        );
                        visitRow(rows, sender.target.parentNode.rowIndex, 
                            function(cell){ Sys.UI.DomElement.removeCssClass(cell, 'over'); }
                        );            
                    }                        
                );                        
                
                //  on click, add the 'select' class
                $addHandler(td, 'click', 
                    function(sender){
                        //  unselect the cells
                        for(var i = 0; i < rows.length; i++){
                            visitRow(rows, rows[i].rowIndex, 
                                function(cell){ Sys.UI.DomElement.removeCssClass(cell, 'selected'); }
                            );  
                            
                            if(i == 0){
                                for(var j = 0; j < rows[j].cells.length; j++){
                                    visitColumn(rows, j, 
                                        function(cell){ Sys.UI.DomElement.removeCssClass(cell, 'selected'); }
                                    );                        
                                }
                            }
                        }  
               
                        visitColumn(rows, sender.target.cellIndex, 
                            function(cell){ Sys.UI.DomElement.addCssClass(cell, 'selected'); }
                        );
                        visitRow(rows, sender.target.parentNode.rowIndex, 
                            function(cell){ Sys.UI.DomElement.addCssClass(cell, 'selected'); }
                        );              
                    } 
                );
            }
        }
    }
}

That's it.  Enjoy.


TrackBack

TrackBack URL for this entry:
http://mattberseth.com/blog-mt/mt-tb.fcgi/77

Listed below are links to weblogs that reference Improving the Presentation of an Extra-Wide GridView:

» Tips for improving the presentation of extra wide grid views from Mike's Random Thoughts
Tips for improving the presentation of extra wide grid views [Read More]

Comments


Posted by: Anderson Fortaleza on October 15, 2007 12:00 AM

Hi Matt, thx for the article, very useful. I have a AJAX question for you, where did you learn to program using ASP.NET AJAX Javascript Library ? Ive been looking for a good reference source but couldnt find any yet.

thank you
-- Anderson

@Anderson -

I picked most of it up from browsing through the AjaxControlToolkit code and reading MSFTs documentation.

Matt.

Very cool, thank you!

I was wondering the same thing Anderson. Man it would be nice if there was a place with docs written like mootools or jquery so it wasnt so difficult to figure out how to use the library already available to me on my .net ajax pages.

The above code was a huge start though - thanks!

Posted by: Stephen on October 25, 2007 12:00 AM

@Anderson - I found this to be a very well wrtten book on ASP.Net Ajax

http://www.amazon.com/gp/product/1933988142/104-9219624-7383145

Posted by: Nathan on October 25, 2007 12:00 AM

Anderson,

You must pick up "ASP.NET AJAX in Action." An amazing book explains both client and server side implementations using the AJAX libraries.

Dont just take my word for it, look at the rave reviews its getting on Amazon.

Hope this helps.

Posted by: Jared Roberts on October 25, 2007 12:00 AM

Matt,

Awesome post. I found a small mistake in the javascript...

in the click handler for the header row...

for(var j = 0; j

it should befor(var j = 0; j

It was j and it should be i.
Keep these posts coming.

Posted by: John on October 27, 2007 12:00 AM

Thanks for the article. Any idea how to make this work with paging?

Posted by: Todd Simon on February 6, 2008 12:00 AM

Hi Matt,

I found your gridview techniques intuitive but am having an issue with it.

After pulling down the code and updating the ajax toolkit version (Iím using .net 3.5). Your solution executes fine.. but when I add references to your dll in a new project and attempt to extend my gridview it throws a pre-render error..

When the gridview is inside of an update panel, it shows the below.

The TargetControlID of is not valid. A control with ID GridView1 could not be found.

When I move the gridview outside of the update panel, it still fails on pre-render but does have the targetcontrolid in the error message.

The TargetControlID of GridViewControlExtender1 is not valid. A control with ID GridView1 could not be found.

Im also seeing a javascript error as well -
Error: Sys.ArgumentException: Value must not be null for Controls and Behaviors. Parameter name: element.

Any ideas on why this happens within my new page/project?

Thanks in advance for the help,

Todd

Posted by: Jeff on March 10, 2008 12:00 AM

Ive been looking at several of your examples and want to merge features of two posts into a single web page. Im new to the .NET world and need a little help getting steered in the right direction. I want to use the features from your post on freezing the gridview column header and providing a scrolling feature (http://mattberseth.com/blog/2007/09/freezing_gridview_column_heade_1.html) as well as highlighting the cells as you outlined in the improving the gridview presentation (http://mattberseth.com/blog/2007/10/improving_the_presentation_of.html) post. Ive been experimenting with the code from both posts and just cant seem to make much headway. Any help from you or other viewers would be appreciated.

Posted by: jinal on March 12, 2008 12:00 AM

This is fine article. but i have one question regarding to postback. if i apply style this way then it will remove on next postback but i want to preserver style on postback. Please tell me a way.

Posted by: pk-one on August 21, 2008 05:46 AM

Nice work!
Unfortunately it seems that this can't be used with pagers... :(

Post a comment

(If you haven't left a comment here before, you may need to be approved by the site owner before your comment will appear. Until then, it won't appear on the entry. Thanks for waiting.)

Consulting Services

Yep - I also offer consulting services. And heck, I'll do just about anything. If you enjoy my blog just drop me an email describing the work you need done.

Recent Comments

  • pk-one wrote: Nice work! Unfortunately it seems that this can't be used with pagers... :(...
  • jinal wrote: This is fine article. but i have one question regarding to postback. if i apply style this way then ...
  • Jeff wrote: Ive been looking at several of your examples and want to merge features of two posts into a single w...
  • Todd Simon wrote: Hi Matt, I found your gridview techniques intuitive but am having an issue with it. After pulling ...
  • John wrote: Thanks for the article. Any idea how to make this work with paging? ...
  • Max wrote: Very cool, thank you! I was wondering the same thing Anderson. Man it would be nice if there was a...
  • Stephen wrote: @Anderson - I found this to be a very well wrtten book on ASP.Net Ajax <a href="http://www.amazon.c...
  • Nathan wrote: Anderson, You must pick up "ASP.NET AJAX in Action." An amazing book explains both client and serv...