Wykrywanie niezapisanych zmian

95

Mam wymóg zaimplementowania monitu „Niezapisane zmiany” w aplikacji ASP .Net. Jeśli użytkownik zmodyfikuje formanty w formularzu internetowym i spróbuje opuścić stronę przed zapisaniem, powinien pojawić się monit ostrzegający go o niezapisanych zmianach i umożliwiający anulowanie i pozostanie na bieżącej stronie. Monit nie powinien być wyświetlany, jeśli użytkownik nie dotknął żadnego elementu sterującego.

Idealnie chciałbym zaimplementować to w JavaScript, ale zanim przejdę na ścieżkę rozwijania własnego kodu, czy są jakieś istniejące frameworki lub zalecane wzorce projektowe, aby to osiągnąć? Idealnie byłoby coś, co można łatwo ponownie wykorzystać na wielu stronach przy minimalnych zmianach.

tbreffni
źródło
Interesuje mnie to samo, ale nie w przypadku asp.net, ale myślę, że istnieje ogólne rozwiązanie.
Michael Neale
Rozwiązanie, które zamieściłem poniżej, może być używane w zwykłym HTML / Javscript. Po prostu zmień „codebehind” na atrybuty w samych tagach HTML.
Wayne
Wiem, że spóźniłem się tutaj o 5 lat, ale myślę, że mogę ulepszyć poprzednie rozwiązania ... Właśnie poprawiłem i zaktualizowałem moją odpowiedź poniżej.
skibulk

Odpowiedzi:

96

Korzystanie z jQuery:

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

W razie potrzeby połącz z onunload/ onbeforeunloadMethods.

Poniższe komentarze odnoszą się do wszystkich pól wejściowych bez powielania kodu:

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

Użycie $(":input")odnosi się do wszystkich elementów wejściowych, tekstowych, zaznaczania i przycisków.

