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

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

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

Ответ 1

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

Вы можете сделать это, обработав событие beforeunload и вернув 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.
});

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

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.
  • Не запускается при вырезании или вставке текста через контекстные меню.
  • Не будет работать для виртуальных входов, таких как указатели даты или флажки/кнопки-переключатели, которые сохраняют свои значения в скрытом вводе через JavaScript.

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

Привязка события input ко всем input данным (и к textarea и к select элементам) на вашей странице не будет работать в старых браузерах и, как и все упомянутые выше решения по обработке событий, не поддерживает отмену. Когда пользователь изменяет текстовое поле, а затем отменяет его или устанавливает и снимает флажок, форма все еще считается грязной.

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

Не изобретай велосипед

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

Если ваше приложение уже использует 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 выпускается, в котором также удаляются пользовательские сообщения.

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

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

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

Leave Stay

Ответ 2

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

Ответ 3

через 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 Form Serialize Google может собирать все входы форм и сохранять их в массиве. Я думаю, этого объяснения достаточно:)

Ответ 4

Универсальное решение, не требующее настройки, которое автоматически обнаруживает все входные изменения, включая 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;
    }
});
})();

Ответ 5

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

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, чтобы она не отображала предупреждения.

Ответ 6

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

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.

Ответ 7

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

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

Просто установите modified на true или false в зависимости от состояния вашего приложения.

Ответ 8

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

var somethingChanged=false;
            $('#managerForm input').change(function() { 
                somethingChanged = true; 
           }); 
            $(window).bind('beforeunload', function(e){
                if(somethingChanged)
                    return "You made some changes and it not saved?";
                else 
                    e=null; // i.e; if form state change show warning box, else don't show it.
            });
        });

Ответ 9

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;

Ответ 10

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

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

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

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

Ответ 11

Вы можете проверить подробное объяснение здесь: 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
    }

Ответ 12

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

let pageModified = true

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

Ответ 13

Решение от 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.'
        }
    });
})

... спонтанно выполнил эту работу для меня в сложной объектно-ориентированной обстановке без каких-либо изменений.

Единственное изменение, которое я применил, состояло в том, чтобы ссылаться на конкретную форму (только одну форму на файл), называемую "formForm" ("form" → "#formForm"):

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

Особенно хорошо сделано то, что кнопка отправки "оставлена в покое".

Кроме того, он работает и для меня с последней версией Firefox (по состоянию на 7 февраля 2019 года).

Ответ 14

Да, мы можем предупредить пользователей перед тем, как покинуть страницу, но Вы не можете добавить собственное сообщение в предупреждающее сообщение. Также обратите внимание:
1]. Вы не можете поместить подтверждение/предупреждение внутрь перед загрузкой.

2]. Мы не можем добавить наше специальное сообщение в предупреждающее сообщение, Bcz Каждый браузер может добавить свой собственный текст в предупреждающее сообщение. (как показано ниже изображения)

Google-Кр:

Google-Crome:

IE:

IE:

Fire Fox:

Firefox:

Ответ 15

Протестировано универсальное решение Eli Grey, сработало только после того, как я упростил код до

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

  })();

Изменения его удаляются при использовании target[defaultValue] и используются только target.dataset[defaultValue] для хранения реального значения по умолчанию.

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

Но это "универсальное" решение работает только в браузерах, но не работает в веб-приложениях, например, в веб-браузерах.

Чтобы заставить его работать в браузерах wechat (частично), еще одно улучшение:

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

Ответ 16

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