3

I have this form and I need to be able to post data to the server through ajax, a user can upload 1 or more photos, or they may not upload any photos at all, anyway how can I send the data taken from the `type=file input and upload it to the server in the background?

Here's the relevant part of the form:

<form action="" method="POST" enctype="multipart/form-data">
            @csrf
                <label for="photos">Photos:</label>
                <input type="file" name="photos[]" id="photos" class="form-control" multiple>


                <button class="btn btn-success mt-3" onclick="ajaxify(event)">Submit</button>
            </div>
        </form>

And here's the relevant part of the javascript:

function ajaxify(event) {
            event.preventDefault();
            let failedValidation = false;

           // I removed parts of the code where I load other dataand do validation, irrelevant to the question.

            let photos = [];


            if(document.getElementById('photos').value !== '') {
                photos = document.getElementById('photos');   // I know this is incorrect, but I don't know what to do here.
            }

           // Here photos.value return something like c://fake/filename
           // And it doesn't return more than 1 file even, so anyway I am definitely doing this wrong.


            if(! failedValidation) {
                axios.post('/listing/create', {
                    client_name: name.value,
                    client_phone_number: client_phone_number.value,
                    category: category.value,
                    type: type.value,
                    governorate: governorate.value,
                    city: city.value,
                    space: space.value,
                    price: price.value,
                    furnished_status: furnished_status.value,
                    payment_type: payment_type.value,
                    initial_deposit: initial_deposit.value,
                    monthly_amount: monthly_amount.value,
                    notes: notes.value,
                    photos: photos.value, // So this should be an array of uploaded files.
                })
                .then((resp) => {
                    invalid.classList.add('d-none');
                    console.log(resp);
                })
            }
        }

What do I want? Is to have the file(s) I uploaded available for Laravel on the server side of the application, when I do a normal post and do dd($request->photos); I get an array of the uploaded files, I'm not sure if that's possible with ajax/json or not, but that's what I want in order to process the photos.

A quick note, I am using the Laravel media library package if that makes any difference.

What I did so far is researching the matter and I read that I need to use FormData(), I've never used that before and I have a couple of questions, do I need to put all of my data inside that FormData() object and feed that to axios? Or do I just need it for the photos? I haven't tried doing any of those two things yet, any guidance will be deeply appreciated.

Omar Tarek
  • 734
  • 1
  • 7
  • 19

1 Answers1

1

You are only getting one file because all the file objects are stored in an array in the files attributes. Just append them to your photos array.

function ajaxify(event) {
  event.preventDefault();

  // use files attribute to get an array of the files
  var photoFiles = document.getElementById("photos").files;

  // using the array of files, create an array 'photos' of FormData objects
  let photos = [];
  for (let photo of photoFiles) {
    photos.push(new FormData(photo);
  }

  // turn your 'photos' array into a javascript object
  let photos = arr2obj(photoFiles);

  // this should fix the empty array problem you were having
  // pass 'photos' to the ajax data

  // ....
}

EDIT: According to this post a FormData object is required for file upload using AJAX as one of the commenters pointed out. Your array must be an array of FormData objects.

EDIT: Sending arrays over JSON is troublesome. Turn your array into an object. You can use a simple function like this to build an object out of the array.

function arr2obj(arr) {
  var obj = {};
  for (let i=0; i<arr.length; i++) {
    obj['photo'+i] = arr[i];
  }
  return obj;
}
Kevin Welch
  • 1,488
  • 1
  • 9
  • 18
  • 1
    After I'd done what you recommended, but there's something I am missing if you can help me with that, when I console.log(photos) i get the file(s) I uploaded as expected, but when I dd($request->photos) on the server I get an array of empty arrays, let's say I uploaded 2 images, I get this: array:2 [ 0 => [] 1 => [] ] I assigned the photos variable to axios like this, instead of photos: photos.value, I have photos: photos, and I think the mistake might be here, do you have any idea? – Omar Tarek Oct 07 '18 at 08:21
  • Well unfortunately I can't get it to work, I have been trying different things and it's just not working, formData is always empty no matter what, and you can't even inspect it through console.log(), and when It is sent to the server it's just an empty array, I ended up defining it like this, var formData = new FormData(); formData.append('photos', photoFiles); and in the axio.post under everything else I have this photos: formData, but I just end up with 'photos' => [] when I dd($request);. – Omar Tarek Oct 07 '18 at 09:37
  • @OmarTarek take a look at [this post](https://stackoverflow.com/questions/16104078/appending-array-to-formdata-and-send-via-ajax). It seems the issue is that sending an array in json can be troublesome. Check 2nd edit. – Kevin Welch Oct 07 '18 at 10:15
  • I copied and pasted the code in the 2nd answer as it is, just to see if that array will be sent to the server, and nope, I get an empty array again. – Omar Tarek Oct 08 '18 at 05:23
  • 1
    I FINALLY figured out the missing part, after implementing your last recommendation based on the other post, the missing part was why did the server get an empty array, turns out it was this axios.post('/listing/create', {data; formData,} ), this was causing the problem, after some researching I changed it to this axios.post('/listing/create', formData), and voila, it finally worked, i migrated the rest of the form elements so that everything is appended to the formData object, anyway thank you so much you've been a great help. – Omar Tarek Oct 08 '18 at 06:15