Electron prevent main window from closing - javascript

I am writing an application in electron where if a user has a unsaved file open I want to prompt the user before saving it. I found this example code online:
window.onbeforeunload = (e) => {
var answer = confirm('Do you really want to close the application?');
e.returnValue = answer; // this will *prevent* the closing no matter what value is passed
if(answer) { mainWindow.destroy(); } // this will close the app
};
This code strangely works if the dialogs Yes, Cancel or X button is pressed within a few seconds of appearing but if you let the dialog rest on screen for a little and then click a button the application will close no matter what is pressed.
This code is located in the my main script file called by index.html

Really strange behavior! I cannot explain why it's happening, but can give you a workaround implemented in main process.
You can use electron's dialog module and create the same confirmation dialog with electron. This one works as expected.
main.js
const { app, BrowserWindow, dialog } = require('electron')
const path = require('path')
app.once('ready', () => {
let win = new BrowserWindow()
win.loadURL(path.resolve(__dirname, 'index.html'))
win.on('close', e => {
let choice = dialog.showMessageBox(
win,
{
type: 'question',
buttons: ['Yes', 'No'],
title: 'Confirm',
message: 'Do you really want to close the application?'
}
)
if (choice === 1) e.preventDefault()
})
})

It might be possible only when DevTools window is activated.
In any case, prefer working with the event close as pergy suggested above. This is so far the best approach.
But be aware that e.preventDefault() is spreading everywhere in the code. Once you managed properly the preventDefault() you need to turn the variable e.defaultPrevented = false to get back to the natural behavior of your app.
Actually, it seems e.preventDefault() function is turnind the variable e.defaultPrevented to true until you change its value.

In my case, I had to use a variable called modificationEnCours which is true when I don't want to close my window and then false if I want to like that:
let mainWindow
let mainMenu // Menu de la fenêtre principale
app.on('ready', () => {
// Listen for app to be ready
// Create the mainWindow
mainWindow = new BrowserWindow({
width: 1024,
height: 768,
minHeight: 350,
minWidth: 500,
frame: true,
webPreferences: {
nodeIntegration: true
}
})
// Quit app when window is closed
mainWindow.on('close', function(e){
console.log('close')
if (modificationEnCours){
e.preventDefault()
if(msgBoxVerifieSauvegarde('Question','Voulez-vous enregistrer avant de quitter ?')) {
modificationEnCours=false
app.quit()
}
} else if (process.platform !== 'darwin') {
modificationEnCours=false
app.quit()
mainWindow = null
}
})
// Load html in window
mainWindow.loadFile(path.join(__dirname, 'mainWindow.html'))
// Build menu from template
mainMenu = Menu.buildFromTemplate(mainMenuTemplate)
// Insert menu
Menu.setApplicationMenu(mainMenu)
})

Related

cant click the button (electron)

