Skip to content

Back to Snippets

formDataMap()

A function that behaves like the FormData DOM interface, but returns immediately useful values for each type of form control.

How it works.

/*
	For the given `form` element, 
	with an optional `submitter` element,
	return an object whose keys 
	are the names of the form elements, 
	mapping to their corresponding values 
	based on the type of each element.
 */
function formDataMap(form, submitter) {
	
	const excludedTags = ['FIELDSET', 'OBJECT', 'OUTPUT']
	const excludedTypes = ['button', 'reset', 'image'];

	function shouldSubmit(el) {
		if (!el.name) return false;
		if (excludedTags.includes(el.tagName)) return false;
		if (excludedTypes.includes(el.type)) return false;
		if (el.type === 'submit' && el !== submitter) return false;
		if (el.type === 'radio' && !el.checked) return false;
		if (el.type === 'checkbox' && !el.checked) return false;
		if (el.disabled || el.matches(':disabled')) return false;
		if (el.closest('datalist')) return false;
		return true;
	}
	
	const result = {};

	function append(key, val) {
		result[key] = 
			Object.hasOwn(result, key) ? 
				[].concat(result[key], val) 
				: val;
	};

	Array.from(form.elements).forEach(el => {
		if (!shouldSubmit(el)) return;
		const { name, type } = el;
		if (type === 'number' || type === 'range') {
			append(name, +el.value);
		} else if (type === 'date' || type === 'datetime-local') {
			append(name, el.valueAsDate());
		} else if (type === 'file') {
			append(name, el.files);
		} else if (type === 'url') {
			append(name, new URL(el.value));
		} else if (type === 'select-one' || type === 'select-multiple') {
			Array.from(el.selectedOptions).forEach(
				option => append(name, option.value)
			);
		} else {
			append(name, el.value);
		}
	});

	return result;
};