Skip to content

Instantly share code, notes, and snippets.

@tabishiqbal
Forked from lazaronixon/_form.html.erb
Created February 27, 2025 00:34
Show Gist options
  • Save tabishiqbal/2dfe6f23260a7ba17621e5875238591f to your computer and use it in GitHub Desktop.
Save tabishiqbal/2dfe6f23260a7ba17621e5875238591f to your computer and use it in GitHub Desktop.
Dropzone.js + Stimulus + Active Storage + CSS Zero (2025)
<%= form_with(model: billboard) do |form| %>
<%= tag.div class: "dropzone", data: { controller: "dropzone", dropzone_param_name_value: "billboard[images][]", dropzone_url_value: rails_direct_uploads_url, dropzone_accepted_files_value: "image/*", dropzone_max_files_value: 3, dropzone_max_filesize_value: 0.300 } do %>
<div class="dz-default dz-message flex flex-col items-center">
<%= image_tag "upload.svg", size: 28, class: "colorize-black", aria: { hidden: true } %>
<h5 class="font-semibold mbs-4">Drop files here or click to upload.</h5>
<p class="text-sm text-subtle">Upload up to 10 files.</p>
</div>
<% end %>
<div class="inline-flex items-center mbs-2 mie-1">
<%= form.submit class: "btn btn--primary" %>
</div>
<% end %>
@import url("https://esm.sh/[email protected]/dist/dropzone.css");
.dropzone {
border-radius: var(--rounded-xl);
border: 2px dashed var(--color-border);
padding: var(--size-2);
text-align: center;
.dz-preview.dz-image-preview {
background: transparent;
}
.dz-preview .dz-error-message {
background: var(--color-negative);
}
.dz-preview .dz-error-message::after {
border-bottom: 6px solid var(--color-negative);
}
.dz-preview:has(.dz-remove) .dz-error-message {
top: 148px;
}
}
import { Controller } from "@hotwired/stimulus"
import { DirectUpload } from "https://esm.sh/@rails/[email protected]?standalone"
import Dropzone from "https://esm.sh/[email protected]?standalone"
export default class extends Controller {
static values = {
url: String,
paramName: String,
maxFiles: { type: Number, default: null },
maxFilesize: { type: Number, default: 256 },
acceptedFiles: { type: String, default: null },
addRemoveLinks: { type: Boolean, default: true }
}
connect() {
this.dropZone = this.#createDropZone()
this.dropZone.enqueueFile = it => this.#upload(it)
this.dropZone.on("removedfile", it => it.uploader?.hiddenInput?.remove())
this.dropZone.on("canceled", it => it.uploader?.xhr?.abort())
}
disconnect() {
this.dropZone.destroy()
}
#createDropZone() {
return new Dropzone(this.element, {
url: this.urlValue,
paramName: this.paramNameValue,
maxFiles: this.maxFilesValue,
maxFilesize: this.maxFilesizeValue,
acceptedFiles: this.acceptedFilesValue,
addRemoveLinks: this.addRemoveLinksValue
})
}
#upload(file) {
new Uploader(file, this.dropZone).start()
}
}
class Uploader {
constructor(file, dropZone) {
this.file = file; this.dropZone = dropZone; this.file.uploader = this;
}
start() {
this.#createDirectUpload((error, { signed_id }) => {
if (error) {
this.#emitDropzoneError(error)
} else {
this.#createHiddenInput(signed_id)
this.#emitDropzoneSuccess()
}
})
}
directUploadWillStoreFileWithXHR(xhr) {
this.xhr = xhr
this.#bindProgress()
this.#emitDropzoneProcessing()
}
#createDirectUpload(callback) {
new DirectUpload(this.file, this.dropZone.options.url, this).create(callback)
}
#createHiddenInput(signedId) {
this.hiddenInput = document.createElement("input")
this.hiddenInput.type = "hidden"
this.hiddenInput.name = this.dropZone.options.paramName
this.hiddenInput.value = signedId
this.dropZone.element.appendChild(this.hiddenInput)
}
#bindProgress() {
this.xhr.upload.addEventListener("progress", it => this.#emitDropzoneUploadProgress(it))
}
#emitDropzoneProcessing() {
this.file.status = Dropzone.PROCESSING
this.dropZone.emit("processing", this.file)
}
#emitDropzoneError(error) {
this.file.status = Dropzone.ERROR
this.dropZone.emit("error", this.file, error)
this.dropZone.emit("complete", this.file)
}
#emitDropzoneSuccess() {
this.file.status = Dropzone.SUCCESS
this.dropZone.emit("success", this.file)
this.dropZone.emit("complete", this.file)
}
#emitDropzoneUploadProgress(e) {
this.dropZone.emit("uploadprogress", this.file, ((100 * e.loaded) / e.total), e.total)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment