23
  1. 在文本区域中输入/更改某些内容
  2. 在提交表单之前,离开页面(例如通过点击浏览器的后退按钮)
  3. 返回编辑页面(例如通过单击前进按钮)

预期结果:在 textarea 中输入的内容应该仍然存在

实际结果:

  • 使用HTTPS:所有更改都消失了(糟糕!)
  • 使用HTTP:更改仍然存在(好!)

为什么在使用 HTTPS 时会发生这种情况?我怎样才能防止这种情况?是浏览器还是网站负责?

4

2 回答 2

90

您可以考虑以下解决方案:

属性( autocompleteHTML5)

这似乎无关,因为告诉浏览器使用基于早期用户输入autocomplete的值完成字段,这些用户输入与表单一起“提交”。但在我的测试中,我看到了这一点;填写表格后未提交;当我按下前进(历史)按钮并再次回击时;如果我设置表单字段会自动填充,并且在设置为.autocomplete="on""off"

所以; (如果针对 HTML5 用户)您可以使用此属性来“缓存”您的表单数据。(适用于所有主流浏览器,Opera 除外)。

<form action="/update" method="post" autocomplete="on">
    Email:    <input type="text" id="email" /><br />
    Username: <input type="text" id="uname" /><br />
    Password: <input type="password" id="pwd" autocomplete="off"/><br />
    <input type="submit" />
</form> 

请注意,当其余表单控件打开时,您可以为特定字段(在本例中为密码)设置自动完成功能。

MSDN 备注:

  • 如果缺少 autocomplete 属性,如果元素没有父表单,或者表单的自动完成设置为“on”,则该字段将默认为“on”状态。
  • 自动完成功能提供的信息不会暴露给对象模型,并且在用户选择其中一个建议作为文本字段的值之前对网页不可见。

在本地保存未提交的表单数据:

您可以将输入数据存储在本地,在页面重定向之前或每个表单控件的焦点输出事件上:

饼干

在这种情况下,旧 cookie 可以派上用场,但您应该考虑缺点:

  1. 即使您可以以编程方式加密值;由于我们将在客户端工作,因此 cookie 对此并不真正安全。Http-Only标记的Securecookie 在这里对我们没有帮助,因为这些选项用于在 cookie 被“发送”(安全)并且无法从 Javascript(仅限 http)访问时强制执行 SSL。
  2. 浏览器有 cookie 大小限制。来自MSDN“大多数浏览器支持最多 4096 字节的 cookie。由于这个小限制,cookie 最好用于存储少量数据”。因此,如果您不注意这个大小(当您编写 cookie 和/或通过maxlength属性限制控件的值时);这可能是个问题。(在这种情况下,修剪值是最糟糕的事情)。
  3. 浏览器对每个域可以设置的 cookie 数量也有限制。所以; 在 cookie 中存储表单数据时;而不是为每个表单字段值设置 cookie;您应该将它们合并到一个或几个 cookie 中;为您的网站不超过此限制。

不过,好的一面是它们受到所有浏览器的支持,如果您不打算通过 Cookies“缓存”敏感和过长的数据,那么您可以使用以下解决方案。如果不是这种情况;您最好采用下一个建议:localStorage

// Below is just a demonstration and is not tested thoroughly for 
// production-ready web applications by any means.  
// But it should give you an idea.

/** 
 * Caches the user-input data from the targeted form, stores it in the cookies 
 * and fetches back to the form when requested or needed. 
 */
var formCache = (function () {
    var _form = null, 
        _formData = [],
        _strFormElements = "input[type='text'],"
                + "input[type='checkbox']," 
                + "input[type='radio']," 
                // + "input[type='password'],"  // leave password field out 
                + "input[type='hidden'],"
                // + "input[type='image'],"
                + "input[type='file'],"
                // more input types...
                + "input[type='email'],"
                + "input[type='tel'],"
                + "input[type='url'],"
                + "select,"
                + "textarea";

    function _warn() {
        console.log('formCache is not initialized.');
    }

    return {

        /**
         * Initializes the formCache with a target form (id). 
         * You can pass any container id for the formId parameter, formCache will 
         * still look for form elements inside the given container. If no form id 
         * is passed, it will target the first <form> element in the DOM. 
         */
        init: function (formId) {
            var f = (typeof formId === 'undefined' || formId === null || $.trim(formId) === '') 
                    ? $('form').first() 
                    : $('#' + formId);
            _form = f.length > 0 ? f : null;
            console.log(_form);
            return formCache; // make it chainable
        },

        /** 
         * Stores the form data in the cookies.
         */
        save: function () {
            if (_form === null) return _warn();

            _form
                .find(_strFormElements)
                .each(function() {
                    var f = $(this).attr('id') + ':' + formCache.getFieldValue($(this));
                    _formData.push(f);
                });
            docCookies.setItem('formData', _formData.join(), 31536e3); // 1 year expiration (persistent)
            console.log('Cached form data:', _formData);
            return formCache;
        },

        /** 
         * Fills out the form elements from the data previously stored in the cookies.
         */
        fetch: function () {
            if (_form === null) return _warn();

            if (!docCookies.hasItem('formData')) return;
            var fd = _formData.length < 1 ? docCookies.getItem('formData').split(',') : _formData;
            $.each(fd, function (i, item) {
                var s = item.split(':');
                var elem = $('#' + s[0]);
                formCache.setFieldValue(elem, s[1]);
            });
            return formCache;
        },

        /** 
         * Sets the value of the specified form field from previously stored data.
         */
        setFieldValue: function (elem, value) {
            if (_form === null) return _warn();

            if (elem.is('input:text') || elem.is('input:hidden') || elem.is('input:image') ||
                    elem.is('input:file') || elem.is('textarea')) {
                elem.val(value);
            } else if (elem.is('input:checkbox') || elem.is('input:radio')) {
                elem.prop('checked', value);
            } else if (elem.is('select')) {
                elem.prop('selectedIndex', value);
            }
            return formCache;
        },

        /**
         * Gets the previously stored value of the specified form field.
         */
        getFieldValue: function (elem) {
            if (_form === null) return _warn();

            if (elem.is('input:text') || elem.is('input:hidden') || elem.is('input:image') ||
                elem.is('input:file') || elem.is('textarea')) {
                    return elem.val();
                } else if (elem.is('input:checkbox') || elem.is('input:radio')) {
                    return elem.prop('checked');
                } else if (elem.is('select')) {
                    return elem.prop('selectedIndex');
                }
            else return null;
        },

        /**
         * Clears the cache and removes the previously stored form data from cookies.
         */
        clear: function () {
            _formData = [];
            docCookies.removeItem('formData');
            return formCache;
        },

        /**
         * Clears all the form fields. 
         * This is different from form.reset() which only re-sets the fields 
         * to their initial values.
         */
        clearForm: function () {
            _form
                .find(_strFormElements)
                .each(function() {
                    var elem = $(this);
                    if (elem.is('input:text') || elem.is('input:password') || elem.is('input:hidden') || 
                        elem.is('input:image') || elem.is('input:file') || elem.is('textarea')) {
                        elem.val('');
                    } else if (elem.is('input:checkbox') || elem.is('input:radio')) {
                        elem.prop('checked', false);
                    } else if (elem.is('select')) {
                        elem.prop('selectedIndex', -1);
                    }
                });
            return formCache;
        }
    };
})();

