Simulate the way that PHP receives the form

Posted on

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 read1

1https://stackoverflow.com/a/4968448/1575353

Leave a Reply

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