How To: Use the HoverMenuExtender for GridView RowCommands

I love the AjaxControlToolkit.  The controls are free, the code can be downloaded, and the documentation is well above average.  However, I was a little surprised when I looked at the code and supporting documentation for the HoverMenuExtender's sample page.  The documentation states the following

In the sample above, an ASP.NET GridView is populated with data from a database. In each row of the GridView, a HoverMenu associates the content of the row with a Panel containing links that operate on that row.

The sample page does contain a GridView, but it doesn't look too much like the GridViews I have created or seen in the web applications I have worked with.  The main differences are the following

  1. The sample GridView doesn't support Sorting 
  2. The markup for the GridView doesn't use BoundFields, but instead has a single TemplateField that has an embedded table for both the ItemTemplate and EditItemTemplate

The first item are show stoppers for me because the DataSources I work with contain thousands of rows, and a table that large that doesn't support sorting isn't of much use.  The last one is more of an annoyance because embedding html tables is tedious.   

To work around these issues I created a prototype application that uses GridView and HoverMenu, but keeps the support for sorting as well as using the regular BoundFields. 

View Demo | Download

The best part is that retro-fitting an existing GridView is easy because requires only adding a new TemplateField for the hover menu (as opposed to moving all of the existing BoundFields into a nested html table - twice).  Assuming your DataSource supports Delete and Update operations, here are the steps you can follow for adding this support to an existing GridView.

Place the GridView in an UpdatePanel

We need the GridView to rerender when we Delete or Update rows, so the complete GridView needs to be placed in an UpdatePanel with the Mode set to Conditional.

Add a TemplateField with an ItemTemplate and EditItemTemplate

The first step is to add a new TemplateField to the GridView.  In this example, I want to display the hover menu to the right of the GridViewRow so I add the TemplateField after the last of the already existing BoundField's as follows

<asp:GridView ID="gvCustomers" runat="server" />
    <Columns>
        <%--Pre-existing columns--%>
        <asp:BoundField HeaderText="ID" DataField="customerid" SortExpression="customerid" ReadOnly="true" />
        <asp:BoundField HeaderText="Company" DataField="companyname" SortExpression="companyname" />
        <asp:BoundField HeaderText="Name" DataField="contactname" SortExpression="contactname" />
        <asp:BoundField HeaderText="Title" DataField="contacttitle" SortExpression="contacttitle" />                        
        <%--Placeholder for the hover menu--%>
        <asp:TemplateField HeaderStyle-Width="0px">
            <ItemTemplate>
            </ItemTemplate>
            <EditItemTemplate>
            </EditItemTemplate>                                
        </asp:TemplateField>
    </Columns>
</asp:GridView>

Add the Edit and Delete Command Buttons to the ItemTemplate

Next, we can start filling in the markup for the ItemTemplate.  This is the template that is rendered when the GridViewRow is in the 'Normal' state.  When the row is in this state, we want to render a small menu to the right of the row with 2 options: Edit and Delete.  When Edit is clicked, the row should go into 'Edit' mode, when Delete is clicked, the row needs to be deleted.

The markup for the ItemTemplate contains an asp:Panel that contains both the Edit and Delete LinkButtons.  We can avoid writing additional code by setting the CommandName property of the LinkButtons to the well-known Edit and Delete values as shown below.  This way the GridView will automatically take care of interacting with the DataSource for the Delete and will take care of setting the RowState to Edit when the Edit button is clicked.  You will also notice that below the Panel is the definition for the HoverMenuExtender.  Here I am setting the PopupControlID to that of the Panel, the PopupPosition to where I want the menu to render, and the HoverCssClass to the class I want applied to the GridViewRow when the mouse is hovering over it.  Additionally, I set the PopDelay to the time in milliseconds I want the menu to stay visible after the mouse has moved off of the row.    

