Implementing a Cross-Browser Context Menu as a jQuery Plugin

Web & App Developer

Often you need to have a custom context menu somewhere on your page. Chances are that you’re already using jQuery on your website since jQuery is used on half of all websites. This tutorial will show you how to implement your own jQuery plugin for creating cross-browser context menus. The final result will consist of one JavaScript file and one CSS file which can easily be included in your pages.

In the interest of promoting good practices, the plugin will use the jQuery plugin suggested guidelines as a starting point. If you need some extra tips, you can also take a look at 10 Tips for Developing Better jQuery Plugins.

The Basics

Throughout this tutorial, the plugin will be referred to as the “Audero Context Menu.” This name is arbitrary, so feel free to call it whatever you want. The starting point of the JavaScript file is taken from the jQuery guidelines page. To summarize, we’ll use an IIFE to ensure that the plugin doesn’t collide with other libraries that use the dollar sign, such as Prototype. We’ll also use namespacing to ensure that the plugin will have a very low chance of being overwritten by other code living on the same page. The chosen namespace is auderoContextMenu. At line 2 of the snippet below, we add the namespace as a property of the $.fn object. Instead of adding every method to the $.fn object, we’ll put them in an object literal as suggested by the guidelines. The plugin’s methods can then be called by passing in the name of the method as a string.

(function($) {
  $.fn.auderoContextMenu = function(method) {
    if (methods[method])
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    else if (typeof method === 'object' || typeof method === 'string' || ! method)
      return methods.init.apply(this, arguments);
    else
      $.error('Method ' + method + ' does not exist on jQuery.auderoContextMenu');
  };
})(jQuery);

As you can see, the else if condition is slightly different from the guidelines. We have added a test to check if the method parameter is a string. This allows the user to use the plugin by simply passing a single parameter which should be the id of the custom context menu. This means that the context menu we’re building, which you’ll see is simply a list, will be a part of the DOM. The plugin works by replacing the default behavior of the right click event, but as you’ll see later, overriding the left click is also easy.

Getting Started

So far, we have code that accepts a method to invoke, along with some parameters. The next question is, what methods do we need? If you think of your browser’s context menu, it’s obvious that we need methods to show and hide the menu. We also need a method to initialize the context menu and some default settings. So, to reiterate, we need the following components.

  1. init() method
  2. show() method
  3. hide() method
  4. default settings

Since we’re so cool, the plugin we’re building will allow for several context menus on the same page. Every element will be completely independent from the others. Supporting multiple context menus requires changes to our code. So, let’s take a look at how the plugin changes.

(function($) {
  // default values used for the menu
  var defaultValues = {'idMenu': null, 'posX': null, 'posY': null};
  // settings for all the elements and menu specified by the user
  var elementsSettings = {};
  var methods = { /* here we'll write the init, show and hide methods */ }
  $.fn.auderoContextMenu = function(method) {
    // Here is the code shown previously
  };
})(jQuery);

Now it’s time to see the details of the init(), show(), and hide() methods mentioned above.

The init() method

This method, as you might expect, initializes the settings of the context menu and overrides the default behavior of the right click event. It also defines the clicked element, the chosen context menu, and its display position. The init() method takes one parameter, which can be an object or string. If an object is provided, it should contain the id of the menu and the coordinates to position it. If the user provides an object, it will be merged with the default settings using the jQuery extend() method. If a string is provided, it is used as the id of the menu to show.

this.on('contextmenu auderoContextMenu', function(event) {
  event.preventDefault();
  event.stopPropagation();
  var params = $.extend({}, elementsSettings[id]);
  if (elementsSettings[id].posX == null || elementsSettings[id].posY == null) {
    params.posX = event.pageX; params.posY = event.pageY;
  }
  methods.show(params, event, id);
});

Obviously, the most important part of this method is the replacement of the default context menu. To attach the custom menu, we need to listen to the contextmenu event using the jQuery on() method. on() takes a callback function as its second parameter. The callback function prevents the default behavior of displaying the browser’s native context menu. Next, we test if the menu has to be shown in a fixed position or at the click coordinates. The last part of the function calls our plugin’s show() method (not the jQuery method).

