I am creating an inventory application with the backend written in Python 3.7. I am using Electron to build a GUI for it and the Node.js Module "python-shell" to be able to communicate with the Python code. I would like for all of the code for python-shell to be in a separate JavaScript file (connector.js). If I run just that file with node from the command line it works perfectly fine, but calling the function from an HTML Button doesn't work.
I know that with one of the last updates to Electron nodeIntegration by default is false, but I did set that to true. Unfortunately it still doesn't allow me to communicate with the Node module within the Electron renderer process. If I copy the connector.js code into the main.js file used by Electron and call it on start of the application, it does work but calling it using an HTML button also obviously doesn't work.
This is the main.js file for Electron with nodeIntegration set to true.
const {app, BrowserWindow, Menu} = require('electron')
const fs = require('fs')
let mainWindow
function createWindow() {
mainWindow = new BrowserWindow({
// Set fixed size of main window
width: 1280,
height: 768,
resizable: false,
webPrefrences: {
nodeIntegration: true,
}
})
mainWindow.loadFile('./main.html')
mainWindow.on('closed', function () {
mainWindow = null
})
//Opens the dev tools by default.
mainWindow.webContents.openDevTools();
}
// Disable the default Menu Bar
Menu.setApplicationMenu(null)
app.on('ready', createWindow)
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', function () {
if (mainWindow === null) createWindow()
})
And this is the connector.js file that calls the Python script with the correct arguments. functionality.py is for testing a very simple script that takes the first commandline argument (in this case addNewDataset) to call a function and the second as the name of a file it creates.
const {PythonShell} = require('python-shell')
var options = {
mode: 'text',
encoding: 'utf8',
pythonPath: 'python3',
pythonOptions: ['-u'],
scriptPath: './',
args: ['addNewDataset', 'testDataset123']
};
function test() {
PythonShell.run('functionality.py', options, function (err) {
if (err) throw err;
console.log('done');
});
}
It is loaded into the HTML file using a simple script tag
<script src="connector.js"></script>
and called using an input
<input type="submit" value="Button" onclick="test()">
I am pretty sure that I am just misunderstanding the way Electron and the main and renderer processes work. Unfortunately no matter what I look for on the Internet I don't seem to be able to get a solution that actually works.
Add a module.exports to your python file. It'll look like this:
module.exports = {
test: function () {
PythonShell.run('functionality.py', options, function (err) {
if (err) throw err;
console.log('done');
});
}
}
That above code will expose your function when you require the html file as a variable. Like this in the html file:
<HTML>
<input id="SpecialButton" type="submit" value="Button" onclick="test()">
<!-- ^add an id to the button -->
<script>
const python = require('python-file-path')
document.getElementById('SpecialButton').onclick = () => {
python.test() //call the function of the python file like this
}
</script>
</HTML>
I haven't tested my code in a IDE so I apologize for any syntax errors.
More about exporting functions in modules: In Node.js, how do I "include" functions from my other files?
Related
I followed the suggestions in the answers of related questions, i.e. specifying
webPreferences: {
plugins: true
}
as part of the options when creating the BrowserWindow instance, but it is simply not working. Whenever I try to load/open/view a PDF file in electron, all I get is what looks like an empty/broken chromium PDF viewer like on this screenshot:
My electron version is "^13.1.2" according to my package.json and this is my main.js
// main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createWindow () {
// Create the browser window.
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
plugins: true
}
});
// and load the index.html of the app.
win.loadFile('test.pdf');
}
app.whenReady().then(() => {
createWindow()
})
app.on('window-all-closed', function () {
app.quit()
})
Can anyone tell me what I'm doing wrong?
An upgrade of electron helped me. i switched from 13.x to 16.x and it works now
Use PDFWindow package
`//main.js
app.on('ready', () => {
const win = new PDFWindow({
width: 800,
height: 600
})
win.loadURL('test.pdf')
})
//package.json
"dependencies": {
"electron-pdf-window": "^1.0.12",
},
`
My app does not display a window when it's built, but works fine when I run npm run serve
There is still an process running in task manager, and the same thing happens if I use the installer. I don't get any errors warnings from electron-builder.
background.js:
import { app, protocol, dialog, BrowserWindow, ipcMain, shell } from 'electron'
import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'
import installExtension, { VUEJS_DEVTOOLS } from 'electron-devtools-installer'
import path from 'path'
import fs from 'fs'
import childProcess from 'child_process'
import ncp from 'ncp'
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: 1200,
height: 600,
resizable: false,
maximizable: false,
frame: false,
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: process.env.ELECTRON_NODE_INTEGRATION,
preload: path.join(__dirname, 'preload.js')
}
})
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(path.join(__dirname,'index.html'))
}
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()
}
})
I tried the fixes here and here to no avail.
I'm using electron-vue. You can find the full code here if you need more context
The issue ended up being code present inside of the app.on('ready') function. Any additional code needs to be written after the window is created, or the issue occurs.
I am having trouble with my Electron app. I had it working about 9 months ago, but now the custom minimise and maximise buttons I made are not functioning properly.
Here is my file structure
node_modules
...
web
css
main.css
html
index.html
images
...
js
index.js
themes
...
main.js
package.json
package-lock.json
require.js
And here is the contents of index.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="../css/main.css">
<style type="text/css">* {visibility: hidden;}</style>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700" rel="stylesheet">
<link rel="icon" href="../images/favicon-32x32.png" />
</head>
<body>
<div id="wrapper">
<div id="title-bar">
<div id="title-bar-btns">
<button id="min-btn" onclick="window_minimise()">—</button>
<button id="close-btn" onclick="window_close()">✕</button>
</div>
</div>
...
</div>
<script>
// You can also require other files to run in this process
require('../../renderer.js')
</script>
<script src="../js/index.js" type="text/javascript"></script>
</body>
</html>
And index.js
...
const {BrowserWindow} = require('electron').remote;
function window_minimise(){
let window = BrowserWindow.getCurrentWindow();
window.minimize();
}
function window_close(){
let window = BrowserWindow.getCurrentWindow();
window.close();
}
...
And here is my main.js file
const {app, BrowserWindow} = require('electron')
let mainWindow
function createWindow () {
let mainWindow = new BrowserWindow({
width: 1200,
height: 748,
icon:'web/images/favicon-32x32.png',
resizable: false,
frame: false,
show: false,
backgroundColor: '#171717',
webPreferences: {
nodeIntegration: true
}
})
mainWindow.once('ready-to-show', () => {
mainWindow.show()
})
mainWindow.setMenu(null);
mainWindow.loadURL('http://localhost:8000/html/index.html')
mainWindow.on('closed', function () {
mainWindow = null
})
}
app.on('ready', createWindow)
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
app.on('activate', function () {
if (mainWindow === null) createWindow()
})
When I click minimise or maximise, nothing happens. So I went to http://localhost:8000/html/index.html and checked the console, and I saw these errors
Uncaught ReferenceError: require is not defined at index.html:137
Uncaught ReferenceError: require is not defined at index.js:110
I am using electron version 5.0.2 by the way.
If someone could help me resolve this, that would be wonderful.
Thanks.
EDIT: The errors I mentioned above are shown when the page loads, and this error is shown when I click minimise
Uncaught ReferenceError: Cannot access 'BrowserWindow' before
initialization at window_minimise (index.js:113) at
HTMLButtonElement.onclick (index.html:18)
EDIT2: I think the issue is that eel (the code that allows me to interface python with my electron app) requires the webpage to be run on localhost, and thus node features such as require do not work. I go into a bit more detail in a GitHub issue here: github.com/ChrisKnott/Eel/issues/147
All you need to do is add contextIsolation: false after nodeIntegration: true in the webPreferences object
Change your index.js file as follows. Then use nodeRequire instead of require keyword.
initNodeRequire();
const {BrowserWindow} = nodeRequire('electron').remote;
function window_minimise(){
let window = BrowserWindow.getCurrentWindow();
window.minimize();
}
function window_close(){
let window = BrowserWindow.getCurrentWindow();
window.close();
}
function initNodeRequire(){
window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;
}
Electron freezes when node-opcua is required in index.html and a process called Electron Helper takes up 100% of the cpu.
I have encountered the problem on macOS 10.14.2, but a friend tested in Windows 10 and that worked.
From package.json
"devDependencies": {
"electron": "^4.0.4"
},
"dependencies": {
"node-opcua": "^0.5.6"
}
Main.js
const { app, BrowserWindow } = require('electron')
function createWindow () {
// Create the browser window.
win = new BrowserWindow({ width: 800, height: 600 })
// and load the index.html of the app.
win.loadFile('index.html')
win.webContents.openDevTools()
}
app.on('ready', createWindow)
Index.html
<script>
// Does not work
const opcua = require('node-opcua')
console.log(opcua)
// Works
// const fs = require('fs')
// console.log(fs)
</script>
When running the simple code it should just print the opcua object in the console. But the complete electorn process freezes.
Solved it by updating node-opcua to 2.1.9 and electron to 6.0.11
I am trying to make a simple Electron application called my-app containing a standard main.js which creates a new browser window that points to index.html.
Inside index.html is an iframe that loads another local file called iframe.html.
Inside iframe.html is some text that when clicked calls a Javascript function launchPowershell().
This function is located inside renderer.js and will create a new Powershell object, add a command, and invoke the object.
However, whenever I run my-app, and click on the text inside iframe.html, I get an error thrown saying that "require is not defined".
Error thrown
If I move the code from iframe.html into index.html and drop the iframe, everything works fine.
So I believe I am missing something to get Electron to work correctly using an iframe. Perhaps something related to the scope of my Javascript variables.
Can anybody provide any suggestions?
package.json:
{
"name": "my-app",
"version": "0.1.0",
"main": "main.js"
}
index.html:
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="renderer.js" ></script>
</head>
<body>
<iframe id="iframe" src="iframe.html"></iframe>
</body>
</html>
iframe.html:
<html>
<head>
<script type="text/javascript" src="renderer.js" ></script>
</head>
<body>
<a onclick="launchPowershell();">Launch powershell</a>
</body>
</html>
renderer.js:
function launchPowershell() {
const powershell = require('node-powershell');
// Create the PS Instance
let ps = new powershell({
executionPolicy: 'Bypass',
noProfile: true
})
// Load the gun
ps.addCommand("Powershell success!")
// Pull the Trigger
ps.invoke()
.then(output => {
console.log(output)
})
.catch(err => {
console.error(err)
ps.dispose()
})
}
main.js:
const electron = require('electron')
// Module to control application life.
// const app = electron.app
const {app, Menu, dialog} = electron
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const path = require('path')
const url = require('url')
// Declare some global variables
global.sharedObj = {
cred: null
};
// 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 createMenu() {
const template = [
{
label: 'View',
submenu: [
{
role: 'reload'
},
{
role: 'forcereload'
},
{
role: 'toggledevtools'
}
]
},
{
label: 'Tools',
submenu: [
{
label: 'Check Cred',
click () {
let user = (global.sharedObj.cred) ? global.sharedObj.cred.user : "Default"
dialog.showMessageBox({
type: "info",
title: "Current Cred",
message: `The current user is: ${user}.`
})
}
}
]
}
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
}
function createWindow () {
// Use custom menu
createMenu()
// Create the browser window.
mainWindow = new BrowserWindow({width: 800, height: 600})
// and load the index.html of the app.
mainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
// 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
})
}
// 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 OS X 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 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 (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.
Figured out my issue. Basically iFrames don't work well with Electron. It's better to use a webview. However, a webview won't work for my purpose because of security restrictions when loading external websites, even a local file.
My solution has been to simply move all my local HTML files into div's inside my main index.html file and then simply toggle their viability as needed.