Labs/Jetpack/JEP/14
Contents
- 1 JEP 14 - Menus
- 1.1 Introduction and Rationale
- 1.2 Proposal
- 1.2.1 Menu Objects
- 1.2.1.1 Constructors
- 1.2.1.2 Properties
- 1.2.1.3 Methods
- 1.2.1.3.1 add(items)
- 1.2.1.3.2 clear()
- 1.2.1.3.3 contextOn(node)
- 1.2.1.3.4 hide()
- 1.2.1.3.5 insertAfter(newItems, target)
- 1.2.1.3.6 insertBefore(newItems, target)
- 1.2.1.3.7 item(target)
- 1.2.1.3.8 popupOn(node)
- 1.2.1.3.9 remove(target)
- 1.2.1.3.10 replace(target, newItems)
- 1.2.1.3.11 reset()
- 1.2.1.3.12 set(items)
- 1.2.1.3.13 show(anchorNode)
- 1.2.1.4 Example Menu
- 1.2.2 Menuitem Objects
- 1.2.3 Menu Bar Menus
- 1.2.4 Context Menus
- 1.2.5 Targets
- 1.2.1 Menu Objects
- 1.3 Example Usage
JEP 14 - Menus
- Author & Champion: Drew Willcoxon <adw at mozilla dot com>
- Editors: Abi Raja <abimanyuraja at gmail dot com>, Aza Raskin <aza at mozilla dot com>
- Status: Draft
- Type: API Track
- Created: 9 June 2009
- Reference Implementation: None
- Implementation Documentation
- JEP Index
Introduction and Rationale
This JEP describes an API to access and manipulate the browser's built-in menus.
As Jetpack's goal is to make extension development easier, Jetpack's menu API should support a wide range of scenarios. Traditional Firefox extensions commonly add items to the menu bar and context menus.
Proposal
The menu API creates two new namespaces:
-
jetpack.Menu
- The menu object constructor.
-
jetpack.menu
- Access to the menu bar and context menus.
Since the API is subject to change, it currently lives in the future and must first be imported before it is used:
jetpack.future.import("menu");
Menu Objects
All menus in Jetpack are jetpack.Menu
objects, including both built-in Firefox menus and menus that features create.
Constructors
Menu()
Creates an empty menu.
Creates a menu with the given items.
menuitems
- An array of menuitems.
Menu(properties)
Creates a menu with specific properties.
properties
- An object defining any of the properties below. The
items
property may be used to define an array of menuitems.
Properties
beforeHide
A function invoked just before the menu is hidden. If the menu is a context menu, it is called as beforeHide(menu, context)
and otherwise as beforeHide(menu)
. menu
is the Menu
object to which beforeHide
is attached. context
is an object describing the context in which the menu was shown.
beforeShow
A function invoked just before the menu is shown. If the menu is a context menu, it is called as beforeShow(menu, context)
and otherwise as beforeShow(menu)
. menu
is the Menu
object to which beforeShow
is attached. context
is an object describing the context in which the menu was shown. beforeShow
may modify the menu, and when the menu is shown, it will reflect the changes.
Example:
var myMenu = new jetpack.Menu(); myMenu.beforeShow = function (menu) { // Note that menu == myMenu == this. menu.set("The current date and time is " + new Date()); };
isShowing
True if the menu is currently visible and false otherwise. Read-only.
items
An array of menuitems in the menu. Read-only.
Methods
add(items)
Adds items to the menu. The position at which the items are added is at Jetpack's discretion. This method and set()
are the recommended methods of adding items to a menu.
items
- A single menuitem or an array of menuitems.
clear()
Removes all items from the menu, even items not added by the feature.
contextOn(node)
Binds the menu to a given node as its context menu.
node
- The menu is attached to this node, which may be either a raw DOM node or a DOM node wrapped by
jQuery
.
hide()
Hides the menu.
insertAfter(newItems, target)
Not yet implemented
Inserts new items after an existing item.
newItems
- A single menuitem or an array of menuitems.
target
- Indicates the existing item. See Targets. If no such target exists, the position at which the items are added is at the discretion of Jetpack.
insertBefore(newItems, target)
Inserts new items before an existing item.
newItems
- A single menuitem or an array of menuitems.
target
- Indicates the existing item. See Targets. If no such target exists, the position at which the items are added is at the discretion of Jetpack.
item(target)
Returns an item in the menu. The item may be modified. If the menu is hidden, it will reflect the changes when it is next shown. If the menu is visible, it will reflect the changes immediately.
target
- Indicates the existing item. See Targets.
- Return value
- A
Menuitem
object, ornull
if no such target exists.
popupOn(node)
Binds the menu to a given node. The menu will be shown when the node is left-clicked.
node
- The menu is attached to this node, which may be either a raw DOM node or a DOM node wrapped by
jQuery
.
remove(target)
Removes an item from the menu.
target
- Indicates the existing item to remove. See Targets. If no such target exists, the call silently fails.
replace(target, newItems)
Replaces an item with new items.
target
- Indicates the existing item to replace. See Targets. If no such target exists, the call silently fails.
newItems
- A single menuitem or an array of menuitems.
reset()
Reverts all the changes to the menu that the feature has made. For menus that the feature creates, this is equivalent to clear()
.
set(items)
Equivalent to calling reset()
and then add(items)
. This method and add()
are the recommended methods of adding items to a menu.
items
- A single menuitem or an array of menuitems.
show(anchorNode)
Shows the menu immediately.
anchorNode
- The menu will pop up on this node, which may be either a raw DOM node or a DOM node wrapped by
jQuery
.
Example Menu
var menu = new jetpack.Menu([ "Razzle-dazzle", { label: "Click Me", icon: "http://example.com/unicorn.png", command: function () jetpack.computer.explode() }, { label: "Iron Chefs", icon: "http://example.com/iron-chef.png", menu: new jetpack.Menu(["Japanese", "French", "Chinese", "Italian"]), command: function (clickedMenuitem) { console.log("You shall challenge Iron Chef " + clickedMenuitem.label); } } ]);
Menuitem Objects
Menus in Jetpack contain Menuitem
objects, including both built-in Firefox menus and menus that features create. No Menuitem
constructor is exposed, because Jetpack automatically boxes simple JavaScript objects into Menuitem
objects.
Creating Menuitems
When a feature passes new menuitems into the API, it should pass one of the following types:
null
A simple menu separator. (Note that any falsey value will suffice, including undefined
, null
, and the empty string. null
is recommended because it stands out.)
function
A menuitem that will update itself when its menu is shown.
The function is invoked just before the item's menu is shown and when the items
array of the item's menu is retrieved. It must return a non-function menuitem. If the item belongs to a context menu, the function is called as function(context)
and otherwise function()
. context
is an object describing the context in which the menu was shown.
Example:
var item = function () "The current date and time is " + new Date(); jetpack.menu.add(item);
string
A simple menuitem with the given string label.
object
A menuitem with specific properties. The object may define any of the properties listed below.
Properties
command
A function that will be called when the menuitem is clicked. If the menu
property is present, command
will instead be called when any of the item's descendents is clicked. In that case, the commands of descendants will be invoked first. In either case, it is called as command(clickedMenuitem)
, where clickedMenuitem
is the menuitem that was clicked.
data
An arbitrary string that the feature may associate with the menuitem.
disabled
A boolean. If true
, the menuitem is disabled.
icon
The URL of an icon to display in the menuitem. Note that some environments, notably Gnome 2.28, do not support menuitem icons either by default or at all.
id
Not yet implemented
The ID of the menuitem. Unlike xulId
, this is a friendly, Jetpack ID. Read-only. TODO: Table of Jetpack IDs
key
Not yet implemented
The keyboard shortcut used to invoke the item's command. It is a string of the following form:
( modifier '+' )* char
where char
is any displayable character (case-insensitive) and modifier
is any of the following:
-
shift
- The Shift key.
-
alt
- The Alt key. On the Macintosh, this is the Option key. On Macintosh this can only be used in conjunction with another modifier, since Alt+Letter combinations are reserved for entering special characters in text.
-
meta
- The Meta key. On the Macintosh, this is the Command key.
-
ctrl
- The Ctrl (or Control) key.
-
accel
- The key used for keyboard shortcuts on the user's platform: Ctrl on Windows and Linux, Command on Mac. Usually this would be the value a feature would use.
-
access
- The access key for activating menus and other elements. On Windows, this is the Alt key, used in conjuction with a menuitem's mnemonic.
Examples:
"t" "accel+t" "shift+accel+t" "accel+shift+t"
Jetpack does not broker key collisions, but it should!
label
The label of the menuitem, a string.
A jetpack.Menu
object. If this property is present, the menuitem expands into a submenu.
mnemonic
A character that is used as the item's shortcut key while the menu is open. It should be one of the characters that appears in the item's label. On some platforms this character is underlined in the label.
radioGroup
Not yet implemented
When type == "radio"
, this string is the name of the radio group to which the item belongs. Only one item at a time in a radio group may be selected. If undefined, all radio items in a menu belong to a single, anonymous group.
Example:
var menu = new jetpack.Menu([ { label: "Vanilla", type: "radio", radioGroup: "ice cream" }, { label: "Chocolate", type: "radio", radioGroup: "ice cream" }, { label: "Pistachio", type: "radio", radioGroup: "ice cream" }, null, { label: "Sprinkles", type: "radio", radioGroup: "topping" }, { label: "Walnuts", type: "radio", radioGroup: "topping" }, { label: "Salmon", type: "radio", radioGroup: "topping" } ]);
type
A string, one of "check"
, "radio"
, or "separator"
. If this property is undefined, the item is a normal menuitem. Note that when type == "separator"
, any other properties (such as label
) may be specified. Fancy separators may behave differently on different platforms, however.
Only "separator"
is currently implemented
xulId
The ID of the menuitem's backing XUL element, exposed for the benefit of advanced developers.
Menu Bar Menus
When a feature needs to expose functionality through a menu but no menu in particular, that feature should do The Right Thing by using jetpack.menu
, the "Jetpack menu." jetpack.menu
is a jetpack.Menu
object corresponding to a menu or a region of a menu in the menu bar that Jetpack sets aside for features. The actual menu is up to Jetpack, but currently it is the Tools menu. In the future it may be a submenu of the Tools menu, for example.
Features meant for wide release should prefer jetpack.menu
to the jetpack.menu.*
menus because:
- Firefox's menus are subject to change and in fact will be changing in Firefox 3.7. By exposing functionality through
jetpack.menu
, a feature is guaranteed an easy transition. - Many users, especially those new to Firefox, don't realize the distinction between add-ons and the browser itself. If a user forgets or is unable to tell whether a certain menuitem is part of Firefox or provided by a feature, she does not know where to turn when she has problems with it.
- If many features' menus are local to one area, the user need not hunt and peck through many menus trying to find a particular item of a feature.
- No one likes a cluttered Tools menu.
- It's less code. (OK, slightly less.)
This is only a recommended practice; developers are of course free to do as they wish.
The browser's File menu. A jetpack.Menu
object.
The browser's Edit menu. A jetpack.Menu
object.
The browser's View menu. A jetpack.Menu
object.
The browser's History menu. A jetpack.Menu
object.
The browser's Bookmarks menu. A jetpack.Menu
object.
The browser's Tools menu. A jetpack.Menu
object.
Context Menus
ContextMenuSet
Context menus behave a little differently from other menus. Unlike the Tools menu, which is always in the same place and whose items basically remain constant, context menus don't really exist until they pop up in a certain context. For example, the items of the page's context menu change depending on what the user clicks: images have a context menu, links have a context menu, and so on. Does the page have a single context menu or many?
Jetpack's solution is to expose ContextMenuSet
objects. A ContextMenuSet
represents a set of context menus. jetpack.menu.context.page
is the set of context menus that appear on the page: the image menu, link menu, and so on.
ContextMenuSet
defines many of the same methods that jetpack.Menu
does. Features can use these methods to modify all the menus in a set at once. They are:
-
add(items)
-
clear()
-
insertAfter(newItems, target)
Not yet implemented -
insertBefore(newItems, target)
-
remove(target)
-
replace(target, newItems)
-
reset()
-
set(items)
The following jetpack.Menu
properties can also be defined on a ContextMenuSet
:
-
beforeHide
-
beforeShow
Note that a jetpack.Menu
object is passed to beforeHide
and beforeShow
, since they are called during a context menu's invocation. They may then modify the menu.
Individual menus are drawn from the set using on()
:
on(selector)
Returns a new set whose context menus are those that arise from nodes that match the given CSS selector.
selector
- A CSS selector, a string.
- Return value
- A new
ContextMenuSet
object.
Example:
jetpack.menu.context.page.on("img").add(function (context) ({ label: "Download Image", command: function () download(context.node.getAttribute("src")) }));
The feature's context menu. All of the feature's context menus are exposed through jetpack.menu.context
, including slidebar, panel, toolbar, and status bar item context menus. A ContextMenuSet
object.
The content context menu. This is the context menu that appears when right-clicking on a Web page. A ContextMenuSet
object.
The chrome context menu. This is the context menu that appears when right-clicking on an element in Firefox's interface, such as a bookmark on the bookmarks toolbar. A ContextMenuSet
object.
Context Objects
Some callbacks passed into the API are called with a context
object, such as ContextMenuSet.beforeShow()
. Context objects are objects that describe the context in which a context menu is shown. They have the following properties:
-
node
- The DOM node on which the menu is shown. This is the node the user clicked to open the menu.
-
document
- The content document in which the menu is shown.
-
window
- The content window containing the page in which the menu is shown.
-
tab
- The Jetpack tab object containing the page in which the menu is shown. This property is defined only if the menu belongs to
jetpack.menu.context.page
. Not yet implemented
Targets
Some methods act on existing items within a menu. Existing items are always identified using targets. A target is a string, regular expression, or integer.
A string target is case-insensitively matched against the label
, id
, and xulId
properties of menuitems.
A regular expression target is tested against the label
, id
, and xulId
properties of menuitems.
An integer target indicates a zero-based index within a menu. Integer targets can be negative: -1 indicates the last item in a menu, -2 the second-to-last, and so on.
Multiple items in a menu may match a target, but action is only ever taken on the first matching item.
TODO: Warning about l10n
Example Usage
Before running any examples, import the API from the future:
jetpack.future.import("menu");
Add a single, static menuitem to the Jetpack menu that doesn't do anything:
jetpack.menu.add("Two Drink Holders and a Captain's Chair");
Add a menuitem to the Jetpack menu that displays the current date and time each time it's opened:
jetpack.menu.add(function () new Date().toString());
Click an item in the Jetpack menu to be notified of the current date and time:
jetpack.menu.add({ label: "Show Current Date and Time", command: function () jetpack.notifications.show(new Date()) });
The same, except on the content context menu:
jetpack.menu.context.page.add({ label: "Show Current Date and Time", command: function () jetpack.notifications.show(new Date()) });
Create a submenu within the content context menu. When the user clicks an item, she's notified of her choice. Note that the submenu contains many items, including a menu separator:
jetpack.menu.context.page.add({ label: "Ice Cream", icon: "http://example.com/ice-cream.png", menu: new jetpack.Menu(["Vanilla", "Chocolate", "Pistachio", null, "None"]), command: function (menuitem) jetpack.notifications.show(menuitem.label) });
Add a "Recent Tweets" submenu to the Jetpack menu. (Assume we've defined a getRecentTweets()
, which invokes a callback with an array of strings.) When the menu is shown, it displays a "Loading..." item. If the menu remains open when getRecentTweets()
receives data from the network and calls done()
, the "Loading..." item is replaced with the tweets, one item per tweet:
This example will not work on OS X due to a platform bug in Firefox. Replace jetpack.menu with jetpack.menu.context.page to see the effect on the content context menu.
jetpack.menu.add({ label: "Recent Tweets", menu: new jetpack.Menu({ beforeShow: function (menu) { menu.set("Loading..."); getRecentTweets(function done(tweets) menu.set(tweets)); } }) });
When the user selects some text on a page, the context menu normally displays a simple item that searches for it. Replace that item with a menu that lets the user search either Google or Wikipedia:
jetpack.menu.context.page.replace("Search", function (context) ({ label: "Search for " + jetpack.selection.text, menu: new jetpack.Menu([ { label: "Google", icon: "http://www.google.com/favicon.ico", data: "http://www.google.com/search?q=" }, { label: "Wikipedia", icon: "http://en.wikipedia.org/favicon.ico", data: "http://en.wikipedia.org/wiki/" } ]), command: function (menuitem) { context.window.location.href = menuitem.data + jetpack.selection.text; } });
Add an item to the hyperlink context menu that tweets the link:
jetpack.menu.context.page.on("a").add(function (context) ({ label: "Tweet", command: function () jetpack.lib.twitter.statuses.update({ status: context.node.href }) }));
Create some div
buttons (e.g., in a slidebar or status bar item) and specify their context menu:
for (let i = 0; i < 10; i++) { var button = $('<div class="button" />', document); buttonContainer.append(button); } jetpack.menu.context.on(".button").add(["Do This", "Do That"]);
Create a div
button (e.g., in a slidebar or status bar item) and attach menus directly to it. contextMenu
becomes the button's context menu. Left-click the button to show popupMenu
:
var button = $("<div />", document); button.text("Click Me"); var contextMenu = new jetpack.Menu(["Do This", "Do That", "And the Other"]); contextMenu.contextOn(button); var popupMenu = new jetpack.Menu(["Frumpy", "Frimpy", "Frompy"]); popupMenu.popupOn(button);