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>
Related
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>
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>
i am new to electron so im sorry if my question is childish
but i have sent some user inout data as object from renderer to main to store in json file (electron-db)
but i want to show all data in a table
i tired this by event.reply but it giving my desired result in preload's log only
i dont know how to take it from preload to html table
here i have attached my code from main
// catch obj This is Main.js
ipcMain.on("message", (event, obj) => {
db.createTable("medicine", (succ, msg) => {
// succ - boolean, tells if the call is successful
if (succ) {
console.log(msg);
} else {
console.log("An error has occured. " + msg);
}
});
console.warn(obj);
if ((db.valid("medicine"), location)) {
db.insertTableContent("medicine", obj, (succ, msg) => {
// succ - boolean, tells if the call is successful
console.log("Success: " + succ);
console.log("Message: " + msg);
});
}
let row;
const app = electron.app || electron.remote.app;
db.getAll("medicine", (succ, data) => {
// succ - boolean, tells if the call is successful
// data - array of objects that represents the rows.
// console.warn(data);
row = data;
});
console.warn(row)
event.reply("message", row)
});
and this is my preload
const {contextBridge, ipcMain, ipcRenderer} = require('electron');
const API = {
sendmsg: (obj) => ipcRenderer.send("message", obj)
}
// const getAllData = function fetchData(){
// ipcRenderer.send("req", "this is requirment for All data")
// }
ipcRenderer.on("message", (_event, row) => {
console.log(row)
})
contextBridge.exposeInMainWorld("api", API);
consol.log(row) giving my an array of all data in my db
but i dont know how to make it availabe in html files script or in renderer
The wiring of your IPC is a bit mixed up and confusing. To simplify your scripts, try separating them into their own functionality. IE: Use your preload.js script to allow communication between the main process and render process(es) only.
Not knowing what database you are using, I will assume your database code is correct and functional.
Below is an example of how I would set-up my preload.js script and use it to communicate between processes.
This preload.js script uses whitelisted channel names to identify which IPC lines of communication can be used, similar to Node.js eventName.
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': [
'message' // 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);
}
}
}
);
For brevity, I have included your database code within the below main.js file.
main.js (main process)
'use strict';
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require("path");
// db = require('db'); // Your (connected) database
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();
}
});
// ---
// From render to main and back again
electronIpcMain.handle('message', (event, obj) => {
// Create the table
db.createTable("medicine", (succ, msg) => {
if (succ) {
console.log(msg);
} else {
console.log('An error has occurred. ' + msg);
}
});
console.warn(obj);
// Insert your data object
if ((db.valid("medicine"), location)) {
db.insertTableContent("medicine", obj, (succ, msg) => {
console.log('Success: ' + succ);
console.log('Message: ' + msg);
});
}
let rows;
// Retrieve all rows
db.getAll("medicine", (succ, data) => {
// succ - boolean, tells if the call is successful
// data - array of objects that represents the rows.
// console.warn(data);
rows = data;
});
console.warn(rows);
return rows; // Simple return the result via IPC
// Testing purposes.
// let rows = [
// {"name": "Amoxicillin", "weight": "250mg"},
// {"name": "Codeine", "weight": "50mg"},
// {"name": "Penicillin", "weight": "25mg"}
// ];
//
// rows.push(obj);
//
// console.log(rows);
//
// return rows;
})
Lastly, I have tried to implement what the basic functionality of your index.html file may be like.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Medicine</title>
</head>
<body>
<h1>Add Medicine</h1>
<label for="name">Name: </label>
<input id="name" type="text">
<label for="weight">Weight: </label>
<input id="weight" type="text">
<input id="button" type="button" value="Add">
</body>
<script>
let name = document.getElementById('name');
let weight = document.getElementById('weight');
document.getElementById('button').addEventListener('click', () => {
// From render to main and (.then) back again
window.ipcRender.invoke('message', {"name": name.value, "weight": weight.value})
.then((rows) => {
console.log(rows);
})
})
</script>
</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'm messing around with electron, one of the things i'm trying to do is monitor a directory for file changes, and send the full path of the file that changed to a browser window via IPC to a renderer. I know the file watcher is working since it's logging to the console, but the path never makes it to the browserwindow.
Putting logging lines in the renderer never log, and remote debugging and stepping through the code, i can see it never fires off to the renderer.
Any idea? I'm new to this javascript stuff so it's probably something simple i'm missing. Thanks in advance!
main.js:
const electron = require('electron')
const app = electron.app
const BrowserWindow = electron.BrowserWindow
const ipc = electron.ipcMain
const watchfiles = require('./watchfiles')
const windows = []
app.on('ready', _ => {
[1].forEach(_ => {
let win = new BrowserWindow({
height: 400,
width: 400
})
win.loadURL(`file://${__dirname}/watchfiles.html`)
win.on('closed', _ => {
mainWindow = null
})
windows.push(win)
})
})
ipc.on('filewatch-start', _ => {
//Log
console.log('Filewatch started')
watchfiles(fullPath => {
windows.forEach(win => {
win.webContents.send('changedfiles', fullPath)
})
})
})
renderer.js:
const electron = require('electron')
const ipc = electron.ipcRenderer
document.getElementById('start').addEventListener('click', _ =>{
ipc.send('filewatch-start')
})
ipc.on('changedfiles', (event, message) => {
document.getElementById('changedfiles').innerHTML = message
})
watchfiles.js:
module.exports = function watchfiles(watchr) {
// Import the watching library
var watchr = require('watchr')
// Define our watching parameters
var path = process.cwd()
function listener (changeType, fullPath, currentStat, previousStat) {
switch ( changeType ) {
case 'update':
console.log('the file', fullPath, 'was updated', currentStat, previousStat)
break
case 'create':
console.log('the file', fullPath, 'was created', currentStat)
break
}
}
function next (err) {
if ( err ) return console.log('watch failed on', path, 'with error', err)
console.log('watch successful on', path)
}
// Watch the path with the change listener and completion callback
var stalker = watchr.open(path, listener, next)
// Close the stalker of the watcher
//stalker.close()
}
watchfile.html:
<html>
<head>
<link rel="stylesheet" href="watchfiles.css" />
</head>
<body>
<div id="container">
<h1 class="title">Watching files:</h1>
<div class="watchfiles" id="changedfiles"></div>
<button class="btn" id="start">Start</button>
</div>
<script>require('./renderer')</script>
</body>
</html>
Regarding only the IPC communication this simpler version works:
main.js
const { app, ipcMain, BrowserWindow } = require('electron')
const windows = []
app.on('ready', _ => {
/* ... */
})
ipcMain.on('filewatch-start', _ => {
console.log('Filewatch started')
windows.forEach(win => {
win.webContents.send('changedfiles', 'examplePath')
})
})
renderer.js
const { ipcRenderer } = require('electron')
window.onload = () => {
document.getElementById('start').addEventListener('click', _ =>{
ipcRenderer.send('filewatch-start')
})
ipcRenderer.on('changedfiles', (event, message) => {
document.getElementById('changedfiles').innerHTML = message
})
}
watchfile.html
<html>
<head>
<script type="text/javascript" src="./renderer.js"></script>
</head>
<body>
<div id="container">
<h1 class="title">Watching files:</h1>
<div class="watchfiles" id="changedfiles" />
<button class="btn" id="start">Start</button>
</div>
</body>
</html>
Regarding the overall design, you can do the job of watchfiles.js in main.js, no need to separate them:
main.js (compact)
const { app, ipcMain, BrowserWindow } = require('electron')
const watchr = require('watchr')
const path = process.cwd()
let windows = []
let stalker = null
const listener = (changeType, fullPath, currentStat, previousStat) => {
var msg = ''
console.log('change detected: ', changeType, currentStat, previousStat)
switch ( changeType ) {
case 'update':
msg = 'the file' + fullPath + ' was updated'
break
case 'create':
msg = 'the file' + fullPath + ' was created'
break
}
windows.forEach(win => {
win.webContents.send('changedfiles', msg)
})
}
const next = (err) => {
if ( err ) return console.log('watch failed on', path, 'with error', err)
console.log('watch successful on', path)
}
app.on('ready', () => {
[1].forEach(() => {
let win = new BrowserWindow({
height: 400,
width: 400
})
win.loadURL(`file://${__dirname}/watchfile.html`)
win.on('closed', _ => {
mainWindow = null
})
windows.push(win)
})
})
app.on('quit', () => {
if (stalker) stalker.close()
})
ipcMain.on('filewatch-start', _ => {
console.log('Filewatch started')
if (!stalker) {
stalker = watchr.open(path, listener, next)
}
})