AJAX Multiple File Upload – Identifying original element?

Assuming multiple upload input types exist on a HTML page, I want to be able to obtain the name attribute of each file upload that is submit with a file, this way the PHP server-side can determine exactly what file uploader is passing what file.

My Code:

$('.php-email-form').submit(function (event) {
  event.preventDefault();
  
  var form = $(this);
  var data = new FormData();
  
  // add datas of input not file to data object
  $.each($(':input', form).not(':input[type=submit]'), function(i, fields){
    data.append($(fields).attr('name'), $(fields).val());
    console.log($(fields).attr('name'), $(fields).val());
  });    
  
  // add data from inputs type file
  $.each($('input[type=file]', form)[0].files, function (i, file) {
    data.append(file.name, file);
    console.log(file.name);
    console.log($(this).attr('name')); // test, trying to get the name attribute of input type file
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form class="php-email-form" enctype="multipart/form-data">
  <input type="text" name="test" value="value"><br>
  <input type="file" class="custom-file-input" id="photo" name="photo"><br>
  <input type="submit">
</form>

When I look at the $_FILES array in PHP, I get the following output:

array(1) { ["test"]=> string(5) "value" }

array(1) { ["IMG_8085_JPG"]=> array(5) { ["name"]=> string(12) "IMG_8085.JPG" ["type"]=> string(10) "image/jpeg" ["tmp_name"]=> string(14) "/tmp/phpxsP6RN" ["error"]=> int(0) ["size"]=> int(65949) } }

As seen above, the index of the above image is the image name (IMG_8085_JPG) though I want this index to be the name of the original input it came from. (Either photo or photo-2)

So if an image was uploaded in the photo element, the array should look like the following:

array(1) { ["test"]=> string(5) "value" }

array(1) { ["photo"]=> array(5) { ["name"]=> string(12) "IMG_8085.JPG" ["type"]=> string(10) "image/jpeg" ["tmp_name"]=> string(14) "/tmp/phpxsP6RN" ["error"]=> int(0) ["size"]=> int(65949) } }

It seems that the name index is overwritten on the elements when a file is uploaded, so $(this).attr("name") no longer references the attribute but rather becomes the name of the file that is uploaded.

I post all my jQuery code below :

$('.php-email-form').submit(function (event) {
    event.preventDefault();

    var form = $(this);     
    var formAction = $(form).attr('action');
    var data = new FormData();

    // add data from inputs type text
    $.each($(':input', form).not(':input[type=submit]'), function(i, fields){
      data.append($(fields).attr('name'), $(fields).val());
    });    

    // add data from inputs type file
    $.each($('input[type=file]', form)[0].files, function (i, file) {
      data.append(file.name, file);
      console.log(file);
      console.log($(this).attr('name'));
    });

    $('.loading').fadeIn();

    $.ajax({
      url: formAction,
      type: "POST",
      data: data,
      contentType: false,
      cache: false,
      processData: false,
    }).done(function(data){
      $('.loading').hide();
      $('.sent-message').fadeIn().html(data);
    }).fail(function(jqXHR, textStatus, errorThrown) {
      $('.loading').hide();
      $('.error-message').show().html(errorThrown);
    });
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

The result i expect when i var_dump $_POST & $_FILES :

array(1) { ["test"]=> string(5) "value" }


array(1) { ["photo"]=> array(5) { ["name"]=> string(12) "IMG_8085.JPG" ["type"]=> string(10) "image/jpeg" ["tmp_name"]=> string(14) "/tmp/phpxsP6RN" ["error"]=> int(0) ["size"]=> int(65949) } } 

Answer

You are iterating through each input’s file directly instead of the element itself, this makes the type attribute accessible.

$.each($('input[type=file]', form), function(i, file) { // Iterate through every file upload element.
    let uploadedFile = this.files[0]; // Get the file that was uploaded to this input.
    data.append(this.name, uploadedFile); // Append it to the form data under the index of the name of the input it was provided in.

    // Testing.
    console.log(this.name); // input name (in this case, 'photo')
    console.log(uploadedFile); // uploaded file element object.
});

The difference here is that when you are iterating through ('input[type=file]', form)[0].files, you are accessing the files directly and the parent input that they are coming from aren’t accessible because there is no reference to them.

The solution is to iterate through every input with the type of ‘file’ so we still fetch every input and have access to the ‘name’ attribute, we can then fetch the file itself that was uploaded within the loop with this.files[0]. (Gets the first file uploaded.)

Leave a Reply

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