diff --git a/atom/browser/api/lib/menu.coffee b/atom/browser/api/lib/menu.coffee index b9ee44e23f5d..1da0b2acea42 100644 --- a/atom/browser/api/lib/menu.coffee +++ b/atom/browser/api/lib/menu.coffee @@ -113,17 +113,64 @@ Menu.getApplicationMenu = -> applicationMenu Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder +Menu._indexOfItemWithId = (insertedItems, id) -> + for each, index in insertedItems + if id is each.id + return index + -1 + +Menu._itemIndexForPosition = (insertedItems, position) -> + insertIndex = insertedItems.length + + if position + [query, id] = position.split '=' + switch query + when 'before' + insertIndex = Menu._indexOfItemWithId insertedItems, id + when 'after' + insertIndex = Menu._indexOfItemWithId insertedItems, id + unless insertIndex is -1 + insertIndex++ + when 'endof' + insertIndex = Menu._indexOfItemWithId insertedItems, id + if insertIndex is -1 + separatorItem = id: id, type: 'separator' + insertIndex = insertedItems.length + insertedItems.push separatorItem + + insertIndex++ + item = insertedItems[insertIndex] + while (insertIndex < insertedItems.length) and item.type != 'separator' + insertIndex++ + item = insertedItems[insertIndex] + + if insertIndex is -1 + console.warn "Could not position item at position #{position}" + insertIndex = insertedItems.length + + insertIndex + Menu.buildFromTemplate = (template) -> throw new TypeError('Invalid template for Menu') unless Array.isArray template - menu = new Menu + positionedTemplate = [] + insertIndex = 0 + for item in template + position = item.position + if position + insertIndex = Menu._itemIndexForPosition positionedTemplate, position + positionedTemplate.splice insertIndex, 0, item + insertIndex++ + + menu = new Menu + + for item in positionedTemplate throw new TypeError('Invalid template for MenuItem') unless typeof item is 'object' item.submenu = Menu.buildFromTemplate item.submenu if item.submenu? menuItem = new MenuItem(item) menuItem[key] = value for key, value of item when not menuItem[key]? - menu.append menuItem menu diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index ca4cb6b3c586..cac1ab5806b9 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -5,17 +5,54 @@ ### new MenuItem(options) * `options` Object + * `click` Function - Callback when the menu item is clicked + * `selector` String - Call the selector of first responder when clicked (OS X only) + * `type` String - Can be `normal`, `separator`, `submenu`, `checkbox` or `radio` + * `label` String + * `sublabel` String + * `accelerator` [Accelerator](accelerator.md) + * `icon` [NativeImage](native-image.md) + * `enabled` Boolean + * `visible` Boolean + * `checked` Boolean + * `submenu` Menu - Should be specified for `submenu` type menu item, when it's specified the `type: 'submenu'` can be omitted for the menu item + + * `id` String - Unique within a single menu. If defined then it can be used + as a reference to this item by the position attribute. + + * `position` String - This field allows fine-grained definition of the specific + location within a given menu. + + It has the form "[placement]=[id]" where placement is one of `before`, + `after`, or `endof` and `id` is the id of an existing item in the menu. + + - `before` - Inserts this item before the id referenced item. If the + referenced item doesn't exist the item will be inserted at the end of + the menu. + + - `after` - Inserts this item after id referenced item. If the referenced + item doesn't exist the item will be inserted at the end of the menu. + + - `endof` - Inserts this item at the end of the logical group containing + the id referenced item. (Groups are created by separator items). If + the referenced item doesn't exist a new separator group is created with + the given id and this item is inserted after that separator. + + When an item is positioned following unpositioned items are inserted after + it, until a new item is positioned. So if you want to position a group of + menu items in the same location you only need to specify a position for + the first item. \ No newline at end of file diff --git a/spec/api-menu-spec.coffee b/spec/api-menu-spec.coffee index 729795c2a75d..f51d8580cc52 100644 --- a/spec/api-menu-spec.coffee +++ b/spec/api-menu-spec.coffee @@ -10,6 +10,80 @@ describe 'menu module', -> menu = Menu.buildFromTemplate [label: 'text', extra: 'field'] assert.equal menu.items[0].extra, 'field' + describe 'Menu.buildFromTemplate should reorder based on item position specifiers', -> + it 'should position before existing item', -> + menu = Menu.buildFromTemplate [ + {label: '2', id: '2'} + {label: '3', id: '3'} + {label: '1', id: '1', position: 'before=2'} + ] + assert.equal menu.items[0].label, '1' + assert.equal menu.items[1].label, '2' + assert.equal menu.items[2].label, '3' + + it 'should position after existing item', -> + menu = Menu.buildFromTemplate [ + {label: '1', id: '1'} + {label: '3', id: '3'} + {label: '2', id: '2', position: 'after=1'} + ] + assert.equal menu.items[0].label, '1' + assert.equal menu.items[1].label, '2' + assert.equal menu.items[2].label, '3' + + it 'should position at endof existing separator groups', -> + menu = Menu.buildFromTemplate [ + {type: 'separator', id: 'numbers'} + {type: 'separator', id: 'letters'} + {label: 'a', id: 'a', position: 'endof=letters'} + {label: '1', id: '1', position: 'endof=numbers'} + {label: 'b', id: 'b', position: 'endof=letters'} + {label: '2', id: '2', position: 'endof=numbers'} + {label: 'c', id: 'c', position: 'endof=letters'} + {label: '3', id: '3', position: 'endof=numbers'} + ] + assert.equal menu.items[0].id, 'numbers' + assert.equal menu.items[1].label, '1' + assert.equal menu.items[2].label, '2' + assert.equal menu.items[3].label, '3' + assert.equal menu.items[4].id, 'letters' + assert.equal menu.items[5].label, 'a' + assert.equal menu.items[6].label, 'b' + assert.equal menu.items[7].label, 'c' + + it 'should create separator group if endof does not reference existing separator group', -> + menu = Menu.buildFromTemplate [ + {label: 'a', id: 'a', position: 'endof=letters'} + {label: '1', id: '1', position: 'endof=numbers'} + {label: 'b', id: 'b', position: 'endof=letters'} + {label: '2', id: '2', position: 'endof=numbers'} + {label: 'c', id: 'c', position: 'endof=letters'} + {label: '3', id: '3', position: 'endof=numbers'} + ] + + assert.equal menu.items[0].id, 'letters' + assert.equal menu.items[1].label, 'a' + assert.equal menu.items[2].label, 'b' + assert.equal menu.items[3].label, 'c' + assert.equal menu.items[4].id, 'numbers' + assert.equal menu.items[5].label, '1' + assert.equal menu.items[6].label, '2' + assert.equal menu.items[7].label, '3' + + it 'should continue inserting items at next index when no specifier is present', -> + menu = Menu.buildFromTemplate [ + {label: '4', id: '4'} + {label: '5', id: '5'} + {label: '1', id: '1', position: 'before=4'} + {label: '2', id: '2'} + {label: '3', id: '3'} + ] + assert.equal menu.items[0].label, '1' + assert.equal menu.items[1].label, '2' + assert.equal menu.items[2].label, '3' + assert.equal menu.items[3].label, '4' + assert.equal menu.items[4].label, '5' + describe 'Menu.insert', -> it 'should store item in @items by its index', -> menu = Menu.buildFromTemplate [