Clickable GridView Headers
By default, you can sort on the columns of a GridView by clicking on the LinkButton in the header of a column that has a SortExpression defined. This post is going to attempt three things.
- Sort the column by clicking anywhere in the header cell
- Add a sort indicator to cells that are sortable. Cells that are sortable will have a "neutral" icon. Note in the screen capture below that UnitsInStock does not have a "neutral" icon because it does not have a SortExpression defined.
- Highlight the column of data that is being sorted.
The idea is to add an onclick attribute that calls a javascript function to all GridView header cells that are sortable.
In the javscript function, we determine which element raised the event. If the element that raised the event is not the LinkButton, we find the LinkButton and invoke its onclick or href attribute method based on the GridView EnableSortingAndPagingCallbacks property. When the EnableSortingAndPagingCallbacks is set to true, we invoke the onclick method otherwise we invoke the method the href attribute points to.
We are checking which element raised the event in the javscript function to prevent the postback or callback from being called twice. This is because, without this check, clicking on the LinkButton will fire the method as well as bubble the event up to its parent cell causing it to be fired again (since we have added an onclick attribute to the cell). The javascript function calls the postback or callback method only if the element that raised the event is not the LinkButton.
public void MakeGridViewHeaderClickable(GridView gridView, GridViewRow gridViewRow) {
if (gridViewRow.RowType == DataControlRowType.Header) {
for (int i = 0; i < gridView.Columns.Count; i++) {
string sortExpression = gridView.Columns[i].SortExpression;
TableCell tableCell = gridViewRow.Cells[i];
//Make sure the column we are working with has a sort expression
if (!string.IsNullOrEmpty(sortExp ression)) {
//Enumerate the controls within the current cell and find the link button.
foreach (Control gridViewRowCellControl in gridViewRow.Cells[i].Controls) {
LinkButton linkButton = gridViewRowCellControl as LinkButton;
if ((linkButton != null) && (linkButton.CommandName == "Sort")) {
//Add an onclick attribute to the current cell
tableCell.Attributes.Add("onclick", "RequestData('" + linkButton.ClientID + "', this, event)");
tableCell.Style.Add(HtmlTextWriterStyle.Cursor, "hand");
tableCell.Style.Add(HtmlTextWriterStyle.Cursor, "pointer");
break;
}
}
}
}
}
}
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e) {
MakeGridViewHeaderClickable(GridView1, e.Row);
}
The javascript function is shown below
<script language="javascript" type="text/javascript">
function RequestData(linkId, cellElement, evt) {
var evtSource;
evt = (evt)? evt : window.event;
evtSource = (evt.srcElement)? evt.srcElement : evt.target;
//When a hyperlink is clicked, Safari returns the text node as the source element rather
//than the hyperlink. parentNode will give us the hyperlink element.
//ref: http://developer.apple.com/internet/webcontent/eventmodels.html
if (evt.target) {
if (evt.target.nodeType == 3) {
evtSource = evtSource.parentNode;
}
}
//If event was raised from an element other than the LinkButton
if ((evtSource.getAttribute("id") != linkId) && (evt.type == "click")) {
//Get a collection of "a" tags inside the cell
var linkCollection = cellElement.getElementsByTagName("a");
for (var i = 0; i < linkCollection.length; i++) {
//If the link button has an onclick attribute, call the onclick.
//The onclick attribute is present when the GridView is using callback
//example: onclick="java script:__gvGridSort1_GridView1.callback(...); return false;"
var onClickAttribute = linkCollection[i].getAttribute("onclick");
if (onClickAttribute != null) {
linkCollection[i].onclick();
break;
}
//If the link button has a href attribute, set the location of the page
//to the href value.
//The href attribute is used when the GridView is not using callbacks
//example: href="java script:__doPostBack('GridSort1$GridView1','Sort$UnitsOnOrder')"
var hrefAttribute = linkCollection[i].getAttribute("href");
this.location.href = hrefAttribute;
break;
}
}
}
</script>
If you run the above code, you will notice that it is different from screen capture above because the sort image and column highlighting are missing. To implement this, we need to add some code to the MakeGridViewHeaderClickable method as shown below.
public void MakeGridViewHeaderClickable(GridView gridView, GridViewRow gridViewRow) {
if (gridViewRow.RowType == DataControlRowType.Header) {
for (int i = 0; i < gridView.Columns.Count; i++) {
string sortExpression = gridView.Columns[i].SortExpression;
TableCell tableCell = gridViewRow.Cells[i];
//Make sure the column we are working with has a sort expression
if (!string.IsNullOrEmpty(sortExp ression)) {
System.Web.UI.WebControls.Image sortDirectionImageControl;
//Create an instance of a Image WebControl
sortDirectionImageControl = new System.Web.UI.WebControls.Image();
//Determine the image url based on the SortDirection
string imageUrl = "~/Images/sort_neutral.gif";
if (sortExpression == gridView.SortExpression) {
imageUrl = (gridView.SortDirection == SortDirection.Ascending) ?
"~/Images/sort_asc.gif" : "~/Images/sort_desc.gif";
}
//Add the Image Web Control to the cell
sortDirectionImageControl.ImageUrl = imageUrl;
sortDirectionImageControl.Style.Add(HtmlTextWriterStyle.MarginLeft, "10px");
tableCell.Wrap = false;
tableCell.Controls.Add(sortDirectionImageControl);
//Enumerate the controls within the current cell and find the link button.
foreach (Control gridViewRowCellControl in gridViewRow.Cells[i].Controls) {
LinkButton linkButton = gridViewRowCellControl as LinkButton;
if ((linkButton != null) && (linkButton.CommandName == "Sort")) {
//Add an onclick attribute to the current cell
tableCell.Attributes.Add("onclick", "RequestData('" + linkButton.ClientID + "', this, event)");
tableCell.Style.Add(HtmlTextWriterStyle.Cursor, "hand");
tableCell.Style.Add(HtmlTextWriterStyle.Cursor, "pointer");
break;
}
}
}
}
}
//Enuerate all the rows and change the color of the column that is being sorted
if (gridViewRow.RowType == DataControlRowType.DataRow) {
for (int i = 0; i < gridViewRow.Cells.Count; i++) {
if ((!String.IsNullOrEmpty(gridView.SortExpr ession)) &&
(gridView.Columns[i].SortExp ression == gridView.SortExp ression)) {
Color sortColumnBgColor;
sortColumnBgColor = (gridViewRow.RowState != DataControlRowState.Alternate) ?
ColorTranslator.FromHtml("#d7e3f1") : ColorTranslator.FromHtml("#f7fbff");
gridViewRow.Cells[i].BackColor = sortColumnBgColor;
}
}
}
}
You can also use this with HeaderTemplates. Just make sure your HeaderTemplate contains a LinkButton with a CommandName attribute set to "Sort" as shown below:
<asp:TemplateField HeaderText="ProductID" SortExpression="ProductID">
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("ProductID") %>'></asp:Label>
</ItemTemplate>
<HeaderTemplate>
<asp:LinkButton runat="server" CommandName="Sort" CommandArgument="ProductID"
Text="ProductID"></asp:LinkButton>
</HeaderTemplate>
</asp:TemplateField>
You can easily make these transparent sort icons by working at the pixel level ( I used Photoshop)
The MakeGridViewHeaderClickable method can be placed in a Utility class if you so desire. A reference to the GridView can be obtained by using gridViewRow.Parent.Parent or (GridView) gridViewRow.NamingContainer if you don't want to pass in a GridView reference to the method.
Tested on IE 6.0.2900, Firefox 1.5.0.6 and Safari 1.3.2
Note: I had to add a space in some variables because Community Server (running this blog) was blocking my scripts. Example: sortExp ression.