I've been trying to search this up, but I can't find the answer.
In WinForms you can make an MDI to create forms inside of, so that the MDI child windows cannot go outside of the MDI parent window. Does Electron have this feature or something similar?
Nope because a Browser window cant contain another browser window ... unless you using a movable div with an iframe element... You have 2 options one create a modal window with your content like I've done here
or you can open a new remote window sub in your main JS entry file
const electron = require('electron');
const url = require('url');
const path = require('path');
const {app,BrowserWindow,Menu,ipcMain}= electron;
//SET env
//process.env.NODE_ENV = 'production';
let mainWindow;
let addWindow;
//listen for the app to be ready
app.on('ready',function(){
//creat the new mainWindow
mainWindow = new BrowserWindow({
width:1200,
height:1000,
frame:false,
webPreferences: {
plugins: true,
nodeIntegration: true
//contextIsolation: true
}
});
mainWindow.loadURL(url.format({
pathname: path.join(__dirname,'index.html'),
protocol:'file',
slashes: true
}));
//close all winsows on close
mainWindow.on('closed', function(){
app.quit();
});
//build menu from mainMenuTemplate
const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
Menu.setApplicationMenu(mainMenu);
});
//handle add new sub window
function createAddWindow()
{
//creat the new mainWindow
addWindow = new BrowserWindow({
width:200,
height:200,
title:'add shoping list item'
});
//load html into window
addWindow.loadURL(url.format({
pathname: path.join(__dirname,'addWindow.html'),
protocol:'file',
slashes: true
}));
//garbage collection Handle
addWindow.on('close',function(){
addWindow= null;
});
}
//Catch Item:add from the subwindown and send to the main html
ipcMain.on('item:add' , function(e, item){
console.log(item);
mainWindow.webContents.send('item:add' , item);
addWindow.close();
});
//var remote = require('remote');
// var BrowserWindow = remote.require('mainWindow');
// function init() {
// Mainwindow.getElementById("min-btn").addEventListener("click", function (e) {
// var lMainwindow = Mainwindow.getFocusedWindow();
// lMainwindow.minimize();
// });
//
// Mainwindow.getElementById("max-btn").addEventListener("click", function (e) {
// var lMainwindow = Mainwindow.getFocusedWindow();
// lMainwindow.maximize();
// });
//
// Mainwindow.getElementById("Close-btn").addEventListener("click", function (e) {
// var lMainwindow = Mainwindow.getFocusedWindow();
// lMainwindow.close();
// });
// };
//
//
// init();
//Creat the menu template
const mainMenuTemplate =[
{
label:'File',
submenu:[
{label:'Add Item',
click(){
createAddWindow();
}
},
{
label:'Clear items',
click(){
mainWindow.webContents.send('item:clear');
console.log('clear click');
}
},
{
label:'Quit',
accelerator:process.platform=='darwin'? 'Command+Q' :'Ctrl+Q',
click(){
app.quit();
}
}
]
},
];
//disable Nasty security warnings
delete process.env.ELECTRON_ENABLE_SECURITY_WARNINGS;
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true;
//if mac add empty object to the menu to get of the title electron
if(process.platform == 'darwin'){
mainMenuTemplate.unshift({});
}
//add dev tools if not in production
if (process.env.NODE_ENV !== 'production')
{
mainMenuTemplate.push({
label:'Developer Tools',
submenu :[
{
label:'toggle Dev Tools',
accelerator:process.platform=='darwin'? 'Command+I' :'Ctrl+I',
click(item , focusedWindow){
focusedWindow.toggleDevTools();
}
},{
role:'reload',
}
]
})
}
here is the electron reference to a new window object Window Object Reference
electron basically gives you a windows app menu with the chrome browser window... Nothing else... everything inside the window you have to code with your web technologies like HTML CSS and JS
If you find something that wont work in a web page it wont work in electron
Related
So I am trying to stop the action of a new window opening whenever I click a link that "opens in a new tab" inside of a webview element, like this:
I would like to grab the url when the link is clicked, and prevent it from opening in a new tab.
Method 1
To stop the action of a new tab/window opening and define your own behavior in Electron, you can use the webview element's new-window event and call preventDefault() on the event object.
const { webContents } = require('electron')
webview.addEventListener('new-window', function(event) {
event.preventDefault()
const url = event.url
// Grab the URL here ...
});
This prevents the new tab/window from opening and allows you to define your own behavior such as grabbing the URL.
Method 2
You could alternatively use the webview.addEventListener method to listen for the did-start-loading event, and then use the webview.executeJavaScript method to inject a script that prevents the link from opening in a new tab/window.
...
webview.addEventListener('did-start-loading', function() {
webview.executeJavaScript(`
document.addEventListener('click', function(event) {
if (event.target.tagName === 'A' && event.target.target === '_blank') {
event.preventDefault()
// Grab the URL here ...
}
})
`)
})
This will listen for a click event for links set to _blank, and stop them from opening in a new tab/window.
I was able to fix the problem by using a combination of preload and webContents.setWindowOpenHandler
main.js
mainWindow = new BrowserWindow({ ... }) // the browserwindow code
mainWindow.webContents.on("did-attach-webview", (_, contents) => {
contents.setWindowOpenHandler((details) => {
mainWindow.webContents.send('open-url', details.url);
return { action: 'deny' }
})
})
Preload.js
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('api', {
send: (channel, data) => ipcRenderer.invoke(channel, data),
handle: (channel, callable, event, data) => ipcRenderer.on(channel, callable(event, data))
})
Renderer.js
window.api.handle(
"open-url",
(event, data) =>
function (event, data) {
document.querySelector("webview").src = data // your webview element
},
event
);
Hey i am making a project where i want to send data to ipc main
my index.js
const {app, BrowserWindow , ipcMain} = require('electron')
const ejse = require('ejs-electron')
app.on('ready', () => {
ipcMain.on("getUrl",(event,url)=>{
console.log(url);
})
mainWindow = new BrowserWindow({
autoHideMenuBar: true,
icon: __dirname + '/logo.ico',
webPreferences: {
devTools: false,
nodeIntegration:true,
webviewTag:true
}
})
mainWindow.loadURL('file://' + __dirname + '/files/index.ejs');
})
index.ejs contains a anchor tag with value as link
<button id="dw" style="display: none;"><a class="button" id="dwl" onclick="heal()" value="#">Download</a></button>
<script src=download.js></script>
download.js i am able to alert the link but ipcRender not working
const { ipcRenderer } = require("electron");
function heal(){
var url = document.getElementById("dwl").value;
alert(url);
ipcRenderer.send("getUrl",url);
}
i am not able to send url from ejs file to main index.js file alert is working in download.js but some error in sending data to main file console.log is not working
The use of contextBridge within your preload.js script safely exposes API's you approve for use in your render
scripts.
Whist understanding how all this works can be challenging at first, becoming familiar with the below content and the
following code should put you in good stead for a solid understanding of how you can wire up your application.
Context Isolation
contextBridge
Enabling Content Isolation for remote content
Inter-Process Communication
ipcMain
ipcRenderer
webContents - contents.send(channel, ...args)
Some people like to implement concrete functions within their preload.js script(s). In this example,
the preload.js script is only used to configure whitelisted channel names for use between the main process and render
process(es). This keeps your code seperated. IE: Separation of concerns.
Let's set the channel name getUrl within the ipc.render.send array.
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': [
'getUrl' // Channel name
],
// 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);
}
}
}
);
Let's set the webPreferences.contextIsolation value to true and specify the webPreferences.preload path.
webPreferences.nodeIntegration can be set to false, but in doing so webPreferences.contextIsolation should be
set to true to "truly enforce strong isolation and prevent the use of Node primitives".
Lastly, let's listen for a message from the render process on the getUrl channel.
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 window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: true,
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();
}
});
// Listen for a message on the 'getUrl' channel
electronIpcMain.on('getUrl', (event, url) => {
console.log(url);
})
This file is fairly standard and easy to understand. Just adapt code for use with .ejs
Added Javascript code in between <script> tags for sake of brevity.
index.html (render process)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Electron Test</title>
</head>
<body>
<input type="button" id="dwl" value="#">
</body>
<script>
let button = document.getElementById('dwl');
button.addEventListener('click', () => {
// Send URL on the 'getUrl' channel
window.ipcRender.send('getUrl', button.value);
});
</script>
</html>
I installed electron-context-menu using the command
npm i electron-context-menu
And then I used the code that is on this site
const {app, BrowserWindow} = require('electron');
const contextMenu = require('electron-context-menu');
contextMenu({
showSaveImageAs: true
});
let mainWindow;
(async () => {
await app.whenReady();
mainWindow = new BrowserWindow({
webPreferences: {
spellcheck: true
}
});
})();
But when I right click on the window, the ContextMenu with the items doesn't appear.
A white window only appears:
What should I do to make ContextMeu appear?
You won't need a lib for do this (And IMO you Shouldn't). The electron API already give to you a context-menu ready to use.
If you already use contextBridge just follow the electron site steps.
main.ts
// ...
ipcMain.on('show-context-menu', (event) => {
const template = [
{
label: 'Menu Item 1',
click: () => { event.sender.send('context-menu-command', 'menu-item-1') }
},
{ type: 'separator' },
{ label: 'Menu Item 2', type: 'checkbox', checked: true }
]
const menu = Menu.buildFromTemplate(template)
menu.popup(BrowserWindow.fromWebContents(event.sender))
})
renderer.ts
window.addEventListener('contextmenu', (e) => {
e.preventDefault()
ipcRenderer.send('show-context-menu')
})
ipcRenderer.on('context-menu-command', (e, command) => {
// What it will do when this options is click it
})
You can create a preload for each action too if you want
Also, if you want the browser default contextMenu, here comes a example.
Trying to come up with a GUI for Puppeteer project.
I thought about using Electron, but run into error:
Error: Passed function is not well-serializable!
when running Puppeteer functions like:
await page.waitForSelector('.modal', { visible: true });
I found a proper way to serialize when dealing with page.evaluate() but how to proceed in case of page.waitForSelector()?
Is there a work around for Puppeter's API functions to be properly serialized when required?
EDIT
I decided to rewrite
await page.waitForSelector('.modal', { visible: true });
using page.evaluate, here is the code:
// first recreate waitForSelector
const awaitSelector = async (selector) => {
return await new Promise(resolve => {
const selectorInterval = setInterval(() => {
if ($(selector).is(':visible')) {
console.log(`${selector} visible`);
resolve();
clearInterval(selectorInterval);
};
}, 1000);
});
}
and later call that function using page.evaluate():
// remember to pass over selector's name, in this case it is ".modal"
await page.evaluate('(' + awaitSelector.toString() + ')(".modal");');
Firstly context:
Generally you can not run puppeteer from browser environment, it works solely in nodejs. Electron provides 2 processes renderer and main. Whenever you want to use node you have to do it in main one.
About communication between both procesess you can read in docs, there are many ways of handling it. From what I know the best practice is to declare it in preload and use ipc bridge. Other solutions violate contextIsolation rule.
I was w wandering aound from one problem to another: like not serializable function, require not defined and many others.
Finally I rewrote everything from scratch and it works here's my solution:
main.js
const { app, BrowserWindow } = require('electron')
const path = require('path')
const { ipcMain } = require('electron');
const puppeteer = require("puppeteer");
function createWindow() {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
nodeIntegration: true,
contextIsolation: true,
},
})
ipcMain.handle('ping', async () => {
await checkPup()
})
async function checkPup() {
const browser = await puppeteer.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
page
.waitForSelector('h1', { visible: true })
.then(() => {
console.log('got it')
});
const [button] = await page.$x("//button[contains(., 'Create account')]");
if (button) {
console.log('button: ', button)
await button.click();
await page.screenshot({ path: 'tinder.png' });
const [button] = await page.$x("//button[contains(., 'Create account')]");
if (button) {
console.log('button: ', button)
await button.click();
await page.screenshot({ path: 'tinder.png' });
}
}
await browser.close();
}
// and load the index.html of the app.
mainWindow.loadFile('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.whenReady().then(() => {
createWindow()
app.on('activate', function () {
// On macOS 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()
})
})
// 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', function () {
if (process.platform !== 'darwin') app.quit()
})
// Attach listener in the main process with the given ID
ipcMain.on('request-mainprocess-action', (event, arg) => {
// Displays the object sent from the renderer process:
//{
// message: "Hi",
// someData: "Let's go"
//}
console.log(
arg
);
});
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.
preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
const { contextBridge, ipcRenderer } = require('electron')
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const type of ['chrome', 'node', 'electron']) {
replaceText(`${type}-version`, process.versions[type])
}
})
contextBridge.exposeInMainWorld('versions', {
node: () => process.versions.node,
chrome: () => process.versions.chrome,
electron: () => process.versions.electron,
ping: () => ipcRenderer.invoke('ping'),
// we can also expose variables, not just functions
})
renderer.js
const information = document.getElementById('info')
const btn = document.getElementById('btn')
information.innerText = `This app is using Chrome (v${versions.chrome()}), Node.js (v${versions.node()}), and Electron (v${versions.electron()})`
btn.addEventListener('click', () => {
console.log('habad!!!!!')
func()
})
const func = async () => {
const response = await window.versions.ping()
information.innerText = response;
console.log(response) // prints out 'pong'
}
Sorry for a little bit of a mess I hope it will help someone maybe finding solutions to some other problems
I call dialog.showOpenDialog() to find the path to the folder. But the problem is that this does not block the mainWindow. It is necessary that when the standard path selection GUI appears, the program continues to work only after the path selection is completed. Googled and realized that you need to use remote. But nothing happens.
I get:
Cannot destructure property dialog of 'undefined' or 'null'.
if from electron.remote take dialog.
I tried a lot of different things (and these are not all attempts, just what I remembered):
const { dialog } = require ('electron').remote;
var remote = electron.remote;
var dialog = remote.dialog;
const dialog = require ('electron').remote.dialog;
I tried to connect a lot and yet.
My main.js:
const url = require('url');
const path = require('path');
const {dialog} = electron.remote;
const {app, BrowserWindow, Menu} = electron;
app.on('ready', function () {
const {start_width, start_height} = electron.screen.getPrimaryDisplay().workAreaSize;
mainWindow = new BrowserWindow({
minWidth: 1250,
minHeight: 800,
height: start_height,
width: start_width,
center: true,
show: false,
});
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'templates/mainWindow.html'),
protocol: 'file:',
slashes: true
}));
mainWindow.on('closed', function () {
app.quit();
});
// mainWindow.webContents.openDevTools();
const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
Menu.setApplicationMenu(mainMenu);
mainWindow.maximize();
mainWindow.show();
});
function createAddWindow() {
addWindow = new BrowserWindow({
width: 300,
height: 200,
title: 'Add item'
});
addWindow.loadURL(url.format({
pathname: path.join(__dirname, 'templates/addWindow.html'),
protocol: 'file:',
slashes: true
}));
addWindow.on('close', function () {
addWindow = null;
})
}
const mainMenuTemplate = [
{
label: 'Analysis',
submenu: [{
label: 'Global search',
accelerator: 'Ctrl+O',
click() {
var path = createAddWindow();
console.log(path);
}
},
{
label: 'Search for a year',
accelerator: 'Ctrl+Alt+O',
click() {
console.log(folderSelect());//i need console.log (like the whole program) to continue to work after folderSelect returns some value.
}
},
{
label: 'Quit',
accelerator: process.platform == 'darwin' ? 'Command+Q' : 'Ctrl+Q',
click() {
app.quit();
}
},
]
}
];
function folderSelect() {
dialog.showOpenDialog({properties: ['openDirectory']}, function (path) {
console.log(path[0]);
if (path === undefined) {
return 'error';
}
else{
return path[0];
}
});
}
I need console.log (like the whole program) to continue to work after folderSelect returns some value.
For example, if I called the folderSelect function, you cannot interact with the program until the folder after selection window closes.
I saw a lot of similar questions on SO, but whatever I did nothing works.
In order to block the main window, you need to pass a BrowserWindow object to the dialog.showOpenDialog method as the first optional argument, the one you would like to attach the dialog to (mainWindow in your case I guess).
Quote from the docs:
dialog.showOpenDialog([browserWindow, ]options)
The browserWindow argument allows the dialog to attach itself to a
parent window, making it modal.
Now, how you make it happen is the whole different matter. It can be done in multiple ways, but if you are looking to invoke the dialog from renderer process the simplest one would be something like this:
import { remote } from 'electron'
remote.dialog.showOpenDialog(
remote.getCurrentWindow(), // <- notice this one
{ properties: ['openDirectory'] }
).then(result => {
// prefer promised API
})
The crucial part for whole thing to work properly is to have nodeIntegration enabled in your BrowserWindow options, which depending on a version of Electron you are using, you might or might not have (they've switched defaults from true to false in version 4). In any case it's better to have it set explicitly from now on. So that's how you would typically initiate your mainwindow now:
mainWindow = new BrowserWindow({
// ...
show: false,
webPreferences: {
nodeIntegration: true // <- this one
}
});