Title pretty much sums it up. I wanted it to support drag and drop as well, which is why the .droparea
is positioned absolutely; It's below the input field and the field itself gets its opacity set to 0, for better UX experience.
Created
May 24, 2023 19:58
-
-
Save tomazzaman/9f8771009bead8d03e0bc6a28d5f10fa to your computer and use it in GitHub Desktop.
Simple Active Storage direct upload form with a Stimulus controller
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<%= form_with(model: @model, class:"mt-6 w-2/4 py-3", data: { controller: "upload" }) do |form| %> | |
<%= form.fields_for :nested do |fields| %> | |
<%= fields.label :attachment, "Upload", | |
class: "block text-sm font-medium leading-6 text-slate-900" | |
%> | |
<div class="relative mt-2 border border-slate-200 shadow-sm rounded-md"> | |
<%= fields.file_field :attachment, direct_upload: true, | |
data: { upload_target: "input", | |
action: "upload#start dragover->upload#hideInput drop->upload#showInput" }, | |
class: "w-full relative z-50 text-sm rounded-md | |
focus:border-sky-500 focus:ring-sky-500 bg-white | |
file:bg-slate-50 file:border-0 file:bg-slate-50 file:mr-4 | |
file:px-4 file:hover:cursor-pointer file:py-3 file:leading-5" | |
%> | |
<div data-upload-target="droparea" | |
class="w-full z-10 px-4 py-3 bg-slate-50 rounded-md absolute top-0"> | |
<p class="text-sm text-center text-slate-600">Drop here</p> | |
</div> | |
</div> | |
<div data-upload-target="progressBar" class="bg-sky-500 rounded-lg h-0.5" style="width: 0%;"></div> | |
<% end %> | |
<% end %> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "@hotwired/stimulus"; | |
import { DirectUpload } from "@rails/activestorage"; | |
export default class extends Controller { | |
static targets = ["progressBar", "input"]; | |
connect() { | |
this.inputTarget.value = null; | |
} | |
start(e) { | |
Array.from(this.inputTarget.files).forEach(file => this.uploadFile(file)) | |
} | |
uploadFile(file) { | |
const url = this.inputTarget.dataset.directUploadUrl; | |
const upload = new DirectUpload(file, url, this); | |
upload.create((error, blob) => { | |
if (error) { | |
console.error("There was an error uploading the file."); | |
} else { | |
this.createHiddenInput(blob); | |
this.element.requestSubmit(); | |
} | |
}) | |
} | |
createHiddenInput(blob) { | |
const hiddenField = document.createElement('input') | |
hiddenField.setAttribute("type", "hidden"); | |
hiddenField.setAttribute("value", blob.signed_id); | |
hiddenField.name = this.inputTarget.name; | |
this.element.appendChild(hiddenField); | |
} | |
directUploadWillStoreFileWithXHR(xhr) { | |
xhr.upload.addEventListener("progress", event => this.directUploadDidProgress(event)); | |
} | |
directUploadDidProgress(event) { | |
const progress = event.loaded / event.total * 100; | |
this.progressBarTarget.style.width = `${progress}%`; | |
} | |
hideInput(event) { | |
event.preventDefault(); | |
this.inputTarget.classList.add('opacity-0'); | |
} | |
showInput() { | |
this.inputTarget.classList.remove('opacity-0'); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment