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
- Include each records row number as the first column in the table
- 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)
- Add a 'Go to' textbox that will take the user to the specified page
- Add a DropDownList for changing the page size
- Include the current page offset as a status message (i.e. '1 - 10 of 69')
- Provide 'next'/'previous' navigation between pages
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:
- 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).
- 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
- 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
- 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!
Comments
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?
Bookmarked! Looks and feels very nice, Ill surely come back to this. Thanks for your effort!
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
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;
}
Looks cool!
Excellent Example
Excellent article! Thanks!
Perfect article. Thank You.
Please consider creating a calendar server control like the one in analytics.
Cheers,http://www.bloggingdeveloper.com
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?
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
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
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!
how to handle textchanged event for a textbox which has been added to a gridview control using template fields
Great Job Done..
Cheers
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.
How to stop next and previous postbacks with Ajax. I dont even hit the event.
Great post! Thanks for showing us how to make great use of the GridView!
Hi Matt,
Great work!Really appreciable.Can you please
upload an article for Gridview Paging like this 1|2|3|4|5|Next5
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?
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!
@Jeeva rebind your grid.
DropDownList dropDown = (DropDownList)sender;
this.gv.PageSize = int.Parse(dropDown.SelectedValue);
this.gv.DataBind();
opps that reply was too Sweetrpea :)
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)
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
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?
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?