javascript - 使用带有后续操作的 send_data 下载 Rails 文件

标签 javascript ruby-on-rails ruby-on-rails-4 download

我正在开发一个代码库,我需要允许用户下载已经驻留在 AWS S3 上的 PDF 文档。我已经实现了一个用于先前功能的下载问题。

对于此功能,我需要在用户完成文件下载后更新 UI(进度步进器)。我最初认为这会很简单:

  1. 用户点击下载
  2. API 调用是在使用 send_data 下载文件的地方进行的。在此 API 调用中,我还将更新 Foo 模型以更改状态以指示用户已下载文件;
  3. 执行redirect_to request.referer 重新加载数据。 Foo 中的更改状态将负责在 UI 中显示更新进度;

我误以为这会很简单。复杂的原因:

  1. send_data 已经在渲染数据,所以我无法使用 redirect_to 刷新页面,因为这会触发多重渲染错误;
  2. send_data 不适用于 remote: true 选项,因此通过 AJAX 链接请求数据和更新 ERB 模板已被淘汰;
  3. 我可以将所有内容写入 JS on click 函数,但这似乎有点 hack。我可能需要直接从 AWS 检索文件并跳过我的 api?我怀疑我可能会遇到 CORS 问题,因为我无法控制服务器。

这是我的 rails 下载方法目前的样子:

def download
  attachment = Attachment.find_by_id(params[:attachment_id])
  content = send_data(
    attachment.file.read,
    filename: "#{attachment.title}.#{attachment.file.file.extension}",
    type: attachment.content_type,
    disposition: "attachment",
  )
end

基本上工作的 js 代码看起来像这样,其中所有相关路径和文件名都通过数据属性传递给 JS:

$(document).on("click", "#download", function(e){
  e.preventDefault();
  const data = $('#temp-information').data();
  var req = new XMLHttpRequest();
  req.open("GET", data.path, true);
  req.responseType = "blob";
  const filename = data.title;
  req.onload = function (event) {
    var blob = req.response;
    console.log(blob.size);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download= filename;
    document.body.appendChild(link);
    link.click();
  };
  if (typeof window.navigator.msSaveBlob !== 'undefined') {
    // Fix to work in IE11
    window.navigator.msSaveBlob(blob, filename);
  } else {
    req.send();
  }
});

在下载完成后处理文件下载和更新 UI 的最有效和 Rails 方式是什么?

最佳答案

您并非 100% 清楚您要完成的任务。如果您试图让用户看到下载进度,我不确定您是否真的需要执行除 send_data 之外的任何操作,然后大多数浏览器将开始下载文件,包括显示进度条.

因为您似乎想在文件下载完成后做一些事情,所以这有点棘手。该问题与 Rails 无关,您使用的方法在我看来非常合理。

关于 this SO thread您会发现对这个问题的冗长讨论以及人们尝试解决它的各种方法。通常,解决方案遵循相同的基本结构,即简单地轮询服务器。

在您的 Rails 应用程序中,您可以大致按如下方式实现。假设您在附件模型中添加了一个字段 status...

def download
  attachment = Attachment.find_by_id(params[:attachment_id])
  attachment.update(status: "downloading")
  send_data(
    attachment.file.read,
    filename: "#{attachment.title}.#{attachment.file.file.extension}",
    type: attachment.content_type,
    disposition: "attachment",
  )
  attachment.update(status: "complete")
end

然后您可以添加一个返回文件状态的端点。因此,当用户开始下载文件时,您就开始轮询该端点。

def attachment_status
  attachment = Attachment.find_by_id(params[:attachment_id])
  respond_to do |format|
    format.json do
      {status: attachment.status}
    end
  end
end

然后在 Javascript 中,例如使用 HttpPromise :

var http = new HttpPromise;
function poll(doneFn) {
  http.get("/status.json") // you will need to set your actual status endpoint path here
      .success(function(data,xhr){
        if (data.status == "complete") {
          doneFn();
        }
      });
};
function downloadFinished(){
  // ... do whatever you want on finish here ...
};
setInterval(function(){ poll(downloadFinished) }, 5000);

这不是世界上最美好的事情,但它应该完成工作。

祝你好运!

关于javascript - 使用带有后续操作的 send_data 下载 Rails 文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50252859/

相关文章:

javascript - 如何从数据库中保留段落分隔符和缩进?

ruby-on-rails - 如何通过yaml将 secret 数据设置为kubernetes secret ?

html - 更改导航栏上的文本颜色

ruby-on-rails - 我如何在演示者中使用 Enumerable mixin?

ruby - 编写正则表达式作为 gsub 的参数!方法——难倒?

JavaScript 替换表单提交

javascript - 如何在 JavaScript 中添加对象数组中的所有数量字段

mysql - Rails 不等于 current_user 总是没有结果

javascript - Ngx-Bootstrap Typeahead 在输入清除时保留下拉值

javascript - 动态加载 wordpress wp_editor (ajax)