Adding a Shutdown Dialog to Electron
You may not realize it yet, but there are many occasions where a shutdown dialog can help with UX. For instance, alerting users to important information prior to the app closing. When using the Electron TrayMenu, you could be telling the user, “The app won’t close until you close it from the tray”. Maybe the user needs to manually save something, so you need to ask them, “Did you save your progress?”. Or maybe you simply want to give the users statistics about their session. Regardless of the use-case, we’ll look at how to implement this while making sure it works on all platforms.
If you want to follow along and test the code, I will demonstrate using the Electron API Demos App.
Creating a Shutdown Dialog in the Close Event
We want to show a dialog before closing, but also offer the option to interrupt the close. Electron’s BrowserWindow ‘close’ event is perfect, as it is triggered anytime a user closes the Main Window. It is triggered with a hotkey, close button, and even the menu Exit/Quit menu item.
We listen to the event from our initiated BrowserWindow. For now, we’ll add the logic needed to shutdown the app when the window is closed:
// main.js file
mainWindow.on('close', e => {
mainWindow.destroy()
app.quit()
})
Bonus: If you are using the TrayMenu, you can use this same block of code to close the app completely when the window is closed.
Using ShowMessageBox as a Shutdown Dialog
We still need to add the dialog that pops up during our close event, so let’s put that into place. After we make sure to add the dialog to the Electron import, we will add our dialog box:
const {app, dialog, BrowserWindow} = require('electron') // Line 7 of main.js
...
mainWindow.on('close', e => { // Line 49
e.preventDefault()
dialog.showMessageBox({
type: 'info',
buttons: ['Ok', 'Exit'],
cancelId: 1,
defaultId: 0,
title: 'Warning',
detail: 'Hey, wait! There\'s something you should know...'
}).then(({ response, checkboxChecked }) => {
console.log(`response: ${response}`)
if (response) {
mainWindow.destroy()
app.quit()
}
})
})
When added, start the app, and close the window. As long as you don’t do a Ctrl+C from the CLI, you will get a dialog like this:
Once you see the dialog, you can use Ok to stay in the app, or use Exit to close the app. The Ok option can of course be clicked, or you can simply press Enter. Same with Exit, you can be click it or execute with the Esc hotkey. It should also be noted that the response from Ok is 0, and a 1 from Exit. With that in mind, look at the options, and we’ll see these responses are literally the element indices for the buttons array. When the response is 1, we make sure to shutdown the app completely.
Working on All Platforms
One of the most important lines of this dialog setup is the preventDefault event handler. When we’re closing the window, the default response is to obviously close the window. If you place this after the dialog, you’ll find the dialog works in OSX, but Windows and Linux will see a dialog flash as the app closes. To prevent this, we place this before the dialog, and everything works as intended. This may seem like common sense, and it is, but there are some examples in the wild which have this alternative placement.
Either way, that’s everything you need to set this shutdown dialogs.
MessageBox Nuances
There are a few nuances with the MessageBox, but let me preface this by saying, you can create a default dialog without any options. Neat! Of course that means the MessageBox must have some automatic or default options. As you surely noticed, the cancelId option sets the MessageBox’s Cancel function to Exit, and likewise the defaultId chooses the highlighted button. You will almost never need both of these options, and we can actually remove the cancelId option to get the same results.
Automated cancelIds
Still I wanted to point out a couple special cases with the MessageBox: When the defaultId is set to 0, cancelId is not set, and there is more than one button in the buttons array, MessageBox will automatically set the cancelId to 1. If defaultId isn’t set, cancelId is set to 0. This can cause some confusion in certain cases, so keep that in mind with this next section.
Note: When the defaultId and cancelIds are both the same. the highlight is switched to a gray disabled background color.
Reserved Button Names
Maybe with the context of the dialog message, you want users to “cancel closing the window”. Unfortunately, when you set the name of the button to Cancel or No, MessageBox may make some changes automatically. When the cancelId is not set, it will be automatically set to the ID of this button. If you have both a Cancel and No button, it will be set to the first in the array. Your may also notice the button order change. Cancel and No are automatically placed in the opposing positions. To see this in action, replace our example’s Exit button with Cancel:
The Source of this Logic
Most of these nuances require the cancelId to be null, but in earlier versions of Electron it happened even with the cancelId. To better understand the logic, here is the relevant portion of the dialog api source:
// Choose a default button to get selected when dialog is cancelled.
if (cancelId == null) {
// If the defaultId is set to 0, ensure the cancel button is a different index (1)
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0;
for (let i = 0; i < buttons.length; i++) {
const text = buttons[i].toLowerCase();
if (text === 'cancel' || text === 'no') {
cancelId = i;
break;
}
}
}
If you liked this article or have questions, let me know in the comments! Also, if you are looking for more about Electron, check out my Electron production checklist in Writing Complete Apps in Electron. Happy coding!
Comments: 1
This is super useful, thank you!