Add position attribute for menu items

This commit adds a position attribute for menu items defined in menu
templates. When the final menu is built the position attribute is used
to determine menu item positions in a similar design to how Eclipse
positions menu items.
This commit is contained in:
Jesse Grosjean 2015-04-07 11:14:28 -04:00
parent 3ae604ca4e
commit c8a3c14a8c
3 changed files with 160 additions and 2 deletions

View file

@ -113,17 +113,64 @@ Menu.getApplicationMenu = -> applicationMenu
Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder 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) -> Menu.buildFromTemplate = (template) ->
throw new TypeError('Invalid template for Menu') unless Array.isArray template throw new TypeError('Invalid template for Menu') unless Array.isArray template
menu = new Menu positionedTemplate = []
insertIndex = 0
for item in template 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' throw new TypeError('Invalid template for MenuItem') unless typeof item is 'object'
item.submenu = Menu.buildFromTemplate item.submenu if item.submenu? item.submenu = Menu.buildFromTemplate item.submenu if item.submenu?
menuItem = new MenuItem(item) menuItem = new MenuItem(item)
menuItem[key] = value for key, value of item when not menuItem[key]? menuItem[key] = value for key, value of item when not menuItem[key]?
menu.append menuItem menu.append menuItem
menu menu

View file

@ -5,17 +5,54 @@
### new MenuItem(options) ### new MenuItem(options)
* `options` Object * `options` Object
* `click` Function - Callback when the menu item is clicked * `click` Function - Callback when the menu item is clicked
* `selector` String - Call the selector of first responder when clicked (OS * `selector` String - Call the selector of first responder when clicked (OS
X only) X only)
* `type` String - Can be `normal`, `separator`, `submenu`, `checkbox` or * `type` String - Can be `normal`, `separator`, `submenu`, `checkbox` or
`radio` `radio`
* `label` String * `label` String
* `sublabel` String * `sublabel` String
* `accelerator` [Accelerator](accelerator.md) * `accelerator` [Accelerator](accelerator.md)
* `icon` [NativeImage](native-image.md) * `icon` [NativeImage](native-image.md)
* `enabled` Boolean * `enabled` Boolean
* `visible` Boolean * `visible` Boolean
* `checked` Boolean * `checked` Boolean
* `submenu` Menu - Should be specified for `submenu` type menu item, when * `submenu` Menu - Should be specified for `submenu` type menu item, when
it's specified the `type: 'submenu'` can be omitted for the menu item 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.

View file

@ -10,6 +10,80 @@ describe 'menu module', ->
menu = Menu.buildFromTemplate [label: 'text', extra: 'field'] menu = Menu.buildFromTemplate [label: 'text', extra: 'field']
assert.equal menu.items[0].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', -> describe 'Menu.insert', ->
it 'should store item in @items by its index', -> it 'should store item in @items by its index', ->
menu = Menu.buildFromTemplate [ menu = Menu.buildFromTemplate [