Modules (188)

Menus

Description

Dependencies

Variables

Public API

AppMenuBar Enumeration

Brackets Application Menu Constants

Type
string
    var AppMenuBar = {
        FILE_MENU       : "file-menu",
        EDIT_MENU       : "edit-menu",
        FIND_MENU       : "find-menu",
        VIEW_MENU       : "view-menu",
        NAVIGATE_MENU   : "navigate-menu",
        HELP_MENU       : "help-menu"
    };
Public API

BEFORE Enumeration

Insertion position constants Used by addMenu(), addMenuItem(), and addSubMenu() to specify the relative position of a newly created menu object

Type
string
    var BEFORE           = "before",
        AFTER            = "after",
        FIRST            = "first",
        LAST             = "last",
        FIRST_IN_SECTION = "firstInSection",
        LAST_IN_SECTION  = "lastInSection";
Public API

ContextMenuIds Enumeration

Brackets Context Menu Constants

Type
string
    var ContextMenuIds = {
        EDITOR_MENU:                    "editor-context-menu",
        INLINE_EDITOR_MENU:             "inline-editor-context-menu",
        PROJECT_MENU:                   "project-context-menu",
        WORKING_SET_CONTEXT_MENU:       "workingset-context-menu",
        WORKING_SET_CONFIG_MENU:        "workingset-configuration-menu",
        SPLITVIEW_MENU:                 "splitview-menu"
    };
Public API

DIVIDER

Other constants

    var DIVIDER = "---";
    var SUBMENU = "SUBMENU";
Public API

MenuSection

Brackets Application Menu Section Constants It is preferred that plug-ins specify the location of new MenuItems in terms of a menu section rather than a specific MenuItem. This provides looser coupling to Bracket's internal MenuItems and makes menu organization more semantic. Use these constants as the "relativeID" parameter when calling addMenuItem() and specify a position of FIRST_IN_SECTION or LAST_IN_SECTION.

Menu sections are denoted by dividers or the beginning/end of a menu

    var MenuSection = {
        // Menu Section                     Command ID to mark the section
        FILE_OPEN_CLOSE_COMMANDS:           {sectionMarker: Commands.FILE_NEW},
        FILE_SAVE_COMMANDS:                 {sectionMarker: Commands.FILE_SAVE},
        FILE_LIVE:                          {sectionMarker: Commands.FILE_LIVE_FILE_PREVIEW},
        FILE_EXTENSION_MANAGER:             {sectionMarker: Commands.FILE_EXTENSION_MANAGER},

        EDIT_UNDO_REDO_COMMANDS:            {sectionMarker: Commands.EDIT_UNDO},
        EDIT_TEXT_COMMANDS:                 {sectionMarker: Commands.EDIT_CUT},
        EDIT_SELECTION_COMMANDS:            {sectionMarker: Commands.EDIT_SELECT_ALL},
        EDIT_MODIFY_SELECTION:              {sectionMarker: Commands.EDIT_INDENT},
        EDIT_COMMENT_SELECTION:             {sectionMarker: Commands.EDIT_LINE_COMMENT},
        EDIT_CODE_HINTS_COMMANDS:           {sectionMarker: Commands.SHOW_CODE_HINTS},
        EDIT_TOGGLE_OPTIONS:                {sectionMarker: Commands.TOGGLE_CLOSE_BRACKETS},

        FIND_FIND_COMMANDS:                 {sectionMarker: Commands.CMD_FIND},
        FIND_FIND_IN_COMMANDS:              {sectionMarker: Commands.CMD_FIND_IN_FILES},
        FIND_REPLACE_COMMANDS:              {sectionMarker: Commands.CMD_REPLACE},

        VIEW_HIDESHOW_COMMANDS:             {sectionMarker: Commands.VIEW_HIDE_SIDEBAR},
        VIEW_FONTSIZE_COMMANDS:             {sectionMarker: Commands.VIEW_INCREASE_FONT_SIZE},
        VIEW_TOGGLE_OPTIONS:                {sectionMarker: Commands.TOGGLE_ACTIVE_LINE},

        NAVIGATE_GOTO_COMMANDS:             {sectionMarker: Commands.NAVIGATE_QUICK_OPEN},
        NAVIGATE_DOCUMENTS_COMMANDS:        {sectionMarker: Commands.NAVIGATE_NEXT_DOC},
        NAVIGATE_OS_COMMANDS:               {sectionMarker: Commands.NAVIGATE_SHOW_IN_FILE_TREE},
        NAVIGATE_QUICK_EDIT_COMMANDS:       {sectionMarker: Commands.TOGGLE_QUICK_EDIT},
        NAVIGATE_QUICK_DOCS_COMMANDS:       {sectionMarker: Commands.TOGGLE_QUICK_DOCS}
    };

NO_ERROR Enumeration

Error Codes from Brackets Shell

Type
number
    var NO_ERROR           = 0,
        ERR_UNKNOWN        = 1,
        ERR_INVALID_PARAMS = 2,
        ERR_NOT_FOUND      = 3;

contextMenuMap

Maps contextMenuID's to ContextMenu objects

Type
Object.<string, ContextMenu>
    var contextMenuMap = {};

menuItemMap

Maps menuItemID's to MenuItem objects

Type
Object.<string, MenuItem>
    var menuItemMap = {};

menuMap

Maps menuID's to Menu objects

Type
Object.<string, Menu>
    var menuMap = {};

Functions

