node.jselectrondialog

Making a Confirmation in Electron.js


I want to make a message box which contains yes and no buttons in a electron.js app. I tried to do it with dialog inside the electron. But it didn't work:

const electron = require('electron')
const { dialog } = electron
console.log(dialog) // undefined
const electron = require('electron')
const dialog = electron.remote.dialog
console.log(dialog) // Uncaught Error: Cannot read "dialog" of undefined (remote is undefined)

Then, I tried to do it with dialog which is a module in npm. But it didn't do the thing that I want to do. There wasn't any yes or no buttons also it returned the same responses when I clicked OK or I closed window:

const electron = require('electron')
const dialog = require('dialog')
dialog.info('Are you sure?', 'Confirmation', function(exitCode) {
        if (exitCode == 0) {
                // Should clicked OK (always response)
        }
        if (exitCode == 1) {
                // Should closed window (but never works)
        }
})

What did I do wrong?


Solution

  • You will want to use Electron's dialog.showMessageBox(); method.

    The dialog.showMessageBoxSync(); method would block your main process until a response is received, so you won't want to use that unless intended.


    I have placed the creation and management of your dialog box in the main.js file. If you want to move this into its own file, that's not a problem. All you would need to do is get() the (main) window instance if you want your dialog box to be a child of the main window.

    main.js (main process)

    // Import required Electron modules
    const electronApp = require('electron').app;
    const electronBrowserWindow = require('electron').BrowserWindow;
    const electronDialog = require('electron').dialog;
    const electronIpcMain = require('electron').ipcMain;
    
    // Import required Node modules
    const nodePath = require('path');
    
    // Prevent garbage collection
    let window;
    
    function createWindow() {
        const window = new electronBrowserWindow({
            x: 0,
            y: 0,
            width: 800,
            height: 600,
            show: false,
            webPreferences: {
                nodeIntegration: false,
                contextIsolation: true,
                preload: nodePath.join(__dirname, 'preload.js')
            }
        });
    
        window.loadFile('index.html')
            .then(() => { window.show(); });
    
        return window;
    }
    
    electronApp.on('ready', () => {
        window = createWindow();
    });
    
    electronApp.on('window-all-closed', () => {
        if (process.platform !== 'darwin') {
            electronApp.quit();
        }
    });
    
    electronApp.on('activate', () => {
        if (electronBrowserWindow.getAllWindows().length === 0) {
            createWindow();
        }
    });
    
    // ---
    
    electronIpcMain.on('openDialog', () => {
        electronDialog.showMessageBox(window, {
            'type': 'question',
            'title': 'Confirmation',
            'message': "Are you sure?",
            'buttons': [
                'Yes',
                'No'
            ]
        })
            // Dialog returns a promise so let's handle it correctly
            .then((result) => {
                // Bail if the user pressed "No" or escaped (ESC) from the dialog box
                if (result.response !== 0) { return; }
    
                // Testing.
                if (result.response === 0) {
                    console.log('The "Yes" button was pressed (main process)');
                }
    
                // Reply to the render process
                window.webContents.send('dialogResponse', result.response);
            })
    })
    

    For proper communication between processes, we must use Inter-Process Communication.

    preload.js (main process)

    // Import the necessary Electron modules
    const contextBridge = require('electron').contextBridge;
    const ipcRenderer = require('electron').ipcRenderer;
    
    // Exposed protected methods in the render process
    contextBridge.exposeInMainWorld(
        // Allowed 'ipcRenderer' methods
        'ipcRenderer', {
            // From render to main
            openDialog: () => {
                ipcRenderer.send('openDialog');
            },
            // From main to render
            dialogResponse: (response) => {
                ipcRenderer.on('dialogResponse', response);
            }
        }
    );
    

    Finally, your index.html file will listen for a button click. Once clicked, send a message to the main process to open the dialog box.

    Once a valid response is received from the dialog box, the response is sent back to the render process for processing.

    PS: The render method ipcRenderer.invoke() could be used instead of the ipcRenderer.send() method. But if it was, you would need to handle the "No" or escape (ESC) response in the render process.

    index.html (render process)

    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Electron Test</title>
            <meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
        </head>
    
        <body>
            <input type="button" id="openDialog" value="Show Dialog">
    
            <hr>
    
            <div id="response"></div>
        </body>
    
        <script>
            // Open dialog (in main process) when "Show Dialog" button is clicked
            document.getElementById('openDialog').addEventListener('click', () => {
                window.ipcRenderer.openDialog('openDialog');
            })
    
            // Response from main process
            window.ipcRenderer.dialogResponse((event, response) => {
                if (response === 0) {
                    // Perform your render action here
                    document.getElementById('response').innerText = 'The "Yes" button was clicked';
                }
            });
        </script>
    </html>
    

    To use more than 2 buttons in your dialog box(es), in the creation of your dialog box you may want to designate a cancelId and check for all valid return values before actioning anything.