我的应用程序中有一些带有表单的页面。
我如何以某种方式保护表单,如果有人导航或关闭浏览器选项卡,则应提示他们确认他们确实要保留未保存数据的表单?
Short, wrong answer:
You can do this by handling the beforeunload
event and returning a non-null string:
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()
会这样做),但是最大的问题是jQueryserialize()
只能在命名的,未禁用的元素上工作,因此更改任何禁用或未命名的元素都不会触发脏标志。有一些解决方法,例如将控件设为只读而不是启用,序列化然后再次禁用控件。
因此,事件似乎是路要走。您可以尝试收听按键。此事件有几个问题:
- 不会触发复选框,单选按钮或通过鼠标输入更改的其他元素。
- 将触发不相关的按键,如Ctrl按键。
- 不会触发通过JavaScript代码设置的值。
- 通过上下文菜单剪切或粘贴文本不会触发。
- 对于日期选择器或复选框/单选按钮美化器等虚拟输入(通过JavaScript将其值保存在隐藏输入中)不起作用。
该change
事件也不会触发从JavaScript代码设置的值,因此也不适用于虚拟输入。
将input
事件绑定到页面上的所有input
s(以及textarea
s和select
s)在旧版浏览器上将无法使用,并且像上述所有事件处理解决方案一样,不支持撤消。当用户更改文本框然后撤消该文本框,或者选中并取消选中一个复选框时,该窗体仍被视为脏页。
当您想要实现更多的行为时,例如忽略某些元素,您将需要做更多的工作。
不要重新发明轮子:
因此,在考虑实施这些解决方案和所有必需的变通办法之前,请先意识到自己正在重新发明轮子,并且容易遇到其他人已经为您解决的问题。
如果您的应用程序已经使用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
查看JavaScript onbeforeunload事件。它是Microsoft推出的非标准JavaScript,但是可在大多数浏览器中使用,并且它们的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.
});
您可以使用Google JQuery表单序列化功能,该功能将收集所有表单输入并将其保存在数组中。我想这解释就足够了:)
通用解决方案,无需配置即可自动检测所有输入修改,包括可编辑内容的元素:
"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 with 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;
}
});
})();
建立在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中进行了测试。
根据先前的答案,并从堆栈溢出的各个地方进行凑整,这是我想出的解决方案,用于解决您实际要提交更改的情况:
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
为true或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;
测试了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");
}
})();
您可以使用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撰写
添加@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
}
简短答案:
let pageModified = true
window.addEventListener("beforeunload",
() => pageModified ? 'Close page without saving data?' : null
)
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日)。
我做了不同的事情,在这里分享,以便某人可以获取帮助,仅使用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;
});
我做了下面的代码。它可以比较所有字段(用.ignoreDirty类标记的字段除外)中的更改,或仅对当前可见的字段进行比较。可以为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自动填充值会给我带来其他麻烦,因为很难初始化和加载它们。因此,我不会在页面加载时进行初始化,而是在任何focusin事件中进行初始化。(但是:可能仍然存在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类添加到提供“保存”的按钮中,这样在这种情况下,我可以避免发出警告。如果用户在收到警告时仍停留在Page上,则ifStayFunc允许我执行某些操作。通常,我可以显示其他“保存按钮”(如果我仍然只看到一些默认/主按钮,这将使“安全” +“更多”,并且我希望允许“此其他”来保存)。
首先,默认情况下,大多数浏览器都具有此功能。为何您完全需要这个呢?为什么不保持表单同步?我的意思是,将其保存在任何更改上,而无需等待用户的任何提交。就像Google通讯录一样。当然,如果仅表单中的所有字段均为必填项。用户不喜欢什么时候强迫他们填充一些东西而又没有机会去思考是否需要它们。:)
文章标签:forms , javascript
版权声明:本文为原创文章,版权归 javascript 所有,欢迎分享本文,转载请保留出处!
评论已关闭!