MooTools ContextMenu Plugin

Written by David Walsh on Wednesday, January 28, 2009


This article may feature code that is no longer best practice in MooTools.
Click here to learn what has changed to make your code framework-compatible.
dwContext: MooTools Context Menu

ContextMenu is a highly customizable, compact context menu script written with CSS, XHTML, and the MooTools javascript framework. ContextMenu allows you to offer stylish, functional context menus on your website.

The XHTML Menu

<ul id="contextmenu">
	<li><a href="#edit" class="edit">Edit</a></li>
	<li class="separator"><a href="#cut" class="cut">Cut</a></li>
	<li><a href="#copy" class="copy">Copy</a></li>
	<li><a href="#paste" class="paste">Paste</a></li>
	<li><a href="#delete" class="delete">Delete</a></li>
	<li class="separator"><a href="#quit" class="quit">Quit</a></li>
</ul>

Use a list of menu items with one link per item. The href attribute is especially important as it must be named the same as the menu’s action, which you’ll see below.

The Sample CSS

/* context menu specific */
#contextmenu	{ border:1px solid #999; padding:0; background:#eee; width:200px; list-style-type:none; display:none; }
#contextmenu .separator	{ border-top:1px solid #999; }
#contextmenu li	{ margin:0; padding:0; }
#contextmenu li a { display:block; padding:5px 10px 5px 35px; width:155px; font-size:12px; text-decoration:none; font-family:tahoma,arial,sans-serif; color:#000; background-position:8px 8px; background-repeat:no-repeat; }
#contextmenu li a:hover	{ background-color:#ddd; }
#contextmenu li a.disabled { color:#ccc; font-style:italic; }
#contextmenu li a.disabled:hover { background-color:#eee; }

/* context menu items */
#contextmenu li a.edit	{ background-image:url(edit.png); }
#contextmenu li a.cut	{ background-image:url(cut.png); }
#contextmenu li a.copy	{ background-image:url(copy.png); }
#contextmenu li a.paste	{ background-image:url(paste.png); }
#contextmenu li a.delete	{ background-image:url(delete.png); }
#contextmenu li a.quit	{ background-image:url(quit.png); }

Make the CSS look however you’d like. For the purposes of IE6, however, you’ll want to set the link widths. Also note that the menu should be initialized as “display:none”.

The MooTools Javascript

var ContextMenu = new Class({

	//implements
	Implements: [Options,Events],

	//options
	options: {
		actions: {},
		menu: 'contextmenu',
		stopEvent: true,
		targets: 'body',
		trigger: 'contextmenu',
		offsets: { x:0, y:0 },
		onShow: $empty,
		onHide: $empty,
		onClick: $empty,
		fadeSpeed: 200
	},
	
	//initialization
	initialize: function(options) {
		//set options
		this.setOptions(options)
		
		//option diffs menu
		this.menu = $(this.options.menu);
		this.targets = $$(this.options.targets);
		
		//fx
		this.fx = new Fx.Tween(this.menu, { property: 'opacity', duration:this.options.fadeSpeed });
		
		//hide and begin the listener
		this.hide().startListener();
		
		//hide the menu
		this.menu.setStyles({ 'position':'absolute','top':'-900000px', 'display':'block' });
	},
	
	//get things started
	startListener: function() {
		/* all elements */
		this.targets.each(function(el) {
			/* show the menu */
			el.addEvent(this.options.trigger,function(e) {
				//enabled?
				if(!this.options.disabled) {
					//prevent default, if told to
					if(this.options.stopEvent) { e.stop(); }
					//record this as the trigger
					this.options.element = $(el);
					//position the menu
					this.menu.setStyles({
						top: (e.page.y + this.options.offsets.y),
						left: (e.page.x + this.options.offsets.x),
						position: 'absolute',
						'z-index': '2000'
					});
					//show the menu
					this.show();
				}
			}.bind(this));
		},this);
		
		/* menu items */
		this.menu.getElements('a').each(function(item) {
			item.addEvent('click',function(e) {
				if(!item.hasClass('disabled')) {
					this.execute(item.get('href').split('#')[1],$(this.options.element));
					this.fireEvent('click',[item,e]);
				}
			}.bind(this));
		},this);
		
		//hide on body click
		$(document.body).addEvent('click', function() {
			this.hide();
		}.bind(this));
	},
	
	//show menu
	show: function(trigger) {
		//this.menu.fade('in');
		this.fx.start(1);
		this.fireEvent('show');
		this.shown = true;
		return this;
	},
	
	//hide the menu
	hide: function(trigger) {
		if(this.shown)
		{
			this.fx.start(0);
			//this.menu.fade('out');
			this.fireEvent('hide');
			this.shown = false;
		}
		return this;
	},
	
	//disable an item
	disableItem: function(item) {
		this.menu.getElements('a[href$=' + item + ']').addClass('disabled');
		return this;
	},
	
	//enable an item
	enableItem: function(item) {
		this.menu.getElements('a[href$=' + item + ']').removeClass('disabled');
		return this;
	},
	
	//diable the entire menu
	disable: function() {
		this.options.disabled = true;
		return this;
	},
	
	//enable the entire menu
	enable: function() {
		this.options.disabled = false;
		return this;
	},
	
	//execute an action
	execute: function(action,element) {
		if(this.options.actions[action]) {
			this.options.actions[action](element,this);
		}
		return this;
	}
	
});

