A Context Menu Control Extender

A Context Menu Control Extender

A Context Menu Control Extender

A Context Menu Control Extender

Author: Fredrik Kalseth An entry about asp.net | ajax | javascript Publication date 4. August 2007 13:19
One of the things I’ve been missing in the Ajax Control Toolkit, is a context menu extender – so I figured I’d write one myself 🙂 Turns out it was much easier than I anticipated, and after about an hour or so I had something that works fairly well. Basically, it allows you to do something like this:
<asp:Panel
    ID="_context"
    runat="server"
    Style="background-color:#cecece; width:250px; height:250px">            
Context area            
</asp:Panel>
<asp:Panel
    ID="_menu"
    runat="server" 
Style="border:solid 1px black; background-color:White; padding:4px;">
    My Context menu!
</asp:Panel>
<iridescence:ContextMenuExtender
    ID="_cmExt"
    runat="server"
    TargetControlID="_context"
    ContextMenuControlID="_menu" /> 
The extender will then make the TargetControl float wherever the mouse is located when you right-click anywhere inside the configured ContextMenuControl. You can get the source code at the bottom of this post – below I’ll just go through the javascript code that makes it work. As I said before when talking about control extenders, I will assume that you know how to write them – if you don’t, check out this tutorial for a better introduction than one I could include here 🙂 To hook things up, we override the initialize function:
initialize : function() 
{
Iridescence.Ajax.ContextMenuBehavior.callBaseMethod(this, 'initialize');
this._contextElement = this.get_element();
this._menuElement = $get(this._contextMenuControlID);
// style the context menu
    this._menuElement.style.display = 'none';
this._menuElement.style.position = 'absolute';
// attach event handlers
    this._onMouseDownHandler = Function.createDelegate(this, this._onMouseDown);
this._onDocumentContextMenuHandler = Function.createDelegate(this, this._onDocumentContextMenu);
this._onDocumentClickHandler = Function.createDelegate(this, this._onDocumentClick);
$addHandler(this._contextElement, 'mousedown', this._onMouseDownHandler);  
$addHandler(document, 'contextmenu', this._onDocumentContextMenuHandler);   
$addHandler(document, 'click', this._onDocumentClickHandler);
}
Here we simply get references to the Target and ContextMenuControls, and then add handlers for the events that we need. First of all, we need to capture the mousedown event of the context area, so that we may show the context menu when the user right clicks inside it. This is handled by the _onMouseDown method:
_onMouseDown : function(e)    
{                
if (e.button == 2)
{                   
// calculate current mouse position            
        var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; 
var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft; 
// and move context menu there
        this.__menuElement.style.left = e.clientX + scrollLeft + 'px';
this.__menuElement.style.top = e.clientY + scrollTop + 'px';
this.__menuElement.style.display = '';
// set flags
        this._menuVisible = true;
this._menuJustShown = true;            
}
}
This method checks whether the right mouse button was clicked, and if so we need to figure out the current mouse position (offset by the scroll position), and position the context menu element accordingly, before showing it. Normally, a right click would cause the browsers context menu to be displayed; we dont want that, as it would hide our custom context menu. Thus we’ve also hooked up to the contextmenu event of the document element, which is handled by the _onDocumentContextMenu method:
_onDocumentContextMenu : function(e)
{        
if(this._menuJustShown)
{
// when our custom context menu is showing, we want to disable the browser context menu
        e.preventDefault();
this._menuJustShown = false;
}
else if(this._menuVisible)
{        
// user right-clicks anywhere while our custom context menu is visible; hide it
        this._hideMenu();
}
}
Here, if our custom context menu was just shown, we prevent the browsers context menu from displaying by calling the preventDefault() method on the event arguments object. The last functionality we need, is to be able to hide the context menu when the user clicks anwhere outside it after it has been shown. Above, we’ve solved that for when the user right-clicks – we also need to include a left-click solution. We do this by handling the click event of the document element, which is handled by the _onDocumentClick method:
_onDocumentClick : function(e)
{                                   
if(this._menuVisible && e.button != 2)
{
// user left-clicked anywhere while custom context menu is visible; hide it
        this._hideMenu();
}
}
The _hideMenu() function that both these calls is very simple – it just hides the context menu element and sets the _menuVisible flag accordingly:
_hideMenu : function()
{    
this._menuElement.style.display = 'none';
this._menuVisible = false;
}
And thats it! The only thing left is to clean up after us, which we do by overriding the dispose method:
dispose : function()     
{
// clean up
    $removeHandler(this._contextElement, 'mousedown', this._onMouseDownHandler);  
$removeHandler(document, 'contextmenu', this._onDocumentContextMenuHandler);   
$removeHandler(document, 'click', this._onDocumentClickHandler);
Iridescence.Ajax.ContextMenuBehavior.callBaseMethod(this, 'dispose');
}
Now this is a fairly simplistic context menu extender – one might want to add asynchronous loading and maybe animation support at some point – but still, it’s quite impressive how simple the ASP.NET Ajax API makes it to write powerful, reusable components with very little code. Gotta love it 🙂 Download the complete source code here. (Updated 1. December 2007 – fixed a few bugs in the script). Update 7. December 2007 –  Be sure to check out this post, which shows how to add animation support to the ContextMenuExtender.
Currently rated 5.0 by 2 people
  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

8/7/2007 12:12:05 PM

mc

well, for me it worked like a charm! thanks! 😉 maybe you ought to put a small demo projec for the sake of completness. cheers! 😉

mc

8/20/2007 12:48:07 AM

