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

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


当前回答

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

其他回答

我做了下面的代码。它可以比较所有字段的变化(除了那些用. 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”)。

下面的一句话对我很管用。

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

根据应用程序的状态将modified设置为true或false。

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

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

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

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