Uploading Image using dropzone.js and Laravel5

User experience is one of the most important thing when it comes to building websites, creating traditional file upload is relatively easy in Laravel but it requires page to be posted which fails to provide good user experience in my opinion, using AJAX to upload images is much more practical and less frustrating for users, for example if you are building user profile page, you have to provide option to upload image along other options such as adding or modifying email, name and so on, you get the idea, It would be great place to use AJAX based image upload and keep image upload process separate from rest of the form.

Building your own AJAX implementation for the uploading image might take a lot of time and you have to maintain the code yourself, there are quite a few javascript libraries to handle image upload but let's focus on dropzone.js.  It is a great library to upload images, it also gives you functionalities such as drag and drop, image manipulation, and does not require any change of code in the backend(Laravel 5), if your code is working on traditional file upload, it's probably going to work with dropzone.js as well, let's see how to create small image upload app using dropzone.js and Laravel.

Prerequisites

  • We will be using blade form syntax which requires HTML package which is not included in Laravel5 by default, you can see how to install HTML package here.
  • We will be using CDN for dopzone.js, you can go to dropzone official site for complete documentation.
  • We assume you have some knowledge of file uploading in Laravel, check out our image upload and file upload tutorials to get the hang of it.

What we are going to build

We are going to build a simple app where we can drag and drop image we want to upload or click the selected area to upload an image with the help of dropzone.js and Laravel5 as backend.

dropzone image upload example

Directory Structure

We will have simple Laravel 5.1 directory structure and file we are going to work with is mentioned in the structure.

laravelroot
|	app
|	|	Http
|	|	|	Controllers
|	|	|	|	ImageController.php
|	|	routes.php
|	|	|
|	config
|	|	app.php
|	|	filesystems.php
|	public
|	|	Images
|	|	js
|	|	|	upload.js
|	|	styles
|	|	|	main.css
|	|	|
|	resources
|	|	view
|	|	|	image
|	|	|	|	create.blade.php
|	Composer.json

Configuring filesystems.php

We are going to use Laravel Filesystem class, we can edit config/filesystems.php file and configure where we want to save our image, API provided by Filesystem class allows us to upload files locally or on the cloud, In this case, e are going to use local storage, if you want to upload files to the cloud, you can do it with very littile configuration, t uploading files to the cloud, check out our Dropbox file upload tutorial.

Open filesystems.php and add new disk in disks Array.

<?php

return [

    'disks' => [

        'image' => [
            'driver' => 'local',
            'root'   => public_path().'/images'
        ],

    ],

];

Setting up Required Routes 

This is very simple application, so we only need two routes, one for showing uploading page and other route will handle the task of uploading image.

<?php

Route::get('/upload', ['as' => 'image.create', 'uses' => 'ImageController@create']);
Route::post('/upload', ['as' => 'image.store' , 'uses' => 'ImageController@store']);

Creating controller to handle Image upload

We are going  to create ImageController which two methods( create, store), create method will show to page to upload image and store method has logic to save the image.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Http\Requests;
use Carbon\Carbon;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Filesystem\Factory as Storage;
use Illuminate\Filesystem\Filesystem;

class ImageController extends Controller {

    /**
     * @return \Illuminate\View\View
     */
    public function create()
    {
        return view( 'image.create' );
    }

    /**
     * @param Storage $storage
     * @param Request $request
     * @return \Illuminate\Http\RedirectResponse|string
     */
    public function store( Storage $storage, Request $request )
    {
        if ( $request->isXmlHttpRequest() )
        {
            $image = $request->file( 'image' );
            $timestamp = $this->getFormattedTimestamp();
            $savedImageName = $this->getSavedImageName( $timestamp, $image );

            $imageUploaded = $this->uploadImage( $image, $savedImageName, $storage );

            if ( $imageUploaded )
            {
                $data = [
                    'original_path' => asset( '/images/' . $savedImageName )
                ];
                return json_encode( $data, JSON_UNESCAPED_SLASHES );
            }
            return "uploading failed";
        }

    }


    /**
     * @param $image
     * @param $imageFullName
     * @param $storage
     * @return mixed
     * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
     */
    public function uploadImage( $image, $imageFullName, $storage )
    {
        $filesystem = new Filesystem;
        return $storage->disk( 'image' )->put( $imageFullName, $filesystem->get( $image ) );
    }