The ContextMenu plugin offers numerous options:

  • actions: a collection of actions (functions) to be executed when a corresponding menu item is clicked
  • menu: the ID of the element that represents the menu XHTML
  • stopEvent: do you want the element’s default action to be stopped when the menu is triggered to display? (defaults to true)
  • targets: element(s) that should show the menu when triggered (defaults to the document body)
  • trigger: event that triggers the menu to display (defaults to “contextmenu”, or right-click)
  • offsets: an {x,y} object with corresponding x and y offsets (x and y both default to 0)
  • onShow: a function to execute when the menu is shown
  • onHide: a function to execute when the menu is hidden
  • onClick: a function to execute when a menu item is clicked

Beyond these initial options, the ContextMenu class also provide some useful methods:

  • disable: disables the context menu
  • enable: enables the context menu
  • disableItem: disables a given menu item
  • enableItem: enables a given menu item
The Sample Usage
window.addEvent('domready', function() {

	//create a context menu
	var context = new ContextMenu({
		targets: 'a', //menu only available on links
		menu: 'contextmenu',
		actions: {
			copy: function(element,ref) { //copy action changes the element's color to green and disables the menu
				element.setStyle('color','#090');
				ref.disable();
			}
		},
		offsets: { x:2, y:2 }
	});
	
	//sample usages of the enable/disable functionality
	$('enable').addEvent('click',function(e) { e.stop(); context.enable(); });
	$('disable').addEvent('click',function(e) { e.stop(); context.disable(); });
	$('enable-copy').addEvent('click',function(e) { e.stop(); context.enableItem('copy'); });
	$('disable-copy').addEvent('click',function(e) { e.stop(); context.disableItem('copy'); });
	
});

The most dynamic part of the ContextMenu instance is the actions option, where you define what action should be taken per menu item. The action is passed the element clicked on and the reference to the context menu. My above example defines the copy action. When you click the “Copy” context menu item, I turn the text color green and disable the context menu. You may define one action per menu item.

This is version 1 of ContextMenu. I’d like to implement a few more features in the future, including:

  • Multi-level menus
  • A core set of actions with corresponding functionality.
  • addItem and removeItem methods

Have suggestions for a version 2? Share them!

ContextMenu is inspired by jQuery Context Menu Plugin.


Follow via RSS Epic Discussion

Commenter Avatar January 28 / #
Umut says:

David,

Great job. I love how context menus improve usability specially in admin UIs.

Commenter Avatar January 28 / #
Rexxars says:

