Read more

Howto prompt before accidentally discarding unsaved changes with JavaScript

Martin Straub
February 18, 2014Software engineer at makandra GmbH

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

Illustration online protection

Rails Long Term Support

Rails LTS provides security patches for old versions of Ruby on Rails (2.3, 3.2, 4.2 and 5.2)

  • Prevents you from data breaches and liability risks
  • Upgrade at your own pace
  • Works with modern Rubies
Read more Show archive.org snapshot

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);

Martin Straub
February 18, 2014Software engineer at makandra GmbH
Posted by Martin Straub to makandra dev (2014-02-18 09:29)