Laravel 5 file upload not working when using AJAX

6k views Asked by At

I have never came across this issue before while working on Laravel.

I have a form that will insert the product details which also has an image field.

Here's how I am creating the view for the insertion of product details:

{!! Form::open(['url' => '/admin/products', 'autocomplete' => 'off', 'id' => 'formAddProduct', 'files' => true]) !!}
    <div class="errors"></div>

    @include('admin.products.form', ['submitButtonText' => 'Add Product', 'submitButtonId' => 'btnAddProduct'])
{!! Form::close() !!}

form.blade.php:

<div class="form-group">
    {!! Form::label('code', 'Code') !!}
    {!! Form::text('code', null, ['class' => 'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('name', 'Name:') !!}
    {!! Form::text('name', null, ['class' => 'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('category_id', 'Category:') !!}
    {!! Form::select('category_id', $categories, null, ['class' => 'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('short_description', 'Short Description:') !!}
    {!! Form::text('short_description', null, ['class' => 'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('description', 'Long Description:') !!}
    {!! Form::text('description', null, ['class' => 'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('price', 'Price:') !!}
    {!! Form::text('price', null, ['class' => 'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('discount_price', 'Discount Price:') !!}
    {!! Form::text('discount_price', null, ['class' => 'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('display', 'Display Status:') !!}
    {!! Form::select('display', ['Enabled' => 'Enabled', 'Disabled' => 'Disabled'], null, ['class' => 'form-control']) !!}
</div>

<div class="form-group">
    {!! Form::label('image_file', 'Image:') !!}
    {!! Form::file('image_file', ['id' => 'image_file', 'class' => 'form-control input-sm']) !!}
</div>

<div class="form-group">
    {!! Form::submit($submitButtonText, ['class' => 'btn btn-primary btn-block', 'id' => $submitButtonId]) !!}
</div>

Controller method:

public function store( Request $request ) {
    $this->validate($request, [
        'code'              => 'required|alpha_dash|unique:products',
        'name'              => 'required',
        'category_id'       => 'required|integer',
        'short_description' => 'string',
        'description'       => 'required',
        'price'             => 'required|regex:/^\d*(\.\d{2})?$/',
        'discount_price'    => 'regex:/^\d*(\.\d{2})?$/',
        'display'           => 'required|in:Enabled,Disabled',
        'image_file'        => 'mimes:jpg'
    ]);

    if ( $request->ajax() ) {
        Product::create( $request->all() );

        if ( $request->file( 'image_file' ) ) {

            $filename = Safeurl::make( $request->get( 'code' ) );
            $image = Image::make( $request->file( 'image_file' ) );
            $path = public_path( 'images/uploads/products/' );

            $image->resize( 450, 450 )->save( $path.$filename.'.jpg', 100 );
        } else {
            return response(['msg' => 'No File']);
        }
        return response(['status' => 'success', 'msg' => 'The product has been added successfully.']);
    }
        return response(['status' => 'failed', 'msg' => 'The product could not be added successfully.']);
    }

And the ajax:

$('#btnAddProduct').on('click', function() {
    var inputData = $('#formAddProduct').serialize();

    $.ajax({
        url: '{{ url('/admin/products') }}',
        type: 'POST',
        data: inputData,
        success: function( message ) {
            alert( message.msg );
            if ( message.status === 'success' ) {
                toastr.success('Product added successfully.');
                toastr.options.closeButton = true;
                toastr.options.showMethod = "slideDown";
                toastr.options.hideMethod = "slideUp";

                $('input').val( '' );
                $('select').val( '' );
            }
        },
        error: function( data ) {
            if ( data.status === 422 ) {
                var errors = data.responseJSON;
                var errorsHtml = '<div class="alert alert-danger"><ul>';
                errorsHtml += '<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>';
                $.each( errors, function( key, value ) {
                    errorsHtml += '<li>' + value[0] + '</li>'; //showing only the first error.
                });
                errorsHtml += '</ul></div>';

                $( '.errors' ).html( errorsHtml );
            }
        }
    });

    return false;
});

The error I get is No file

When I do var_dump( $request->file( 'image_file' ) ); I get null in the Response Tab of the chrome

When I do var_dump( Input::file( 'image_file' ) ); I get null in the Response Tab of the chrome

Where have I made mistake ? Kindly help me. Thanks.

P.S.: I have used Intervention as my image uploading functionality.

3

There are 3 answers

6
Sulthan Allaudeen On BEST ANSWER

You should add data: formData, and async: false, in your ajax call

So your ajax will be

$("form[name='yourformname']").submit(function(e) {//If you use form submit then have your form name here
//$('#btnAddProduct').on('click', function() {
var inputData = new FormData($(this)[0]);
//var inputData = $('#formAddProduct').serialize(); not matter :p
  e.preventDefault();
$.ajax({
    url: '{{ url('/admin/products') }}',
    type: 'POST',
    data: inputData,
    async: false,
    success: function( message ) {
        alert( message.msg );
        if ( message.status === 'success' ) {
            toastr.success('Product added successfully.');
            toastr.options.closeButton = true;
            toastr.options.showMethod = "slideDown";
            toastr.options.hideMethod = "slideUp";

            $('input').val( '' );
            $('select').val( '' );
        }
    },
    error: function( data ) {
        if ( data.status === 422 ) {
            var errors = data.responseJSON;
            var errorsHtml = '<div class="alert alert-danger"><ul>';
            errorsHtml += '<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>';
            $.each( errors, function( key, value ) {
                errorsHtml += '<li>' + value[0] + '</li>'; //showing only the first error.
            });
            errorsHtml += '</ul></div>';

            $( '.errors' ).html( errorsHtml );
        }
    }
});

return false;
});

Update :

  1. Turn your async:true in your ajax call

  2. Have this in your Laravel Validator Rule image_file' => 'mimes:jpeg,jpg,JPG'

0
fahad.hasan On

The serialize() method does not send file information for the remote handler to process. The alternative is to use FormData. See this link for an example: http://portfolio.planetjon.ca/2014/01/26/submit-file-input-via-ajax-jquery-easy-way/

NOTE: Its important to set processData and contentType to false in the jQuery ajax request, otherwise it will result into an error while jQuery try to process it.

0
Codeparl On

There are some pitfalls and techniques you need to know before issuing an Ajax request containing files wrapped in a formData object.

  1. make sure to set contentType: false and processData: false
  2. If you are just appending files on your formData, make sure you access the file data like so

//This function collects all form fields to be appended 
//to a formData object
function collectFormData($form){
    var formdata  =  new FormData(), value, fieldId,$field ;
var formFields = ['title','summary','categories','tags','screenshot','flowchart'];
                
    $form.find('input , select , textarea').each(function(){
      $field  =  $(this);
      fieldId = $field .attr('id')
     //check whether this field is needed
        if($.inArray(fieldId, formFields) !== -1 ){
            switch (fieldId) {
            //access the file content of the file input
                    case 'screenshot':
                    value = $field[0].files[0];
                    break;
                     case 'flowchart':
                    value = $field[0].files[0];
                    break;
                default:
                value  =  $field.val();
                break;
            }//end switch
            formdata.append(fieldId, value)
        }//end if
    });
    return formdata;
}

function submitFormData(){
  $('.submit-btn').on('click', function(e){
    e.preventDefault();
var $thisBtn  =  $(this);
var $form  =  $thisBtn.closest('form');

var formdata  =  collectFormData($form);

//add token for laravel projects
$.ajaxSetup({'headers': {'X-CSRF-TOKEN':$form.find('input[name="_token"]').val()}});


//prepare an ajax request to send form details
$.ajax({
    url: $form.attr('action'),
        method: "post",
        processData: false, //Do not allow proccessing of the data
        dataType: "json", //you can change this property
        data: formdata, //this is our formData object being passed here
        async: false,
       cache: false,
       contentType: false, //very important for sending files

        success: function(data) {
            console.log(data)
        }, error:function(data){
            console.log(data)
        }
});//end ajax request

return false;
  });

}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form action="path/to/your/backendscript" id="uploadForm" method="post">

            <input type="text" name="title" id="title" >

            <textarea name="summary" id="summary" cols="30" rows="10"></textarea>

            <select name="categories[]" multiple id="categories">
                <option>Web Development</option>
                <option>Java Programming</option>
                <option>PHP programming</option>
            </select>


            <select name="tags[]" multiple id="tags">
                <option>AJAX</option>
                <option >Swing Components</option>
                <option>Laravel</option>
            </select>

            <input type="file" name="screenshot" id="screenshot">
            <input type="file" name="flowchart" id="flowchart">
<button class="submit-btn" type="submit">Submit</button>
        </form>

This is the solution that worked for me perfectly. Cheers!