Skip to content

Instantly share code, notes, and snippets.

@tomazzaman
Created May 24, 2023 19:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tomazzaman/9f8771009bead8d03e0bc6a28d5f10fa to your computer and use it in GitHub Desktop.
Save tomazzaman/9f8771009bead8d03e0bc6a28d5f10fa to your computer and use it in GitHub Desktop.
Simple Active Storage direct upload form with a Stimulus controller

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.

<%= 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 %>
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