it-swarm.com.de

Warnen Sie den Benutzer, bevor Sie die Webseite mit nicht gespeicherten Änderungen verlassen

Ich habe einige Seiten mit Formularen in meiner Bewerbung.

Wie kann ich das Formular so sichern, dass jemand, der weg navigiert oder den Browser-Tab schließt, aufgefordert wird, zu bestätigen, dass er das Formular wirklich mit nicht gespeicherten Daten verlassen möchte?

298
Khan

Kurze, falsche Antwort:

Sie können dies tun, indem Sie das beforeunload -Ereignis behandeln und eine Nicht-Null-Zeichenfolge zurückgeben :

window.addEventListener("beforeunload", function (e) {
    var confirmationMessage = 'It looks like you have been editing something. '
                            + 'If you leave before saving, your changes will be lost.';

    (e || window.event).returnValue = confirmationMessage; //Gecko + IE
    return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
});

Das Problem bei diesem Ansatz ist, dass das Absenden eines Formulars auch das Entladeereignis auslöst. Dies lässt sich leicht beheben, indem Sie das Flag hinzufügen, mit dem Sie ein Formular senden:

var formSubmitting = false;
var setFormSubmitting = function() { formSubmitting = true; };

window.onload = function() {
    window.addEventListener("beforeunload", function (e) {
        if (formSubmitting) {
            return undefined;
        }

        var confirmationMessage = 'It looks like you have been editing something. '
                                + 'If you leave before saving, your changes will be lost.';

        (e || window.event).returnValue = confirmationMessage; //Gecko + IE
        return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
    });
};

Rufen Sie dann den Setter an, wenn Sie Folgendes einreichen:

<form method="post" onsubmit="setFormSubmitting()">     
    <input type="submit" />
</form>

Aber lesen Sie weiter ...

Lange, richtige Antwort:

Sie möchten diese Nachricht auch nicht anzeigen , wenn der Benutzer in Ihren Formularen nichts geändert hat . Eine Lösung besteht darin, das Ereignis beforeunload in Kombination mit einem "dirty" -Flag zu verwenden, das die Eingabeaufforderung nur dann auslöst, wenn sie wirklich relevant ist.

var isDirty = function() { return false; }

window.onload = function() {
    window.addEventListener("beforeunload", function (e) {
        if (formSubmitting || !isDirty()) {
            return undefined;
        }

        var confirmationMessage = 'It looks like you have been editing something. '
                                + 'If you leave before saving, your changes will be lost.';

        (e || window.event).returnValue = confirmationMessage; //Gecko + IE
        return confirmationMessage; //Gecko + Webkit, Safari, Chrome etc.
    });
};

Um nun die isDirty Methode zu implementieren, gibt es verschiedene Ansätze.

Sie können jQuery- und Formularserialisierung verwenden, dieser Ansatz weist jedoch einige Fehler auf. Zuerst müssen Sie den Code ändern, um mit jedem Formular arbeiten zu können ($("form").each() wird funktionieren), aber das größte Problem ist, dass serialize() von jQuery nur mit benannten, nicht deaktivierten Elementen funktioniert Das Ändern eines deaktivierten oder unbenannten Elements löst das Dirty-Flag nicht aus. Hierfür gibt es Problemumgehungen . Sie können beispielsweise Steuerelemente schreibgeschützt erstellen, anstatt die Steuerelemente zu aktivieren, zu serialisieren und anschließend wieder zu deaktivieren.

Ereignisse scheinen also der richtige Weg zu sein. Sie können versuchen auf Tastendruck warten . Diese Veranstaltung hat einige Probleme:

  • Wird nicht bei Kontrollkästchen, Optionsfeldern oder anderen Elementen ausgelöst, die durch Mauseingabe geändert werden.
  • Löst bei irrelevanten Tastendrücken wie dem aus Ctrl Schlüssel.
  • Wird nicht bei Werten ausgelöst, die über JavaScript-Code festgelegt wurden.
  • Wird nicht beim Ausschneiden oder Einfügen von Text durch Kontextmenüs ausgelöst.
  • Funktioniert nicht für virtuelle Eingaben wie Datumsauswahl oder Checkbox-/Radiobutton-Verschönerungen, die ihren Wert in einer verborgenen Eingabe über JavaScript speichern.

Das change -Ereignis also wird nicht bei Werten ausgelöst, die über JavaScript-Code festgelegt wurden , funktioniert also auch nicht für virtuelle Eingaben.

