Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET, jQuery, dirty forms, and window.onbeforeunload

In my ASP.NET web app, I'm trying to create a universal way of warning users before navigating away from a form when they've made changes, using jQuery. Pretty standard stuff, but after a lot of searching I have yet to find a technique that works.

Here's what I have at this point:

    addToPostBack = function(func) {
        var old__doPostBack = __doPostBack;
        if (typeof __doPostBack != 'function') {
            __doPostBack = func;
        } else {
            __doPostBack = function() {
                old__doPostBack();
                func();
            }
        }
    }

    var isDirty = false;

    $(document).ready(function() {
        addToPostBack(function() {
            alert("Postback detected.")
            clearDirty();
        });
        $(':input').bind("change select keydown", setDirty);
        window.onbeforeunload = function(e) {
            var msg = "You have unsaved changes. "
            if (isDirty == true) {
                var e = e || window.event;
                if (e) { e.returnValue = msg; }
                return msg;
            }
        };
    });

    setDirty = function() {isDirty = true;}

    clearDirty = function() {isDirty = false;}

This works as far as warning the user from navigating away. The problem is that I get the warning on every same-page postback. There are a number of things on my forms that might trigger a postback:

  1. There are Save, Cancel, and Delete linkbuttons on the page
  2. There might be other linkbuttons on the page that execute server-side functionality while staying on the same page
  3. There might be other controls with autopostback=true that also have server-side functions attached to them, but which don't result in the user leaving the page.

None of these things should provoke a warning, because the user isn't leaving the page. My code tries to hijack addToPostBack (more details on that in this question) to clear the isDirty bit before posting back, but the problem is that in IE onbeforeunload fires before __doPostBack, apparently because IE fires onbeforeunload immediately when a link is clicked (as described here).

Of course, I could wire up each of these controls to clear the isDirty bit, but I'd prefer a solution that operates on the form level and that doesn't require that I touch every single control that might trigger a postback.

Does anyone have an approach that works in ASP.NET and that doesn't involve wiring up every control that might cause a postback?

like image 572
Herb Caudill Avatar asked Aug 05 '09 18:08

Herb Caudill


4 Answers

I came across this post while Googling for a solution for doing the same thing in MVC. This solution, adapted from Herb's above, seems to work well. Since there's nothing MVC-specific about this, it should work just as well for PHP, Classic ASP, or any other kind of application that uses HTML and JQuery.

var isDirty = false;

$(document).ready(function () {
    $(':input').bind("change select keydown", setDirty);
    $('form').submit(clearDirty);

    window.onbeforeunload = function (e) {
        var msg = "You have unsaved changes. "
        if (isDirty == true) {
            var e = e || window.event;
            if (e) { e.returnValue = msg; }
            return msg;
        }
    };
});

setDirty = function () { isDirty = true; }
clearDirty = function () { isDirty = false; }
like image 192
Kevin Crumley Avatar answered Nov 13 '22 01:11

Kevin Crumley


Interesting, but... why don't you do everything with jQuery?

var  defaultSubmitControl = false;
var  dirty = false;
$(document).ready(function( ) {
    $('form').submit(function( ) { dirty = false });
    $(window).unload(function( ) {
        if ( dirty && confirm('Save?') ) {
            __doPastBack(defaultSubmitControl || $('form :submit[id]').get(0).id, '');
        }
    });
});
···
dirty = true;
···

Now, if that still causes the same issue (unload triggering before submit), you could try a different event tree, so instead of calling __doPostBack directly you do...

setTimeout(function( ) {
    __doPastBack(defaultSubmitControl || $('form :submit[id]').get(0).id, '');
}, 1);  // I think using 0 (zero) works too

I haven't tried this and it's from the top of my head, but I think it could be a way to solve it.

like image 42
void Avatar answered Nov 13 '22 01:11

void


You could always create an inherited page class that has a custom OnLoad / OnUnload method that adds in immediate execution JavaScript.

Then you don't have to handle it at a control specific level but rather the form / page level.

like image 23
Jonathan Avatar answered Nov 13 '22 01:11

Jonathan


Got this to work by basically tracking the mouse position. Keep in mind you can still get positive values to your Y value (hence my < 50 line of code), but as long as your submit buttons are more than 100 pixels down you should be fine.

Here is the Javascript I added to track mouse changes and capture the onbeforeunload event:

    <script language="JavaScript1.2">
    <!--

    // Detect if the browser is IE or not.
    // If it is not IE, we assume that the browser is NS.
    var IE = document.all?true:false

    // If NS -- that is, !IE -- then set up for mouse capture
    if (!IE) document.captureEvents(Event.MOUSEMOVE)

    // Set-up to use getMouseXY function onMouseMove
    document.onmousemove = getMouseXY;

    // Temporary variables to hold mouse x-y pos.s
    var tempX = 0
    var tempY = 0

    // Main function to retrieve mouse x-y pos.s

    function getMouseXY(e) {
      if (IE) { // grab the x-y pos.s if browser is IE
        tempX = event.clientX + document.body.scrollLeft
        tempY = event.clientY + document.body.scrollTop
      } else {  // grab the x-y pos.s if browser is NS
        tempX = e.pageX
        tempY = e.pageY
      }  
      // catch possible negative values in NS4
      if (tempX < 0){tempX = 0}
      if (tempY < 0){tempY = 0}  
      // show the position values in the form named Show
      // in the text fields named MouseX and MouseY
      document.Show.MouseX.value = tempX
      document.Show.MouseY.value = tempY
      return true
    }

    //-->
    </script>


    <script type="text/javascript">

        window.onbeforeunload = HandleOnClose;

        function HandleOnClose(e) {

            var posY = 0;
            var elem = document.getElementsByName('MouseY');
            if (elem[0]) {
                posY = elem[0].value;
            }

            if (posY < 50) {    // Your form "submit" buttons will hopefully be more than 100 pixels down due to movement
                return "You have not selected an option, are you sure you want to close?";
            }
        }

    </script>

Then just add the following form onto your page:

    <form name="Show">
        <input type="hidden" name="MouseX" value="0" size="4">
        <input type="hidden" name="MouseY" value="0" style="display:block" size="0">
    </form>

And that's it! It could use a little cleanup (remove the MouseX, etc), but this worked in my existing ASP.net 3.5 application and thought I would post to help anyone out. Works in IE 7 and Firefox 3.6, haven't tried Chrome yet.

like image 1
Mark Kadlec Avatar answered Nov 13 '22 02:11

Mark Kadlec