Is there a way to specify menu item order when inserting menu items?


#1

When I use this to add a new item to the file menu:

'menu': [
  {
    label: 'File'
    submenu: [
      { label: 'My New Item', command: 'mynewcommand' }
    ]
  }
]

It gets added to the very end of the list of File menu items. Is it possible to at it more precisely? For example I’d really like to add it right after “New File”?

Jesse


#2

I think this needs some sort of solution. If it’s not already on the roadmap I’ll try to implement it, but I’m looking for feedback first. I only want to spend time implementing if it’s probable that my changes will get merged into Atom. Thoughts, feedback?


#3

I’ve created a solution for Atom shell here:

Hopefully that will be accepted, then the next step will be defining ids for Atom’s existing menu items. And then a few changes in menu-helpers to make sure id and position attributes are merged into the template. And then I think you’ll be able to include position attributes for your menu items like this:

'menu': [
  {
    label: 'File'
    submenu: [
      { label: 'New Outline', command: 'birch-outline-editor:new-outline' position: 'after=new-file'}
    ]
  }
]

In this case the “New Outline” item would be inserted after “New File” instead of just dumped onto the end of the “File” menu. Feedback still welcome.


#4

Making progress… https://github.com/atom/atom/issues/6270 is now incorporated into Atom Shell/Electron. Now to be able to use those optional menu position features the Atom menu templates need to include ids for each menu item. Any thoughts on what conventions I should use to generate these IDs?

Here’s a first thought:

  {
    id: 'file'
    label: 'File'
    submenu: [
      { id: 'new.window', label: 'New Window', command: 'application:new-window' }
      { id: 'new.file', label: 'New File', command: 'application:new-file' }
      { id: 'open', label: 'Open...', command: 'application:open' }
      { id: 'add.project.folder', label: 'Add Project Folder...', command: 'application:add-project-folder' }
      { id: 'reopen.last.item', label: 'Reopen Last Item', command: 'pane:reopen-closed-item' }
      { id: 'save.group', type: 'separator' }
      { id: 'save', label: 'Save', command: 'core:save' }
      { id: 'save.as', label: 'Save As...', command: 'core:save-as' }
      { id: 'save.all', label: 'Save All', command: 'window:save-all' }
      { id: 'close.group', type: 'separator' }
      { id: 'close.tab', label: 'Close Tab', command: 'core:close' }
      { id: 'close.pane', label: 'Close Pane', command: 'pane:close' }
      { id: 'close.window', label: 'Close Window', command: 'window:close' }
    ]
  }
  1. Take menu label, lowercase it and replace spaces with ‘.’
  2. For separators make up relevant name and then add ‘.group’

Seem OK?

Jesse


#5

What about using hyphens instead? I think that would be more consistent with other Atom naming conventions.


#6

Right… to better mirror command names…

That bring up a new questions: How unique and stable are command names? We could remove a lot of duplication by using “command” as a backup menu “id”. So for menu items whose command names are unique in the menu and expected to not change we wouldn’t need to define an id:

  {
    id: 'file'
    label: 'File'
    submenu: [
      { label: 'New Window', command: 'application:new-window' }
      { label: 'New File', command: 'application:new-file' }
      { label: 'Open...', command: 'application:open' }
      { label: 'Add Project Folder...', command: 'application:add-project-folder' }
      { label: 'Reopen Last Item', command: 'pane:reopen-closed-item' }
      { id='save-group', type: 'separator' }
      { label: 'Save', command: 'core:save' }
      { label: 'Save As...', command: 'core:save-as' }
      { label: 'Save All', command: 'window:save-all' }
      { id = 'close-group', type: 'separator' }
      { label: 'Close Tab', command: 'core:close' }
      { label: 'Close Pane', command: 'pane:close' }
      { label: 'Close Window', command: 'window:close' }
    ]
  }

Better?


#7

Oh that’s a good point! As far as I know, command names must be unique, so they already serve as an “id” of sorts. I think they’re mostly stable, but as long as we’re in pre-Atom 1.0 they could always change. :wink:

I think it would probably be helpful too to throw an error if a menu entry tries to refer to an id / command that doesn’t exist (rather than failing silently).