<ItemTemplate>
    <asp:Panel ID="popupMenu" runat="server" style="display:none">
        <div style="border:1px outset white;padding:2px;">
            <div><asp:LinkButton ID="lnkButtonEdit" runat="server" CommandName="Edit" Text="Edit" /></div>
            <div><asp:LinkButton ID="lnkButtonDelete" runat="server" CommandName="Delete" Text="Delete" /></div>
        </div>
    </asp:Panel>    
    <ajaxToolkit:HoverMenuExtender ID="hoverMenu" runat="server"  
        PopupControlID="popupMenu"
        PopupPosition="Right"
        HoverCssClass="popupHover"
        TargetControlID="popupMenu"
        PopDelay="50" />
</ItemTemplate>

Add the Update and Cancel Command Buttons to the EditItemTemplate

The exact same technique is used for the EditItemTemplate, except the LinkButtons are for Update and Cancel.

<EditItemTemplate>
    <asp:Panel ID="popupMenu" runat="server">
        <div style="border:1px outset white;padding:2px;">
            <div><asp:LinkButton ID="lnkButtonUpdate" runat="server" CommandName="Update" Text="Update" /></div>
            <div><asp:LinkButton ID="lnkButtonCancel" runat="server" CommandName="Cancel" Text="Cancel" /></div>
        </div>
    </asp:Panel>    
    <ajaxToolkit:HoverMenuExtender ID="hoverMenu" runat="server"  
        PopupControlID="popupMenu"
        PopupPosition="Right"
        HoverCssClass="popupHover"
        TargetControlID="popupMenu"
        PopDelay="50" />
</EditItemTemplate>  

Handle the GridView's RowCreated Event

Finally, the trick.  The AjaxControlToolkit sample page for the HoverMenuExtender places the nested html tables in an asp:Panel.  Then when defining the HoverMenuExtender, the ID of this Panel is used as the TargetControlID.  This way when ever the mouse is over the panel the menu is displayed.  Because this sample is using the regular BoundFields, there is no Panel to attach to.  We just have a table for the GridView its corresponding tr and td elements.  To work around this I have attached to the GridView's RowCreated event and programmatically set  each rows HoverMenu's TargetControlID to that of the tr element for the GridViewRow like so.  Now when the user hover overs a  row in the table, the hover menu is displayed. 

    protected void GvCustomers_RowCreated(object sender, GridViewRowEventArgs e)
    {
        if (e.Row.RowType == DataControlRowType.DataRow)
        {
            HoverMenuExtender hoveMenu = (HoverMenuExtender)e.Row.FindControl("hoverMenu");
            e.Row.ID = e.Row.RowIndex.ToString();
            hoveMenu.TargetControlID = e.Row.ID;            
        }
    }

Thats it.  Enjoy!


TrackBack

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

Comments


Posted by: Paul Schranz on August 15, 2007 12:00 AM

Hi Matt:

Thanks for this useful post. Sorting was a feature we were missing in our GridView tables since we developed our pages based on the HoverMenuExtender sample application. I will definitely give your approach a try.

We were having another problem with the HoverMenuExtender. On some computers using IE, it appears that the hover menu is not that stable. When a user hovers over a row and then elects to proceed to the hover menu, it sometimes disappears as they advance upon the hover menu. In some situations there was no way that any of the hover menu links could be activated due to the disappearance of the hover menu.

Have you ever noticed this problem?

Thanks,
Paul

Posted by: Stephen Long on August 16, 2007 12:00 AM

Thanks for the sample. I took this and incorporated it in addition to the Modal pop-up confirmation when Delete on the Hover is pressed. It works quite well.

One problem that I have noticed, though is that formatting when the GridView extends beyond the edge of the browser visible area is an issue. Any idea how to handle this?

Posted by: Susan Harrison on September 13, 2007 12:00 AM

Thanks. I had attached the popup to a control within the grid, but your way is nicer if you want the whole row to trigger the popup.

Have you tried this with 200+ rows of data? Its apparently generating controls for every row, taking 10-15 seconds just to load the page. (My page winds up having tons of blank space at the end, proportional to the number of rows in the grid.) This is discussed on the ASP.NET forum as a limitation. Can you think of a way around it?

Posted by: Martin Feuersteiner on October 1, 2007 12:00 AM

Brilliant! You made my day :-) Thanks Matt

Matt this is perfect. Way better than the lame example on the Microsoft site!

