Many desktop applications offers manipulation and customization features. Those features are usually (not always) in a different window than the original application, so while working with Electron you will need to learn how to share the information from one window to another.
In this article, we'll show you how you can use the IPCMain and IPCRenderer modules of Electron to achieve this simple task.
Note
The process can be made bidirectional, so you can follow to send information from the first window to the second window and viceversa.
1. Configure 2 Windows (optional)
If you already have 2 Windows instances in the main process, then skip this step. In our example we'll have 2 windows with the variables mainWindow
and secondWindow
. These windows are created in the main process (from main.js
) and they will show up simultaneously, so you can change their behaviour if you want. The important thing is that you have 2 Windows and they're accesible through variables:
// Keep a global reference of the windows object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
let secondWindow;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({ width: 800, height: 600 });
secondWindow = 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
}))
// and load the second window.
secondWindow.loadURL(url.format({
pathname: path.join(__dirname, 'otherfile.html'),
protocol: 'file:',
slashes: true
}))
// 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;
secondWindow = null;
})
}
Both of the windows load 2 different HTML files.
2. Add listener in the main process to share data between windows
Now that you have the 2 windows that will communicate with each other, you can create the endpoint in the main process that works as a bridge between them. Using the ipcMain module of Electron, you can add an "event listener" in the main (background process) where you have access to the 2 windows, then this event can be triggered from the view of the first window using the ipcRenderer.
The following code adds a single event listener (in main.js
) that will be executed when the 'request-update-label-in-second-window
' (dat name) event is triggered in the view of the first window. Inside the callback, as you are in the main.js
file where the 2 windows are accesible, you can access the webContents
property of the window where you want to send the information. The webContents
is an EventEmitter. It is responsible for rendering and controlling a web page and is a property of the BrowserWindow
object. Using the send
method, you can trigger an event in the renderer process of the second window (namely action-update-label
):
const { ipcMain } = require('electron');
// Attach event listener to event that requests to update something in the second window
// from the first window
ipcMain.on('request-update-label-in-second-window', (event, arg) => {
// Request to update the label in the renderer process of the second window
// We'll send the same data that was sent to the main process
// Note: you can obviously send the
secondWindow.webContents.send('action-update-label', arg);
});
Now, our bridge in the background is built. As next you need to build the entry point to the bridge in the second window.
3. Add listener in the view of the second window to receive data
The rest of the logic is very obvious, as you trigger the event action-update-label
from the main process, you need to do something in the second window when this event is triggered. Proceed to add the event listener with the ipcRenderer module in the renderer process of the second window (otherfile.html
) with the following code:
// Require ipcRenderer
const { ipcRenderer } = require('electron');
// When the action-update-label event is triggered (from the main process)
// Do something in the view
ipcRenderer.on('action-update-label', (event, arg) => {
// Update the second interface or whatever you need to do
// for example show an alert ...
alert("Hello, you did something in the first window !");
// arg contains the data sent from the first view
console.log(arg);
});
Now when the event 'request-update-label-in-second-window
' is triggered from the first window in the main process, the main process will trigger the action-update-label
event that will do what you need.
4. Trigger share event
As final step, you only need to trigger the 'request-update-label-in-second-window
' in the first window (in the renderer process):
const { ipcRenderer } = require('electron');
// Some data that will be sent to the main process
// Feel free to modify the object as you wish !
let Data = {
message: "Hello World !"
};
// Trigger the event listener action to this event in the renderer process and send the data
ipcRenderer.send('request-update-label-in-second-window', Data);
This will follow the mentioned logic, sending the Data
object with a property message to the renderer process in the second view.
Example
The following 3 file based example illustrate the mentioned steps in a simple application:
main.js
const electron = require('electron');
const path = require('path');
const { ipcMain } = require('electron');
// Module to control application life.
const app = electron.app;
// Module to create native browser window.
const BrowserWindow = electron.BrowserWindow
const url = require('url')
// Keep a global reference of the windows object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;
let secondWindow;
function createWindow() {
// Create the browser window.
mainWindow = new BrowserWindow({ width: 800, height: 600 });
secondWindow = 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
}))
// and load the second window.
secondWindow.loadURL(url.format({
pathname: path.join(__dirname, 'otherfile.html'),
protocol: 'file:',
slashes: true
}))
// Attach event listener to event that requests to update something in the second window
// from the first window
ipcMain.on('request-update-label-in-second-window', (event, arg) => {
// Request to update the label in the renderer process of the second window
secondWindow.webContents.send('action-update-label', arg);
});
// 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;
secondWindow = 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()
}
})
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Electron Application</title>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0, maximum-scale=1, minimum-scale=1">
</head>
<body>
<div id="app">
Hello First Window
<br />
<input type="text" id="field" value="This String Will be Sent to the second Window" />
<input type="button" id="btn" value="Update Label in Second Window with Text" />
</div>
<script>
const { ipcRenderer } = require('electron');
document.getElementById("btn").addEventListener("click", () => {
// Some data that will be sent to the main process
let Data = {
message: document.getElementById("field").value,
backgroundColor: "black",
color: 'white'
};
// Trigger the event listener action to this event in the renderer process and send the data
ipcRenderer.send('request-update-label-in-second-window', Data);
}, false);
</script>
</body>
</html>
otherfile.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Electron Second Window</title>
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=0, maximum-scale=1, minimum-scale=1">
</head>
<body>
<div id="app">
Hello Second Window
<br />
<span id="label">Hello This Label Will be updated </span>
</div>
<script>
const { ipcRenderer } = require('electron');
ipcRenderer.on('action-update-label', (event, arg) => {
// Update the second window label content with the data sent from
// the first window :) !
let label = document.getElementById("label");
label.innerHTML = arg.message;
label.style.color = arg.color;
label.style.backgroundColor = arg.backgroundColor;
});
</script>
</body>
</html>
The execution of the previous code in Electron, will display 2 windows. In the first window you will find a simple text input and a button, when it's clicked, it will send an object with some CSS style (background color black and text color white) and the text of the input to the second window. In the second window, this object will be processed and shown in the view:
Note that you can follow the same process but inverted (from the second window to the first one).
Happy coding !