im try to do a project that connect pc to pc like screencast. when following the coding online im having a problem when trying to click the button. i cant click the button to pup up the id code.
This the code for app.js
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('path')
const { v4: uuid4} = require('uuid');
const screenshot= require('screenshot-desktop');
var socket = require('socket.io-client')('http://172.20.10.3:5000');
var interval;
const createWindow = () => {
// Create the browser window.
const mainWindow = new BrowserWindow({
width: 500,
height: 150,
webPreferences: {
preload: path.join(__dirname, 'index.js')
}
})
// and load the index.html of the app.
mainWindow.removeMenu();
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(() => {
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()
})
ipcMain.on("start-share", function(event , arg){
var uuid = uuid4();
socket.emit("join-message", uuid);
event.reply("uuid", uuid);
})
ipcMain.on("stop-share", function(event, arg){
})
// 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.
This is the index.js file
const ipcRenderer = require ('electron').ipcRenderer;
window.onload = function(){
ipcRenderer.on("uuid", (event, data)=>{
document.getElementById("code").innerHTML = data;
})
}
function startShare(){
ipcRenderer.send("start-share", {} );
document.getElementById("start").style.display = "none";
document.getElementById("stop").style.display = "block";
}
function stopShare(){
ipcRenderer.send("stop-share", {});
document.getElementById("stop").style.display = "none";
document.getElementById("start").style.display = "block";
}
this is the popup when running the code. i cant click the start button
enter image description here
im following this video from (youtube)
this the documentation that i follow in electronelectron website
if someone having problem to see the codes i will try to edit and insert more code or maybe send a zip file. im really needed some help in developed this project for education purpose
im expecting some guide to develop this project. if can i would like the same in the video but i have follow that video from start to end but stuck in the middle of the video. really needed the help
It seems you didn't add your startShare and stopShare functions to your index.html. The preload script is used only for a bridge between your main process and ui process. It doesn't have window object and doesn't attach itself to the index.html.
webPreferences: {
preload: path.join(__dirname, 'index.js')
}
You have to create a bridge and pass ipcRenderer.on function to your ui process. Or you also can add nodeIntegration: true to your webPreferences object. You can read some disadvantages about it.
Then you need to attach your index.js file to the index.html using <script> tag.

How can Electron communicate with its Angular-Frontend?

I'm running an Angular Frontend within a frameless Electron-Window. Because the window is frameless, I need to implement the minimizing/maximizing/unmaximizing/closing-behaviour myself. I have a button for maximize and one for unmaximize and would like to hide one of them at all times, depending on the window state.
I have node-integration set to false and wonder how I can communicate from Electron to my Angular-Frontend. Then I would only need to find a way to get my app-window and emit an event, whenever it is maximized/unmaximized and then change my UI accordingly.
My communication from Angular to Electron works like this:
in Angular I have an 'electronService' which is injected in my components and calls the electron functions.
In my preload.js I expose a function from my main.js to my renderer-process like this:
const { ipcRenderer, contextBridge } = require('electron');
contextBridge.exposeInMainWorld('electron', {
maximizeWindow: () => {
return ipcRenderer.invoke('electron::maximize-window');
}
});
And in my main.js I handle the incoming calls like this:
app.whenReady().then(() => {
ipcMain.handle('electron::maximize-window', maximizeWindow);
});
function maximizeWindow(_) {
...
}
Is there a way to do this in the opposite direction?
Though I don't use Angular, the implementation of your maximize / restore functionality is quite simple once laid out, understood and implemented correctly. The use of IPC and the detection and communication of your window state will be core to its functionality.
Desired functionality:
When the window is maximised, show the restore button.
When the window is not maximised (restored), show the maximise button.
Implementation:
Our render will show all four buttons: Minimise, restore, maximise and close.
On render button click, send an IPC message to the main process to implement window functionality.
Depending on the window state (change) in the main process, send a message to the render process to show / hide respective buttons.
On window creation, send a message to the render process to show / hide respective buttons.
Detect when the title bar is double-clicked to toggle render buttons between maximise and restore.
You may wonder why point 3 is needed? It exists due to points 4 and 5. Point 4 is used on window creation. No matter what settings you use to create your window (manually or pulled from a .json file), your newly created window will dynamically display the correct button.
Point 5 is when you double-click your title bar in and out of maximise / restore.
Within your preload.js script we need two functions. One to indicate which render button was click (point 2) and the other to receive the state of the window (point 3).
preload.js (main process)
const { ipcRenderer, contextBridge } = require('electron');
contextBridge.exposeInMainWorld('electron', {
buttonClicked: (button) => {
ipcRenderer.send('buttonClicked', button);
},
windowState: (state) => {
ipcRenderer.on('windowState', state);
}
});
Within your main.js script we add functionality to tell the render process what state the window is in upon creation (point 4).
Here, we also receive the button click message from the render process (point 2) and implement functionality accordingly.
Lastly, listen for double-clicking of the title bar (point 5).
main.js (main process)
const electronApp = require('electron').app;
const electronBrowserWindow = require('electron').BrowserWindow;
const electronIpcMain = require('electron').ipcMain;
const nodePath = require('path');
let window;
function initialiseButtons() {
if (window.isMaximized()) {
window.webContents.send('windowState','maximised')
} else {
window.webContents.send('windowState','restored')
}
}
function createWindow() {
const window = new electronBrowserWindow({
x: 0,
y: 0,
width: 800,
height: 600,
frame: false,
show: false,
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
preload: nodePath.join(__dirname, 'preload.js')
}
});
window.loadFile('index.html')
// .then(() => { window.maximize(); }) // Testing
.then(() => { initialiseButtons(); })
.then(() => { window.show(); });
// Double-click of title bar from restore to maximise
window.on('maximize', () => {
initialiseButtons();
})
// Double-click of title bar from maximise to restore
window.on('unmaximize', () => {
initialiseButtons();
})
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();
}
});
// ---
electronIpcMain.on('buttonClicked', (event, buttonClicked) => {
if (buttonClicked === 'minimise') {
window.minimize();
return;
}
if (buttonClicked === 'restore') {
window.restore();
window.webContents.send('windowState','restored');
return;
}
if (buttonClicked === 'maximise') {
window.maximize();
window.webContents.send('windowState','maximised');
return;
}
if (buttonClicked === 'close') {
window.close();
}
})
Lastly, we send the button click event(s) to the main process (point 1) and listen for the window state upon window creation (point 4).
For simplicity, i have overlooked the use of inline styling and buttons for interactivity.
index.html (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';"/>
</head>
<body style="margin: 0; padding: 0;">
<div style="display: flex; flex-flow: row nowrap; padding: 5px; background-color: #ccc;">
<div style="flex: 1 0 auto">Title</div>
<div style="flex: 0 1 auto">
<input type="button" id="minimise" value="_">
<input type="button" id="restore" value="❐">
<input type="button" id="maximise" value="☐">
<input type="button" id="close" value="☓">
</div>
</div>
</body>
<script>
let restoreButton = document.getElementById('restore');
let maximiseButton = document.getElementById('maximise');
document.getElementById('minimise').addEventListener('click', () => {
window.electron.buttonClicked('minimise');
});
document.getElementById('close').addEventListener('click', () => {
window.electron.buttonClicked('close');
});
restoreButton.addEventListener('click', () => {
window.electron.buttonClicked('restore');
});
maximiseButton.addEventListener('click', () => {
window.electron.buttonClicked('maximise');
});
window.electron.windowState((event, state) => {
if (state === 'maximised') {
restoreButton.style.display = 'inline-block';
maximiseButton.style.display = 'none';
} else {
restoreButton.style.display = 'none';
maximiseButton.style.display = 'inline-block';
}
})
</script>
</html>

How to force the closing of an Electron app?

I have an electron app that loads the URL of the web version of the app.
However I cannot close the application by clicking in the X button and I dont have access to the webapp.
I have tried this:
let count = BrowserWindow.getAllWindows().length;
///returns 1, so there is only 1 window)
window.onbeforeunload=function(e){
e.returnValue=undefined;
return undefined;
}
window.close();
app.quit();
Nothing happens.
app.exit(0) works, but I dont want to use it. Is there a way I can close the app with window.close and app.quit?
EDIT:
The problem are those global beforeunload events. If i click remove in the devtools I am able to close the app.
Also this let names= window.eventNames();returns an array that does not have any beforeunload events
In the file where you created the BrowserWindow, you can attach an event to the 'close' handler for the window. For Electron, this is usually main.js.
const {app, BrowserWindow} = require('electron');
let mainWindow = null;
app.on('ready', () => {
mainWindow = new BrowserWindow({
//options here
});
mainWindow.loadURL([url here]);
//attach event handler here
mainWindow.on("close", () => {
mainWindow = null;
app.quit();
});
});
For good measure, in the case that you may have more than one window, place this in your main.js file.
app.on('window-all-closed', () => {
if(process.platform !== "darwin")
app.quit();
});
You need to save the BrowserWindow object reference. Then listen for a 'closed' event and dereference it:
const { app, BrowserWindow } = require('electron');
let win; // = new BrowserWindow();
win.on('closed', () => {
win = null
})
After dereference, it's all simple, just listen for 'window-all-closed' event on app:
app.on('window-all-closed', () => {
app.quit()
})

Keeping page content loaded upon closing the window in Electron

What I'm trying to achieve is "real" app-like behaviour in a sense that when I close the app on MacOS (hit X, so the app is still in dock) and afterwards open the app from dock again, the webpage content should be there immediately. As I'm trying to build a container for a web-app, the behaviour I'm getting is that every time I open the app, the web page is loaded again causing friction in the UX.
I've tried some dirty workarounds, like calling .hide() on main window from the renderer process before unloading the window:
const {remote} = require('electron');
const main = remote.require('./main.js');
window.onbeforeunload = e => {
main.hideWindow();
e.returnValue = false;
};
and in main process
exports.hideWindow = () => {
mainWindow.hide();
};
But that way I cannot quit my app at all.
Another option I considered was to load the whole mainWindow DOM in the memory, then upon opening the app, in the <webview> preload script load the cached content into the webview and once the page loads, overwrite the webview content, but it also seems very hackish.
I know Slack behaves exactly how I want my app to behave, but I'm struggling to find how they achieve that instant-load (or perhaps not ever closing, except when Quit is selected from the Dock or Cmd+Q is hit).
If I understand your issue correctly then I think there are a couple of standard workarounds for this. Specifically around
...not ever closing, except when Quit is selected from the Dock or
Cmd+Q is hit)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// 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()
}
})
And...
open the app from dock again, the webpage content should be there
immediately.
app.on('activate', () => {
// 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()
}
})
I ended up having a flag which governs the "closing" behaviour
let allowQuitting = false;
and handling the closing like this
function quitApp() {
if (app) {
allowQuitting = true;
app.quit();
}
}
function closeApp() {
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.hide();
}
}
On closing, I listen on closing events
function createWindow() {
mainWindow.on('closed', function () {
closeApp();
});
mainWindow.on('close', event => {
if (allowQuitting === false) {
event.preventDefault();
closeApp();
}
});
...
}
app.on('ready', createWindow);
On activating, I first check if the window exists
app.on('activate', function () {
if (mainWindow === null) {
createWindow();
} else {
mainWindow.show();
}
});
The app can be closed using Cmd+Q due to the accelerator:
const template = [{
label: 'Application',
submenu: [
...
{
label: 'Quit', accelerator: 'Command+Q', click: () => quitApp()
}
]
},
...
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
This gives me the desired result, albeit with a side-effect. The app can be closed with Cmd+Q, but can't be closed from the dock (by selecting Quit) or when shutting the system down (says it interrupted the shutdown).

How to catch the event of clicking the app window's close button in Electron app

I wish to catch the event of clicking the app window's close button in Electron app.
I'm trying to develope Electron app for Mac OSX.
I want to hide the app window, not to terminate the app when a user clicks the window's close button like other Mac apps.
However, I can not detect wether the system should be terminated or it should be hidden, because in any case, a close event of browser-window is called when a close button is clicked, the OS is shut down or the app is terminated with quit command, Cmd+Q.
Is there any way to catch the event of clicking the app window's close button in Electron app?
Thank you for your help.
Postscript
To detect the event of clicking a close button, I tried this code
var app = require('app');
var BrowserWindow = require('browser-window');
var Menu = require('menu');
var force_quit = false;
var menu = Menu.buildFromTemplate([
{
label: 'Sample',
submenu: [
{label: 'About App', selector: 'orderFrontStandardAboutPanel:'},
{label: 'Quit', accelerator: 'CmdOrCtrl+Q', click: function() {force_quit=true; app.quit();}}
]
}]);
app.on('window-all-closed', function(){
if(process.platform != 'darwin')
app.quit();
});
app.on('ready', function(){
Menu.setApplicationMenu(menu);
mainWindow = new BrowserWindow({width:800, height:600});
mainWindow.on('close', function(e){
if(!force_quit){
e.preventDefault();
mainWindow.hide();
}
});
mainWindow.on('closed', function(){
console.log("closed");
mainWindow = null;
app.quit();
});
app.on('activate-with-no-open-windows', function(){
mainWindow.show();
});
});
With this code, the app is hidden when a close button of the app window is clicked, and the app is terminated when Cmd+Q is typed. However, when I try to shut down the OS, the shutdown event is prevented.
You can catch it by using the close event of the browser-window api. You can try the following to verify this...
var app = require('app');
var force_quit = false;
app.on('ready', function () {
mainWindow = new BrowserWindow({ width: 800, height: 600 });
mainWindow.on('close', function() { // <---- Catch close event
// The dialog box below will open, instead of your app closing.
require('dialog').showMessageBox({
message: "Close button has been pressed!",
buttons: ["OK"]
});
});
});
Update:
To separate functionality you can do the following...
var app = require('app');
var BrowserWindow = require('browser-window');
var Menu = require('menu');
var force_quit = false;
var menu = Menu.buildFromTemplate([
{
label: 'Sample',
submenu: [
{label: 'About App', selector: 'orderFrontStandardAboutPanel:'},
{
label: 'Quit',
accelerator: 'CmdOrCtrl+Q',
click: function() {
force_quit=true; app.quit();
}
}
]
}]);
app.on('window-all-closed', function(){
if(process.platform != 'darwin')
app.quit();
});
app.on('ready', function(){
Menu.setApplicationMenu(menu);
mainWindow = new BrowserWindow({width:800, height:600});
// Continue to handle mainWindow "close" event here
mainWindow.on('close', function(e){
if(!force_quit){
e.preventDefault();
mainWindow.hide();
}
});
// You can use 'before-quit' instead of (or with) the close event
app.on('before-quit', function (e) {
// Handle menu-item or keyboard shortcut quit here
if(!force_quit){
e.preventDefault();
mainWindow.hide();
}
});
// Remove mainWindow.on('closed'), as it is redundant
app.on('activate-with-no-open-windows', function(){
mainWindow.show();
});
});
// This is another place to handle events after all windows are closed
app.on('will-quit', function () {
// This is a good place to add tests insuring the app is still
// responsive and all windows are closed.
console.log("will-quit");
mainWindow = null;
});
The above code uses the before-quit event handler to handle app "close" events on the app api. Browser-window "close" events are still handled on the browser-window api by mainWindow.on('close').
Additionally, the will-quit event is a better place to test for problems before the app closes completely.
app.on('ready', ()=> {
let win = new BrowserWindow({width:800, height:600})
win.loadURL('file://'+__dirname+'/index.html')
win.on('closed', () => {
console.log(' ---- Bye Bye Electron ---- ')
});
})
Thus You Can catch the Close Event

Categories