A Context Menu Control Extender
Author: Fredrik Kalseth
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.
Comments
This post is also available in: English
8/7/2007 12:12:05 PM
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
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
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)
Fredrik
10/18/2007 12:56:58 PM
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
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
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
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
Good catch Sreekanth!
I’ll update the post with the fixes asap
Fredrik
11/9/2007 11:58:39 PM
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
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
Oops I guess the bug was already caught above. Next time I’ll read.
Michael Carr
12/2/2007 5:37:33 AM
Thanks
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
Is there a demo project?
IT Hero
12/6/2007 6:18:26 AM
I will be posting an update with some new features and a demo project soon
Fredrik
12/7/2007 5:05:15 AM
thank you for your code
aswood
12/16/2007 4:56:25 AM
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
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
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
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
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
11/10/2008 5:19:33 PM
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
3/9/2009 3:20:47 PM
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
7/10/2009 3:40:08 AM
Thanks for this great post – I will be sure to check out your blog more often
Maria
7/10/2009 5:01:57 AM
Nice blog, just bookmarked it for later reference
Maria