Thanks,
Chris

Posted by: alborz on October 9, 2007 12:00 AM

Hi mat.
thank you for your blog.
i have a problem.
if i have a button in my grid then how can i have a wating gif on it.
the UpdatePanelAnimationExtender dont recognize the button in the grid .
i have a template column in my grid.

@alborz -

If you send me a sample I will take a look at it.

Thanks,
Matt

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

Hi Matt,

This is very interesting. Thanks for this posting.
Greate Work!!!

Sunil

How about using ONE panel, defined outside the gridview, and attaching the panel to each row. Only issue is that when the panel pops-up, you have to get a chance to apply some context.

Any suggestions on how to do that?

Hi,
Please will you demostrate this example more clearly using a database table with vb.net? I love to use this this control.
thanks
Diko

That example comes in one example of the documentation of the AJAX toolkit but they dont explained °°°, I have been seeking for it until this moment when you explained crearly ... Thanks°

Posted by: Kem on December 21, 2007 12:00 AM

In the code below for the line "hoveMenu.TargetControlID = e.Row.ID;" an exception is thrown, saying that "NullReferanceException was unhandled by user code: Object reference not set to an instance of an object."

protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e)

{

HoverMenuExtender hoveMenu = (HoverMenuExtender)e.Row.FindControl("hoverMenu");

e.Row.ID = e.Row.RowIndex.ToString();

hoveMenu.TargetControlID = e.Row.ID;

}

Although i have the HoverMenuExtender on my GridView i face such a problem. No matter what i do, i couldnt handle this problem. Could someone pls help me?

Posted by: Bart Coppens on December 28, 2007 12:00 AM

Thanx Matt, thats much better than a panel with a table on each row...

For Kem:
Add the next line (as Matt described), that will help a lot!!
if (e.Row.RowType == DataControlRowType.DataRow)
{
}

Posted by: Felix on January 2, 2008 12:00 AM

Hello!,

Im trying to use set dynamic items inside the popupMenu but I cant get any type of postback from the dynamic created links inside the panel.

The links are created with a repeater and when I change the content of the grid the new set of links for the menu are loaded I cant do any postbacks.

I holpe you can help me with this one.

protected void grillaView_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
object clavePrimaria = e.Row.FindControl("HiddenFieldClavePrimaria");
string clavePrimariaStr = string.Empty;

if (clavePrimaria != null AND clavePrimaria is HiddenField)
{
foreach (string clave in CamposLlavePrimaria)
{

clavePrimariaStr += ((DataRowView)e.Row.DataItem)[clave] + Separador;
}
clavePrimariaStr = clavePrimariaStr.TrimEnd(Separador.ToCharArray());
((HiddenField)clavePrimaria).Value = clavePrimariaStr;
}

Panel popupMenu = (Panel)e.Row.FindControl("popupMenu");

if (popupMenu != null)
{
ScriptManager sm = ScriptManager.GetCurrent(this.Page);

Repeater rep = (Repeater)e.Row.FindControl("RepeaterLinks");

//sm.RegisterAsyncPostBackControl(rep);
rep.DataSource = MenuContextualColeccion;
rep.DataBind();
foreach (RepeaterItem item in rep.Items)
{
LinkButton link = (LinkButton)item.FindControl("LinkButtonPopUpMenu");
if (link != null)
{
link.CommandArgument = clavePrimariaStr;
}
}
}
}
}
protected void grillaView_RowCreated(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.DataRow)
{
HoverMenuExtender hoveMenu = (HoverMenuExtender)e.Row.FindControl("hoverMenu");
if (hoveMenu != null)
{
e.Row.ID = e.Row.RowIndex.ToString();

hoveMenu.TargetControlID = e.Row.ID;
}
}
}
No existen datos.


CommandArgument="">

PopupPosition="Left" HoverCssClass="popupHover" TargetControlID="popupMenu" PopDelay="50" />


Hope you help me!

Posted by: Willie on February 26, 2008 12:00 AM

Good article.

I was able to resolve one readers issue of slowness when the gridview has 200+ rows. Simply enable AllowPaging="True" PageSize="20".

