在未保存更改离开网页前警告用户

我的申请表里有几页表格。

如何确保表单的安全性,以便在用户导航离开或关闭浏览器选项卡时,提示他们确认是否确实希望保留未保存数据的表单?

398561 次浏览

查看JavaScript onbeforeunload事件。它是微软引入的非标准JavaScript,但它可以在大多数浏览器中运行,他们的onbeforeunload文档有更多的信息和示例。

简短而错误的回答:

你可以通过 处理 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.
});

这种方法的问题在于 提交表单也会触发卸载事件。这个问题可以通过添加表单中的标志来轻松解决:

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

然后在提交时呼叫 setter:

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

继续读下去。

长而正确的回答:

您也不希望显示此消息 当用户没有更改表单上的任何内容时。一种解决方案是将 beforeunload事件与“ Dirty”标志结合使用,该标志只有在真正相关时才触发提示。

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(以及 textareas 和 selects) ,并且像上面提到的所有事件处理解决方案一样,不支持撤消。当用户更改文本框并撤消该文本框时,或者选中复选框并取消选中复选框时,表单仍被认为是脏的。

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

不要重蹈覆辙:

因此,在你考虑实施这些解决方案和所有必要的变通方法之前,要意识到你是个重造轮子,你很容易遇到别人已经为你解决的问题。

如果您的应用程序已经使用了 jQuery,那么您可以使用经过测试的、维护的代码而不是自己的代码,并且使用第三方库来完成所有这些工作。

(由@troseman 在注释中建议)提供了一些函数,用于正确检测表单是否已更改,并防止用户在显示提示时离开页面。它还有其他有用的功能,比如重置表单,将表单的当前状态设置为“干净”状态。示例用法:

$("#myForm").dirty({preventLeaving: true});

一个较老的,目前已经废弃的项目是 你确定吗,它也工作得很好; 参见它们的 演示页面。示例用法:

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

不是所有地方都支持自定义消息

请注意,从2011年开始,火狐4在此对话框中不支持自定义消息。截至2016年4月,Chrome51正在推出 其中自定义消息也被删除

在这个网站的其他地方也存在一些替代方案,但我认为这样的对话框足够清晰:

你想离开这个网站吗?

您所做的更改可能无法保存。

Leave Stay

基于以前的答案,并从堆栈溢出的不同地方拼凑起来,下面是我想出的解决方案,当您真正想要提交更改时,它可以处理这种情况:

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,以使其不显示警报。

通过 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 Form Serialize 函数,这将收集所有表单输入并将其保存为数组。我想这个解释就足够了:)

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;

增加@codecaster 的想法 你可以把它添加到每个页面的一个表单(在我的情况下,我使用它在全局的方式,所以只有在表单上会有这个警告)改变他的功能

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

还放在表格提交包括登录和取消按钮链接,所以当人按取消或提交表格不会触发警告也在每个页面与表单..。

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

下面的代码非常好用。您需要通过 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.
});
});

下面的俏皮话对我很有用。

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

只需根据应用程序的状态将 modified设置为 没错假的即可。

通用解决方案不需要自动检测所有输入修改(包括可内容元素)的配置:

"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", (evt) => {
modified_inputs.clear();
// to prevent the warning from happening, it is advisable
// that you clear your form controls back to their default
// state with evt.target.reset() or form.reset() 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;
}
});
})();

简短的回答:

let pageModified = true


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

构建在 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 和 IE11中进行了测试。

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日)。

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

通过序列化表单值并在卸载前检查表单是否已更改,可以使用 seralize ()创建 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?'
}
})

请参考这个链接 < a href = “ https://coderwall.com/p/gny70a/alert-when-leave-page-with-unsave-form”rel = “ norefrer”> https://coderwall.com/p/gny70a/alert-when-leaving-page-with-unsaved-form 作者: Vladimir Sidorenko

我做的不一样,在这里分享,这样有人可以得到帮助,测试只与 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.";
}
};

工作很好,但是有一个-其他的问题,当我提交表单时,我得到了不想要的警告,我看到了很多解决方案,这是因为 onbefore 卸载之前发生 onmit,这就是为什么我们不能处理它在像 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;
});

我做了下面的代码。它可以比较所有字段中的更改(标记为。或者只对当前可见字段使用。它可以为 Javascript 添加的新字段重新初始化。由于这个原因,我不保存窗体状态,而是保存每个控件的状态。

/* Dirty warning for forms */
dirty = (skipHiddenOrNullToInit) => {
/*  will return True if there are changes in form(s)
for first initialization you can use both: .dirty(null) or .dirty() (ignore its result)
.dirty(null) will (re)initialize all controls - in addititon use it after Save if you stay on same page
.dirty() will initialize new controls - in addititon use it if you add new fields with JavaScript
then
.dirty() (or: .dirty(false)) says if data are changed without regard to hidden fields
.dirty(true) says if data are changed with regard to hidden fields (ie. fields with .d-none or .hidden class)
controls with .ignoreDirty class will be skipped always
previous about .d-none, .hidden, .ignoreDirty applies to the control itself and all its ancestors
*/
let isDirty = false;
let skipSelectors = '.ignoreDirty';
if (skipHiddenOrNullToInit) {
skipSelectors += ', .d-none, .hidden'
} else if (skipHiddenOrNullToInit === undefined) {
skipHiddenOrNullToInit = false;
}
$('input, select').each(
function(_idx, el) {
if ($(el).prop('type') !== 'hidden') {
let dirtyInit = $(el).data('dirty-init');
if (skipHiddenOrNullToInit === null || dirtyInit === undefined) {
try {
isChromeAutofillEl = $(el).is(":-webkit-autofill");
} catch (error) {
isChromeAutofillEl = false;
}
if (isChromeAutofillEl && $(el).data('dirty-init') === undefined) {
setTimeout(function() {  // otherwise problem with Chrome autofilled controls
$(el).data('dirty-init', $(el).val());
}, 200)
} else {
$(el).data('dirty-init', $(el).val());
}
} else if ($(el).closest(skipSelectors).length === 0 && dirtyInit !== $(el).val()) {
isDirty = true;
return false; // breaks jQuery .each
}
}
}
);
return isDirty;
}

我有额外的麻烦与 Chrome 自动填充值,因为它是困难的初始化和已经加载他们。因此,我不初始化页面加载,但在任何焦点事件。(但是: 也许 JavaScript 改变的控制值仍然存在问题。)我使用以下代码,我调用在页面加载:

let init_dirty = (ifStayFunc) => {
/*  ifStayFunc: optional callback when user decides to stay on page
use .clearDirty class to avoid warning on some button, however:
if the button fires JavaScript do't use .clearDirty class and instead
use directly dirty(null) in code - to be sure it will run before window.location */
$('input, select').on('focusin', function(evt) {
if (!$('body').data('dirty_initialized')) {
dirty();
$('body').data('dirty_initialized', true);
}
});
window.addEventListener('beforeunload', (evt) => {
if (dirty(true)) {
if (ifStayFunc) {
ifStayFunc();
}
evt.preventDefault();
evt.returnValue = '';  // at least Google Chrome requires this
}
});
$('.clearDirty').on('click', function(evt) {
dirty(null);
});
};

所以,我加上。ClearDirty 类转换为提供保存的按钮,这样我就可以避免在这种情况下出现警告。 回调 ifStayFunc 允许我做一些事情,如果用户将留在页面上,而他被警告。通常我可以显示额外的保存按钮(如果我仍然只看到一些默认/主按钮,这使得安全 + 某事更多-我希望允许保存没有这个“某事更多”)。