In a previous article I explored a way to preview images before uploading with Stimulus.
I now want to extend its functionality by adding drag & drop. Along the way I am also using Stimulus outlets to tie the two functionalities together. Showcasing more advanced use of small Stimulus controllers.
I assume you walked through all the steps of the previous mentioned article.
Let’s start with the HTML. It’s using the other HTML with just a few attributes added.
<div data-controller="image-preview dropzone" data-action="dragover->dropzone#dragOver dragleave->dropzone#dragLeave drop->dropzone#drop">
<img data-image-preview-target="canvas" hidden class="object-cover size-48">
<input type="file" accept="image/*" data-image-preview-target="source" data-dropzone-target="input" data-action="image-preview#show" hidden>
</div>
Let’s create the dropzone_controller.js.
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["input"];
dragOver(event) {
event.preventDefault();
}
dragLeave(event) {
event.preventDefault();
}
drop(event) {
event.preventDefault();
this.#updateInputField(event.dataTransfer.files[0]);
}
}
All these methods do is preventing the default event when these actions are invoked. The drop()
function also calls the private function this.#updateInputField()
. Let’s add it.
export default class extends Controller {
// …
// private
#updateInputField(file) {
const dataTransfer = new DataTransfer();
dataTransfer.items.add(file);
this.inputTarget.files = dataTransfer.files;
}
// …
}
This will inject the dropped image into the inputTarget field. And just like that you can drag and drop images onto the element. 🤯
Preview images with outlets
An important part is missing though… the image isn’t showing which looks like a bug. Luckily with the image_preview_controller.js already done. This is a simple exercise.
First tweak the HTML with the following attributes:
<div data-controller="image-preview dropzone" data-dropzone-image-preview-outlet="#image-preview" data-action="dragover->dropzone#dragOver dragleave->dropzone#dragLeave drop->dropzone#drop" id="image-preview>
<img data-image-preview-target="canvas" hidden class="object-cover size-48">
<input type="file" accept="image/*" data-image-preview-target="source" data-dropzone-target="input" data-action="image-preview#show" hidden>
</div>
Added:
data-dropzone-image-preview-outlet="#image-preview"
;id="image-preview"
.
Now two lines are needed in the dropzone_controller.js.
export default class extends Controller {
static outlets = ["image-preview"];
// …
drop(event) {
// …
this.imagePreviewOutlet.show();
// …
}
// …
}
Now when you drop an image it fires the show()
function on the image_preview_controller.js (created in the previous article). 🥳
I like to use small Stimulus controllers like these that work great together.
Need an alternative to S3 to store your images?
Rails Designer has this Stimulus controller packaged with a few extras added! Get your copy today.