Skip to content

Commit

Permalink
A demo of the new html5 context menus in Firefox
Browse files Browse the repository at this point in the history
thewebrocks.com/demos/context-menu/

http://hacks.mozilla.org/2011/11/html5-context-menus-in-firefox-screenca
st-and-code/
  • Loading branch information
codepo8 committed Nov 24, 2011
0 parents commit 2385b36
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 0 deletions.
Binary file added arrow_rotate_clockwise.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
155 changes: 155 additions & 0 deletions context.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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 <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/interactive-elements.html#context-menus">context menus</a>, 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 <a href="http://thewebrocks.com/demos/context-meny">context menu demo</a> which is available on the web, too.

<video controls width="100%" preload="none" poster="http://cf.cdn.vid.ly/8e1y1v/poster.jpg"><source src="http://cf.cdn.vid.ly/8e1y1v/mp4.mp4" type="video/mp4"><source src="http://cf.cdn.vid.ly/8e1y1v/webm.webm" type="video/webm"><source src="http://cf.cdn.vid.ly/8e1y1v/ogv.ogv" type="video/ogg"><a target='_blank' href='http://vid.ly/8e1y1v'><img src='http://cf.cdn.vid.ly/8e1y1v/poster.jpg' width="500"></a></video>

The image example is pretty simple and was actually written by Paul Rouget as a demo <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=617528">in the original Firefox bug request</a>. The main core is the HTML of it:

<pre lang="xml">
<section id="noninteractive" contextmenu="imagemenu">

<img src="html5.png" alt="HTML5" id="menudemo">

<menu type="context" id="imagemenu">
<menuitem label="rotate" onclick="rotate()"
icon="arrow_rotate_clockwise.png">
</menuitem>
<menuitem label="resize" onclick="resize()"
icon="image-resize.png">
</menuitem>
<menu label="share">
<menuitem label="twitter" onclick="alert('not yet')"></menuitem>
<menuitem label="facebook" onclick="alert('not yet')"></menuitem>
</menu>
</menu>

</section>
</pre>

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


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

<pre lang="javascript">
function rotate() {
document.querySelector('#menudemo').classList.toggle('rotate');
}
function resize() {
document.querySelector('#menudemo').classList.toggle('resize');
}
</pre>

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

<pre lang="css">
#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);
}
</pre>

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.

<h2>Detecting support and visual hinting</h2>

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 <code>context-menu</code> cursor available to us. When context menus are available, this should be shown:

<pre lang="css">
.contextmenu #menudemo, .contextmenu .demo {
cursor: context-menu;
}
</pre>

We test the browser for support by checking for contextmenu on the body element and for <code>HTMLMenuItemElement</code> in the window.

<pre lang="javascript">
if ('contextMenu' in document.body && 'HTMLMenuItemElement' in window) {
document.documentElement.classList.add('contextmenu');
} else {
return;
}
</pre>

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

<h2>Turning menuitems on and off depending on functionality</h2>

As a slightly more complex example, let's add a "count words" functionality to the document. We start with two sections with context menus:

<pre lang="xml">
<section id="noninteractive" contextmenu="countmenu">
<menu type="context" id="countmenu">
<menuitem class="wordcount" label="count words"></menuitem>
</menu>
</section>

<section id="interactive" contextmenu="countmenuinteractive">
<menu type="context" id="countmenuinteractive">
<menuitem class="wordcount" label="count words"></menuitem>
</menu>
</section>
</pre>

We then loop through all the <code>menuitems</code> with the class <code>wordcount</code> and apply the functionality.

<pre lang="javascript">
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.style.left = x + 'px';
counter.style.top = y + 'px';
counter.className = '';
}, false);
}</pre>

<pre lang="javascript">
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.style.left = x + 'px';
counter.style.top = y + 'px';
counter.className = '';
}, false);
}</pre>

<pre lang="javascript">
document.querySelector('#interactive').addEventListener(
'contextmenu', function(ev) {
this.querySelector('.wordcount').disabled =
document.getSelection().isCollapsed;
},
false);
</pre>

<pre lang="javascript">
document.addEventListener('click', function(ev){
if (ev.which === 3) {
x = ev.pageX;
y = ev.pageY;
}
},false);
</pre>



https://plus.google.com/115133653231679625609/posts/CJMyExJTbug
http://addyosmani.github.com/jQuery-contextMenu/
Binary file added html5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added image-resize.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
177 changes: 177 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Context Menu Demos</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<style>
*{margin:0;padding:0;}
body{
font-size:15px;
font-family:georgia,arial,sans-serif;
color: #333;
background: #f8f8f8;
}
footer,section,header{display:block;}
p {padding: 10px 20px 5px 20px;}
a{color:#999;}
footer p{text-align:right;font-size:12px;}
h2 {
font-size:20px;
margin: 10px 20px;
}
header h1{
font-size: 32px;
color: #369;
margin: 10px 20px;
}
p.demo {
margin: 1em;
border: 1px solid #999;
background: #ccc;
border-radius: 5px;
padding: 10px 20px;
}
.contextmenu #menudemo, .contextmenu .demo {
cursor: context-menu;
}

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

#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); }
</style>
</head>
<body>
<header><h1>Context menu demos</h1></header>


<section id="noninteractive" contextmenu="imagemenu">
<header>
<h2>Several menus example </h2>
</header>

<p>Right-click the image to rotate and resize</p>

<img src="html5.png" alt="HTML5" id="menudemo">

<menu type="context" id="imagemenu">
<menuitem label="rotate" onclick="rotate()"
icon="arrow_rotate_clockwise.png">
</menuitem>
<menuitem label="resize" onclick="resize()"
icon="image-resize.png">
</menuitem>
<menu label="share">
<menuitem label="twitter" onclick="alert('not yet')"></menuitem>
<menuitem label="facebook" onclick="alert('not yet')"></menuitem>
</menu>
</menu>

</section>

<section id="noninteractive" contextmenu="countmenu">

<header>
<h2>Non-interactive context menu</h2>
</header>

<p>Right-click anywhere in here to see context menu</p>

<menu type="context" id="countmenu">
<menuitem class="wordcount" label="count words"></menuitem>
</menu>

<p class="demo">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</section>

<section id="interactive" contextmenu="countmenuinteractive">

<header>
<h2>Interactive context menu</h2>
</header>

<p>Right-click anywhere in here to see context menu -
if you have nothing selected, the "count words" menu is not active.</p>

<menu type="context" id="countmenuinteractive">
<menuitem class="wordcount" label="count words"></menuitem>
</menu>

<p class="demo">Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
</section>

<footer>
<p>Written by
<a href="http://twitter.com/codepo8">Chris Heilmann</a> and
<a href="@paulrouget">Paul Rouget</a>
</p>
</footer>
<script>
(function(){

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

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

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

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

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

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

}());
function rotate() {
document.querySelector('#menudemo').classList.toggle('rotate');
}
function resize() {
document.querySelector('#menudemo').classList.toggle('resize');
}
</script>
</body>
</html>

0 comments on commit 2385b36

Please sign in to comment.