I'm trying to make my custom title button but it seems like some error has occur-ed. I tried the method from this one but not working with error message.
main.js:
const { app, BrowserWindow } = require('electron');
const path = require('path');
const electronIpcMain = require('electron').ipcMain;
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
// eslint-disable-next-line global-require
if (require('electron-squirrel-startup')) {
app.quit();
}
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 1370,
height: 755,
resizable: false,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
icon: path.join(__dirname, 'res/applogo.png'),
frame: false,
movable: false,
});
// and load the index.html of the app.
mainWindow.loadFile(path.join(__dirname, 'index.html'));
// Open the DevTools.
mainWindow.webContents.openDevTools();
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
// On OS X it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
//set applogo.png to every window
app.on("browser-window-created", (event, window) => {
window.setIcon(path.join(__dirname, 'res/applogo.png'))
});
//win btns
electronIpcMain.on('window:minimize', () => {
window.minimize();
})
electronIpcMain.on('window:maximize', () => {
window.maximize();
})
electronIpcMain.on('window:restore', () => {
window.restore();
})
electronIpcMain.on('window:close', () => {
window.close();
})
preload.js:
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'window:minimize', // Channel names
'window:maximize',
'window:restore',
'window:close'
],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>my app</title>
<link rel="stylesheet" href="index.css" />
</head>
<body>
<div class="title-container">
<img src="res/applogo.png" style="width: 22px; height: 22px;">
<div style="width: 5px; height: 22px;"></div>
<p class="title-text">Phigros Fanmade - Editor - Chart Name</p>
<div class="title-button" id="min-btn">
<img src="res/icons/titleButton/minimize_button.png" style="width: 20px; height: 20px; margin: 1px;">
</div>
<div class="title-button" id="res-btn">
<img src="res/icons/titleButton/restore_button.png" style="width: 20px; height: 20px; margin: 1px;">
</div>
<div class="title-button" id="max-btn">
<img src="res/icons/titleButton/maximize_button.png" style="width: 20px; height: 20px; margin: 1px;">
</div>
<div class="title-button" id="clo-btn">
<img src="res/icons/titleButton/close_button.png" style="width: 20px; height: 20px; margin: 1px;">
</div>
</div>
<div class="application-container">
<!-- ... -->
</div>
<script>
//developer tool open command
document.querySelector("div[data-title='Developer Tool']").addEventListener('click', () => {
window.open('devTool.html', "_blank", "width=1200,height=714,resizable=false,autoHideMenuBar=true,frame=false");
})
</script>
<script src="index.js"></script>
</body>
<script>
//title buttons call commands
document.getElementById('min-btn').addEventListener('click', () => {
window.ipcRender.send('window:minimize');
});
document.getElementById('max-btn').addEventListener('click', () => {
window.ipcRender.send('window:maximize');
});
document.getElementById('res-btn').addEventListener('click', () => {
window.ipcRender.send('window:restore');
});
document.getElementById('clo-btn').addEventListener('click', () => {
window.ipcRender.send('window:close');
});
</script>
</html>
index.js is empty and index.css is just basic stylings.
Error Message:
A JavaScript error occurred in the main process
Uncaught Exception:
ReferenceError: window is not defined
at IpcMainImpl. (C:\Users...\src\main.js:64:3)
at IpcMainImpl.emit (node:events:527:28)
at EventEmitter. (node:electron/js2c/browser_init:161:11014)
at EventEmiiter.emit (node:events:527:28)
Thank you for reading so far, please help me if you're able to. If you need me to provide more info, I'll update it as soon as possible when I see it. Thank you.
The primary problem is the scope of window within your main.js file.
The variable window is used within your electronIpcMain.on() functions, but it is not declared prior. As a result, you receive the window is not defined at error message.
To rectify this, you will need to add let window; before the createWindow() const.
Additionally, I would just declare the createWindow object as a function and not a const. This will simplify things. Additionally, this createWindow() function should return the Electron window object, allowing you to manipulate it further at a late time should you need to do so, like when you are minimizing, maximizing, restoring and closing. Other manipulations may be x, y, width, height, etc.
I have simplified the below main.js file to only include the above stated changes and code needed for a minimum working example.
main.js (main process)
const { app, BrowserWindow } = require('electron');
const path = require('path');
const electronIpcMain = require('electron').ipcMain;
// Declare window in the (file) scope, so it can be accessed by other functions in this file
let window;
function createWindow() {
// This window const is function scoped, therefore not accessible outside this function
const window = new BrowserWindow({
width: 1370,
height: 755,
resizable: false,
autoHideMenuBar: true,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
frame: false,
movable: false,
});
window.loadFile(path.join(__dirname, 'index.html'))
// The loadFile() function returns a promise, so let's use it correctly below
.then(() => {window.webContents.openDevTools();})
// Return this function scoped window const
return window;
}
app.on('ready', () => {
// Assign the returned value of the createWindow() function to this (file) scoped variable.
window = createWindow();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
// Assign the returned value of the createWindow() function to this (file) scoped variable.
window = createWindow();
}
});
app.on('browser-window-created', (event, window) => {
window.setIcon(path.join(__dirname, 'res/applogo.png'));
});
electronIpcMain.on('window:minimize', () => {
// Now we can access the window variable
window.minimize();
})
electronIpcMain.on('window:maximize', () => {
// Now we can access the window variable
window.maximize();
})
electronIpcMain.on('window:restore', () => {
// Now we can access the window variable
window.restore();
})
electronIpcMain.on('window:close', () => {
// Now we can access the window variable
window.close();
})
Your preload.js script remains unchanged.
preload.js (main process)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'window:minimize',
'window:maximize',
'window:restore',
'window:close'
],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Lastly, I have simplified the index.html file for a minimum reproducible example.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>my app</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';"/>
</head>
<body>
<div>
<div class="title-button" id="min-btn"> Minimize </div>
<div class="title-button" id="res-btn"> Restore </div>
<div class="title-button" id="max-btn"> Maximize </div>
<div class="title-button" id="clo-btn"> Close </div>
</div>
</body>
<script>
document.getElementById('min-btn').addEventListener('click', () => {
window.ipcRender.send('window:minimize');
});
document.getElementById('res-btn').addEventListener('click', () => {
window.ipcRender.send('window:restore');
});
document.getElementById('max-btn').addEventListener('click', () => {
window.ipcRender.send('window:maximize');
});
document.getElementById('clo-btn').addEventListener('click', () => {
window.ipcRender.send('window:close');
});
</script>
</html>
Related
Hi i'm not able to open the provided directory path(/home/userxyz/Releases/alpha) with electron
What i'm trying
i have a path like this on ubuntu /home/userxyz/Releases/alpha
when i try to open this path with below code
const files = await dialog.showOpenDialog({
title:'Browse mapped drive',
defaultPath: "/home/userxyz/Releases/alpha",
properties: ['openFile', 'multiSelections']
});
it is opening some junk directory
Note: i will be having variable let openDirectory = "/home/userxyz/Releases/alpha" which will change based on config value.
Question: i want to open a directory with the provided path let say the path will be /home/userxyz/Releases/alpha
Regarding successfully opening a defaultPath, I have noticed that if the passed-in path does not exist, then either
the last opened path or the valid part of the defaultPath (starting from the left) is opened. Therefore, you would want to confirm that
the defaultPath exists prior to opening a dialog.
I have included a full but minimised example on how to implement your desired action. Note the use of the preload.js
script (via whitelisted channel names only) and
the ipcRenderer.invoke(channel, ...args)
method in the render process and
the ipcMain.handle(channel, listener)
method in the main process.
I have used the channel name openFileDialog.
preload.js (main process)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': [
'openFileDialog' // Channel name
]
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
See below for further use of the preload.js script.
/**
* Render --> Main
* ---------------
* Render: window.ipcRender.send('channel', data); // Data is optional.
* Main: electronIpcMain.on('channel', (event, data) => { methodName(data); })
*
* Main --> Render
* ---------------
* Main: windowName.webContents.send('channel', data); // Data is optional.
* Render: window.ipcRender.receive('channel', (data) => { methodName(data); });
*
* Render --> Main (Value) --> Render
* ----------------------------------
* Render: window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', (event, data) => { return someMethod(data); });
*
* Render --> Main (Promise) --> Render
* ------------------------------------
* Render: window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', async (event, data) => {
* return await promiseName(data)
* .then(() => { return result; })
* });
*/
In this main.js file, one would want to check that the defaultPath exists first, else when the dialog
opens it may be unreliable / appear as a random path.
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronDialog = require('electron').dialog;
const electronIpcMain = require('electron').ipcMain;
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();
}
});
// ---
// Only pass in a valid defaultPath
let defaultPath = '/home/user/Releases/alpha'; // Ubuntu
// let defaultPath = 'C:\\Users\\user\\invalid\\path'; // Windows
electronIpcMain.handle('openFileDialog', () => {
// Dialog options
let options = {
title: 'Browse mapped drive',
defaultPath: defaultPath,
properties: ['openFile', 'multiSelections']
};
// Open dialog
return electronDialog.showOpenDialog(window, options)
.then((result) => {
// Bail early if user cancelled dialog
if (result.canceled) { return }
return result.filePaths;
})
})
Detect button click, send an IPC message to the main thread which opens the dialog. Upon the dialog returning a value,
it is returned to the render process and displayed as <li> items.
index.html (render process)
<!DOCTYPE html>
<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="button" value="Open File Dialog">
<ul id="paths"></ul>
</body>
<script>
document.getElementById('button').addEventListener('click', () => {
window.ipcRender.invoke('openFileDialog')
.then((paths) => {
if (paths === undefined) { return } // Dialog was cancelled
let result = '';
for (let path of paths) {
result += '<li>' + path + '</li>';
}
document.getElementById('paths').innerHTML = result;
})
})
</script>
</html>
I am having a problem with using remote windows in electron I am trying to process user input ans use that input to create a new window with a certain width and height yet when I press submit nothing happens
I am not sure why its not working in theory everything looks fine i am not getting any errors and testt.html is loading up just fine when i press submit it just resets
here is my code:
testt.html
<script src="./renderer.js"></script>
<form onsubmit="SetAction(this)">
<label for="fname">Width</label><br>
<input type="text" id="fname" name="fname"><br>
<label for="lname">Height</label><br>
<input type="text" id="lname" name="lname"><br>
<input type="submit">
</form>
renderer.js
function SetAction(form) {
const { BrowserWindow } = require('#electron/remote/main')
//const remote = require('electron').remote;
//const BrowserWindow = remote.BrowserWindow;
const w = form.fname.value;
const h = form.lname.value;
const win = new BrowserWindow({
height: w,
width: h,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: false,
webSecurity: false
}
});
window.require("#electron/remote/main").enable(win.webContents);
win.loadFile('index.html')
// var w = form.fname.value;
// alert(w)
}
cleint.js
const WindowPosition = require( 'electron-window-position' );
const path = require('path')
const prompt = require('electron-prompt');
const fs = require('fs')
function createWindow () {
// Create the browser window.
const position = new WindowPosition();
var pos = position.getActiveScreenCenter(0,0);
const mainWindow = new BrowserWindow({
x: pos.x,
y: pos.y,
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false,
webSecurity: false
}
})
require("#electron/remote/main").initialize(); require("#electron/remote/main").enable(mainWindow.webContents);
console.log(app.getPath('userData'))
mainWindow.loadFile('index.html')
mainWindow.setBackgroundColor("#000F1A")
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
There are many ways to implement this functionality. Choosing a particular way and fine-tuning it would be based on
personal preference and desired functionality.
In the example below, I have not used nodeIntegration: true and contextIsolation: false. Instead, I have these
settings reversed. IE: nodeIntegration: false and contextIsolation: true. This ensures that "both your preload
scripts and Electron's internal logic run in a separate context to the website you load in a webContents".
See Context Isolation for more information.
To keep things simple, I have the below preload.js script only managing the definition of "channel names" and
implementation of Inter-Process Communication. Apart from
the preload.js script, all other files shown below should give you a good idea on how to implement an answer.
preload.js (main process)
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'openWindow'
],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
Within your main.js script, create the main window and listed for a call on the channel name openWindow. When
received, pass on the form options (width and height) to create the newIndex.html window.
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// Prevent garbage collection.
let mainWindow;
let newWindow;
function createWindow(options) {
return new electronBrowserWindow({
x: 0,
y: 0,
width: options.width,
height: options.height,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
}
electronApp.on('ready', () => {
mainWindow = createWindow({width: 800, height: 600});
mainWindow.loadFile('index.html')
.then(() => { mainWindow.show(); });
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
// ----------------
electronIpcMain.on('openWindow', (event, options) => {
newWindow = createWindow(options);
newWindow.loadFile('newIndex.html')
.then(() => { newWindow.show(); });
})
This is the main window that will be displayed on application start-up, along with form logic and an IPC
call (openWindow) from the render to the main process.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Main Window</title>
</head>
<body>
<label for="width">Width: </label>
<input type="number" id="width" min="400" max="800" step="50" value="800">
<label for="height">Height: </label>
<input type="number" id="height" min="200" max="600" step="50" value="600">
<input type="button" id="button" value="Open Window">
</body>
<script>
document.getElementById('button').addEventListener('click', () => {
let options = {
// Convert values from strings to integers
width: parseInt(document.getElementById('width').value),
height: parseInt(document.getElementById('height').value)
};
window.ipcRender.send('openWindow', options);
});
</script>
</html>
And finally, the second window that will be created on form submission.
newIndex.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>New Window</title>
</head>
<body>
<h1>New Window</h1>
</body>
</html>
Hi at the moment i have used the ipcRenderer in one of my JS files to execute a function in the main.js. This code executes without a problem. What I'm trying to do straight after is send a callback function to the main renderer.js file so that i can execute another function which manipulates the appearance of some icons in the main window.
main.js code:
ipcMain.on('ReadyBtn', (event) => {
console.log('request received')
ToggleReady();
event.returnValue = "toggle ready test";
})
sidebar.js code - this file is where I'm using the ipc renderer:
const ReadyBtn = document.getElementById('btnReady')
ReadyBtn.addEventListener('click', function(){
ipcRenderer.sendSync("ReadyBtn")
});
The code above works fine, when the button is clicked the toggleReady() function within the main.js file is triggered.
What i would now like to do is set up a call back function in the renderer.js file so that i can execute another function "stateReady()", after the button is clicked. Below is what i have so far:
(async () =>{
await ipcRenderer.invoke('ReadyBtn')
stateReady();
})();
Noting that you have not included your preload.js script in your question, I have added the below preload.js script
which has no concrete implementations in it (apart from the IPC methods), only configuration of whitelisted channel names.
preload.js (main process)
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': [
'ReadyBtn' // Channel name
]
}
};
// Exposed protected methods in the render process.
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods.
'ipcRender', {
// From render to main.
send: (channel, args) => {
let validChannels = ipc.render.send;
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, args);
}
},
// From main to render.
receive: (channel, listener) => {
let validChannels = ipc.render.receive;
if (validChannels.includes(channel)) {
// Deliberately strip event as it includes `sender`.
ipcRenderer.on(channel, (event, ...args) => listener(...args));
}
},
// From render to main and back again.
invoke: (channel, args) => {
let validChannels = ipc.render.sendReceive;
if (validChannels.includes(channel)) {
return ipcRenderer.invoke(channel, args);
}
}
}
);
This preload.js script is used like so.
/**
* Render --> Main
* ---------------
* Render: window.ipcRender.send('channel', data); // Data is optional.
* Main: electronIpcMain.on('channel', (event, data) => { methodName(data); })
*
* Main --> Render
* ---------------
* Main: windowName.webContents.send('channel', data); // Data is optional.
* Render: window.ipcRender.receive('channel', (data) => { methodName(data); });
*
* Render --> Main (Value) --> Render
* ----------------------------------
* Render: window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', (event, data) => { return someMethod(data); });
*
* Render --> Main (Promise) --> Render
* ------------------------------------
* Render: window.ipcRender.invoke('channel', data).then((result) => { methodName(result); });
* Main: electronIpcMain.handle('channel', async (event, data) => {
* return await promiseName(data)
* .then(() => { return result; })
* });
*/
In your main.js file you will use the handle method to listen for a message on the ReadyBtn channel, process the request and then return a response.
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
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.handle('ReadyBtn', (event, message) => {
console.log('Request received'); // Testing
// toggleReady();
return 'Toggle ready test';
})
In your index.html file, you will use the invoke method to send a message on the ReadyBtn channel to the main process and then wait for a response.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron Test</title>
</head>
<body>
<input id="button" type="button" value="Button">
</body>
<script>
document.getElementById('button').addEventListener('click', () => {
window.ipcRender.invoke('ReadyBtn')
.then((result) => {
console.log(result); // Testing
console.log('Request received'); // Testing
// stateReady();
})
})
</script>
</html>
I am completely new to Electron so please forgive me if I am doing this completely wrong.
Apparently for security reasons I need to use a contextBridge to communicate between the preload and renderer scripts and the main script. I could not find a clear example for how to set this up so I took the provided answer to this stack overflow question: How to use preload.js properly in Electron. However, when I try to access window.api.receive I get the error Cannot read property 'receive' of undefined. So my window.api is undefined but I can not figure out why.
I also tried to simply set window.isWorking = true in my preload.js script and tried to print it in my renderer script like I have seen others do, but this also just prints undefined.
I use the following code:
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');
let win;
function createWindow() {
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
enableRemoteModule: false,
preload: 'preload.js'
}
});
win.loadFile('index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
ipcMain.on("test", (event, args) => {
console.log("received test request");
win.webContents.send("test", "some data");
});
preload.js
import { ipcRenderer, contextBridge } from "electron";
contextBridge.exposeInMainWorld(
"api", {
send: (channel, data) => {
let validChannels = ["test"];
if (validChannels.includes(channel)) {
ipcRenderer.send(channel, data);
}
},
receive: (channel, func) => {
let validChannels = ["test"];
if (validChannels.includes(channel)) {
ipcRenderer.on(channel, (event, ...args) => func(...args));
}
}
}
);
window.isWorking = true;
renderer.js
console.log(window.isWorking); // prints undefined
window.api.receive("test", (data) => { // exception on trying to access window.api.receive
console.log("data", data);
});
window.api.send("test", "some data");
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
</head>
<body style="background: white;">
<h1>Hello World!</h1>
<script src="renderer.js"></script>
</body>
</html>
I think { contextIsolation: false; nodeIntegration: true } allows importing electron and allows to set window.something
So try writing:
win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: 'preload.js'
}
});
I have a 50x50 px div element and I want to change its background color when I hit Ctrl+K. How do I do it?
UPDATE : After searching around for a bit, I tried this code and it still isn't working
index.html
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<div id="d" style="background-color: red; height: 50px; width: 50px;"></div>
<script>
const {ipcRenderer} = require('electron')
ipcRenderer.on('changeCol', (event,color) => {
document.getElementById('d').style.backgroundColor = color;
})
</script>
</body>
</html>
Here's the main.js
const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const globalShortcut = electron.globalShortcut;
let mainWindow
function createWindow () {
mainWindow = new BrowserWindow({width: 800, height: 600})
mainWindow.loadURL(`file://${__dirname}/index.html`)
mainWindow.webContents.openDevTools()
mainWindow.on('closed', function () {
mainWindow = null
})
}
app.on('ready', function() {
createWindow();
// registered a shortcut for Ctrl+K
globalShortcut.register('Control+K', () => {
mainWindow.webContents.on('did-fininsh-load', () => {
mainWindow.webContents.send('changeCol','green');
})
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', function () {
if (mainWindow === null) {
createWindow()
}
})
Alright, so I figured out I can't modify DOM directly from the main.js file. We have to use webContents.send() and ipcRenderer to send and recieve asynchronous messages via channels. Here's some simple code which lets you modify a div's background color.
main.js
app.on('ready', function() {
globalShortcut.register('A', () => {
mainWindow.webContents.send('changeColor','green');
});
});
index.html
<script>
const {ipcRenderer} = require('electron')
ipcRenderer.on('changeColor', (event,col) => {
var element = document.getElementById('element');
element.style.color = col;
})
</script>
Probably by using the Accelerator Api
const {app, globalShortcut} = require('electron')
app.on('ready', () => {
// Register a 'Control+K' shortcut listener.
globalShortcut.register('Control+K', () => {
// Do stuff when K and Command is pressed.
})
})
Then just some simple javascript where it says // Do stuff when K and Command is pressed. This is going to depend how your HTML/CSS is set up and the exact results you're looking for.