Private

_isContextMenu

Check whether a ContextMenu exists for the given id.

id string
Returns: boolean
    function _isContextMenu(id) {
        return !!getContextMenu(id);
    }

    function _isHTMLMenu(id) {
        return (!brackets.nativeMenus || _isContextMenu(id));
    }
Public API

addMenu

Adds a top-level menu to the application menu bar which may be native or HTML-based.

name non-nullable string
display text for menu
id non-nullable string
unique identifier for a menu. Core Menus in Brackets use a simple title as an id, for example "file-menu". Extensions should use the following format: "author.myextension.mymenuname".
position nullable string
constant defining the position of new the Menu relative to other Menus. Default is LAST (see Insertion position constants).
relativeID nullable string
id of Menu the new Menu will be positioned relative to. Required when position is AFTER or BEFORE, ignored when position is FIRST or LAST
Returns: ?Menu
the newly created Menu
    function addMenu(name, id, position, relativeID) {
        name = _.escape(name);
        var $menubar = $("#titlebar .nav"),
            menu;

        if (!name || !id) {
            console.error("call to addMenu() is missing required parameters");
            return null;
        }

        // Guard against duplicate menu ids
        if (menuMap[id]) {
            console.log("Menu added with same name and id of existing Menu: " + id);
            return null;
        }

        menu = new Menu(id);
        menuMap[id] = menu;

        if (!_isHTMLMenu(id)) {
            brackets.app.addMenu(name, id, position, relativeID, function (err) {
                switch (err) {
                case NO_ERROR:
                    // Make sure name is up to date
                    brackets.app.setMenuTitle(id, name, function (err) {
                        if (err) {
                            console.error("setMenuTitle() -- error: " + err);
                        }
                    });
                    break;
                case ERR_UNKNOWN:
                    console.error("addMenu(): Unknown Error when adding the menu " + id);
                    break;
                case ERR_INVALID_PARAMS:
                    console.error("addMenu(): Invalid Parameters when adding the menu " + id);
                    break;
                case ERR_NOT_FOUND:
                    console.error("addMenu(): Menu with command " + relativeID + " could not be found when adding the menu " + id);
                    break;
                default:
                    console.error("addMenu(): Unknown Error (" + err + ") when adding the menu " + id);
                }
            });
            return menu;
        }

        var $toggle = $("<a href='#' class='dropdown-toggle' data-toggle='dropdown'>" + name + "</a>"),
            $popUp = $("<ul class='dropdown-menu'></ul>"),
            $newMenu = $("<li class='dropdown' id='" + id + "'></li>").append($toggle).append($popUp);

        // Insert menu
        var $relativeElement = relativeID && $(_getHTMLMenu(relativeID));
        _insertInList($menubar, $newMenu, position, $relativeElement);

        // Install ESC key handling
        PopUpManager.addPopUp($popUp, closeAll, false);

        // todo error handling

        return menu;
    }
Public API

closeAll

Closes all menus that are open

    function closeAll() {
        $(".dropdown").removeClass("open");
    }
Public API

getAllMenus

Retrieves the map of all Menu objects.

Returns: Object.<string,Menu>
    function getAllMenus() {
        return menuMap;
    }
Public API

getContextMenu

Retrieves the ContextMenu object for the corresponding id.

id string
Returns: ContextMenu
    function getContextMenu(id) {
        return contextMenuMap[id];
    }
Public API

getMenu

Retrieves the Menu object for the corresponding id.

id string
Returns: Menu
    function getMenu(id) {
        return menuMap[id];
    }
Public API

getMenuItem

Retrieves the MenuItem object for the corresponding id.

id string
Returns: MenuItem
    function getMenuItem(id) {
        return menuItemMap[id];
    }

    function _getHTMLMenu(id) {
        return $("#" + StringUtils.jQueryIdEscape(id)).get(0);
    }

    function _getHTMLMenuItem(id) {
        return $("#" + StringUtils.jQueryIdEscape(id)).get(0);
    }

    function _addKeyBindingToMenuItem($menuItem, key, displayKey) {
        var $shortcut = $menuItem.find(".menu-shortcut");

        if ($shortcut.length === 0) {
            $shortcut = $("<span class='menu-shortcut' />");
            $menuItem.append($shortcut);
        }

        $shortcut.data("key", key);
        $shortcut.text(KeyBindingManager.formatKeyDescriptor(displayKey));
    }

    function _addExistingKeyBinding(menuItem) {
        var bindings = KeyBindingManager.getKeyBindings(menuItem.getCommand().getID()),
            binding = null;

        if (bindings.length > 0) {
            // add the latest key binding
            binding = bindings[bindings.length - 1];
            _addKeyBindingToMenuItem($(_getHTMLMenuItem(menuItem.id)), binding.key, binding.displayKey);
        }

        return binding;
    }

    var _menuDividerIDCount = 1;
    function _getNextMenuItemDividerID() {
        return "brackets-menuDivider-" + _menuDividerIDCount++;
    }

    // Help function for inserting elements into a list
    function _insertInList($list, $element, position, $relativeElement) {
        // Determine where to insert. Default is LAST.
        var inserted = false;
        if (position) {

            // Adjust relative position for menu section positions since $relativeElement
            // has already been resolved by _getRelativeMenuItem() to a menuItem
            if (position === FIRST_IN_SECTION) {
                position = BEFORE;
            } else if (position === LAST_IN_SECTION) {
                position = AFTER;
            }

            if (position === FIRST) {
                $list.prepend($element);
                inserted = true;
            } else if ($relativeElement && $relativeElement.length > 0) {
                if (position === AFTER) {
                    $relativeElement.after($element);
                    inserted = true;
                } else if (position === BEFORE) {
                    $relativeElement.before($element);
                    inserted = true;
                }
            }
        }

        // Default to LAST
        if (!inserted) {
            $list.append($element);
        }
    }
