Inspired by this StackOverflow question.

The issue is that JSON.stringify() does not handle FormData well. However, it can handle arrays. Therefore we can use Array.from() to convert FormData into Array:

const form = document.getElementById('someform');
const data = new FormData(form);
const arr  = Array.from(data);
localStorage.setItem('myform', JSON.stringify(arr));

Array.from(data) will produce something like this:

Deserialization of the array is pretty easy:

for (let i=0; i<arr.length; ++i) {
    const item = arr[i];
    const el   = form.elements[item[0]];
    el.value = item[1];
}

However, there are a few gotchas:

  1. If el.type === 'file', you cannot assign it any value other than '': an attempt to do so will result in a SecurityError exception. Thus, we first need to check the type of the field, and if it is a file, then assign an empty string to it.
  2. If we have elements with the same name — like this:
    <form id="form">
        <input name="radio" type="radio" value="0"/>
        <input name="radio" type="radio" value="1"/>
    
        <input name="checkbox" type="checkbox" value="0"/>
        <input name="checkbox" type="checkbox" value="1"/>
    
        <input type="text" name="text"/>
        <input type="text" name="text" disabled="disabled"/>
        <input type="text" name="text"/>
    </form>
    

    then form.elements will store them as RadioNodeList, like this:

  3. FormData skips disabled elements.

Taking the above into account, we can rewrite deserialization like this:

const map = {};
for (let item of a) {
	const name = item[0];
	const el   = form.elements[name];

	if (el instanceof RadioNodeList) {
		if (el[0].type === 'radio') {
			el.value = item[1];
		}
		else if (el[0].type === 'checkbox') {
			for (let j=0; j<el.length; ++j) {
				if (el[j].value == item[1]) {
					el[j].checked = true;
					break;
				}
			}
		}
		else {
			let idx;

			if (name in map) {
				idx = ++map[name];
			}
			else {
				idx = map[name] = 0;
			}

			while (el[idx].disabled && idx < el.length) {
				++idx;
				++map[name];
			}

			if (idx < el.length) {
				el[idx].value = el[idx].type === 'file' ? '' : item[1];
			}
		}
	}
	else if (el.type === 'file') {
		el.value = '';
	}
	else {
		el.value = item[1];
	}
}

This solution is still not an ideal one: for example, it is possible to break it by giving the same name to inputs of different types (does anyone do that?). However, it can give you a starting point in writing your own code.

Store FormData object in localStorage
Tagged on:     

Leave a Reply

Your email address will not be published. Required fields are marked *