如何在客户端而不是在表单提交时触发文件上传?

时间:2018-09-25 19:25:28

标签: ruby-on-rails amazon-s3 rails-activestorage

我有一个使用s3的活动存储示例的工作版本:

https://edgeguides.rubyonrails.org/active_storage_overview.html

现在,我希望能够执行的不是上完表格,而是在用户选择要上载的文件后立即执行上载。 实际上,就我而言,我有一个所见即所得的编辑器,该编辑器具有一个触发的 on drop 事件

var myCodeMirror = CodeMirror.fromTextArea(post_body, {
   lineNumbers: true,
   dragDrop: true
  });

  myCodeMirror.on('drop', function(data, e) {
    var file;
    var files;
    // Check if files were dropped
    files = e.dataTransfer.files;
    if (files.length > 0) {
      e.preventDefault();
      e.stopPropagation();
      file = files[0];
      console.log('File: ' + file.name);
      console.log('File: ' + file.type);
      return false;
    }
  });

那么,由于文件删除触发了此事件,我是否可以将其以某种方式发送到活动存储,以便立即将文件上传到S3?

2 个答案:

答案 0 :(得分:2)

从客户端触发上传

Active Storage公开了DirectUpload JavaScript类,您可以使用它来直接从客户端触发文件上传。

您可以利用它与第三方插件(例如Uppy,Dropzone)或您自己的自定义JS代码集成。

使用DirectUpload

您需要做的第一件事是确保将AWS S3设置为可以处理直接上传。这需要确保正确设置了CORS配置。

接下来,您只需实例化DirectUpload类的实例,并向其传递要上传的文件和上传URL。

import { DirectUpload } from "activestorage"

// your form needs the file_field direct_upload: true, which
// provides data-direct-upload-url
const input = document.querySelector('input[type=file]')
const url = input.dataset.directUploadUrl
const upload = new DirectUpload(file, url)

upload.create((error, blob) => { 
   // handle errors OR persist to the model using 'blob.signed_id'
})

在此处查看完整文档: https://edgeguides.rubyonrails.org/active_storage_overview.html#integrating-with-libraries-or-frameworks

DirectUpload#create方法将启动到S3的上传,并返回错误或上传的文件Blob。

假设没有错误,最后一步是将上传的文件持久保存到模型中。您可以使用blob.signed_id并将其放在页面上某个地方的隐藏字段中,也可以使用AJAX请求来更新模型。

上载文件

在上述情况下,要开始直接在drop上进行上传,只需将上面的代码放入drop处理程序中。

类似这样的东西:

myCodeMirror.on('drop', function(data, e) {
   // Get the file
   var file = e.dataTransfer.files[0];

   // You need a file input somewhere on the page...
   const input = document.querySelector('input[type=file]')
   const url = input.dataset.directUploadUrl

   // Instantiate the DirectUploader object
   const upload = new DirectUpload(file, url)

   // Upload the file
   upload.create((error, blob) => { ... })
});

使用资产管道

如果您仅使用资产管道而不使用JavaScript捆绑工具,则可以像这样创建DirectUpload类的实例

const upload = new ActiveStorage.DirectUpload(file, url)

答案 1 :(得分:0)

该主题的主要问题是-您不能在表单的Java脚本部分中导入DataUpload。但是我们可以创建对象InstantUploader,如下所示:

全局Java脚本部分

upload / uploader.js

import { DirectUpload } from "@rails/activestorage"

export default class Uploader {
  constructor(file, url) {
    this.file = file
    this.url = url
    this.directUpload = new DirectUpload(this.file, this.url, this)
  }
 
  upload() {
    return new Promise((resolve, reject) => {
      this.directUpload.create((error, blob) => {
        if (error) {
          // Handle the error
          reject(error)
        } else {
          // Add an appropriately-named hidden input to the form
          // with a value of blob.signed_id
          resolve(blob)
        }
      })
    })
  }
}

upload / index.js

import Uploader from './uploader.js'

export default {
    upload (file, url) {
        const uploader = new Uploader(file, url)
        return uploader.upload()
    }
}

application.js

window.ImmediateUploader = require('./upload');

组成部分
现在,我们可以使用InstantUploader将所选文件直接上传到活动存储中,并在加载后不提交提交就更新映像:


<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
  <%= f.error_notification %>

  <div class="form-inputs">
    <div class="row">
      <img id="avatar" class="centered-and-cropped" width="100" height="100" style="border-radius:50%" src="<%= url_for(user.photo) %>"> 
      <button type="button" class="btn" onclick="event.preventDefault(); document.getElementById('user_photo').click()">Change avatar</button>
    </div>
    <%= f.file_field :photo, direct_upload: true, class: "hiddenfile" %>  
  </div>
  <div class="form-actions">
    <%= f.button :submit, t(".update"), class: 'btn btn-primary'  %>
  </div>
<% end %>

<% content_for :js do %>
<script>
const input = document.querySelector('input[type=file]')

input.addEventListener('change', (event) => {
  Array.from(input.files).forEach(file => uploadFile(file))
  // clear uploaded files from the input
  input.value = null
})

const uploadFile = (file) => {
  // your form needs the file_field direct_upload: true, which
  //  provides data-direct-upload-url
  const url = input.dataset.directUploadUrl;
  ImmediateUploader.default.upload (file, url)
    .then(blob => {
      // get blob.signed_id and add it to form values to submit form
      const hiddenField = document.createElement('input')
      hiddenField.setAttribute("type", "hidden");
      hiddenField.setAttribute("value", blob.signed_id);
      hiddenField.name = input.name
      document.querySelector('form').appendChild(hiddenField)
      // Update new avatar Immediately
      document.getElementById('avatar').src = '/rails/active_storage/blobs/' + blob.signed_id + '/' + blob.filename;     
      // Update photo in Database
      axios.post('/users/photo', { 'photo': blob.signed_id }).then(response => {});
    });
}</script>
<% end %>

控制器:

class RegistrationController < Devise::RegistrationsController
  def update
    super
    @user = current_user
    @user.avatar = url_for(@user.photo.variant(resize_to_limit: [300, 300]).processed) if @user.photo.attached?
    @user.save
  end
  def updatephoto
    @photo = params[:photo]
    @user = current_user
    @user.photo = @photo
    @user.save

    @user = current_user
    @user.avatar = url_for(@user.photo.variant(resize_to_limit: [300, 300]).processed) if @user.photo.attached?
    @user.save
  end
end