2016-03-04 94 views
2

我有一个NGINX + Flask + KnockoutJS单页面应用程序,我想创建一个下载按钮,允许用户下载数据他/她正在可视化和操纵客户端而无需重新加载完整页面 有许多纯JavaScript解决方案(如download.js),但它们都不与所有主流浏览器(例如Safari)完全兼容。使用JavaScript打开“另存为”/“下载”对话框以下载动态创建的文件

基本上我想有什么:

  1. 这个应用程序显示了一个表,用户
  2. 用户按下下载按钮
  3. 的JavaScript将数据发送到服务器端的端点
  4. 服务器根据客户端发送的数据即时生成文件
  5. 浏览器打开下载/另存为对话框

可能吗?

+1

download.js作者:如果有人知道Safari浏览器修复,我会非常感激。也就是说,如果您使用服务器生成内容,则只需使用内容处置标头即可在隐藏的iframe中触发下载。 – dandavis

+1

有一个很好的剪辑顶部,将告诉你如何下载表单响应或iframe位置更改:http://php.net/manual/en/function.header.php#refsect1-function.header-examples – dandavis

回答

0

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

+0

safari不支持'a [download]',根据OP ... http://caniuse.com/#feat=download – dandavis

+0

这并不能真正改变我答案的事实,这是浏览器决定要做什么与下载的文件,而不是任何JS代码。 –

+0

@ScottMarcus你是对的,但重点不在于对话本身。我只是用它来解释这个事实,即我想要下载文件而不是在浏览器中打开。 – raben

2

这可能吗?

当然。让我们有一个例子:

  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. 用户按下载按钮

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

  • 的JavaScript将数据发送到服务器端的端点
  • 为什么关于JavaScript的照顾,当你有标准的HTML表单,可以在提交数据到服务器纯粹是浏览器不可知的方式,如第1点所示。如果你真的关心一些javascript,你总是可以订阅我之前展示的<form>的动作onsubmit,并使用javascript将必要的数据作为隐藏字段注入到DOM中。

  • 服务器基于从客户端
  • 对,就是像标准的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 
    
  • 在浏览器中打开下载/另存为对话框
  • 这正是任何浏览器在处理先前显示的来自服务器的HTTP响应时所要做的。

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

    +0

    谢谢,我关心JavaScript,因为我想在没有页面刷新的情况下下载文件(我编辑了我的问题以阐明)我尝试向我的服务器应用程序端点发送POST HTTP请求,但即使HTTP响应成功文件未被下载。 – raben

    +0

    如果用户POST一个HTML表单到一个服务器端端点,该端点用一个'Content-Disposition':附件头来传输文件,如我的答案所示。仅显示“另存为”对话框,供用户选择文件目的地,并且不会发生任何页面重新加载。 –

    +0

    哦,我不知道!我会尝试。 – raben

    0

    在和我意识到,也许我的问题不是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。

    相关问题