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.

Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.