Thursday, October 25, 2007

Hover Menu inside a gridview

It's been a while since I found myself doing something that's trickey and not available on google the way I wanted.

Problem:
Scenario1
So, what are we talking about here? Lets say We have a gridview and have some command fields inside the grid, like Edit, Delete, NavigatetoURL and so on. It'll look clumsy to have all these commands (linkbuttons or hyperlinks) in seperate columns or even in one column.


Scenario2
Sometimes, we might want some commands to show up based on the row's data. In the page that I was working on, I have to show PRINT, EMAIL, EDIT and PREVIEW buttons for each row in a gridview. Moreover, not all rows have the same buttons. Some rows cannot be editied and some rows cannot be emailed (when there is no email address to email).


Solution:
So, what can we do to have the above two functionalities and at the same time make our gridview look pretty? Well here we are:



The above is a regular gridview that we all might have seen and implemented. It's plain and doesn't implement the command fields as of now. As you know we usually have the last or first column containing command fields like EDIT (UPDATE & CANCEL), DELETE and so on.


Now, don't get discouraged yet because, our final product will look something like this:

(Ignore the blue vertical column. It is a portion of the page background and will do us no harm). Now notice that the row style changes when you do a mouseover event over it. Also, you get a div popup showing some menu buttons (they're actually databound command fields!). That popup is what this article is about.



Pretty cool eh? This works for row specific command fields (like we discussed earlier) as well. We just have to tweak it.


Behind the scenes:
So, what's happening? Let's start from the markup of the gridview. Since I am assuming that you already have a working knowledge on the gridview and template fields, I am not going to go into details.

--------------------------------------------------------


The template field markup: (click for larger image)
Now this is the template for the last column. That is where we're going to add our div popup. As you can see, I currently have two link buttons inside the div. You can have any number of buttons you want. Also, you can apply styles to it to make it look pretty. I leave that portion to you.

As you can see, I am placing the div tag with all the applicable menu items (as linkbuttons) within the last column's itemtemplate section. Note that I also have set the z-index of the div tag and an absolute positioning. This is vital for the positioning of the div. Since I have the div insite the itemtemplate, I can bind it with a dataitem and even give it a commandname. Sweet!

---------------------------------------

The Client side script to toggle the div's visibility ON and OFF:

// Shows DIV popup commands for gridview
function ShowPopup(lbtn1,lbtn2, panel, grid)
{
var link1 = document.getElementById(lbtn1);
var link2 = document.getElementById(lbtn2);
var pnl = document.getElementById(panel);
var grd = document.getElementById(grid);
pnl.style.display = "block";
//grd.style.backgroundColor="#ffff00";
grd.style.backgroundImage = "url(../images/td_mouseover.gif)";
if(link1 != null)
link1.style.display = "block";
if(link2 != null)
link2.style.display = "block";
//pnl.style.backgroundColor='#99cccc';
pnl.style.backgroundImage = "url(../images/td_mouseover_inverted.gif)";
}
//Hides DIV popup commands for gridview
function HidePopup(lbtn1,lbtn2, panel, grid)
{
var link1 = document.getElementById(lbtn1);
var link2 = document.getElementById(lbtn2);
var pnl = document.getElementById(panel);
var grd = document.getElementById(grid);
grd.style.backgroundImage="url(../images/spacer.gif)";
pnl.style.display = "none";
if(link1 != null)
link1.style.display = "none";
if(link2 != null)
link2.style.display = "none";
}

Understanding this script is very simple. The parameters you saw in the two functions ShowPopup and HidePopup are described below:

  • lbtn1: LinkButton1. I just named it this way so that I'l know that it is the first button in our menu. Remember to databind it because you're putting it inside a gridview.
  • lbtn2: LinkButton2. The second link button in the div popup. As I mentioned earlier, you can have any number of controls (usually buttons or hyperlinks for our purposes) inside the div. All you then have to do is add the id of that control as a parameter in your javascript functions and set the visibility to 'none' or 'block'.
  • panel: I just call the div layer as panel becuase, essentially they're the same. You have to toggle visibility for the div (or the panel as I call it here) on and off first before you toggle the visibility of the menu items inside it (the linkbuttons and/or any other controls that you might have).

Customizing the menu items inside the div popup:
If you see the image of the new gridview, it will show you options to PRINT and EMAIL. You can set the visibility of these items ON or OFF on the row's mouseover event. That you already know. Now, the interesting part is, you can display row-specfic command fields based on a value from the row. That means, you can display only the PRINT button for certain rows, and EMAIL button for certain rows. Of course you can have permutations and combinations for this. How do we go about doig that?

Well, here is the Row Data Bound event for the gridview. As we know, this event fires up when a gridview row is data-bound. So, this is probably the best place to set row specific scripts and conditions.


protected void gvwMyReferrals_RowDataBound(object sender, GridViewRowEventArgs e)
{
// Implement below logic if needed
// Get Status and show toggle linkbutton visibility
if (e.Row.RowType == DataControlRowType.DataRow)
{
LinkButton lbtnGrdPrint = (LinkButton)e.Row.Cells[6].FindControl("lbtnGrdPrint");
LinkButton lbtnGrdEmail = (LinkButton)e.Row.Cells[6].FindControl("lbtnGrdEmail");
HtmlGenericControl panel = (HtmlGenericControl)e.Row.Cells[6].FindControl("gvwMyReferralsDiv");
string showPopup = "ShowPopup('" + lbtnGrdPrint.ClientID + "','" + lbtnGrdEmail.ClientID + "','" + panel.ClientID + "','"+e.Row.ClientID+"')";
string hidePopup = "HidePopup('" + lbtnGrdPrint.ClientID + "','" + lbtnGrdEmail.ClientID + "','" + panel.ClientID + "','" + e.Row.ClientID + "')";
e.Row.Attributes.Add("onmouseover", "javascript:"+showPopup);
e.Row.Attributes.Add("onmouseout", "javascript:" +hidePopup);
}
}


I first check to see if the row is a data row (which eliminates pager, header and footer rows). Then, I get the instance of the controls that make up the div popup menus. First I get the instances of the linkbuttons and then I get the instance of the div tag. Notice that I am casting the div as a panel? Well, that is not casting! Since the panel is a div tag, this method works fine and does not throw casting exceptions. If you have more controls inside the div, get instances of those controls and also the instance of the div.

Now that we have instances of the neccasary contols, it is time to call the client script functions but, not just yet. I'll explain what I have not implemented as of yet. Remember me writing that you can have row-specific menu items inside the div? Let's discuss about that a little. In the row data bound, we can get some value from the data source that is bound to a boundfield or template field, and based on that, we can manipulate the visibility of the controls inside the link button. In my example (see the image of the new gridview), I have set the div tag to be a part of the last column with name as STATUS. Now, I can use STATUS's cell data to display the linkbuttons. All I have to do is, within an if statement, check the STATUS's string value and based on any condition, I can set the value of a variable (that I have declared before) to either the client ID of the control(if I want to show it inside the div) or just pass the variable to the script (instead of the client ID). In short, make sure to pass to the javascript the actual client ID of the control you want to show up and a ZERO VALUE instead if you don't want it to show up. So, when I pass the empty string to the script, it ignores that parameter based on this condition:

if(link1 != null) link1.style.display = "none";

This will make sure the script doesn't blow up. Now, when you hover over each row, you can have menu specific items inside your div popup. Since I am also passing e.Row to the script, I can make the whole row's style change on an onmouseover event. That will give the grid a look and feel of a clickable row. Of course you can extend it by adding styles through javascript. Pretty cool eh?

Style's files:
Well what did I miss? Ah, the images! I'll just put the two images that I used to set the background of the row when you mouse your mouse pointer over it.


The above is used to set the style for the gridview row when you do an onmouseover event.

The below image is used by the div as a background. You can change anything you want to fit your needs.

Questions? Sure! Suggestions? Even better! I like to know how this can be enhanced and/or tuned. I can be reached at vimaljonn@gmail.com.