Public API

registerContextMenu

Registers new context menu with Brackets.

Extensions should generally use the predefined context menus built into Brackets. Use this API to add a new context menu to UI that is specific to an extension.

After registering a new context menu clients should:

 - use addMenuItem() to add items to the context menu
 - call open() to show the context menu.
 For example:
 $("#my_ID").contextmenu(function (e) {
     if (e.which === 3) {
         my_cmenu.open(e);
     }
 });

To make menu items be contextual to things like selection, listen for the "beforeContextMenuOpen" to make changes to Command objects before the context menu is shown. MenuItems are views of Commands, which control a MenuItem's name, enabled state, and checked state.

id string
unique identifier for context menu. Core context menus in Brackets use a simple title as an id. Extensions should use the following format: "author.myextension.mycontextmenu name"
Returns: ?ContextMenu
the newly created context menu
    function registerContextMenu(id) {
        if (!id) {
            console.error("call to registerContextMenu() is missing required parameters");
            return null;
        }

        // Guard against duplicate menu ids
        if (contextMenuMap[id]) {
            console.log("Context Menu added with same name and id of existing Context Menu: " + id);
            return null;
        }

        var cmenu = new ContextMenu(id);
        contextMenuMap[id] = cmenu;
        return cmenu;
    }

    // Deprecated menu ids
    DeprecationWarning.deprecateConstant(ContextMenuIds, "WORKING_SET_MENU", "WORKING_SET_CONTEXT_MENU");
    DeprecationWarning.deprecateConstant(ContextMenuIds, "WORKING_SET_SETTINGS_MENU", "WORKING_SET_CONFIG_MENU");

    // Define public API
    exports.AppMenuBar = AppMenuBar;
    exports.ContextMenuIds = ContextMenuIds;
    exports.MenuSection = MenuSection;
    exports.BEFORE = BEFORE;
    exports.AFTER = AFTER;
    exports.LAST = LAST;
    exports.FIRST = FIRST;
    exports.FIRST_IN_SECTION = FIRST_IN_SECTION;
    exports.LAST_IN_SECTION = LAST_IN_SECTION;
    exports.DIVIDER = DIVIDER;
    exports.getMenu = getMenu;
    exports.getAllMenus = getAllMenus;
    exports.getMenuItem = getMenuItem;
    exports.getContextMenu = getContextMenu;
    exports.addMenu = addMenu;
    exports.removeMenu = removeMenu;
    exports.registerContextMenu = registerContextMenu;
    exports.closeAll = closeAll;
    exports.Menu = Menu;
    exports.MenuItem = MenuItem;
    exports.ContextMenu = ContextMenu;
});
Public API

removeMenu

Removes a top-level menu from the application menu bar which may be native or HTML-based.

id non-nullable string
unique identifier for a menu. Core Menus in Brackets use a simple title as an id, for example "file-menu". Extensions should use the following format: "author.myextension.mymenuname".
    function removeMenu(id) {
        var menu,
            commandID = "";

        if (!id) {
            console.error("removeMenu(): missing required parameter: id");
            return;
        }

        if (!menuMap[id]) {
            console.error("removeMenu(): menu id not found: %s", id);
            return;
        }

        // Remove all of the menu items in the menu
        menu = getMenu(id);

        _.forEach(menuItemMap, function (value, key) {
            if (_.startsWith(key, id)) {
                if (value.isDivider) {
                    menu.removeMenuDivider(key);
                } else {
                    commandID = value.getCommand();
                    menu.removeMenuItem(commandID);
                }
            }
        });

        if (_isHTMLMenu(id)) {
            $(_getHTMLMenu(id)).remove();
        } else {
            brackets.app.removeMenu(id, function (err) {
                if (err) {
                    console.error("removeMenu() -- id not found: " + id + " (error: " + err + ")");
                }
            });
        }

        delete menuMap[id];
    }

removeMenuItemEventListeners

Removes the attached event listeners from the corresponding object.

menuItem ManuItem
    function removeMenuItemEventListeners(menuItem) {
        menuItem._command
            .off("enabledStateChange", menuItem._enabledChanged)
            .off("checkedStateChange", menuItem._checkedChanged)
            .off("nameChange", menuItem._nameChanged)
            .off("keyBindingAdded", menuItem._keyBindingAdded)
            .off("keyBindingRemoved", menuItem._keyBindingRemoved);
    }

Classes

Constructor

MenuItem

MenuItem represents a single menu item that executes a Command or a menu divider. MenuItems may have a sub-menu. A MenuItem may correspond to an HTML-based menu item or a native menu item if Brackets is running in a native application shell

Since MenuItems may have a native implementation clients should create MenuItems through addMenuItem() and should NOT construct a MenuItem object directly. Clients should also not access HTML content of a menu directly and instead use the MenuItem API to query and modify menus items.

MenuItems are views on to Command objects so modify the underlying Command to modify the name, enabled, and checked state of a MenuItem. The MenuItem will update automatically

