Recreating the Google Analytics Table as an ASP.NET GridView

I really like the grid in Google Analytic's 'Top Content' grid (see below).  I like it because it incorporates a number of useful grid features without taking up too much room.   

I thought it would be interesting to see what it would take to have an ASP.NET GridView render in a similar fashion.  In particular, I was interested in incorporating the following features

  1. Include each records row number as the first column in the table
  2. Improve the column sort presentation.  The grid in the image above is currently being sorted by the Pageviews column.  Notice how there is not only an icon that indicates the sort order (ascending or descending), but the column becomes a darker shade of gray (both header and data rows)
  3. Add a 'Go to' textbox that will take the user to the specified page
  4. Add a DropDownList for changing the page size
  5. Include the current page offset as a status message (i.e. '1 - 10 of 69') 
  6. Provide 'next'/'previous' navigation between pages

Live Demo | Download

I followed these steps to create this sample.

Create the GridView and ObjectDataSource

To start, I created a very simple GridView that binds to an ObjectDataSource which provides read-only access to the rows contained in the standard Northwind.dbo.Customers table.  To remove the dependency on having an actual database, I extracted the customer rows and put them into an xml file.  Next, I created a CustomerDataObject class that sits on top of the xml file and defines the Select method the ObjectDataSource uses to fetch the data.  Here is the markup for the ObjectDataSource and the GridView.  If you download the sample project you can take a look at the CustomersDataObject.  To avoid full postback's, I placed the GridView inside an UpdatePanel.

<asp:ObjectDataSource ID="odsCustomers" runat="server" SelectMethod="Select" TypeName="CustomersDataObject" />

<asp:GridView ID="gvCustomers" runat="server" DataSourceID="odsCustomers" AutoGenerateColumns="false">
    <Columns>
        <asp:BoundField HeaderText="ID" DataField="customerid" SortExpression="customerid" />
        <asp:BoundField HeaderText="Company" DataField="companyname" SortExpression="companyname" />
        <asp:BoundField HeaderText="Contact Name" DataField="contactname" SortExpression="contactname" />
        <asp:BoundField HeaderText="Contact Title" DataField="contacttitle" SortExpression="contacttitle" />
        <asp:BoundField HeaderText="Country" DataField="country" SortExpression="country" />
        <asp:BoundField HeaderText="Phone" DataField="phone" SortExpression="phone" />
    </Columns>
</asp:GridView>

Include Row Number's

After I had the basic GridView created and operational, I implemented the first feature: displaying the row number of each row in the grid.

Because the GridViewRow object exposes a property for this purpose we can use a databinding expression to implement this feature directly within a standard TemplateField. 

<asp:GridView ID="gvCustomers" runat="server" DataSourceID="odsCustomers" AutoGenerateColumns="false">
    <Columns>
        <asp:TemplateField ItemStyle-HorizontalAlign="Center" ItemStyle-Width="30px">
            <ItemTemplate>
                <%# Convert.ToInt32(DataBinder.Eval(Container, "DataItemIndex")) + 1 %>.
            </ItemTemplate>
        </asp:TemplateField>
        ...
    </Columns>
</asp:GridView>

Improve the Column Sort Presentation

Like mentioned earlier, the Google Analytics grid has 2 nice features related to column sorting. 

The first is that an ascending or descending icon is displayed in the column header of the column that is currently being sorted by.  The second is that the complete column changes color to a darker gray making the sort selection even more obvious.  To implement both of these features I handled the GridView's RowDataBound event, looked up the column that will be used for the sort and update the style of the corresponding header cell.  The code below shows this.  There are a couple of things to note:

  1. I am finding the DataControlField by matching exactly on the GridView's SortExpression property.  This technique will not support grid's that allow multiple levels of sorting (i.e. ID asc, LastName desc).
  2. This loop is executed for every row within the current page of data.  For production, you should cache the cellIndex value within an instance member variable on the page that contains the gridview
  3. For the HeaderRow, I check the SortDirection of the GridView to determine which style to assign the the cell.  The style contains the reference to the arrow icon's
  4. For DataRow's I see if the row is an alternating row or not before I assign the style to the cell.
