Problem
Let’s say I have this form:
<form action="/user/store" method="POST" enctype="multipart/form-data">
Name: <input type="text" name="name"><br>
Male: <input type="radio" name="gender" value="male"><br>
Female: <input type="radio" name="gender" value="female"><br>
Photo: <input type="file" name="photo"><br>
Hobbies: <select name="hobbies[]" multiple>
<option value="sport">Sport</option>
<option value="movies">Movies</option>
<option value="music">Music</option>
<option value="games">Games</option>
</select><br>
Accept the terms?<input type="checkbox" name="terms"><br>
<input type="submit">
</form>
If I submit it to a PHP server with my data, this is the result:
array (size=5)
'name' => string 'John' (length=4)
'gender' => string 'male' (length=4)
'hobbies' =>
array (size=2)
0 => string 'sport' (length=5)
1 => string 'music' (length=5)
'terms' => string 'on' (length=2)
'photo' =>
object(SymfonyComponentHttpFoundationFileUploadedFile)[9]
private 'test' => boolean false
private 'originalName' => string 'test.jpg' (length=8)
private 'mimeType' => string 'image/jpeg' (length=10)
private 'size' => int 130677
private 'error' => int 0
I want to have similar results with JavaScript, so I wrote this function:
//Add event listener for the form submit to the document
$(document).on('submit', 'form', function(e){
//Prevent form submitting
e.preventDefault();
var form = $(':input').not(':submit,:button,:image,:radio,:checkbox');
$.merge(form, $(':checked').not('option'));
var data = [], input, name;
form.each(function(i, obj){
input = {};
name = obj.name;
if(name.indexOf('[]') < 0) {
input['name'] = name;
if(obj.type==='file'){
var files = obj.files;
input['value'] = files[files.length - 1];
} else {
input['value'] = obj.value;
}
} else {
name = name.replace('[]', '');
if(name in data){
var tmp = input['value'];
if(obj.type==='file'){
input['value'] = $.merge(tmp, obj.files);
} else if(obj.type === 'select-multiple') {
input['value'] = $.merge(tmp, $.map(obj.selectedOptions, function(option, i){
return option.value;
}));
} else {
input['value'] = $.merge(tmp, obj.value);
}
} else {
data[name] = {};
if(obj.type === 'file'){
input['value'] = obj.files;
} else if(obj.type === 'select-multiple') {
input['value'] = $.map(obj.selectedOptions, function(option, i){
return option.value;
});
} else {
input['value'] = [];
input['value'].push(obj.value);
}
}
}
data.push(input);
});
});
And this return (JSON format):
[
{"name":"firstname","value":"John"},
{"name":"photo","value":
{
"webkitRelativePath":"",
"lastModifiedDate":"2014-09-08T18:29:36.000Z",
"name":"test.jpg",
"type":"image/jpeg","size":130677
}
},
{"name":"hobbies","value":["sport","music"]},
{"name":"gender","value":"male"},
{"name":"terms","value":"on"}
]
Is there any better way to do that? By better code, I mean less code. I don’t mind if it will become slower.
Solution
I’m not sure if you still maintain this code but if so, you could consider using a FormData
object. I see it has been documented on MDN since 2015 and the link to the specification points to whatwg.org, which has been archived since 2012. I found a post on SO from 2011 that uses it. That way you wouldn’t have to manually query the DOM for inputs (I.e. var form = $(':input').not(':submit,:button,:image,:radio,:checkbox'); $.merge(form, $(':checked').not('option'));
)- you could just iterate over a newly created FormData object. You would still need to alter input names to remove the brackets when multiple values are allowed.
You might be able to remove jQuery as a dependency, unless there are specific plugins used on the page. For more information, check out youmightnotneedjquery.com/.
I see places where bracket notation is used to alter properties of objects, like below:
input['name'] = name;
and
input['value'] = files[files.length - 1];
However dot notation could be used instead, just as it is used to check the type of the object being iterated over (i.e. if(obj.type==='file'){
). It is “faster to write and clearer to read“1