Integrating with the application menu

In this section, we are going to provide support for the application menu. For the sake of simplicity, let's integrate the Open and Save features for now and extend the menu as we introduce new features to the application:

You can find out more about how to work with menus in Chapter 2, Building a Markdown Editor. Examples are also provided.
  1. In the project root folder, create a menu.js file with the following content:
const { Menu, BrowserWindow, dialog } = require('electron');
const fs = require('fs');

module.exports = Menu.buildFromTemplate([
{
label: 'File',
submenu: [
{
label: 'Open',
accelerator: 'CommandOrControl+O',
click() {
loadFile();
}
},
{
label: 'Save',
accelerator: 'CommandOrControl+S',
click() {
BrowserWindow.getFocusedWindow().webContents.send('commands', {
command: 'file.save'
});
}
}
]
}
]);

As you can see, we're declaring a File menu (the application menu on macOS) with Open and Save entries. Each menu item does nothing except send a JSON payload to the client-side code.

To make processing easy and universal, we've introduced a convention—each payload has the command parameter, along with the enclosed key of the action the client needs to execute. To open files, we're using the file.open key, while to save files, we're using the file.save key.

Later, we can introduce even more commands. The command handlers at the client side won't require rewrites or significant refactoring.
  1. Add the loadFile function implementation, as shown in the following code:
function loadFile() {
const window = BrowserWindow.getFocusedWindow();
const options = {
title: 'Pick a markdown file',
filters: [{ name: 'Markdown files', extensions: ['md'] }]
};
dialog.showOpenDialog(window, options, paths => {
if (paths && paths.length > 0) {
const content = fs.readFileSync(paths[0]).toString();
window.webContents.send('commands', {
command: 'file.open',
value: content
});
}
});
}

We invoke the native Open Dialog, read the file, and send it back to the client-side code with the file.open command and a value property.

We need a native dialog here because modern browsers don't allow us to invoke file-related operations when the code isn't related to user interaction. Unfortunately, sending a message from the Node.js process to the Chrome-based process isn't going to work for the input type=file elements due to security reasons. This is why we use native code in the main process and provide the renderer process with the result.

  1. To enable the application menu that we have just declared in the menu.js file, we need to update the main.js file. Import Menu from the electron namespace and use it to build the custom menu instance, as shown in the following code:
const { app, BrowserWindow, Menu } = require('electron');
const menu = require('./menu');

Menu.setApplicationMenu(menu);

function createWindow() {
// ...
}

app.on('ready', createWindow);
  1. At the client side, we need to implement a universal command handler function. Update the Editor.js file and add the handleCommand function, as shown in the following code:
const handleCommand = payload => {
if (payload) {
switch (payload.command) {
case 'file.open':
setCode(payload.value || '');
break;
case 'file.save':
saveFile(code);
break;
default:
break;
}
}
};

The preceding code should be easy to understand. Upon using the file.open command, we get payload.value and pass it to the setCode hook. Also, once the file.save command arrives, we invoke the saveFile function.

  1. Now, we need to update the Editor component function so that we can handle the commands. I suggest adding some safety checks to make the code compatible with both browsers and Electron applications. Append the following function after the handleCommand one:
if (window.require) {
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;

ipcRenderer.on('commands', (_, args) => handleCommand(args));
}

The preceding code is an excellent example of cross-application compatibility. Upon running the code in the Electron shell, the code finds the window.require object and performs additional configuration. If you ever decide to run your application with regular browsers, where window.require is missing, the code isn't going to break.

  1. Run the Electron shell with the npm run electron command and check out the application menu:

  1. Click the Open menu item and check that it loads markdown files into the editor correctly.

Congratulations on reaching the end of this section. Now, you are able to send commands from the application menu back to the client-side code.

Now, let's learn how to generate a digital book out of the markdown's content.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset