Electron: cannot read property 'getCurrentWebContents' of undefined - javascript

I'm trying to read the files from a directory using the fs module, and display that in a div with the id displayfiles in an Electron app. I keep getting the error :
Cannot read property 'getCurrentWebContents' of undefined
I've tried a coupld of different things to get access to the webcontents of the main window in my menu.js but I keep getting undefined errors.
My code in menu.js looks like this:
Cannot read property 'getCurrentWebContents' of undefined
const electron = require('electron');
const dialog = electron.dialog;
const fs = require('fs');
const remote = require ("electron").remote;
const template=[
{
label: 'File',
submenu: [
{
label:'Open USB',
click () { dialog.showOpenDialog( {
properties: ['openDirectory']
},directorySelectorCallback)
}
},
{
label:'Exit',
click() {
}
},
]
},
{
label: 'Edit',
submenu: [
{role: 'undo'},
{role: 'redo'},
{type: 'separator'},
{role: 'cut'},
{role: 'copy'},
{role: 'paste'},
{role: 'pasteandmatchstyle'},
{role: 'delete'},
{role: 'selectall'}
]
},
{
label: 'View',
submenu: [
{role: 'reload'},
{role: 'forcereload'},
{role: 'toggledevtools'},
{type: 'separator'},
{role: 'resetzoom'},
{role: 'zoomin'},
{role: 'zoomout'},
{type: 'separator'},
{role: 'togglefullscreen'}
]
},
{
role: 'window',
submenu: [
{role: 'minimize'},
{role: 'close'}
]
},
]
function directorySelectorCallback(filenames) {
console.log("test:");
if (filenames && filenames.length > 0) {
console.log(filenames[0]);
fs.readdir(filenames[0], (err, files) => {
'use strict';
if (err) throw err;
//the files parameter is an array of the files and folders in the path we passed. So we loop through the array, printing each file and folder
for (let file of files) {
//the += after innerHTML means we are appending to the existing content
remote.getCurrentWebContents().executeJavaScript(`
document.getElementById("display-files").innerHTML += '<li> ${file} </li>'
`)
}
});
}
}
module.exports.template=template;
Here is my index.html file:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Rexnord</title>
</head>
<body>
<div ><ol id="display-files"></ol></div>
<script>
// You can also require other files to run in this process
require('./renderer.js')
require('./menu/menu.js')
</script>
</body>
</html>
Here is my main.js file:
// Modules to control application life and create native browser window
const electron = require('electron')
const {app, BrowserWindow,Menu} = require('electron')
const menuTemplate=require('./menu/menu.js')
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
var mainWindow
function createWindow () {
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600,icon: __dirname + '/Rexnord.ico'})
// and load the index.html of the app.
mainWindow.loadFile('index.html')
// Open the DevTools.
mainWindow.webContents.openDevTools()
// Emitted when the window is closed.
mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null
})
let menu = Menu.buildFromTemplate(menuTemplate.template)
Menu.setApplicationMenu(menu);
}
// 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.
app.on('window-all-closed', function () {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
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 (mainWindow === null) {
createWindow()
}
})
// 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.
module.exports.mainWindow=mainWindow;
How would I get access to the web contents of the main window in my menu.js file so I can manipulate the html?

Related

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

