|
// app/javascript/controllers/dropzone_controller.js |
|
import { Controller } from "@hotwired/stimulus" |
|
import { DirectUpload } from "@rails/activestorage"; |
|
import { Dropzone } from "dropzone"; |
|
|
|
|
|
export default class extends Controller { |
|
static targets = ["input"]; |
|
|
|
connect() { |
|
this.dropZone = this.createDropZone(this); |
|
this.hideFileInput(); |
|
this.bindEvents(); |
|
Dropzone.autoDiscover = false; |
|
} |
|
|
|
// Private |
|
hideFileInput() { |
|
this.inputTarget.disabled = true; |
|
this.inputTarget.style.display = "none"; |
|
} |
|
|
|
bindEvents() { |
|
this.dropZone.on("addedfile", file => { |
|
setTimeout(() => { |
|
file.accepted && this.createDirectUploadController(this, file).start(); |
|
}, 500); |
|
}); |
|
|
|
this.dropZone.on("removedfile", file => { |
|
file.controller && this.removeElement(file.controller.hiddenInput); |
|
}); |
|
|
|
this.dropZone.on("canceled", file => { |
|
file.controller && file.controller.xhr.abort(); |
|
}); |
|
} |
|
|
|
get headers() { |
|
return { "X-CSRF-Token": this.getMetaValue("csrf-token") }; |
|
} |
|
|
|
get url() { |
|
return this.inputTarget.getAttribute("data-direct-upload-url"); |
|
} |
|
|
|
get maxFiles() { |
|
return this.data.get("maxFiles") || 1; |
|
} |
|
|
|
get maxFileSize() { |
|
return this.data.get("maxFileSize") || 256; |
|
} |
|
|
|
get dictFileTooBig() { |
|
return this.data.get("dictFileTooBig") || "File sile is {{filesize}} but only files up to {{maxFilesize}} are allowed"; |
|
} |
|
|
|
get dictInvalidFileType() { |
|
return this.data.get("dictInvalidFileType") || "Invalid file type"; |
|
} |
|
|
|
get acceptedFiles() { |
|
return this.data.get("acceptedFiles"); |
|
} |
|
|
|
get addRemoveLinks() { |
|
return this.data.get("addRemoveLinks") || true; |
|
} |
|
|
|
getMetaValue(name) { |
|
const element = this.findElement(document.head, `meta[name="${name}"]`); |
|
if (element) { |
|
return element.getAttribute("content"); |
|
} |
|
} |
|
|
|
findElement(root, selector) { |
|
if (typeof root == "string") { |
|
selector = root; |
|
root = document; |
|
} |
|
return root.querySelector(selector); |
|
} |
|
|
|
removeElement(el) { |
|
if (el && el.parentNode) { |
|
el.parentNode.removeChild(el); |
|
} |
|
} |
|
|
|
createDirectUploadController(source, file) { |
|
return new DirectUploadController(source, file); |
|
} |
|
|
|
createDropZone(controller) { |
|
return new Dropzone(controller.element, { |
|
url: controller.url, |
|
headers: controller.headers, |
|
maxFiles: controller.maxFiles, |
|
maxFilesize: controller.maxFileSize, |
|
dictFileTooBig: controller.dictFileTooBig, |
|
dictInvalidFileType: controller.dictInvalidFileType, |
|
acceptedFiles: controller.acceptedFiles, |
|
addRemoveLinks: controller.addRemoveLinks, |
|
autoQueue: false |
|
}); |
|
} |
|
} |
|
|
|
class DirectUploadController { |
|
constructor(source, file) { |
|
this.directUpload = this.createDirectUpload(file, source.url, this); |
|
this.source = source; |
|
this.file = file; |
|
} |
|
|
|
start() { |
|
this.file.controller = this; |
|
this.hiddenInput = this.createHiddenInput(); |
|
this.directUpload.create((error, attributes) => { |
|
if (error) { |
|
this.removeElement(this.hiddenInput); |
|
this.emitDropzoneError(error); |
|
} else { |
|
this.hiddenInput.value = attributes.signed_id; |
|
this.emitDropzoneSuccess(); |
|
} |
|
}); |
|
} |
|
|
|
createHiddenInput() { |
|
const input = document.createElement("input"); |
|
input.type = "hidden"; |
|
input.name = this.source.inputTarget.name; |
|
this.insertAfter(input, this.source.inputTarget); |
|
return input; |
|
} |
|
|
|
insertAfter(el, referenceNode) { |
|
return referenceNode.parentNode.insertBefore(el, referenceNode.nextSibling); |
|
} |
|
|
|
removeElement(el) { |
|
if (el && el.parentNode) { |
|
el.parentNode.removeChild(el); |
|
} |
|
} |
|
|
|
directUploadWillStoreFileWithXHR(xhr) { |
|
this.bindProgressEvent(xhr); |
|
this.emitDropzoneUploading(); |
|
} |
|
|
|
bindProgressEvent(xhr) { |
|
this.xhr = xhr; |
|
this.xhr.upload.addEventListener("progress", event => |
|
this.uploadRequestDidProgress(event) |
|
); |
|
} |
|
|
|
uploadRequestDidProgress(event) { |
|
const element = this.source.element; |
|
const progress = (event.loaded / event.total) * 100; |
|
this.findElement( |
|
this.file.previewTemplate, |
|
".dz-upload" |
|
).style.width = `${progress}%`; |
|
} |
|
|
|
findElement(root, selector) { |
|
if (typeof root == "string") { |
|
selector = root; |
|
root = document; |
|
} |
|
return root.querySelector(selector); |
|
} |
|
|
|
emitDropzoneUploading() { |
|
this.file.status = Dropzone.UPLOADING; |
|
this.source.dropZone.emit("processing", this.file); |
|
} |
|
|
|
emitDropzoneError(error) { |
|
this.file.status = Dropzone.ERROR; |
|
this.source.dropZone.emit("error", this.file, error); |
|
this.source.dropZone.emit("complete", this.file); |
|
} |
|
|
|
emitDropzoneSuccess() { |
|
this.file.status = Dropzone.SUCCESS; |
|
this.source.dropZone.emit("success", this.file); |
|
this.source.dropZone.emit("complete", this.file); |
|
} |
|
|
|
createDirectUpload(file, url, controller) { |
|
return new DirectUpload(file, url, controller); |
|
} |
|
} |
alternative way how to load layout where CDN css for dropzone is loaded when we use
dropzone_controller_div
helper