I do have a question of my own though. I have some code inside of the GridViews SelectedIndexChanging event. Logic that determines if the user is allowed to "select" a given row or not. If not, then updates the text in a user feedback label. The SelectedIndexChanging event fires as expected, however the page does NOT render with the updated text to the label control, when using the Hover Menu code supplied in this example.

Any thoughts?

Posted by: Willie on March 5, 2008 12:00 AM

Found the problem with my user feedback label not rendering...It was outside the Ajax Update Panel!

Posted by: Willie on March 6, 2008 12:00 AM

What could I use for a TargertControlID if I do not want the HoverMenu popping up for the entire row, but say only when I hover over one specific column in the gridview?

I tried: hoveMenu.TargetControlID = e.Row.Cells(1).ID

but e.Row.Cells(1).ID always came back as nothing for me.

Posted by: shylaja on March 17, 2008 12:00 AM

hai ur article was really helpful and solved my problem.keep it up...

Posted by: Anders Persson on May 3, 2008 12:00 AM

Matt, thanks for a very good and clear article on the GridView - Hover combo. I have been trying this myself with some success but you added the missing pieces for me. Very valuable and thanks for your mindful site !!

Posted by: Anders Persson on May 3, 2008 12:00 AM

Matt, thanks for a very good and clear article on the GridView - Hover combo. I have been trying this myself with some success but you added the missing pieces for me. Very valuable and thanks for your mindful site !!

Posted by: courtney on May 29, 2008 12:00 AM

are there any known issues with this and safari? i got this to work and its great -- but im getting feedback that in safari 3.1.1. its just hanging on the hover.

Posted by: kanishka on May 31, 2008 12:00 AM

sir, i wanted to ask a question if u can resolve that,

in the grid view,where we have links like in the heading as sort expression, when we hover mouse over it in the status bar it shows its java script id, so can u tell me how to remove it.

thanks

kanishka

Posted by: miket on June 11, 2008 12:00 AM

Matt
Great job! You saved me tons of work. This looks fantastic!
Thanks!

Mike

Posted by: Steve on June 24, 2008 12:00 AM

Hello Matt.

Great article, like the info!

Just so you know, I believe you are hosting in a clustered environment, because I have had your page opened for a while (not viewing it) and when I go to a different page, I get a MAC address mismatch error, something about a clustered environment, and something about a control cant be used in one of those.
Not sure if this really helps, but I thought I would let you know.

Posted by: KM on June 27, 2008 12:00 AM

Thank you very much for the great article. It is really handy. I am very very new to ASP and Ajax. I have been trying to get my gv to work but having some issues.
How do I add an Add Button?
I tried by copying the Update Data Object Method and modifying it but it would not fire. I added a button on the gridview game it commandname=insert and added the method to the dataobject but it still would not fire.
Also is it possible to modify the code to say delete records where the Row ID is not equal to 1?
I am sorry to bother you with these questions. Thanks again.
P/S: The periods are single quotes and the brackets are xml tags


... (summary)
...
... (/summary)
... (returns)(/returns)
_
Public Function Insert(ByVal originalRecordID As String, ByVal Company As String, ByVal ISA_Qual As String, ByVal ISA_ID As String, ByVal GS_ID As String, ByVal chrISA_Qual As String, _
ByVal chrISA_ID As String, ByVal chrGS_ID As String) As Integer
Dim rows As DataRow() = Me.RecordTable.[Select]([String].Format("RecordID={0}", originalRecordID))

Posted by: KM on June 27, 2008 12:00 AM

Thank you very much for the great article. It is really handy. I am very very new to ASP and Ajax. I have been trying to get my gv to work but having some issues.
How do I add an Add Button?
I tried by copying the Update Data Object Method and modifying it but it would not fire. I added a button on the gridview game it commandname=insert and added the method to the dataobject but it still would not fire.
Also is it possible to modify the code to say delete records where the Row ID is not equal to 1?
I am sorry to bother you with these questions. Thanks again.
P/S: The periods are single quotes and the brackets are xml tags


