w3ctech

【Polyfill】jQuery 模拟 HTML5 form 属性

在设计 HTML 表单的时候,通常的结构都是一个 form 里面包含了各种 input / select / button 等字段元素,这样就可以把元素和表单关联起来了。但如果字段在 form 的外面呢?在 HTML5 中引入的 form 属性,可以显式地给字段指定一个“目的地”表单。

例如如下代码:

    <form id="contact_form" method="get">
    <label>Name:</label><input type="text" id="name" name="name">Email:</label>
    <input type="email" id="email" name="email">
    <input type="submit" id="submit" value="SEND">
    </form>

    <textarea id="comments" form="contact_form"></textarea>

可是……为什么要把元素放在外面?这样岂不是无论语义上还是结构上都更难理解?

一开始我也觉得这是一个鸡肋的功能,直到后来遇到了两难的情况。

当一个表单中出现多个值不同的 input[type=submit] 时,默认按钮取决于 HTML 中第一个出现的按钮。按下回车时会触发一次默认按钮的 onclick,然后才是表单的 onsubmit。这样一来我的实现代码出现了一些棘手的 bug,具体细节跟本文无关,此处不表。最后只好把提交按钮放到 form 之外并用这一特性进行关联。

项目不打算对 IE 进行向下兼容,看样子是没事了。不过问题又来了,IE11 居然也不支持。

在我的需求里仅仅是模拟一个 submit 按钮,所以兼容起来很简单:

$('[type=submit][form]').click(function() {
  var formId = $(this).attr('form'),
    form = $('#' + formId);
    field = $('<input type="text" />').attr('type', 'hidden').attr('name', this.name).val(this.value);
    form.append(field).submit();
});

如果需要实现一个通用的 polyfill 呢?

这个属性的行为可以用一句话带过——关联表单和字段,不过具体实现起来至少需要完成4个任务:

  • 检测浏览器是否支持 form 属性
  • 点击提交表单(上面已经初步实现了)
  • 点击重置表单
  • 处理 onsubmit,同步表单外字段的值

这里面最麻烦的是字段值的同步,因为要处理文本框、单选、复选、下拉框等多种控件。还好这个轮子已经有人造好了,那就是 jQuery 的 serializeArray 方法:

https://github.com/jquery/jquery/blob/10399ddcf8a239acc27bdec9231b996b178224d3/src/serialize.js#L87

已知一个表单,要获取所有在 form 元素之外并设置了 form 属性的字段值,简单地调用即可:

var data = $('[form='+ this.id + ']').serializeArray();

此外,这个方法的实现代码也让我们剩下的“重置”和“提交”功能有了思路。

模拟 input[type=reset] 行为手工重置字段的值有三种情况:

  • input[type=text],textarea 等文本框:恢复 this.valuethis.defaultValue
  • input[type=checkbox] 复选框:恢复 this.checkedthis.defaultChecked
  • select > option 下拉框 / 多选列表 / 单选列表:恢复 this.selectedthis.defaultSelected

最后实现代码如下,初步测试 XP+IE6 通过:

    (function($) {
      /**
       * polyfill for html5 form attr
       */

      // detect if browser supports this
      if (window.HTMLFormElement && $('[form]').get(0).form instanceof HTMLFormElement) {
        // browser supports it, no need to fix
        return;
      }

      /**
       * Append a field to a form
       *
       */
      $.fn.appendField = function(data) {
        // for form only
        if (!this.is('form')) return;

        // wrap data
        if (!$.isArray(data) && data.name && data.value) {
          data = [data];
        }

        var $form = this;

        // attach new params
        $.each(data, function(i, item) {
          $('<input/>')
            .attr('type', 'hidden')
            .attr('name', item.name)
            .val(item.value).appendTo($form);
        });

        return $form;
      };

      /**
       * Find all input fields with form attribute point to jQuery object
       * 
       */
      $('form[id]').submit(function(e) {
        // serialize data
        var data = $('[form='+ this.id + ']').serializeArray();
        // append data to form
        $(this).appendField(data);
      }).each(function() {
        var form = this,
          $fields = $('[form=' + this.id + ']');

        $fields.filter('button, input').filter('[type=reset],[type=submit]').click(function() {
          var type = this.type.toLowerCase();
          if (type === 'reset') {
            // reset form
            form.reset();
            // for elements outside form
            $fields.each(function() {
              this.value = this.defaultValue;
              this.checked = this.defaultChecked;
            }).filter('select').each(function() {
              $(this).find('option').each(function() {
                this.selected = this.defaultSelected;
              });
            });
          } else if (type.match(/^submit|image$/i)) {
            $(form).appendField({name: this.name, value: this.value}).submit();
          }
        });
      });


    })(jQuery);

有点小题大做,不过也顺便深入研究了一下浏览器对表单的处理。请轻喷~

w3ctech微信

扫码关注w3ctech微信公众号

共收到3条回复

  • 代码请使用 ``` 的方式。

    通过四个空格不是特别好用。

    回复此楼
  • 已改 ╮(╯▽╰)╭

    回复此楼
  • 最后实现代码如下,初步测试 XP+IE6 通过:

    上面这段话以下的代码,也需要修改哈。 现在有一些还单独在外面。

    回复此楼