1

I want to upload a product with multiple images using Dropzone, I have a form which has other fields like price, name etc. I have seen other tutorials but they only upload images not images with other fields(price, name) at once. I have set the Dropzone which shows the preview but if I submit the button I get a validation Please enter product image. How can I pass images to the controller using Dropzone?

Controller

 public function store(Request $request)
 {
  $formInput=$request->except('filename');

    $product = product::create(array_merge($formInput, [
        'user_id' => Auth::user()->id
    ]));
    foreach ($request->file as $photo) {
        $filename = $photo->store('public/photos');
        ProductsPhoto::create([
            'product_id' => $product->id,
            'filename' => $filename
        ]);
     }
 }

Blade

//The form

 <div class="panel-body">
   <form>
    @csrf
     <input type="hidden" value="{{csrf_token()}}" id="token"/>
  <label for="pro_name">Name</label>
  <input type="text" class="form-control" name="pro_name" id="pro_name" placeholder="Enter product name">

    <label for="pro_price">Price</label>
     <input type="text" class="form-control" name="pro_price" id="pro_price" placeholder="Enter price">

 <label for="photos">Choose 5 Images</label>
 <div class="needsclick dropzone" id="document-dropzone">  // Display images preview

  </div>

 <input type="submit" class="btn btn-primary" value="Submit" id="btn"/>

</div>

Ajax

   //This is how I submit the form
   <script>

    var token = $("#token").val();
    $(document).ready(function(){
        $("#btn").click(function (e) {
            e.preventDefault();
            $("#loading").show();
            var url = '{{ route('product.store') }}';
            var form = $('form')[0]; // You need to use standard javascript object here
            var formData = new FormData(form);
            formData.append('_token', token); // adding token
            $.ajax({
                url: url,
                data: formData, //just that without variables
                type: 'POST',
                cache: false,
                contentType: false, // NEEDED, DON'T OMIT THIS (requires jQuery 1.6+)
                processData: false, // NEEDED, DON'T OMIT THIS
                success:function(data){
                if($.isEmptyObject(data.error)){
                $("#msg").html("Product has been added successfull");
                $("#msg").fadeOut(3000);
                 window.location.href =  "<?php echo url('seller/product') ?>";
                 $("#loading").hide();
                 }
                 else{

                        printErrorMsg(data.error);

                        }
                }
            });
            function printErrorMsg (msg) {
            $("#loading").hide();
            $(".print-error-msg").find("ul").html('');
            $(".print-error-msg").css('display','block');
            $.each( msg, function( key, value ) {
                $(".print-error-msg").find("ul").append('<li>'+value+'</li>');
            });
            }
        });

    });

    var uploadedDocumentMap = {}
Dropzone.options.documentDropzone = {
  url: '{{ route('product.store') }}',
  maxFilesize: 10, // MB
  addRemoveLinks: true,
  headers: {
    'X-CSRF-TOKEN': "{{ csrf_token() }}"
  },
  success: function (file, response) {
    $('form').append('<input type="hidden" name="document[]" value="' + file.name + '">')
    uploadedDocumentMap[file.name] = response.name
  },
  removedfile: function (file) {
    file.previewElement.remove()
    var name = ''
    if (typeof file.file_name !== 'undefined') {
      name = file.file_name
    } else {
      name = uploadedDocumentMap[file.name]
    }
    $('form').find('input[name="document[]"][value="' + name +  '"]').remove()
  },
  init: function () {
    @if(isset($project) && $project->document)
      var files =
        {!! json_encode($project->document) !!}
      for (var i in files) {
        var file = files[i]
        this.options.addedfile.call(this, file)
        file.previewElement.classList.add('dz-complete')
        $('form').append('<input type="hidden" name="document[]"  value="' + file.file_name + '">')
      }
    @endif
  }
  }