Nice plugin! A little note on which license you’re releasing this under would be nice :-) I will concider using this one on some of my admin panels, if the license is right, hehe :-)

There’s a couple of things I’d personally alter, though most of these are simply code style differences.
I like to keep the indentation to a minimum, and I also like to take advantage of the way mootools handles groups of elements.

For example, instead of doing this.targets.each and then running a item.addEvent on that, you could simply run addEvent on this.targets instead. The only difference here would be that you’d refer to this.target instead of item, or set item to this.target. Also, instead of doing “if(!this.options.disabled) { indented code below }”, you could check it the other way around, and return.. so it becomes “if(this.options.disabled) return;”

All of these things are, as I said, merely code style, so these are just tips if you want to cut down on the indenting and possibly save a few lines of code. I posted the altered code on your pastebin if you are interested.

David Walsh January 28 / #
david says:

@Rexxars: Thank you for the input and I’ll look at the licenses. Hadn’t thought about it. Like everything else on this site, use it however you’d like!

Commenter Avatar January 28 / #
Ryan says:

Wow … definitely using this on a few of my sites.

Commenter Avatar January 28 / #
rborn says:

Hi
This is not working in opera… because of opera of course.
As I remember there is a setting in opera to allow or not right click, but most of the users will have the standard settings.
So i think you can detect opera and add something like ctrl+click to launch the menu.

Commenter Avatar January 28 / #
Ahmed says:

Wow, I’ve been waiting for this for so long!
Unfortunately it doesn’t work on Opera because of how annoying opera is, I am sure you’ll find a solution for this sometime soon, or like rborn suggested! Nonetheless, its a great class!
Thanks :)

David Walsh January 28 / #
david says:

@rborn: The beauty is that you can choose the trigger. You could make it double-click if you need Opera users to be able to use the menu.

Commenter Avatar January 28 / #

Excellent, David! I know a lot of places where something like this could be used.

Commenter Avatar January 28 / #

Is it possible to have the menu close when you left click outside of the region?

David Walsh January 28 / #
david says:

Updated. Thanks Mark!

Commenter Avatar January 28 / #
Thiago says:

It looks fantastic. Looking forward an opportunity to use in one of my projects. Some feedback tho:
– I’m getting errors when running in IE (“Object doesn’t support this property or method” kind of error on lines 144 and 2705);
– Displays fine in FF and Safari. I don’t use Opera in my desktop;
– It didn’t copy the selected text in any browser(Safari and FF) I tested.

keep up the good work :)

David Walsh January 28 / #
david says:

@Thiago: It wasn’t supposed to copy text to your clipboard — it’s supposed to turn text green and disable the menu. Also, I’ve just cured the IE issue.

Commenter Avatar February 25 / #
bioule says:

Hi, really nice work !
I used it in a complex interface. It needs to be initialized several times in it’s lifetime. So, when I call for the second time the constructor, the menu will execute two times the expected function…
So, I just removed the events just before add them
At line 64 of ContextMenu Class, I just added this code:

/* menu items */
this.menu.getElements(‘a’).each(function(item){
item.removeEvents();
});

Thank you again for your class :)

Commenter Avatar March 04 / #
David Hinckle says:

Nice script David. I can’t figure out how to keep the script from appending the url when clicking on a menu item. Is there a way??? Thanks in advance.

David Walsh March 04 / #

@David Hinckle: What do you mean by this? I’m not following.

Commenter Avatar March 04 / #
David Hinckle says:

Sorry, I’m probably being too anal about this to begin with. Using your demo as an example, when one clicks on the edit link, “#edit” is appended to the end of the url. I’d like to do away with this. Thanks again. DH

Commenter Avatar March 13 / #
scott says:

Hey David…I’m having the same problem as @David Hinckle. My script keeps appending “#edit” or whatever the “click” event is to the end of my URL string. Normally this wouldn’t be a problem, but it messes up when I throw .htaccess into the mix.

Example:

Say I’ve got an url: http://www.whatever.com/services/ <== using .htaccess for clean url

When the “click” event is added: http://www.whatever.com/services/#edit <== appended to url after “click” event…page won’t load.

I hope I’ve explained this well enough…and suggestions?

Thanks…great site btw.

Commenter Avatar March 13 / #
scott says:

Nevermind….I figured it out. I added e.stop(); to the “click” event function.

Commenter Avatar March 14 / #

Not working in Opera.

David Walsh March 16 / #

@Ahmed Alfy: Use a different “trigger”, like double click.

Commenter Avatar March 17 / #
Jose Gonzalez says:

Wow David, your work is impressive like always!!

i’ll try it ;D!

Commenter Avatar April 09 / #
milder says:

It’s not working perfectly in safari on OSX.

The menu will show, but you can’t use it as it should be. It’s ignoring the mouseover. You have to click on the menu first and hold down the mouseclick, move the cursor away from the menu, release the mouseclick and now you can use it.

Anyway.. nice menu! Keep up the good work!

Commenter Avatar May 16 / #
Ben says:

Hey, just want to say I love this tool! I was actually wondering if it was somehow possible to pass the element to the onShow event, so I could change the menu based on the elements properties before the menu actually showed?

Thanks!

Commenter Avatar May 19 / #
Ben says:

Acutally, I figured it out after reading over your code again. Thx :)

Commenter Avatar May 29 / #
Kyle says:

Hi David,

I’m not sure if anyone else noticed or was bothered by this, but when you right-click to get the context menu, in everything but opera apparently, there’s the potential that it will highlight text. You can prevent that by adding this to the show() function:

if(document.selection && document.selection.empty) {
document.selection.empty();
} else if(window.getSelection) {
window.getSelection().removeAllRanges();
}

Commenter Avatar July 09 / #
Csaba says:

This is something I would like to see done in JQuery.

Commenter Avatar August 12 / #
Maximiliano says:

Hi There, i wonder if there is any way to put this context menu to every row in a table, of couse each item has to have a different menu so i can get for example a data from the row.

David Walsh August 12 / #

@Maximiliano: Yes, so set the “targets” to “#MyTable tr”

Commenter Avatar August 12 / #
Maximiliano says:

Hi Again, the thing your mention it works, so im trying right now to gather one element from the Table or something , can you write something that could help me?

Commenter Avatar August 12 / #
Maximiliano says:

I make it works with element.cells[1].innerText

Commenter Avatar August 13 / #
Maximiliano says:

There is anyway to know the element using the onShow Event?

Commenter Avatar August 13 / #
Maximiliano says:

Sorry, i figured out, i can use this

onShow: function(){
alert(this.options.element.cells[1].innerText);
}

Commenter Avatar November 10 / #
jpodnegara says:

great one David..!! thanks.. ^_^ but does it work on omnigrid ?

Commenter Avatar November 11 / #
bosko says:

hello

wery nice script david. i want to use on whole body (wherever its right clicked) not on only one DIV … its there possible to make that?

thank you

Commenter Avatar November 22 / #

Just thought I’d let you know that I’ve added an adapted version of this to my PDF annotation Moodle module, so thanks for making this code available!

(FYI the link to the Moodle module is above)

Commenter Avatar November 23 / #
Alfred says:

Hi,

sorry – looking at the code -but seems i’m stupid. Where you trigger right mouse click?

How to bring up contextmenu with a left-mouse click ??

Thanks

David Walsh November 23 / #

@Alfred: It’s the “trigger” option. You’d want:

trigger: ‘click’

Commenter Avatar November 24 / #
Alfred says:

ok, answering myself ;-)

trigger: ‘click’,

Commenter Avatar November 24 / #
Alfred says:

LOL – Thanks – i see you answered same :-)

Be Heard!

I want to hear what you have to say! Share your comments and questions below.

Name*:
Email*:
Website:  


© David Walsh 2007-2010. Contact David Walsh. Powered by the remarkable MooTools javascript framework.