ASP.NET & CSS: Styling sortable columns in the GridView using CSS

The following is an article describing one of the techniques I spoke about during my recent RDN topic, CSS Layout with ASP.NET & Visual Studio 2008.

A common user interface requirement for applications is to visually indicate when:

  1. a column in a grid is sortable;
  2. which column in a grid is currently the sort column; and
  3. which direction the sort is being applied in (ascending or descending).

In ASP.NET, we generally use the GridView control to display tabular data and provide the user with the ability to sort and page through that data. There are many examples on the web of techniques you can use to programmatically insert an image to indicate the currently sorted column and its sort direction (including the MSDN documentation). There are a number of problems with this approach:

  1. It mixes functional code with presentation logic. The code behind for our pages and user controls is complicated enough without having to trawl through formatting and layout logic;
  2. It requires you to add this code on every GridView (or sub-class the GridView and use the new version instead); and
  3. It dilutes the semantic meaning of the resultant HTML mark-up, thus affecting the page’s usability (IMG tags have no place in the header of a TABLE).

So what’s a better solution I hear you say? The following solution uses ASP.NET, the CSS Friendly Control Adapters and CSS styling to:

  1. add sorting information to a GridView in a semantic and accessible fashion; and
  2. apply visual effects relating to sorting behaviour.

The CSS Friendly Control Adapters alter the HTML mark-up produced by ASP.NET for many of the included server controls so that it is more accessible, standards compliant and, importantly, easier to style using CSS. You can grab the latest source code for the adapters from CodePlex (be sure to download from the source code section, not the release section), however the technique shown here requires a small change to the included GridView adapter (further details about incorporating the CSS Friendly Control Adapters into your web project are included in the download). So, once you’ve downloaded the source, open up the solution and find the GridViewAdapter.cs file:

image

Replace its contents with this modified version. This adds a new function that sets the CSS class of the GridView’s header cells based on which column the GridView is currently sorted by. So for example if I have a GridView defined as such:

<asp:GridView ID="gvExample" runat="server" DataSourceID="pdsExample" DataKeyNames="Id"
AllowPaging="true" AllowSorting="true" PageSize="10" AutoGenerateColumns="false" CssClass="customers-grid">
<
Columns>
<
asp:BoundField HeaderText="First Name" DataField="FirstName" SortExpression="FirstName"
HeaderStyle-CssClass="first-name" ItemStyle-CssClass="first-name" />
<
asp:BoundField HeaderText="Last Name" DataField="LastName" SortExpression="LastName"
HeaderStyle-CssClass="last-name" ItemStyle-CssClass="last-name" />
<
asp:BoundField HeaderText="Age" DataField="Age" SortExpression="Age"
HeaderStyle-CssClass="age" ItemStyle-CssClass="age" />
<
asp:BoundField HeaderText="Member for" DataField="YearsAsMember" SortExpression="YearsAsMember"
HeaderStyle-CssClass="years-as-member" ItemStyle-CssClass="years-as-member" />
</
Columns>
</
asp:GridView>

Then when the GridView is rendered sorted by the First name column, the HTML mark-up emitted will look like this:

<div class="AspNet-GridView" id="ctl00_main_gvExample">
<
table cellpadding="0" cellspacing="0" summary="" class="customers-grid">
<
thead>
<
tr class="AspNet-GridView-Header">
<
th class="sortable sorted asc first-name" scope="col">
<
a href="javascript:__doPostBack('ctl00$main$gvExample','Sort$FirstName')">First Name</a></th>
<
th class="sortable last-name" scope="col">
<
a href="javascript:__doPostBack('ctl00$main$gvExample','Sort$LastName')">Last Name</a></th>
<
th class="sortable age" scope="col">
<
a href="javascript:__doPostBack('ctl00$main$gvExample','Sort$Age')">Age</a></th>
<
th class="sortable years-as-member" scope="col">
<
a href="javascript:__doPostBack('ctl00$main$gvExample','Sort$YearsAsMember')">Member for</a></th>
</
tr>
</
thead>
<
tbody><!-- Data rows here --></tbody>
</
table>
</
div>

Note how we now have CSS classes added to the <th> tags to describe their sorting behaviour (sortable, sorted and asc). We can see that all columns are sortable, and that the First Name column is currently being sorted by in the ascending direction. And because the logic to apply these classes is in the control adapter, it is automatically applied to all GridViews in the application.

