AngularJS unbind after onBeforeUnload prompt, $destroy not firing - javascript

I have a requirement to show a prompt whenever our reservation page is navigated away from. When navigating within the app, there is no problem. A simple $onLocationChange works fine with a custom prompt. However when navigating to , for example, google or our logout page (which is in a separate angular app) the $onLocationChange does not work. In this case, I used:
window.onbeforeunload = onUnloadAppStart;
function onUnloadAppStart() {
var message = figureOutWhichMessageToUse();
return message;
}
this way I can at least customize the navigate message, even though I cant change the prompt box itself. However this causes a memory leak. So I need to set to null when I leave. Something like this:
$scope.$on("$destroy", function() {
window.onbeforeunload = null;
}
however this(the $destroy event) only seems to fire when I navigate within the app, same as the $onLocationChange. Furthermore I need to clean up binding AFTER the prompt, which is after the onUnloadAppStart function. This is because if I remove the binding and the user says "no, stay on this page", now my "onbeforeunload" is screwed up. Is there a way to fire functions after the onbeforeunload prompt?

Related

How can I warn user on back button click?

www.example.com/templates/create-template
I want to warn users if they leave create-template page. I mean whether they go to another page or to templates.
I use this code to warn users on a page reload and route changes should the form be dirty.
function preventPageReload() {
var warningMessage = 'Changes you made may not be saved';
if (ctrl.templateForm.$dirty && !confirm(warningMessage)) {
return false
}
}
$transitions.onStart({}, preventPageReload);
window.onbeforeunload = preventPageReload
It works as expected on a page reload and route changes if it is done by clicking on the menu or if you manually change it. However, when I click the back button, it does not fire the warning. only it does if I click the back button for the second time, reload the page, or change route manually.
I am using ui-router. When you click back button, you go from app.templates.create-template state to app.templates state.
How to warn if they press Back button?
First of all, you are using it wrong:
from https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload:
Note: To combat unwanted pop-ups, some browsers don't display prompts
created in beforeunload event handlers unless the page has been interacted
with; some don't display them at all. For a list of specific browsers, see the
Browser_compatibility section.
and
window.onbeforeunload = funcRef
funcRef is a reference to a function or a function expression.
The function should assign a string value to the returnValue property of the Event object and return the same string.
You cannot open any dialogs in onbeforeunload.
Because you don't need a confirm dialog with onbeforeunload. The browser will do that for you if the function returns a value other than null or undefined when you try to leave the page.
Now, as long as you are on the same page, onbeforeunload will not fire because technically you are still on the same page. In that case, you will need some function that fires before the state change where you can put your confirm dialog.
How you do that depends on the router that you are using. I am using ui-router in my current project and I have that check in the uiCanExit function.
Edit:
You can keep your preventPageReload for state changes in angular. But you need a different function for when the user enters a new address or tries to leave the page via link etc.
Example:
window.onbeforeunload = function(e) {
if (ctrl.templateForm.$dirty) {
// note that most broswer will not display this message, but a builtin one instead
var message = 'You have unsaved changes. Do you really want to leave the site?';
e.returnValue = message;
return message;
}
}
However, you can use this as below:(using $transitions)
$transitions.onBefore({}, function(transition) {
return confirm("Are you sure you want to leave this page?");
});
Use $transitions.onBefore insteadof $transitions.onStart.
Hope this may help you. I haven't tested the solutions. This one also can help you.

Prevent user to navigate away with unsaved changes

I'm currently using Backbone.Marionette to create a SPA and in one of the views it is possible for the user to navigate away with unsaved changes. I can control some of these events, like buttons and menu options within the view that would take the user away, but some others would require to manipulate either Backbone.Router or work with the DOM events directly.
I already tried listening to beforeunload (doesn't work as the application is still loaded) and hashchange (doesn't work as you cannot stop the browser from navigating away). These solutions (1, 2, 3) don't work in this case, the Javascript is never unloaded.
Changing the Backbone.Router seems to be the best option, but because of how it is initialized I don't think it is possible to introduce this feature or at least I cannot find a way of doing it. This solution, for example, doesn't work because hashchange is not cancelable (you cannot call stopPropagation on it), and this other solution doesn't work because navigate is not defined on the Backbone.Router object.
Any suggestions?
I've managed to find a solution to this, although some more work is required. For this solution, I am assuming that you keep track when a view is dirty.
There are 4 main ways of moving out of a view;
Click on a link on the view
Click on link outside the view
Click on refresh or external link
Click on back/forward on the browser
1. Application link
This is the easiest case. When you click on your own link, you have to check if your view is dirty. For example, I have an in-app back button that is handled by a historyBack function. On the view:
historyBack: function() {
if (this.isDirty) {
answer = confirm("There are unsaved changes.\n\nDo you wish to continue?")
if (answer) {
this.isDirty = false
window.history.back()
}
}
else {
window.history.back()
}
}
2. Links outside your view
This type of interaction can be handled by extending the Router prototype's execute method, not the navigate method as proposed in other places.
There should be a variable somewhere accessible by the Router that stores the state of the view. In my case, I'm using the Router itself and I update this variable every time I change the dirty flag on the view.
The code should look something like this:
_.extend(Backbone.Router.prototype, {
execute: function (callback, args, name) {
if (Backbone.Router.isDirty) {
answer = confirm "There are unsaved changes.\n\nDo you wish to continue?";
if (!answer) {
return false;
}
}
Backbone.Router.isDirty = false
if (callback) callback.apply(this, args)
}
}
3. Refresh or external link
Refresh and external links actually unload your Javascript so here the solutions based on beforeunload (see question) actually work. Wherever you manage your view, I use a controller but let's assume it's on the same view, you add a listener on show and remove it on destroy:
onShow: function() {
$(window).bind("beforeunload", function (e) {
if (this.isDirty) {
return "There are unsaved changes.";
}
}
}
onDestroy: function() {
$(window).unbind("beforeunload");
}
4. Back/Forward on the browser
This is the trickiest case and the one I haven't figured out completely yet. When hitting back/forward, the user can navigate out of the app or within the app, both cases are covered by the code on 1 and 3, but there is an issue I can't figure out and I will create another question for it.
When hitting back/forward, the browser changes the address bar before calling the router so you end up with an inconsistent state: The address bar shows a different route to the application state. This is a big issue, if the user clicks again on the back button, after saving or discarding the changes, she will be taken to another route, not the previous one.
Everything else works fine, it shows a pop up asking the user if she wants to leave or continue and doesn't reload the view if the user chooses to stay.

Attach jQuery event handler to popup window contents

EDIT - A short/brief description of the question. Is it possible using jQuery and javascript to intercept popup windows (on open) and listen/detect for keyboard input into text boxes from the parent window.
I am working on a site that has recently switched to Single Sign On type authentication. As a result, the user's session is no longer controlled by the application, but rather a central authorization server.
In order to prevent users from being booted without realizing their session is timing out, we created a simple timer. After X minutes of no activity, a warning banner appears with a link to extend the session.
Here is the code snippet we use to listen for activity on a web page
function DetectChanges() {
$('input[type=text], textarea').on('change keyup paste', function () {
console.log('Input detected - Resetting ession timers');
$('input[type=text], textarea').off('change keyup paste');
ResetSessionExpirationInformation();
setTimeout(DetectChanges, msToCheckInput);
});
}
Basically the premise is, every so often, see if the user is hitting the keyboard. If so, extend the session and stop listening for keyboard input for a little bit.
The problem I am running into is that I need to be able to tell if the user is hitting the keyboard in a child window (old schoool popup, not a dialog or modal). Since this site uses tons of inline JS, I don't have an easy way to gain access to these new windows. However, I did find that I can override the window.open function in order to track the windows.
var openedWindows = [];
window._open = window.open; // saving original function
window.open = function (url, name, params) {
var newWin = window._open(url, name, params);
newWin.focus();
newWin.onload = setTimeout(DetectChangesChild(newWin), msToCheckInput);
ResetSessionExpirationInformation();
openedWindows.push(newWin);
I am passing the reference to the window in the above DetectChangesChild method so I have a reference later on the code path.
function DetectChangesChild(childWindow) {
childWindow.alert("Listening Child Window");
$(childWindow).contents('input[type=text], textarea').on('change keyup paste', function () {
alert('Stuff happened');
console.log('Input detected - Resetting session timers');
childWindow.$('input[type=text], textarea').off('change keyup paste');
ResetSessionExpirationInformation();
setTimeout(DetectChangesChild(childWindow), msToCheckInput);
});
}
Does anyone know how to listen on text input elements in a popup for changes so that I can reset the session expiration timers appropriately?
Note - I am aware of the cautionary statements against overriding native functions, but we are in no danger of colliding with another override of this function.

How to handle / prevent browser navigation or reload in angularjs?

I would like to detect in my angular app when a user is navigating away from or reloading a page.
App (that uses some login process) should then distinguish that it was re-loaded, so user won't lose his auth data and app should be able to restore then necessary information from localStorage.
Please suggest some best techniques to "handle" browser reloading / navigation.
All of your javascript and in memory variables disappear on reload. In js, you know the page was reloaded when the code is running again for the first time.
To handle the reload itself (which includes hitting F5) and to take action before it reloads or even cancel, use 'beforeunload' event.
var windowElement = angular.element($window);
windowElement.on('beforeunload', function (event) {
// do whatever you want in here before the page unloads.
// the following line of code will prevent reload or navigating away.
event.preventDefault();
});
I had the same problem, but Ben's answer didn't work for me.
This answer put me on the right track. I wanted to add a warning on some states but not all of them. Here is how I did it (probably not the cleanest way) :
window.onbeforeunload = function(event) {
if ($state.current.controller === 'ReloadWarningController') {
// Ask the user if he wants to reload
return 'Are you sure you want to reload?'
} else {
// Allow reload without any alert
event.preventDefault()
}
};
(in the ReloadWarningController definition, which had the $state injected)

How to implement a isDirty flag onLeave for a single page application?

I have a single page application written in MVC4 that uses pjax() to push html into various s in my page. I have one sub-form that allows the user to edit the data and it if the user changes the data an isDirty flag gets set in javascript to trigger an alert at the bottom of the page that there are un-saved updates. I would also like to implement an additional warning when the user tries to leave the page without saving. If I use a traditional onbeforeunload function like this
window.onbeforeunload = function() {
if (isDirty) {
return 'You have unsaved changes!';
}
return null;
};
it calls the alert if I try to close the page or navigate away from the site entirely but if the user clicks on one of my links that re-populates the with some different information it does not trigger because you are not actually leaving the page. How can I architect it so that one of these pjax() links causes an alert similar to if I close the page?
You could subscribe to a global event that fires before a pjax request:
$(document).on('pjax:beforeSend', function() {
if (isDirty) {
return confirm('You have unsaved changes! Are you sure you want to continue?');
}
return true;
});
You could add a delegated event handler onto the links in the page. You just have to make sure the handler is bound to links that may load a "new page".

Categories

Resources