#8

In Atom Shell where I’ve implemented the actual insert logic I console.warn when a id is referred to but can’t be found. And then use the default insertion logic as if no position was specified.

I think this is better then a full exception, because I don’t think menu items shifting around should ever really break anything.


#9

Ah, good call. That seems like a better way to go.


#10

Ok, unless I get more feedback I’ll:

  1. Add ids for separators in Atoms core menu templates.
  2. Update menu-manager.coffee and context-menu-manager.coffee to set item.id to item.command if item.id doesn’t already exist.

#11

Although this would be a lot more work, I sort of think that instead of separators I’d rather have named groups (which would automatically create separators). So something like:

{
  id: 'file'
  label: 'File'
  submenu: [
    { label: 'New Window', command: 'application:new-window' }
    { label: 'New File', command: 'application:new-file' }
    { label: 'Open...', command: 'application:open' }
    { label: 'Add Project Folder...', command: 'application:add-project-folder' }
    { label: 'Reopen Last Item', command: 'pane:reopen-closed-item' }
    { 'save-group': [
      { label: 'Save', command: 'core:save' }
      { label: 'Save As...', command: 'core:save-as' }
      { label: 'Save All', command: 'window:save-all' }
    ]}
    { 'close-group': [
      { label: 'Close Tab', command: 'core:close' }
      { label: 'Close Pane', command: 'pane:close' }
      { label: 'Close Window', command: 'window:close' }
    ]}
  ]
}

But I’m not 100% sure on this either. It is a bit less explicit than writing separator objects.


#12

I agree that’s a little nicer to my eyes too, but it’s a more fundamental change to the menu template format. @kevinsawicki @zcbenz have any thoughts on this?

Jesse


#13

I wonder: If I have two different menu items that invoke the same command, then they always have the same behavior, right? If that’s true, then the only use case for id is that we want two different menu items that do the same thing.

Well, the other only use case is separators. Why does a separator need an id, though? Oh, right, it needs an id so that I can address it when I want to say I want to add a menu item after it. But it feels so wrong to provide an id to a separator!

How about allowing users to insert a menu item before or after an existing menu item? That should allow folks to insert menu items on the right side of the separator, without having to mention separators at all. It also allows folks to add a menu item to the beginning or the end of the menu, which would not be possible otherwise.

Thoughts?


#14

This part is already possible with the existing design. Assuming you wanted to insert an item after “New File” (also assuming that if id isn’t declared we use command for id) then:

'menu': [
  {
    id: 'file'
    submenu: [
      { label: 'My New Item', command: 'mynewcommand', position: 'after=application:new-file' }
    ]
  }
]

It’s true that names groups is mostly a convenience. You can already insert your item anywhere you want with just after/before insertions. With that said separator groups still seem useful to me:

  1. They feel more stable: “Insert my item into this group” vrs saying “Insert my item after this item”.

  2. Group names are likely to be a bit shorter then the names of specific items, so you’ll have a short position attribute.

  3. I’m copying this design from Eclipse and over time they’ve found it useful to have named groups.

Maybe it would look better like this:

  {
    label: 'File'
    submenu: [
      { label: 'New Window', command: 'application:new-window' }
      { label: 'New File', command: 'application:new-file' }
      { label: 'Open...', command: 'application:open' }
      { label: 'Add Project Folder...', command: 'application:add-project-folder' }
      { label: 'Reopen Last Item', command: 'pane:reopen-closed-item' }
      { group: 'save' }
      { label: 'Save', command: 'core:save' }
      { label: 'Save As...', command: 'core:save-as' }
      { label: 'Save All', command: 'window:save-all' }
      { group: 'close' }
      { label: 'Close Tab', command: 'core:close' }
      { label: 'Close Pane', command: 'pane:close' }
      { label: 'Close Window', command: 'window:close' }
    ]
  }

So instead of creating separators explicitly a separator is instead created by giving a single “group” attribute?

Jesse

And Ha! My son’s name is “Kai Grosjean” :smile:


#15

Thank you so much for pointing this out! You’re totally right, of course. We have a saying in German: I’m older than a cow, yet I still learn something now.

Woot! Many greetings from Kai to Kai.