2016-03-04 60 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。

    相關問題