id string
command string,Command
the Command this MenuItem will reflect. Use DIVIDER to specify a menu divider
    function MenuItem(id, command) {
        this.id = id;
        this.isDivider = (command === DIVIDER);
        this.isNative = false;

        if (!this.isDivider && command !== SUBMENU) {
            // Bind event handlers
            this._enabledChanged = this._enabledChanged.bind(this);
            this._checkedChanged = this._checkedChanged.bind(this);
            this._nameChanged = this._nameChanged.bind(this);
            this._keyBindingAdded = this._keyBindingAdded.bind(this);
            this._keyBindingRemoved = this._keyBindingRemoved.bind(this);

            this._command = command;
            this._command
                .on("enabledStateChange", this._enabledChanged)
                .on("checkedStateChange", this._checkedChanged)
                .on("nameChange", this._nameChanged)
                .on("keyBindingAdded", this._keyBindingAdded)
                .on("keyBindingRemoved", this._keyBindingRemoved);
        }
    }

Methods

Private

_checkedChanged

Synchronizes MenuItem checked state with underlying Command checked state

    MenuItem.prototype._checkedChanged = function () {
        var checked = !!this._command.getChecked();
        if (this.isNative) {
            var enabled = !!this._command.getEnabled();
            brackets.app.setMenuItemState(this._command.getID(), enabled, checked, function (err) {
                if (err) {
                    console.log("Error setting menu item state: " + err);
                }
            });
        } else {
            ViewUtils.toggleClass($(_getHTMLMenuItem(this.id)), "checked", checked);
        }
    };
Private

_enabledChanged

Synchronizes MenuItem enabled state with underlying Command enabled state

    MenuItem.prototype._enabledChanged = function () {
        if (this.isNative) {
            var enabled = !!this._command.getEnabled();
            var checked = !!this._command.getChecked();
            brackets.app.setMenuItemState(this._command.getID(), enabled, checked, function (err) {
                if (err) {
                    console.log("Error setting menu item state: " + err);
                }
            });
        } else {
            ViewUtils.toggleClass($(_getHTMLMenuItem(this.id)), "disabled", !this._command.getEnabled());
        }
    };
Private

_keyBindingAdded

    MenuItem.prototype._keyBindingAdded = function (event, keyBinding) {
        if (this.isNative) {
            var shortcutKey = keyBinding.displayKey || keyBinding.key;
            brackets.app.setMenuItemShortcut(this._command.getID(), shortcutKey, KeyBindingManager.formatKeyDescriptor(shortcutKey), function (err) {
                if (err) {
                    console.error("Error setting menu item shortcut: " + err);
                }
            });
        } else {
            _addKeyBindingToMenuItem($(_getHTMLMenuItem(this.id)), keyBinding.key, keyBinding.displayKey);
        }
    };
Private

_keyBindingRemoved

    MenuItem.prototype._keyBindingRemoved = function (event, keyBinding) {
        if (this.isNative) {
            brackets.app.setMenuItemShortcut(this._command.getID(), "", "", function (err) {
                if (err) {
                    console.error("Error setting menu item shortcut: " + err);
                }
            });
        } else {
            var $shortcut = $(_getHTMLMenuItem(this.id)).find(".menu-shortcut");

            if ($shortcut.length > 0 && $shortcut.data("key") === keyBinding.key) {
                // check for any other bindings
                if (_addExistingKeyBinding(this) === null) {
                    $shortcut.empty();
                }
            }
        }
    };
Private

_nameChanged

Synchronizes MenuItem name with underlying Command name

    MenuItem.prototype._nameChanged = function () {
        if (this.isNative) {
            brackets.app.setMenuTitle(this._command.getID(), this._command.getName(), function (err) {
                if (err) {
                    console.log("Error setting menu title: " + err);
                }
            });
        } else {
            $(_getHTMLMenuItem(this.id)).find(".menu-name").text(this._command.getName());
        }
    };

getCommand

Gets the Command associated with a MenuItem

Returns: Command
    MenuItem.prototype.getCommand = function () {
        return this._command;
    };

getParentMenu

Returns the parent Menu for this MenuItem

Returns: Menu
    MenuItem.prototype.getParentMenu = function () {
        var parent = $(_getHTMLMenuItem(this.id)).parents(".dropdown").get(0);
        if (!parent) {
            return null;
        }

        return getMenu(parent.id);
    };
Constructor

Menu

Menu represents a top-level menu in the menu bar. A Menu may correspond to an HTML-based menu or a native menu if Brackets is running in a native application shell.

Since menus may have a native implementation clients should create Menus through addMenu() and should NOT construct a Menu object directly. Clients should also not access HTML content of a menu directly and instead use the Menu API to query and modify menus.

id string
    function Menu(id) {
        this.id = id;
    }

    Menu.prototype._getMenuItemId = function (commandId) {
        return (this.id + "-" + commandId);
    };

Methods

Private

_getMenuItemForCommand

Determine MenuItem in this Menu, that has the specified command

command Command
the command to search for.
Returns: ?HTMLLIElement
menu item list element
    Menu.prototype._getMenuItemForCommand = function (command) {
        if (!command) {
            return null;
        }
        var foundMenuItem = menuItemMap[this._getMenuItemId(command.getID())];
        if (!foundMenuItem) {
            return null;
        }
        return $(_getHTMLMenuItem(foundMenuItem.id)).closest("li");
    };
Private

_getRelativeMenuItem