Long story short I am working on a single page application that sends commands over a local network. Testing out Electron JS and I can't even seem to get a simple button to work. I feel like I am not linking the logic between main.js and index.js somehow but for the life of me I cannot figure out the correct way to do it. I have even put breakpoints in index.js and through main.js & index.html but none of the breakpoints are hit aside from the ones in main.js. I put a simple function in a preload.js file and that function is correctly called but the one I am trying to attach to a button located in index.html and index.js is never even being hit. A lot of the commented out code is things I want to remember or things I have noticed a different method of creating and just wanted to try and see if that worked. If anyone has any answers or guidance it would be greatly appreciated! :D
Below is my main.js
//#region ---for dev only | hot reload
try {
require('electron-reloader')(module)
} catch (_) {}
//#endregion
const electron = require('electron');
const {app, BrowserWindow, Menu} = require('electron');
const path = require('path');
const ipcMain = electron.ipcMain;
//#region globals
const SRC_DIR = '/src/'
const IMG_DIR = '/assets/images'
//#endregion
function createWindow () {
const win = new BrowserWindow({
width: 800,
height: 600,
//frame: false,
webPreferences: {
contextIsolation: true,
preload: path.join(__dirname, 'preload.js')
}
});
//Used to auto open dev tools for debugging
//win.openDevTools();
win.loadFile('src/index.html');
// win.loadURL(url.format({
// pathname: path.join(__dirname, 'index.html'),
// protocol: 'file',
// slashes: true
// }));
}
app.whenReady().then(() => {
//nativeTheme.shouldUseDarkColors = true;
createWindow();
})
//closes app processes when window is closed
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit();
})
var menu = Menu.buildFromTemplate([
{
label: 'Menu',
submenu: [
{label: 'Edit'},
{type: 'separator'},
{
label: 'Exit',
click() {
app.quit();
}
}
]
}
])
Menu.setApplicationMenu(menu);
Here is index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'">
<title>Ecas Software</title>
<link rel="stylesheet" href="index.css">
</head>
<body>
<p id="myText">Let's get started :)</p>
<button id="myBtn">Change Text</button>
<script type="text/javascript" src="./index.js" ></script>
</body>
</html>
Lastly here is my index.js (aka my first and only renderer?)
const electron = require('electron');
const chgBtn = document.getElementById('myBtn');
function replaceText(selector, text){
const element = document.getElementById(selector);
if (element) element.innerText = text;
}
chgBtn.onclick = function() {
replaceText('myText', 'no boom...');
}
// chgBtn.addEventListener('click', function(){
// // if (document.getElementById('myText').innerText == 'boom'){
// // replaceText('myText','no boom...');
// // } else {
// // replaceText('myText','boom');
// // }
// document.alert("working function");
// });
//chgBtn.addEventListener('click', replaceText('myText','no boom...'));
Why you have this error
The problem here is that you didn't use your scripts files the way Electron was intended.
If you use the Devtools Console (by uncommenting win.openDevTools()), you should see this error in your console :
Uncaught ReferenceError: require is not defined (from index.js file)
This is because your index.js file is loaded as a "normal javascript file". If you want to use the Node syntaxe (aka the "require" syntaxe), you need to do it in your preload script. Only the preload script can use the require syntaxe, since it is the only script allowed by Electron to use Node.
You can also use other javascripts files, by import it in your HTML as you did for the index.js file, but you should remove the require call. As the "require" call (on the first line) will throw and error, all the following code will not run. This is why your button did not react on click.
The correct way to do it
If you need to use some methods from the Electron Renderer API (such as the ipcRenderer), you need to put it in your preload script.
If you want to use your own script, in a separate file, you can also do it, you will not be able to directly call Electron API. There is a solution if you want to call the Electron API in your own script, it is called the Context Bridge. This allows you to create an object in your preload script, that can use the Electron API. You can give this object a name, and then call it from your others script by using the window global object.
For example, if you want to use ipcRenderer.send(channel, payload) :
// Preload script
const { contextBridge, ipcRenderer } = require('electron')
contextBridge.exposeInMainWorld('theNameYouWant',
{
send: (channel, payload) => ipcRenderer.send(channel, payload)
}
)
// index.js file, imported in your HTML file
window.theNameYouWant.send("channel-name", { someData: "Hello" })
In your example
// Add this in your main.js file to see when a user click on the button from main process
ipcMain.on("button-clicked", (event, data) => console.log(data))
// Preload script
const { contextBridge, ipcRenderer } = require("electron")
contextBridge.exposeInMainWorld("electron", {
send: (channel, payload) => ipcRenderer.send(channel, payload),
})
// index.js
const chgBtn = document.getElementById("myBtn")
function replaceText(selector, text) {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
chgBtn.onclick = function () {
replaceText("myText", "no boom...")
window.electron.send("button-clicked", { someData: "Hello" })
}

Is there a way to let the program remember which URL was open when the user closed the program?

Is there a way to let the program remember which URL was open when the user closed the program? For example if the user closes the application, the last URL gets added to the loadURL. The program is being used for users that only can interact with touchscreen and cant leave the specific site. I am using windows 10 and the newest version of electron.
// Modules to control application life and create native browser window.
const { app, BrowserWindow, Menu } = require("electron");
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
frame: false,
webPreferences: {
preload: `${__dirname}/preload.js`
}
});
// The loadURL that loads if you start up the application.
mainWindow.loadURL("https://google.com");
// Emitted when the window is closed.
mainWindow.on("closed", function() {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
mainWindow = null;
});
}
// The toggleFullScreen function stated in the createMainMenu function.
function toggleFullscreen() {
if (mainWindow.isFullScreen()) {
mainWindow.setFullScreen(false);
} else {
mainWindow.setFullScreen(true);
}
}
function createMainMenu() {
const template = [
{
label: "Options",
submenu:
[
{
label: "Quit",
accelerator: "CmdOrCtrl+Q",
click() {
app.quit();
}
},
{
label: 'Toggle full screen',
accelerator: 'CmdOrCtrl+F',
click: () => {
toggleFullscreen();
}
},
{
label: 'Toggle developer tools',
accelerator: 'CmdOrCtrl+I',
click(item, focusedWindow){
focusedWindow.toggleDevTools();
}
}
]
}
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
// 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();
createMainMenu();
});
// Quit when all windows are closed.
app.on("window-all-closed", function() {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q.
if (process.platform !== "darwin") {
app.quit();
}
});
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 (mainWindow === null) {
createWindow();
}
});
// 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.
You have two options.
First, each time the URL changes, use LocalStorage in the renderer process. It will work just like in a web app.
Second, each the URL changes, have it send a message back to the main process, and have it write that to a local file. getPath('appData') can be used to get the OS-specific directory of where settings files can be written.

