9

This is the markup I use:

<input type="text" form="myform" name="inp1" />
<form id="myform" name="myform">
    ...        
</form>

Now I realized that it does not work for old IE and therefore I am searching for a HTML 5 polyfill.

Anyone aware of a certain polyfill which covers this HTML5 feature?

1
  • May not be worth pollyfilling if there is ajax and before/after logic that's not triggered directly with .submit(). I just spent an hour or so to figure out that it would be faster to do onClick() in the whole project in stead of polyfilling form=id. Commented Nov 14, 2016 at 16:38

6 Answers 6

12

I wrote this polyfill to emulate such feature by duplicating fields upon form submission, tested in IE6 and it worked fine.

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

  // detect if browser supports this
  var sampleElement = $('[form]').get(0);
  var isIE11 = !(window.ActiveXObject) && "ActiveXObject" in window;
  if (sampleElement && window.HTMLFormElement && (sampleElement.form instanceof HTMLFormElement || sampleElement instanceof window.HTMLFormElement) && !isIE11) {
    // 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);
Sign up to request clarification or add additional context in comments.

9 Comments

I have added this to the Modernizr list of polyfills.
The part $('[form]').get(0).form in the first code line is likely to raise an »Cannot read property 'form' of undefined« error if .get(0) returns undefined. I stored $('[form]') in a variable and return'ed if that .length was 0.
When used inside $(document).ajaxComplete(...), the detection-line sampleElement && window.HTMLFormElement && sampleElement.form instanceof HTMLFormElement returns true in IE 11 (although IE11 does not support this). This is because sampleElement.form returns the actual form. Although when used in the Console, $('[form]').get(0).form returns null How to fix it, so it works inside $(document).ajaxComplete(...) in IE11?
Additionally this does not work in conjunction with the attributes formaction, formtarget, formmethod
@wertzui That problem is so wierd so I addictionally added IE 11 as an exception
|
8

The polyfill above doesn't take into account the Edge browser. I have amended it to use feature detection, which I have tested in IE7+, Edge, Firefox (mobile/desktop), Chrome (mobile/desktop), Safari (mobile/desktop), and Android browser 4.0.

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

    // detect if browser supports this
    var SAMPLE_FORM_NAME = "html-5-polyfill-test";
    var sampleForm = $("<form id='" + SAMPLE_FORM_NAME + "'/>");
    var sampleFormAndHiddenInput = sampleForm.add($("<input type='hidden' form='" + SAMPLE_FORM_NAME + "'/>"));     
    sampleFormAndHiddenInput.prependTo('body'); 
    var sampleElementFound = sampleForm[0].elements[0];
    sampleFormAndHiddenInput.remove();
    if (sampleElementFound) {
        // 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);

1 Comment

Thanks, it works perfectly. I hope this gets more votes
2

I improved patstuart's polyfill, such that:

  • a form can now be submitted several times, e.g. when using the target attribute (external fields were duplicated previously)

  • reset buttons now work properly

Here it is:

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

// detect if browser supports this
var SAMPLE_FORM_NAME = "html-5-polyfill-test";
var sampleForm = $("<form id='" + SAMPLE_FORM_NAME + "'/>");
var sampleFormAndHiddenInput = sampleForm.add($("<input type='hidden' form='" + SAMPLE_FORM_NAME + "'/>"));     
sampleFormAndHiddenInput.prependTo('body'); 
var sampleElementFound = sampleForm[0].elements[0];
sampleFormAndHiddenInput.remove();
if (sampleElementFound) {
    // browser supports it, no need to fix
    return;
}

/**
 * Append a field to a form
 *
 */
var CLASS_NAME_POLYFILL_MARKER = "html-5-polyfill-form-attr-marker";
$.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)
      .attr('class', CLASS_NAME_POLYFILL_MARKER)
      .val(item.value).appendTo($form);
  });

  return $form;
};

/**
 * Find all input fields with form attribute point to jQuery object
 * 
 */
$('form[id]').submit(function(e, origSubmit) {
  // clean up form from last submit
  $('.'+CLASS_NAME_POLYFILL_MARKER, this).remove();
  // serialize data
  var data = $('[form='+ this.id + ']').serializeArray();
  // add data from external submit, if needed:
  if (origSubmit && origSubmit.name)
    data.push({name: origSubmit.name, value: origSubmit.value})
  // append data to form
  $(this).appendField(data);
})

//submit and reset behaviour
$('button[type=reset], input[type=reset]').click(function() {
  //extend reset buttons to fields with matching form attribute
  // reset form
  var formId = $(this).attr("form");
  var formJq = $('#'+formId);
  if (formJq.length)
    formJq[0].reset();
  // for elements outside form
  if (!formId)
    formId = $(this).closest("form").attr("id");
  $fields = $('[form=' + formId + ']');
  $fields.each(function() {
    this.value = this.defaultValue;
    this.checked = this.defaultChecked;
  }).filter('select').each(function() {
    $(this).find('option').each(function() {
      this.selected = this.defaultSelected;
    });
  });
});
$('button[type=submit], input[type=submit], input[type=image]').click(function() {
  var formId = $(this).attr("form") || $(this).closest("form").attr("id");
  $('#'+formId).trigger('submit', this);  //send clicked submit as extra parameter
});

})(jQuery);

