Easier Ajax With the HTML5 FormData Interface

Craig Buckler

UK Web Developer and Writer

Published

If you’re developing a single-page application or practicing progressive enhancement techniques you’ll often need to intercept form submissions and translate them to an Ajax call. Let’s look at a typical form:

<form id="myform" action="webservice.php" method="post">

<input type="email" name="email" />

<select name="job">
<option value="">role</option>
<option>web developer</option>
<option>IT professional</option>
<option>other</option>
</select>

<input type="checkbox" name="freelancer" /> are you a freelancer?

<input type="radio" name="experience" value="4" /> less than 5 year's experience
<input type="radio" name="experience" value="5" /> 5 or more year's experience

<textarea name="comments" rows="3" cols="60"></textarea>

<button type="submit">Submit</button>

</form>

Form interception is straight-forward in jQuery because you can pass the form node to the serialize method to extract all field data, e.g.

$("myform").on("submit", function(e) {
	e.preventDefault();
	$.post(this.action, $(this).serialize());
});

If you’re using raw JavaScript, you’ll need to implement similar functionality yourself. You can either manually fetch every field one-by-one or implement a generic form element data extraction loop:

document.getElementById("myform").onsubmit = function(e) {

	e.preventDefault();
	
	var f = e.target,
		formData = '',
		xhr = new XMLHttpRequest();
	
	// fetch form values
	for (var i = 0, d, v; i < f.elements.length; i++) {
		d = f.elements[i];
		if (d.name && d.value) {
			v = (d.type == "checkbox" || d.type == "radio" ? (d.checked ? d.value : '') : d.value);
			if (v) formData += d.name + "=" + escape(v) + "&";
		}
	}
	
	xhr.open("POST", f.action);
	xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
	xhr.send(formData);

}

That’s a reasonable quantity of code even if you define it in a re-usable function. You may also require additional checks if you’ve disabled fields or made them read-only.

Fortunately, a little-known FormData interface has been added to XMLHttpRequest2 which handles much of the hard work for you. Let’s re-write our JavaScript submit handler to use it:

document.getElementById("myform").onsubmit = function(e) {

	e.preventDefault();
	
	var f = e.target,
		formData = new FormData(f),
		xhr = new XMLHttpRequest();
	
	xhr.open("POST", f.action);
	xhr.send(formData);
}

That’s much simpler — it’s also faster and easier to read than the jQuery alternative.

The FormData constructor can be passed a form element node; this instructs it to retrieve and encode all field name/value pairs. You’ll also notice we didn’t need to explicitly set xhr.setRequestHeader("Content-Type") since data is sent in the same format defined in the form’s submit() method. An encoding of multipart/form-data is also used so you can upload files.

If no form element is passed to the constructor, an empty FormData object is created. Which ever way it’s initialized, you can append additional name/value pairs using the append method, e.g.

var formData = new FormData();
formData.append("name", "value");
formData.append("a", 1);
formData.append("b", 2);

If the value is a File or Blob, a third parameter can specify an optional filename.

FormData is supported in all modern browsers. Only IE9 and below will cause trouble but, if you’re supporting the older versions of IE, you’ll probably be using jQuery or another library which implements its own field data extraction method.

For more information, refer to the FormData reference and Using FormData Objects on MDN.