Aaron Powell
źródło
32
Głosuj za, ponieważ korzystałem z tego rozwiązania, ale jest trochę łatwiejszy sposób. Jeśli używasz: $ (': input'). Change (function () {wtedy to działa dla wszystkich pól wejściowych, nie musisz replikować dla innych typów danych wejściowych. $ (": Input") wybiera wszystkie dane wejściowe, textarea , wybierz i przyciski elementów
CodeClimber
5
Myślę, że to może mieć problemy w Firefoksie. Jeśli użytkownik zmodyfikuje formularz, a następnie kliknie przycisk Odśwież przeglądarki, strona zostanie ponownie załadowana, a Firefox wypełni formularz poprzednimi wpisami użytkownika - bez wywoływania zdarzenia zmiany. Teraz formularz będzie brudny, ale flaga nie zostanie ustawiona, chyba że użytkownik dokona kolejnej edycji.
Oskar Berggren
8
Tylko podpowiedź. Jeśli formularz jest brudny, nadal nie oznacza to, że zawiera naprawdę zmienione dane. Dla lepszej użyteczności musisz faktycznie porównać stare dane z nowymi;)
Slava Fomin II
Należy pamiętać, że w changeprzypadku wprowadzania tekstu wyzwalane są jednorazowo po opuszczeniu / utracie fokusu, natomiast inputwyzwalacze przy każdym naciśnięciu klawisza. (Zwykle używam go razem changez JQuery one(), aby zapobiec wielu zdarzeniom.)
SpazzMarticus
46

Jeden element układanki:

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

I jeszcze :

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

Podsumuj to wszystko i co otrzymasz?

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

Prawdopodobnie będziesz również chciał zmienić rejestrację programu beforeunloadobsługi zdarzeń, aby używała LIBRARY_OF_CHOICErejestracji zdarzeń.

Jonny Buchanan
źródło
W przeglądarce Firefox 9.0.1 nie dostaję żadnej wiadomości. Mam domyślny komunikat „Czy na pewno chcesz opuścić tę stronę? Niektóre dane mogą zostać utracone” (jest to mniej więcej tłumaczenie komunikatu wyświetlanego przez moją przeglądarkę w języku innym niż angielski)
Romain Guidoux
1
Ten skrypt jest niesamowity, ale dla mnie wyświetlał również ostrzeżenie po przesłaniu mojego formularza. Naprawiłem to, rozpinając zdarzenie w onClick ( onclick="window.onbeforeunload=null")
Joost
1
Podoba mi się podejście polegające na porównywaniu właściwości defaultValue itp. Zamiast ustawiania brudnego bitu. Oznacza to, że jeśli ktoś zmieni pole, a następnie zmieni je z powrotem, to formularz nie zgłosi się jako brudny.
thelem
Dzięki, to świetnie. Czy możesz nam powiedzieć, jak zarejestrować się w jQuery? O ile nie mam tego skryptu na tej samej stronie html, to się nie uruchamia. Jeśli mam go w zewnętrznym pliku .js, to nie działa.
Shiva Naru
17

Na stronie .aspx potrzebujesz funkcji Javascript, aby stwierdzić, czy informacje w formularzu są „brudne”

<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()">

aw polu za kodem dodaj wyzwalacze do pól wejściowych, a także zresetuj na przyciskach przesyłania / anulowania ....

btnSubmit.Attributes.Add("onclick", "isDirty = 0;");
btnCancel.Attributes.Add("onclick", "isDirty = 0;");
txtName.Attributes.Add("onchange", "setDirty();");
txtAddress.Attributes.Add("onchange", "setDirty();");
//etc..
Wayne
źródło
9

Poniższe elementy wykorzystują funkcję onbeforeunload przeglądarki i jQuery do przechwytywania dowolnego zdarzenia onchange. IT szuka również jakichkolwiek przycisków przesyłania lub resetowania, aby zresetować flagę wskazującą na zmiany.

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;
Colin Houghton
źródło
Cześć, resetowanie Colina nie działało, gdy umieściłem pole wyboru, początkowo zaznaczyłem je i ponownie odznaczyłem, ale ostrzeżenie jest uruchamiane, jak mogę zresetować, gdy użytkownik odznaczy pole wyboru
programista
8

Dzięki za odpowiedzi wszystkim. Skończyło się na wdrożeniu rozwiązania przy użyciu JQuery i Protect-Data wtyczki . Dzięki temu mogę automatycznie stosować monitorowanie do wszystkich kontrolek na stronie.

Jest jednak kilka zastrzeżeń, szczególnie w przypadku aplikacji ASP .Net:

  • Gdy użytkownik wybierze opcję anulowania, funkcja doPostBack zgłosi błąd JavaScript. Musiałem ręcznie umieścić próbny catch wokół wywołania .submit w doPostBack, aby je stłumić.

  • Na niektórych stronach użytkownik może wykonać akcję, która wykonuje ogłaszanie zwrotne na tej samej stronie, ale nie jest zapisem. Powoduje to zresetowanie logiki JavaScript, więc uważa, że ​​nic się nie zmieniło po ogłoszeniu zwrotnym, gdy coś mogło się zmienić. Musiałem zaimplementować ukryte pole tekstowe, które jest wysyłane z powrotem ze stroną i służy do przechowywania prostej wartości logicznej wskazującej, czy dane są brudne. Problem ten utrzymuje się w ogłoszeniach zwrotnych.

  • Możesz chcieć, aby niektóre ogłoszenia zwrotne na stronie nie wywoływały okna dialogowego, na przykład przycisk Zapisz. W takim przypadku możesz użyć JQuery, aby dodać funkcję OnClick, która ustawia wartość window.onbeforeunload na null.

Mam nadzieję, że jest to pomocne dla każdego, kto musi wdrożyć coś podobnego.

tbreffni
źródło
7

Rozwiązanie ogólne Obsługa wielu formularzy na jednej stronie ( wystarczy skopiować i wkleić w projekcie )

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

Uwaga: to rozwiązanie jest połączone z rozwiązaniami innych, aby stworzyć ogólne zintegrowane rozwiązanie.

Cechy:

  • Po prostu skopiuj i wklej do swojej aplikacji.
  • Obsługuje wiele formularzy.
  • Możesz stylizować lub uczynić akcje brudnymi formami, ponieważ mają one klasę „form-dirty”.
  • Możesz wykluczyć niektóre formularze, dodając klasę „ignoruj-zmiany”.
MhdSyrwan
źródło
Działa to dobrze, ale zastępuję dodane klasy _isDirtyzmienną opisaną przez Aarona Powella. Nie widziałem potrzeby dodawania i usuwania klas z HTML zamiast zmiennych w javascript.
Martin
1
Ideą wykorzystania tutaj klas html jest dołączenie atrybutu „dirty” do każdego formularza, ponieważ nie można po prostu użyć zmiennych do stworzenia ogólnego rozwiązania wize js / html aplikacji.
MhdSyrwan,
1
Innymi słowy, użycie zmiennej zadziała tylko dla pojedynczego formularza, w przeciwnym razie będziesz potrzebować tablicy brudnych flag, które znacznie skomplikują rozwiązanie.
MhdSyrwan,
1
Dziękuję za wyjaśnienie tej kwestii. Potrzebowałem tylko jednej strony formularza.
Martin
6

Poniższe rozwiązanie działa dla prototypu (testowane w FF, IE 6 i Safari). Używa ogólnego obserwatora formularza (który uruchamia formularz: zmieniany, gdy dowolne pola formularza zostały zmodyfikowane), którego możesz użyć również do innych rzeczy.

/* 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);
reto
źródło
6

Oto proste rozwiązanie javascript / jquery. Uwzględnia "cofnięcie" przez użytkownika, jest zawarty w funkcji dla ułatwienia aplikacji i nie zapala się po przesłaniu. Po prostu wywołaj funkcję i podaj identyfikator swojego formularza.

Ta funkcja serializuje formularz raz po załadowaniu strony i ponownie przed opuszczeniem strony przez użytkownika. Jeśli dwa stany formularza są różne, zostanie wyświetlony monit.

Wypróbuj: 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');
});
skibulk
źródło
2
Próbowałem twojego podejścia. Działa z przeglądarkami Chrome i Firefox, ale nie może działać w Safari na iPadzie Mini ..
Dimitry K
4

Niedawno współtworzyłem wtyczkę open source jQuery o nazwie dirtyForms .

Wtyczka jest zaprojektowana do pracy z dynamicznie dodawanym kodem HTML, obsługuje wiele formularzy, może obsługiwać praktycznie każdą strukturę okna dialogowego, wraca do przeglądarki przed dialogiem pobierania, ma podłączaną platformę pomocniczą, która obsługuje pobieranie statusu od niestandardowych edytorów (dołączona jest wtyczka tinyMCE) , działa w elementach iFrames, a stan „zabrudzenia” można dowolnie ustawiać lub resetować.

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

NightOwl888
źródło
4

Wykrywanie zmian w formularzach za pomocą jQuery jest bardzo proste:

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

// check for form changes
if ($('#formId').serialize() != formInitVal) {
    // show confirmation alert
}
v.babak
źródło
2

Rozszerzyłem powyższą sugestię Slace'a, aby uwzględnić większość edytowalnych elementów, a także wykluczyć niektóre elementy (ze stylem CSS o nazwie „srSearch”), które powodują ustawienie flagi dirty.

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

PeterX
źródło
2
      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;

Ankit
źródło
0

Jedna metoda polega na użyciu tablic do przechowywania zmiennych, aby można było śledzić zmiany.

Oto bardzo prosta metoda wykrywania zmian , ale reszta nie jest tak elegancka.

Inna metoda, która jest dość prosta i niewielka, z 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>
Adam Davis
źródło
0

W IE document.ready nie będzie działać poprawnie, zaktualizuje wartości danych wejściowych.

więc musimy powiązać zdarzenie load wewnątrz funkcji document.ready, która będzie obsługiwać również przeglądarkę IE.

poniżej znajduje się kod, który należy umieścić w funkcji document.ready.

 $(document).ready(function () {
   $(window).bind("load", function () { 
    $("input, select").change(function () {});
   });
});
Krupall
źródło