Rails 7 从浏览器客户端查看并直接上传图像

发布于 2025-01-17 04:12:44 字数 804 浏览 4 评论 0原文

我已经很长一段时间没有使用 Rails 作为 Web 应用程序的前端了,我当然想使用最新的版本,但似乎发生了很多变化,我不知道哪个是最Rails 的方式不再这样做了。

我尝试过使用 JQuery 和 FileUpload 插件,但我们不再有 JQuery,我的意思是我尝试添加它,但使用新的导入映射很麻烦(我知道这是个问题如果我查找一些教程,我可以做 i),但这似乎违背了 Rails 应用程序中 JS 当前的心态。

然后我去检查新的 Hotwire + Stimulus,但我什至现在不知道从哪里开始,但从我所看到的一点来看,我不知道是否可以处理这种情况:我已经有一个 presigned_url 来自我的 S3 Bucket,并且只需一个带有 f.file_field 的表单,我想将此文件从客户端浏览器直接上传到 S3 正在做一个POST 请求,这样用户就不会被阻止等待上传完成

如果我错了,请纠正我,但要触发 JS 函数,Rails Way 现在是使用 StimulusHTML 数据属性 但我不确定是否可以在此数据属性中传递文件。

查看其他教程,我开始认为最好的方法是使用 turbo_stream_tag 来包装我的表单,然后在提交表单时将命中此 Turbo 控制器,该控制器将充当 ajax 请求,使用 Net:HTTP 甚至 s3 gem 本身异步运行 post 请求,我只是不确定是否有权访问该文件。

有好心人来澄清一下吗?感谢并抱歉发了这么长的帖子。

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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

柠北森屋 2025-01-24 04:12:44

如果您还没有看过,您可能想看看:https://edgeguides .rubyonrails.org/active_storage_overview.html#example

这是一个带有导入映射的默认 Rails 7 设置的入门程序。大部分取自上面的示例链接并包含在刺激中。

# 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>

一旦选择文件,就会开始上传。 Turbo 是非阻塞的。只要浏览器不刷新就会上传。

// app/javascript/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 the 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;
  }
}

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 will be uploading.

// app/javascript/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 the 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;
  }
}
娇纵 2025-01-24 04:12:44

Soooooo,很抱歉延迟了,这是实现主动存储直接上传的最简单、最先进的方法,它非常简单,尽管文档并不清楚如何使用 Stimulus 来实现它。因此,如果您遵循此文档并且希望使其与 Stimulus 一起使用,请确保使用此部分覆盖 javascript,首先,创建 Stimulus 控制器,为您想要的操作添加事件侦听器,在我的示例中,它将是 onDroponChange 事件,然后将其与我们将创建的自定义上传器类绑定。

// rails generate stimulus direct_uploads
// => creates a js file under app/javascript/controllers/direct_uploads_controller.js

import { Controller } from '@hotwired/stimulus';

// don't worry about this import you'll find the class right below
import Uploader from '../utils/direct_uploads';

// Connects to data-controller="direct-uploads"
export default class extends Controller {
  connect() {
  }

  // drop->direct-uploads#handleInputChange
  handleOnDrop(event) {
    event.preventDefault();
    const { files } = event.dataTransfer;
    Array.from(files).forEach((file) => this.uploadFile(file));
  }

  // change->direct-uploads#handleInputChange
  handleInputChange({ currentTarget }) {
    const { files } = currentTarget;
    Array.from(files).forEach((file) => this.uploadFile(file, currentTarget));
  }

  uploadFile(file, currentTarget) {
    const upload = new Uploader(file, currentTarget);
    upload.start();
  }
}

// now let's create the uploader class.
// => app/javascript/utils/direct_uploads.js

import { DirectUpload } from '@rails/activestorage';

export default class Uploader {
  constructor(file, currentTarget) {
    const { dataset: { directUploadUrl } } = currentTarget;

    this.directUpload = new DirectUpload(file, directUploadUrl, this);
    this.targetForm = currentTarget.closest('form');
    this.file = file;
    this.currentTarget = currentTarget;
  }

  start() {
    this.directUpload.create((error, blob) => {
      if (error) {
        // Handle the error
      } else {
        // Add an appropriately-named hidden input to the form
        // with a value of blob.signed_id
      }
    });
  }

  directUploadWillStoreFileWithXHR(request) {
    request.upload.addEventListener(
      'progress',
      (event) => this.directUploadDidProgress(event),
    );
  }

  directUploadDidProgress(event) {
    // Use event.loaded and event.total to update the progress bar
  }
}


如果您知道更好的方法,而不使用任何额外的库,请告诉我

Soooooo, sorry for the delay just got into this, the easiest and most forward way of implementing active storage direct upload, it's pretty simple although the docs are not really clear about how to achieve it with Stimulus. So if you follow this docs and you want to make it work with Stimulus make sure to override the javascript with this section, first, create the Stimulus controller, add event listeners for the actions you want, in my example, it would be onDrop and onChange event and then bind it with the custom uploader class that we will create.

// rails generate stimulus direct_uploads
// => creates a js file under app/javascript/controllers/direct_uploads_controller.js

import { Controller } from '@hotwired/stimulus';

// don't worry about this import you'll find the class right below
import Uploader from '../utils/direct_uploads';

// Connects to data-controller="direct-uploads"
export default class extends Controller {
  connect() {
  }

  // drop->direct-uploads#handleInputChange
  handleOnDrop(event) {
    event.preventDefault();
    const { files } = event.dataTransfer;
    Array.from(files).forEach((file) => this.uploadFile(file));
  }

  // change->direct-uploads#handleInputChange
  handleInputChange({ currentTarget }) {
    const { files } = currentTarget;
    Array.from(files).forEach((file) => this.uploadFile(file, currentTarget));
  }

  uploadFile(file, currentTarget) {
    const upload = new Uploader(file, currentTarget);
    upload.start();
  }
}

// now let's create the uploader class.
// => app/javascript/utils/direct_uploads.js

import { DirectUpload } from '@rails/activestorage';

export default class Uploader {
  constructor(file, currentTarget) {
    const { dataset: { directUploadUrl } } = currentTarget;

    this.directUpload = new DirectUpload(file, directUploadUrl, this);
    this.targetForm = currentTarget.closest('form');
    this.file = file;
    this.currentTarget = currentTarget;
  }

  start() {
    this.directUpload.create((error, blob) => {
      if (error) {
        // Handle the error
      } else {
        // Add an appropriately-named hidden input to the form
        // with a value of blob.signed_id
      }
    });
  }

  directUploadWillStoreFileWithXHR(request) {
    request.upload.addEventListener(
      'progress',
      (event) => this.directUploadDidProgress(event),
    );
  }

  directUploadDidProgress(event) {
    // Use event.loaded and event.total to update the progress bar
  }
}


let me know if you know a better way to do it, without using any extra libraries

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文