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

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


当前回答

下面的代码工作得很好。你需要通过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.
            });
        });

其他回答

简短的错误回答:

你可以通过处理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将其值保存在隐藏的输入中。

从JavaScript代码中设置的值也不会触发change事件,因此也不适用于虚拟输入。

将输入事件绑定到页面上的所有输入(以及文本区域和选择)在旧的浏览器上不起作用,并且,像上面提到的所有事件处理解决方案一样,不支持撤销。当用户更改文本框然后取消该文本框,或者选中又取消选中复选框时,表单仍然被认为是脏的。

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

不要白费力气:

所以,在你考虑实施这些解决方案和所有需要的变通方法之前,要意识到你是在重新发明轮子,你很容易遇到别人已经为你解决了的问题。

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

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

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

jQuery的Are You Sure?插件,也很好用;查看他们的演示页面。使用示例:

<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年开始,Firefox 4就不支持这个对话框中的自定义消息。截至2016年4月,Chrome 51正在推出,自定义消息也将被删除。

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

你想离开这个网站吗? 您所做的更改可能不会被保存。 离开待

简短的回答:

let pageModified = true

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

你可以在这里查看详细的解释: 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
    }

我做了下面的代码。它可以比较所有字段的变化(除了那些用. 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类添加到提供Save的按钮中,这样就可以防止在这种情况下出现警告。 回调ifStayFunc允许我做一些事情,如果用户将停留在页面上,而他是警告。通常我可以显示额外的保存按钮(如果我仍然只看到一些默认/主按钮,这使得安全+SomethingMore -我想允许保存没有这个“SomethingMore”)。

我做了不同的,分享在这里,以便有人可以得到帮助,只测试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;
});