Binden des Ereignisses input an alle inputs (und textareas und selects) auf Ihrer Seite wird nicht arbeiten mit älteren Browsern und unterstützen, wie alle oben genannten Event-Handling-Lösungen, das Rückgängigmachen nicht. Wenn ein Benutzer ein Textfeld ändert und dieses dann rückgängig macht oder ein Kontrollkästchen aktiviert oder deaktiviert, wird das Formular weiterhin als fehlerhaft betrachtet.

Und wenn Sie mehr Verhalten implementieren möchten, beispielsweise das Ignorieren bestimmter Elemente, müssen Sie noch mehr tun.

Das Rad nicht neu erfinden:

Bevor Sie über die Implementierung dieser Lösungen und aller erforderlichen Problemumgehungen nachdenken, sollten Sie sich darüber im Klaren sein, dass Sie das Rad neu erfinden und leicht auf Probleme stoßen, die andere bereits für Sie gelöst haben.

Wenn Ihre Anwendung bereits jQuery verwendet, können Sie auch getesteten, verwalteten Code verwenden, anstatt Ihren eigenen Code zu rollen, und für all dies eine Bibliothek eines Drittanbieters verwenden. jQueries Are You Sure? Plugin funktioniert hervorragend, siehe deren Demoseite . So einfach ist das:

<script src="jquery.are-you-sure.js"></script>

<script>
  $(function() {
    $('#myForm').areYouSure(
      {
        message: 'It looks like you have been editing something. '
               + 'If you leave before saving, your changes will be lost.'
      }
    );
  });

</script>

Benutzerdefinierte Nachrichten werden nicht überall unterstützt

Beachten Sie, dass Firefox 4 keine benutzerdefinierten Nachrichten in diesem Dialogfeld unterstützt. Ab dem letzten Monat wird Chrome 51 eingeführt in dem auch benutzerdefinierte Nachrichten entfernt werden .

An anderer Stelle auf dieser Site gibt es einige Alternativen, aber ich denke, ein Dialog wie dieser ist klar genug:

Möchtest du diese Seite verlassen?

Von Ihnen vorgenommene Änderungen werden möglicherweise nicht gespeichert.

LeaveStay

511
CodeCaster

Überprüfen Sie das JavaScript vor dem Entladen . Es handelt sich um nicht standardmäßiges JavaScript, das von Microsoft eingeführt wurde. Es funktioniert jedoch in den meisten Browsern und deren Onbeforunload-Dokumentation enthält weitere Informationen und Beispiele.

75
Paul Annesley

via jquery

$('#form').data('serialize',$('#form').serialize()); // On load save form current state

$(window).bind('beforeunload', function(e){
    if($('#form').serialize()!=$('#form').data('serialize'))return true;
    else e=null; // i.e; if form state change show warning box, else don't show it.
});

Sie können die Google JQuery Form Serialize-Funktion verwenden, um alle Formulareingaben zu erfassen und im Array zu speichern. Ich denke, dies zu erklären ist genug :)

33
Wasim A.

Universelle Lösung, die keine Konfiguration erfordert, die alle Eingabemodifikationen, einschließlich inhaltsbearbeitbarer Elemente, automatisch erkennt:

"use strict";
(() => {
const modified_inputs = new Set;
const defaultValue = "defaultValue";
// store default values
addEventListener("beforeinput", (evt) => {
    const target = evt.target;
    if (!(defaultValue in target || defaultValue in target.dataset)) {
        target.dataset[defaultValue] = ("" + (target.value || target.textContent)).trim();
    }
});
// detect input modifications
addEventListener("input", (evt) => {
    const target = evt.target;
    let original;
    if (defaultValue in target) {
        original = target[defaultValue];
    } else {
        original = target.dataset[defaultValue];
    }
    if (original !== ("" + (target.value || target.textContent)).trim()) {
        if (!modified_inputs.has(target)) {
            modified_inputs.add(target);
        }
    } else if (modified_inputs.has(target)) {
        modified_inputs.delete(target);
    }
});
addEventListener("beforeunload", (evt) => {
    if (modified_inputs.size) {
        const unsaved_changes_warning = "Changes you made may not be saved.";
        evt.returnValue = unsaved_changes_warning;
        return unsaved_changes_warning;
    }
});
})();
12
Eli Grey

Errichtet auf Wasim A.'s ausgezeichnete Idee, Serialisierung zu verwenden. Das Problem dabei war, dass die Warnung auch beim Absenden des Formulars angezeigt wurde. Dies wurde hier behoben.

var isSubmitting = false

$(document).ready(function () {
    $('form').submit(function(){
        isSubmitting = true
    })

    $('form').data('initial-state', $('form').serialize());

    $(window).on('beforeunload', function() {
        if (!isSubmitting && $('form').serialize() != $('form').data('initial-state')){
            return 'You have unsaved changes which will not be saved.'
        }
    });
})