</script>
joh
  • 238
  • 1
  • 5
  • 25
  • Why are you setting `contentType: false`? – Johannes Dec 04 '19 at 08:19
  • does it cause a problem while submitting the form ?@Johannes – joh Dec 04 '19 at 08:24
  • Here you can find a minimum working example: https://stackoverflow.com/questions/41981922/minimum-working-example-for-ajax-post-in-laravel-5-3 What I noticed as well is that you are missing the @csrf annotation in your blade template, otherwise Laravel will reject your request (see https://laravel.com/docs/5.7/csrf) After changing this, add a `console.log(data)` to the `success` callback. – Johannes Dec 04 '19 at 08:27
  • I have this line `` under
    – joh Dec 04 '19 at 08:31
  • 1
    Yes, but as far as I know you need a `name` attribute as well. `@csrf` is doing it implicitly. – Johannes Dec 04 '19 at 08:32
  • The thing is it was working fine I was able to submit the form with ajax and everything was okay but after Implementing the dropzone , then I don't know how to connect them ajax together so that if I click the submit button it should submit the product name, price and images at once. @Johannes – joh Dec 04 '19 at 08:36
  • Ok, got it. Have a look at the answer for this question: https://stackoverflow.com/questions/49245903/dropzone-manually-upload- acceptedfiles-via-ajax and try to create a manual `FormData` object. It looks like you are not adding the files to the AJAX request properly because Dropzone is no "standard" form field. You can see what is send to the server by opening your developer tools (`F12`) and watching the requests in the network tab. – Johannes Dec 04 '19 at 08:45
  • I have checked it and I didn't understand my situation is a little bit different @Johannes – joh Dec 04 '19 at 08:51
  • Dou you have your project e.g. in GitHub so that I can try it out? – Johannes Dec 04 '19 at 08:53
  • No I don't have them in git. Can you look again the question and see if it is possible to connect the ajax so that If I click submit then all data should be submitted, You can try then I'll be editing the code and let you know if I get any errors @Johannes – joh Dec 04 '19 at 08:57
  • 1
    [Here is an answer describing how to upload your form fields with a Dropzone image](https://stackoverflow.com/questions/46728205/dropzone-submit-button-on-upload/46732882#46732882), all at once, when you click a button. As Johannes pointed out your CSRF code is invalid, just use [`@csrf`](https://laravel.com/docs/5.8/csrf). As to the backend, `Please enter product image` is not anywhere in the code you've shown us, so we can only guess what might be the problem there. – Don't Panic Dec 04 '19 at 10:06
  • @Don'tPanic `autoProcessQueue: false` allows you to delay the file upload but not sending the files with the remaining form fields, right? I think I found out what he is trying to do (see my answer below). @joh Please keep in mind that Dropzone is issueing a separate request for uploading each of the selected files. – Johannes Dec 04 '19 at 10:24
  • @Johannes The answer I link to shows how to do both. – Don't Panic Dec 04 '19 at 10:29
  • I have tried the one you linked but still not working @Don'tPanic – joh Dec 04 '19 at 10:50
  • `still not working` - what does that mean? You need to tell us exactly what happens if you want us to be able to help. – Don't Panic Dec 04 '19 at 10:51
  • Should I edit the question with what I have tried ? @Don'tPanic – joh Dec 04 '19 at 10:52
  • Yes, please do so. Have you tried my *answer*? – Johannes Dec 04 '19 at 12:06
  • Yeah I have tried your answer but still the same problem @Johannes – joh Dec 04 '19 at 12:09
  • We need at least the error message and the information of what you have changed. Just "same problem" doesn't lead to a solution. – Johannes Dec 04 '19 at 12:12
  • So when I change `` to `@csrf` I get the mismatch csrf token @Johannes – joh Dec 04 '19 at 12:14
  • This is the error I get `CSRF token mismatch.` from the updated code @Johannes – joh Dec 04 '19 at 12:18
  • I've created a gist of my working solution, please compare it to your solution: https://gist.github.com/laundy/71adae0b96f5bd358013b977e30e467f – Johannes Dec 04 '19 at 12:19
  • I have compared with your gist then If I place the submit button I see this `Uncaught Error: Dropzone already attached.` on console and on network (response) it shows `{"error":["Please enter product image ."]}` @Johannes – joh Dec 04 '19 at 12:25
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/203589/discussion-between-johannes-and-joh). – Johannes Dec 04 '19 at 12:34

1 Answers1

0

Some things are not right in your code AND your concept (at least in my opinion):

  1. You need to prevent the default behavior of the #btn because you need to intercept the form submission. Otherwise, the form will just get submitted as a GET request (what is the default behavior).
$("#btn").click(function (e) {
  e.preventDefault();
  // ...
}
  1. The <form> element is not closed. Furthermore, don't override the _ token in your JavaScript but just add @csrf to the form. Larvel and jQuery will handle everything for you.
<form>
  @csrf
  1. I think I understood what you were trying to achieve now. Dropzone is uploading (= POST request) the files directly, so you need a separate route (or another code branch) to handle the file upload. Then, you can get the filename of the previously uploaded file and attach it as a hidden input field like so:
success: function (file, response) {
  $('form').append('<input type="hidden" name="document[]" value="' + file.name + '">')
},

What you will receive in your controller's method is the pro_name, pro_price and an array document containing the names of the uploaded files. Following your logic, the file must be already existing in the storage because it was uploaded by the Dropzone action. You could then save the filename to the database or whatever and use it as a reference for accessing the file later. Anyway, I would not recommend to use the client-provided filename for storage because it may not be unique. Laravel offers a lot of helpful tools for this scenario: https://laravel.com/docs/5.7/filesystem#file-uploads

Johannes
  • 1,478
  • 1
  • 12
  • 28