HTML5 context menus in Firefox (Screencast and Code)

You may not know it, but the HTML5 specifications go beyond what we put in the pages and also define how parts of the browser should become available to developers with HTML, CSS and JavaScript. One of these parts of the specs are context menus, or “right click menus”. Using HTML5 and a menu element you can add new options to these without having to write a browser add-on. In Firefox 8 (the current one) we have support for those. See the following screencast for a context menu demo.

The image example is pretty simple and was actually written by Paul Rouget as a demo in the original Firefox bug request. The main core is the HTML of it:

HTML5

As you can see you link the menu element to an element via its ID. The contextmenu attribute then points to this one. Each menu can have several menuitems. Each of those gets a textual label and a possible icon. You can also nest menu elements to create multiple layer menus. Here, we add inline onclick handlers to point to different JavaScript functions to call when the menu item gets activated. The resulting context menu looks like this:

image with a context menu

The functionality is simple, all the rotate() and resize() functions do is add class names to the image using querySelector and classList:

function rotate() {
  document.querySelector('#menudemo').classList.toggle('rotate');
}
function resize() {
  document.querySelector('#menudemo').classList.toggle('resize');
}

The real effect is in CSS transforms and transitions. As the image has an ID of menudemo here is what is needed in CSS to rotate and resize:

#menudemo {
  -moz-transition: 0.2s;
  width:200px;
}
#menudemo.rotate {
  -moz-transform: rotate(90deg);
}
#menudemo.resize {
  -moz-transform: scale(0.7);
}
#menudemo.resize.rotate {
  -moz-transform: scale(0.7) rotate(90deg);
}

Notice that in a real product we should of course add the other browser prefixes and go prefix-less but as the functionality now only works in Firefox, this is enough for this demo.

Detecting support and visual hinting

Now, as this is extending the normal user offerings in the browser we need to make it obvious that there is a right-click menu available. In CSS3, there is a context-menu cursor available to us. When context menus are available, this should be shown:

.contextmenu #menudemo, .contextmenu .demo {
  cursor: context-menu;
}

We test the browser for support by checking for contextmenu on the body element and for HTMLMenuItemElement in the window (this has been added as a pull request to Modernizr, too).

if ('contextMenu' in document.body && 'HTMLMenuItemElement' in window) {
  document.documentElement.classList.add('contextmenu');
} else {
  return;
}

Wouldn’t HTMLMenuItemElement be enough? Yes, but a real context menu should only offer functionality when it is sensible, and that is where contextMenu comes in.

Turning menuitems on and off depending on functionality

As a slightly more complex example, let’s add a “count words” functionality to the document. For this, we generate a counter element that will become a tooltip when the words were counted:

var counter = document.createElement('span');
counter.id = 'counter';
counter.className = 'hide';
document.body.appendChild(counter);

counter.addEventListener('click', function(ev){
  this.className = 'hide';
},false);

This one is hidden by default and becomes visible when the hide class is removed. To make it smooth, we use a transition:

#counter{
  position: absolute;
  background: rgba(0,0,0,0.7);
  padding:.5em 1em;
  color: #fff;
  font-weight:bold;
  border-radius: 5px;
  -moz-transition: opacity 0.4s;
}
#counter.hide{
  opacity: 0;
}

We start with two sections with context menus:

We then loop through all the menuitems with the class wordcount and apply the functionality.

var wordcountmenus = document.querySelectorAll('.wordcount'),
    i = wordcountmenus.length;

while (i--) {
  wordcountmenus[i].addEventListener('click', function(ev){
    // add functionality
  }, false);
}

We need to find out what has been selected in the page. We do this by using getSelection() and splitting its string version at whitespace. We then show the counter by removing the hide class name.

var wordcountmenus = document.querySelectorAll('.wordcount'),
    i = wordcountmenus.length;

while (i--) {
  wordcountmenus[i].addEventListener('click', function(ev){
    var text = document.getSelection(),
        count = text.toString().split(/s/).length;
    counter.innerHTML = count + ' words';
    counter.className = '';
  }, false);
}

You can see this in action in the second context menu demo. Now, the issue with this (as explained in the screencast) is that it always counts the words, regardless of the user having selected some text. What we want is the menu only to be active when there is text selected.

context menu item available or not available depending on selection

So in order to make our menu only become available when it makes sense we check if there is a selection in the document. Every context menu fires an event called contextmenu when it opens. So all we need to do is to subscribe to this event.

When something is selected in the document document.getSelection().isCollapsed is true. Otherwise it is false, so all we need to do is to enable or disable the menu item accordingly:

document.querySelector('#interactive').addEventListener(
  'contextmenu', function(ev) {
    this.querySelector('.wordcount').disabled =
    document.getSelection().isCollapsed;
  },
false);

The last thing to solve is the position of the mouse to position the counter element. As the menu selection event doesn’t give us the mouse position we need to add a contextmenu handler to the whole document that positions the counter invisibly behind the menu when it is opened:

document.body.addEventListener(
  'contextmenu', function(ev) {
    counter.style.left = ev.pageX + 'px';
    counter.style.top = ev.pageY + 'px';
    counter.className = 'hide';
  },
false);

Further reading and resources

About Chris Heilmann

Evangelist for HTML5 and open web. Let's fix this!

More articles by Chris Heilmann…


