How to access DOM elements in electron?
DOM can not be accessed in the main process, only in the renderer that it belongs to.
There is an ipc
module, available on main process as well as the renderer process that allows the communication between these two via sync/async messages.
You also can use the remote module to call main process API from the renderer, but there's nothing that would allow you to do it the other way around.
If you need to run something in the main process as a response to user action, use the ipc
module to invoke the function, then you can return a result to the renderer, also using ipc
.
Code updated to reflect actual (v0.37.8) API, as @Wolfgang suggested in comment, see edit history for deprecated API, if you are stuck with older version of Electron.
Example script in index.html
:
var ipc = require('electron').ipcRenderer;
var authButton = document.getElementById('auth-button');
authButton.addEventListener('click', function(){
ipc.once('actionReply', function(event, response){
processResponse(response);
})
ipc.send('invokeAction', 'someData');
});
And in the main process:
var ipc = require('electron').ipcMain;
ipc.on('invokeAction', function(event, data){
var result = processData(data);
event.sender.send('actionReply', result);
});
How to access html DOM element through electron js
To get the innerText
(or equivalent) of the div
element in your index.html
window, you will need to send a message to your render thread requesting this information. Following this, you will then need your render thread to send the innerText
back to your main thread for processing (saving).
Electron's Inter-Process Communication can be confusing at times but if implemented correctly it can be simple and safe.
To learn more about the processes involved you will want to read and try and understand the following links:
- ipcMain.on()
- webContents.send()
- Context Isolation
- contextBridge
Let's begin with building out your html document first. At a minimum it must include an editable <div>
tag and a 'save' button.
index.html
(render thread)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Test Editor</title>
<style>
#editor {
width: 50vw;
height: 50vh;
}
<style>
</head>
<body>
<div id="content" contenteditable="true"></div>
<input type="button" id="save" value="Save">
</body>
<script src="script.js"></script>
</html>
See Example: A simple but complete rich text editor for some cool ideas.
Now let's add the 'save' button and IPC message functionality.
script.js
(render thread)
// IIFE (Immediately Invoked Function Expression)
(function() => {
let content = document.getElemetById('content').innerText;
document.getElementById('save').addEventListener('click', saveContent(content));
window.ipcRender.receive('editor:getContent', () => { saveContent(content); });
});
function saveContent(content) {
window.ipcRender.send('editor:saveContent', content);
}
Here is your main.js
file with the following updates.
- Add Electron's
ipcMain
module. - Add the
win
object to the top scope so it is noit garbage collected. - Listen for message(s) from the render thread (using an IFFE).
- Add the
saveContent()
function (to be fully fleshed out by you). - Remove
const
from thenew BrowserWindow
line. - Return
win
from thecreateWindow()
function so it can be referenced later on. - Update the globalShortcut
ctrl+s
function.
main.js
(main thread)
const { app, BrowserWindow, globalShortcut, ipcMain } = require('electron');
const path = require('path');
let win = null;
// IIFE (Immediately Invoked Function Expression)
(function() => {
ipcMain.on('editor:saveContent', (event, content) => { saveContent(content); });
})();
function saveContent(content) {
console.log("saving...");
// Save content...
console.log("saved...");
}
// Create the main window
function createWindow() {
// Adjust a few settings
win = new BrowserWindow({
// What the height and width that you open up to
width: 500,
height: 600,
// Minimun width and height
minWidth: 400,
minHeight: 400,
icon: __dirname + '/icon.png',
// Change the window title
title: "text editor",
webPreferences: {
// Preload so that the javascript can access the text you write
preload: path.join(__dirname, 'preload.js'),
}
});
win.loadFile('index.html');
// Remove that ugly title bar and remove unnecessary keyboard shortcuts
win.removeMenu();
return win;
}
// Create window on ready so that no nasty errors happen
app.on('ready', () => {
// Create the window.
win = createWindow();
// Global shortcut so the user has the ability to exit
globalShortcut.register('ctrl+e', () => {
console.log("exiting...");
app.exit();
});
// Global shortcut to save editable content.
globalShortcut.register('ctrl+s', () => {
console.log('ctrl+s pressed.');
win.webContents.send('editor:getContent');
});
})
// when all windows close this app actually closes
app.on('window-all-closed', () => {
if (process !== 'darwin') app.quit();
})
Note that I have left the actual saving to the filesystem functionality to you. See Node.js: fs.writeFile() for more information.
Ok, the last piece of the puzzle is a working preload.js
script. This is the script that grants the use of a list of whitelisted channels between the main and render threads.
In here we add the editor:saveContent
and editor:getContent
channel names.
preload.js
(main thread)
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [
'editor:saveContent'
],
// From main to render.
'receive': [
'editor:getContent'
],
// From render to main and back again.
'sendReceive': []
}
};
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);
}
}
}
);
Note that I do not perform any functions so-to-speak in the
preload
script. I only manage a list
of channel names and the transfer of any data associated with those channel names.
Access DOM in Electron BrowserView
You'll have to get this information via view.webContents.executeJavaScript("document.getElementById(...)")
.
The page is loaded in a separate renderer process, so you can't really just access that information from your main process, except via the executeJavaScript` API.
If you have control over the page, you could send down the appropriate information via IPC, though you won't be able to send down the the entire DOM Element over since it won't be serialized correctly as per here:
Cloning DOM nodes likewise throws a
DATA_CLONE_ERR
exception.
Electron - Access DOM elements in a loaded document using remote url
I prefered to use webview
and i set preload
tag on the webview based on the link below and every thing works as expected.
Github issues page
Related Topics
Get Function Name in JavaScript
Appending Array to Formdata and Send via Ajax
Reactjs Syntheticevent Stoppropagation() Only Works with React Events
Javascript/Jquery Check Broken Links
Disabling the Long-Running-Script Message in Internet Explorer
How to Listen for a Click-And-Hold in Jquery
Get Decimal Portion of a Number with JavaScript
How to Disable Right-Click Context-Menu in JavaScript
How to Update States 'Onchange' in an Array of Object in React Hooks
Adding Prototype to JavaScript Object Literal
Paste an Image from Clipboard Using JavaScript
Passing Python Data to JavaScript via Django
Replace All Spaces in a String with '+'
Capture Click on Div Surrounding an Iframe
Pagination on a List Using Ng-Repeat
What Are Alternatives to Extjs
How to Set the Id Attribute of a HTML Element Dynamically with Angularjs (1.X)