Determine relative MenuItem

relativeID nullable string
id of command (future: sub-menu).
position nullable string
only needed when relativeID is a MenuSection
Returns: ?HTMLLIElement
menu item list element
    Menu.prototype._getRelativeMenuItem = function (relativeID, position) {
        var $relativeElement;

        if (relativeID) {
            if (position === FIRST_IN_SECTION || position === LAST_IN_SECTION) {
                if (!relativeID.hasOwnProperty("sectionMarker")) {
                    console.error("Bad Parameter in _getRelativeMenuItem(): relativeID must be a MenuSection when position refers to a menu section");
                    return null;
                }

                // Determine the $relativeElement by traversing the sibling list and
                // stop at the first divider found
                // TODO: simplify using nextUntil()/prevUntil()
                var $sectionMarker = this._getMenuItemForCommand(CommandManager.get(relativeID.sectionMarker));
                if (!$sectionMarker) {
                    console.error("_getRelativeMenuItem(): MenuSection " + relativeID.sectionMarker +
                                  " not found in Menu " + this.id);
                    return null;
                }
                var $listElem = $sectionMarker;
                $relativeElement = $listElem;
                while (true) {
                    $listElem = (position === FIRST_IN_SECTION ? $listElem.prev() : $listElem.next());
                    if ($listElem.length === 0) {
                        break;
                    } else if ($listElem.find(".divider").length > 0) {
                        break;
                    } else {
                        $relativeElement = $listElem;
                    }
                }

            } else {
                if (relativeID.hasOwnProperty("sectionMarker")) {
                    console.error("Bad Parameter in _getRelativeMenuItem(): if relativeID is a MenuSection, position must be FIRST_IN_SECTION or LAST_IN_SECTION");
                    return null;
                }

                // handle FIRST, LAST, BEFORE, & AFTER
                var command = CommandManager.get(relativeID);
                if (command) {
                    // Lookup Command for this Command id
                    // Find MenuItem that has this command
                    $relativeElement = this._getMenuItemForCommand(command);
                }
                if (!$relativeElement) {
                    console.error("_getRelativeMenuItem(): MenuItem with Command id " + relativeID +
                                  " not found in Menu " + this.id);
                    return null;
                }
            }

            return $relativeElement;

        } else if (position && position !== FIRST && position !== LAST) {
            console.error("Bad Parameter in _getRelativeMenuItem(): relative position specified with no relativeID");
            return null;
        }

        return $relativeElement;
    };

addMenuDivider

Inserts divider item in menu.

position nullable string
constant defining the position of new the divider relative to other MenuItems. Default is LAST. (see Insertion position constants).
relativeID nullable string
id of menuItem, sub-menu, or menu section that the new divider will be positioned relative to. Required for all position constants except FIRST and LAST
Returns: MenuItem
the newly created divider
    Menu.prototype.addMenuDivider = function (position, relativeID) {
        return this.addMenuItem(DIVIDER, "", position, relativeID);
    };

addMenuItem

Adds a new menu item with the specified id and display text. The insertion position is specified via the relativeID and position arguments which describe a position relative to another MenuItem or MenuGroup. It is preferred that plug-ins insert new MenuItems relative to a menu section rather than a specific MenuItem (see Menu Section Constants).

TODO: Sub-menus are not yet supported, but when they are implemented this API will allow adding new MenuItems to sub-menus as well.

Note, keyBindings are bound to Command objects not MenuItems. The provided keyBindings will be bound to the supplied Command object rather than the MenuItem.