19 comments

  1. Mårten Björk

    Great news! How accessible is this? For instance, is there any way to indicate that the word count is a result of the action in the contextual menu?

    November 24th, 2011 at 03:09

  2. fpiat

    Can we only had new options or have we methods / attributes for disabling or hiding the browser menu options?

    November 24th, 2011 at 03:20

    1. fpiat

      must read “Can we only add” and not “had”

      November 24th, 2011 at 03:22

    2. passcod

      I think that’d be rather inconvenient, and might actually be a security risk. The first silly use would probably be to disable to “View Source” menu item or the “View Image”/”Save Image” items for pseudo-protection… but I think it could go much further than that, exploit-wise.

      November 24th, 2011 at 04:26

      1. Chris Heilmann

        Yes, this is why for now you can “only add” new items and not override the original ones.

        November 24th, 2011 at 06:51

        1. Ronny

          Hey Chris, thanks for the write-up! This is cool.
          The first thing I thought of when reading this article was “hehe let’s try to replace a default action like Back with some custom action, could be fun”.
          So I guess “only add” is a good call ;-)

          November 24th, 2011 at 09:48

      2. Jonas Finnemann Jensen

        This would be awesome, if I’m writting a web app there’s no reason why “View Source”, “Save Image”, etc. should take up space in the context menu. They’re not useful if a web app, why should I be able to save the image of a button?

        People can always find these options elsewhere, like in a menu or download the source manually.

        My point is that when you force web apps to have all these left-over things that are only useful for when a website is a document, you’re making the web application platform less appealing.

        Trust the web developer, if they want to obscure images, which is as you put it “silly”, they can just do it using js, css or whatever.

        November 24th, 2011 at 11:07

        1. Jonas Finnemann Jensen

          My point is that from a usability point of view, letting people remove default context menu items would make sense.

          And if some web devs, wishes to destroy the default usability by abusing this feature, that’s their loss.

          I agree that alerts with “you can’t right click here” are silly, but why not let the silly people be silly if they want to so badly? :)

          November 24th, 2011 at 11:19

          1. dhinesh

            it was a nice theory

            September 12th, 2012 at 03:26

  3. Rodney Rehm

    I find it pretty interesting, that you guys link to Addy Osmani’s fork of the HTML5 contextmenu polyfill. His repo is out of sync. You’ll find the real deal at http://medialize.github.com/jQuery-contextMenu/ :) – Native Demo here: http://medialize.github.com/jQuery-contextMenu/demo/html5-polyfill-firefox8.html

    I’d be interested in finding out how this happened. Is it because Addy Osmani is a namel well known on the net and I am not? Or did the guy looking for a polyfill just link to the first hit on google?

    Is there a road map regarding your context menu stuff? Currently one cannot (optically) tell aparty controls, checkboxes and radio elements within a context menu. (Amongst other difficulties I have with the native implementation). Who would I bug with my questions?

    November 24th, 2011 at 05:57

    1. Chris Heilmann

      Actually it is because we were talking to each other when I was showing him the native implementation and he went for it. He was the first to tweet back and start the discussion on Google+. Nothing about being famous at all. Great to get your resources and feedback. I will try to find out who is the best person to talk to you about it. Is there a bug open on the issue with checkboxes?

      November 24th, 2011 at 06:49

      1. Rodney Rehm

        Nope, haven’t opened any bugs yet. Should I do that before hand, or wait for some mozillian discussion? And thanks for updating the link :)

        November 24th, 2011 at 06:59

        1. Rodney Rehm

          There you go, I fought my way through bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=705292

          November 25th, 2011 at 09:15

  4. Luke Dorny

    Just a nitpic as we discuss html5, usage of the Section element without a heading inside it. If not, it should be a div or something better.
    Otherwise a fascinating new realm of possibilities making your site feel like it is an app, or better. Awesome article.

    November 24th, 2011 at 10:37

  5. Daniel Piechnick

    I can see a lot of different applications for this. Can we expect this in IE anytime soon? :)

    Daniel Piechnick

    November 25th, 2011 at 00:22

  6. kangax

    Just a small tweak — `’contextMenu’ in document.documentElement` would be better, for cases when body doesn’t exist yet.

    Also, what exactly is `”HTMLMenuItemElement” in window` supposed to determine? That menuitem element is supported? It just made me wonder if this kind of relation check is safe — is implementation really required to expose HTMLMenuItemElement interface publicly+globally when adding support for menu item element. Does IDL mention it anywhere?

    November 26th, 2011 at 17:03

  7. Isuru

    Just what I was looking for this script I’m working on! really cool!

    January 11th, 2012 at 20:07

  8. icaaq

    Just a heads up, the menuitem-element is not yet part of the html-specification. Follow the issue here :) https://www.w3.org/Bugs/Public/show_bug.cgi?id=13608

    March 19th, 2012 at 06:16

  9. pedz

    This is very interesting. I bumped into it while surfing trying to answer the following request: I’m looking for suggestions on how to give the user a visual clue that a context menu is available.

    I have a table, like a report, of “hits” in a database. Each is one line, with about 7 columns. Each element has its own context menu. Currently there is no visual clue to users that all those features are available and based upon feedback, people don’t know that they are there.

    And… perhaps I should not even have a context menu at those points but its all I could come up with. The current implementation is circa 2008 using javascript and a hidden UL element that pops up on a right mouse click.

    May 25th, 2012 at 06:10

Comments are closed for this article.