There is a caveat to this approach however. The adapter relies on two properties of the GridView to determine the current sort column and direction: SortExpression and SortDirection. These are read only properties that are only populated when you provide data to the GridView by way of one of the data source controls introduced in ASP.NET 2.0, e.g. ObjectDataSource, SqlDataSource, etc., and the DataSourceID property. If you provide data to your GridView by setting its DataSource property directly from code behind (and calling DataBind()) the sort properties are not set and thus the adapter cannot insert the required CSS class names. You must also initially call the Sort() method on the GridView from the Page_Load() method in order to force the setting of these properties the first time the page is rendered:

protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
// Set default sort expression
gvExample.Sort("FirstName", SortDirection.Ascending);
}
}

If you are not using the data source controls (the ObjectDataSource in particular) I thoroughly recommended you take a look as they provide you with "free" sorting and paging functionality out of the box without the need to manually handle events on the GridView. If you don’t like the way the ObjectDataSource requires a separate business logic class to provide its data access functionality, for instance if you are implementing a MVP pattern in the "Passive View" flavour, then you can sub-class the OjectDataSource and force it to look on the instance of the class for the hosting page (or user control) for its methods instead (this is the method I used in the RDN sample solution and I will post an article on this technique shortly).

Now that we have our CSS classes set we can attach some CSS rules that provide some visual indicators on the sorting behaviour. The following CSS rules do just that (I’ve placed these in a common Grid.css file in the sample project):

div.AspNet-GridView {
border: 1px solid #828790;
font-size: 0.8em;
min-height: 1px;
font-family: "Lucida Sans";
}

div.AspNet-GridView table {
width: 100%;
border-collapse: collapse;
}

div.AspNet-GridView table thead tr th {
padding: 3px 3px 2px 2px;
background: url('grid-header-back.gif') top left repeat-x;
font-weight: normal;
border-bottom: 1px solid #d5d5d5;
border-right: 1px solid #e3e4e6;

}
div.AspNet-GridView table thead tr th.sortable {
padding: 0px;
}
div.AspNet-GridView table thead tr th.sortable:hover {
background: #b7e7fb url('grid-header-sortable-back-hover.gif') top left repeat-x;
border: 1px solid #96d9f9;
border-top: none;
border-left: none;
}

div.AspNet-GridView table thead tr th.sortable a {
display: block;
padding: 3px 3px 2px 2px;
color: Black;
min-height: 1px; /* Force layout in IE7 to prevent rendering issues */
}
div.AspNet-GridView table thead tr th.sortable a:hover {
text-decoration: none;
}

div.AspNet-GridView table thead tr th.sorted {
background: #d8ecf6 url('grid-header-sorted-back.gif') top left repeat-x;
border: 1px solid #96d9f9;
border-top: none;
border-left: none;
}

div.AspNet-GridView table thead tr th.asc a {
background: transparent url('grid-header-asc-glyph.gif') center 1px no-repeat;
}

div.AspNet-GridView table thead tr th.desc a {
background: transparent url('grid-header-desc-glyph.gif') center 1px no-repeat;
}

div.AspNet-GridView table tbody tr td {
padding: 2px 6px 2px 4px;
border: 1px solid #efefef;
}

div.AspNet-GridView table thead tr th.action,
div.AspNet-GridView table tbody tr td.action {

border-left: none;
border-right: none;
}
div.AspNet-GridView table tbody tr td.action {
padding: 2px 2px 2px 2px;
width: 40px;
text-align: center;
}

div.AspNet-GridView table tbody tr.AspNet-GridView-Alternate td {
background: #f2f9fc;
}

div.AspNet-GridView-Pagination {
background: #e6e9ee;
text-align: center;
padding: 2px 3px 2px 3px;
font-size: 0.9em;
min-height: 1px; /* Force layout in IE7 to prevent rendering issues */
}
div.AspNet-GridView-Pagination span {
padding: 0px 3px;
background: #d8ecf6;
border: 1px solid #96d9f9;
}

div.grid-row-count {
color: #666666;
padding: 2px 0px 2px 6px;
font-size: 0.8em;
}

These style rules use a small number of background images to give our GridView a nice Vista-like look:

image

You can immediately see that the First Name column is currently the sort column in the ascending direction. Hovering over other sortable columns provides feedback by way of a :hover background image, here I’ve hovered my mouse over the Last Name column:

