Skip to content
This repository has been archived by the owner on Aug 14, 2021. It is now read-only.
Martin@MBP edited this page Oct 4, 2015 · 17 revisions

jquery.ui-contextmenu - Tutorial

A jQuery plugin that provides a context menu (based on the standard jQueryUI menu widget).

sample

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());
             }
         },
        ...
        ]
});

Initialize menu from an existing <ul> element

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>

Modify the menu depending on the context

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.

Tips and Tricks

[Howto] Add right-aligned shortcut hints

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;
}

[Howto] Enable keyboard control

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.

[Howto] Modify the menu using an asynchronous request

$(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();
    },

[Howto] Bind different contextmenus to the same DOM element

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());
    }
});
Clone this wiki locally