Cross platform menus in Electron js

- Andrés Cruz

En español

Cross platform menus in Electron js

The menu structure is nothing more than an array of objects with a predefined structure; we can define the options in the menu of two types, personalized and with predefined roles as we will present in the following sections.

A classic menu looks like this:

const { Menu } = require('electron')

const template = [
   ***
   {
       label: 'About us',
       submenu: [
           ***
           {
               label: "About the app",
               click() {
                   //console.log("Hello world")
                   // TO DO
               }
           },

       ]
   },
]

Each menu is accompanied by a submenu in which we indicate the menu items, which can be personalized or have predefined roles that we will see later; In short, and as you can see in the previous structure, a menu is made up of an array, therefore, we can place as many items in our menu as we need; in turn, the submenus property is an array so you can indicate as many items as you want.

Returning to the classification or options, we have two:

Custom options

The main idea of the menus is that we can include personalized options to be able to use throughout the application; therefore, they are used as a click event in JavaScript associated with a button (for example) but, in this case, instead of a button, it is an option in a menu:

const { Menu, shell } = require('electron')

const template = [
   {
       role: 'About Electron',
       submenu: [
           {
               label: "About the app",
               click() {
                   shell.openExternal("https://www.electronjs.org")
               }
           },

       ]
   },
]

With the shell, we can open a web page in a link, as if it were a navigation link.

Its use is very simple, and they are the ones we use when we want to use a custom method.

Predefined roles

With the roles we already have specific functions that we can easily reuse:

{ role: 'reload' },
{ role: 'forceReload' },
{ role: 'toggleDevTools' },
{ role: 'resetZoom' },
{ role: 'zoomIn' },
{ role: 'zoomOut' },
{ role: 'togglefullscreen' }

Which allow:

  • reload, Reload the page.
  • forceReload, Force the reload of the page (they are being a reloading host).
  • toggleDevTools, Activate the browser's developer console.
  • resetZoom, Reset the zoom.
  • zoomIn, Zoom In, zoom in.
  • zoomOut, Zoom Out, Zoom out.
  • togglefullscreen, Enables the full screen mode of the application.

We also have the separator to organize the menus:

{
   type:'separator'
},

Finally, a menu looks like the following:

menu
menu

As you can see, since it is a large structure, it is best to define it in a separate file:

menu.js

const { app, ipcMain, Menu, shell, BrowserWindow, globalShortcut } = require('electron')
const { open_file, save_file } = require("./editor-options")
const template = [
   {
       label: 'About us',
       click() {
           shell.openExternal("https://www.electronjs.org")
       }
   },
   {
       label: 'File',
       submenu: [
           {
               label: "Save",
               accelerator: 'CommandOrControl+Shift+S',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editorchannel', 'file-save')
               }
           },
           {
               label: "Open",
               accelerator: 'CommandOrControl+Shift+O',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   open_file(win)
               }
           },
       ]
   },
   {
       label: 'Style and format',
       submenu: [
           {
               label: 'Bold',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editorchannel', 'style-bold')
                   console.log("Negritas")
               }
           },
           {
               label: 'Italic',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editor-channel', 'style-italic')
               }
           },
           {
               type: 'separator'
           },
           {
               label: 'H1',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editor-channel', 'style-h1')
               }
           },
           {
               label: 'H2',
               click() {
                   const win = BrowserWindow.getFocusedWindow()
                   win.webContents.send('editor-channel', 'style-h2')
               }
           },
       ]
   }
]
if (process.platform == 'win32' || process.platform == 'darwin') {
   template.push(
       {
           label: 'Default',
           submenu: [
               { role: 'reload' },
               { role: 'forceReload' },
               { role: 'toggleDevTools' },
               { role: 'resetZoom' },
               { role: 'zoomIn' },
               { role: 'zoomOut' },
               {
                   type: 'separator'
               },
               { role: 'togglefullscreen' },
           ]
       }
   )
}
ipcMain.on('editor-channel', (event, arg) => {
   console.log("Mensaje recibido del canal 'editor-channel': " + arg)
})
ipcMain.on('file-open', (event, arg) => {
   const win = BrowserWindow.getFocusedWindow()
   open_file(win)
})
ipcMain.on('file-save', (event, arg) => {
   const win = BrowserWindow.getFocusedWindow()
   save_file(win, arg)
})
app.on('ready', () => {
   globalShortcut.register('CommandOrControl+Shift+S', () => {
       const win = BrowserWindow.getFocusedWindow()
       win.webContents.send('editor-channel', 'file-save')
   })
   globalShortcut.register('CommandOrControl+Shift+O', () => {
       const win = BrowserWindow.getFocusedWindow()
       open_file(win)
   })
});
const menu = Menu.buildFromTemplate(template);
module.exports = menu

To consume it, we have, from the main file:

const menu = require('./menu')
***
app.whenReady().then(createWindow)
Menu.setApplicationMenu(menu)

And when executing a:

npm run start

We have:

menu
menu

This content is part of both my book on Electron and my Electron course.

I agree to receive announcements of interest about this Blog.

We are going to learn how to handle cross-platform menus in Electron.js.

- Andrés Cruz

En español

)