在离开网页之前进行未保存的更改之前警告用户


340

我的应用程序中有一些带有表单的页面。

如何以某种方式保护表单,如果有人离开或关闭了浏览器选项卡,则应提示他们确认他们确实要保留未保存的数据?



3
这里这个问题应该有你后回答:stackoverflow.com/questions/1244535/...
Nexerus


Answers:


556

简短错误的答案:

您可以通过处理beforeunload事件并返回非null字符串来做到这一点:

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事件绑定到页面上的所有inputs(以及textareas和selects)较旧的浏览器上将不起作用,并且像上述所有事件处理解决方案一样,不支持撤消。当用户更改文本框然后撤消该文本框,或者选中并取消选中一个复选框时,该表单仍被认为是脏表单。

当您想要实现更多的行为时,例如忽略某些元素,您将需要做更多的工作。

不要重新发明轮子:

因此,在考虑实施这些解决方案和所有必需的变通办法之前,请先意识到自己正在重新发明轮子,并且容易遇到其他人已经为您解决的问题。

如果您的应用程序已经使用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在此对话框中不支持自定义消息。从2016年4月开始,Chrome 51即将推出,其中自定义消息也将被删除

本网站其他地方也有一些替代方法,但我认为这样的对话框很清楚:

您要离开这个网站吗?

您所做的更改可能不会保存。

Leave Stay


6
请注意,在Chrome中,不再使用自定义字符串了;chromestatus.com/feature/5349061406228480
amann

5
是的,这在我的答案的最后一部分中进行了描述。
CodeCaster

2
@Chris,这是因为Angular正在操纵DOM来更改页面,而不是离开当前文档。
CodeCaster

15
请注意,jQuery areYouSure自2014年以来已被放弃(Github上有57个未解决的问题),它充满了我的经验,似乎一开始就可以使用,但是经过一些快速测试后,我意识到它并不总是可以使用。完全不会推荐这样做
马克

4
@JacopoStanchi很遗憾,我没有找到任何东西。我的意思是说我看上去很多-但这是1月10日,所以有人可能写了一些东西,但我对此表示怀疑。最好的选择是自己实施它-这不是您想听到的,抱歉。(但是至少您不会像我使用jQuery areYouSure那样获得任何惊喜。)还有谁知道您甚至可以将其开源,并且您的实现将与他人共享;)
Mark


34

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

您可以使用Google JQuery表单序列化功能,该功能将收集所有表单输入并将其保存在数组中。我想这解释就足够了:)


6
不要使用#form作为表单ID,因为窗口将创建一个名为form的对象:)
mdikici 2014年

1
这里检查,如果它不为你工作:stackoverflow.com/questions/7080269/...
Leniel Maccaferri

16

通用解决方案,无需配置即可自动检测所有输入修改,包括可编辑内容的元素:

"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);
    }
});
// clear modified inputs upon form submission
addEventListener("submit", () => {
    modified_inputs.clear();
    // to prevent the warning from happening, it is advisable
    // that you clear your form controls back to their default
    // state after submission
});
// warn before closing if any inputs are modified
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;
    }
});
})();

1
凉豆。只需将这段代码复制粘贴到您的测试页中,进行更改并点击刷新即可。它将显示本机浏览器警报,警告您尚未保存的更改。
TheLegendaryCopyCoder

1
这对我来说适用于Chrome 71.0.3578.98(正式版本)(64位)
JacobRossDev,

2
这确实很好,但是如果您将此表单与提交表单一起使用,则需要先清除modified_inputs设置,然后才能收到确认更改警报。我在表单中添加了一个事件侦听器以调用submit,然后将set函数运行为clear,这样,当beforeunload事件侦听器运行时,modified_inputs就会清除,并允许发布继续进行。
stepquick

1
酷实际上复制并粘贴。谢谢您节省我的时间。在Firfox 75.0中为我工作
Connectify_user

1
喜欢这个片段
Rawburner

10

建立在Wasim A.使用序列化绝妙想法之上。问题在于,提交表单时也会显示警告。此问题已在此处修复。

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中进行了测试。


Wasim A和Eerik Sven Puudist-谢谢,我使用id表示表单,使用id表示保存按钮,因为我所有按钮的类型均为Submit <form action =“” id =“ marksform” method =“ POST”> <td> <input type = “ submit” name =“ submit_button” id =“保存” value =“保存”> </ td> <td> <输入类型=“ submit” name =“ submit_button” value =“ Next”> </ td>很少更多所有类型的提交因此替换为.Submit(通过特定的单击$('#save')。click(function(){isSubmitting = true})使用'#marksform'而不是'
form'– Jayanta,

7

根据先前的答案,并从堆栈溢出的各个地方进行凑整,这是我想出的解决方案,用于解决您实际要提交更改的情况:

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才能不显示警报。


该解决方案没有验证任何值从其原始状态被改变,因此在用户输入文本并且在试图导航之前删除输入的文本的情况下无效。
Brett Weber 2014年

好点,@ BrettWeber。您可以在这种情况下修改closeEditorWarning,并使用“ if(window.thisPage.isDirty && uiHasChanged())”,其中uiHasChanged是一个了解服务器原始状态并检查所有差异的函数,但是此解决方案是针对在这种情况下,我并不想特别做所有额外的工作,并且只有当在文本区域中进行了实际的按键操作时,才会出现误报,我认为这是一个合理的妥协。:)
Jonathan

