-
Notifications
You must be signed in to change notification settings - Fork 79
Home
A jQuery plugin that provides a context menu (based on the standard jQueryUI menu widget).
First, include dependencies:
- jQuery 1.7+ (1.10 or later recommended)
- jQuery UI 1.9+ (at least core, widget, menu), 1.11+ recommended
- One of the ThemeRoller CSS themes or a custom one
- jquery.ui-contextmenu.js (also available as CDN on jsdelivr or cdnjs)
<head>
<link href="//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css"
type="text/css" rel="stylesheet" />
<script src="//code.jquery.com/jquery-1.11.3.min.js" type="text/javascript"></script>
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.min.js" type="text/javascript"></script>
<script src="assets/jquery.ui-contextmenu.min.js"></script>
Assume we have some HTML elements that we want to attach a popup menu to:
<div id="container">
<div class="hasmenu">AAA</div>
<div class="hasmenu">BBB</div>
<div class="hasmenu">CCC</div>
</div>
Now we can enable a context menu like so:
$("#container").contextmenu({
delegate: ".hasmenu",
menu: [
{title: "Copy", cmd: "copy", uiIcon: "ui-icon-copy"},
{title: "----"},
{title: "More", children: [
{title: "Sub 1", cmd: "sub1"},
{title: "Sub 2", cmd: "sub1"}
]}
],
select: function(event, ui) {
alert("select " + ui.cmd + " on " + ui.target.text());
}
});
See the API Docs for a list of options.
The delegate
option defines a CSS selector, which is evaluated for all
elements inside the context element (#container
in our example).
In order to attach menus to all matching elements on the page that have
class="hasmenu"
, we may use document
as context:
$(document).contextmenu({
delegate: ".hasmenu",
...
});
Note: only one contextmenu widget instance can be bound to one element. See the Howto below for a solution to this problem.
The menu
options may contain a (nested) array of entry definitions.
See the API Docs for a
list of menu entry options.
Instead of handling all menu commands in the select
event, it is also possible
to attach callbacks directly to menu entries:
$(document).contextmenu({
delegate: ".hasmenu",
menu: [
{ title: "Copy", uiIcon: "ui-icon-copy", action: function(event, ui) {
alert("Copy " + ui.target.text());
}
},
...
]
});
In this case menu
must point to the markup:
$(document).contextmenu({
delegate: ".hasmenu",
menu: "#options",
select: function(event, ui) {
...
}
});
We also have to provide some HTML markup that defines the context menu structure, see jQueryUI menu for details:
<ul id="options" class="ui-helper-hidden">
<li data-command="copy"><span class="ui-icon ui-icon-copy"></span>Copy</li>
<li data-command="paste" class="ui-state-disabled">Paste</li>
<li>----</li>
<li>More
<ul>
<li data-command="sub1">Sub 1</li>
<li data-command="sub2">Sub 2</li>
</ul>
</li>
</ul>
Note: until and including jQuery UI 1.10 the use of anchors (<a>
) in menu
items was required:
<ul id="options" class="ui-helper-hidden">
<li data-command="copy"><a href="#"><span class="ui-icon ui-icon-copy"></span>Copy</a>
...
</ul>
Often we need to modify the menu before it is displayed, in order to reflect the
current context.
This can be done in the beforeOpen
event:
$(document).contextmenu({
delegate: ".hasmenu",
menu: [
{title: "Cut", cmd: "cut", uiIcon: "ui-icon-scissors"},
{title: "Copy", cmd: "copy", uiIcon: "ui-icon-copy"},
{title: "Paste", cmd: "paste", uiIcon: "ui-icon-clipboard", disabled: true },
...
],
beforeOpen: function(event, ui) {
var $menu = ui.menu,
$target = ui.target,
extraData = ui.extraData; // optionally passed when menu was opened by call to open()
// Optionally return false, to prevent opening the menu
// return false;
// En/disable single entries
$(document).contextmenu("enableEntry", "paste", false);
// Show/hide single entries
$(document).contextmenu("showEntry", "cut", false);
// Redefine the title of single entries
$(document).contextmenu("setEntry", "copy", "Copy '" + $target.text() + "'")
// Redefine all attributes of single entries
$(document).contextmenu("setEntry", "cut", {title: "Cuty", uiIcon: "ui-icon-heart", disabled: true});
// Redefine the whole menu
$(document).contextmenu("replaceMenu", [{title: "aaa"}, {title: "bbb"}, ...]);
// Redefine the whole menu from another HTML definition
$(document).contextmenu("replaceMenu", "#options2");
},
...
});
See the API Docs for a list of methods and a list of events.
Simply add a tag of your choice to the title (for example <kbd>
)
$(document).contextmenu({
delegate: ".hasmenu",
menu: [
{title: "Edit title<kbd>[F2]</kbd>", cmd: "rename"},
{title: "Copy <kbd>[Ctrl+C]</kbd>", cmd: "copy"}, ...
],
and make it right aligned via CSS:
.ui-menu kbd {
float: right;
}
In order open a context menu with the keyboard, make sure the target elements
are tabbable, for example by adding a tabindex="0"
attribute.
Also make sure the autoFocus: true
option is set.
This will allow to Use Tab and the Windows Menu keys.
$(document).contextmenu({
...
beforeOpen: function(event, ui) {
// Immediate menu changes
$(document).contextmenu("setEntry", "test", "(loading...)");
// Menu opens, then we submit a request and wait for the resonse
$.ajax({
...
}).done(function(data) {
// Modify the menu from the ajax response. The menu will be updated
// while open
$(document).contextmenu("setEntry", "test", {
title: "New entry", cmd: "test",
children: [ ... ]
});
});
},
Alternatively we can delay the opening until the response arrives:
$(document).contextmenu({
...
beforeOpen: function(event, ui) {
var dfd = new $.Deferred();
$.ajax({
...
}).done(function(data) {
// Modify the menu from the ajax response. The menu will be opened
// afterwards
$(document).contextmenu("setEntry", "test", {
title: "New entry", cmd: "test",
children: [ ... ]
});
dfd.resolve(); // Notify about finished response
});
// Return a promise to delay opening until an async response becomes
// available
ui.result = dfd.promise();
},
This is especially useful if we want to bind contextmenus for different selectors
to the document
element, in order to make them global:
$(document).contextmenu({
delegate: ".hasmenu",
menu: ...,
select: function(event, ui) {
alert("select contextmenu 1" + ui.cmd + " on " + ui.target.text());
}
});
Another call to $(document).contextmenu({...})
would destroy the previous
instance, because the jQuery Widget Factory
only allows one instance per element.
The soulution is to create new widget with another name but identical functionality:
// 1. Create and register another widget that inherits directly from
// jquery-ui-contextmenu:
$.widget("moogle.contextmenu2", $.moogle.contextmenu, {});
// 2. Now we can bind this new widget to the same DOM element without
// destroying a previous widget.
$(document).contextmenu2({
delegate: ".hasmenu2",
menu: ...,
select: function(event, ui) {
alert("select contextmenu2" + ui.cmd + " on " + ui.target.text());
}
});