command non-nullable string, Command
the command the menu will execute. Pass Menus.DIVIDER for a menu divider, or just call addMenuDivider() instead.
keyBindings nullable string, Array.<{key: string, platform: string}>
register one one or more key bindings to associate with the supplied command.
position nullable string
constant defining the position of new MenuItem relative to other MenuItems. Values: - With no relativeID, use Menus.FIRST or LAST (default is LAST) - Relative to a command id, use BEFORE or AFTER (required) - Relative to a MenuSection, use FIRST_IN_SECTION or LAST_IN_SECTION (required)
relativeID nullable string
command id OR one of the MenuSection.* constants. Required for all position constants except FIRST and LAST.
Returns: MenuItem
the newly created MenuItem
    Menu.prototype.addMenuItem = function (command, keyBindings, position, relativeID) {
        var menuID = this.id,
            id,
            $menuItem,
            menuItem,
            name,
            commandID;

        if (!command) {
            console.error("addMenuItem(): missing required parameters: command");
            return null;
        }

        if (typeof (command) === "string") {
            if (command === DIVIDER) {
                name = DIVIDER;
                commandID = _getNextMenuItemDividerID();
            } else {
                commandID = command;
                command = CommandManager.get(commandID);
                if (!command) {
                    console.error("addMenuItem(): commandID not found: " + commandID);
                    return null;
                }
                name = command.getName();
            }
        } else {
            commandID = command.getID();
            name = command.getName();
        }

        // Internal id is the a composite of the parent menu id and the command id.
        id = this._getMenuItemId(commandID);

        if (menuItemMap[id]) {
            console.log("MenuItem added with same id of existing MenuItem: " + id);
            return null;
        }

        // create MenuItem
        menuItem = new MenuItem(id, command);
        menuItemMap[id] = menuItem;

        // create MenuItem DOM
        if (_isHTMLMenu(this.id)) {
            if (name === DIVIDER) {
                $menuItem = $("<li><hr class='divider' id='" + id + "' /></li>");
            } else {
                // Create the HTML Menu
                $menuItem = $("<li><a href='#' id='" + id + "'> <span class='menu-name'></span></a></li>");

                $menuItem.on("click", function () {
                    menuItem._command.execute();
                });

                var self = this;
                $menuItem.on("mouseenter", function () {
                    self.closeSubMenu();
                });
            }

            // Insert menu item
            var $relativeElement = this._getRelativeMenuItem(relativeID, position);
            _insertInList($("li#" + StringUtils.jQueryIdEscape(this.id) + " > ul.dropdown-menu"),
                          $menuItem, position, $relativeElement);
        } else {
            var bindings = KeyBindingManager.getKeyBindings(commandID),
                binding,
                bindingStr = "",
                displayStr = "";

            if (bindings && bindings.length > 0) {
                binding = bindings[bindings.length - 1];
                bindingStr = binding.displayKey || binding.key;
            }

            if (bindingStr.length > 0) {
                displayStr = KeyBindingManager.formatKeyDescriptor(bindingStr);
            }

            if (position === FIRST_IN_SECTION || position === LAST_IN_SECTION) {
                if (!relativeID.hasOwnProperty("sectionMarker")) {
                    console.error("Bad Parameter in _getRelativeMenuItem(): relativeID must be a MenuSection when position refers to a menu section");
                    return null;
                }

                // For sections, pass in the marker for that section.
                relativeID = relativeID.sectionMarker;
            }

            brackets.app.addMenuItem(this.id, name, commandID, bindingStr, displayStr, position, relativeID, function (err) {
                switch (err) {
                case NO_ERROR:
                    break;
                case ERR_INVALID_PARAMS:
                    console.error("addMenuItem(): Invalid Parameters when adding the command " + commandID);
                    break;
                case ERR_NOT_FOUND:
                    console.error("_getRelativeMenuItem(): MenuItem with Command id " + relativeID + " not found in the Menu " + menuID);
                    break;
                default:
                    console.error("addMenuItem(); Unknown Error (" + err + ") when adding the command " + commandID);
                }
            });
            menuItem.isNative = true;
        }

        // Initialize MenuItem state
        if (menuItem.isDivider) {
            menuItem.dividerId = commandID;
        } else {
            if (keyBindings) {
                // Add key bindings. The MenuItem listens to the Command object to update MenuItem DOM with shortcuts.
                if (!Array.isArray(keyBindings)) {
                    keyBindings = [keyBindings];
                }
            }

            // Note that keyBindings passed during MenuItem creation take precedent over any existing key bindings
            KeyBindingManager.addBinding(commandID, keyBindings);

            // Look for existing key bindings
            _addExistingKeyBinding(menuItem);

            menuItem._checkedChanged();
            menuItem._enabledChanged();
            menuItem._nameChanged();
        }

        return menuItem;
    };

addSubMenu

Creates a new submenu and a menuItem and adds the menuItem of the submenu to the menu and returns the submenu.

A submenu will have the same structure of a menu with a additional field parentMenuItem which has the reference of the submenu's parent menuItem.

A submenu will raise the following events:

  • beforeSubMenuOpen
  • beforeSubMenuClose

Note, This function will create only a context submenu.

TODO: Make this function work for Menus

name non-nullable string
displayed in menu item of the submenu
id non-nullable string
position nullable string
constant defining the position of new MenuItem of the submenu relative to other MenuItems. Values: - With no relativeID, use Menus.FIRST or LAST (default is LAST) - Relative to a command id, use BEFORE or AFTER (required) - Relative to a MenuSection, use FIRST_IN_SECTION or LAST_IN_SECTION (required)
relativeID nullable string
command id OR one of the MenuSection.* constants. Required for all position constants except FIRST and LAST.
Returns: Menu
the newly created submenu
    Menu.prototype.addSubMenu = function (name, id, position, relativeID) {

        if (!name || !id) {
            console.error("addSubMenu(): missing required parameters: name and id");
            return null;
        }

        // Guard against duplicate context menu ids
        if (contextMenuMap[id]) {
            console.log("Context menu added with id of existing Context Menu: " + id);
            return null;
        }

        var menu = new ContextMenu(id);
        contextMenuMap[id] = menu;

        var menuItemID = this.id + "-" + id;

        if (menuItemMap[menuItemID]) {
            console.log("MenuItem added with same id of existing MenuItem: " + id);
            return null;
        }

        // create MenuItem
        var menuItem = new MenuItem(menuItemID, SUBMENU);
        menuItemMap[menuItemID] = menuItem;

        menu.parentMenuItem = menuItem;

        // create MenuItem DOM
        if (_isHTMLMenu(this.id)) {
            // Create the HTML MenuItem
            var $menuItem = $("<li><a href='#' id='" + menuItemID + "'> "   +
                             "<span class='menu-name'>" + name + "</span>" +
                             "<span style='float: right'>&rtrif;</span>"   +
                             "</a></li>");

            var self = this;
            $menuItem.on("mouseenter", function(e) {
                if (self.openSubMenu && self.openSubMenu.id === menu.id) {
                    return;
                }
                self.closeSubMenu();
                self.openSubMenu = menu;
                menu.open();
            });

            // Insert menu item
            var $relativeElement = this._getRelativeMenuItem(relativeID, position);
            _insertInList($("li#" + StringUtils.jQueryIdEscape(this.id) + " > ul.dropdown-menu"),
            $menuItem, position, $relativeElement);
        } else {
            // TODO: add submenus for native menus
        }
        return menu;
    };

