Howto prompt before accidentally discarding unsaved changes with JavaScript

Updated . Posted . Visible to the public.

Ask before leaving an unsaved CKEditor

Vanilla JavaScript way, but removes any other onbeforeunload handlers:

  $(function(){
    document.body.onbeforeunload = function() {
      for(editorName in CKEDITOR.instances) {
        if (CKEDITOR.instances[editorName].checkDirty()) {
          return "Unsaved changes present!"
        }
      }
    }
  }

A robuster implementation example

Note: Don't forget to mark the 'search as you type' forms with the skip_pending_changes_warning class.

var WarnBeforeAccidentallyDiscardUnsavedChanges = (function () {

  var anyDirtyInputFields = false;
  var unloadTriggeredBySubmit = false;
  var warningMessage = "Ungespeicherte Änderungen vorhanden!";
  var confirmationMessage = "Möchten Sie diese Seite wirklich verlassen?";
  var skipWarningClass = "skip_pending_changes_warning";

  function init() {
    skipFormsWithOverwrittenSubmit();
    observeInputFieldsForChanges();
    observeSubmitEvents();
    bindAlertMethod();
  }

  // API method for others which need to know for unsaved changes
  function isUnsavedChangeConfirmed() {
    if (isUnsavedChangePresent()) {
      return confirm(warningMessage + "\n\n" + confirmationMessage);
    } else {
      return true;
    }
  }

  // opens a confirmation dialog if unsaved changes are present
  function bindAlertMethod() {
    $(window).on('beforeunload', function() {
      if (isUnsavedChangePresent() && !unloadTriggeredBySubmit) {
        return warningMessage;
      }
    });
  }

  function isUnsavedChangePresent() {
    var unsavedChanges = false;

    // objects providing an isDirty() method
    unsavedChanges = unsavedChanges || anyDirtyCkeditors();
    // elements which have to be observed manually
    unsavedChanges = unsavedChanges || anyDirtyInputFields;

    return unsavedChanges;
  }


  // dirty check helper methods

  function anyDirtyCkeditors() {
    var anyDirty = false;
    if(typeof CKEDITOR != 'undefined') {
      _.each(CKEDITOR.instances, function(instance) {
        anyDirty = anyDirty || instance.checkDirty();
      });
    }
    return anyDirty;
  }


  // observer helpers

  // Using a css class as marker may make developers awere of the
  // special form behavior when inspecting the form classes
  function skipFormsWithOverwrittenSubmit() {
    _.each($('form input[type=submit]'), function(submitInput) {
      if (!!$(submitInput).attr('onclick')) {
        $(submitInput).closest('form').addClass(skipWarningClass);
      }
    });
  }

  function observeInputFieldsForChanges() {
    var $forms = $('form:not(.' + skipWarningClass + ')');
    var $inputs = $forms.find(
      'input:not(.' + skipWarningClass + '), ' +
        'textarea:not(.' + skipWarningClass + '), ' +
        'select:not(.' + skipWarningClass + ')');
    $inputs.on('change', function() {
      anyDirtyInputFields = true;
    });
  }

  function observeSubmitEvents() {
    $('form').submit(function() {
      unloadTriggeredBySubmit = true;
    });
  }

  return {
    init: init,
    isUnsavedChangeConfirmed: isUnsavedChangeConfirmed
  };

}());

$(WarnBeforeAccidentallyDiscardUnsavedChanges.init);

Profile picture of Martin Straub
Martin Straub
Last edit
Keywords
browser
License
Source code in this card is licensed under the MIT License.
Posted by Martin Straub to makandra dev (2014-02-18 08:29)