protected void GvCustomers_RowDataBound(object sender, GridViewRowEventArgs e)
{
    GridView gridView = (GridView)sender;

    //  is the grid currently being sorted
    if (gridView.SortExpression.Length > 0)
    {
        //  find the index of the header cell that is causing the sort
        int cellIndex = -1;
        foreach (DataControlField field in gridView.Columns)
        {
            if (field.SortExpression == gridView.SortExpression)
            {
                cellIndex = gridView.Columns.IndexOf(field);
                break;
            }
        } 

        //  if we found it, update the style of the header and datarows
        //  based on the sort
        if (cellIndex > -1)
        {
            if (e.Row.RowType == DataControlRowType.Header)
            {
                //  this is a header row,
                //  set the sort style
                e.Row.Cells[cellIndex].CssClass = 
                    gridView.SortDirection == SortDirection.Ascending ? "sortascheaderstyle" : "sortdescheaderstyle";
            }
            else if (e.Row.RowType == DataControlRowType.DataRow)
            {
                //  this is a data row
                e.Row.Cells[cellIndex].CssClass = 
                    e.Row.RowIndex % 2 == 0 ? "sortalternatingrowstyle" : "sortrowstyle";
            }
        }
    }
}

Add a DropDownList for Toggling PageSize

Now that the sorting features have been handled we can move onto the paging.  The first feature here is allowing the user to select the page size. 

This is a dropdownlist that contains the page sizes that we are allowing the user to choose from.  We can add this feature by creating a PagerTemplate for our GridView.  Within this template we can add a Label and DropDownList for handling the presentation.  Lastly, we can set handle the SelectedIndexChanged of the DropDownList where we will update the PageSize property on the GridView with the SelectedValue.  Make sure to set the AutoPostback property to true so a postback is triggered when the selected index changes.

<PagerTemplate>
    <asp:Label runat="server" Text="Show rows:" />
    <asp:DropDownList ID="ddlPageSize" runat="server" AutoPostBack="true" OnSelectedIndexChanged="GvCustomers_SelectedIndexChanged">
        <asp:ListItem Value="5" />
        <asp:ListItem Value="10" />
        <asp:ListItem Value="15" />
        <asp:ListItem Value="20" />
    </asp:DropDownList>
</PagerTemplate>
protected void GvCustomers_SelectedIndexChanged(object sender, EventArgs e)
{
    DropDownList dropDown = (DropDownList)sender;
    this.gvCustomers.PageSize = int.Parse(dropDown.SelectedValue);
}

Include the Current Page Information

The next feature on my list is to allow the user to see what page they are currently on as well as go directly to a specific page by typing the page number in a textbox.

I decided to implement this slightly different than Google's.  I combined the status and goto feature into a single element.  To do this I again used the PagerTemplate.  First I added an asp:TextBox for the 'goto' feature.  Then I added an asp:Label for holding the total number of pages.  Next, I went back to the RowDataBound event handler I created earlier and included a condition that checks to see if the current row is a PagerRow.  If it is, I fetch the controls from the pager row and update their values with the PageCount, PageIndex and PageSize properties of the GridView.  Lastly, I make sure the TextBox is setup to AutoPostback and I handle the TextChanged event to update the PageIndex to the value that is entered by the user.

Here is the markup and code for these items.

</PagerTemplate>
    Page 
    <asp:TextBox ID="txtGoToPage" runat="server" AutoPostBack="true" OnTextChanged="GoToPage_TextChanged" />
    of
    <asp:Label ID="lblTotalNumberOfPages" runat="server" />
</PagerTemplate>
protected void GoToPage_TextChanged(object sender, EventArgs e)
{
    TextBox txtGoToPage = (TextBox)sender;
    
    int pageNumber;
    if (int.TryParse(txtGoToPage.Text.Trim(), out pageNumber) && pageNumber > 0 && pageNumber <= this.gvCustomers.PageCount)
    {
        this.gvCustomers.PageIndex = pageNumber - 1;
    }
    else
    {
        this.gvCustomers.PageIndex = 0;
    }
}

Move to the Next and Previous Pages

The final step is to add the Next and Previous paging buttons.  Again, we can leverage the PagerTemplate for presentation, but we can also use the 'Page' GridView command for handling moving the grid to the next and previous rows.  When we create our buttons, all we have to do is set the CommandName for both to 'Page' and then set the CommandArgument for the move previous to 'Prev' and 'Next' for the move next button.  If you are not familiar with these commands, you can read more about them here.

<asp:Button runat="server" CommandName="Page" ToolTip="Previous Page" CommandArgument="Prev" CssClass="previous" />
<asp:Button runat="server" CommandName="Page" ToolTip="Next Page" CommandArgument="Next" CssClass="next" />

Conclusion

Here is a quick shot of the final product.

That's it.  Enjoy!


TrackBack

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

Comments


Posted by: Anonymous on August 23, 2007 12:00 AM

Its possible to encapsulate this functionality on a custom control or something like that so you can drop it in a page and utilize like a "normal" GridView? Any tip?