closeSubMenu

Closes the submenu if the menu has a submenu open.

    Menu.prototype.closeSubMenu = function() {
        if (this.openSubMenu) {
            this.openSubMenu.close();
            this.openSubMenu = null;
        }
    };

removeMenuDivider

Removes the specified menu divider from this Menu.

menuItemID non-nullable string
the menu item id of the divider to remove.
    Menu.prototype.removeMenuDivider = function (menuItemID) {
        var menuItem,
            $HTMLMenuItem;

        if (!menuItemID) {
            console.error("removeMenuDivider(): missing required parameters: menuItemID");
            return;
        }

        menuItem = getMenuItem(menuItemID);

        if (!menuItem) {
            console.error("removeMenuDivider(): parameter menuItemID: %s is not a valid menu item id", menuItemID);
            return;
        }

        if (!menuItem.isDivider) {
            console.error("removeMenuDivider(): parameter menuItemID: %s is not a menu divider", menuItemID);
            return;
        }

        if (_isHTMLMenu(this.id)) {
            // Targeting parent to get the menu divider <hr> and the <li> that contains it
            $HTMLMenuItem = $(_getHTMLMenuItem(menuItemID)).parent();
            if ($HTMLMenuItem) {
                $HTMLMenuItem.remove();
            } else {
                console.error("removeMenuDivider(): HTML menu divider not found: %s", menuItemID);
                return;
            }
        } else {
            brackets.app.removeMenuItem(menuItem.dividerId, function (err) {
                if (err) {
                    console.error("removeMenuDivider() -- divider not found: %s (error: %s)", menuItemID, err);
                }
            });
        }

        if (!menuItemMap[menuItemID]) {
            console.error("removeMenuDivider(): menu divider not found in menuItemMap: %s", menuItemID);
            return;
        }

        delete menuItemMap[menuItemID];
    };

removeMenuItem

Removes the specified menu item from this Menu. Key bindings are unaffected; use KeyBindingManager directly to remove key bindings if desired.

command non-nullable string, Command
command the menu would execute if we weren't deleting it.
    Menu.prototype.removeMenuItem = function (command) {
        var menuItemID,
            commandID;

        if (!command) {
            console.error("removeMenuItem(): missing required parameters: command");
            return;
        }

        if (typeof (command) === "string") {
            var commandObj = CommandManager.get(command);
            if (!commandObj) {
                console.error("removeMenuItem(): command not found: " + command);
                return;
            }
            commandID = command;
        } else {
            commandID = command.getID();
        }
        menuItemID = this._getMenuItemId(commandID);

        var menuItem = getMenuItem(menuItemID);
        removeMenuItemEventListeners(menuItem);

        if (_isHTMLMenu(this.id)) {
            // Targeting parent to get the menu item <a> and the <li> that contains it
            $(_getHTMLMenuItem(menuItemID)).parent().remove();
        } else {
            brackets.app.removeMenuItem(commandID, function (err) {
                if (err) {
                    console.error("removeMenuItem() -- command not found: " + commandID + " (error: " + err + ")");
                }
            });
        }

        delete menuItemMap[menuItemID];
    };

removeSubMenu

Removes the specified submenu from this Menu.

Note, this function will only remove context submenus

TODO: Make this function work for Menus

subMenuID non-nullable string
the menu id of the submenu to remove.
    Menu.prototype.removeSubMenu = function (subMenuID) {
        var subMenu,
            parentMenuItem,
            commandID = "";

        if (!subMenuID) {
            console.error("removeSubMenu(): missing required parameters: subMenuID");
            return;
        }

        subMenu = getContextMenu(subMenuID);

        if (!subMenu || !subMenu.parentMenuItem) {
            console.error("removeSubMenu(): parameter subMenuID: %s is not a valid submenu id", subMenuID);
            return;
        }

        parentMenuItem = subMenu.parentMenuItem;


        if (!menuItemMap[parentMenuItem.id]) {
            console.error("removeSubMenu(): parent menuItem not found in menuItemMap: %s", parentMenuItem.id);
            return;
        }

        // Remove all of the menu items in the submenu
        _.forEach(menuItemMap, function (value, key) {
            if (_.startsWith(key, subMenuID)) {
                if (value.isDivider) {
                    subMenu.removeMenuDivider(key);
                } else {
                    commandID = value.getCommand();
                    subMenu.removeMenuItem(commandID);
                }
            }
        });

        if (_isHTMLMenu(this.id)) {
            $(_getHTMLMenuItem(parentMenuItem.id)).parent().remove(); // remove the menu item
            $(_getHTMLMenu(subMenuID)).remove(); // remove the menu
        } else {
            // TODO: remove submenus for native menus
        }


        delete menuItemMap[parentMenuItem.id];
        delete contextMenuMap[subMenuID];
    };
Constructor

ContextMenu (extends Menu)

Represents a context menu that can open at a specific location in the UI.

Clients should not create this object directly and should instead use registerContextMenu() to create new ContextMenu objects.

Context menus in brackets may be HTML-based or native so clients should not reach into the HTML and should instead manipulate ContextMenus through the API.