import menu to browser window by "require" function

I am working on a electron demo by following this tutorial.
just wondering what happened in the require line of code.
./menu/mainmenu.js defines the menu items.
const {Menu} = require('electron')
const electron = require('electron')
const app = electron.app
const template = [
{
label: 'Edit',
submenu: [
{
role: 'undo'
},
{
role: 'redo'
},
{
type: 'separator'
},
{
role: 'cut'
},
{
role: 'copy'
},
{
role: 'paste'
},
{
role: 'pasteandmatchstyle'
},
{
role: 'delete'
},
{
role: 'selectall'
}
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
let win;
function createWindow () {
win = new BrowserWindow({
width: 880,
height: 660,
webPreferences: {
nodeIntegration: true
}
})
// and load the index.html of the app.
win.loadFile('index.html')
require('./menu/mainmenu') //does this line copied the whole mainmenu.js file?
}
does the require('./menu/mainmenu') copy whole file into main.js?
Or imported some modules? In the mainmenu.js file There is no export keyword.
according to the node.js documentation,
"The basic functionality of require is that it reads a JavaScript file, executes the file, and then proceeds to return the exports object."
require here doesn't copy file around (unlike c++ #include)
Instead it execute the file and return the exported items (if any)
Since there is no export in './menu/mainmenu' when you call require, it simply executed that file.
The problem with this approach is require would only process that file once*, the proper way is actually export something which can be used multiple times.
example:
./menu/mainmenu.js
//...
const menu = Menu.buildFromTemplate(template)
export default ()=>Menu.setApplicationMenu(menu)
main.js
const { app, BrowserWindow, ipcMain } = require('electron');
let win;
function createWindow () {
//...
const setmenu = require('./menu/mainmenu') // you can put this at top, too
setmenu();
// or you can execute it inline
// require('./menu/mainmenu')()
}
note: you may need https://www.npmjs.com/package/babel-plugin-add-module-exports or some workaround to make require and default export works together.
*the problem with this is you cannot really rely on it to work everytime, e.g. change menu from A -> B -> A , the second require('A') would silently do nothing.

Vue CLI Plugin Electron Builder shows complete blank screen on build

When I build my electron app with this plugin, all I get when installing the package is a blank, white, screen. I've configured the window to open dev tools in the built version, but when I look at the inspect menu, the only content on the page are the <html>, <head> and <body> tags, and there are no errors; the console is completely empty:
Elements:
Console:
I've looked just about everywhere online, but none of the solutions there worked.
I have tried changing the router mode from history to hash and also running vue invoke electron-builder, but they didn't help.
From what I can tell, this is failing:
win.loadURL('app://index.html')
because the path is incorrect. But I don't know if that's the case or if it's something else, since there are no errors reported.
Here's my background.js file:
'use strict'
import { app, protocol, BrowserWindow } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
const isDevelopment = process.env.NODE_ENV !== 'production'
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
// Scheme must be registered before the app is ready
protocol.registerSchemesAsPrivileged([
{ scheme: 'app', privileges: { secure: true, standard: true } }
])
function createWindow() {
// Create the browser window.
win = new BrowserWindow({
width: 1500,
height: 845,
webPreferences: {
// Use pluginOptions.nodeIntegration, leave this alone
// See nklayman.github.io/vue-cli-plugin-electron-builder/guide/security.html#node-integration for more info
nodeIntegration: true,
enableRemoteModule: true
}
})
win.removeMenu()
win.webContents.openDevTools();
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
win.loadURL(process.env.WEBPACK_DEV_SERVER_URL)
// if (!process.env.IS_TEST) win.webContents.openDevTools();
} else {
createProtocol('app')
// Load the index.html when not in development
win.loadURL('app://index.html')
}
win.once('ready-to-show', () => {
win.show();
})
win.on('closed', () => {
win = null
})
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
// 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 (win === null) {
createWindow()
}
})
// 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', async () => {
if (isDevelopment && !process.env.IS_TEST) {
// Install Vue Devtools
try {
await installExtension(VUEJS_DEVTOOLS)
} catch (e) {
console.error('Vue Devtools failed to install:', e.toString())
}
}
createWindow()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', (data) => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
How can I fix this problem?
I found that solution is to change router mode from "history" to "hash". So in router config set:
mode: process.env.IS_ELECTRON ? 'hash' : 'history',
Problem solution source
So after more Googling, I stumbled upon this site which suggested to do this:
Replace your default win.loadURL() (which might look like this:
win.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html');,
protocol: 'file',
slashes: true
}))
or it could be different; it doesn't matter), with this:
win.loadURL(path.join(__dirname, 'index.html'));
Basically, the difference is that this just removes the formatUrl which seems to screw things up.
I replaced mine, which was:
win.loadURL("app://./index.html");
with this, and it works fine now.
Also make sure you don't delete createProtocol('app') if that is there too, (it was a few lines above win.loadUrl()), because you could break your app :).
if you are using vue-router, make sure to set it on "hash mode".
source: https://nklayman.github.io/vue-cli-plugin-electron-builder/guide/commonIssues.html