The show() method

The show() method displays the menu in the appropriate position. This method begins by hiding the menu that is going to be shown. This is done because it could already be visible due to a previous call to the method. The menu could be hidden using the jQuery hide() method, but since our plugin defines a hide() method, we’ll use our method as shown below.

methods.hide(idMenu);

The next step is to either use the coordinates provided by the user, or use the mouse coordinates at the time of the click event. The code to do this is shown below.

if (typeof params !== 'object' || params.posX == undefined || params.posY == undefined) {
  if (event == undefined) {
    params = {'idMenu': params, 'posX': 0, 'posY': 0}
  } else {
    params = {'idMenu': params, 'posX': event.pageX, 'posY': event.pageY}
  }
}

The code that actually displays the menu is quite simple. We use jQuery to get the menu through its id, then set the position (in pixels) starting from the upper-left corner. Finally, the jQuery show() method is used to display the menu. Thanks to jQuery chaining, these steps are accomplished with just one statement, as shown below. Our amazing menu now magically appears.

$('#' + idMenu)
.css('top', params.posY + 'px')
.css('left', params.posX + 'px')
.show();

The hide() method

The hide() method will be used to hide a menu. Since our plugin allows multiple context menus to be visible at the same time, it’ll be convenient to have the chance to hide all of the menus at once. Our hide() method takes a single optional parameter that represents the menu(s) to be hidden. If specified, the parameter can be a string or an array of strings. If the parameter is null or undefined, then all of the menus in elementsSettings will be recursively hidden.

hide: function(id) {
  if (id === undefined || id === null) {
    for(var Key in elementsSettings)
      methods.hide(elementsSettings[Key].idMenu);
  } else if ($.isArray(id)) {
    for(i = 0; i < id.length; i++)
      methods.hide(id[i]);
  } else
      $('#' + id).hide();
}

Adding Some Style!

We would like our custom context menus to work like native context menus as much as possible. To do this, we’ll need some CSS. We’ll want to hide the list that contains the menu, and show it only when needed. Moreover, we need to use absolute positioning to move the element around the page. The last relevant choice is to use a border to separate the different entries of the menu. All these choices will result in the following CSS code.

ul.audero-context-menu {
  position: absolute;
  display: none;
  background-color: menu;
  list-style-type: none !important;
  margin: 0px !important;
  padding: 0px !important;
}
ul.audero-context-menu * {
  color: menutext;
}
ul.audero-context-menu > li {
  border: 1px solid black;
  margin: 0px !important;
  padding: 2px 5px !important;
}
ul.audero-context-menu > li:hover {
  background-color: activecaption;
}
ul.audero-context-menu > li a {
  display: block;
}

Using the Plugin

Our plugin is very easy to use. In fact, its basic usage consists of just one line of code. For example, let’s say we have the following piece of HTML.

<ul id="context-menu" class="audero-context-menu">
  <li><a href="http://www.sitepoint.com">SitePoint</a></li>
  <li><a href="http://ug.audero.it">Audero user group</a></li>
</ul>
<div id="area">Right click here to show the custom menu.</div>

To allow the plugin to show the custom context menu, context-menu, when area is right clicked, you would write the following code.

$(document).ready (function() {
  $('#area').auderoContextMenu('context-menu');
});

If you want to show the custom menu on the left click too, simply add the following code.

$('#area').click (function(event) {
  $(this).auderoContextMenu('show', 'context-menu', event);
});

Conclusions

This tutorial has shown how to create a jQuery plugin that creates custom context menus. To see how it works, take a look at the online demo or download the source code. If you need more examples or a detailed explanation of the methods, please refer to the official documentation. The Audero Context Menu is completely free and is released under CC BY 3.0 license.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • cir

    (id == undefined || id == null) => (id == null) => (!id)

    • http://www.audero.it/ Aurelio De Rosa

      Yes, you can also write the condition in that way. Actually the condition should have been if (id === undefined || id === null), so I updated the code.