Es wurde getestet in Chrome und IE 11.

7

Auf der Grundlage der vorherigen Antworten, die an verschiedenen Stellen im Stapelüberlauf zusammengeschustert wurden, habe ich die folgende Lösung entwickelt, die den Fall behandelt, in dem Sie Ihre Änderungen tatsächlich übermitteln möchten:

window.thisPage = window.thisPage || {};
window.thisPage.isDirty = false;

window.thisPage.closeEditorWarning = function (event) {
    if (window.thisPage.isDirty)
        return 'It looks like you have been editing something' +
               ' - if you leave before saving, then your changes will be lost.'
    else
        return undefined;
};

$("form").on('keyup', 'textarea', // You can use input[type=text] here as well.
             function () { 
                 window.thisPage.isDirty = true; 
             });

$("form").submit(function () {
    QC.thisPage.isDirty = false;
});
window.onbeforeunload = window.thisPage.closeEditorWarning;

Es ist erwähnenswert, dass IE11 anscheinend erfordert, dass die Funktion closeEditorWarningundefined zurückgibt, damit keine Warnung angezeigt wird.

7
Jonathan

Der folgende Einzeiler hat für mich gearbeitet.

window.onbeforeunload = s => modified ? "" : null;

Setzen Sie modified auf true oder false, abhängig vom Status Ihrer Anwendung.

6
spencer.sm

Der folgende Code funktioniert hervorragend. Sie müssen die Eingabeänderungen Ihrer Formularelemente über das Attribut id erreichen:

var somethingChanged=false;
            $('#managerForm input').change(function() { 
                somethingChanged = true; 
           }); 
            $(window).bind('beforeunload', function(e){
                if(somethingChanged)
                    return "You made some changes and it's not saved?";
                else 
                    e=null; // i.e; if form state change show warning box, else don't show it.
            });
        });
4
metzelder
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;
3
Ankit

Eine ausführliche Erklärung finden Sie hier: http://techinvestigations.redexp.in/comparison-of-form-values-on-load-and-before-close/ Vergleich von Formularwerten beim Laden und vor dem Schließen

Der Hauptcode:

function formCompare(defaultValues, valuesOnClose) {

    // Create arrays of property names
    var aPropsFormLoad = Object.keys(defaultValues);
    var aPropsFormClose = Object.keys(valuesOnClose);

    // If number of properties is different,
    // objects are not equivalent
    if (aPropsFormLoad.length != aPropsFormClose.length) {
        return false;
    }

    for (var i = 0; i < aPropsFormLoad.length; i++) {
        var propName = aPropsFormLoad[i];

        // If values of same property are not equal,
        // objects are not equivalent
        if (defaultValues[aPropsFormLoad]+"" !== valuesOnClose[aPropsFormLoad]+"") {
            return false;
        }
    }

    // If we made it this far, objects
    // are considered equivalent
    return true;

}

//add polyfill for older browsers, as explained on the link above

//use the block below on load
    for(i=0; i < document.forms[0].elements.length; i++){
    console.log("The field name is: " + document.forms[0].elements[i].name +
        " and it’s value is: " + document.forms[0].elements[i].value );
    aPropsFormLoad[i] = document.forms[0].elements[i].value;
    }

//create a similar array on window unload event.

//and call the utility function
    if (!formCompare(aPropsOnLoad, aPropsOnClose))
    {
    //perform action: 
    //ask user for confirmation or
    //display message about changes made
    }
1
Kanika Sud

Um die Idee von @codecaster zu erweitern, können Sie dies zu jeder Seite mit einem Formular hinzufügen (in meinem Fall verwende ich es global, sodass nur bei Formularen diese Warnung angezeigt wird). Ändern Sie seine Funktion in

if ( formSubmitting || document.getElementsByTagName('form').length == 0) 

Legen Sie auch Formulare mit Anmelde- und Abbruchtasten-Links an, damit die Warnung nicht auf jeder Seite mit einem Formular ausgelöst wird, wenn eine Person auf Abbrechen drückt oder das Formular abschickt.

<a class="btn btn-danger btn-md" href="back/url" onclick="setFormSubmitting()">Cancel</a>
1

Kurze Antwort:

let pageModified = true

window.addEventListener("beforeunload", 
  () => pageModified ? 'Close page without saving data?' : null
)
1

Die Lösung von Eerik Sven Puudist ...

var isSubmitting = false;

$(document).ready(function () {
    $('form').submit(function(){
        isSubmitting = true
    })

    $('form').data('initial-state', $('form').serialize());

    $(window).on('beforeunload', function() {
        if (!isSubmitting && $('form').serialize() != $('form').data('initial-state')){
            return 'You have unsaved changes which will not be saved.'
        }
    });
})