Posted by: Holger on August 23, 2007 12:00 AM

Bookmarked! Looks and feels very nice, Ill surely come back to this. Thanks for your effort!

Posted by: German Paak on August 23, 2007 12:00 AM

excelent !!!! Thanks.

my humble collaboration!!!

CSS File.
th {
background-color: #007DB3;
Background-image: url(bg.jpg);
}

http://farm2.static.flickr.com/1021/1217787822_9ccf9d06fc_o.gif

Hi Matt,
Always great material offered!

I kindly invite you to check my GridView that I have created last year with 7 new featres added on the GridView.

http://aspalliance.com/946

Regards

Posted by: Mike on August 30, 2007 12:00 AM

Good article. You should put a demo online, so we see it in action!

Superb!

great article. i addded "display:block" on the gridview header hyperlinks, this way the user can click anywhere in the header cell and do a sort, without having to click on the text itself.

i had to add prioritise the padding for the sort images on account of the display:block hyperlink, which fills the entire cell by default (overlapping the image).
/* gridview HeaderStyle */
tr.GridHead a
{
display: block;
color: #FFF;
}
/* gridview cell styles */
td.SortAsc
{
padding-right: 18px !important;
background:#999 url(pics/sort_asc.gif) no-repeat scroll right center;
}
td.SortDesc
{
padding-right: 18px !important;
background:#999 url(pics/sort_desc.gif) no-repeat scroll right center;
}

Posted by: Jakub on August 30, 2007 12:00 AM

Looks cool!

Posted by: Mega on August 30, 2007 12:00 AM

Excellent Example

Posted by: Ceex on September 1, 2007 12:00 AM

Excellent article! Thanks!

Perfect article. Thank You.
Please consider creating a calendar server control like the one in analytics.

Cheers,http://www.bloggingdeveloper.com

Posted by: paul on September 2, 2007 12:00 AM

thanks for the article... i will definetly be trying a few things..

one problem i have at the moment is I use a ajax hover menu extender on each gridview row with "Edit Detail" text popping up which takes you to a new page... to stop a js error occuring i have

but this means the paging buttons dont work within the update panel... they force a page reload...

any ideas to stop the page reload?

Posted by: Mark on September 5, 2007 12:00 AM

Hi - this is very good - one question, if I add a number for the dropdown list, which is more than the items in my dataset, I lose the pager footer - and cant therefore set the pager settings to a smaller number again. Would you know how to resolve that?

Thanks for a very good article.

Mark

Posted by: Mustafa Basgun on September 17, 2007 12:00 AM

Hi! VB.NET version of the Column Sort Presentation section of this article can be something like this:

Protected Sub GvCustomers_RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs)

Dim gridView As GridView = CType(sender, GridView)

If gridView.SortExpression.Length > 0 Then

Dim cellIndex As Integer = -1
Dim field As DataControlField

For Each field In gridView.Columns

If field.SortExpression = gridView.SortExpression Then
cellIndex = gridView.Columns.IndexOf(field)
Exit For
End If

Next field

If cellIndex > -1 Then

If e.Row.RowType = DataControlRowType.Header Then

If gridView.SortDirection = SortDirection.Ascending Then
e.Row.Cells(cellIndex).CssClass = "sortascheaderstyle"
Else
e.Row.Cells(cellIndex).CssClass = "sortdescheaderstyle"
End If

ElseIf e.Row.RowType = DataControlRowType.DataRow Then

If e.Row.RowIndex Mod 2 = 0 Then
e.Row.Cells(cellIndex).CssClass = "sortalternatingrowstyle"
Else
e.Row.Cells(cellIndex).CssClass = "sortrowstyle"
End If

End If

End If

End If

End Sub

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

FOr the issue of the pager being hidden if the number of total records is smaller then the page size you can set the pager to always visible in the pre-render event.

protected void GridView5_PreRender(object sender, EventArgs e)
{
GridView grid = (GridView)sender;
if (grid != null)
{
GridViewRow pagerRow = (GridViewRow)grid.BottomPagerRow;
if (pagerRow != null)
{
pagerRow.Visible = true;
}
}
}

Nicely done! Been messing around with this sample a little bit today and I got a question for you. Is it normal for the row number column to remain static when changing the sort order? I guess I was expecting it to change when I resorted, but I dont know that it should.

Thanks!

Posted by: Anonymous on November 12, 2007 12:00 AM

how to handle textchanged event for a textbox which has been added to a gridview control using template fields

Posted by: Ratnesh on December 3, 2007 12:00 AM

Great Job Done..

Cheers