image

I’ve also added some formatting to the pager section of the GridView to highlight the current page.

To apply styles that are specific to this particular GridView, e.g. column widths, we can add some style rules like thus:

table.customers-grid {

}

table.customers-grid th.first-name,
table.customers-grid td.first-name {
width: 35%;
}

table.customers-grid th.last-name,
table.customers-grid td.last-name {

}

table.customers-grid th.age,
table.customers-grid td.age {
text-align: center;
width: 10%;
}

table.customers-grid th.years-as-member,
table.customers-grid td.years-as-member {
width: 15%;
text-align: center;
}

Note: I’ve tested the included CSS here with IE7 and Firefox. There is a small issue with the Firefox rendering which I haven’t bothered to fix (the left-most border is not displayed due to the different ways IE7 and Firefox collapse borders) and you will definitely need to make some changes to properly support IE6 (e.g. adding support for the :hover pseudo class on elements other than <a> using an .htc file) but they are outside the scope of this article.

So there you have it. Using this method we have freed our code-behind of nasty image injection code and made our GridViews more accessible and stylable to boot.

Download the sample project for this article here


13 Comments on “ASP.NET & CSS: Styling sortable columns in the GridView using CSS”

  1. Kurniawan says:

    Hi Damian,That’s really cool solution.However It seems that ObjectDataSource is very expensive.It will call your GetData Method everytime it postback.On the other hand, if we use databind , we can optimize so that it only bind in time of need.And also in GetData Method, you can not share any variable for example you want to have an option where the user can change the page size or filter by first name. Inside GetData Method we can not access another control, such as txt will be null because it runs on different context.Do you think we can not implement this easily in without DataSource ?Thanks..Kurniawankajaxnet.blogspot.com

  2. Damian says:

    Hi Kurniawan,
     
    In answer to your comment: it depends :)
     
    I’m about to publish another article that discusses this exact issue and, more importantly, offers a solution by way of sub-classing the ObjectDataSource and changing its behaviour so that it binds to the page (or user control) it is hosted on, rather than a business object. This means the SelectMethod, UpdateMethod, etc. can be instance methods on your page, allowing you to access the values of other controls and include whatever logic you wish regarding data retrieval. So while the data source control might still call the SelectMethod on every postback, you’re in total control of what the SelectMethod actually does (retrieve data from cache, viewstate, etc.) That way you get the benefits of the ObjectDataSource (paging, sorting, declarative binding) with the control of the DataSource & DataBind() method.
     
    I’ll be posting this article sometime today. I hope it answers your concerns.

  3. Kurniawan says:

    Thanks Damian,

    It will be very nice if ObjectDataSource can be an instance method of the page.
    Thanks in advance for your next post

    Is there a way to suppress Object Data Source to call SelectMethod ?
    So that It will not call select method everytime it is postback.

    Thanks,

  4. Kurniawan says:

    Have you ever get problems when you put GridView inside Update Panel ?When I try this inside update panel It throw exception "Unknown Runtime Error" when clicking on sorting and paging.Is that related with the styles ?

  5. Kurniawan says:

    Because If I disable the CSS Friendly, In APP_Browser for gridview, It works fine.Any idea ?

  6. Kurniawan says:

    Find the problem.It needs to put GridView under Table ….If It put it under div / without – It always throw that error.I don’t know why it happen ?Any suggestion ?Thanks

  7. Kurniawan says:

    It works fine now. That’s because there is <a name="body" /> in Master page.btw, If I put your GridView outside update panel and keep EnableSortingAndPagingCallbacks="True" When I click on Paging and Sorting, It throw error Microsoft JScript runtime error: ‘panelElement’ is null or not an object. I think this is cause by CSS Friendly, because If I take the CSS Friendly out for GridView, It works fineAny comment ?Thanks

  8. Rcqedjwe says:

    How many more years do you have to go? Toplist Ls Model
    453

  9. Jorge says:

    thanks for sharing. I found your article to be very useful. I’ve been having trouble applying CSS to gridview controls.

  10. site says:

    Just to let you know your web site appears a little bit different on Safari on my laptop using Linux .

  11. rikky says:

    Canada>Canada hilton cigarettes online of an inter-disciplinary health

  12. промокод kidmart says:

    I’d like to tell you about a change of address промокоды kidmart


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: