【问题标题】:Open a "Save As" / "Download" dialog with JavaScript to download a file created on the fly使用 JavaScript 打开“另存为”/“下载”对话框以下载动态创建的文件
【发布时间】:2016-03-04 20:25:34
【问题描述】:

我有一个 NGINX+Flask+KnockoutJS 单页应用程序,我想创建一个下载按钮,允许用户下载他/她正在可视化和操作客户端的数据无需重新加载整个页面 有许多纯 JavaScript 解决方案(例如 download.js),但没有一个完全兼容所有主流浏览器(例如 Safari)。

基本上我想要的是:

  1. 应用向用户显示表格
  2. 用户按下下载按钮
  3. JavaScript 将数据发送到服务器端端点
  4. 服务器根据客户端发送的数据动态生成文件
  5. 浏览器打开下载/另存为对话框

有可能吗?

【问题讨论】:

  • download.js 作者在这里:如果有人知道 safari 修复程序,我将非常感激。也就是说,如果您使用服务器生成内容,您可以简单地使用 content-disposition 标头来触发隐藏 iframe 中的下载。
  • 顶部有一个很好的片段,它将向您展示如何下载表单响应或 iframe 位置更改:php.net/manual/en/…

标签: javascript jquery flask


【解决方案1】:

有可能吗?

当然。举个例子:

  1. 应用向用户显示表格

你的意思是,类似于使用<table> 标签:

<table>
    <tr>
        <td>Foo bar</td>
        <td>123</td>
        <td>
            <form action="/download/foo/bar/123" method="post">
                <button type="submit" value="Download foo bar 123" />
            </form>
        </td>
    </tr>

    ... here come some other rows of the table ...
</table>
  1. 用户按下下载按钮

好的,这个很明显。用户通过单击表格所需行上的相应提交按钮来提交表单。

  1. JavaScript 将数据发送到服务器端端点

当您拥有标准 HTML 表单时,为什么要关心 javascript,它可以以纯粹与浏览器无关的方式向服务器提交数据,如第 1 点所示。如果您真的关心一些 javascript,您可以随时订阅我之前展示的 &lt;form&gt;onsubmit 操作,并使用 javascript 将必要的数据作为隐藏字段注入 DOM。

  1. 服务器根据客户端发送的数据动态生成文件

是的,这就像标准的 HTTP 协议。服务器将简单地处理/download/foo/bar/123 端点并将文件作为附件发送:

HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 29
Content-Disposition: attachment; filename=foobar123.bin

HERE COMES THE BINARY CONTENT
  1. 浏览器打开下载/另存为对话框

这正是任何浏览器在处理之前显示的来自服务器的 HTTP 响应时会做的事情。

结论:HTTP 协议和标准 HTML 表单已经为您提供了必要的工具来满足您的要求。如果您想要一些额外的花哨,只需在提交时使用 javascript 增强 HTML 表单,以便将任何必填字段附加为您想要发送到服务器的隐藏输入元素。然后让浏览器来处理下载。

【讨论】:

  • 谢谢,我确实关心 JavaScript,因为我想在不刷新页面的情况下下载文件(我编辑了我的问题以澄清)我试图向我的服务器应用程序端点发送一个 POST HTTP 请求,但即使如果 HTTP 响应成功,则不会下载文件。
  • 如果用户将 HTML 表单发布到服务器端端点,该端点使用Content-Disposition: 流式传输文件,则不会刷新页面,如我的回答所示。 Save As 对话框仅显示供用户选择文件目标,并且不会发生任何页面重新加载。
  • 哦,我不知道!我会试试的。
【解决方案2】:

浏览器会根据自己的下载设置显示另存为对话框。文件是否被下载取决于操作系统是否有程序来处理打开文件。您可以通过使用下载属性制作锚点来强制下载(覆盖打开文件)。

【讨论】:

  • safari 不支持 a[download],根据 OP...caniuse.com/#feat=download
  • 并没有真正改变我回答的事实,即浏览器决定如何处理下载的文件,而不是任何 JS 代码。
  • @ScottMarcus 你是对的,但重点不是对话本身。我只是用它来解释我希望下载文件而不是在浏览器中打开的事实。
【解决方案3】:

然后我意识到我的问题可能不是 100% 清楚。无论如何,我想分享我想出的解决方案。我在我的 Flask 应用程序中创建了两个终点:

首先通过 AJAX POST 从客户端获取数据并将它们临时存储在 Redis 中(我已经有一个 Redis 实例用于缓存)并为文件生成一个 UUID。

@mod.route("/create-csv", methods=['POST'])
def create_csv():
    csv_string = request.form.get('csv')
    file_id = str(uuid())
    rstore.setex(file_id, 60, csv_string)
    return jsonify({}), 202, {'Location': url_for('api.download',
                                              file_id=file_id,
                                              _external=True, 
                                              _scheme='https')}

第二个端点只是将带有适当标头的文件发送到客户端。

@mod.route("/download/<file_id>", methods=['GET'])
def download(file_id):
    file_content = rstore.get(file_id)
    response = make_response(file_content)
    response.headers["Content-Disposition"] = "attachment; filename=keywords.csv"
    response.headers['Content-Type'] = "application/octet-stream"
    return response

在客户端站点上,我有以下 JavaScript 代码:

  self.save = function(csvdata) {
    $.post( "/api/create-csv", csvdata, function(data, status, response){

      var file_url = response.getResponseHeader('Location');
      window.location.assign(file_url);

    });
  }

因此,当 POST 请求成功发送后,我只需将文件下载的 URL 分配给当前 URL。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-13
    • 1970-01-01
    • 2011-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多