Jimmy

I got this error Assembly ‘ContextMenu, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null’ contains a Web resource with name ‘Iridescence.Ajax.ContextMenuBehavior.js’, but does not contain an embedded resource with name ‘Iridescence.Ajax.ContextMenuBehavior.js’.

Jimmy

8/21/2007 8:18:38 AM

Fredrik

Jimmy, you need to set the Build Action on the .js file to ‘Embedded Resource’ in the project (right click and select Properties on the .js file) Smile

Fredrik

10/18/2007 12:56:58 PM

dzhang

I got the same error as Jimmy did and I did set the Build Action on the .js file to ‘Embedded Resource’ in the project, any suggestions?

dzhang

10/19/2007 9:37:36 AM

Fredrik

Hmm, the only other thing I can think of is that you’ve put the code into a different namespace? If so, you need to also change the assembly attribute that registers the embedded resource accordingly.

Fredrik

10/31/2007 10:52:45 AM

Sreekanth

I used this ContextMenuExtender inside GridView in one of my columns. I have a panel that contains two link buttons inside it in the same column of my grid. Now, when i right click, it is properly showing the context menu, but it is always showing the panel of LAST ROW in my grid. I am not sure what is causing this beahvior. I even tried to add code to my rowdatabound method that will properly set the ContextMenuControlID to the panel object contained in corresponding Row of the grid. But still it does not work. If any one thinks that they can help me, please write me a line to my email address given below and i will reply with complete source code. sreekanthgolla_2000@yahoo.com Thank you, Sreekanth.

Sreekanth

11/2/2007 9:49:51 AM

Sreekanth

I fixed the problem i am facing of trying to have more than one ContextMenuExtender in the same aspx page. In my case i have contextmenuextender inside gridview (in one of my columns) Though i cannot upload the fixed source code here, the solution for the problem is quite simple: Do not forget to prefix every member variable inside ***Behavior.js file with this. keyword. If you need the actual fixed ContextMenuExtender.js file, drop me a line of email and i will reply to you. -Sreekanth.

Sreekanth

11/2/2007 10:44:55 AM

Fredrik

Good catch Sreekanth! I’ll update the post with the fixes asap Smile

Fredrik

11/9/2007 11:58:39 PM

anikin

Sreekanth, I want your fixed ContextMenuExtender.js file ,thanks my mail address is anikin2008@hotmail.com thanks

anikin

12/1/2007 8:14:18 PM

Michael Carr

Thanks for the code, it was exactly what I was looking for. However, I think I found a small bug. In your code, you reference “_contextElement” and “_menuElement”. I think you really mean to reference “this._contextElement” and “this._menuElement”. (You reference it correctly in your initialization code, but the rest of the code doesn’t) The problem doesn’t show up until you try to use several context menus on one page and then funny things start happening. Thanks again for a great contribution! I don’t know why Microsoft didn’t include an extender like this in the toolbox already…

Michael Carr

12/1/2007 8:25:02 PM

Michael Carr

Oops I guess the bug was already caught above. Next time I’ll read.

Michael Carr

12/2/2007 5:37:33 AM

Fredrik

Thanks Smile I had actually fixed that bug, but apparently forgotten to update the post&source code download.. should be fixed now!

Fredrik

12/4/2007 1:46:24 PM

IT Hero

Is there a demo project?

IT Hero

12/6/2007 6:18:26 AM

Fredrik

I will be posting an update with some new features and a demo project soon Smile

Fredrik

12/7/2007 5:05:15 AM

aswood

thank you for your code

aswood

12/16/2007 4:56:25 AM

Martin

Hi there, I have testet your extender and like it ;o) But is it possible in anyway to add the menu to all the links in a treeview that is populated from a database and then add a ?id=1 to the links in the context menu where the the id is the id beloning to the records from the database ?

Martin

12/18/2007 9:35:43 AM

Thomas

Hi! Wonderful work, thanks a lot. I cannot manage this menu to be visible also when I click the left mouse button. How can this be done? I have tried to add e.button == 0 in else clause of method _onMouseDown. Thanks, Thomas

Thomas

12/20/2007 4:52:21 PM

Fredrik

Martin – the way the extender is implemented, this won’t be easily possible, sorry. It’s an interesting problem though, I might look into it later – but I can make no promises Smile Thomas – Have you checked out the PopupControlExtender in the Ajax control toolkit? It can be used to show a popup on a left mouse-button click.

Fredrik

9/29/2008 1:22:04 PM

DuongNguyen

I work well but when i try using two different context menu in a page, but both of them show only the content of the second context menu, how can i fix this bug. Please help me.

DuongNguyen United States

11/10/2008 5:19:33 PM

Jacob Cordingley

Works Great I’m using it for a filter in a gridview I was able to add a cssContextmenu to style the menu and set focus where if the menu is a textbox it will focus to it

Jacob Cordingley United States

3/9/2009 3:20:47 PM

Ronald Langi

Hi, This is great. I need something like this for a gridview. When I click a menu item after right clicking on a row of a gridview to bring up the context menu, how can I pass the key value of that row to the command of the context menu item in java script? I don’t want the page to postback. Can you help me with this? Thanks, Ronald

Ronald Langi Republic of the Philippines

7/10/2009 3:40:08 AM

Maria

Thanks for this great post – I will be sure to check out your blog more often

Maria Republic of the Philippines

7/10/2009 5:01:57 AM

Maria

Nice blog, just bookmarked it for later reference

Maria United Kingdom

This post is also available in: Norsk bokmål