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


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

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

15 82

15 ответов:

С помощью jQuery:

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

объединить с onunload/onbeforeunload методы по мере необходимости.

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

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

используя $(":input") относится ко всем элементам ввода, текстового поля, выбора и кнопки.

одна часть головоломки:

/**
 * 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Регистрация событий.

In the .страница 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()">

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

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

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

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;

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

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

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

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

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

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

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

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

вот простое решение 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');
});

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

$(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;
    });
});

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

характеристики:

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

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

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

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

выявить изменения формы с помощью jQuery очень просто:

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

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

я расширил предложение 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.';
            }
        });        

      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?");
        }
    } 

Это именно то, что 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 на разгрузку. Легко, как пирог.

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

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

другой метод, который довольно прост и мал, от Надуманными Блог:

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

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

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

ниже приведен код, который вы должны поместить в документ.готовая функция.

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