jQuery: Form changed warning

jQuery Logo

jQuery

The majority of us has had a moment where we’ve been filling out a web page form, been distracted and then left the page without submitting, accidentally clicked a link and navigated away or even had no input focused and hit backspace. Wouldn’t it have been nice to get a little warning saying “Hey, you’ve put changed this form data.  You sure you want leave this page and lose it all?”.  Here’s my effort using jQuery and trying to allow for multiple forms on a single page.

Approach taken

  • When the page loads, check for any forms and store their initial data.
  • If the page is unloaded, stop it and warn the user if any form data is different to that stored.
  • If a form is submitted, check that no other forms have been changed.  Otherwise, step over the unload event.

How not to identify individual forms

var formData = {};
$(function() {
  $('form').each(function() {
    var action = this.action;
    formData[action] = {};
    // Only checking text fields for now...
    $('input[type=text]', this).each(function() {
      formData[action][this.name] = this.value;
    });
  });
});

My first attempt involved using a global variable to store multiple form data in, identified by their action attributes.  I soon changed my mind on this as forms don’t even have to have an action attribute or several forms could have the same action.

Not all objects are created equally

$(function() {
  $('form').each(function() {
    var formData = {};
    // only checking text fields for now...
    $('input[type=text]', this).each(function() {
      formData[this.name] = this.value;
    });
    $(this).data('initialData', formData);
  }).submit(function(e) {
    e.preventDefault();
    var formData = {};
    // Just testing the comparison
    $('input[type=text]', this).each(function() {
      formData[this.name] = this.value;
    });
    if (formData != $(this).data('initialData')) {
      alert('Form data has changed');
    } else {
      // Never saw this as different objects are never equal
      alert('Form data is the same');
    }
  });
});

Brushing up on the jQuery documentation, I remember the .data() function.  It provides a simple was to store information against one or more elements.  This provided a great way to tie the initial form data against itself.  The problem was JavaScript doesn’t like comparing two different objects.  They’ll never be equal unless they’re both references to the same object.  There are functions available to compare objects properly but I didn’t want to get too complicated.

A bowl of serialize for breakfast

$(function() {
  $('form').each(function() {
    $(this).data('initialData', $(this).serialize());
  }).submit(function(e) {
    e.preventDefault();
    if ($(this).serialize() != $(this).data('initialData')) {
      alert('Form data has changed');
    } else {
      alert('Form data is the same');
    }
  });
});

I dumped the idea of walking the form elements myself and took advantage of more jQuery goodness, the .serialize() function.  This was nice enough to take my form element and spit out a query string of it’s fields.  It also made the code a lot shorter than it used to be, which is always a good thing.

“Don’t leave me this way…”

var catcher = function() {
  var changed = false;
  $('form').each(function() {
    if ($(this).data('initialForm') != $(this).serialize()) {
      changed = true;
      $(this).addClass('changed');
    } else {
      $(this).removeClass('changed');
    }
  });
  if (changed) {
    return 'One or more forms have changed!';
  }
};

$(function() {
  $('form').each(function() {
    $(this).data('initialForm', $(this).serialize());
  });
  $(window).bind('beforeunload', catcher);
});

Now I need to stop the user leaving this page without giving them a chance to know what’s happened.  To catch all the various ways of leave (clicking a link, back button, close the browser etc…) we’ll use the “onbeforeunload” event.  This isn’t your usual event and differently from other ones where you simply return a string to prompt the user.  The browser then takes care of prompting them with it and providing buttons to allow them to stay or leave the page.  I also added a class to the form as it’d make sense to show the user which forms have changed.

“Don’t you, forget about me…”

var catcher = function() {
  var changed = false;
  $('form').each(function() {
    if ($(this).data('initialForm') != $(this).serialize()) {
      changed = true;
      $(this).addClass('changed');
    } else {
      $(this).removeClass('changed');
    }
  });
  if (changed) {
    return 'One or more forms have changed!';
  }
};

$(function() {
  $('form').each(function() {
    $(this).data('initialForm', $(this).serialize());
  }).submit(function(e) {
    var formEl = this;
    var changed = false;
    $('form').each(function() {
      if (this != formEl && $(this).data('initialForm') != $(this).serialize()) {
        changed = true;
        $(this).addClass('changed');
      } else {
        $(this).removeClass('changed');
      }
    });
    if (changed && !confirm('Another form has been changed. Continue with submission?')) {
      e.preventDefault();
    } else {
      $(window).unbind('beforeunload', catcher);
    }
  });
  $(window).bind('beforeunload', catcher);
});

The final piece to the puzzle.  Making sure that if you’re submitting a form, it must also warn you about other changed forms and sidestep the “onbeforeunload” event.  This just meant very similar code for the submission event and if everything is okay with the other forms, removing the “onbeforeunload” event to prevent it firing.

