MooTools ContextMenu Plugin
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
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.
David,
Great job. I love how context menus improve usability specially in admin UIs.
Nice plugin! A little note on which license you’re releasing this under would be nice :-) I will consider 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 becomesif(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.
@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!
Wow … definitely using this on a few of my sites.
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.
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 :)
@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.
Excellent, David! I know a lot of places where something like this could be used.
Is it possible to have the menu close when you left click outside of the region?
Updated. Thanks Mark!
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 :)
@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.
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:
Thank you again for your class :)
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 Hinckle: What do you mean by this? I’m not following.
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
I suggest using the “name” attribute instead of the “href” on the link to avoid the “#action” being addded to the url. Easy fix, as follows:
In your HTML :
then in line 68 0f the script:
Enjoy!
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.
Nevermind….I figured it out. I added e.stop(); to the “click” event function.
Not working in Opera.
@Ahmed Alfy: Use a different “trigger”, like double click.
Wow David, your work is impressive like always!!
i’ll try it ;D!
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!
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!
Acutally, I figured it out after reading over your code again. Thx :)
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:This is something I would like to see done in JQuery.
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.
@Maximiliano: Yes, so set the “targets” to “#MyTable tr”
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?
I make it works with element.cells[1].innerText
There is anyway to know the element using the onShow Event?
Sorry, i figured out, i can use this
great one David..!! thanks.. ^_^ but does it work on omnigrid ?
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
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)
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
@Alfred: It’s the “trigger” option. You’d want:
trigger: ‘click’
ok, answering myself ;-)
trigger: ‘click’,
LOL – Thanks – i see you answered same :-)
Hi,
I havent tried it out but I would like to know if this menu can be initialized many times on one page on different objects i.e. on right click, a different menu (with varying links on it) based on which objects is right clicked.
Thanx
Thanks for the plugin. That works magic. The only thing I would like to add is to being able to specify not only element type, but perhaps a class. For example, a page may have a myriad of links , but what if I only want to attach the menu to those belonging to a specific class, eg …
How can I do that?
Thx again!
Oh, dear, my HTML code got parsed…
What I wanted to say was:
imagine you have two spans: (I replaced “” with “@”)
@span class=”mif-tree-name”@ Click me @/span@
@span class=”some-class”@ Click me @/span@
Then if you do as described above, only the first span will have the JS context menu attached; the second one will have browser-default context menu
Regards
Hi David,
I’m attempting to use your contextMenu class yet again but have a question. If I rightclick on a tr (targets: ‘tbody tr’), how do I get the contextmenu to close when I mouseout of the same row? THANKS AGAIN!
It’s very good.
I like this.
Thanks for share.
And I wrote something to introduce this project for my readers.
You can find the post about this in my website.
If something is wrong,pls figure it out.thanks.
Btw, it’s tested to work in recent PC versions of Firefox, Explorer, Chrome, Opera and Safari.
One further revision. I found myself in need of adding and removing trigger items, so I added two methods providing this functionality, and broke another method up in two. Here are the changes (with my previous revisions included):
Hello folks
The following code works fine for me to reinitialize the context menu:
Fantastic component!
The only trouble im having at present is that the click event is conflicting with the sortables class.
After opening the menu I can no loger drag and drop in my sortable lists.
The context menu is not to blame for breaking the sortable class. There’s a bug in v1.2.4.4 of the sortables class itself.
For details and a solution, check out https://mootools.lighthouseapp.com/projects/24057/tickets/292-sortables-breaking-on-right-clicking-mouse-button2-the-handle
Good luck!
Very nice plugin!
I needed the ability to define my own z-index for the contextmenu. Therefore I made the
following changes:
After doing these changes one can do this:
Damn! I’ve forgotten to use the code-Tags. :(
Could you please fix my post for better readability. Thanks!
Like Ben and Maximiliano I needed to know the clicked element inside the show-method.
Therefore I made the following changes:
...
//show the menu
this.show(this.options.element);
...
...
show: function(clicked) {
this.fx.start(1);
this.fireEvent('show', clicked);
...
}
I have found one small problem ,i.e we can right click again on context menu and if we right click at any place in the dom context menu does not get hide,
Figured an easy fix,
just add this after line 90,
Cheerzzzzz :)
$(document.body).addEvent('contextmenu', function(e) {
this.hide();
}.bind(this));
Fantastic script Thanks David.
I have given the menu more depth for anyone who is in need of it.
http://jsfiddle.net/DeanOutlaw/hHLCm/6/
Hello,
can someone help customzing my copy past version, copying a certain
li
from
lul1
l to
lul2
l
The demo page for this plugin:
http://davidwalsh.name/demo/moo-context-menu.php
doesn’t appear to work.
1. The context menu pops up in the wrong screen location (significantly right of and below the click point),
2. The “Copy” menu item does not appear in the proper place in the menu: it appears as a separate object (in a blue rounded rectangle) which overlaps with the menu.
3. The icons for each of the other menu items are clipped at the bottom (this is a minor issue).
The same behaviour occurs in four different browsers (Google Chrome Version 24.0.1312.56 m, Firefox 18.0.1, Opera 12.12, and IE 9.0.8112.16421) so it looks like the menu plugin is broken.
Great plugin!
Have you thought about putting this in MooTools Forge?
Ha, never mind, I see that it’s there!