Laravel multiple drag & drop file uploader with dropzone js — custom popup message on error + ‘remove file’ link implementation
Before starting, here the final result
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
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!