Last active
June 9, 2021 15:57
-
-
Save saqueib/317216a4d0143fc19990e0bf43dfdb41 to your computer and use it in GitHub Desktop.
Reusable upload component in Laravel with Dropzone.js - visit http://wp.me/p8cmxL-9Q for tutorial
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@php $dropzoneId = isset($dz_id) ? $dz_id : str_random(8); @endphp | |
<div id="{{$dropzoneId}}" class="dropzone"> | |
<div class="dz-default dz-message"> | |
<h3>{{ $title or 'Drop files here or click to upload.'}}</h3> | |
<p class="text-muted">{{ $desc or 'Any related files you can upload' }} <br> | |
<small>One file can be max {{ config('attachment.max_size', 0) / 1000 }} MB</small></p> | |
</div> | |
</div> | |
<!-- Dropzone {{ $dropzoneId }} --> | |
@push('scripts') | |
<script> | |
// Turn off auto discovery | |
Dropzone.autoDiscover = false; | |
$(function () { | |
// Attach dropzone on element | |
$("#{{ $dropzoneId }}").dropzone({ | |
url: "{{ route('attachments.store') }}", | |
addRemoveLinks: true, | |
maxFilesize: {{ isset($maxFileSize) ? $maxFileSize : config('attachment.max_size', 1000) / 1000 }}, | |
acceptedFiles: "{!! isset($acceptedFiles) ? $acceptedFiles : config('attachment.allowed') !!}", | |
headers: {'X-CSRF-TOKEN': "{{ csrf_token() }}"}, | |
params: {!! isset($params) ? json_encode($params) : '{}' !!}, | |
init: function () { | |
// uploaded files | |
var uploadedFiles = []; | |
@if(isset($uploadedFiles) && count($uploadedFiles)) | |
// show already uploaded files | |
uploadedFiles = {!! json_encode($uploadedFiles) !!}; | |
var self = this; | |
uploadedFiles.forEach(function (file) { | |
// Create a mock uploaded file: | |
var uploadedFile = { | |
name: file.filename, | |
size: file.size, | |
type: file.mime, | |
dataURL: file.url | |
}; | |
// Call the default addedfile event | |
self.emit("addedfile", uploadedFile); | |
// Image? lets make thumbnail | |
if( file.mime.indexOf('image') !== -1) { | |
self.createThumbnailFromUrl( | |
uploadedFile, | |
self.options.thumbnailWidth, | |
self.options.thumbnailHeight, | |
self.options.thumbnailMethod, | |
true, function(thumbnail) { | |
self.emit('thumbnail', uploadedFile, thumbnail); | |
}); | |
} else { | |
// we can get the icon for file type | |
self.emit("thumbnail", uploadedFile, getIconFromFilename(uploadedFile)); | |
} | |
// fire complete event to get rid of progress bar etc | |
self.emit("complete", uploadedFile); | |
}) | |
@endif | |
// Handle added file | |
this.on('addedfile', function(file) { | |
var thumb = getIconFromFilename(file); | |
$(file.previewElement).find(".dz-image img").attr("src", thumb); | |
}) | |
// handle remove file to delete on server | |
this.on("removedfile", function (file) { | |
// try to find in uploadedFiles | |
var found = uploadedFiles.find(function (item) { | |
// check if filename and size matched | |
return (item.filename === file.name) && (item.size === file.size); | |
}) | |
// If got the file lets make a delete request by id | |
if( found ) { | |
$.ajax({ | |
url: "/attachments/" + found.id, | |
type: 'DELETE', | |
headers: { | |
'X-CSRF-TOKEN': "{{ csrf_token() }}" | |
}, | |
success: function(response) { | |
console.log('deleted'); | |
} | |
}); | |
} | |
}); | |
// Handle errors | |
this.on('error', function(file, response) { | |
var errMsg = response; | |
if( response.message ) errMsg = response.message; | |
if( response.file ) errMsg = response.file[0]; | |
$(file.previewElement).find('.dz-error-message').text(errMsg); | |
}); | |
} | |
}); | |
}) | |
// Get Icon for file type | |
function getIconFromFilename(file) { | |
// get the extension | |
var ext = file.name.split('.').pop().toLowerCase(); | |
// if its not an image | |
if( file.type.indexOf('image') === -1 ) { | |
// handle the alias for extensions | |
if(ext === 'docx') { | |
ext = 'doc' | |
} else if (ext === 'xlsx') { | |
ext = 'xls' | |
} | |
return "/images/icon/"+ext+".svg"; | |
} | |
// return a placeholder for other files | |
return '/images/icon/txt.svg'; | |
} | |
</script> | |
@endpush |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Support\Facades\Storage; | |
class Attachment extends Model | |
{ | |
protected $guarded = []; | |
protected $appends = ['url']; | |
public function attachable() | |
{ | |
return $this->morphTo(); | |
} | |
public function getUrlAttribute() | |
{ | |
return Storage::url($this->uid); | |
} | |
public static function boot() | |
{ | |
parent::boot(); | |
static::deleting(function($attachment) { | |
// delete associated file from storage | |
Storage::disk('public')->delete($attachment->uid); | |
}); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
return [ | |
// Allowed file types with . prefix | |
'allowed' => '.pdf,.doc,.xls,.docx,.xlsx,.jpg,.png,.gif,.jpeg', | |
// Max file size in KB | |
'max_size' => 5000 | |
]; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Controllers; | |
use App\Attachment; | |
use Illuminate\Http\Request; | |
class AttachmentController extends Controller | |
{ | |
/** | |
* Store a newly created resource in storage. | |
* | |
* @param \Illuminate\Http\Request $request | |
* @return \Illuminate\Http\Response | |
*/ | |
public function store(Request $request) | |
{ | |
$request->validate([ | |
'file' => 'required|file|max:5000|mimes:' . $this->getAllowedFileTypes(), | |
'attachable_id' => 'required|integer', | |
'attachable_type' => 'required', | |
]); | |
// save the file | |
if ($fileUid = $request->file->store('/upload', 'public')) { | |
return Attachment::create([ | |
'filename' => $request->file->getClientOriginalName(), | |
'uid' => $fileUid, | |
'size' => $request->file->getClientSize(), | |
'mime' => $request->file->getMimeType(), | |
'attachable_id' => $request->get('attachable_id'), | |
'attachable_type' => $request->get('attachable_type'), | |
]); | |
} | |
return response(['msg' => 'Unable to upload your file.'], 400); | |
} | |
/** | |
* Remove the specified resource from storage. | |
* | |
* @param \App\Attachment $attachment | |
* @return \Illuminate\Http\Response | |
*/ | |
public function destroy(Attachment $attachment) | |
{ | |
return (string) $attachment->delete(); | |
} | |
/** | |
* Remove . prefix so laravel validator can use allowed files | |
* | |
* @return string | |
*/ | |
private function getAllowedFileTypes() | |
{ | |
return str_replace('.', '', config('attachment.allowed', '')); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Illuminate\Support\Facades\Schema; | |
use Illuminate\Database\Schema\Blueprint; | |
use Illuminate\Database\Migrations\Migration; | |
class CreateAttachmentsTable extends Migration | |
{ | |
/** | |
* Run the migrations. | |
* | |
* @return void | |
*/ | |
public function up() | |
{ | |
Schema::create('attachments', function (Blueprint $table) { | |
$table->increments('id'); | |
$table->string('filename'); | |
$table->string('uid'); | |
$table->integer('size'); | |
$table->string('mime', 100); | |
$table->morphs('attachable'); | |
$table->timestamps(); | |
}); | |
} | |
/** | |
* Reverse the migrations. | |
* | |
* @return void | |
*/ | |
public function down() | |
{ | |
Schema::dropIfExists('attachments'); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* The MIT License | |
* Copyright (c) 2012 Matias Meno <[email protected]> | |
*/ | |
// Permission is hereby granted, free of charge, to any person obtaining a copy of | |
// this software and associated documentation files (the "Software"), to deal in | |
// the Software without restriction, including without limitation the rights to | |
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | |
// of the Software, and to permit persons to whom the Software is furnished to do | |
// so, subject to the following conditions: | |
// The above copyright notice and this permission notice shall be included in all | |
// copies or substantial portions of the Software. | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
// SOFTWARE. | |
@mixin keyframes($name) { | |
@-webkit-keyframes #{$name} { | |
@content; | |
} | |
@-moz-keyframes #{$name} { | |
@content; | |
} | |
@keyframes #{$name} { | |
@content; | |
} | |
} | |
@mixin prefix($map, $vendors: webkit moz ms o) { | |
@each $prop, $value in $map { | |
@if $vendors { | |
@each $vendor in $vendors { | |
#{"-" + $vendor + "-" + $prop}: #{$value}; | |
} | |
} | |
// Dump regular property anyway | |
#{$prop}: #{$value}; | |
} | |
} | |
@include keyframes(passing-through) { | |
0% { | |
opacity: 0; | |
@include prefix((transform: translateY(40px))); | |
} | |
30%, 70% { | |
opacity: 1; | |
@include prefix((transform: translateY(0px))); | |
} | |
100% { | |
opacity: 0; | |
@include prefix((transform: translateY(-40px))); | |
} | |
} | |
@include keyframes(slide-in) { | |
0% { | |
opacity: 0; | |
@include prefix((transform: translateY(40px))); | |
} | |
30% { | |
opacity: 1; | |
@include prefix((transform: translateY(0px))); | |
} | |
} | |
@include keyframes(pulse) { | |
0% { @include prefix((transform: scale(1))); } | |
10% { @include prefix((transform: scale(1.1))); } | |
20% { @include prefix((transform: scale(1))); } | |
} | |
.dropzone, .dropzone * { | |
box-sizing: border-box; | |
} | |
.dropzone { | |
$image-size: 120px; | |
$image-border-radius: 10px; | |
&.dz-clickable { | |
cursor: pointer; | |
* { | |
cursor: default; | |
} | |
.dz-message { | |
&, * { | |
cursor: pointer; | |
} | |
} | |
} | |
min-height: 250px; | |
border: 1px dashed rgba($brand-primary, 0.6); | |
background: white; | |
padding: 20px 20px; | |
border-radius: $image-border-radius; | |
&.dz-started { | |
.dz-message { | |
display: none; | |
} | |
} | |
&.dz-drag-hover { | |
border-color: $brand-primary; | |
.dz-message { | |
opacity: 0.5; | |
transform: scale(1.2); | |
} | |
} | |
.dz-message { | |
text-align: center; | |
margin-top: 5em; | |
transform: scale(1); | |
transition: all ease 0.3s; | |
h3 { | |
font-weight: bold; | |
} | |
} | |
.dz-preview { | |
position: relative; | |
display: inline-block; | |
vertical-align: top; | |
margin: 16px; | |
min-height: 100px; | |
&:hover { | |
// Making sure that always the hovered preview element is on top | |
z-index: 1000; | |
.dz-details { | |
opacity: 1; | |
} | |
} | |
&.dz-file-preview { | |
.dz-image { | |
border-radius: $image-border-radius; | |
background: #999; | |
background: linear-gradient(to bottom, #eee, #ddd); | |
} | |
.dz-details { | |
opacity: 1; | |
} | |
} | |
&.dz-image-preview { | |
background: white; | |
.dz-details { | |
@include prefix((transition: opacity 0.2s linear)); | |
} | |
} | |
.dz-remove { | |
font-size: 14px; | |
text-align: center; | |
display: block; | |
cursor: pointer; | |
border: none; | |
&:hover { | |
text-decoration: underline; | |
} | |
} | |
&:hover .dz-details { | |
opacity: 1; | |
} | |
.dz-details { | |
$background-color: #444; | |
z-index: 20; | |
position: absolute; | |
top: 0; | |
left: 0; | |
opacity: 0; | |
font-size: 13px; | |
min-width: 100%; | |
max-width: 100%; | |
padding: 2em 1em; | |
text-align: center; | |
color: rgba(0, 0, 0, 0.9); | |
$width: 120px; | |
line-height: 150%; | |
.dz-size { | |
margin-bottom: 1em; | |
font-size: 16px; | |
} | |
.dz-filename { | |
white-space: nowrap; | |
&:hover { | |
span { | |
border: 1px solid rgba(200, 200, 200, 0.8); | |
background-color: rgba(255, 255, 255, 0.8); | |
} | |
} | |
&:not(:hover) { | |
span { | |
border: 1px solid transparent; | |
} | |
overflow: hidden; | |
text-overflow: ellipsis; | |
} | |
} | |
.dz-filename, .dz-size { | |
span { | |
background-color: rgba(255, 255, 255, 0.4); | |
padding: 0 0.4em; | |
border-radius: 3px; | |
} | |
} | |
} | |
&:hover { | |
.dz-image { | |
// opacity: 0.8; | |
img { | |
@include prefix((transform: scale(1.05, 1.05))); // Getting rid of that white bleed-in | |
@include prefix((filter: blur(8px)), webkit); // Getting rid of that white bleed-in | |
} | |
} | |
} | |
.dz-image { | |
border-radius: $image-border-radius; | |
overflow: hidden; | |
width: $image-size; | |
height: $image-size; | |
position: relative; | |
display: block; | |
z-index: 10; | |
img { | |
display: block; | |
} | |
} | |
&.dz-success { | |
.dz-success-mark { | |
@include prefix((animation: passing-through 3s cubic-bezier(0.770, 0.000, 0.175, 1.000))); | |
svg path { | |
fill: $brand-success; | |
} | |
} | |
} | |
&.dz-error { | |
.dz-error-mark { | |
opacity: 1; | |
@include prefix((animation: slide-in 3s cubic-bezier(0.770, 0.000, 0.175, 1.000))); | |
svg g { | |
fill: $brand-danger; | |
} | |
} | |
} | |
.dz-success-mark, .dz-error-mark { | |
$image-height: 54px; | |
$image-width: 54px; | |
pointer-events: none; | |
opacity: 0; | |
z-index: 500; | |
position: absolute; | |
display: block; | |
top: 50%; | |
left: 50%; | |
margin-left: -($image-width/2); | |
margin-top: -($image-height/2); | |
svg { | |
display: block; | |
width: $image-width; | |
height: $image-height; | |
} | |
} | |
&.dz-processing .dz-progress { | |
opacity: 1; | |
@include prefix((transition: all 0.2s linear)); | |
} | |
&.dz-complete .dz-progress { | |
opacity: 0; | |
@include prefix((transition: opacity 0.4s ease-in)); | |
} | |
&:not(.dz-processing) { | |
.dz-progress { | |
@include prefix((animation: pulse 6s ease infinite)); | |
} | |
} | |
.dz-progress { | |
opacity: 1; | |
z-index: 1000; | |
pointer-events: none; | |
position: absolute; | |
height: 16px; | |
left: 50%; | |
top: 50%; | |
margin-top: -8px; | |
width: 80px; | |
margin-left: -40px; | |
// border: 2px solid #333; | |
background: rgba(255, 255, 255, 0.9); | |
// Fix for chrome bug: https://code.google.com/p/chromium/issues/detail?id=157218 | |
-webkit-transform: scale(1); | |
border-radius: 8px; | |
overflow: hidden; | |
.dz-upload { | |
background: #333; | |
background: linear-gradient(to bottom, #666, #444); | |
position: absolute; | |
top: 0; | |
left: 0; | |
bottom: 0; | |
width: 0; | |
@include prefix((transition: width 300ms ease-in-out)); | |
} | |
} | |
&.dz-error { | |
.dz-error-message { | |
display: block; | |
} | |
&:hover .dz-error-message { | |
opacity: 1; | |
pointer-events: auto; | |
} | |
} | |
.dz-error-message { | |
$width: $image-size + 20px; | |
$color: rgb(190, 38, 38); | |
pointer-events: none; | |
z-index: 1000; | |
position: absolute; | |
display: block; | |
display: none; | |
opacity: 0; | |
@include prefix((transition: opacity 0.3s ease)); | |
border-radius: 8px; | |
font-size: 13px; | |
top: $image-size + 26px; | |
left: -10px; | |
width: $width; | |
background: $color; | |
background: linear-gradient(to bottom, $color, darken($color, 5%)); | |
padding: 0.5em 1.2em; | |
color: white; | |
// The triangle pointing up | |
&:after { | |
content: ''; | |
position: absolute; | |
top: -6px; | |
left: $width / 2 - 6px; | |
width: 0; | |
height: 0; | |
border-left: 6px solid transparent; | |
border-right: 6px solid transparent; | |
border-bottom: 6px solid $color; | |
} | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@extends('layouts.app') | |
@section('content') | |
<div class="container"> | |
<div class="panel panel-default"> | |
<div class="panel-heading">Dashboard</div> | |
<div class="panel-body"> | |
@if (session('status')) | |
<div class="alert alert-success"> | |
{{ session('status') }} | |
</div> | |
@endif | |
@component('partials.uploader', [ | |
'title' => 'Upload Post Images', | |
'params' => [ | |
'attachable_id' => 1, | |
'attachable_type' => 'App\Post' | |
], | |
'acceptedFiles' => '.jpg,.png', | |
'uploadedFiles' => $post->attachments->toArray() | |
]) | |
@endcomponent | |
<br> | |
@component('partials.uploader', [ | |
'title' => 'Document Uploader', | |
'desc' => 'Upload PDF, DOC, or XLS document', | |
'params' => [ | |
'attachable_id' => 2, | |
'attachable_type' => 'App\Post' | |
], | |
'acceptedFiles' => '.doc,.xls,.pdf, .docx', | |
'uploadedFiles' => $post2->attachments->toArray() | |
]) | |
@endcomponent | |
</div> | |
</div> | |
</div> | |
@endsection |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment