4

I have a file that contains a function that opens a file open dialog. I want to call that function from the app's menu. But when I use require to use the function from the file I get an error.

Code:

Main.js:

const { app, BrowserWindow, Menu } = require('electron');
const url = require('url');
const path = require('path');
const { openFile } = require('./index.js');

let win;

function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600,
        icon: __dirname + 'src/styles/media/icon.ico',
        webPreferences: {
            nodeIntegration: true,
            enableRemoteModule: true,
        },
    });
    win.loadURL(
        url.format({
            pathname: path.join(__dirname, 'src/index.html'),
            protocol: 'file:',
            slashes: true,
        })
    );
    var menu = Menu.buildFromTemplate([
        {
            label: 'File',
            submenu: [
                {
                    label: 'Open File',
                    click() {
                        openFile();
                    },
                    accelerator: 'CmdOrCtrl+O',
                }
            ]
        }]);
    Menu.setApplicationMenu(menu);
}

app.on('ready', createWindow);

index.js:

const { dialog } = require('electron').remote;

function openFile() {
    dialog.showOpenDialog({
        title: 'Open File',
        properties: ['openFile'],
    });
}

module.exports = {
    openFile,
};

index.html:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="stylesheet" href="styles/style.css" />
        <title>Stonecutter</title>
    </head>
    <body>
        <script src="./index.js"></script>
    </body>
</html>

Error: error

When I do the same thing but without the required the code works fine:

// index.js
// --------
// This code works
function openFile() {
    console.log('Open file');
}

module.exports = {
    openFile,
};

// Main.js is the same
3174N
  • 188
  • 2
  • 14
  • One beautiful thing about JavaScript is you can often take an actual look at the code of many libraries, or even run in a debugger to see what things are at runtime. In this case, a good look at the `electron` docs would be prudent. – Chris Dec 14 '20 at 19:24

1 Answers1

3

You're using a remote inside the main process. This is what causes the problem. Remote is what you use from Renderer process (scripts required from a BrowserView). So you need to write two different openFile functions for main and renderer processes.

So when you require index.js from main.js this is what cause the error. You need determine where you are in the main process or in the renderer. Watch open-file.js below to see how to do it.

All together it should look like this:

main.js

const { app, BrowserWindow, Menu } = require('electron');
const url = require('url');
const path = require('path');
const {openFile} = require('./open-file')

let win;

function createWindow() {
    win = new BrowserWindow({
        width: 800,
        height: 600,
        icon: __dirname + 'src/styles/media/icon.ico',
        webPreferences: {
            nodeIntegration: true,
            enableRemoteModule: true,
        },
    });
    win.loadURL(
        url.format({
            pathname: path.join(__dirname, 'index.html'),
            protocol: 'file:',
            slashes: true,
        })
    );

  }

app.on('ready', () => {
  createWindow()
  var menu = Menu.buildFromTemplate([
  {
      label: 'File',
      submenu: [
          {
              label: 'Open File',
              click() {
                  openFile();
              },
              accelerator: 'CmdOrCtrl+O',
          }
      ]
  }]);
  Menu.setApplicationMenu(menu);
})

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <link rel="stylesheet" href="styles/style.css" />
        <title>Stonecutter</title>
    </head>
    <body>
      <button id="open-file">Open file</button>
      <script>
        const {openFile} = require('./open-file.js')

        document.querySelector('#open-file').addEventListener('click', () => openFile())
      </script>
    </body>
</html>

open-file.js

const electron = require('electron');

// electron.remote for Renderer Process and electron for Main Process
const {dialog} = (electron.remote || electron)

function openFile() {
    dialog.showOpenDialog({
        title: 'Open File',
        properties: ['openFile'],
    });
}

module.exports = {
    openFile,
};


This examples works as you expect. File open-file.js is what you have in index.js.

This is so because Electron runs its parts in different processes: the first is the Main process. It is where you're getting when running electron main.js. The second is a Renderer process is running in a separate os process. This is where you get calling win.loadURL() and it has slightly different API and set of libraries.

Paul Rumkin
  • 6,737
  • 2
  • 25
  • 35
  • This solution executes `openFile()` immediately. How can I use it to execute `openFile()` from the menu? – 3174N Dec 14 '20 at 20:01
  • This is another question. You can bind a listener to a button. Inside of index.html script do this: `document.querySelector('#open-file').addEventListener('click', () => openFile())`. Of cause you need to add a button in the body element before calling this. – Paul Rumkin Dec 14 '20 at 20:05
  • I updated the example. So as soon as I answered your question and solve the error issue, mark my answer as correct. Thanks! – Paul Rumkin Dec 14 '20 at 20:07
  • I think you understood me incorrectlyץ While your solution works perfectly when pressing the button. I want to open the file when pressing a menu item ([Menu](https://imgur.com/uAjW049)) ([inside `click()`](https://imgur.com/p6GycEZ)), not from a button. – 3174N Dec 14 '20 at 20:10
  • 1
    I've updated the answer: `remote.js` was renamed into `open-file-remote.js` and `open-file.js` was added. The last one is the same code but for the main process. – Paul Rumkin Dec 14 '20 at 20:20
  • 1
    Well I've simplified example code and remove duplicated parts from it to make it easier to understand. Now open file contains code for main and webpage contexts. – Paul Rumkin Dec 14 '20 at 20:29
  • Can I use jQuery with this method? – 3174N Dec 15 '20 at 10:53
  • I'm not sure about jQuery itself. I think it should work in renderer process where DOM is presented. – Paul Rumkin Dec 15 '20 at 13:27