Labs/Jetpack/JEP/14

From MozillaWiki
< Labs‎ | Jetpack‎ | JEP
Jump to: navigation, search
Draft-template-image.png THIS PAGE IS A WORKING DRAFT Pencil-emoji U270F-gray.png
The page may be difficult to navigate, and some information on its subject might be incomplete and/or evolving rapidly.
If you have any questions or ideas, please add them as a new topic on the discussion page.

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.

Menu(menuitems)

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, or null 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.

menu

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

jetpack.menu

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.

jetpack.menu.file

The browser's File menu. A jetpack.Menu object.

jetpack.menu.edit

The browser's Edit menu. A jetpack.Menu object.

jetpack.menu.view

The browser's View menu. A jetpack.Menu object.

jetpack.menu.history

The browser's History menu. A jetpack.Menu object.

jetpack.menu.bookmarks

The browser's Bookmarks menu. A jetpack.Menu object.

jetpack.menu.tools

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

jetpack.menu.context

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.

jetpack.menu.context.page

The content context menu. This is the context menu that appears when right-clicking on a Web page. A ContextMenuSet object.

jetpack.menu.context.browser

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