Events:

  • beforeContextMenuOpen
  • beforeContextMenuClose
    function ContextMenu(id) {
        Menu.apply(this, arguments);

        var $newMenu = $("<li class='dropdown context-menu' id='" + StringUtils.jQueryIdEscape(id) + "'></li>"),
            $popUp = $("<ul class='dropdown-menu'></ul>"),
            $toggle = $("<a href='#' class='dropdown-toggle' data-toggle='dropdown'></a>").hide();

        // assemble the menu fragments
        $newMenu.append($toggle).append($popUp);

        // insert into DOM
        $("#context-menu-bar > ul").append($newMenu);

        var self = this;
        PopUpManager.addPopUp($popUp,
            function () {
                self.close();
            },
            false);

        // Listen to ContextMenu's beforeContextMenuOpen event to first close other popups
        PopUpManager.listenToContextMenu(this);
    }
    ContextMenu.prototype = Object.create(Menu.prototype);
    ContextMenu.prototype.constructor = ContextMenu;
    ContextMenu.prototype.parentClass = Menu.prototype;
    EventDispatcher.makeEventDispatcher(ContextMenu.prototype);

Methods

assignContextMenuToSelector STATIC

Associate a context menu to a DOM element. This static function take care of registering event handlers for the click event listener and passing the right "position" object to the Context#open method

    ContextMenu.assignContextMenuToSelector = function (selector, cmenu) {
        $(selector).on("click", function (e) {
            var buttonOffset,
                buttonHeight;

            e.stopPropagation();

            if (cmenu.isOpen()) {
                cmenu.close();
            } else {
                buttonOffset = $(this).offset();
                buttonHeight = $(this).outerHeight();
                cmenu.open({
                    pageX: buttonOffset.left,
                    pageY: buttonOffset.top + buttonHeight
                });
            }
        });
    };

close

Closes the context menu.

    ContextMenu.prototype.close = function () {
        if (this.parentMenuItem) {
            this.trigger("beforeSubMenuClose");
        } else {
            this.trigger("beforeContextMenuClose");
        }
        this.closeSubMenu();
        $("#" + StringUtils.jQueryIdEscape(this.id)).removeClass("open");
    };

isOpen

Detect if current context menu is already open

    ContextMenu.prototype.isOpen = function () {
        return $("#" + StringUtils.jQueryIdEscape(this.id)).hasClass("open");
    };

open

Displays the ContextMenu at the specified location and dispatches the "beforeContextMenuOpen" event or "beforeSubMenuOpen" event (for submenus). The menu location may be adjusted to prevent clipping by the browser window. All other menus and ContextMenus will be closed before a new menu will be closed before a new menu is shown (if the new menu is not a submenu).

In case of submenus, the parentMenu of the submenu will not be closed when the sub menu is open.

mouseOrLocation MouseEvent,{pageX:number,pageY:number}
pass a MouseEvent to display the menu near the mouse or pass in an object with page x/y coordinates for a specific location.This paramter is not used for submenus. Submenus are always displayed at a position relative to the parent menu.
    ContextMenu.prototype.open = function (mouseOrLocation) {

        if (!this.parentMenuItem &&
           (!mouseOrLocation || !mouseOrLocation.hasOwnProperty("pageX") || !mouseOrLocation.hasOwnProperty("pageY"))) {
            console.error("ContextMenu open(): missing required parameter");
            return;
        }

        var $window = $(window),
            escapedId = StringUtils.jQueryIdEscape(this.id),
            $menuAnchor = $("#" + escapedId),
            $menuWindow = $("#" + escapedId + " > ul"),
            posTop,
            posLeft;

        // only show context menu if it has menu items
        if ($menuWindow.children().length <= 0) {
            return;
        }


        // adjust positioning so menu is not clipped off bottom or right
        if (this.parentMenuItem) { // If context menu is a submenu

            this.trigger("beforeSubMenuOpen");

            var $parentMenuItem = $(_getHTMLMenuItem(this.parentMenuItem.id));

            posTop = $parentMenuItem.offset().top;
            posLeft = $parentMenuItem.offset().left + $parentMenuItem.outerWidth();

            var elementRect = {
                top:    posTop,
                left:   posLeft,
                height: $menuWindow.height() + 25,
                width:  $menuWindow.width()
            },
            clip = ViewUtils.getElementClipSize($window, elementRect);

            if (clip.bottom > 0) {
                posTop = Math.max(0, posTop + $parentMenuItem.height() - $menuWindow.height());
            }

            posTop -= 30;   // shift top for hidden parent element
            posLeft += 3;

            if (clip.right > 0) {
                posLeft = Math.max(0, posLeft - $parentMenuItem.outerWidth() - $menuWindow.outerWidth());
            }
        } else {
            this.trigger("beforeContextMenuOpen");

            // close all other dropdowns
            closeAll();

            posTop  = mouseOrLocation.pageY;
            posLeft = mouseOrLocation.pageX;

            var elementRect = {
                top:    posTop,
                left:   posLeft,
                height: $menuWindow.height() + 25,
                width:  $menuWindow.width()
            },
            clip = ViewUtils.getElementClipSize($window, elementRect);

            if (clip.bottom > 0) {
                posTop = Math.max(0, posTop - clip.bottom);
            }
            posTop -= 30;   // shift top for hidden parent element
            posLeft += 5;


            if (clip.right > 0) {
                posLeft = Math.max(0, posLeft - clip.right);
            }
        }

        // open the context menu at final location
        $menuAnchor.addClass("open")
                   .css({"left": posLeft, "top": posTop});
    };