Electron JS open window when click HTML Button - electron

I have googled it and it seems like a recurrent task , but I did not find a good solution , Here is my code .
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World!</title>
</head>
<body>
<h1 style="color: red">Todos</h1>
<button type="button">Todos panel</button>
<script>
const btn = document.querySelector('button');
button.addEventListener('click', () => {
newtaskwindow();
});
function newtaskwindow() {
const remote = require('electron').remote;
const BrowserWindow = remote.BrowserWindow;
const win = new BrowserWindow({
height: 600,
width: 800
});
win.loadFile('todos.html');
}
</script>
</body>
</html>
This does not work .. I want to click the button to open a new child window.
const { app, BrowserWindow, Menu } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true
}
});
win.loadFile('index.html');
}
const todoWin = () => {
const win = new BrowserWindow({
width: 500,
height: 400,
modal: true,
parent: ''
});
win.loadFile('todos.html');
};
// Menu
const isMac = process.platform === 'darwin';
const template = [
...(isMac
? [
{
label: 'Demo',
submenu: [{ role: 'about' }]
}
]
: []),
{
label: 'File',
submenu: [isMac ? { role: 'close' } : { role: 'quit' }]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
app.whenReady().then(() => {
createWindow();
});
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit();
});

I see two things...
In main.js add enableRemoteModule and contextIsolation
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation:false
}
});
win.loadFile('index.html');
}
in index.html you use button.addEventListener instead of btn.addEventListener. Change it.
<script>
const btn = document.querySelector('button');
btn.addEventListener('click', () => {
newtaskwindow();
});
function newtaskwindow() {
const remote = require('electron').remote;
const BrowserWindow = remote.BrowserWindow;
const win = new BrowserWindow({
height: 600,
width: 800
});
win.loadFile('todos.html');
}
</script>
That way should work.
WARNING
As stated in Electron's documentation remote module is deprecated. You are encouraged to use IPC in order to execute code on main on request of renderer.

document.querySelector('form').addEventListener('submit', (e) => {
e.preventDefault();
const { ipcRenderer } = require('electron');
const value = document.querySelector('input').value;
ipcRenderer.send('todo:save', value);
window.close();
});
I did it through ipcRenderer

Related

require cannot be used in Electron's preloadjs

main.js
//...
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, './preload/preload.js')
}
});
//...
preload.js
const { contextBridge, ipcRenderer } = require('electron');
const qrcode = require('qrcode');
contextBridge.exposeInMainWorld('electronAPI', {
qrc: qrcode
})
the error
Unable to load preload script: F:\project\bluetools\preload\preload.js
Error: module not found: qrcode
Although direct exposure is not a good behavior, but I need to do it; and the example in the official documentation can be exposed directly, although it is not recommended; but why I require any third-party package in preloadjs shows "module not found: xxx"
Just add nodeIntegration: true to webPreferences. Like that.
//...
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, './preload/preload.js')
}
});
//...
I refer to here, I have got a solution, but it seems to be more troublesome, but what is this compared to security and normative?
ipc.js
module.exports = {
xxx_req: (argements) => {
const { ipcMain, winb: win } = argements;
ipcMain.on('xxx_req', (event, response) => {
win.webContents.send("xxx", "hello");
});
}
}
main.js
const ipcm = require('./ipc.js');
const createWindow = () => {
const win = new BrowserWindow({...});
ipcm.xxx_req({ ipcMain, winb: win })
}
preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
xxx_req: (response) => ipcRenderer.send('xxx_req',response),
xxx: (callback) => ipcRenderer.on('xxx',callback),
})
index.js(render process)
window.electronAPI.xxx_req("hello");
window.electronAPI.qrcode((event, detail) => {
console.log(detail)
});
If you still don't understand why I did this, please refer to here.

BrowserView rendering below fold of BrowserWindow

Running into a really strange quirk and can't figure out what I'm doing wrong:
init.js
const os = require('os');
const path = require('path');
const { app, BrowserView, BrowserWindow, ipcMain } = require('electron');
let win = null;
// Init function
const init = function() {
return new Promise((resolve, reject) => {
try {
// BrowserWindow config
const config = {
backgroundColor: '#1d1e20',
show: false,
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
}
};
// Make BrowserWindow frameless (win) or hide titlebar (mac)
if ('win32' === os.platform()) {
config.frame = false;
} else if ('darwin' === os.platform()) {
config.titleBarStyle = 'hidden';
}
// Create BrowserWindow
win = new BrowserWindow(config);
// Add listener for BrowserWindow 'ready-to-show' event
win.once('ready-to-show', () => {
// Set traffic light position (mac)
win.setTrafficLightPosition({ x: 10, y: 27 });
// Show browser window
win.show();
// Open DevTools
win.openDevTools({ mode: "detach" });
resolve();
});
// Load app html
win.loadFile('./app.html').then(() => {
// loaded
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// Add browser view
const addView = function(url) {
return new Promise((resolve, reject) => {
try {
// Get window size
const bounds = win.getSize();
// Create BrowserView
const view = new BrowserView({
backgroundColor: "#ffffff",
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
//preload: path.join(__dirname, 'preload.js'),
}
});
view.setBounds({
height: 375,
width: 375,
x: 0,
// Set "y" coordinate to 0
// (should be relative to BrowserWindow 0, 0)
y: 0
// Set "y" coordinate to a
// negative integer
//y: -200
// Instead, set "y" to inverse of
// BrowserWindow height
//y: bounds[1] * -1
});
// Load file
view.webContents.loadFile('./new.html').then(() => {
// Add to BrowserWindow
win.addBrowserView(view);
// Open DevTools
view.webContents.openDevTools({ mode: "detach" });
resolve();
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// init when ready
app.whenReady().then(() => {
init().then(() => {
addView().then(() => {
console.log("Everything should be working right");
}).catch((err) => {
console.log("Error loading BrowserView");
console.error(err);
});
}).catch((err) => {
console.log("Error loading BrowserWindow");
console.error(err);
});
});
Expected result: new BrowserView stacked squarely on top of BrowserWindow at defined width and height, positioned at 0, 0.
Actual result: new BrowserView displayed below the fold, with its "y" coordinate relative to bottom of BrowserWindow. To get BrowserView to show at BrowserWindow 0, 0, must set BrowserView "y" coordinate to (BrowserWindowHeight * -1)
Edit: P.S.
package.json
{
"name": "test",
"version": "1.0.0",
"description": "",
"main": "init.js",
"scripts": {
"start": "electron ."
},
"author": "",
"license": "",
"devDependencies": {
"electron": "^12.0.1"
}
}
preload.js
(no contents)
app.html
(no contents)
app.js
(no contents)
app.css
(no contents)
new.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Test</title>
<style>
html {
margin: 0;
padding: 0;
}
body {
background: #ffffff;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<h1>Hello World!</h1>
</body>
</html>
Update #1
Delay call to view.setBounds() by moving inside promise callback of view.loadURL(). No change.
Update #2
Further delay call to view.setBounds() by additionally wrapping in setTimeout() with delay of 1000ms. No change.
Update #3
By adding the following, the BrowserView does snap to correct 0, 0 position within parent BrowserWindow, but only upon resize event:
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({
height: newBounds.height,
width: newBounds.width,
x: 0,
y: 0
});
});
});
Update #4
Replaced BrowserWindow.loadFile() and BrowserView.loadFile() calls with .loadURL() using google.com and stackoverflow.com, respectively, to eliminate the possibility of it being something to do with my local files. No change.
Update #5
By modifying the init.js as follows, it works as expected, however it's not pretty as it "snaps" into position. (Edit: to clarify, BrowserView defined with solid background which I expect to be displayed until .loadURL() completes, unless I've somehow misunderstood the purpose of the backgroundColor property) (Edit: I've just realized that backgroundColor is not a valid property of the BrowserView)
const os = require('os');
const path = require('path');
const { app, BrowserView, BrowserWindow, ipcMain } = require('electron');
let win = null;
// Init function
const init = function() {
return new Promise((resolve, reject) => {
try {
// BrowserWindow config
const config = {
backgroundColor: '#1d1e20',
show: false,
webPreferences: {
contextIsolation: true,
enableRemoteModule: false,
preload: path.join(__dirname, 'preload.js'),
}
};
// Make BrowserWindow frameless (win) or hide titlebar (mac)
if ('win32' === os.platform()) {
config.frame = false;
} else if ('darwin' === os.platform()) {
config.titleBarStyle = 'hidden';
}
// Create BrowserWindow
win = new BrowserWindow(config);
// Add listener for BrowserWindow 'ready-to-show' event
win.once('ready-to-show', () => {
// Set traffic light position (mac)
win.setTrafficLightPosition({ x: 10, y: 27 });
// Show browser window
win.show();
// Open DevTools
win.openDevTools({ mode: "detach" });
});
// Add listener for BrowserWindow 'show' event
win.once('show', () => {
resolve();
});
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({ height: newBounds.height, width: newBounds.width, x: 0, y: 0 });
});
});
// Load app html
win.loadURL("https://google.com").then(() => {
// loaded
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// Add browser view
const addView = function(url) {
return new Promise((resolve, reject) => {
try {
// Create BrowserView
const view = new BrowserView({
backgroundColor: "#edeef0",
webPreferences: {
contextIsolation: true,
enableRemoteModule: false
}
});
// Load file
view.webContents.loadURL('https://stackoverflow.com').then(() => {
setTimeout(() => {
// Get window size
const bounds = win.getSize();
// Set BrowserView bounds
view.setBounds({ height: bounds[1], width: bounds[0], x: 0, y: 0 });
}, 1);
// Add to BrowserWindow
win.addBrowserView(view);
// Open DevTools
view.webContents.openDevTools({ mode: "detach" });
resolve();
}).catch((err) => {
reject(err);
});
} catch (err) {
reject(err);
}
});
};
// init when ready
app.whenReady().then(() => {
init().then(() => {
addView().then(() => {
console.log("Everything should be working right");
}).catch((err) => {
console.log("Error loading BrowserView");
console.error(err);
});
}).catch((err) => {
console.log("Error loading BrowserWindow");
console.error(err);
});
});
Update #6
I've come to the conclusion that in current version of Electron (12.0.1), a BrowserView is rendered with equivalent of CSS position: relative, but almost immediately thereafter, changed to equivalent of CSS position: absolute. Calling BrowserView.setBounds({ x: 0, y: 0 }) immediately upon creation of the BrowserView, the BrowserView is actually displayed at { x: 0, y: (BrowserWindow height) }, whereas calling BrowserView.setBounds({ x: 0, y: 0 }) within a setTimeout() after a 1ms delay, the BrowserView is actually displayed at { x: 0, y: 0}. I'm not sure why this is, but it seems the fix (tested only on macOS 10.14.6) is as follows:
const view = new BrowserView(...);
const size = win.getSize();
view.setBounds({ height: size[1], width: size[0], x: 0, y: size[0] * -1 });
and, on the BrowserWindow:
win.on('will-resize', (event, newBounds) => {
win.getBrowserViews().forEach((view) => {
view.setBounds({ height: newBounds.height, width: newBounds.width, x: 0, y: 0 });
});
});
Update #7
This still feels very unwieldy and I suspect this is not the intended behavior and I still may be doing something wrong. I'm going to leave this question open and hope that someone has a better solution than what I've found.
Update #8
Finally figured out that it was my sloppy implementation. After RTFM, adding some promises, and doing things in the proper order, there is no problem.
For educational purposes, I broke it down and reversed my steps to find the source of the problem so I know for future reference. For anyone else who may ever find this and have the same problem -- you must call BrowserWindow.addBrowserView() BEFORE calling BrowserView.setBounds(). That was the problem.
I was calling BrowserView.setBounds() immediately after creating the BrowserView -- at which point it was not added to the BrowserWindow yet, hence why the coords were off. I didn't call BrowserWindow.addBrowserView() until BrowserView.loadFile() resolved.
Must call BrowserWindow.addBrowserView() BEFORE calling BrowserView.setBounds() -- (see Update #8)

Electron window.webContents.send doesn't work

main.js
mainWindow = new BrowserWindow({ show: false, minWidth: 400, minHeight: 400, webPreferences: {nodeIntegration: true} });
mainWindow.maximize();
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, '/html/index.html'),
protocol: 'file:',
slashes: true
}));
mainWindow.on('closed', function(){
app.quit();
});
const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
Menu.setApplicationMenu(mainMenu);
mainWindow.once('ready-to-show', () => {
mainWindow.show();
const data = {id: 'checking_for_updates', display: 'none'}
console.log(data)
mainWindow.webContents.send('change_display', data)
})
index.html
<script>
const electron = require('electron');
const ipcRenderer = electron.ipcRenderer;
ipcRenderer.on('change_display', function (event, data) {
console.log(data);
document.getElementById(data.id).style.display=data.display;
})
</script>
<body>
<!-- Checking if you can update -->
<div class="container updating" id="checking_for_updates" style="display: block">
<h1 class="updating-text">Checking for updates.</h1>
</div>
it only logs 'data' once so probably the ipcRenderer doesn't run the ipcRenderer.on change_display event. How would I fix this issue?
Your code here:
mainWindow.once('ready-to-show', () => {
mainWindow.show();
const data = {id: 'checking_for_updates', display: 'none'}
console.log(data)
mainWindow.webContents.send('change_display', data)
})
only sends the change_display once. And so you'll see data logged once as well.

Getting absolute file path from file input in isolated environment in Electron app

I am starting an Electron based desktop app, which extends existing web app capabilities.
I am interested whether I can get the absolute file path from file input. HTML is displayed inside a BrowserView, which has nodeIntegration disabled and contextIsolation enabled.
My simple test does return a valid file path in this case, though my understanding is that it shouldn’t.
Maybe I am missing something? That looks like a security vulnerability…
Can I count in my desktop app that Electron will return a valid file path in a scenario similar to the one in the code below?
main.js
const { app, BrowserWindow, BrowserView } = require('electron');
let mainWindow;
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
});
let view = new BrowserView({
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
})
mainWindow.setBrowserView(view)
view.setBounds({ x: 0, y: 0, width: 800, height: 600 })
view.webContents.loadURL('http://localhost:8000/index.html')
mainWindow.on('closed', () => {
mainWindow = null;
});
};
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
index.html
<html>
<script type="text/javascript">
function _on_file_change_() {
file_input = document.getElementById('file1');
console.log(file_input.files[0]);
}
</script>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<input type="file" id='file1' onchange="_on_file_change_(this.files)"/>
</body>
</html>

Opening new Window - Electron

I'm currently trying to implement a new Window on my Electron App.
So I want to include a button, and when you click on this Button, a new Window should be opened.
I didn't find anything in the Electron documentation, maybe one of you can help me.
Perhaps something like this:
const button = document.getElementById('<your_button_id>');
button.addEventListener('click', () => {
createBrowserWindow();
});
function createBrowserWindow() {
const remote = require('electron').remote;
const BrowserWindow = remote.BrowserWindow;
const win = new BrowserWindow({
height: 600,
width: 800
});
win.loadURL('<url>');
}
I believe the answer that has been taken as correct is outdated.
I have managed to do it with the ipc module and with the solution given by Nishkal.
Please, read the ipc module, I'm new to electron and not very experienced with programming. I'm sure you can come with better solutions.
Code I added in order for it to work:
my main.js
const {app, BrowserWindow} = require('electron');
const path = require('path');
//ipc
const { ipcMain } = require('electron')
ipcMain.on('asynchronous-message', (event, arg) => {
createWindow();
})
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, '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();
})
my preload.js
window.addEventListener('DOMContentLoaded', () => {
const { ipcRenderer } = require('electron')
ipcRenderer.on('asynchronous-reply', (event, arg) => {
console.log(arg) // prints "pong"
})
//button and its event listener
const b1 = document.getElementById('b1');
b1.addEventListener('click', () => {
ipcRenderer.send('asynchronous-message', 'ping')
})
})
To open a window from the renderer:
window.open("https://github.com", "_blank", "top=500,left=200,frame=false,nodeIntegration=no");
https://www.electronjs.org/docs/latest/api/window-open

Resources