Обнаружение несохраненных изменений

У меня есть требование внедрить приглашение "Несвязанные изменения" в приложении ASP.Net. Если пользователь изменяет элементы управления в веб-форме и пытается уйти перед сохранением, появляется сообщение о том, что они имеют несохраненные изменения и дают им возможность отменить и остаться на текущей странице. Приглашение не должно отображаться, если пользователь не коснулся каких-либо элементов управления.

В идеале я хотел бы реализовать это в JavaScript, но прежде чем я спустился по пути моего собственного кода, существуют ли какие-либо существующие рамки или рекомендуемые шаблоны проектирования для этого? В идеале мне бы хотелось что-то, что можно легко использовать на нескольких страницах с минимальными изменениями.

Ответ 1

Использование jQuery:

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

При необходимости совместите с методами onunload/onbeforeunload.

Из комментариев следующие ссылки на все поля ввода, без дублирующего кода:

$(':input').change(function () {

Использование $(":input") относится ко всем элементам ввода, textarea, select и button.

Ответ 2

Один кусочек головоломки:

/**
 * 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;
}

И еще один:

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.";
  }
};

Оберните все это, и что вы получите?

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

Вероятно, вы также захотите изменить регистрацию обработчика событий beforeunload для использования регистрации событий LIBRARY_OF_CHOICE.

Ответ 3

На странице .aspx вам нужна функция Javascript, чтобы сообщить, является ли информация о форме "грязной"

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

    function setDirty() {
        isDirty = true;
    }

    function checkSave() {
        var sSave;
        if (isDirty == true) {
            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()">

и в коде, добавьте триггеры в поля ввода, а также сброс на кнопках отправки/отмены....

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

Ответ 4

В следующем браузере используется функция onbeforeunload и jquery для захвата любого события onchange. IT также ищет кнопки отправки или reset для reset, флаг, указывающий на изменения.

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;

Ответ 5

Спасибо за ответы всем. Я закончил внедрение решения с использованием JQuery и Protect-Data плагина. Это позволяет автоматически применять мониторинг ко всем элементам управления на странице.

Однако существует несколько предостережений, особенно при работе с приложением ASP.Net:

  • Когда пользователь выбирает вариант отмены, функция doPostBack выдает ошибку JavaScript. Мне пришлось вручную установить try-catch вокруг вызова .submit внутри doPostBack, чтобы его подавить.

  • На некоторых страницах пользователь может выполнить действие, которое выполняет обратную передачу на ту же страницу, но не сохраняет. Это приводит к любой перезагрузке JavaScript-логики, поэтому он думает, что после обратной передачи ничего не изменилось, когда что-то может быть. Мне пришлось реализовать скрытое текстовое поле, которое отправляется обратно со страницей и используется для хранения простого логического значения, указывающего, загрязнены ли данные. Это сохраняется после обратной передачи.

  • Вы можете захотеть, чтобы некоторые обратные ссылки на странице не вызывали диалог, например кнопку "Сохранить". В этом случае вы можете использовать JQuery для добавления функции OnClick, которая устанавливает window.onbeforeunload в null.

Надеюсь, это поможет всем, кто должен реализовать что-то подобное.

Ответ 6

Следующее решение работает для прототипа (тестируется в FF, IE 6 и Safari). Он использует общий наблюдатель формы (который формирует форму: изменяется, когда какие-либо поля формы были изменены), которые вы также можете использовать для других вещей.

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

Ответ 7

Общее решение Поддержка нескольких форм на данной странице (Просто скопируйте и вставьте в свой проект)

$(document).ready(function() {
    $('form :input').change(function() {
        $(this).closest('form').addClass('form-dirty');
    });

    $(window).bind('beforeunload', function() {
        if($('form:not(.ignore-changes).form-dirty').length > 0) {
            return 'You have unsaved changes, are you sure you want to discard them?';
        }
    });

    $('form').bind('submit',function() {
        $(this).closest('form').removeClass('form-dirty');
        return true;
    });
});

Примечание. Это решение сочетается с другими решениями для создания общего интегрированного решения.

Особенности:

  • Просто скопируйте и вставьте в свое приложение.
  • Поддержка нескольких форм.
  • Вы можете создавать или делать действия грязными формами, так как они имеют класс "форма-грязный".
  • Вы можете исключить некоторые формы, добавив класс "игнорировать-изменения".

Ответ 8

Вот простое решение javascript/jquery. Он учитывает "отмену" пользователя, он инкапсулирован в функцию для удобства приложения, и он не пропускает пропуски при отправке. Просто вызовите функцию и передайте идентификатор вашей формы.

Эта функция сериализует форму один раз, когда страница загружается, и снова, прежде чем пользователь покинет страницу. Если два состояния формы различны, отображается приглашение.

Попробуйте: http://jsfiddle.net/skibulk/Ydt7Y/

function formUnloadPrompt(formSelector) {
    var formA = $(formSelector).serialize(), formB, formSubmit = false;

    // Detect Form Submit
    $(formSelector).submit( function(){
        formSubmit = true;
    });

    // Handle Form Unload    
    window.onbeforeunload = function(){
        if (formSubmit) return;
        formB = $(formSelector).serialize();
        if (formA != formB) return "Your changes have not been saved.";
    };
}

$(function(){
    formUnloadPrompt('form');
});

Ответ 9

Недавно я внес вклад в плагин jQuery с открытым исходным кодом под названием dirtyForms.

Плагин предназначен для работы с динамически добавленным HTML, поддержкой нескольких форм, поддержкой практически любой диалоговой структуры, возвратом к диалоговому окну beforeunload, имеет подключаемую вспомогательную инфраструктуру для поддержки получения грязного статуса от пользовательских редакторов (плагин tinyMCE включен), работает в iFrames, а грязный статус может быть установлен или reset по желанию.

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

Ответ 10

Обнаружение изменений формы с помощью jQuery очень просто:

var formInitVal = $('#formId').serialize(); // detect form init value after form is displayed

// check for form changes
if ($('#formId').serialize() != formInitVal) {
    // show confirmation alert
}

Ответ 11

Я расширил предложение Slace выше, включив в него большинство редактируемых элементов, а также исключив некоторые элементы (со стилем CSS, называемым "srSearch" здесь), из-за того, что был установлен грязный флаг.

<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.';
            }
        });        

Ответ 12

      var unsaved = false;
    $(":input").change(function () {         
        unsaved = true;
    });

    function unloadPage() {         
        if (unsaved) {             
          alert("You have unsaved changes on this page. Do you want to leave this page and discard your changes or stay on this page?");
        }
    } 

window.onbeforeunload = unloadPage;

Ответ 13

Это именно то, что плагин Fleegix.js fleegix.form.diff ( http://js.fleegix.org/plugins/form/diff). Сериализуйте начальное состояние формы при загрузке с помощью fleegix.form.toObject ( http://js.fleegix.org/ref#fleegix.form.toObject) и сохраните его в переменной, а затем сравните с текущим состоянием, используя fleegix.form.diff при разгрузке. Легко, как пирог.

Ответ 14

Один метод, используя массивы для хранения переменных, чтобы изменения можно отслеживать.

Здесь очень простой способ обнаруживать изменения, но остальное не так элегантно.

Другой метод, который довольно прост и мал, из 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>

Ответ 15

В IE document.ready не будет работать должным образом, он обновит значения ввода.

поэтому нам нужно связать событие загрузки внутри функции document.ready, которая также будет обрабатывать браузер IE.

ниже - это код, который вы должны ввести внутри функции document.ready.

 $(document).ready(function () {
   $(window).bind("load", function () { 
    $("input, select").change(function () {});
   });
});