If you're already processing file uploads with Livewire but need to add a drag and drop, here's how it works in a few easy steps.
This article covers passing dragged and dropped files over to Livewire rather than directly handling the files with JavaScript. So, think of this as sprinkling in drag and drop to an existing file upload!
First, let's start with a simple uploader component with Livewire. This component allows a file to be selected, uploaded to a temporary location with Livewire and dumped out.
class Uploader extends Component
{
use WithFileUploads;
#[Validate('required|image|max:10240')]
public $file;
public function updatedFile()
{
dd($this->file);
}
public function render()
{
return view('livewire.uploader');
}
}
And here's the view:
<div>
<div
class="flex justify-center rounded-lg border border-dashed px-6 py-16 border-gray-900/25"
>
<div class="text-center">
<div class="flex text-sm/6 text-gray-600">
<label for="file-upload" class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500">
<span>Upload a file</span>
<input id="file-upload" name="file-upload" type="file" class="sr-only" wire:model="file">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs/5 text-gray-600">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</div>
Notice wire:model="file"
above, which hooks the file upload into our Livewire component's $file
property.
Here's what the component looks like right now:
Our file upload works, so let's add some drop events with Alpine.
First, we need a way to monitor whether we're in a dropping state.
<div>
<div
class="flex justify-center rounded-lg border border-dashed px-6 py-16"
x-bind:class="{ 'border-gray-900/50': dropping, 'border-gray-900/25': !dropping }"
x-data="{
dropping: false,
}"
>
<div class="text-center">
<div class="flex text-sm/6 text-gray-600">
<label for="file-upload" class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500">
<span>Upload a file</span>
<input id="file-upload" name="file-upload" type="file" class="sr-only" wire:model="file">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs/5 text-gray-600">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</div>
I've also added an x-bind:class
binding to switch around the opacity of the dashed border if we're in a dragging state with a file. We'll see this later as we change the dropping
variable.
Next, we'll change the dropping
variable when someone drags a file over.
<div>
<div
class="flex justify-center rounded-lg border border-dashed px-6 py-16"
x-bind:class="{ 'border-gray-900/50': dropping, 'border-gray-900/25': !dropping }"
x-data="{
dropping: false,
}"
x-on:dragover.prevent="dropping = true"
x-on:dragleave.prevent="dropping = false"
x-on:drop="dropping = false"
>
<div class="text-center">
<div class="flex text-sm/6 text-gray-600">
<label for="file-upload" class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500">
<span>Upload a file</span>
<input id="file-upload" name="file-upload" type="file" class="sr-only" wire:model="file">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs/5 text-gray-600">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</div>
This updates the dropping
variable on each event and looks something like this:
Next, we need to pass the uploaded file over to Livewire!
Add another event to the Alpine component to handle this:
<div>
<div
class="flex justify-center rounded-lg border border-dashed px-6 py-16"
x-data="{
dropping: false,
}"
x-bind:class="{ 'border-gray-900/50': dropping, 'border-gray-900/25': !dropping }"
x-on:drop="dropping = false"
x-on:dragover.prevent="dropping = true"
x-on:dragleave.prevent="dropping = false"
x-on:drop.prevent="
if ($event.dataTransfer.files.length !== 1) {
return
}
const files = $event.dataTransfer.files
@this.upload('file', files[0])
"
>
<div class="text-center">
<div class="flex text-sm/6 text-gray-600">
<label for="file-upload" class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500">
<span>Upload a file</span>
<input id="file-upload" name="file-upload" type="file" class="sr-only" wire:model="file">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs/5 text-gray-600">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</div>
Here, we check to see if any files exist in the dropped files and grab the first one.
The line below passes the uploaded file to our Livewire component:
@this.upload('file', files[0])
This triggers the file upload, as we'd normally see with a file selection.
To handle dragging and dropping multiple files, first specify that the input
should accept multiple files and switch it to a files
property in your Livewire component:
<input id="file-upload" name="file-upload" type="file" class="sr-only" wire:model="files" multiple>
Here's the updated Livewire component to hold multiple files:
class Uploader extends Component
{
use WithFileUploads;
#[Validate('required|image|max:10240')]
public array $files;
public function updatedFiles()
{
dd($this->files);
}
public function render()
{
return view('livewire.uploader');
}
}
Next, we'll update the drop
handler:
<div>
<div
class="flex justify-center rounded-lg border border-dashed px-6 py-16"
x-data="{
dropping: false,
}"
x-bind:class="{ 'border-gray-900/50': dropping, 'border-gray-900/25': !dropping }"
x-on:drop="dropping = false"
x-on:dragover.prevent="dropping = true"
x-on:dragleave.prevent="dropping = false"
x-on:drop.prevent="
if ($event.dataTransfer.files.length <= 0) {
return
}
const files = $event.dataTransfer.files
@this.uploadMultiple('files', files)
"
>
<div class="text-center">
<div class="flex text-sm/6 text-gray-600">
<label for="file-upload" class="relative cursor-pointer rounded-md bg-white font-semibold text-indigo-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-indigo-600 focus-within:ring-offset-2 hover:text-indigo-500">
<span>Upload a file</span>
<input id="file-upload" name="file-upload" type="file" class="sr-only" wire:model="files">
</label>
<p class="pl-1">or drag and drop</p>
</div>
<p class="text-xs/5 text-gray-600">PNG, JPG, GIF up to 10MB</p>
</div>
</div>
</div>
And that's it! You should now be able to drag and drop multiple files. Once you do, you'll see an array of TemporaryUploadedFile
's dumped out.
For either solution, you can now store these files as you need.
So, it turns out that adding drag and drop upload functionality to a Livewire component isn't that tricky with Alpine.js. Feel free to tweak the basics we've covered here to suit your own applications.