Why does it look like IPC messages from one menu item in Electron reach my window, but not when sent from another menu iteam?

I have a simple application that needs to run a background process to get some data. I would like to show a loading indicator while the data is being retrieved and I am having trouble with getting that done.
I am using ipcrenderer to receive the message in the main window. My code is below:
//// main.js
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;
let workerWindow;
// Listen for app to be ready
app.on('ready', function () {
// Create new window
mainWindow = new BrowserWindow({});
// Load the HTML file into the window
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'mainWindow.html'),
protocol: 'file',
slashes: true
}));
// Quit app when closed
mainWindow.on('closed', function () {
app.quit();
})
// Build menu from menu template
const mainMenu = Menu.buildFromTemplate(mainMenuTemplate);
// Insert menu
Menu.setApplicationMenu(mainMenu);
});
// Handle create worker window
function createWorkerWindow() {
mainWindow.webContents.send('status:showLoading'); // This does NOT work
// Create new window
workerWindow = new BrowserWindow({
width: 300,
height: 200,
title: 'workerWindow',
show: process.env.NODE_ENV == 'production' ? false : true
});
// Load the HTML file into the window
workerWindow.loadURL(url.format({
pathname: path.join(__dirname, 'worker.html'),
protocol: 'file',
slashes: true
}));
// Garbage collection
workerWindow.on('close', function () {
workerWindow = null;
})
mainWindow.webContents.send('status:hideLoading'); // This does NOT work
}
// Catch item:add-worker
ipcMain.on('item:add-worker', function(e, item){
console.log(item);
mainWindow.webContents.send('item:add-worker', item);
workerWindow.close();
});
// Create menu template
const mainMenuTemplate = [{
label: 'File',
submenu: [
{
label: 'Add Item from Worker',
click() {
mainWindow.webContents.send('status:showLoading'); // This does NOT work
createWorkerWindow();
}
},
{
label: 'Clear Items',
click(){
mainWindow.webContents.send('item:clear'); // This works
}
},
{
label: 'Show Loading',
click(){
mainWindow.webContents.send('status:showLoading'); // This works
}
},
{
label: 'Hide Loading',
click(){
mainWindow.webContents.send('status:hideLoading') // This works
}
},
{
label: 'Quit',
accelerator: process.platform == 'darwin' ? 'Command+Q' : 'Ctrl+Q',
click() {
app.quit();
}
}
]
}];
When I use the Show Loading and Hide Loading menu items, the main window receives the message and does what it needs to do. However, when I click the Add Item from Worker menu item, the message appears to not reach the main window.
<!-- mainWindow.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Security-Policy" content="default-src file: https:">
<title>Shopping List</title>
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0-beta/css/materialize.min.css">
<link rel="stylesheet" href="mainWindow.css">
</head>
<body>
<nav>
<div class="nav-wrapper">
<a class="brand-logo center">Shopping List</a>
<div id="loadingIndicator" class="progress">
<div class="indeterminate"></div>
</div>
</div>
</nav>
<ul></ul>
<script src="mainWindow.js"></script>
</body>
</html>
//// mainWindow.js
// Show Loading
ipcRenderer.on('status:showLoading', function(){
document.getElementById('loadingIndicator').style.display = 'block';
});
// Hide Loading
ipcRenderer.on('status:hideLoading', function(){
document.getElementById('loadingIndicator').style.display = 'none';
});
I tried to add the mainWindow.webContents.send('status:showLoading'); line in side the click() function for the menu item and inside the createWorkerWindow() function. Neither appear to work.
Any insight would be much appreciated.
Thank you.
The problem is that loading a url in a browser window isn't synchronous so when it runs createWorkerWindow it will send the status:showLoading message then it'll create the worker window and send the status:hideLoading all in a split second. If you wanted to hide the loading window after the worker window has finished loading you can use:
workerWindow.webContents.on('did-finish-load', function () {
mainWindow.webContents.send('status:hideLoading');
});
Although keep in mind this will run when any url is loaded not just the first one.
did-finish-load docs

Categories

Resources