Laravel multiple drag & drop file uploader with dropzone js — custom popup message on error + ‘remove file’ link implementation

Francesco
5 min readApr 29, 2021

Before starting, here the final result

Correctly, the first file is uploaded. The second return an error because there is a file with the same name

Prerequisites

I assume you are familiar with Laravel (in this case version 5.x) and you have already a laravel webapp installed with dropzone js imported in your upload page (follow the installation guide from the link)

Moreover, you’ll find the code as images only for ease of reading. You can find it to paste on the bottom of the article!

Before starting, let’s see the workflow and some detail to better understand the code:

  • File model with some basic attributes as file_name, path and folder
  • Upload page that upload under a specific folder (can be null)
  • The uploader return the file id so we can delete it if we want
  • The uploader upload one file at a time, so each time a file enter the form a post request will initiates

Ok, let’s start!

Import css and js files

custom-dropzone.css and dropzone-config.js are custom made (you can find it later). dropzone.css and dropzone.js are imported from the library (see installation guide link define above)

Upload page

Let’s define the upload page

Easy, nothing strange. We:

  • imported the dropzone config (we’ll define it in just a minute, be patient)
  • defined a form with multipart/form-data
  • note
<input type="file" name="file" multiple>

Custom-dropzone.css

Dropzone config

Take a look at the content of this file

If the response is 200:

  • we add a green border and we add, to the remove link, a data attribute with the fileId, in order, if clicked, to know which file to remove.

If the response is 400:

  • we add a red border and populate the popup with the message from the response.

removedfile:

  • we call a simple function inside FileController.php that delete the file from the filesystem and from the db. This is not included in this article.

Route

Define this route on web.php

Route::post('/upload/{folder?}', 'FileController@store')->name('upload.add');

FileController.php

Here we define the logic of the uploader

Here we get the file, we check if it is duplicate on the same folder (checkDuplicateOnDb check if the there is another file on the same folder, it take into account some logic not necessary to the purpose of this article) and, if yes, return an error (status 400 with a custom message).

If not, we try to save it on the db and in case of exception we return an error. Otherwise, we return a successful response (status 200).

Note the attributes of the response. In particular, fileId is useful to remove the file from the uploader.

Complete code

upload blade

@extends('layouts.app')

@section('content')
{{--This will help us to define some configuration for Dropzone--}}
<script src="{{ asset('/js/dropzone-config.js') }}"></script>

<div class="container">
<div class="row justify-content-center">
<div class="col-md-10">
<h1 class="upload-title">Upload File</h1>

<h3 class="upload-title">The file will be uploaded under <b>/tmp_folder</b></h3>

<form action="{{ URL::route('upload.add', [$folder ]) }}"
enctype="multipart/form-data"
class="file-uploader dropzone"
id="file-uploader">
@csrf
<div class="dz-message">
<div class="col-xs-8">
<div class="message">
<p>Drop files here or Click to Upload</p>
</div>
</div>
</div>
<div class="fallback">
<input type="file" name="file" multiple>
</div>
</form>
</div>
</div>
</div>
@endsection

FileController.php

<?php

namespace App\Http\Controllers;
use App\File;
use App\Folder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;

class FileController extends Controller
{
protected $basePath = 'public/documents/';

public function store(Request $request, Folder $folder=null){
$uploaded = $request->file('file'); //get file

//custom method to check if the file is duplicate inside a folder, you can delete it
if($this->checkDuplicateOnDb($uploaded->getClientOriginalName(), $folder)) {
$msg = "Error! There is another file with the same name ({$uploaded->getClientOriginalName()})";
return response() // send a response with status 400 - dropzone will see an error
->json(['message'=> $msg, 'file' => $uploaded->getClientOriginalName(), "fileId" => null], 400);
}

$path = Storage::putFile('public/documents', $request->file('file')); // save file
$path = basename($path);
$message = 'File Uploaded correctly!';
$status = 200;
try {
$file = new File; // create model
$file->file_system_path = $path;
$file->file_name = $uploaded->getClientOriginalName();
$file->folder_id = $folder->id ?? null;
$file->save();
} catch (\Exception $e) {
Storage::delete($path);
$message = 'Unable to upload file. Contact the admin';
$status = 400;
Log::error("Unable to store a file", ["project" => $project->id, "exception" => $e]);
}
return response()->json(
['message'=> $message, 'file' => $uploaded->getClientOriginalName(), "fileId" => $file->id ?? null], $status);
}
}

dropzone-config.js

Dropzone.options.fileUploader = {
parallelUploads: 1,
addRemoveLinks: true,

init: function () {
this.on("error", function (file, responseText) { //the status from the response is 400
var status = $(file.previewElement).find('.dz-error-message');
status.text(responseText.message);
status.show();

var msgContainer = $(file.previewElement).find('.dz-image');
msgContainer.css({"border" : "2px solid #d90101"}) //red border if fail
});

this.on("removedfile", function (file) {
// check if the file is uploaded or not. If uploaded we remove it from the server, otherwise we do nothing
//because it isn't uploaded
if($(file.previewElement).hasClass('dz-error')) return;

$(file.previewElement).find('.dz-remove');
var fileId = $(file.previewElement).find('.dz-remove').data("dz-remove")
$.post({
type: 'DELETE',
url: `/delete-file/${fileId}`,
data: {id: file.name, _token: $('[name="_token"]').val()},
success: function (result) {
if(!result) alert("Error! Contact the admin") // add your code if error
}
});
});
},
success: function (file, response) { //the status from the response is 200
var msgContainer = $(file.previewElement).find('.dz-image');
msgContainer.css({"border" : "2px solid #38873C"}) //green border

//set id file to remove link function
var removeLink = $(file.previewElement).find('.dz-remove')
removeLink.attr("data-dz-remove", response.fileId);
},
};

custom-dropzone.css

.file-uploader{
border: 2px dashed #417bee !important;
}

#file-uploader .message {
font-family: "Segoe UI Light", "Arial", serif !important;
font-weight: 200 ;
color: #122539;
font-size: 1.5em;
letter-spacing: 0.02em;
}

Hope it may help you!

--

--

Francesco

Master’s degree in Computer Engineering for Robotics and Smart Industry — Smart Systems & Data Analytics