Please let me know if you take a different approach or what you think of this one. :)

*Update

I’ve managed to get a demo sorted out.

http://jsbin.com/amecu3

Remember to try the following:

  • Update both forms and try to submit one.
  • Update any of the forms and try to click the link.
  • Update the forms and use your back button.
  • Edit the forms and refresh the page.
About these ads

39 thoughts on “jQuery: Form changed warning

    1. misterdai Post author

      Good to hear it’s being put to some use. I was hoping to wrap it up into a plugin when I get time, although the code isn’t that bad as it is.

      Noticed your website is a company based in Cardiff, which is where I am too ;)

      Reply
  1. WhiteEyebrows

    I’m kind of new to jQuery and JS. This is an AWESOME piece of code!

    I’m trying to use this code, but I have a CKEditor on the page and it’s not capturing the input to the CKEditor box. I found out about a method to access the contents of the CKEditor (CKEDITOR.instances.body.getData();) but I’m not sure where to incorporate this into your logic.

    Help?

    Reply
    1. misterdai Post author

      Good question! Can’t forget about those types of editors ;)

      You’d have to change line 18 from

      $(this).data('initialForm', $(this).serialize());

      to

      $(this).data('initialForm', $(this).serialize() + '&' + FieldNameOfEditor + '=' + escape(ContentsOfEditor));

      And a similar change to line 4 from

      if ($(this).data('initialForm') != $(this).serialize()) {

      to

      var formData = $(this).serialize() + '&' + FieldNameOfEditor + '=' + escape(ContentsOfEditor);
      if ($(this).data('initialForm') != formData) {

      Let me know how that works our for you :)

      Reply
  2. Cat

    Hey Mister Dai! I’ve copied the code you have above. When make changes to the form and click the back button or Refresh the page, the message will come up, but if I click on other links on the same page, the message will not show. Any idea why that happened?

    Thanks

    Reply
  3. EvilBMP

    @Mister Dai
    Please delete my previous comment … the submitted code had issues!

    Hi all,

    You may want to use this as a jquery plugin on your page…

    /**
     * jquery.observeform
     * (c) 2010 Mister Dai (http://misterdai.wordpress.com/)
     *
     * @see:  http://misterdai.wordpress.com/2010/06/04/jquery-form-changed-warning/
     * @demo: http://jsbin.com/amecu3
     */
    (function($){
        $.fn.extend({
            observeForm: function(opt) {
                opt = $.extend({
                    changeClass: 'changed',
                    msgCatcher: "One or more forms have changed! Unsaved changes will be lost.\nReally continue?",
                    msgForm: "Another form has been changed! Unsaved changes will be lost.\nReally continue?"
                }, opt || {});
    
                var fs = $(this);
    
                var catcher = function() {
                    var changed = false;
                    fs.each(function() {
                        if ($(this).data('initialForm') != $(this).serialize()) {
                            changed = true;
                            $(this).addClass(opt.changeClass);
                        } else {
                            $(this).removeClass(opt.changeClass);
                        }
                    });
                    if (changed) {
                        return opt.msgCatcher;
                    }
                };
    
                fs.each(function() {
                    $(this).data('initialForm', $(this).serialize());
                }).submit(function(e) {
                    var formEl = this;
                    var changed = false;
                    fs.each(function() {
                        if (this != formEl && $(this).data('initialForm') != $(this).serialize()) {
                            changed = true;
                            $(this).addClass(opt.changeClass);
                        } else {
                            $(this).removeClass(opt.changeClass);
                        }
                    });
                    if (changed && !confirm(opt.msgForm)) {
                        e.preventDefault();
                    } else {
                        $(window).unbind('beforeunload', catcher);
                    }
                });
                $(window).bind('beforeunload', catcher);
            }
        });
    })(jQuery);
    

    Have fun…

    @Mister Dai
    The code still has issues. If you submit the form, jquery triggers two “e is NULL” errors at the console – in your demo too.
    But I am not sure, whether this is a jquery bug or a bug in the snippet…

    Regards,
    EvilBMP

    Reply
    1. Rax

      I copied this code to my jsp page in javascript place. and included JQuery.js files still its not working, is there any thing that i need to do.?

      Reply
  4. vanderlord

    Thanks for the script. Learned some new things from it :) .

    I have a problem – some type of input fields need to be ignored and i can’t find the way. :(

    Reply
  5. William

    Is anyone else having trouble with the window.unbind not being called?

    At least that’s what I assume is going on based on what I can observe. When I submit the form (there’s only one form on the page), it still warns that that form has been changed. Its really weird; this is under firefox 3.6.10 (I don’t think its browser specific).

    Googling for reasons why it would fail have mainly result in people who added teh event handler in the actual html code, which obviously isn’t what I’m doing as I’m following the example above.

    Reply
  6. Adam

    This is just what I was looking for.

    However, I had to change line 36 from

    $(window).bind('beforeunload', catcher);

    to

    window.onbeforeunload = catcher;

    In order for it to work for me.

    From the jQuery docs http://api.jquery.com/bind/:

    The beforeunload and error events on the window object use nonstandard conventions and are not supported by jQuery; attach a handler directly to the window object instead.

    Reply
  7. Greg Corey

    This works wonderfully for me. However, one catch. I load a form via AJAX and if I have the script in the “parent” page (i.e. the element), it alerts no matter what. If I move the script into the form that is loaded, it works like a charm. Just a little tip for others.

    Reply
  8. Wayne Zhang

    Nice code!

    We have a project that has a requirement to capture leave page without save/submit. I have copied it to our project and it works fine.

    Thank you very much!

    Reply
  9. Thomas Güttler

    Hi Mister Dai,

    thank you for this code. But where is the latest version? On this pages there are several
    versions, and I don’t know which one is the latest.

    It would be nice if you could download the code, maybe with a version number…

    Thomas

    Reply
  10. Derek Lavine

    Hi Mister Dai,

    Great code, but I am trying to find a way to ignore some specific fields as at the moment those fields do an auto post back when they change and I cannot change the way these work at this time. And of course your script then warns user they have changed something and asks if they want to leave the page.

    I wanted to add a class such as “requirePageRefresh” to fields that I want your script to ignore but of course the latest version is working at the form level not at the the field level or so it seems to me.

    Is the version “Not all objects are created equally” working at the field level?

    But Is the code

    // Just testing the comparison
    13 $(‘input[type=text]‘, this).each(function() {
    14 formData[this.name] = this.value;
    15 });

    Only checking “text input fields” or is this going to also handle listboxes checkboxes etc? as I want to check everything and just ignore inputs with the class “requirePageRefresh”

    Many thanks for any hints you may be able to provide

    Derek

    Reply
    1. Sean

      Hi Derek,

      You may be able to do this by using the not() method, could try changing the serialize line to…

      $(this).not(‘.requirePageRefresh’).serialize()

      Cheers,
      Sean

      Reply
  11. Rax

    hi
    I am new to JQuery, i have same requirement, so i copied your code. i got script error like JQuery is undefined, Do i need to include any jars or something to work this JQuery code.?

    As of now no jQuery in my project.

    Reply
    1. geraldcor

      Javascript and Java are in no way related. You must include a reference to jquery if you are going to use this script. This does not interact with jars or anything java related.

      Reply
  12. Rax

    I included like this and also downloaded to my js folder and tried . Both the ways its not working. But this time no errors.

    Reply
    1. geraldcor

      One step at a time. You need to add this code either in the $(document).ready(function(){…}); of your site, or it needs to be at the bottom of your html as noted in the jsbin example above http://jsbin.com/amecu3 Look at the page source for this link and see if that clears things up.

      Reply
  13. Jim

    Seeing some inconsistent behavior using the jQuery form plugin. When I initially load the page everything works and I’m alerted if there are any changes. However if I submit my form ( via Ajax) then change something and navigate away from the page – I’m not alerted. It seems like I need to reinit something on a successful Ajax submit?

    Reply
  14. Pingback: 10 jQuery Form – Accessibility and Functionality | jQuery4u

  15. Pingback: 10 forms based on jQuery scripts and plug-ins to enhance - Open News

  16. Pingback: jQuery form, 100 Best plugins, tools & tutorials « Zeeshan Akhter

  17. misterdai Post author

    Stumbled across my own code recently for this and thought I’d rewrite into a nicer jQuery plugin. Just working out what extras to throw in there and how to better handle different forms. :)

    Reply
  18. realthing02

    Thanks for this post. I had just created an input field color-on-change function and this was my next step (the warning on leaving the page). I liked your code so I implemented my color change into it.

    Reply
  19. Josh

    Hello,

    Great script, thank you for providing. I have one minor issue with it though. It seems to be saving the ‘changed’ status from page to page.

    I have a form that is broken up across multiple tabs. If I change a form element on tab 1 and click on tab 2, it alerts me, as it should. I select ‘Leave Page’ and go to tab 2′s form. If I then click back onto tab 1, I am not alerted, the page is refreshed, loading tab 1′s form, and the form values are reset. Clicking again on tab 2 alerts me again that the form has changed. (I hope this makes sense.)

    Is there a way to re-initialize the script when the page is refreshed? It seems to be hanging onto old values.

    Thank you.

    Reply
    1. Josh

      I think I figured out what the problem is…. part of the form is being loaded via AJAX, so those form elements aren’t on the page when the script runs.

      Reply
      1. Josh

        Just talking to myself here. I added a “delay” and that seemed to catch all the form elements. If you have a better solution I would love to know what it is…

        setTimeout(‘checkFormChange();’, 500);

        function checkFormChange(){
        // Your code here
        }

        Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s