I have created an electron application that has multiple windows that can open ex. an about page and a preferences page. This is what my main.js looks like.
const {app, Tray, Menu, BrowserWindow} = require('electron');
//electron application stuff
const path = require('path'); //allows for use of path
const url = require('url'); //allows for loadURL and url.format
const iconPath = path.join(__dirname, 'icon.png'); //grab the icon
let tray = null; //set the tray to null
let win = null; //set the main window to null
app.on('ready', function(){
win = new BrowserWindow({width: 600, height: 400, resizable: false});
//create main window
win.setMenu(null); //the main window had no menu
win.loadURL(url.format({ //loads the webpage for the main window
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
win.openDevTools(); //starts the application with developer tools open
win.on('minimize',function(event){ //prevents standard minimize
function of a main window
event.preventDefault()
win.hide();
});
win.on('close', function (event) { //prevents the closing of the
aplication when the window closes
if( !app.isQuiting){
event.preventDefault()
win.hide();
}
return false;
});
tray = new Tray(iconPath); //create a new tray
var contextMenu = Menu.buildFromTemplate([ //start buliding out the
menu for the tray
{ label: 'Insomnia', click: function(){ //makes the main window reappear
win.show();
} },
{ label: 'About', click: function(){ //shows the about window
abt = new BrowserWindow({width: 400, height: 400, resizable: false});
abt.setMenu(null); //the about window has no menu
abt.loadURL(url.format({ //loads the webpage for the about window
pathname: path.join(__dirname, 'about.html'),
protocol: 'file:',
slashes: true
}))
} },
{
label: 'Preferences', click: function(){ //shows the about window
pref = new BrowserWindow({width: 400, height: 400, resizable: false});
pref.setMenu(null); //the about window has no menu
pref.loadURL(url.format({ //loads the webpage for the about window
pathname: path.join(__dirname, 'preferences.html'),
protocol: 'file:',
slashes: true
}))
}
},
{ label: 'Quit', click: function(){ //quit the application
app.isQuiting = true;
app.quit(); //quit called
} }
]);
tray.setToolTip('Insomnia'); //Honestly no clue but itll make the tray
say insomnia in some other place
tray.setContextMenu(contextMenu); //attach the menu to the tray
});
When the user opens the preferences window I have a button there that lets them store their preferences. When i use scripts from the java script file on win window they actually work. However using that same logic for the preferences window none of the java script functions actually run. This is what my preferences.html looks like
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Preferences</title>
</head>
<body BGCOLOR="#02D9CC" >
</div>
<center> <img src="sleep.png" alt="sleep" style="width:100px;height:100px;"> </center>
<div style="position:absolute;bottom:250px;margin-left:110px">
<center>
<input onclick="storeTimePreference()" type="radio" id="timeType" name="timeType" value="Military"> Military </input>
<input onclick="storeTimePreference()" type="radio" id ="timeType2" name="timeType" value="Standard" checked="checked"> Standard </input>
</center>
</div>
<div style="position:absolute;bottom:150px;margin-left:110px">
<center><input type="time" id="defaultTime" /><br>
<button onlick="readFile()" >Set Default Wake Up Time</button>
</center>
</div>
<div class ="version" style="position:absolute;bottom:5px;color:white;margin-left:225px">
<center >
Insomnia Version 1.0.0
</center>
</div>
<script src="./js/script.js"></script>
</body>
</html>
Where the readFile() script and storeTimePreference() functions are both part of the script.js included at the bottom. Which is the same way i did it for the index.html however here it doesn't work and I'm not sure why. Can anyone explain what I'm doing wrong here or why this doesn't work and what a workaround would be?
Related
Code
I am building an application menu for the menu bar, and I am using roles to automate some pre-defined functionalities like so:
{
label: 'File',
submenu: []
},
{
label: 'Edit',
submenu: [
{ role: 'undo' },
{ role: 'redo' },
{ type: 'separator' },
{ role: 'cut' },
{ role: 'copy' },
{ role: 'paste' }
]
},
{
label: 'View',
submenu: []
},
...
Issue
The following code part renders the menu and works like magic: { role: 'undo' } but only for normal operations.
However, when you programmatically replace the value of a form control (textbox, texarea, etc), and then do some manual changes to that form control, the Undo feature will no longer work.
What could have I missed on this? Is this a bug or something? Below are the versions of software used.
Environment
Software
Version
Electron
12.2.3
Node.js
14.16.0
MacOS
10.13.6
As Electron's Roles are only an extension of Chrome's functionality, the real test is to see if the same occurs when not using Electron.
I have tested your problem in both a standard html code base and an Electron code base.
Standard HTML Code Base
In the below file I have two text fields. One is the input field and the other is to provide feedback, just to be sure.
The button is to dynamically append some text to the end of the input field.
To test:
Type some text into the input field.
Try undo and redo functionality. // Success
Windows: Ctrl-Z and Ctrl-Y respectively.
MacOS: Cmd-Z and Shift-Cmd-Z respectively.
Now click the button to append some text.
Try undo and redo again. // Failure
After dynamically adding the text, undo and redo no longer works. Why? I don't know why, but this appears to be standard functionality for Chrome.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Undo / Redo Test</title>
</head>
<body>
<div>
<label for="input">Input: </label>
<input type="text" id="input">
<input type="button" id="button" value="Append 'text'">
</div>
<div>
<label for="output">Output: </label>
<input type="text" id="output" disabled>
</div>
</body>
<script>
let input = document.getElementById('input');
let output = document.getElementById('output');
input.addEventListener('keyup', () => {
output.value = input.value;
});
document.getElementById('button').addEventListener('click', () => {
let value = input.value + ' text';
input.value = value;
output.value = value;
});
</script>
</html>
Electron Code Base
Now, let's move the exact same html code over into an Electron application, so we can test it with the Edit -> Undo / Redo menu as well.
Testing via the keyboard shortcuts yields the same result as above.
Let's test it with the menu:
Type some text into the input field.
Select edit -> undo and redo functionality. // Succees
Now click the button to append some text.
Try edit -> undo and redo again. // Failure
The same result. After dynamically adding the text, undo and redo no longer works. A reflection of the Standard HTML Code Base.
main.js (main thread)
'use strict';
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const nodePath = require("path");
let window;
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
fullscreen: false,
resizable: true,
movable: true,
minimizable: true,
maximizable: true,
enableLargerThanScreen: true,
closable: true,
focusable: true,
fullscreenable: true,
frame: true,
hasShadow: true,
backgroundColor: '#fff',
show: false,
icon: nodePath.join(__dirname, 'icon.png'),
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
worldSafeExecuteJavaScript: true,
enableRemoteModule: false,
devTools: (! electronApp.isPackaged),
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html')
.then(() => { window.show(); });
return window;
}
electronApp.on('ready', () => {
window = createWindow();
});
electronApp.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
electronApp.quit();
}
});
electronApp.on('activate', () => {
if (electronBrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
preload.js (main thread)
'use strict';
// Import the necessary Electron components.
const contextBridge = require('electron').contextBridge;
const ipcRenderer = require('electron').ipcRenderer;
// White-listed channels.
const ipc = {
'render': {
// From render to main.
'send': [],
// From main to render.
'receive': [],
// From render to main and back again.
'sendReceive': []
}
};
// Exposed protected methods in the render process.
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);
}
}
}
);
index.html (render thread)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Undo / Redo Test</title>
</head>
<body>
<div>
<label for="input">Input: </label>
<input type="text" id="input">
<input type="button" id="button" value="Append 'text'">
</div>
<div>
<label for="output">Output: </label>
<input type="text" id="output" disabled>
</div>
</body>
<script>
let input = document.getElementById('input');
let output = document.getElementById('output');
input.addEventListener('keyup', () => {
output.value = input.value;
});
document.getElementById('button').addEventListener('click', () => {
let value = input.value + ' text';
input.value = value;
output.value = value;
});
</script>
</html>
Tested using:
Electron: 17.1.0
Node: 16.13.0
Chrome: 98.04.4758.102
Tested on:
Windows: 10
MacOS: 10.15.7
The Memento & Command Design Patterns
You could try and hook your undo and redo with something like execCommand but I think, if you're wanting to do it right, make it scalable and readable then you should really implement something along the lines of the Memento and Command design patterns.
Though a fair bit of work to setup, once working, it could be applied to any <form> field on the page, not only <input type="text"> and <textarea>.
But why stop there, make it applicable to page or application wide functionality as well (where it can be applied). EG: 'delete', 'rename', 'move', 'post', 'settings update', etc.
I want to create an electron.js app. The first window that opens should be a login window. But now I have the problem that I can't manage that when I press the login button the new main window opens.
At the moment I always get the error: Uncaught ReferenceError: require is not defined
at createBrowserWindow (login.js:16)
at HTMLFormElement. (login.js:9)
Here is my main.js file
// main.js
// Modules to control application life and create native browser window
const { app, BrowserWindow } = require('electron')
const path = require('path')
function createLoginWin() {
const loginWindow = new BrowserWindow({
width: 300,
height: 500,
minWidth: 300,
minHeight: 500,
maxWidth: 300,
maxHeight: 500,
icon: path.join(__dirname, 'assets/images/command.png'),
webPreferences: {
preload: path.join(__dirname, 'preload_login.js')
}
})
loginWindow.loadFile('./src/index.html');
loginWindow.setMenuBarVisibility(false);
}
// 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(() => {
createLoginWin()
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()
})
It is basically the file from the electron.js documentation.
Here is my html file:
<!--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>Document</title>
</head>
<body>
<div id="container">
<form id="login-form">
<input type="text" placeholder="Username or Email" id="username_in">
<input type="password" placeholder="password" id="password_in">
<button type="submit" id="submit_btn">Login</button>
</form>
</div>
<script src="./login.js"></script>
</body>
</html>
And here is my login.js file:
const loginForm = document.getElementById('login-form')
loginForm.addEventListener("submit", (event) => {
event.preventDefault();
const test = document.getElementById('container')
test.style.backgroundColor = 'black';
console.log('TestHTML')
createBrowserWindow();
console.log('TestHTML2')
});
function createBrowserWindow() {
console.log('TestJS1')
const remote = require('electron').remote;
console.log('TestJS2')
const BrowserWindow = remote.BrowserWindow;
const win = new BrowserWindow({
height: 600,
width: 800,
webPreferences: {
nodeIntegration: true
}
});
win.loadFile('./index.html')
win.show()
}
const loginForm = document.getElementById('login-form')
loginForm.addEventListener("submit", (event) => {
event.preventDefault();
const test = document.getElementById('container')
test.style.backgroundColor = 'black';
console.log('TestHTML')
createBrowserWindow();
console.log('TestHTML2')
});
function createBrowserWindow() {
console.log('TestJS1')
const remote = require('electron').remote;
console.log('TestJS2')
const BrowserWindow = remote.BrowserWindow;
const win = new BrowserWindow({
height: 600,
width: 800,
webPreferences: {
nodeIntegration: true
}
});
win.loadFile('./index.html')
win.show()
}
Im new in this electron space, and have a problem. i didn´t manage to get my custom buttons to work. This drives me crazy. Anyone an idea what i´m doing wrong? the Buttons do simply nothing when i click. My code is an example from an older youtube Tutorial, and i think things have changed since then.
Here is my code:
menuHandler.js
const $ = require('jquery');
const { remote } = require('electron');
var win = remote.getCurrentWindow();
$('#minimize').click(function () {
win.minimize();
});
$('#close').click(function () {
win.close();
});
$('#maximize').click(function () {
if (win.isMaximized()) {
win.unmaximize();
} else {
win.maximize();
}
console.log(win.isMaximized());
});
my index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="css/reset.css" rel="stylesheet">
<link href="css/menu.css" rel="stylesheet">
<script type ="text/javascript"> src="/src/js/menuHandler.js"</script>
</head>
<body>
<div id="container">
<nav>
<div id="buttons">
<div id="minimize">
<span id="minimize-font">—</span>
</div>
<div id="maximize">
<span id="size-font">◻</span>
</div>
<div id="close">
<span id="close-font" >✕</span>
</div>
</div>
</nav>
</div>
</html>
</body>
</html>
and my app.js
const { app, BrowserWindow } = require('electron');
const path = require('path');
const url = require('url');
let win;
var IMG_DIR = '/img/';
var APP_DIR = '/src/';
function createWindow() {
win = new BrowserWindow({
width: 1100,
height: 750,
minWidth: 930,
minHeight: 650,
frame: false,
/*title: "Nader | Initio",
resizable: true, */
backgroundColor: '#1A373E',
icon: path.join(__dirname, IMG_DIR, 'icon.png'),
webPreferences: {
nodeIntegration: true
}
});
win.openDevTools()
win.loadURL(url.format({
pathname: path.join(__dirname, APP_DIR, 'index.html'),
protocol: 'file:',
slashes: true
}))
}
app.on('ready', createWindow);
app.on('window-all-closed', () => {
if(process.platform !== 'darwin') {
app.quit();
}
});
thank you for your help!
Several small things are needed to make your code work:
<script type ="text/javascript"> src="/src/js/menuHandler.js"</script>
needs to be
<script type="text/javascript" src="/src/js/menuHandler.js"></script>
Since Electron 10, released in August 2020, you need to set enableRemoteModule: true in the webPreferences if you want to use remote in the renderer process.
Lastly, you should register the button click event handlers after the page is loaded. Otherwise, chances are the elements don't exist yet.
$(window).on("load", function() {
console.log("loaded");
$("#minimize").click(function () {
console.log("minimize");
win.minimize();
});
// etc.
});
As a general tip I'd advise to make use of the DevTools, which you already activate in your code. There, in the Console tab, you can see JavaScript errors happening in the renderer process.
I am working on an about this app window on Windows and I want to make a custom menu bar for my about window. Since I already have a custom menu is there a way I can create another one and apply it only to that specific window?
Side note:
Here is my code for the new window that is supposed to stop it from being adjusted and going into full screen, but for some reason the minimize and enlarge button still work.
app.on('ready', createWindow);
electron.app.on('ready', () => {
//Triger update check
if (!isDev) {
autoUpdater.checkForUpdates();
}
})
function createWindow(){
//create brower window
win = new BrowserWindow({
backgroundColor: '#2e2c29',
width: 800,
height: 600,
//transparent: true,
frame: false,
titleBarStyle: 'hidden',
backgroundColor: '#0000',
webPreferences: {
nodeIntegration: true
}
});
//Quit when all windows are closed
app.on('window-all-closed', () => {
app.quit()
})
app.once('ready', function() {
const template = [
{
label: 'File',
submenu: [
{
label: 'About Hubris',
click: () =>
openAboutWindow()
},
{ type: 'separator' },
{ role: 'hide' },
{ role: 'hideothers' },
{ role: 'unhide' },
{ type: 'separator' },
{ role: 'quit' }
]
},
{
label: 'View',
submenu: [
{ role: 'minimize' },
{ role: 'zoom' },
{ type: 'separator' },
{ role: 'togglefullscreen' }
]
},
]
const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)
//here is the code for the about window
var newWindow = null
function openAboutWindow() {
if (newWindow) {
newWindow.focus()
return
}
newWindow = new BrowserWindow({
height: 439,
resizable: false,
width: 599,
title: 'About Hubris',
minimizable: false,
fullscreenable: false,
frame: false,
titleBarStyle: 'hidden',
})
newWindow.loadURL('file://' + __dirname + '/about-this-app.html')
newWindow.on('closed', function() {
newWindow = null
})
}
});
You can switch menus on the fly. I have an app with 'editor' and 'presentation' modes. I create and store a menu for each mode (they have different menu items):
let editorMenu = Menu.buildFromTemplate(menuTemplate);
and subscribe to the relevant window events (focus, blur, etc). Then when a window gets focus
Menu.setApplicationMenu(editorMenu);
To do this, you have to import menu from electron in at the top of our main.js file:
// From
const {app, BrowserWindow} = require('electron')
// To
const {app, BrowserWindow, Menu} = require('electron').
Then, near the bottom of our createWindow() function, we add:
function createWindow () {
// some code here removed for brevity
var menu = Menu.buildFromTemplate([
{
label: 'Menu',
submenu: [
{label:'Adjust Notification Value'},
{label:'CoinMarketCap'},
{label:'Exit'}
]
}
])
Menu.setApplicationMenu(menu);
}
Next, we reference Menu.buildFromTemplate([{}]), which is where our menu is actually defined and built, within a series of arrays and objects.
The "label" represents the name you want your menu to display so put what you like.
The "submenu" property is an array of objects, with each object defining the actual menu items displayed when the label is clicked.
Finally, use .setApplicationMenu to set the menu. If you save the project, and run npm start in the console, you will see the menu with it's items (array) being displayed but if your click on them nothing happens.
You can change this by going back to our main.js, add the following code to make our Exit button close the application:
var menu = Menu.buildFromTemplate([
{
label: 'Menu',
submenu: [
{label:'Adjust Notification Value'},
{label:'CoinMarketCap'},
{
label:'Exit',
click() {
app.quit()
}
}
]
}
])
So, to make a menu item clickable, we simply add a comma after the label value, and reference "click() { }"
In this case, we're calling app.quit()" when the Exit submenu item is clicked. Give it a try by running npm start in the console and click Exit.
That's it!
I'm new to Electron and JavaScript. I'm building an Electron app. I know how to open a URL in a browser via clicking an item in native menu(by studying the documentation), but I need to open a html file in another Electron window using an Electron's native menu click. If I have my menu structure like below, how can I achieve this? Please help.
const {Menu} = require('electron');
const nativeMenus = [
{
label: 'About',
submenu: [
{
label: 'About',
click () {--- code to open about.html file in another electron window}
}
]
}
]
const menu = Menu.buildFromTemplate(nativeMenus);
Menu.setApplicationMenu(menu);
If it is all in the main.js just create a function to create a new window and then call that on menu item click.
const { Menu } = require('electron')
const ipc = require('electron').ipcRenderer
const nativeMenus = [
{
label: 'About',
submenu: [
{
label: 'About',
click() {
openAboutWindow()
}
}
]
}
]
const menu = Menu.buildFromTemplate(nativeMenus)
Menu.setApplicationMenu(menu)
var newWindow = null
function openAboutWindow() {
if (newWindow) {
newWindow.focus()
return
}
newWindow = new BrowserWindow({
height: 185,
resizable: false,
width: 270,
title: '',
minimizable: false,
fullscreenable: false
})
newWindow.loadURL('file://' + __dirname + '/views/about.html')
newWindow.on('closed', function() {
newWindow = null
})
}
Let me know if this works for you.
You stored your BrowserWindow instance in a variable, for the sake of this answer I am gonna suppose it's win. user7252292 did provide you with a good answer. But if you need another window then you will have to create another function for the same purpose. I will make a function for creating modals. They are basically windows that need a parent window and you cannot respond to the parent window until the modal is closed.
const createModal = (htmlFile, parentWindow, width, height) => {
let modal = new BrowserWindow({
width: width,
height: height,
modal: true,
parent: parentWindow,
webPreferences: {
nodeIntegration: true
}
})
modal.loadFile(htmlFile)
return modal;
}
const {Menu} = require('electron');
const nativeMenus = [
{
label: 'About',
submenu: [
{
label: 'About',
click () {
createModal("myfile.html",win,600,800); // Win is the browerwindow instance
}
}
]
}
]
const menu = Menu.buildFromTemplate(nativeMenus);
Menu.setApplicationMenu(menu);
Plus, you can store the createModal in a variable and modify the modal because it returns the modal itself.