I wrote a small reusable component based of some of the examples to this question on Stack Overflow about Activating OnBeforeUnload
only when field values have changed.
It works like this:
- You can specify the portion of the page that it will use to listen for any data changes to input fields by adding the class
.alertChanges
. - Since you'll occasionally want to leave the page for good reason when hitting save or cancel, you can avoid the extra popup by adding the class
.bypassChanges
to those elements. - If anything other than a bypass change button attempts to leave the page (following another link / closing the browser), then the user will get an alert if there are unsaved changes.
- If there are no unsaved changes, they won’t get plagued by messages every time they try to navigate through the application.
Here's the code in a file AlertChanges.js which must come after jQuery:
//####### Alert Changes ##################################################################
// Utilizes two classes:
// .alertChanges - Will listen for changes on any :input child elements.
// If a change occurs, the user will be be prompted before navigating away
// .bypassChanges - Sometimes the user has already indicated they want to save any changes
// by clicking cancel or save. Add this class so the user will not be
// bothered with an extra popup
// Note: cannot guarantee that the user didn't change a value and later revert it
// https://stackoverflow.com/a/6579437/1366033
// IIFE automatically runs
(function ($, window) {
// jQuery Extension
// The handler is executed at most once for all elements for all event types.
$.fn.only = function (events, callback) {
// add listener and save original collection as jQuery object
var $this = $(this).on(events, myCallback);
// when callback fires, remove event handler and raise passed in function
function myCallback(e) {
$this.off(events, myCallback);
callback.call(this, e);
}
// return original collection
return this;
};
// Wait for document ready
$(function () {
var $alerts = $(".alertChanges");
// only run if we have an element of interest
if ($alerts.length) {
var needToConfirm = false;
// check before leaving
window.onbeforeunload = askConfirm;
function askConfirm() {
if (needToConfirm) {
return "Are you sure you want to navigate away? Any unsaved data will be lost.";
}
}
// wait for any other page changes
setTimeout(function () {
needToConfirm = false;
listenForChanges();
}, 1000);
// if any input element changes, we'll need to confirm exit
function listenForChanges() {
$alerts.find(":input").only('change', function () {
needToConfirm = true;
});
}
// disable confirmation message for select elements
$(".bypassChanges").click(function () {
needToConfirm = false;
});
}
});
})(jQuery, window);
Here's an example of a potential usage of this, but with the right classes, you can throw it on any page:
<form method="get" class="alertChanges">
<input type="text" /><br/>
<input type="submit" value="Save - Allow" class="bypassChanges" />
<input type="submit" value="Go somewhere else - Warn"/>
</form>
Here's a Demo in Plunker
You'll have to run the example in a new window in order to get the onbeforeunload
event to fire.
I don't have a specific point of inquiry into the code.
The thing that feels hackiest is the timeout for other elements to load. When a form loads, sometimes we'll modify certain elements based on others. If that happens, I didn't want the page load changes in jQuery to be responsible for triggering the change event for this script. One second seemed like enough time to let a script execute while not so much that a user has done any serious data entry that they would be remiss to inadvertently lose.