// Save form data right before we unload the form-page
$(window).on('beforeunload', function (event) {
    formCache.save();
    return false;
});

// Initialize and fetch form data (if exists) when we load the form-page back
$(document).on('ready', function (event) {
    formCache.init().fetch();
});

这是关于 jsFiddle 的工作演示

注意:来自developer.mozilla.org的“cookies reader/writer”脚本应该包含在上面的代码中。您还可以使用 Yahoo 的YUI 2: Cookie Utility,它有一个有用的setSub()方法,用于在单个 cookie 中设置子 cookie,用于我之前提到的浏览器限制。

本地存储

您还可以使用更现代的技术,例如localStorage(HTML5)。它更安全,更快捷。所有主流浏览器都支持此功能,包括 IE 8+。(此外,iOS 和 Android 支持!)

if (typeof Storage !== 'undefined') { // We have local storage support
    localStorage.username = 'Onur'; // to save to local storage
    document.getElementById('uname').value = localStorage.username; // to fetch from local storage
}

因此,就像在 cookie 示例中一样;

$(window).on('beforeunload', function (event) {
    saveFormToLocalStorage();
    return false;
});

$(document).on('ready', function (event) {
    fillFormFromLocalStorage()
});

会话存储

这几乎以相同的方式工作。来自 W3C: sessionStorage 对象与 localStorage 对象相同,只是它只存储一个会话的数据。

通过静默 AJAX 帖子将表单数据保存到服务器/数据库:

这不是一种非常有效的方法,但您可能希望在其他方法不可行的情况下使用它。您可以在beforeunload事件上发布帖子并向用户提示消息。

$(window).on('beforeunload', function (event) {
    //check if at least one field is filled out.
    //make the AJAX post if filled out.
    return "You are leaving the page without submitting the form...";
});

在页面加载时从服务器检索以前保存的数据:

提醒你一下; 例如,如果用户正在填写“更新”表格;您始终可以从服务器获取以前保存的数据并自动填写表格(非敏感字段)。

结论

如果您真的需要这个并且值得麻烦;您应该考虑实现回退机制的跨浏览器解决方案;如:

  • 如果您支持 HTML5 功能;使用HTML5autocomplete 属性。(您可以预先在 HTML 中嵌入该属性,或者在测试浏览器支持时通过 Javascript/jQuery 进行设置。)
  • ELSE 如果您支持该Storage对象;一起去 localStorage
  • ELSE IF [cookies your current session stores] + [cookie size your form data needs] < 4096 bytes; 然后使用cookies.
  • 否则,如果您有服务器端 Web 应用程序,请发出静默 AJAX 请求以 将数据存储在 server 上
  • 否则不要这样做。

注意:对于 HTML5 功能检测,请查看此页面此页面,或者您可以使用Modernizr

HTTPS 问题

原因是,使用HTTPS时所有表单更改都消失了;这是一个安全协议。表单主要用于用户输入,并且可以(可能)包含敏感数据。所以这种行为看起来很自然,也是意料之中的。我在上面提供的解决方案与在 HTTP 上的工作方式相同。所以这应该涵盖你所有的担忧。

进一步阅读:

于 2013-01-20T04:40:36.300 回答
0

这对我有用。

<select
  class="form-select custom-select page-number-select"
  (change)="onPageChange($event)"
  data-test="XXXX"
  [attr.aria-labelledby]="XXXX"
  [value]="pageNumber" <---- This fixed the problem
>
  <ng-container
    *ngFor="let pageNumber of totalPageCount"
  >
    <option value="{{ pageNumber }}" [attr.selected]="pageNumber == page ? '' : null" >
      {{ t('pageN', { pageNumber: pageNumber }) }}
    </option>
   </ng-container>
</select>

在value属性中添加来自流的数据可确保始终显示正确的值。即使在浏览器的 popstate 事件(后退和前进按钮点击)

于 2021-04-23T11:32:02.467 回答