I have implemented an "unsaved changes" warning using techniques described on these pages:
Client/JS Framework for "Unsaved Data" Protection?
http://kenbrowning.blogspot.com/2009/01/using-jquery-to-standardize.html
This works well except for a DropDownList on the page. It does an AutoPostBack, and I want onbeforeunload to fire because unsaved changes will be lost, but it isn't working. Should it be raising the onbeforeunload event? Can I somehow make it raise the event?
Edit:
The DropDownList is inside an UpdatePanel, so that means it isn't unloading the page and that would be why onbeforeunload isn't being triggered. Is there any way I can trigger the event programmatically? Or do I have to roll my own imitation Confirm dialog?
Edit2
I now have a solution that adds the dialog to asynchronous postbacks from an UpdatePanel. I have edited the original script, adding the call to setConfirmAsyncPostBack() as described in my solution.
Here is my JavaScript:
/****Scripts to warn user of unsaved changes****/
//https://stackoverflow.com/questions/140460
//http://jonstjohn.com/node/23
//Activates the confirm message onbeforeunload.
function setConfirmUnload(on) {
setConfirmAsyncPostBack();
if (on) {
removeCheckFromNoWarnClasses();
fixIEonBeforeUnload();
window.onbeforeunload = unloadMessage
return;
}
window.onbeforeunload = null
}
function unloadMessage() {
return 'You have unsaved changes.';
}
//Moves javascript from href to onclick to prevent IE raising onbeforeunload unecessarily
//http://kenbrowning.blogspot.com/2009/01/using-jquery-to-standardize.html
function fixIEonBeforeUnload() {
if (!$.browser.msie)
return;
$('a').filter(function() {
return (/^javascript\:/i).test($(this).attr('href'));
}).each(function() {
var hrefscript = $(this).attr('href');
hrefscript = hrefscript.substr(11);
$(this).data('hrefscript', hrefscript);
}).click(function() {
var hrefscript = $(this).data('hrefscript');
eval(hrefscript);
return false;
}).attr('href', '#');
}
//Removes warnings from Save buttons, links, etc, that have been can be given "no-warn" or "no-warn-validate" css class
//"no-warn-validate" inputs/links will only remove warning after successful validation
//use the no-warn-validate class on buttons/links that cause validation.
//use the no-warn class on controls that have CausesValidation=false (e.g. a "Save as Draft" button).
function removeCheckFromNoWarnClasses() {
$('.no-warn-validate').click(function() {
if (Page_ClientValidate == null || Page_ClientValidate()) {
setConfirmUnload(false);
}
});
$('.no-warn').click(function() {
setConfirmUnload(false);
});
}
//Adds client side events to all input controls to switch on confirmation onbeforeunload
function enableUnsavedChangesWarning() {
$(':input').one('change', function() {
window.onbeforeunload = function() {
return 'You have unsaved changes.';
}
});
removeCheckFromNoWarnClasses();
}
And in my ASP.NET page, when the user makes a change:
if (changed)
{
...
//Confirm unload if there are unsaved changes.
//NB we also have to call fixIEonBeforeUnload() to fix links, done in in page load to include links that are rendered during callbacks
ScriptManager.RegisterStartupScript(Page, GetType(), "unsavedchanges", "setConfirmUnload(true);", true);
}
else
...
Also see How to prevent AutoPostBack when DropDownlist is selected using jQuery
//http://msdn.microsoft.com/en-us/magazine/cc163413.aspx
//https://stackoverflow.com/questions/2424327/prevent-asp-net-dopostback-from-jquery-submit-within-updatepanel
//Adds an event handler to confirm unsaved changes when an asynchronous postback is initialised by an UpdatePanel
function setConfirmAsyncPostBack() {
if (typeof (Sys.WebForms) === "undefined" || typeof (Sys.WebForms.PageRequestManager) === "undefined")
return;
var prm = Sys.WebForms.PageRequestManager.getInstance();
prm.add_initializeRequest(confirmAsyncPostBack);
}
//An event handler for asynchronous postbacks that confirms unsaved changes, cancelling the postback if they are not confirmed
//Adds the confirmation to elements that have a css class of "warn"
function confirmAsyncPostBack(sender, args) {
if (window.onbeforeunload != null && args.get_postBackElement().className == "warn" && !unloadConfirmed())
args.set_cancel(true);
}
//Displays a confirmation dialog that imitates the dialog displayed by onbeforeunload
function unloadConfirmed() {
var confirmed = confirm("Are you sure you want to navigate away from this page?\n\n" + unloadMessage() + "\n\nPress OK to continue or Cancel to stay on the current page.");
if (confirmed)
window.onbeforeunload = null;
return confirmed;
}
Related
In JavaScript, is it possible to distinguish between beforeunload events that were triggered by the user closing a browser tab vs clicking a mailto link?
Basically, I would like to do this:
window.addEventListener("beforeunload", function (e) {
if(browserTabClosed) {
// Do one thing
}
else if (mailtoLinkClicked) {
// Do a different thing
}
}
Found a solution by looking at the event (e below) that gets passed in:
window.addEventListener("beforeunload", function (e) {
// We can use `e.target.activeElement.nodeName`
// to check what triggered the passed-in event.
// - If triggered by closing a browser tab: The value is "BODY"
// - If triggered by clicking a link: The value is "A"
const isLinkClicked = (e.target.activeElement.nodeName === "A");
// If triggered by clicking a link
if (isLinkClicked) {
// Do one thing
}
// If triggered by closing the browser tab
else {
// Do a different thing
}
}
The beforeunload method has an unstable behaviour between browsers, the reason is that browser implementations try to avoid popups and other malicious code runned inside this handler.
There is actually no general (cross-browser) way to detect what triggered the beforeunload event.
Said that, in your case you could just detect a click on the window to discriminate between the two required behaviours:
window.__exit_with_link = false;
window.addEventListener('click', function (e) {
// user clicked a link
var isLink = e.target.tagName.toLowerCase() === 'a';
// check if the link has this page as target:
// if is targeting a popup/iframe/blank page
// the beforeunload on this page
// would not be triggered anyway
var isSelf = !a.target.target || a.target.target.toLowerCase() === '_self';
if (isLink && isSelf) {
window.__exit_with_link = true;
// ensure reset after a little time
setTimeout(function(){ window.__exit_with_link = false; }, 50);
}
else { window.__exit_with_link = false; }
});
window.addEventListener('beforeunload', function (e) {
if (window.__exit_with_link) {
// the user exited the page by clicking a link
}
else {
// the user exited the page for any other reason
}
}
Obviously it is not the proper way, but still working.
At the same way, you could add other handlers to check other reasons the user left the page (eg. keyboard CTRL-R for refresh, etc.)
Test URL: https://github.com/darkred/test/issues/new
GitHub allows in the issues area for a public repo:
submitting a new issue with just 1 character as title and no body, and
posting a comment with just 1 character .
The above happens to me quite a lot because the build-in hotkey for 'submiting an issue or comment' is Ctrl + Enter: I accidentally press that keyboard shortcut before my issue/comment text is ready.
So, I'm trying to make a script (using Greasemonkey) that would show a confirmation popup whenever I try to:
submit a new issue, or
post a comment
via pressing Ctrl + Enter:
if user presses Ok in the popup, then the script to allow the submit,
but if the user presses Cancel in the popup, then the script to stop the submit.
I've come across these two approaches:
After the helpful comment by Brock Adams I have the following code:
var targArea_1 = document.querySelector('#issue_body'); // New issue textarea
var targArea_2 = document.querySelector('#new_comment_field'); // New comment textarea
function manageKeyEvents (zEvent) {
if (zEvent.ctrlKey && zEvent.keyCode == 13) { // If Ctrl+Enter is pressed
if (confirm('Are you sure?') == false) { // If the user presses Cancel in the popup
zEvent.stopPropagation(); // then it stops propagation of the event
zEvent.preventDefault(); // and cancels/stops the submit submit action bound to Ctrl+Enter
}
}
}
if (targArea_1 !== null) {targArea_1.addEventListener('keydown', manageKeyEvents);}
if (targArea_2 !== null) {targArea_2.addEventListener('keydown', manageKeyEvents);}
Now the popup appears ok whenever I press Ctrl + Enter.
The problem is that the issue/comment is not submitted when pressing Ok in the popup (even if I haven't pressed Cancel in the popup before, at all). How to fix this?
And, how to re-allow the issue/comment submit after I have pressed Cancel in the popup once?
In other words: how to re-enable default after preventDefault() ?
Based on the help by user trespassersW here (I thank him a lot)
i.e. that my code was missing an else branch:
if (confirm('Are you sure?') == false) {
// ...
} else {
var btn = document.querySelector("#partial-new-comment-form-actions button");
if (btn) btn.click();
}
and that's because the confirm messagebox clears keyboard events queue.
(therefore the click 'Ok' action must be done by the script).
Here is a full working script:
// ==UserScript==
// #nameGitHub Confirm Create and Close issues
// #include https://github.com/*
// #grant none
// ==/UserScript==
(function () { // Self-Invoking function
function init() {
// For submitting issues in issue body textarea via Ctrl+Enter
var targArea1 = document.querySelector('#issue_body'); // New issue textarea
function manageKeyEvents1(zEvent) {
if (zEvent.ctrlKey && zEvent.keyCode === 13) {
if (confirm('Are you sure?') === false) {
zEvent.stopPropagation();
zEvent.preventDefault();
} else {
var btn1 = document.querySelector('.btn-primary');
if (btn1) {btn1.click();}
}
}
}
if (targArea1 !== null) { targArea1.addEventListener('keydown', manageKeyEvents1); }
// ------------------------------------------------------------------------------------------------
// For submitting issues in new comment textarea via Ctrl+Enter
var targArea2 = document.querySelector('#new_comment_field'); // New comment textarea
function manageKeyEvents2(zEvent) {
if (zEvent.ctrlKey && zEvent.keyCode === 13) {
if (confirm('Are you sure?') === false) {
zEvent.stopPropagation();
zEvent.preventDefault();
} else {
var btn2 = document.querySelector('#partial-new-comment-form-actions button');
if (btn2) {btn2.click();}
}
}
}
if (targArea2 !== null) { targArea2.addEventListener('keydown', manageKeyEvents2); }
}
// Page load
init();
// On pjax (because GitHub uses the History API)
document.addEventListener('pjax:end', init);
})();
How can i redirect him if a user clicks on leave a page button on onbeforeunload. Please check my code
function openNewWindow() {
window.open('http://google.com/','_blank');
window.focus();
}
window.onbeforeunload = function(event) {
event = event || window.event;
var confirmClose = 'Are you sure?';
if (event) {
event.returnValue = confirmClose;
if(confirmClose)
{
if(true)
{
openNewWindow();
}
}
return confirmClose;
}
}
Thanks
If the users chooses yes in the onbeforeunload dialog then he will leave the page, you can not prevent this. You can however do some things before the dialog shows, like you are doing in your code, but the dialog it self is only displayed AFTER your function executes, displaying the return value.
Your code seems a bit obscure to, what are you expecting from if(confirmClose), this will always evaluate to true because a non empty string is "truthy" in javascript.
This question already has answers here:
Intercepting call to the back button in my AJAX application
(12 answers)
Closed 8 years ago.
I have to detect if a user has clicked back button or not.
For this I am using
window.onbeforeunload = function (e) {
}
It works if a user clicks back button. But this event is also fired if a user click F5
or reload button of browser. How do I fix this?
So as far as AJAX is concerned...
Pressing back while using most web-apps that use AJAX to navigate specific parts of a page is a HUGE issue. I don't accept that 'having to disable the button means you're doing something wrong' and in fact developers in different facets have long run into this problem. Here's my solution:
window.onload = function () {
if (typeof history.pushState === "function") {
history.pushState("jibberish", null, null);
window.onpopstate = function () {
history.pushState('newjibberish', null, null);
// Handle the back (or forward) buttons here
// Will NOT handle refresh, use onbeforeunload for this.
};
}
else {
var ignoreHashChange = true;
window.onhashchange = function () {
if (!ignoreHashChange) {
ignoreHashChange = true;
window.location.hash = Math.random();
// Detect and redirect change here
// Works in older FF and IE9
// * it does mess with your hash symbol (anchor?) pound sign
// delimiter on the end of the URL
}
else {
ignoreHashChange = false;
}
};
}
}
As far as Ive been able to tell this works across chrome, firefox, haven't tested IE yet.
Please try this (if the browser does not support "onbeforeunload"):
jQuery(document).ready(function($) {
if (window.history && window.history.pushState) {
$(window).on('popstate', function() {
var hashLocation = location.hash;
var hashSplit = hashLocation.split("#!/");
var hashName = hashSplit[1];
if (hashName !== '') {
var hash = window.location.hash;
if (hash === '') {
alert('Back button was pressed.');
}
}
});
window.history.pushState('forward', null, './#forward');
}
});
best way I know
window.onbeforeunload = function (e) {
var e = e || window.event;
var msg = "Do you really want to leave this page?"
// For IE and Firefox
if (e) {
e.returnValue = msg;
}
// For Safari / chrome
return msg;
};
I'm detecting the back button by this way:
window.onload = function () {
if (typeof history.pushState === "function") {
history.pushState("jibberish", null, null);
window.onpopstate = function () {
history.pushState('newjibberish', null, null);
// Handle the back (or forward) buttons here
// Will NOT handle refresh, use onbeforeunload for this.
};
}
It works but I have to create a cookie in Chrome to detect that i'm in the page on first time because when i enter in the page without control by cookie, the browser do the back action without click in any back button.
if (typeof history.pushState === "function"){
history.pushState("jibberish", null, null);
window.onpopstate = function () {
if ( ((x=usera.indexOf("Chrome"))!=-1) && readCookie('cookieChrome')==null )
{
addCookie('cookieChrome',1, 1440);
}
else
{
history.pushState('newjibberish', null, null);
}
};
}
AND VERY IMPORTANT, history.pushState("jibberish", null, null); duplicates the browser history.
Some one knows who can i fix it?
Since the back button is a function of the browser, it can be difficult to change the default functionality. There are some work arounds though. Take a look at this article:
http://www.irt.org/script/311.htm
Typically, the need to disable the back button is a good indicator of a programming issue/flaw. I would look for an alternative method like setting a session variable or a cookie that stores whether the form has already been submitted.
I'm assuming that you're trying to deal with Ajax navigation and not trying to prevent your users from using the back button, which violates just about every tenet of UI development ever.
Here's some possible solutions:
JQuery History
Salajax
A Better Ajax Back Button
I got a function which checks if some input fields are changed:
var somethingchanged = false;
$(".container-box fieldset input").change(function() {
somethingchanged = true;
});
And a function which waits on window.onload and fires this:
window.onbeforeunload = function(e) {
if (somethingchanged) {
var message = "Fields have been edited without saving - continue?";
if (typeof e == "undefined") {
e = window.event;
}
if (e) {
e.returnValue = message;
}
return message;
}
}
But if I edit some of the fields and hit the save button, the event triggers, because there is a post-back and the fields have been edited. Is there anyway around this, so the event does not fire upon clicking the save button?
Thanks
When I do this pattern I have a showDirtyPrompt on the page. Then whenever an action occurs which I don't want to go through the dirty check I just set the variable to false. You can do this on the client side click event of the button.
The nice thing about this is that there might be other cases where you don't want to prompt, the user you might have other buttons which do other post backs for example. This way your dirty check function doesn't have to check several buttons, you flip the responsability around.
<input type="button" onclick="javascript:showDirtyPrompt=false;".../>
function unloadHandler()
{
if (showDirtyPrompt)
{
//have your regular logic run here
}
showDirtyPrompt=true;
}
Yes. Check to see that the button clicked is not the save button. So it could be something like
if ($this.id.not("savebuttonID")) {
trigger stuff
}