Skip to main content

Detecting Unsaved Changes using JavaScript


I have a requirement to implement an "Unsaved Changes" prompt in an ASP .Net application. If a user modifies controls on a web form, and attempts to navigate away before saving, a prompt should appear warning them that they have unsaved changes, and give them the option to cancel and stay on the current page. The prompt should not display if the user hasn't touched any of the controls.



Ideally I'd like to implement this in JavaScript, but before I go down the path of rolling my own code, are there any existing frameworks or recommended design patterns for achieving this? Ideally I'd like something that can easily be reused across multiple pages with minimal changes.


Source: Tips4allCCNA FINAL EXAM

Comments

  1. Or with jQuery:

    var _isDirty = false;
    $("input[type='text']").change(function(){
    _isDirty = true;
    });
    // replicate for other input types and selects


    then just use any of the other onunload/ onbeforeunload methods

    ReplyDelete
  2. One piece of the puzzle:

    /**
    * Determines if a form is dirty by comparing the current value of each element
    * with its default value.
    *
    * @param {Form} form the form to be checked.
    * @return {Boolean} <code>true</code> if the form is dirty, <code>false</code>
    * otherwise.
    */
    function formIsDirty(form) {
    for (var i = 0; i < form.elements.length; i++) {
    var element = form.elements[i];
    var type = element.type;
    if (type == "checkbox" || type == "radio") {
    if (element.checked != element.defaultChecked) {
    return true;
    }
    }
    else if (type == "hidden" || type == "password" ||
    type == "text" || type == "textarea") {
    if (element.value != element.defaultValue) {
    return true;
    }
    }
    else if (type == "select-one" || type == "select-multiple") {
    for (var j = 0; j < element.options.length; j++) {
    if (element.options[j].selected !=
    element.options[j].defaultSelected) {
    return true;
    }
    }
    }
    }
    return false;
    }


    And another:

    window.onbeforeunload = function(e) {
    e = e || window.event;
    if (formIsDirty(document.forms["someForm"])) {
    // For IE and Firefox
    if (e) {
    e.returnValue = "You have unsaved changes.";
    }
    // For Safari
    return "You have unsaved changes.";
    }
    };


    Wrap it all up, and what do you get?

    var confirmExitIfModified = (function() {
    function formIsDirty(form) {
    // ...as above
    }

    return function(form, message) {
    window.onbeforeunload = function(e) {
    e = e || window.event;
    if (formIsDirty(document.forms[form])) {
    // For IE and Firefox
    if (e) {
    e.returnValue = message;
    }
    // For Safari
    return message;
    }
    };
    };
    })();

    confirmExitIfModified("someForm", "You have unsaved changes.");


    You'll probably also want to change the registration of the beforeunload event handler to use LIBRARY_OF_CHOICE's event registration.

    ReplyDelete
  3. In the .aspx page, you need a Javascript function to tell whether or not the form info is "dirty"

    <script language="javascript">
    var isDirty;
    isDirty = 0;

    function setDirty() {
    isDirty = 1;
    }

    function checkSave() {
    var sSave;
    if (isDirty == 1) {
    sSave = window.confirm("You have some changes that have not been saved. Click OK to save now or CANCEL to continue without saving.");
    if (sSave == true) {
    document.getElementById('__EVENTTARGET').value = 'btnSubmit';
    document.getElementById('__EVENTARGUMENT').value = 'Click';
    window.document.formName.submit();
    } else {
    return true;
    }
    }
    }
    </script>
    <body class="StandardBody" onunload="checkSave()">


    and in the codebehind, add the triggers to the input fields as well as resets on the submission/cancel buttons....

    btnSubmit.Attributes.Add("onclick", "isDirty = 0;");
    btnCancel.Attributes.Add("onclick", "isDirty = 0;");
    txtName.Attributes.Add("onchange", "setDirty();");
    txtAddress.Attributes.Add("onchange", "setDirty();");
    //etc..

    ReplyDelete
  4. Thanks for the replies everyone. I ended up implementing a solution using JQuery and the Protect-Data plug-in. This allows me to automatically apply monitoring to all controls on a page.

    There are a few caveats however, especially when dealing with an ASP .Net application:


    When a user chooses the cancel option, the doPostBack function will throw a JavaScript error. I had to manually put a try-catch around the .submit call within doPostBack to suppress it.
    On some pages, a user could perform an action that performs a postback to the same page, but isn't a save. This results in any JavaScript logic resetting, so it thinks nothing has changed after the postback when something may have. I had to implement a hidden textbox that gets posted back with the page, and is used to hold a simple boolean value indicating whether the data is dirty. This gets persisted across postbacks.
    You may want some postbacks on the page to not trigger the dialog, such as a Save button. In this case, you can use JQuery to add an OnClick function which sets window.onbeforeunload to null.


    Hopefully this is helpful for anyone else who has to implement something similar.

    ReplyDelete
  5. The following uses the browser's onbeforeunload function and jquery to capture any onchange event. IT also looks for any submit or reset buttons to reset the flag indicating changes have occurred.

    dataChanged = 0; // global variable flags unsaved changes

    function bindForChange(){
    $('input,checkbox,textarea,radio,select').bind('change',function(event) { dataChanged = 1})
    $(':reset,:submit').bind('click',function(event) { dataChanged = 0 })
    }


    function askConfirm(){
    if (dataChanged){
    return "You have some unsaved changes. Press OK to continue without saving."
    }
    }

    window.onbeforeunload = askConfirm;
    window.onload = bindForChange;

    ReplyDelete
  6. The following solution works for prototype (tested in FF, IE 6 and Safari). It uses a generic form observer (which fires form:changed when any fields of the form have been modified), which you can use for other stuff as well.

    /* use this function to announce changes from your own scripts/event handlers.
    * Example: onClick="makeDirty($(this).up('form'));"
    */
    function makeDirty(form) {
    form.fire("form:changed");
    }

    function handleChange(form, event) {
    makeDirty(form);
    }

    /* generic form observer, ensure that form:changed is being fired whenever
    * a field is being changed in that particular for
    */
    function setupFormChangeObserver(form) {
    var handler = handleChange.curry(form);

    form.getElements().each(function (element) {
    element.observe("change", handler);
    });
    }

    /* installs a form protector to a form marked with class 'protectForm' */
    function setupProtectForm() {
    var form = $$("form.protectForm").first();

    /* abort if no form */
    if (!form) return;

    setupFormChangeObserver(form);

    var dirty = false;
    form.observe("form:changed", function(event) {
    dirty = true;
    });

    /* submitting the form makes the form clean again */
    form.observe("submit", function(event) {
    dirty = false;
    });

    /* unfortunatly a propper event handler doesn't appear to work with IE and Safari */
    window.onbeforeunload = function(event) {
    if (dirty) {
    return "There are unsaved changes, they will be lost if you leave now.";
    }
    };
    }

    document.observe("dom:loaded", setupProtectForm);

    ReplyDelete
  7. I've created a jQuery plug-in which can be used to implement a warn-on-unsaved-changes feature for web applications. It supports postbacks. It also includes a link to information on how to normalize behavior of the onbeforeunload event of Internet Explorer.

    ReplyDelete
  8. This is exactly what the Fleegix.js plugin fleegix.form.diff (http://js.fleegix.org/plugins/form/diff) was created for. Serialize the initial state of the form on load using fleegix.form.toObject (http://js.fleegix.org/ref#fleegix.form.toObject) and save it in a variable, then compare with the current state using fleegix.form.diff on unload. Easy as pie.

    ReplyDelete
  9. I expanded on Slace's suggestion above, to include most editable elements and also excluding certain elements (with a CSS style called "srSearch" here) from causing the dirty flag to be set.

    <script type="text/javascript">
    var _isDirty = false;
    $(document).ready(function () {

    // Set exclude CSS class on radio-button list elements
    $('table.srSearch input:radio').addClass("srSearch");

    $("input[type='text'],input[type='radio'],select,textarea").not(".srSearch").change(function () {
    _isDirty = true;
    });
    });

    $(window).bind('beforeunload', function () {
    if (_isDirty) {
    return 'You have unsaved changes.';
    }
    });

    ReplyDelete
  10. I recently contributed to an open source jQuery plugin called dirtyForms.

    The plugin is designed to work with dynamically added HTML, supports multiple forms, can support virtually any dialog framework, falls back to the browser beforeunload dialog, has a pluggable helper framework to support getting dirty status from custom editors (a tinyMCE plugin is included), works within iFrames, and the dirty status can be set or reset at will.

    https://github.com/snikch/jquery.dirtyforms

    ReplyDelete
  11. One method, using arrays to hold the variables so changes can be tracked.

    Here's a very simple method to detect changes, but the rest isn't as elegant.

    Another method which is fairly simple and small, from Farfetched Blog:

    <body onLoad="lookForChanges()" onBeforeUnload="return warnOfUnsavedChanges()">
    <form>
    <select name=a multiple>
    <option value=1>1
    <option value=2>2
    <option value=3>3
    </select>
    <input name=b value=123>
    <input type=submit>
    </form>

    <script>
    var changed = 0;
    function recordChange() {
    changed = 1;
    }
    function recordChangeIfChangeKey(myevent) {
    if (myevent.which && !myevent.ctrlKey && !myevent.ctrlKey)
    recordChange(myevent);
    }
    function ignoreChange() {
    changed = 0;
    }
    function lookForChanges() {
    var origfunc;
    for (i = 0; i < document.forms.length; i++) {
    for (j = 0; j < document.forms[i].elements.length; j++) {
    var formField=document.forms[i].elements[j];
    var formFieldType=formField.type.toLowerCase();
    if (formFieldType == 'checkbox' || formFieldType == 'radio') {
    addHandler(formField, 'click', recordChange);
    } else if (formFieldType == 'text' || formFieldType == 'textarea') {
    if (formField.attachEvent) {
    addHandler(formField, 'keypress', recordChange);
    } else {
    addHandler(formField, 'keypress', recordChangeIfChangeKey);
    }
    } else if (formFieldType == 'select-multiple' || formFieldType == 'select-one') {
    addHandler(formField, 'change', recordChange);
    }
    }
    addHandler(document.forms[i], 'submit', ignoreChange);
    }
    }
    function warnOfUnsavedChanges() {
    if (changed) {
    if ("event" in window) //ie
    event.returnValue = 'You have unsaved changes on this page, which will be discarded if you leave now. Click "Cancel" in order to save them first.';
    else //netscape
    return false;
    }
    }
    function addHandler(target, eventName, handler) {
    if (target.attachEvent) {
    target.attachEvent('on'+eventName, handler);
    } else {
    target.addEventListener(eventName, handler, false);
    }
    }
    </script>

    ReplyDelete

Post a Comment

Popular posts from this blog

[韓日関係] 首相含む大幅な内閣改造の可能性…早ければ来月10日ごろ=韓国

div not scrolling properly with slimScroll plugin

I am using the slimScroll plugin for jQuery by Piotr Rochala Which is a great plugin for nice scrollbars on most browsers but I am stuck because I am using it for a chat box and whenever the user appends new text to the boxit does scroll using the .scrollTop() method however the plugin's scrollbar doesnt scroll with it and when the user wants to look though the chat history it will start scrolling from near the top. I have made a quick demo of my situation http://jsfiddle.net/DY9CT/2/ Does anyone know how to solve this problem?

Why does this javascript based printing cause Safari to refresh the page?

The page I am working on has a javascript function executed to print parts of the page. For some reason, printing in Safari, causes the window to somehow update. I say somehow, because it does not really refresh as in reload the page, but rather it starts the "rendering" of the page from start, i.e. scroll to top, flash animations start from 0, and so forth. The effect is reproduced by this fiddle: http://jsfiddle.net/fYmnB/ Clicking the print button and finishing or cancelling a print in Safari causes the screen to "go white" for a sec, which in my real website manifests itself as something "like" a reload. While running print button with, let's say, Firefox, just opens and closes the print dialogue without affecting the fiddle page in any way. Is there something with my way of calling the browsers print method that causes this, or how can it be explained - and preferably, avoided? P.S.: On my real site the same occurs with Chrome. In the ex