1
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ This doesn't work on some browsers, like (sadly) Opera 12.17. You should handle this kind of case some other way, if you want. \$\endgroup\$ – Ismael Miguel Nov 3 '14 at 21:06
1
\$\begingroup\$

From a once over:

  • From the docs, you should assign a string value to the returnValue property of the Event object and return the same string, you only coded the latter part.
  • I am not sure why you use a temporary variable for onbeforeunload, you could simpply

       // check before leaving
        window.onbeforeunload = function askConfirm(e) {
            if (needToConfirm) {
                return e.returnValue = 'Are you sure you want to navigate away? Any unsaved data will be lost.';
            }
        }
    
  • For the hacky part, you could use the obsolete http://api.jquery.com/live/ or use the proper approach which is documented in that link as well. Waiting a second is wrong, especially when you think mobile.

  • Other than that I really like the code. I understood it immediately, it is nicely compact and commented.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the feedback! It's funny that MDN doesn't use their reccomended sytax with returnValue in their own example. In terms of using .live (or .on), I'm not sure how I'd be able to implement that in this example. I need to give elements some breathing room from any changes that other scripts run when the page loads. I could start by listening to them right away, but I would still need to have a cutoff point for changes made by javascript and those made by the user that I'd want to alert on. \$\endgroup\$ – KyleMit Nov 4 '14 at 16:47
  • \$\begingroup\$ Ah, I thought you worried about newly created elements not caught by $(".alertChanges") I am not sure what to do for value initializing scripts.. Perhaps some stacktrace magic to distinguish scripts from genuine user input? \$\endgroup\$ – konijn Nov 4 '14 at 16:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.