Bart De Kimpe
Bart De Kimpe

Reputation: 359

Best way to upload files using Ajax and before submitting

I have a form with multiple fields but also a file upload. I am able to upload multiple files.

I also know that it is possible to upload files with AJAX. So I would like to upload my files using ajax while i'm filling in every other field. But how would I link the already uploaded images then? And also prevent the images to be uploaded again?

This is the form:

<form id="form_validation" method="POST" action="{{route('storeexpense')}}" enctype="multipart/form-data">
                            {{ csrf_field() }}
                            <div class="form-group form-float">
                                <div class="form-line">
                                    <input type="text"  class="form-control" name="description" required>
                                    <label class="form-label">Omschrijving</label>
                                </div>
                            </div>
                            <div class="form-group form-float">
                                <div class="form-line">
                                    <input type="number" class="form-control" name="amount" required>
                                    <label class="form-label">Bedrag</label>
                                </div>
                            </div>
                            <div class="form-group form-float">
                                @foreach ($types as $type)
                                    @if ($type->id === 1)
                                        <input name="transactiontype" type="radio" id="rdio{{$type->id}}" value="{{$type->id}}" checked />
                                        <label for="rdio{{$type->id}}">{{$type->description}}</label>
                                    @else
                                        <input name="transactiontype" type="radio" id="rdio{{$type->id}}" value="{{$type->id}}" />
                                        <label for="rdio{{$type->id}}">{{$type->description}}</label>
                                    @endif
                                @endforeach
                            </div>
                            <div class="form-group form-float">
                                <div class="form-line">
                                    <input type="text" class="datepicker form-control" name="date" placeholder="Please choose a date..." required>

                                    <!-- <label class="form-label">Datum</label> -->
                                </div>
                            </div>
                            <div class="form-group demo-tagsinput-area">
                                <div class="form-line">
                                    <input type="text" class="form-control" id="tagsinput" data-role="tagsinput" placeholder="Labels" name="tags" required>

                                </div>
                            </div>
                            <div class="form-group form-float">
                                <div class="form-line">
                                     @if (count($errors) > 0)
                                        <ul>
                                            @foreach ($errors->all() as $error)
                                                <li>{{ $error }}</li>
                                            @endforeach
                                        </ul>
                                    @endif
                                    <input type="file" name="attachments[]" multiple class="custom-file-control"/>
                                </div>
                            </div>


                            <button class="btn btn-primary waves-effect" type="submit">SAVE</button>
                        </form>

This is the PHP code that saves the information from the form:

public function store(UploadRequest $request)
    {

        // Save new transaction in database
          $transaction = new Transaction;
        $transaction->description = $request->description;
        $transaction->amount = $request->amount;
        $input  = $request->date;
            $format = 'd/m/Y';
            $date = Carbon::createFromFormat($format, $input);

        $transaction->date = $date;
        $transaction->transactiontype_id = $request->transactiontype;
        $transaction->user_id = Auth::id();
        $transaction->save();

        // Put tags in array
        $inputtags = explode(",", $request->tags);

        // Loop through every tag exists
        // EXISTS: get ID
        // NOT EXISTS: Create and get ID
        foreach ($inputtags as $inputtag)
        {
            $tag = Tag::firstOrCreate(['description' => $inputtag]);
            $transaction->tags()->attach($tag->id); //Put the 2 ID's in intermediate table ('tag_transaction')
        }


        //Check if there are files 
        if (!is_null($request->attachments))
        {
            //Loop through every file and upload it
            foreach ($request->attachments as $attachment) {
            $filename = $attachment->store('attachments');

            // Store the filename in the database with link to the transaction
            Attachment::create([
                'transaction_id' => $transaction->id,
                'path' => $filename
            ]);
            }
        }

Thanks, Bart

Upvotes: 0

Views: 176

Answers (1)

Juank
Juank

Reputation: 6196

It sounds like you want to make a fancy form that starts uploading the file as soon as you choose it and meanwhile the user can continue filling the rest of the form. If so, I'd do it like this:

Implement your main text/data form, eg.

<form method="POST" action="/save-data-endpoint.php">
    <input name="email" type="text" />
    <button type="submit>Submit</button>
</form>

Next to it, a form for the images. eg.

<form method="POST" class="file-upload-form" action="/save-file.php">
    <input name="my-file" type="file" />
    <!-- note that we wont show a submit button -->
</form>

For the user, it all looks like the same form but clicking the submit button will send only the data to the save-data-endpoint.php. Now we need some js to control this madness (I'll use jQuery for brevity). But you can use FileReader api in js, ajax progress tracking to make it even fancier. See https://developer.mozilla.org/en-US/docs/Web/API/File/Using_files_from_web_applications for more.

$(function(){ // run when document is ready
    // listen when the input changes (when a file is selected)
    $('.file-upload-form').on('change', function(e){
        // file has been selected, submit it via ajax
        // show some kind of uploading indication, eg. a spinner
        $.ajax({
            type:'POST',
            url: $(this).attr('action'),
            data: new FormData(this),
            cache:false,
            contentType: false,
            processData: false,
            success:function(data){
                // the save-file.php endpoint returns an id and/or a url to the saved/resized/sanitized image
                console.log(data.id, data.url);
                // we then inject this id/url, into the main data form
                var $hiddenInput = $('<input type="hidden" name="uploads[]" value="'+data.id+'">');
                $('.main-form').append($hiddenInput);

                // show a thumbnail maybe?
                var $thumbnail = $('<img src="'+data.url+'" width="20" height="20" />');
                $('.main-form').append($thumbnail);

                // hide spinner
                // reactivate file upload form to choose another file
                $('.file-upload-form').find('input').val('');
            },
            error: function(){
                console.log("error");
            }
        });
    });
});

Your backend will get the images as they are selected, one by one. You then save them and return an id and/or a url to the image to be used in the success handler in js. After adding some images your main form should look something like this:

<form method="POST" action="/save-data-endpoint.php">
    <input name="email" type="text" />
    <button type="submit>Submit</button>

    <input type="hidden" name="uploads[]" value="x">
    <img src="...x.jpg" width="20" height="20" />
    <input type="hidden" name="uploads[]" value="y">
    <img src="...y.jpg" width="20" height="20" />
</form>

Now when the user fills the remaining fields and clicks submit, your server will get all the data along with an array called uploads which contains all the image ids/paths you have already saved. You can now store this data and relate it to the files.

I wont go deeper on the backend side as it can be implemented on any language. In summary the basic flow would be:

  • send files one at a time to a save file endpoint that returns a file identifier (can be an id, hash, full path to image, etc)
  • js injects those ids into the main form
  • the main form is submitted to a save data endpoint that returns a success or error message and stores + relates all the data in your preferred method of storage.

Hope it helps!

Upvotes: 1

Related Questions