Reputation: 553
I am trying to create a Debug app for Body Positioning Data. This data is received as JSON via MQTT in my receiveAndConversion.js
. I was able to receive the data properly and print to console. So far so good. But now I want in my main window the values that I receive to show up (make the screen green for example when hand closed).
I have tried a lot of things including ipc and adding
nodeIntegration: true, contextIsolation: false, enableRemoteModule: true,
as preferences in main.js
Researching this is kind of a pain, as I always get to questions where the person tries to change the DOM from main.js
instead of the renderer.
I am new to electron and have been spending hours on the documentation but all their examples are either triggerd on launch of the app or by a button (user interaction). I need to change the DOM when a new message is received, independent on user interaction or other things.
My structure at the moment is like this:
Except for receiveAndConversion.js
, renderer.js
and the mentioned Preferences in main.js
, the code is more or less the same as The quick start guide.
The main issue that seems to block me is, that I cant seem to be able to call my renderer.js from my receiveAndConversion.js mqttClient.on()
which runs when I have received a new message. My thinking was I could just call from there a render function in render.js but as it is called from receiveAndConversion.js I get a "document is not defined" error (at least I believe that's the reason).
I would really appreciate if you had an idea for me on how to implement this without having to put everything in main.js.
You can find the complete code below.
// main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow, ipcMain } = require('electron')
//const Renderer = require('electron/renderer')
const path = require('path')
const mqttClient = require('./receiveAndConversion.js')
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
//nativeWindowOpen: true,
nodeIntegration: true,
contextIsolation: false,
enableRemoteModule: true,
preload: path.join(__dirname, 'preload.js')
}
})
// 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(() => {
//
//ipcMain.handle('left-hand-closed', (event, arg) => {
// console.log('left hand is closed');
//}
//)
createWindow()
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 (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', () => {
if (process.platform !== 'darwin') app.quit()
})
// 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.
<!--index.html-->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<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>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
We are using Node.js <span id="node-version"></span>,
Chromium <span id="chrome-version"></span>,
and Electron <span id="electron-version"></span>.
<!-- Create different field which will be used to visualise if hands are open or not. So one field for left hand one field for right hand. -->
<div id="left-hand"></div>
<div id="right-hand"></div>
<!-- You can also require other files to run in this process -->
<script src="./renderer.js"></script>
</body>
</html>
//renderer.js
// get info of open or closed hands from receiveAndConversion.js
// then make the left-hand div or right-hand div green or red depending on open or closed
//const {ipcRenderer} = require('electron')
// //write something in the divs
// leftHandDiv.innerHTML = 'Left Hand: ' + leftHandClosed
// rightHandDiv.innerHTML = 'Right Hand: ' + rightHandClosed
// ipcRenderer.handle('left-hand-closed', (event, arg) => {
// leftHandDiv.innerHTML = 'Left Hand: ' + arg
// }
// )
// ipcRenderer.handle('right-hand-closed', (event, arg) => {
// rightHandDiv.innerHTML = 'Right Hand: ' + arg
// }
// )
function handChange(leftHandClosed, rightHandClosed) {
//get the divs from the html file
const leftHandDiv = document.getElementById('left-hand')
const rightHandDiv = document.getElementById('right-hand')
//check if the hand is open or closed
if (leftHandClosed) {
leftHandDiv.style.backgroundColor = 'green'
console.log('left hand is closed');
} else {
leftHandDiv.style.backgroundColor = 'red'
console.log('left hand is open');
}
if (rightHandClosed) {
rightHandDiv.style.backgroundColor = 'green'
console.log('right hand is closed');
} else {
rightHandDiv.style.backgroundColor = 'red'
console.log('right hand is open');
}
}
//make handChange() usable outside of the renderer.js
module.exports = {
handChange
}
// preload.js
// All of the Node.js APIs are available in the preload process.
// It has the same sandbox as a Chrome extension.
window.addEventListener('DOMContentLoaded', () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector)
if (element) element.innerText = text
}
for (const dependency of ['chrome', 'node', 'electron']) {
replaceText(`${dependency}-version`, process.versions[dependency])
}
})
Upvotes: 1
Views: 449
Reputation: 3217
To keep your receiveAndConversion.js
file separate from your main.js
file and send signals via IPC to index.html
, you need access to Electrons window instance of index.html
. This can be achieved by using a "getter" method. Separating the creation (and getting) of mainWindow
into its own file will therefore be needed.
Good use of a preload.js
script dictates setting nodeIntegration: false
and contextIsolation: true
. Additionally, there should be no need to use any form of remote modules. I have re-worked your preload.js
script to be used only as a form of communication between the main process and render process. Various forms of preload.js
scripts can be used, but in this instance I have used the most simplified (but not very flexible) approach.
Lastly, not having access to your receiveAndConversion.js
file, I have mocked a random hand / position data stream.
As you can see, separating domains into their own files can keep the overall application logical, easy to maintain and bug free when adding additional features.
main.js
(main process)
// Require the necessary Electron modules
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
// Require the necessary Node modules
const nodePath = require('path');
// Require the necessary Application modules
const appMainWindow = require(nodePath.join(__dirname, './main-window'));
const appReceiveAndConversion = require(nodePath.join(__dirname, './receiveAndConversion'));
// Prevent garbage collection
let mainWindow;
electronApp.on('ready', () => {
mainWindow = appMainWindow.create();
appReceiveAndConversion.run();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
appMainWindow.create();
}
});
Having the create()
and get
methods of the mainWindow
in its own file allows for inclusion within any other file that need reference to the mainWindow
.
main-window.js
(main process)
// Require the necessary Electron modules
const electronBrowserWindow = require('electron').BrowserWindow;
// Require the necessary Node modules
const nodePath = require('path');
let mainWindow;
function create() {
mainWindow = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, './preload.js')
}
});
mainWindow.loadFile('index.html')
.then(() => { mainWindow.show(); })
return mainWindow;
}
function get() {
return mainWindow;
}
module.exports = {create, get}
receiveAndConversion.js
(main process)
Mocked...
// Require the necessary Node modules
const nodePath = require('path');
// Require the necessary Application modules
const appMainWindow = require(nodePath.join(__dirname, './main-window'));
let mainWindow;
// Generate a random number
function randomNumber(lower, upper) {
return Math.floor(Math.random() * (upper - lower + 1) + lower)
}
// An infinitely polled function
function listener() {
let hand = (randomNumber(0, 1)) ? 'leftHand' : 'rightHand';
let position = (randomNumber(0, 1)) ? 'opened' : 'closed';
console.log(hand + ' ' + position); // Testing
mainWindow.webContents.send(hand, position);
}
// Called from main.js
function run() {
mainWindow = appMainWindow.get();
setInterval(() => { listener(); }, 350);
}
module.exports = {run}
preload.js
(main process)
A simple (but rigid) example.
// Import the necessary Electron components
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// Exposed protected methods in the render process
contextBridge.exposeInMainWorld(
// Allowed 'ipcRenderer' methods
'electronAPI', {
// From main to render
leftHand: (position) => {
ipcRenderer.on('leftHand', position);
},
rightHand: (position) => {
ipcRenderer.on('rightHand', position);
}
});
Instead of changing background colors via JavaScript, it is better to change class names or even better again, data attribute values in this instance.
For simplicity, I have incorporated a revised
renderer.js
between the<script>
tags.
index.html.js
(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';"/>
<style>
body {
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-flow: row nowrap;
}
#left-hand,
#right-hand {
flex: 1 0 50%;
display: flex;
justify-content: center;
align-items: center;
font-size: 3em;
}
#left-hand[data-position="closed"],
#right-hand[data-position="closed"] {
background-color: darkred;
}
#left-hand[data-position="opened"],
#right-hand[data-position="opened"] {
background-color: darkgreen;
}
</style>
</head>
<body>
<div id="left-hand" data-position="closed">Left Hand</div>
<div id="right-hand" data-position="closed">Right Hand</div>
</body>
<script>
let leftHand = document.getElementById('left-hand');
let rightHand = document.getElementById('right-hand');
window.electronAPI.leftHand((event, position) => {
console.log('leftHand ' + position); // Testing
leftHand.dataset.position = position;
});
window.electronAPI.rightHand((event, position) => {
console.log('rightHand ' + position); // Testing
rightHand.dataset.position = position;
});
</script>
</html>
Upvotes: 1