I am making a writing application. Users can open the app, write some text, save their work, etc.
I am trying to make it so that clicking the window close button will prompt the user to (a) save their work (if necessary) or (b) just quit.
I am trying to use window.beforeunload to achieve this, but find I am getting stuck in a loop, where trying to "quit" makes the same prompt appear ad infinitum.
Here's some code:
windowCloseCheck() {
window.onbeforeunload = function(e) {
e.returnValue = false;
// window.alert('try to close me');
if(file.isUnsaved() || file.hasChanged()) {
// prompt - save or just quit?
file.fileWarning('You have unsaveeed work.', 'Save', 'Quit', function(){
// OPTION A - save
file.save();
}, function() {
// OPTION B: Quit.
ipcRenderer.send('quitter')
})
}
else {
// file is saved and no new work has been done:
ipcRenderer.send('quitter')
}
windowCloseCheck is invoked when the application is setup, initiating an event listener for closing the window. My conditional is checking if the file is unsaved or has changed from the original.
fileWarning is a function that just wraps the electron dialog box, making a pop up appear with two choices and respective functions to call for each choice.
The rest of the code is available if I'm (probably) leaving out necessary information. Would be happy to clarify if I'm not being very clear.
Please add following block inside the function where you have defined browser window(In my case it's createWindow() function declared in main.js)
// Create the browser window.
mainWindow = new BrowserWindow({width: 400, height: 400})
mainWindow.on('close', function(e){
var choice = require('electron').dialog.showMessageBox(this,
{
type: 'question',
buttons: ['Yes', 'No'],
title: 'Confirm',
message: 'Are you sure you want to quit?'
});
if(choice == 1){
e.preventDefault();
}
});
Answer for Electron 7+
The following is a working solution for latest Electron, based on awijeet's answer and Sergio Mazzoleni's comment.
At the bottom of createWindow(), below your win = new BrowserWindow(...), use:
win.on('close', function(e) {
const choice = require('electron').dialog.showMessageBoxSync(this,
{
type: 'question',
buttons: ['Yes', 'No'],
title: 'Confirm',
message: 'Are you sure you want to quit?'
});
if (choice === 1) {
e.preventDefault();
}
});
I ended up going with using window.destroy() to smash the render process to bits (from the main process):
ipcMain.on('quitter', (e) => {
mainWindow.destroy(); // necessary to bypass the repeat-quit-check in the render process.
app.quit()
})
Not really sure if this is recommended as there seem to be better ways to properly quit an electron app. Still happy to get any feedback if you know a better answer!
/shrug!
// Create the browser window.
mainWindow = new BrowserWindow({width: 400, height: 400})
mainWindow.on('close', function(e){
e.preventDefault();
var choice = require('electron').dialog.showMessageBox(mainWindow,
{
type: 'question',
buttons: ['Yes', 'No'],
title: 'Confirm',
message: 'Are you sure you want to quit?'
});
choice.then(function(res){
// 0 for Yes
if(res.response== 0){
// Your Code
}
// 1 for No
if(res.response== 1){
// Your Code
}
}
});
Notice that dialog.showMessageBox Returns Promise<Object> According to Electron Doc
And change this to mainWindow
This is modified answer for original answer to Awijeet
you can add a local variable to avoid mainWindow.destroy(), like:
let showExitPrompt = true;
// ask renderer process whether should close the app (mainWindow)
mainWindow.on('close', e => {
if (showExitPrompt) {
e.preventDefault(); // Prevents the window from closing
mainWindow.webContents.send('on-app-closing');
}
});
// renderer allows the app to close
ipcMain.on('quitter', (e) => {
showExitPrompt = false;
mainWindow.close();
})
Awijeet's solution is just perfect! Bth, thx Awijeet: it saved me hours! ;-)
In addition, if you need to go further, be aware 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.
If the user would choose the save button, then we should save the data and close the window using window.close().
But, With this approach we will get an infinite loop that asks for saving the unsaved work because window.close() will emit window's close event again.
To solve this issue, we must declare a new boolean var forceQuit that initially set to false then we set it to true after saving the data or if the user decided to just close without saving the data.
import { app, BrowserWindow, dialog } from 'electron';
let win = null;
let forceQuit = false;
app.on('ready', () => {
win = new BrowserWindow({
// Your window options..
});
mainWindow.on('close', e => {
if (!forceQuit) {
const clickedButtonId = dialog.showMessageBox(mainWindow, {
type: 'warning',
buttons: ['Save', 'Cancel', `Don't save`],
cancelId: 1,
title: 'Confirm',
message: 'You have unsaved work!'
});
if (clickedButtonId === 0) {
e.preventDefault();
console.log('Saving...');
/** Here do your data saving logic */
forceQuit = true;
win.close();
} else if (clickedButtonId === 1) {
e.preventDefault();
} else if (clickedButtonId === 2) {
forceQuit = true;
win.close();
}
}
});
});
Related
I'm writing an electron app. I used loadURL to show a website inside my app. I want the website inside the loadURL to go back when I press the backspace key. The Official documentary on loadURL was not helpful for me. How do I make the loadUrl content go back when the backspace is pressed,please?
You can use globalshortcuts and webContents.goBack( ) to do this.
For eg :
const { app, globalShortcut, BrowserWindow } = require('electron')
app.on('ready', () => {
let win = new BrowserWindow({ width: 800, height: 1500 })
win.loadURL('http://github.com')
let contents = win.webContents
// Register a 'backspace' shortcut listener.
const ret = globalShortcut.register('Backspace', () => {
console.log('Backspace is pressed');
contents.goBack();
})
if (!ret) {
console.log('registration failed')
}
// Check whether a shortcut is registered.
console.log(globalShortcut.isRegistered('Backspace'))
})
app.on('will-quit', () => {
// Unregister a shortcut.
globalShortcut.unregister('Backspace')
// Unregister all shortcuts.
globalShortcut.unregisterAll()
})
In my Electron app, I would like to do something that is done very often in other OSX apps. That is... I would like to NOT close the app of the red X is clicked in the top right. But, if they right click the app icon in the dock, and say Quit, then I would like to quit the app. How do I do this?
I have tried using the onbeforeunload event from the rendererProcess, as well as the browserWindow.on("close", fn) event to try and prevent this. The problem is that they both file the onbeforeunload event. And I can't tell the different between the red X being clicked and the dock icon being right clicked and told to quit. Any help would be nice. Has anyone else done this in Electron for OSX?
try this
if (process.platform === 'darwin') {
var forceQuit = false;
app.on('before-quit', function() {
forceQuit = true;
});
mainWindow.on('close', function(event) {
if (!forceQuit) {
event.preventDefault();
/*
* your process here
*/
}
});
}
This is the only answer that worked for me:
const electron = require('electron');
const app = electron.app;
let willQuitApp = false;
let window;
app.on('ready', () => {
window = new electron.BrowserWindow();
window.on('close', (e) => {
if (willQuitApp) {
/* the user tried to quit the app */
window = null;
} else {
/* the user only tried to close the window */
e.preventDefault();
window.hide();
}
});
window.loadURL('foobar'); /* load your page */
});
/* 'activate' is emitted when the user clicks the Dock icon (OS X) */
app.on('activate', () => window.show());
/* 'before-quit' is emitted when Electron receives
* the signal to exit and wants to start closing windows */
app.on('before-quit', () => willQuitApp = true);
via https://discuss.atom.io/t/how-to-catch-the-event-of-clicking-the-app-windows-close-button-in-electron-app/21425/8
After much looking, I found the following solution. When you right click on the dock and select Quit, before that fires the onbeforeunload in the rendererProcess, it will first fire the close event on the app itself. So, in the rendererProcess you have an onbeforeunload listener. And you tell that to return false always. Returning false from that event will prevent the window from unloading/closing ever. Then in your mainProcess you add app.on('close',fn) listener. That listener can send an event to the rendererProcess telling it to allow the close. Perhaps you can set a global allowClose = true or something. Then in your onbeforeunload, you add the logic to not return true if allowClose is true.
Take a look at the window-all-closed event of app in the main process. This event is typically used to quit the app on Linux and Windows but not on OS X (for an example, see Electron's Quick Start Tutorial). On OS X you should then probably also handle the activate event to open a new window if there is currently no window open.
Have a look at the electron quick start guide
Please notice the two below solutions needs to be implemented in main.js, and not on the JS executed on your html page.
Specific window close
If you want to execute code when a specific BrowserWindow is closed:
mainWindow.on('closed', function() {
// Your code to be executed before "really" stopping the app
});
All window close
If you want execute code when ALL the windows are closed (app API):
app.on('window-all-closed', function() {
// do stuff here
});
You need to handle this from your main.js file, by checking if it's a darwin platform, on window-all-closed event and re-create the window on activate event.
// 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();
}
});
More info/Example: https://github.com/atom/electron-quick-start/blob/master/main.js
This is how i solved it and this works perfectly.
import { app } from "electron";
let window: any;
let forceQuit = false;
app.on("ready", () => {
window = //YOUR BROWSER WINDOW
window.on("close", e => {
if (process.platform === "darwin" && forceQuit) {
window = null;
} else {
e.preventDefault();
app.hide();
}
});
app.on("activate", function() {
app.show();
});
app.on("before-quit", function(event) {
if (!forceQuit) {
event.preventDefault();
forceQuit = true;
app.quit();
}
});
I'm using the notification API for my project to show browser notifications where each notification has a unique tag (ID), but I can't seem to find a way to close or hide the notification by the tag name, without calling the close function on the object, since it might be closed with other pages than where it was originated. Is this sort of thing possible?
You could save the notifications in localStorage and then retrieve it and close.
e.g.
// on create
var n = new Notification('Notification Title', {
tag: _this.attr('data-notification-id')
});
window.localStorage.setItem('data-notification-id', n);
and
// then later
var n = window.localStorage.getItem('data-notification-id');
n.close();
I've solved this now, but my solutions seems odd, so I'm still accepting other answers that follow a more "normal" approach.
Basically, a new notification object that is created with a tag while a notification that is currently already visible already has the same tag, the original notification is removed. So by creating a new notification object with the same tag and immediately removing it, I can "remove" the old notifications.
The link to view the notification
View this notification
And the jQuery
$('a[data-notification-id]').on('click', function(){
var _this = $(this);
var n = new Notification('Notification Title', {
tag: _this.attr('data-notification-id')
});
setTimeout(n.close.bind(n), 0);
});
You could stringify the notification options and save to session (or local) storage using the tag as the storage key. Then you can use the stored notification options to re-create/replace it and then call close.
Create the notification:
if (("Notification" in window)) {
if (Notification.permission === "granted") {
var options = {
body: sBody,
icon: sIcon,
title: sTitle, //used for re-create/close
requireInteraction: true,
tag: sTag
}
var n = new Notification(sTitle, options);
n.addEventListener("click", function (event) {
this.close();
sessionStorage.removeItem('notification-' + sTag);
}, false);
sessionStorage.setItem('notification-' + sTag, JSON.stringify(options));
}
}
Clear the notification:
function notificationClear(sTag) {
var options = JSON.parse(sessionStorage.getItem('notification-' + sTag));
if (options) {
options.requireInteraction = false;
if (("Notification" in window)) {
if (Notification.permission === "granted") {
var n = new Notification(options.title, options);
setTimeout(function () {
n.close();
sessionStorage.removeItem('notification-' + sTag);
}, 500); //can't close it immediately, so setTimeout is used
}
}
}
}
I have an application which uses backbone hash routing. When the page reloads I use the onbeforeunload event to tell the user that there are unsaved changes and prevent the page load. This does not work on hash change; so if the user presses back the dialog does not tell them there are changes pending and just goes back.
Is there anyway to detect the hash change and prevent it happening? something like onbeforehashchange
Nope, you can detect the hashchange as it happens with something like below, but you can't really detect it before it happens. If you need the hash before it changes you can just store it somewhere.
var myHash = document.location.hash;
$(document).on('hashchange', function() {
if (myHash != document.location.hash) {
//do something
}
});
You can't really detect the back button either, and if it does'nt trigger a reload, onbeforeunload won't work either.
If this functionality is essential, you should consider trying the haschange plugin or the history plugin, as one of those would make this a lot easier, and let you control the browser history and back/forth.
I ended up creating a function in my router. This is run before each route and puts up a jquery ui dialog and waits for a reply to run the route. This is quite messy code a I stripped out the application specific stuff.
close: function(callback) {
var hash = window.location.hash;
if (this.afterHash && this.afterHash == hash) {
this.dialog.dialog("close");
return;
}
callback = callback || function () {};
if (window.onbeforeunload) {
var text = window.onbeforeunload();
if (text) {
if (!this.dialog) {
var t = this;
this.afterHash = this.previous;
this.dialog = $("<div><p>" + text + "</p><p>Are you sure you want to close the dialog?</p></div>").dialog({
modal: true,
width: 420,
title: "Confirm Navigation",
close: function() {
t.dialog.dialog("destroy");
if (t.afterHash) {
t.navigate(t.afterHash, {
trigger: false
});
t.afterHash = null;
}
t.dialog = null;
},
buttons: {
'Leave this Page': function() {
t.afterHash = null;
t.dialog.dialog("close");
closeViewer();
callback.apply(t);
},
'Stay on this Page': function() {
t.dialog.dialog("close");
}
}
});
}
return;
}
}
this.previous = window.location.hash;
callback.apply(this);
},
on initialize you must add this.previous = window.location.hash;
I'm opening a new window by clicking on the extension button near the search bar.
I'd like to open a new window only if it's not already opened; in that case, I'd prefer showing the old one.
Here is my code, but it doesn't work.
var v = null;
var vid = null;
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.windows.getAll({}, function(list) {
// check if already exists
for(window in window_list)
if(window.id == vid) { window.focus(); return; }
chrome.windows.getCurrent(function(w) {
v = chrome.windows.create({'url': 'my_url', 'type': 'panel', 'focused': true});
vid = w.id;
});
});
});
Can someone explain me how to fix it?
Most probably, both v and vid values are deleted after closing the app (after it finish to execute the script), but how can I fix it? If possible, without using localStorage or cookies.
I've tried specifying the tabId properties while creating the window, but it doesn't work.
I've also tried using the chrome.windows.onRemoved.addListener functionality, but it doesn't work too.
Change window to another variable name.
Be consistent in variable names. window_list and list are different things.
Use chrome.windows.update instead of window.focus(), because the latter does not work.
Use chrome.windows.get to see whether the window exists, instead of maintaining a list of windows.
The details of the new window are available in the callback of chrome.windows.create. Use this method in the correct way:
Code:
chrome.windows.get(vid, function(chromeWindow) {
if (!chrome.runtime.lastError && chromeWindow) {
chrome.windows.update(vid, {focused: true});
return;
}
chrome.windows.create(
{'url': 'my_url', 'type': 'panel', 'focused': true},
function(chromeWindow) {
vid = chromeWindow.id;
}
);
});
Or, instead of checking whether the window exists, just update the window, and when an error occurs, open a new one:
chrome.windows.update(vid, {focused: true}, function() {
if (chrome.runtime.lastError) {
chrome.windows.create(
{'url': 'my_url', 'type': 'panel', 'focused': true},
function(chromeWindow) {
vid = chromeWindow.id;
});
}
});
chrome.windows.getAll({}, function(window_list) {
var extWindow = '';
window_list.forEach(function(chromeWindow) {
//Check windows by type
if (chromeWindow.type == 'panel') {
extWindow = chromeWindow.id;
//Update opened window
chrome.windows.update(extWindow, {focused: true});
return;
}
});
if (extWindow == '') {
//Open window
chrome.windows.create(
{
'url' : 'my_url',
'type' : 'panel',
'focused' : true
},
function(chromeWindow) {
extWindow = chromeWindow.id;
}
);
}
});
It is an alternative code that works for me