Check out the context menus:


(right-click on a link)

If this does not work, see the section Browser compatibility below.

Introduction


There are plenty of context menu examples out there that simply add a context menu for the whole page.  While this can useful, I find it much more useful to have item-specific context menus.  For example, one might only want to target hyperlinks or list items.  This example only targets hyperlinks, but it is extremely easy to change.  In addition, one can disable the context menu quite easily, so that power-users can regain the extra functionality their browser provides.

JavaScript Events


The relevant events are the body's onMouseDown and onContextMenu.  The first is required in order to hide our custom context menu at the proper time.  The latter is required for telling the browser not to show its own context menu.  While the events could go in the <body> tag, I have decided to use the preferred method of wiring them up in an initialization function: InitContext().  It is important to return false; in the function that handles the onContextMenu event if you want to suppress the browser's context menu.

Two important events are attached to the context menu: onMouseOver and onMouseOut.  We use these to keep track of whether the mouse is over the context menu.  This lets us take action if the context menu has been clicked, and to either show it somewhere else or make it disappear when something else is clicked.

The Context Menu


The context menu is simply a div element with display set to none and position set to absolute.  I added a border, but that wasn't necessary.  If the the position attribute is not set, the menu will shift other elements around when it appears; definitely not what you want.

<div id="divContext" style="border: 1px solid blue; display: none; position: absolute"> <ul class="cmenu"> <li><a id="aContextNav" href="#">Navigate to</a></li> <li><a id="aAddWebmark" href="#">Add to WebMark</a></li> <li class="topSep"> <a id="aDisable" href="#">disable this menu</a> </li> </ul> </div>

Note the aDisable hyperlink above with text "disable this menu"; this exists to allow the user to regain normal right-click functionality.  The corresponding aEnable hyperlink is located near the top of the document, but can be located anywhere.  It re-enables the custom right-click functionality:

<p><a id="aEnable" style="display:none" href="#">Enable context menus</a></p>

Global Variables


I use three global variables and one global variable that's just an easier way to refer to the context menu div:

var _replaceContext = false; // replace the system context menu? var _mouseOverContext = false; // is the mouse over the context menu? var _noContext = false; // disable the context menu? var _divContext = $('divContext'); // makes my life easier

Functions


The functions should be self-explanatory -- if you disagree, tell me what's unclear.  Two hacks were necessary in order to target standards-compliant browsers and remain compatible with IE:

  1. The standard is document.body.scrollTop, but IE uses document.documentElement.scrollTop.
  2. Events are supposed to pass the event object as the first argument to the function handling the event, but with IE, one must use window.event.
function InitContext() { _divContext.onmouseover = function() { _mouseOverContext = true; }; _divContext.onmouseout = function() { _mouseOverContext = false; }; $('aDisable').onclick = DisableContext; $('aEnable').onclick = EnableContext; document.body.onmousedown = ContextMouseDown; document.body.oncontextmenu = ContextShow; } // call from the onMouseDown event, passing the event if standards compliant function ContextMouseDown(event) { if (_noContext || _mouseOverContext) return; // IE is evil and doesn't pass the event object if (event == null) event = window.event; // we assume we have a standards compliant browser, but check if we have IE var target = event.target != null ? event.target : event.srcElement; // only show the context menu if the right mouse button is pressed // and a hyperlink has been clicked (the code can be made more selective) if (event.button == 2 && target.tagName.toLowerCase() == 'a') _replaceContext = true; else if (!_mouseOverContext) _divContext.style.display = 'none'; } function CloseContext() { _mouseOverContext = false; _divContext.style.display = 'none'; } // call from the onContextMenu event, passing the event // if this function returns false, the browser's context menu will not show up function ContextShow(event) { if (_noContext || _mouseOverContext) return; // IE is evil and doesn't pass the event object if (event == null) event = window.event; // we assume we have a standards compliant browser, but check if we have IE var target = event.target != null ? event.target : event.srcElement; if (_replaceContext) { $('aContextNav').href = target.href; $('aAddWebmark').href = 'http://luke.breuer.com/webmark/?addurl=' + encodeURIComponent(target.href) + '&title=' + encodeURIComponent(target.innerHTML); // document.body.scrollTop does not work in IE var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop; var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft; // hide the menu first to avoid an "up-then-over" visual effect _divContext.style.display = 'none'; _divContext.style.left = event.clientX + scrollLeft + 'px'; _divContext.style.top = event.clientY + scrollTop + 'px'; _divContext.style.display = 'block'; _replaceContext = false; return false; } } function DisableContext() { _noContext = true; CloseContext(); $('aEnable').style.display = ''; return false; } function EnableContext() { _noContext = false; _mouseOverContext = false; // this gets left enabled when "disable menus" is chosen $('aEnable').style.display = 'none'; return false; } // comes from prototype.js; this is simply easier on the eyes and fingers function $(id) { return document.getElementById(id); }

CSS


The CSS below is not required for anything to work, but it does give the context menu in this page the look and feel that it has.

.cmenu {margin: 0; padding: 0.3em; list-style-type: none; background-color: white;} .cmenu li:hover {} .cmenu hr {border: 0; border-bottom: 1px solid grey; margin: 3px 0px 3px 0px; width: 10em;} .cmenu a {border: 0 !important;} .cmenu a:hover {text-decoration: underline !important;} .cmenu .topSep {font-size: 90%; border-top: 1px solid gray; margin-top: 0.3em; padding-top: 0.3em;}

Some Things to Consider


Overriding the default right-click functionality browsers provide is a very questionable business.  Deviating from standard interface guidelines means that it will be harder for users to use the functionality you add by breaking standard interface rules.  I have two responses to this: 1) it is easy to make this standard by turning it into a left-click-on-an-image; 2) sometimes the default isn't the best.  Take this as you will; I provide it so that if people want to implement this, they can implement it in a fairly clean manner.

Note that unless your context menu actions either change the page or explicitly hide the context menu, you will have to do so manually; use CloseContext() to do so.

Using Left or Middle Click


There is no reason to wire the body's onmousedown event handler if you want to use left or middle click, as the browser's context menu only pops up on right click.  While the code above may work with left/middle click, one could also simply wire up onclick for processing left clicks; this is actually required for hyperlinks, as otherwise they will work like hyperlinks normally do.  Because browser makers love incompatibility, the codes for event.button vary from browser to browser; see the below table for values and feel free to provide me with additional rows for other browsers.

BrowserLeft ClickMiddle ClickRight Click
Firefox012
Internet Explorer142

Note that IE uses the values 1, 2, and 4 so that one can determine every mouse button that is pressed; Firefox does not allow this.

Browser compatibility


This context menu will not work in browsers that disallow overriding of the context menu by default.  It appears that some versions of Safari and Opera might do this -- I suspect there is a setting that allows the user to change this behavior.  IE, Firefox, and Google Chrome appear to allow such overriding by default.  As noted above, it is easy to change from right-click to left-click, ctrl-left-click, or something like that.

In Firefox, the relevant setting is here: Tools->Options->Content->Advanced->Disable or replace context menus. (Use the first advanced button.)

Other Context Menus


Support Tutorials like This


If you found this tutorial helpful, please link to it.  If you really want to show your appreciation... :-)