Posted by: Felipe Santana on December 3, 2007 12:00 AM

Great, i put trackback in my blog

http:netcodigo.webinfo.es

Very interesting post! Good job and I love your eye for detail. Nice!

This page is full of great articles, like this one, this enhances the simple and borinbg gridview but it demostrates to all of us the potential of this data control, i like the fancy drop down to move through the gridview refreshing the page number, there isnt to much sites talking to this topic about customizing grid, but theres a lot that sell a solution, with no code, in my opinion, keep doing this type of articles, they are awesome.

Posted by: UstesG on December 18, 2007 12:00 AM

How to stop next and previous postbacks with Ajax. I dont even hit the event.

Posted by: Alvic Paje on December 21, 2007 12:00 AM

Great post! Thanks for showing us how to make great use of the GridView!

Posted by: Jeeva on January 9, 2008 12:00 AM

Hi Matt,

Great work!Really appreciable.Can you please
upload an article for Gridview Paging like this 1|2|3|4|5|Next5

Posted by: Sweetrpea on January 9, 2008 12:00 AM

GoToPage_TextChanged and GvCustomers_SelectedIndexChanged dont seem to work when I have them in the code behind. No errors, the events just dont fire.

Any ideas?

Posted by: oooshola on January 20, 2008 12:00 AM

Fantastic article. I have only one question: Is the Select method always returning all the rows in the table? If so, perhaps it would be more efficient to only return rows needed for the specified page index and page size.

Thanks in advance to anyone who can answer this!

Posted by: Matt G on February 15, 2008 12:00 AM

@Jeeva rebind your grid.

DropDownList dropDown = (DropDownList)sender;
this.gv.PageSize = int.Parse(dropDown.SelectedValue);
this.gv.DataBind();

Posted by: Matt G on February 15, 2008 12:00 AM

opps that reply was too Sweetrpea :)

Posted by: Mathew Baker on February 28, 2008 12:00 AM

Matt,

Your articles are always quite impressive. The dropdown list and page navigation jazzed up the page. However, it also caused a half day of hardache - due to the pager row disappearing!

The problem arose due to the pager row disappearing after the page size was larger than the row count - logical. However, when redirecting to another page via linkbutton, and the users passion to press the Back button in the browser - thats when things started to get messy. To make my situation worse, I have a timer control on the master page that causes one postback (a sort progress bar on background activities...). After pressing the back button, the gridview shows the pager row again, and then when the async postback occurs for the timer control, the dreaded Invalid Viewstate error was occuring, and then the Invalid postback or callback argument error. After many hours of tearing my hair out - the solution (I mean your solution MAN) was to make the pager row always visible!

Problem solved - for now anyway.

Your my savour man - my own personal Jesus Christ! (Matrix)

Posted by: Jay on April 23, 2008 12:00 AM

First of all, I must say that your articles are simply awesome. Really appreciate you sharing them with us.

Now coming to the pager template, it works great. However, I was wondering if there was a way to reuse this logic. I have lots of gridviews in my app and to wire up the logic for each one of those is quite tedious.

Any insights would be greatly appreciated!

Thanks,
-Jay

Posted by: Mark on April 24, 2008 12:00 AM

Hi, Matt. Great and Usefull Article.
I encountered problem with gridview stylechanging after changing sorting or page. Gridview and Letters become bigger, bolder.
How can i solve this problem?

Posted by: jagjot on June 25, 2008 12:00 AM

Greatest article ever on grids. well done... if there is any OSCAR for Grid i will give you.....but i am stuck in one problem...
GoToPage_TextChanged and GvCustomers_SelectedIndexChanged dont seem to work when I have them in the code behind. No errors, the events just dont fire.

Any ideas?

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

  • jagjot wrote: Greatest article ever on grids. well done... if there is any OSCAR for Grid i will give you.....but ...
  • Mark wrote: Hi, Matt. Great and Usefull Article. I encountered problem with gridview stylechanging after changin...
  • Jay wrote: First of all, I must say that your articles are simply awesome. Really appreciate you sharing them w...
  • Mathew Baker wrote: Matt, Your articles are always quite impressive. The dropdown list and page navigation jazzed up th...
  • Matt G wrote: @Jeeva rebind your grid. DropDownList dropDown = (DropDownList)sender; this.gv.PageSize = int.Pars...
  • Matt G wrote: opps that reply was too Sweetrpea :) ...
  • oooshola wrote: Fantastic article. I have only one question: Is the Select method always returning all the rows in t...
  • Jeeva wrote: Hi Matt, Great work!Really appreciable.Can you please upload an article for Gridview Paging like t...