... (summary)
...
... (/summary)
... (returns)(/returns)
_
Public Function Insert(ByVal originalRecordID As String, ByVal Company As String, ByVal ISA_Qual As String, ByVal ISA_ID As String, ByVal GS_ID As String, ByVal chrISA_Qual As String, _
ByVal chrISA_ID As String, ByVal chrGS_ID As String) As Integer
Dim rows As DataRow() = Me.RecordTable.[Select]([String].Format("RecordID={0}", originalRecordID))

Hi Matt! I use HoverMenuExtender in GridView rows. I need to be able to enable/disable a grid together with pop-ups displayed by HoverMenuExtender. How would you do that?

Posted by: Tim Moman on August 31, 2008 02:58 PM

Hello Matt
I couldn't figure out my issue. I used your suggestion. The Popup AJAX HoverMenuExtender won't showed up. Could you please take a look it and tell me where is the problem? Thanks





Project Assignment




Width="110px">
DataTextField="Project Name" DataValueField="Project ID" Width="150px" OnSelectedIndexChanged="Page_Load" AutoPostBack="true">



DataTextField="EmployeeName" DataValueField="EmployeeID" Width="150px">





CssClass="list" AlternatingRowStyle-CssClass="odd" PageSize="10" DataSourceID="SqlDataSource4" Width="100%">













PopupControlID="PopupMenu"
PopupPosition="Right"
HoverCssClass="popupHover"
TargetControlID="PopupMenu"
PopDelay="50" />








PopupControlID="PopupMenu"
PopupPosition="Right"
HoverCssClass="popupHover"
TargetControlID="PopupMenu"
PopDelay="50" />











TargetControlID="TextBoxRole"
WatermarkText="Type Member Project Role"
WatermarkCssClass="watermarked" />
Add

runat="server"
Display="None"
ControlToValidate="TextBoxRole"
ErrorMessage="Project Role is required!">

runat="server"
TargetControlID="RequiredFieldValidatorProjectRole">

"
SelectCommand="GetProjectList" SelectCommandType="StoredProcedure">

Type="Int32" />



"
SelectCommand="GetProjectChildrenList" SelectCommandType="StoredProcedure">

Type="Int32" />



"
SelectCommand="select EmployeeID, EmployeeName, LoginID
from fnGetHumanResource()">

"
SelectCommand="GetSpecProjMember" SelectCommandType="StoredProcedure" DeleteCommand="RemoveEmployeeProject" DeleteCommandType="StoredProcedure" OnDeleting="SqlDataSourceProjectMember_Deleting" UpdateCommand="UpdateEmployeeProject" UpdateCommandType="StoredProcedure" OnUpdating="SqlDataSourceProjectMember_Updating">

PropertyName="SelectedValue" Type="Int32" />


















 



Posted by: Derek on October 22, 2008 01:46 PM

Felix,
Did you ever get a solution to your problem? I'm having the exact same issue. I have several panels (representing different groupings of menu items) - during the OnRowDataBound call, I not only assign the TargetControlId to the row in question, but I also assign the PopupControlID based on the type of content being displayed. The result is a context sensitive menu per row. The only problem is I get no events fired when I select an item from the menu - Felix, did you or anyone else for that matter, have/find a solution to this?

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

  • Derek wrote: Felix, Did you ever get a solution to your problem? I'm having the exact same issue. I have severa...
  • Tim Moman wrote: Hello Matt I couldn't figure out my issue. I used your suggestion. The Popup AJAX HoverMenuExtender ...
  • Vladimir Kelman wrote: Hi Matt! I use HoverMenuExtender in GridView rows. I need to be able to enable/disable a grid togeth...
  • KM wrote: Thank you very much for the great article. It is really handy. I am very very new to ASP and Ajax. I...
  • KM wrote: Thank you very much for the great article. It is really handy. I am very very new to ASP and Ajax. I...
  • Steve wrote: Hello Matt. Great article, like the info! Just so you know, I believe you are hosting in a cluster...
  • miket wrote: Matt Great job! You saved me tons of work. This looks fantastic! Thanks! Mike ...
  • kanishka wrote: sir, i wanted to ask a question if u can resolve that, in the grid view,where we have links like in...