Предупреждать пользователя перед выходом с веб-страницы с несохраненными изменениями

У меня есть несколько страниц с формами в моем приложении.

Как я могу защитить форму таким образом, что если кто-то переходит или закрывает вкладку браузера, им должно быть предложено подтвердить, что они действительно хотят оставить форму с несохраненными данными?

13 ответов


короче, неправильный ответ:

вы можете сделать это обработка beforeunload событие и возврат ненулевой строки:

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

проблема с этим подходом заключается в том, что отправка формы также запускает событие выгрузки. Это легко исправить, добавив флаг a, который вы отправляете форму:

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

затем вызов сеттера при подаче:

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

, но и читать...

долго, правильный ответ:

вы также не хотите показывать это сообщение когда пользователь ничего не изменил в формах. Одним из решений является использование beforeunload событие в сочетании с" грязным " флагом, который только запускает приглашение, если оно действительно актуально.

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

теперь реализовать isDirty метод, существуют различные подходы.

можно использовать jQuery и сериализация форм, но у этого подхода есть некоторые недостатки. Сначала вам нужно изменить код для работы над любой формой ($("form").each() будет делать), но самая большая проблема заключается в том, что jQuery serialize() будет работать только на именованных, не отключенных элементах, поэтому изменение любого отключенного или неназванного элемента не вызовет грязный флаг. для этого есть обходные пути, например, сделать элементы управления только для чтения вместо включения, сериализации, а затем снова отключить элементы управления.

таким образом, события, кажется, путь. Вы можете попробовать слушать нажатия клавиш. Это событие имеет несколько вопросов:

  • не будет запускать флажки, переключатели или другие элементы, которые изменяются с помощью ввода мыши.
  • вызовет для нерелевантных нажатий клавиш, таких как Ctrl ключ.
  • не будет вызывать значения, заданные с помощью кода JavaScript.
  • не будет срабатывать при вырезании или вставке текста через контекстные меню.
  • не будет работать для виртуальных входов, таких как datepickers или checkbox / radiobutton beautifiers, которые сохраняют свое значение в скрытом вводе через JavaScript.

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

привязка input событие для всех inputs (и textareas и selects) на странице не будет работать на старых браузерах и, как все решения для обработки событий упомянутый выше, не поддерживает undo. Когда пользователь изменяет текстовое поле, а затем отменяет его или проверяет и снимает флажок, форма по-прежнему считается грязной.

и когда вы хотите реализовать больше поведения, например, игнорировать определенные элементы, у вас будет еще больше работы.

Не изобретайте колесо:

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

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

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

пользовательские сообщения не поддерживаются везде

отметим, что Firefox 4 не поддерживает пользовательские сообщения в этом диалоге. По состоянию на прошлый месяц, Chrome 51 выкатывается в котором пользовательские сообщения также удаляются.

некоторые альтернативы существуют в другом месте на этом сайте, но я думаю, что такой диалог достаточно ясен:

вы действительно хотите покинуть этот сайт?

внесенные вами изменения могут не быть сохранены.

оставить остаться


Проверьте JavaScript событие onbeforeunload. Это нестандартный JavaScript, представленный Microsoft, однако он работает в большинстве браузеров и их onbeforeunload документации больше информации и примеров.


через 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.
});

вы можете сериализовать функцию формы jQuery Google, это соберет все входные данные формы и сохранит ее в массиве. Думаю, этого объяснения достаточно:)


основываясь на предыдущих ответах и собранных из разных мест в Stack overflow, вот решение, которое я придумал, которое обрабатывает случай, когда вы действительно хотите отправить свои изменения:

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;

стоит отметить, что IE11, похоже, требует, чтобы closeEditorWarning возвращает функция undefined для него не показывать предупреждение.


следующий однострочный работал для меня.

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

просто набор modified to правда или false в зависимости от состояния приложения.


следующий код прекрасно работает. Вам нужно достичь входных изменений элементов формы через атрибут id:

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

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;

универсальное решение, не требующее настройки, которое автоматически обнаруживает все изменения ввода, включая contenteditable элементы:

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

короткий ответ:

let pageModified = true

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

построен поверх Васим А. отличная идея использовать сериализацию. Проблема заключалась в том, что предупреждение также показывалось при подаче формы. Это было исправлено здесь.

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

Он был протестирован в Chrome и IE 11.


добавление к идее @codecaster вы можете добавить это на каждую страницу с формой (в моем случае я использую ее глобальным способом, поэтому только на формах будет это предупреждение) измените его функцию на

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

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

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

вы можете проверить подробное описание здесь: http://techinvestigations.redexp.in/comparison-of-form-values-on-load-and-before-close/ сравнения-форма-значений-на-нагрузк-и-перед-закрыть

основной код:

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
    }

во-первых, большинство браузеров имеет эту функцию по умолчанию. И зачем тебе это вообще нужно? Почему бы не синхронизировать форму? Я имею в виду, сохраните его при любом изменении, не дожидаясь отправки от пользователя. Как и Контакты Google. Конечно, если только все поля в форме являются обязательными. Пользователям не нравится, когда их заставляют что-то заполнять без возможности уйти, чтобы подумать, Нужно ли им это. :)