A Context Menu Control Extender

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 3.7 by 3 people

  • Currently 3.666667/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Comments

Powered by BlogEngine.NET 1.4.5.0

Welcome!

My name is Fredrik Kalseth, and this is my blog - thanks for visiting! I am fortunate enough to work with what I love for a living, and this blog is essentially the biproduct of that.

I work as a senior consultant for Capgemini, and am also an active participant in the Norwegian .NET community, as an avid attendee but also as a speaker (most recently at NNUG and MSDN Live).

As a developer, I have a wide circle of interest. My primary passion is for agile, test-driven development, with focus on best practices and clean code. That said, I also love to work on the frontend, especially with web development.

On Twitter? My handle is fkalseth. On LinkedIn? I`m there too.


Disclaimer

This is a personal blog; any opinions expressed here are my own and do not necessarily reflect those of my employer. All content herein is my own original creation, and as such is protected by copyright law. Unless otherwise stated, all source code posted on this blog is freely usable under the Microsoft Permissive License.

What Readers Talk About

Comment RSS