Welcome to Code Couch

Form serialisation in less than 700 bytes

Posted by at 11:26am on June 3, 2012.

I recently had need to serialise a form so that I could easily store its data in either a cookie or using web storage, without the need to use any server-side code or databases.

While I could’ve used one of the many JavaScript frameworks available to me, I didn’t really want to add them to my project purely for the sake of one piece of functionality – and for this project, code size is an issue: it may well end up embedded in some firmware on a NAS box.

I did some digging around and found many stand-alone candidates, the smallest of which was form-serialize by Dimitar Ivanov at 1207 bytes

While the code worked well for bog-standard form elements, there were some issues that put me off using it:

  • It only worked with form elements that had been explicitly specified in the code: none of the new HTML5 elements such as the colour picker, date picker, etc. were serialised
  • It serialised disabled form elements – something that should not happen

I decided to roll my own, and am pretty happy with the results – especially as I know that it will cope with the newer HTML5 input types without an issue. I tested it on a very large form with many elements, and comparing the resulting string with those produced by jQuery 1.7.2 and Prototype 1.7.0.0 gave me hope that it should cater for most circumstances. There were only 3 differences between my output and the output from jQuery and Prototype:

  • On my Mac, jQuery converted LF characters in a textarea to a CR+LF pair. Prototype did not do this, nor does my code
  • jQuery converted spaces into ‘+’ characters, whereas my code and Prototype use ‘%20′
  • Prototype will happily include disabled option elements in its serialised form data (see my test harness here. Neither my code nor jQuery do this.
  • So, all in all, I’m pretty happy with the results!

    I actually wrote 2 versions of the code, and I’ll post both here (they both return identical output, but the initial version will probably be easier to follow if you’re fairly new to JavaScript).

    Here’s the first version. It just didn’t feel right, having 3 branches for various element types, which is why I re-wrote it!

    // From http://www.codecouch.com/2012/06/form-serialisation-in-less-than-700-bytes
    function serialise(formEl) {
    	var els, loop, el, subEls, subLoop, subEl, vals = [], cbRadio;
    	els = Array.prototype.slice.call(formEl.elements).reverse();
     
    	function add(name, val) {
    		vals.push(encodeURIComponent(name) + '=' + encodeURIComponent(val));
    	};
     
    	for (loop=els.length; loop--;) {
    		el = els[loop];
    		cbRadio = /checkbox|radio/.test(el.type);
     
    		// Skip elements that have no 'name' attribute, are disabled, or are a button, image or file input
    		if (el.name == '' || el.disabled || el.nodeName == 'BUTTON' || (el.nodeName == 'INPUT' && /^(button|submit|reset|image|file)$/.test(el.type))) continue;
     
    		if (el.nodeName == 'SELECT') {
     
    			// If we're dealing with a select element (whether single or multi), ignore selected options if they are also disabled
    			subEls = el.options;
    			for (subLoop=subEls.length; subLoop--;) {
    				subEl = subEls[subLoop];
    				if (!subEl.disabled && subEl.selected) add(el.name, subEl.value);
    			}
     
    		} else if (el.length) {
     
    			// If we're dealing with values from multiple inputs with the same name, ignore any values associated with a disabled element
    			// Also ignore values from radio buttons & checkboxes that are not checked
    			for (subLoop=el.length; subLoop--;) {
    				subEl = el[subLoop];
    				if (!subEl.disabled && (!cbRadio || (cbRadio && subEl.checked))) add(el.name, subEl.value);
    			}
     
    		} else {
     
    			// We're dealing with a non-disabled single input or textarea.
    			// If the element is not a checkbox or radio button, add the value. If it is, add if checked
    			if (!cbRadio || (cbRadio && el.checked)) add(el.name, el.value);
     
    		}
    	}
    	return vals.join('&');
    }

    And here’s the rewrite… hopefully the comments at the top are understandable :-)

    /*
    	Some form elements should only be serialised if a specific property is set on them, e.g. 'selected', 'checked'.
    	Some form elements store their own collections of values that need to be iterated through, e.g. 'options' for a select element.
    	If there are multiple form elements with the same name, form.elements['theName'] will have a length property and will need to be iterated over.
     
    	I wanted to keep the code size small, so rather than have several branches of code to deal with all of these circumstances, I thought
    	it was nicer to store the name of property that determines whether serialisation should happen or not, and treat any uniquely-named form
    	elements as if they were in a collection. This way, only 1 loop is needed.
     
    	I store the property that determines whether a value should be serialised in "includeIfHas", and I store all element with the same name
    	in "subEls". If the element is uniquely-named, it gets wrapped in an array (which provides the "length" property).
    */
    // From http://www.codecouch.com/2012/06/form-serialisation-in-less-than-700-bytes
    function serialise(formEl) {
    	var els, loop, el, includeIfHas, subEls, subLoop, subEl, vals = [];
    	els = Array.prototype.slice.call(formEl.elements).reverse();
    	for (loop=els.length; loop--;) {
    		el = els[loop];
     
    		// Skip elements that have no 'name' attribute, that are disabled, or that are a button, image or file input
    		if (el.name == '' || el.disabled || el.nodeName == 'BUTTON' || (el.nodeName == 'INPUT' && /^(button|submit|reset|image|file)$/.test(el.type))) continue;
     
    		includeIfHas = (el.nodeName == 'SELECT') ? 'selected' : (/^(checkbox|radio)$/.test(el.type)) ? 'checked' : 'name';
    		subEls = (el.nodeName == 'SELECT') ? el.options : (el.length) ? el : [el];
     
    		for (subLoop=subEls.length; subLoop--;) {
    			subEl = subEls[subLoop];
    			if (!subEl.disabled && subEl[includeIfHas]) vals.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(subEl.value));
    		}
    	}
    	return vals.join('&');
    }

    Ignoring the comments and removing all unnecessary whitespace, the code weighs in at 679 bytes… which sure beats adding jQuery or Prototype to the project!

    // From http://www.codecouch.com/2012/06/form-serialisation-in-less-than-700-bytes
    function serialise(formEl){var els=Array.prototype.slice.call(formEl.elements).reverse(),loop,el,includeIfHas,subEls,subLoop,subEl,vals=[];for(loop=els.length;loop--;){el=els[loop];if(el.name==''||el.disabled||el.nodeName=='BUTTON'||(el.nodeName=='INPUT'&&/^(button|submit|reset|image|file)$/.test(el.type)))continue;includeIfHas=(el.nodeName=='SELECT')?'selected':(/^(checkbox|radio)$/.test(el.type))?'checked':'name';subEls=(el.nodeName=='SELECT')?el.options:(el.length)?el:[el];for(subLoop=subEls.length;subLoop--;){subEl=subEls[subLoop];if(!subEl.disabled&&subEl[includeIfHas])vals.push(encodeURIComponent(el.name)+'='+encodeURIComponent(subEl.value))}}return vals.join('&')}

    I can’t say that I’ve tested this on every possible combination of form elements, so if you find something that doesn’t work for you, post a comment and let me know!

    Note: Just in case you are thinking of using my code, there are a couple of ‘gotchas’ that you should probably know about – circumstances under which it may fail to serialise correctly:

    • If the form had multiple elements with the same name, but of different types
    • If the form had multiple select elements with the same name

    If you need to serialise forms with such an eclectic mix of controls, this code is not for you :-)

    Post to Twitter

Comments

There is one response to this post.

  1. I am looking for something similar to this. I currently have a form and I can handle all the elements of it easily. I want one more which will be a list of phrases that backend php code will pick at random to post comments. The problem I am having is getting the right html element and php code to connect the way I need. I thought of using a SELECT tag as it has the desired base functionality, but it is missing or I don't know how to use it to provide the other functionality I need. I want a list of items that when I select one it will populate a text box so the option can be edited and then saved back to the SELECT box, or whatever I use. Also the text box can be used to add new phrases. I also want to be able to select an option and remove it. This all seems possible with javascript. The problem is... when I submit the form I want ALL of the options to be passed as an array named comments. If you could point me in the right direction on how to accomplish these things I would be grateful.

Leave a reply

You must either log in or enter your name and email address to post a comment.

Your email address will not be published.

  • You do not need to log in to comment, but you can if you wish.
  • Log in