Comments

1

after reading thru the docs of webshim it seems it has a polyfill for that.

http://afarkas.github.io/webshim/demos/demos/webforms.html

1 Comment

1

I made a vanilla JavaScript polyfill based on the above polyfills and uploaded it on GitHub: https://github.com/Ununnilium/form-attribute-polyfill. I also added a custom event to handle the case when submit is processed by JavaScript and not directly by the browser. I tested the code only shortly with IE 11, so please check it yourself before use. The polling should maybe be replaced by a more efficient detection function.

function browserNeedsPolyfill() {
    var TEST_FORM_NAME = "form-attribute-polyfill-test";
    var testForm = document.createElement("form");
    testForm.setAttribute("id", TEST_FORM_NAME);
    testForm.setAttribute("type", "hidden");
    var testInput = document.createElement("input");
    testInput.setAttribute("type", "hidden");
    testInput.setAttribute("form", TEST_FORM_NAME);
    testForm.appendChild(testInput);
    document.body.appendChild(testInput);
    document.body.appendChild(testForm);
    var sampleElementFound = testForm.elements.length === 1;
    document.body.removeChild(testInput);
    document.body.removeChild(testForm);
    return !sampleElementFound;
}

// Ideas from jQuery form attribute polyfill https://stackoverflow.com/a/26696165/2372674
function executeFormPolyfill() {
    function appendDataToForm(data, form) {
        Object.keys(data).forEach(function(name) {
            var inputElem = document.createElement("input");
            inputElem.setAttribute("type", "hidden");
            inputElem.setAttribute("name", name);
            inputElem.value = data[name];
            form.appendChild(inputElem);
        });
    }

    var forms = document.body.querySelectorAll("form[id]");
    Array.prototype.forEach.call(forms, function (form) {
        var fields = document.querySelectorAll('[form="' + form.id + '"]');
        var dataFields = [];
        Array.prototype.forEach.call(fields, function (field) {
            if (field.disabled === false && field.hasAttribute("name")) {
                dataFields.push(field);
            }
        });
        Array.prototype.forEach.call(fields, function (field) {
            if (field.type === "reset") {
                field.addEventListener("click", function () {
                    form.reset();
                    Array.prototype.forEach.call(dataFields, function (dataField) {
                        if (dataField.nodeName === "SELECT") {
                            Array.prototype.forEach.call(dataField.querySelectorAll('option'), function (option) {
                                option.selected = option.defaultSelected;
                            });
                        } else {
                            dataField.value = dataField.defaultValue;
                            dataField.checked = dataField.defaultChecked;
                        }
                    });
                });
            } else if (field.type === "submit" || field.type === "image") {
                field.addEventListener("click", function () {
                    var obj = {};
                    obj[field.name] = field.value;
                    appendDataToForm(obj, form);
                    form.dispatchEvent(eventToDispatch);
                });
            }
        });
        form.addEventListener("submit", function () {
            var data = {};
            Array.prototype.forEach.call(dataFields, function (dataField) {
                data[dataField.name] = dataField.value;
            });
            appendDataToForm(data, form);
        });
    });
}

// Poll for new forms and execute polyfill for them
function detectedNewForms() {
    var ALREADY_DETECTED_CLASS = 'form-already-detected';
    var newForms = document.querySelectorAll('form:not([class="' + ALREADY_DETECTED_CLASS + '"])');
    if (newForms.length !== 0) {
        Array.prototype.forEach.call(newForms, function (form) {
            form.className += ALREADY_DETECTED_CLASS;
        });
        executeFormPolyfill();
    }
    setTimeout(detectedNewForms, 100);
}


// Source: https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent
function polyfillCustomEvent() {
    if (typeof window.CustomEvent === "function") {
        return false;
    }

    function CustomEvent(event, params) {
        params = params || {bubbles: false, cancelable: false, detail: undefined};
        var evt = document.createEvent('CustomEvent');
        evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
        return evt;
    }

    CustomEvent.prototype = window.Event.prototype;
    window.CustomEvent = CustomEvent;
}

if (browserNeedsPolyfill()) {
    polyfillCustomEvent();   // IE is missing CustomEvent

    // This workaround is needed if submit is handled by JavaScript instead the browser itself
    // Source: https://stackoverflow.com/a/35155789/2372674
    var eventToDispatch = new CustomEvent("submit", {"bubbles": true, "cancelable": true});
    detectedNewForms();   // Poll for new forms and execute form attribute polyfill for new forms
}

Comments

1

I take some time to send an update for this polyfill because it doesn't work with MS Edge.

I add 2 line to fix it :

      var isEdge = navigator.userAgent.indexOf("Edge");
      if (sampleElement && window.HTMLFormElement && sampleElement.form instanceof HTMLFormElement && !isIE11 && isEdge == -1) {
        // browser supports it, no need to fix
        return;
      }

UPDATE: Edge now support it: https://caniuse.com/#feat=form-attribute

1 Comment

It would be better if we had a polyfill that used feature detection, though, since Edge will probably support it in the future.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.