Home > OS >  Rails 7 view and direct upload image from browser client
Rails 7 view and direct upload image from browser client

Time:03-28

It's been a long time since I've used Rails for the frontend of a web app, and I want to use the most update version of course, but it seems a lot has changed and I don't know which is the most Rails Way to do it anymore.

I've tried to use JQuery and the FileUpload plugin, but we don't have JQuery anymore, I mean I've tried to add it but it was a pain in the ass using the new import map (problem with me, I know if I look up some tutorials I can do i), but that seems to go agains the current mentality of JS in rails apps.

Then I've went to checkout the new Hotwire Stimulus but I don't even now where to start, but from the little that I saw don't know if will handle this scenario: I already have a presigned_url from my S3 Bucket, and simply have a form with a f.file_field which I want to upload this file from the clients browser directly to S3 doing a POST request, so the user don't get blocked waiting the upload to finish

Correct me if I'm wrong but to trigger JS functions the Rails Way now is to use Stimulus with HTML Data Attributes but I'm not sure if I could pass the file in this data attribute.

Looking other tutorials I'm starting to think that the best approach would be to have a turbo_stream_tag to wrap my form, and then when submitting the form will hit this turbo controller which will act as a ajax request, running asynchronously doing a post request using Net:HTTP or even the s3 gem itself, I'm just not sure if I would have access to the file.

Any kind soul to clarify this? Thanks and sorry for the long post.

CodePudding user response:

You're probably want to look at this if you haven't: https://edgeguides.rubyonrails.org/active_storage_overview.html#example

Here is a starter with default Rails 7 setup with importmaps. Mostly taken from example link above and wrapped in Stimulus.

# config/importmap.rb
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
pin_all_from "app/javascript/controllers", under: "controllers"

pin "@rails/activestorage", to: "https://ga.jspm.io/npm:@rails/[email protected]/app/assets/javascripts/activestorage.esm.js"
<!-- inside your form -->
<div data-controller="upload">
  <%= form.file_field :avatar, direct_upload: true,
    data: { upload_target: 'input', action: "change->upload#uploadFile" } %>
  <div data-upload-target="progress"></div>
</div>

This will start uploading as soon as file is selected. With turbo it is non blocking. As long as the browser is not refreshed it'll be uploading.

// app/javascripts/controllers/upload_controller.js

import { Controller } from "@hotwired/stimulus";
import { DirectUpload } from "@rails/activestorage";

export default class extends Controller {
  static targets = ["input", "progress"];

  uploadFile() {
    Array.from(this.inputTarget.files).forEach((file) => {
      const upload = new DirectUpload(
        file,
        this.inputTarget.dataset.directUploadUrl,
        this // callback directUploadWillStoreFileWithXHR(request)
      );
      upload.create((error, blob) => {
        if (error) {
          console.log(error);
        } else {
          this.createHiddenBlobInput(blob);
          // if you're not submitting a form after upload. you need to attach
          // uploaded blob to some model here and skip hidden input.
        }
      });
    });
  }

  // add blob id to be submitted with the form
  createHiddenBlobInput(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(request) {
    request.upload.addEventListener("progress", (event) => {
      this.progressUpdate(event);
    });
  }

  progressUpdate(event) {
    const progress = (event.loaded / event.total) * 100;
    this.progressTarget.innerHTML = progress;

    // if you navigate away from the form, progress can still be displayed 
    // with something like this:
    // document.querySelector("#global-progress").innerHTML = progress;
  }
}

  • Related