-
-
Save lazaronixon/dca1b48c241422d6347f4b0c93bec739 to your computer and use it in GitHub Desktop.
import { Controller } from "stimulus" | |
import { DirectUpload } from "@rails/activestorage" | |
import Dropzone from "dropzone" | |
import { getMetaValue, findElement, removeElement, insertAfter } from "helpers" | |
Dropzone.autoDiscover = false | |
export default class extends Controller { | |
static targets = [ "input" ] | |
connect() { | |
this.dropZone = createDropZone(this) | |
this.hideFileInput() | |
this.bindEvents() | |
} | |
// Private | |
hideFileInput() { | |
this.inputTarget.disabled = true | |
this.inputTarget.style.display = "none" | |
} | |
bindEvents() { | |
this.dropZone.on("addedfile", (file) => { | |
setTimeout(() => { file.accepted && createDirectUploadController(this, file).start() }, 500) | |
}) | |
this.dropZone.on("removedfile", (file) => { | |
file.controller && removeElement(file.controller.hiddenInput) | |
}) | |
this.dropZone.on("canceled", (file) => { | |
file.controller && file.controller.xhr.abort() | |
}) | |
this.dropZone.on("processing", (file) => { | |
this.submitButton.disabled = true | |
}) | |
this.dropZone.on("queuecomplete", (file) => { | |
this.submitButton.disabled = false | |
}) | |
} | |
get headers() { return { "X-CSRF-Token": 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 acceptedFiles() { return this.data.get("acceptedFiles") } | |
get addRemoveLinks() { return this.data.get("addRemoveLinks") || true } | |
get form() { return this.element.closest("form") } | |
get submitButton() { return findElement(this.form, "input[type=submit], button[type=submit]") } | |
} | |
class DirectUploadController { | |
constructor(source, file) { | |
this.directUpload = 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) { | |
removeElement(this.hiddenInput) | |
this.emitDropzoneError(error) | |
} else { | |
this.hiddenInput.value = attributes.signed_id | |
this.emitDropzoneSuccess() | |
} | |
}) | |
} | |
// Private | |
createHiddenInput() { | |
const input = document.createElement("input") | |
input.type = "hidden" | |
input.name = this.source.inputTarget.name | |
insertAfter(input, this.source.inputTarget) | |
return input | |
} | |
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 | |
findElement(this.file.previewTemplate, ".dz-upload").style.width = `${progress}%` | |
} | |
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) | |
} | |
} | |
// Top level... | |
function createDirectUploadController(source, file) { | |
return new DirectUploadController(source, file) | |
} | |
function createDirectUpload(file, url, controller) { | |
return new DirectUpload(file, url, controller) | |
} | |
function createDropZone(controller) { | |
return new Dropzone(controller.element, { | |
url: controller.url, | |
headers: controller.headers, | |
maxFiles: controller.maxFiles, | |
maxFilesize: controller.maxFileSize, | |
acceptedFiles: controller.acceptedFiles, | |
addRemoveLinks: controller.addRemoveLinks, | |
autoQueue: false | |
}) | |
} |
export function getMetaValue(name) { | |
const element = findElement(document.head, `meta[name="${name}"]`) | |
if (element) { | |
return element.getAttribute("content") | |
} | |
} | |
export function findElement(root, selector) { | |
if (typeof root == "string") { | |
selector = root | |
root = document | |
} | |
return root.querySelector(selector) | |
} | |
export function removeElement(el) { | |
if (el && el.parentNode) { | |
el.parentNode.removeChild(el); | |
} | |
} | |
export function insertAfter(el, referenceNode) { | |
return referenceNode.parentNode.insertBefore(el, referenceNode.nextSibling); | |
} |
<div class="dropzone dropzone-default dz-clickable" data-controller="dropzone" data-dropzone-max-file-size="5" data-dropzone-accepted-files="text/csv"> | |
<%= form.file_field :file, direct_upload: true, data: { target: 'dropzone.input' } %> | |
<div class="dropzone-msg dz-message needsclick"> | |
<h3 class="dropzone-msg-title">Solte o arquivo aqui ou clique para fazer o upload.</h3> | |
<span class="dropzone-msg-desc">Use os templates abaixo para criar o arquivo, o tamanho maximo é de 5 MiB.</span> | |
</div> | |
</div> |
Did you have to change anything in the view? I'm getting the same error even after making the changes you suggest.
@tfolk I also had to overwrite some method signatures to include the token and attachment name like this:
class DirectUploadController {
constructor(source, file, token, attachmentName) {
this.directUpload = createDirectUpload(file, source.url, token, attachmentName)
this.source = source
this.file = file
}
...
}
and
// Top level...
function createDirectUploadController(source, file, token, attachmentName) {
return new DirectUploadController(source, file, token, attachmentName)
}
function createDirectUpload(file, url, token, attachmentName) {
return new DirectUpload(file, url, token, attachmentName)
}
@lohannon I'll give it a try. Thanks.
Does anyone know what is the best way to display the files already uploaded in edit case? Thanks
This implementation does not actually work! Sure it works for the new action, but in the edit action, controller can not find form elements and a never ending rigmarole of JS and Stimulus... never ending! Does't work! We waste all this time implementing basics over and over, while in reality if you don't come from money and big teams, and you're working solo on a project, it means you will spill your guts working on the basics and working on the complex.
Is there a quick way to not have the controller overwrite the existing files? With my plain form files input I was doing something like this:
https://stackoverflow.com/questions/59803964/what-is-the-correct-way-to-update-images-with-has-many-attached-in-rails-6
This would just add new files to the existing ones which is what I am looking for. Now I could add another controller in this case as this page has just the files for the parent model and no other parent model attributes.
Side question in my code above I was going to add a turbo append to each attach and do a live update to the page showing the files added (setting auto-queue to true). Can you some how add this via drop zone?