    /**
     * @return string
     */
    protected function getFormattedTimestamp()
    {
        return str_replace( [' ', ':'], '-', Carbon::now()->toDateTimeString() );
    }

    /**
     * @param $timestamp
     * @param $image
     * @return string
     */
    protected function getSavedImageName( $timestamp, $image )
    {
        return $timestamp . '-' . $image->getClientOriginalName();
    }
}
Note: For the sake of simplicity we are adding uploading logic in controller, and also creating some helper functions in the controller, you should create a separate class or repository to handle image uploading logic.

To keep our image name unique, we are prefixing timestamp with image name, for this we are using Carbon class, as you can see we are importing carbon namespace, We are also using Filesytem and Factory class so let's import them too.

Creating View to upload Image

We have only one view, so keep things simple, we are not going to create master page and whole markup will be on the view.

We are going to need bootstrap, jquery and dropzone libraries so let's get them using CDN.

We are also going to write some JavaScript and CSS, to keep our code less muddy let's create sperate files for scripts( /public/js/upload.js) and styles(/public/styles/main.css).

In this view we will create a simple form with only one input (which is file type), when we drop image file to that input, it will post file to the route 'image.store' and store method on Image controller will handle the image and save to public/images folder.

resources/view/Image/create.blade.php

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Image Upload</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet" >
    <link rel="stylesheet" href="/styles/main.css">
</head>
<body>

    <div class="container">
        <div class="row">
            <div class="col-md-12">
                <h1>Uploading Image using dropzone.js and Laravel</h1>
                {!! Form::open([ 'route' => [ 'image.store' ], 'files' => true, 'enctype' => 'multipart/form-data', 'class' => 'dropzone', 'id' => 'book-image' ]) !!}
                <div>
                    <h3>Upload Image</h3>
                </div>
                {!! Form::close() !!}
            </div>
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/4.2.0/min/dropzone.min.js"></script>
    <script src="/js/upload.js"></script>
</body>
</html>

public/js/upload.js

(function() {
    Dropzone.options.bookImage = {
        paramName           :       "image", // The name that will be used to transfer the file
        maxFilesize         :       2, // MB
        dictDefaultMessage  :       "Drop File here or Click to upload Image",
        thumbnailWidth      :       "300",
        thumbnailHeight     :       "300",
        accept              :       function(file, done) { done() },
        success             :       uploadSuccess,
        complete            :       uploadCompleted
    };

    function uploadSuccess(data, file) {
        var messageContainer    =   $('.dz-success-mark'),
            message             =   $('<p></p>', {
                'text' : 'Image Uploaded Successfully! Image Path is: '
            }),
            imagePath           =   $('<a></a>', {
                'href'  :   JSON.parse(file).original_path,
                'text'  :   JSON.parse(file).original_path,
                'target':   '_blank'
            })

        imagePath.appendTo(message);
        message.appendTo(messageContainer);
        messageContainer.addClass('show');
    }

    function uploadCompleted(data) {
        if(data.status != "success")
        {
            var error_message   =   $('.dz-error-mark'),
                message         =   $('<p></p>', {
                    'text' : 'Image Upload Failed'
                });

            message.appendTo(error_message);
            error_message.addClass('show');
            return;
        }
    }
})();

public/styles/main.css

body {
    margin-top: 100px;
}

h1 {
    margin-bottom: 20px;
}

.dropzone {
    border: 2px dashed #EB7260;
    box-shadow: 0 0 0 5px #373b44;
    padding: 20px;
    background: #373b44;
    color: #fff;
    text-align: center;
}

.dropzone h3 {
    color: white;
    text-align: center;
    line-height: 3em;
    margin-top: 20px;
}

.dz-clickable {
    cursor: pointer;
}

.dz-drag-hover {
    border: 2px solid #EB7260;
}

.dz-preview, .dz-processing, .dz-image-preview, .dz-success, .dz-complete {
    background-color: rgba(0, 0, 0, .5);
    padding: 5px;
}

.dz-preview {
    width: auto !important;
}

.dz-image {
    text-align: center;
    /*border: 1px solid #EB7260;*/
}

.dz-details {
    padding: 10px;
}

.dz-success-mark, .dz-error-mark,  {
    display: none;
}

That's it guys, Now our image upload app should be working as we intended, like always, if you have any questions or have something to say tell us in the comment section, join in the  existing thread or create a new thread in the forums.

Something to say? Tell us in comment section.