... erledigte die Arbeit für mich spontan in einem komplexen objektorientierten Umfeld, ohne dass Änderungen notwendig waren.

Die einzige Änderung, die ich vorgenommen habe, betraf das konkrete Formular (nur ein Formular pro Datei) mit dem Namen "formForm" ('form' -> '#formForm'):

<form ... id="formForm" name="formForm" ...>

Besonders gut gelungen ist die Tatsache, dass der Submit-Button "in Ruhe gelassen" wird.

Außerdem funktioniert es bei mir auch mit der neuesten Version von Firefox (Stand 7. Februar 2019).

1
mtjmohr

Getestet Eli Greys universelle Lösung, funktionierte erst nachdem ich den Code auf vereinfacht hatte

  'use strict';
  (() => {
    const modified_inputs = new Set();
    const defaultValue = 'defaultValue';
    // store default values
    addEventListener('beforeinput', evt => {
      const target = evt.target;
      if (!(defaultValue in target.dataset)) {
        target.dataset[defaultValue] = ('' + (target.value || target.textContent)).trim();
      }
    });

    // detect input modifications
    addEventListener('input', evt => {
      const target = evt.target;
      let original = target.dataset[defaultValue];

      let current = ('' + (target.value || target.textContent)).trim();

      if (original !== current) {
        if (!modified_inputs.has(target)) {
          modified_inputs.add(target);
        }
      } else if (modified_inputs.has(target)) {
        modified_inputs.delete(target);
      }
    });

    addEventListener(
      'saved',
      function(e) {
        modified_inputs.clear()
      },
      false
    );

    addEventListener('beforeunload', evt => {
      if (modified_inputs.size) {
        const unsaved_changes_warning = 'Changes you made may not be saved.';
        evt.returnValue = unsaved_changes_warning;
        return unsaved_changes_warning;
      }
    });

  })();

Die Änderungen an ihm werden durch die Verwendung von target[defaultValue] Gelöscht und verwenden nur target.dataset[defaultValue], Um den tatsächlichen Standardwert zu speichern.

Und ich habe einen Listener für gespeicherte Ereignisse hinzugefügt, in dem das gespeicherte Ereignis von Ihnen selbst ausgelöst wird, wenn Ihre Speicheraktion erfolgreich war.

Diese "universelle" Lösung funktioniert jedoch nur in Browsern, nicht in der Webansicht der App, z. B. in Wechat-Browsern.

Damit es auch in Wechat-Browsern (teilweise) funktioniert, nochmal ein paar Verbesserungen:

  'use strict';
  (() => {
    const modified_inputs = new Set();
    const defaultValue = 'defaultValue';
    // store default values
    addEventListener('beforeinput', evt => {
      const target = evt.target;
      if (!(defaultValue in target.dataset)) {
        target.dataset[defaultValue] = ('' + (target.value || target.textContent)).trim();
      }
    });

    // detect input modifications
    addEventListener('input', evt => {
      const target = evt.target;
      let original = target.dataset[defaultValue];

      let current = ('' + (target.value || target.textContent)).trim();

      if (original !== current) {
        if (!modified_inputs.has(target)) {
          modified_inputs.add(target);
        }
      } else if (modified_inputs.has(target)) {
        modified_inputs.delete(target);
      }

      if(modified_inputs.size){
        const event = new Event('needSave')
        window.dispatchEvent(event);
      }
    });

    addEventListener(
      'saved',
      function(e) {
        modified_inputs.clear()
      },
      false
    );

    addEventListener('beforeunload', evt => {
      if (modified_inputs.size) {
        const unsaved_changes_warning = 'Changes you made may not be saved.';
        evt.returnValue = unsaved_changes_warning;
        return unsaved_changes_warning;
      }
    });

    const ua = navigator.userAgent.toLowerCase();

    if(/MicroMessenger/i.test(ua)) {
      let pushed = false

      addEventListener('needSave', evt => {
        if(!pushed) {
          pushHistory();

          window.addEventListener("popstate", function(e) {
            if(modified_inputs.size) {
              var cfi = confirm('确定要离开当前页面嘛?' + JSON.stringify(e));
              if (cfi) {
                modified_inputs.clear()
                history.go(-1)
              }else{
                e.preventDefault();
                e.stopPropagation();
              }
            }
          }, false);
        }

        pushed = true
      });
    }

    function pushHistory() {
      var state = {
        title: document.title,
        url: "#flag"
      };
      window.history.pushState(state, document.title, "#flag");
    }
  })();
0
Jeff Tian