QC.thisPage.isDirty = false;应该window.thisPage.isDirty = false;吗?这样,它将检查您是否正在提交表单并且不显示警告。似乎为我的测试工作。而且,大多数形式都带有input而不是textarea。我使用了以下效果很好的方法:$("form").on('keyup', 'textarea,input,select', function () {
Mike

7

以下一线工作对我有用。

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

只需根据应用程序的状态将其设置modifiedtruefalse


2
一些建议:返回自定义消息,而不是空字符串,因为某些浏览器会显示该消息(例如Edge);如果返回false ,则返回undefined而不是nullIE会输出“ null” modified
凯文·李

1
移动设备呢?我的理解是IOS设备不支持onbeforeunload。
jaybro

4

以下代码效果很好。您需要通过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.
            });
        });

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

我将其插入一个单独的文件中,并将其包含在<script src =“ warnOnChange.js”> </ script>中,但在修改文本字段然后执行标头命令时没有任何反应。
Fabrizio Bartolomucci 2014年

2

您可以使用serialize()通过序列化表单值来创建URL编码的文本字符串,并在卸载前检查表单是否已更改

$(document).ready(function(){
    var form = $('#some-form'),
        original = form.serialize()

    form.submit(function(){
        window.onbeforeunload = null
    })

    window.onbeforeunload = function(){
        if (form.serialize() != original)
            return 'Are you sure you want to leave?'
    }
})

请参阅此链接https://coderwall.com/p/gny70a/alert-when-leaving-page-with-unsaved-form 由Vladimir Sidorenko撰写


1
它确实有效,但是正如代码转换程序所回答的那样,此方法存在一些缺陷,请参阅以下说明stackoverflow.com/a/7317311/10555981
Pradeep

使用已有两个答案serialize(),并且确实有其缺点。
CodeCaster

这应该是正确的答案,使用起来非常简单并且可以按预期工作。
Jodyshop '19

1

添加到@codecaster的想法,您可以将它添加到具有表单的每个页面中(在我的情况下,我以全局方式使用它,因此仅在表单上会有此警告),将其功能更改为

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

还放置表单提交,包括登录和“取消”按钮链接,因此当用户按下“取消”或提交表单时,表单的每一页也不会触发警告...

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

1

您可以在此处查看详细说明: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
    }


1

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(截至2019年2月7日)。


1

测试了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]存储实际默认值。

我添加了一个“保存的”事件侦听器,您可以在成功执行保存操作后自行触发“保存的”事件。

但是,这种“通用”解决方案仅适用于浏览器,不适用于应用程序的Webview(例如,微信浏览器)。

为了使其(部分地)在微信浏览器中工作,再次进行了另一项改进:

  '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

我做了不同的事情,在这里分享,以便某人可以获得帮助,仅使用Chrome进行测试。

我只在有一些更改的情况下才在关闭选项卡之前警告用户。

<input type="text" name="field" value="" class="onchange" />

var ischanged = false;

$('.onchange').change(function () {
    ischanged = true;
});

window.onbeforeunload = function (e) {
    if (ischanged) {
        return "Make sure to save all changes.";
    }        
};

效果很好,但是还有另一个问题,当我提交表单时收到了不必要的警告,我看到了很多解决方法,这是因为onbeforeunload在onsubmit之前触发,这就是为什么我们无法在onsubmit事件中处理它onbeforeunload = null,但是提交按钮的onclick事件在这两个事件之前触发,因此我更新了代码

var isChanged = false;
var isSubmit = false;

window.onbeforeunload = function (e) {
    if (isChanged && (!isSubmit)) {
        return "Make sure to save all changes.";
    }        
};

$('#submitbutton').click(function () {
    isSubmit = true;
});

$('.onchange').change(function () {
    isChanged = true;
});

-5

首先,默认情况下,大多数浏览器都具有此功能。为何您完全需要它?为什么不保持表单同步?我的意思是,将其保存在任何更改上,而无需等待用户的任何提交。就像Google通讯录一样。当然,如果仅表单中的所有字段都是必填字段。用户不喜欢什么时候强迫他们填充一些东西而又没有机会去思考是否需要它们。:)


您是否正确,但是我的表单是动态生成的,并且字段不在我的控制范围内,因此对我来说,无法跟踪所有字段并通过ajax发送请求,如果您有任何想法,请与我分享。

好的,您可能不需要这样做,但仍然可以实现它。如果表单元素在屏幕上,则可以附加自己的事件处理程序并获取所需的任何数据。然后使用AJAX将数据保存到您想要的任何位置。即使表单具有随机生成的ID或类名,如果可以预期表单的结构,也可以使用XPath。是XML / HTML对吗?因此,您甚至可以与负责动态生成表单的人员一起创建JAD,并讨论XSTL如何在此项目上使您俩都受益。只是一个想法...
SimonDever

1
save it on any change without waiting any submitting from user您并不总是想要这样做。有时,用户想要进行许多更改,然后查看并确认他们要保存。
BadHorsie

1
我不建议提交表格。数据可以保存在localStorage中,某些缓存中,甚至可以通过草稿标志提交。有很多方法可以解决此问题。这比警告用户有关他尚未填写表格要好得多,如果他不愿意并且将关闭窗口,则下次必须再次填写该表格。“来吧,伙计,别偷懒,您的火车/公共汽车/飞机/等都没有关系,我们也不在乎您是否忘记了笔记本电脑的充电器,只需填写这张该死的表格!”
YuS
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.