Skip to content

electron js – cannot get button to perform simple actions from click

Long story short I am working on a single page application that sends commands over a local network. Testing out Electron JS and I can’t even seem to get a simple button to work. I feel like I am not linking the logic between main.js and index.js somehow but for the life of me I cannot figure out the correct way to do it. I have even put breakpoints in index.js and through main.js & index.html but none of the breakpoints are hit aside from the ones in main.js. I put a simple function in a preload.js file and that function is correctly called but the one I am trying to attach to a button located in index.html and index.js is never even being hit. A lot of the commented out code is things I want to remember or things I have noticed a different method of creating and just wanted to try and see if that worked. If anyone has any answers or guidance it would be greatly appreciated! 😀

Below is my main.js

//#region ---for dev only | hot reload
try {
    require('electron-reloader')(module)
} catch (_) {}
//#endregion
const electron = require('electron');
const {app, BrowserWindow, Menu} = require('electron');
const path = require('path');
const ipcMain = electron.ipcMain;

//#region globals
const SRC_DIR = '/src/'
const IMG_DIR = '/assets/images'
//#endregion

function createWindow () { 
    const win = new BrowserWindow({
        width: 800,
        height: 600,                
        //frame: false,
        webPreferences: {  
            contextIsolation: true,          
            preload: path.join(__dirname, 'preload.js')
        }
    });

    //Used to auto open dev tools for debugging
    //win.openDevTools();    
    
    win.loadFile('src/index.html');
    // win.loadURL(url.format({
    //     pathname: path.join(__dirname, 'index.html'),
    //     protocol: 'file',
    //     slashes: true
    // }));
}

app.whenReady().then(() => {    
    //nativeTheme.shouldUseDarkColors = true;
    createWindow();
})

//closes app processes when window is closed 
app.on('window-all-closed', function () {
    if (process.platform !== 'darwin') app.quit();
})

var menu = Menu.buildFromTemplate([
    {
        label: 'Menu',
        submenu: [
            {label: 'Edit'},
            {type: 'separator'},
            {
                label: 'Exit',
                click() {
                    app.quit();
                }
            }
        ]
    }
])

Menu.setApplicationMenu(menu);

Here is index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">    
    <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
    <title>Ecas Software</title>
    <link rel="stylesheet" href="index.css">
  </head>
  <body>
    <p id="myText">Let's get started :)</p>    
    <button id="myBtn">Change Text</button>
    <script type="text/javascript" src="./index.js" ></script>    
  </body>
</html>  

Lastly here is my index.js (aka my first and only renderer?)

const electron = require('electron');

const chgBtn = document.getElementById('myBtn');

function replaceText(selector, text){
    const element = document.getElementById(selector);
    if (element) element.innerText = text;
}    

chgBtn.onclick = function() {
    replaceText('myText', 'no boom...');
}

// chgBtn.addEventListener('click', function(){
//     // if (document.getElementById('myText').innerText == 'boom'){
//     //     replaceText('myText','no boom...');    
//     // } else {
//     //     replaceText('myText','boom');    
//     // }    
//     document.alert("working function");
// });


//chgBtn.addEventListener('click', replaceText('myText','no boom...'));

Answer

Why you have this error

The problem here is that you didn’t use your scripts files the way Electron was intended.

If you use the Devtools Console (by uncommenting win.openDevTools()), you should see this error in your console :

Uncaught ReferenceError: require is not defined (from index.js file)

This is because your index.js file is loaded as a “normal javascript file”. If you want to use the Node syntaxe (aka the “require” syntaxe), you need to do it in your preload script. Only the preload script can use the require syntaxe, since it is the only script allowed by Electron to use Node.

You can also use other javascripts files, by import it in your HTML as you did for the index.js file, but you should remove the require call. As the “require” call (on the first line) will throw and error, all the following code will not run. This is why your button did not react on click.

The correct way to do it

If you need to use some methods from the Electron Renderer API (such as the ipcRenderer), you need to put it in your preload script.

If you want to use your own script, in a separate file, you can also do it, you will not be able to directly call Electron API. There is a solution if you want to call the Electron API in your own script, it is called the Context Bridge. This allows you to create an object in your preload script, that can use the Electron API. You can give this object a name, and then call it from your others script by using the window global object.

For example, if you want to use ipcRenderer.send(channel, payload) :

// Preload script
const { contextBridge, ipcRenderer } = require('electron')

contextBridge.exposeInMainWorld('theNameYouWant',
    {
        send: (channel, payload) => ipcRenderer.send(channel, payload)
    }
)

// index.js file, imported in your HTML file

window.theNameYouWant.send("channel-name", { someData: "Hello" })

In your example

// Add this in your main.js file to see when a user click on the button from main process
ipcMain.on("button-clicked", (event, data) => console.log(data))
// Preload script
const { contextBridge, ipcRenderer } = require("electron")

contextBridge.exposeInMainWorld("electron", {
    send: (channel, payload) => ipcRenderer.send(channel, payload),
})
// index.js
const chgBtn = document.getElementById("myBtn")

function replaceText(selector, text) {
    const element = document.getElementById(selector)
    if (element) element.innerText = text
}

chgBtn.onclick = function () {
    replaceText("myText", "no boom...")
    window.electron.send("button-clicked", { someData: "Hello" })
}