通过jQuery.Ajax下载文件

时间:2023-02-06 00:19:36

I have a Struts2 action in the server side for file downloading.

我在服务器端有一个用于文件下载的Struts2操作。

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

However when I call the action using the jQuery:

然而,当我使用jQuery调用该操作时:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

in Firebug I see the data is retrieved with the Binary stream. I wonder how to open the file downloading window with which the user can save the file locally?

在Firebug中,我看到使用二进制流检索数据。我想知道如何打开文件下载窗口,让用户在本地保存文件?

16 个解决方案

#1


585  

Bluish is completely right about this, you can't do it through Ajax because JavaScript cannot save files directly to a user's computer (out of security concerns). Unfortunately pointing the main window's URL at your file download means you have little control over what the user experience is when a file download occurs.

Bluish完全正确,你不能通过Ajax实现,因为JavaScript不能将文件直接保存到用户的电脑上(出于安全考虑)。不幸的是,在您的文件下载中指向主窗口的URL意味着您几乎无法控制用户体验是什么,当一个文件下载发生时。

I created jQuery File Download which allows for an "Ajax like" experience with file downloads complete with OnSuccess and OnFailure callbacks to provide for a better user experience. Take a look at my blog post on the common problem that the plugin solves and some ways to use it and also a demo of jQuery File Download in action. Here is the source

我创建了jQuery文件下载,它允许“类似Ajax”的文件下载体验,并提供OnSuccess和OnFailure回调,以提供更好的用户体验。看看我的博文,关于插件解决的常见问题和一些使用方法,以及jQuery文件下载的演示。这是源

Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.

这里有一个简单的用例演示,使用带有承诺的插件源代码。演示页面还包含许多其他“更好的用户体验”示例。

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

Depending on what browsers you need to support you may be able to use https://github.com/eligrey/FileSaver.js/ which allows more explicit control than the IFRAME method jQuery File Download uses.

根据需要支持的浏览器,可以使用https://github.com/eligrey/FileSaver.js/,它允许比IFRAME方法jQuery文件下载使用更显式的控制。

#2


175  

Noone posted this @Pekka's solution... so I'll post it. It can help someone.

没人贴出这个@Pekka的解决方案…所以我会发布它。它可以帮助别人。

You can't and don't need to do this through Ajax. Just use

您不能也不需要通过Ajax实现这一点。只使用

window.location="download.action?para1=value1...."

#3


24  

1. Framework agnostic: Servlet downloading file as attachment

1。框架不可知:Servlet下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts2 Framework: Action downloading file as attachment

2。Struts2框架:动作下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

It would be better to use <s:a> tag pointing with OGNL to an URL created with <s:url> tag:

最好使用 标记指向使用 标记创建的URL: 带有ognl的>

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

In the above cases, you need to write the Content-Disposition header to the response, specifying that the file needs to be downloaded (attachment) and not opened by the browser (inline). You need to specify the Content Type too, and you may want to add the file name and length (to help the browser drawing a realistic progressbar).

在上述情况下,您需要将内容配置头写到响应中,指定需要下载文件(附件),而不是由浏览器打开(内联)。您还需要指定内容类型,您可能需要添加文件名和长度(以帮助浏览器绘制一个实际的progressbar)。

For example, when downloading a ZIP:

例如,当下载一个ZIP:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

With Struts2 (unless you are using the Action as a Servlet, an hack for direct streaming, for example), you don't need to directly write anything to the response; simply using the Stream result type and configuring it in struts.xml will work: EXAMPLE

使用Struts2(除非您将操作用作Servlet,例如直接流的黑客),您不需要直接向响应写入任何内容;只需使用流结果类型并在struts中配置它。xml:工作的例子

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3. Framework agnostic (/ Struts2 framework): Servlet(/Action) opening file inside the browser

3所示。框架不可知(/ Struts2框架):Servlet(/Action)在浏览器内打开文件

If you want to open the file inside the browser, instead of downloading it, the Content-disposition must be set to inline, but the target can't be the current window location; you must target a new window created by javascript, an <iframe> in the page, or a new window created on-the-fly with the "discussed" target="_blank":

如果要在浏览器中打开文件,而不是下载文件,则必须将内容配置设置为内联,但目标不能是当前窗口位置;您必须针对由javascript创建的新窗口、页面中的

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>

#4


22  

You can with HTML5

你可以用HTML5

NB: The file data returned MUST be base64 encoded because you cannot JSON encode binary data

NB:返回的文件数据必须是base64编码的,因为您不能JSON编码二进制数据。

In my AJAX response I have a data structure that looks like this:

在我的AJAX响应中,我有这样的数据结构:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

That means that I can do the following to save a file via AJAX

这意味着我可以执行以下操作,通过AJAX保存文件

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

The function base64ToBlob was taken from here and must be used in compliance with this function

函数base64ToBlob是从这里获取的,必须按照这个函数使用

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

This is good if your server is dumping filedata to be saved. However, I've not quite worked out how one would implement a HTML4 fallback

如果您的服务器正在转储要保存的文件数据,这是很好的。然而,我还没有搞清楚如何实现HTML4的回退

#5


21  

I have created little function as workaround solution (inspired by @JohnCulviner plugin):

我创建了一个小函数作为解决方案(受到@JohnCulviner插件的启发):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Demo with click event:

与单击事件演示:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});

#6


15  

Ok, based on ndpu's code heres an improved (I think) version of ajax_download;-

好的,基于ndpu的代码,这里有一个改进版的ajax_download;-

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Use this like this;-

使用这个这样;-

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

The params are sent as proper post params as if coming from an input rather than as a json encoded string as per the previous example.

这些参数作为适当的post参数发送,就像来自输入而不是像前面的示例那样作为json编码的字符串一样。

CAVEAT: Be wary about the potential for variable injection on those forms. There might be a safer way to encode those variables. Alternatively contemplate escaping them.

注意:对于那些形式的变量注入,要小心。也许有一种更安全的方法来对这些变量进行编码。或者考虑逃避它们。

#7


10  

I faced the same issue and successfully solved it. My use-case is this.

我遇到了同样的问题,并成功地解决了它。我的用例是这样的。

"Post JSON data to the server and receive an excel file. That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"

将JSON数据发送到服务器并接收excel文件。该excel文件由服务器创建,并作为对客户机的响应返回。在浏览器中以自定义名称下载该响应

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

The above snippet is just doing following

上面的代码片段只是执行以下操作

  • Posting an array as JSON to the server using XMLHttpRequest.
  • 使用XMLHttpRequest向服务器发布一个JSON数组。
  • After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it. I did a POST request here. Instead, you can go for a simple GET too. We cannot download the file through Ajax, must use XMLHttpRequest.
  • 在以blob(二进制)方式获取内容之后,我们创建了一个可下载的URL并将其附加到不可见的“a”链接,然后单击它。我在这里做了一个POST请求。相反,你也可以选择简单的GET。我们不能通过Ajax下载文件,必须使用XMLHttpRequest。

Here we need to carefully set few things on the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.

在这里,我们需要小心地在服务器端设置一些东西。我在Python Django HttpResponse中设置了一些头部。如果使用其他编程语言,则需要相应地设置它们。

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.

因为我在这里下载了xls(excel),所以我将contentType调整为上面的一个。您需要根据文件类型设置它。您可以使用此技术下载任何类型的文件。

#8


6  

Here is what I did, pure javascript and html. Did not test it but this should work in all browsers.

这就是我所做的,纯javascript和html。没有进行测试,但是应该可以在所有浏览器中使用。

Javascript Function

Javascript函数

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

Using just components that is supported in all browsers no additional libraries.

只使用所有浏览器支持的组件,不使用其他库。

通过jQuery.Ajax下载文件通过jQuery.Ajax下载文件

Here is my server side JAVA Spring controller code.

这是我的服务器端JAVA Spring控制器代码。

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }

#9


4  

The simple way to make the browser downloads a file is to make the request like that:

让浏览器下载文件的简单方法是:

 function downloadFile(urlToSend) {
     var req = new XMLHttpRequest();
     req.open("GET", urlToSend, true);
     req.responseType = "blob";
     req.onload = function (event) {
         var blob = req.response;
         var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
         var link=document.createElement('a');
         link.href=window.URL.createObjectURL(blob);
         link.download=fileName;
         link.click();
     };

     req.send();
 }

This opens the browser download pop up.

这将打开浏览器下载。

#10


3  

Adding some more things to above answer for downloading a file

为下载文件添加以上内容

Below is some java spring code which generates byte Array

下面是一些生成字节数组的java spring代码

@RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
    public ResponseEntity<byte[]> downloadReport(
            @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {

        OutputStream out = new ByteArrayOutputStream();
        // write something to output stream
        HttpHeaders respHeaders = new HttpHeaders();
        respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        respHeaders.add("X-File-Name", name);
        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
        return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
    }

Now in javascript code using FileSaver.js ,can download a file with below code

现在在javascript代码中使用文件共享。可以下载一个包含以下代码的文件。

var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 201) {
        var res = this.response;
        var fileName=this.getResponseHeader('X-File-Name');
        var data = new Blob([res]);
        saveAs(data, fileName); //this from FileSaver.js
    }
}    
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);

The above will download file

以上将下载文件

#11


3  

function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}

#12


1  

Ok so here is the working code when Using MVC and you are getting your file from a controller

这是使用MVC时的工作代码从控制器中获取文件

lets say you have your byte array declare and populate, the only thing you need to do is to use the File function (using System.Web.Mvc)

假设您已经声明并填充了字节数组,您只需使用File函数(使用System.Web.Mvc)

byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");

and then, in the same controller, add thoses 2 functions

然后,在同一个控制器中,添加这两个函数

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

and then you will be able to call your controller to download and get the "success" or "failure" callback

然后您将能够调用您的控制器来下载并获得“成功”或“失败”回调

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });

#13


1  

In Rails, I do it this way:

在Rails中,我是这样做的:

function download_file(file_id) {
  let url       = '/files/' + file_id + '/download_file';
    $.ajax({
    type: 'GET',
    url: url,
    processData: false,
    success: function (data) {
       window.location = url;
    },
    error: function (xhr) {
     console.log(' Error:  >>>> ' + JSON.stringify(xhr));
    }
   });
 }

The trick is the window.location part. The controller's method looks like:

关键是窗户。位置的部分。控制器的方法如下:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end

#14


0  

If you want to use jQuery File Download , please note this for IE. You need to reset the response or it will not download

如果您想使用jQuery文件下载,请注意IE。您需要重置响应,否则它将不下载。

    //The IE will only work if you reset response
    getServletResponse().reset();
    //The jquery.fileDownload needs a cookie be set
    getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
    //Do the reset of your action create InputStream and return

Your action can implement ServletResponseAware to access getServletResponse()

您的操作可以实现ServletResponseAware来访问getServletResponse()

#15


0  

I found a fix that while it's not actually using ajax it does allow you to use a javascript call to request the download and then get a callback when the download actually starts. I found this helpful if the link runs a server side script that takes a little bit to compose the file before sending it. so you can alert them that it's processing, and then when it does finally send the file remove that processing notification. which is why I wanted to try to load the file via ajax to begin with so that I could have an event happen when the file is requested and another when it actually starts downloading.

我找到了一个解决方案,虽然它实际上并不使用ajax,但它确实允许您使用javascript调用来请求下载,然后在下载真正开始时获得回调。我发现,如果链接运行服务器端脚本,在发送文件之前需要编写一些文件,那么这将非常有用。你可以提醒他们它正在处理,当它最终发送文件时,删除处理通知。这就是为什么我想尝试通过ajax加载文件,这样我就可以在请求文件时发生一个事件,而另一个在实际开始下载时发生。

the js on the front page

首页上的js

function expdone()
{
    document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
   document.getElementById('exportdiv').style.display='block';
   document.getElementById('exportif').src='test2.php?arguments=data';
}

the iframe

iframe

<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>

then the other file:

其他文件:

<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
    window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>

I think there's a way to read get data using js so then no php would be needed. but I don't know it off hand and the server I'm using supports php so this works for me. thought I'd share it in case it helps anyone.

我认为有一种方法可以使用js来读取数据,这样就不需要php了。但是我现在还不知道,我使用的服务器支持php,所以这对我来说是可行的。我想分享一下,以防它对任何人都有帮助。

#16


0  

It is certain that you can not do it through Ajax call.

可以肯定的是,您不能通过Ajax调用进行此操作。

However, there is a workaround.

然而,有一个变通的办法。

Steps :

步骤:

If you are using form.submit() for downloading the file, what you can do is :

如果你正在使用form.submit()下载文件,你可以做的是:

  1. Create an ajax call from client to server and store the file stream inside the session.
  2. 从客户端到服务器创建一个ajax调用,并将文件流存储在会话中。
  3. Upon "success" being returned from server, call your form.submit() to just stream the file stream stored in the session.
  4. 在从服务器返回“success”时,调用form.submit()来对会话中存储的文件流进行流处理。

This is helpful in case when you want to decide whether or not file needs to be downloaded after making form.submit(), eg: there can be a case where on form.submit(), an exception occurs on the server side and instead of crashing, you might need to show a custom message on the client side, in such case this implementation might help.

这是有用的情况下当你想决定是否需要下载文件后form.submit(),例如:可以有情况在form.submit(),在服务器端发生异常而不是崩溃,您可能需要在客户端显示一个自定义的消息,在这样的情况下实现可能会有所帮助。

#1


585  

Bluish is completely right about this, you can't do it through Ajax because JavaScript cannot save files directly to a user's computer (out of security concerns). Unfortunately pointing the main window's URL at your file download means you have little control over what the user experience is when a file download occurs.

Bluish完全正确,你不能通过Ajax实现,因为JavaScript不能将文件直接保存到用户的电脑上(出于安全考虑)。不幸的是,在您的文件下载中指向主窗口的URL意味着您几乎无法控制用户体验是什么,当一个文件下载发生时。

I created jQuery File Download which allows for an "Ajax like" experience with file downloads complete with OnSuccess and OnFailure callbacks to provide for a better user experience. Take a look at my blog post on the common problem that the plugin solves and some ways to use it and also a demo of jQuery File Download in action. Here is the source

我创建了jQuery文件下载,它允许“类似Ajax”的文件下载体验,并提供OnSuccess和OnFailure回调,以提供更好的用户体验。看看我的博文,关于插件解决的常见问题和一些使用方法,以及jQuery文件下载的演示。这是源

Here is a simple use case demo using the plugin source with promises. The demo page includes many other, 'better UX' examples as well.

这里有一个简单的用例演示,使用带有承诺的插件源代码。演示页面还包含许多其他“更好的用户体验”示例。

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

Depending on what browsers you need to support you may be able to use https://github.com/eligrey/FileSaver.js/ which allows more explicit control than the IFRAME method jQuery File Download uses.

根据需要支持的浏览器,可以使用https://github.com/eligrey/FileSaver.js/,它允许比IFRAME方法jQuery文件下载使用更显式的控制。

#2


175  

Noone posted this @Pekka's solution... so I'll post it. It can help someone.

没人贴出这个@Pekka的解决方案…所以我会发布它。它可以帮助别人。

You can't and don't need to do this through Ajax. Just use

您不能也不需要通过Ajax实现这一点。只使用

window.location="download.action?para1=value1...."

#3


24  

1. Framework agnostic: Servlet downloading file as attachment

1。框架不可知:Servlet下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts2 Framework: Action downloading file as attachment

2。Struts2框架:动作下载文件作为附件

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

It would be better to use <s:a> tag pointing with OGNL to an URL created with <s:url> tag:

最好使用 标记指向使用 标记创建的URL: 带有ognl的>

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

In the above cases, you need to write the Content-Disposition header to the response, specifying that the file needs to be downloaded (attachment) and not opened by the browser (inline). You need to specify the Content Type too, and you may want to add the file name and length (to help the browser drawing a realistic progressbar).

在上述情况下,您需要将内容配置头写到响应中,指定需要下载文件(附件),而不是由浏览器打开(内联)。您还需要指定内容类型,您可能需要添加文件名和长度(以帮助浏览器绘制一个实际的progressbar)。

For example, when downloading a ZIP:

例如,当下载一个ZIP:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

With Struts2 (unless you are using the Action as a Servlet, an hack for direct streaming, for example), you don't need to directly write anything to the response; simply using the Stream result type and configuring it in struts.xml will work: EXAMPLE

使用Struts2(除非您将操作用作Servlet,例如直接流的黑客),您不需要直接向响应写入任何内容;只需使用流结果类型并在struts中配置它。xml:工作的例子

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3. Framework agnostic (/ Struts2 framework): Servlet(/Action) opening file inside the browser

3所示。框架不可知(/ Struts2框架):Servlet(/Action)在浏览器内打开文件

If you want to open the file inside the browser, instead of downloading it, the Content-disposition must be set to inline, but the target can't be the current window location; you must target a new window created by javascript, an <iframe> in the page, or a new window created on-the-fly with the "discussed" target="_blank":

如果要在浏览器中打开文件,而不是下载文件,则必须将内容配置设置为内联,但目标不能是当前窗口位置;您必须针对由javascript创建的新窗口、页面中的

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>

#4


22  

You can with HTML5

你可以用HTML5

NB: The file data returned MUST be base64 encoded because you cannot JSON encode binary data

NB:返回的文件数据必须是base64编码的,因为您不能JSON编码二进制数据。

In my AJAX response I have a data structure that looks like this:

在我的AJAX响应中,我有这样的数据结构:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

That means that I can do the following to save a file via AJAX

这意味着我可以执行以下操作,通过AJAX保存文件

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

The function base64ToBlob was taken from here and must be used in compliance with this function

函数base64ToBlob是从这里获取的,必须按照这个函数使用

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

This is good if your server is dumping filedata to be saved. However, I've not quite worked out how one would implement a HTML4 fallback

如果您的服务器正在转储要保存的文件数据,这是很好的。然而,我还没有搞清楚如何实现HTML4的回退

#5


21  

I have created little function as workaround solution (inspired by @JohnCulviner plugin):

我创建了一个小函数作为解决方案(受到@JohnCulviner插件的启发):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Demo with click event:

与单击事件演示:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});

#6


15  

Ok, based on ndpu's code heres an improved (I think) version of ajax_download;-

好的,基于ndpu的代码,这里有一个改进版的ajax_download;-

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Use this like this;-

使用这个这样;-

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

The params are sent as proper post params as if coming from an input rather than as a json encoded string as per the previous example.

这些参数作为适当的post参数发送,就像来自输入而不是像前面的示例那样作为json编码的字符串一样。

CAVEAT: Be wary about the potential for variable injection on those forms. There might be a safer way to encode those variables. Alternatively contemplate escaping them.

注意:对于那些形式的变量注入,要小心。也许有一种更安全的方法来对这些变量进行编码。或者考虑逃避它们。

#7


10  

I faced the same issue and successfully solved it. My use-case is this.

我遇到了同样的问题,并成功地解决了它。我的用例是这样的。

"Post JSON data to the server and receive an excel file. That excel file is created by the server and returned as a response to the client. Download that response as a file with custom name in browser"

将JSON数据发送到服务器并接收excel文件。该excel文件由服务器创建,并作为对客户机的响应返回。在浏览器中以自定义名称下载该响应

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

The above snippet is just doing following

上面的代码片段只是执行以下操作

  • Posting an array as JSON to the server using XMLHttpRequest.
  • 使用XMLHttpRequest向服务器发布一个JSON数组。
  • After fetching content as a blob(binary), we are creating a downloadable URL and attaching it to invisible "a" link then clicking it. I did a POST request here. Instead, you can go for a simple GET too. We cannot download the file through Ajax, must use XMLHttpRequest.
  • 在以blob(二进制)方式获取内容之后,我们创建了一个可下载的URL并将其附加到不可见的“a”链接,然后单击它。我在这里做了一个POST请求。相反,你也可以选择简单的GET。我们不能通过Ajax下载文件,必须使用XMLHttpRequest。

Here we need to carefully set few things on the server side. I set few headers in Python Django HttpResponse. You need to set them accordingly if you use other programming languages.

在这里,我们需要小心地在服务器端设置一些东西。我在Python Django HttpResponse中设置了一些头部。如果使用其他编程语言,则需要相应地设置它们。

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Since I download xls(excel) here, I adjusted contentType to above one. You need to set it according to your file type. You can use this technique to download any kind of files.

因为我在这里下载了xls(excel),所以我将contentType调整为上面的一个。您需要根据文件类型设置它。您可以使用此技术下载任何类型的文件。

#8


6  

Here is what I did, pure javascript and html. Did not test it but this should work in all browsers.

这就是我所做的,纯javascript和html。没有进行测试,但是应该可以在所有浏览器中使用。

Javascript Function

Javascript函数

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

Using just components that is supported in all browsers no additional libraries.

只使用所有浏览器支持的组件,不使用其他库。

通过jQuery.Ajax下载文件通过jQuery.Ajax下载文件

Here is my server side JAVA Spring controller code.

这是我的服务器端JAVA Spring控制器代码。

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }

#9


4  

The simple way to make the browser downloads a file is to make the request like that:

让浏览器下载文件的简单方法是:

 function downloadFile(urlToSend) {
     var req = new XMLHttpRequest();
     req.open("GET", urlToSend, true);
     req.responseType = "blob";
     req.onload = function (event) {
         var blob = req.response;
         var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
         var link=document.createElement('a');
         link.href=window.URL.createObjectURL(blob);
         link.download=fileName;
         link.click();
     };

     req.send();
 }

This opens the browser download pop up.

这将打开浏览器下载。

#10


3  

Adding some more things to above answer for downloading a file

为下载文件添加以上内容

Below is some java spring code which generates byte Array

下面是一些生成字节数组的java spring代码

@RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
    public ResponseEntity<byte[]> downloadReport(
            @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {

        OutputStream out = new ByteArrayOutputStream();
        // write something to output stream
        HttpHeaders respHeaders = new HttpHeaders();
        respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        respHeaders.add("X-File-Name", name);
        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
        return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
    }

Now in javascript code using FileSaver.js ,can download a file with below code

现在在javascript代码中使用文件共享。可以下载一个包含以下代码的文件。

var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 201) {
        var res = this.response;
        var fileName=this.getResponseHeader('X-File-Name');
        var data = new Blob([res]);
        saveAs(data, fileName); //this from FileSaver.js
    }
}    
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);

The above will download file

以上将下载文件

#11


3  

function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}

#12


1  

Ok so here is the working code when Using MVC and you are getting your file from a controller

这是使用MVC时的工作代码从控制器中获取文件

lets say you have your byte array declare and populate, the only thing you need to do is to use the File function (using System.Web.Mvc)

假设您已经声明并填充了字节数组,您只需使用File函数(使用System.Web.Mvc)

byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");

and then, in the same controller, add thoses 2 functions

然后,在同一个控制器中,添加这两个函数

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

and then you will be able to call your controller to download and get the "success" or "failure" callback

然后您将能够调用您的控制器来下载并获得“成功”或“失败”回调

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });

#13


1  

In Rails, I do it this way:

在Rails中,我是这样做的:

function download_file(file_id) {
  let url       = '/files/' + file_id + '/download_file';
    $.ajax({
    type: 'GET',
    url: url,
    processData: false,
    success: function (data) {
       window.location = url;
    },
    error: function (xhr) {
     console.log(' Error:  >>>> ' + JSON.stringify(xhr));
    }
   });
 }

The trick is the window.location part. The controller's method looks like:

关键是窗户。位置的部分。控制器的方法如下:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end

#14


0  

If you want to use jQuery File Download , please note this for IE. You need to reset the response or it will not download

如果您想使用jQuery文件下载,请注意IE。您需要重置响应,否则它将不下载。

    //The IE will only work if you reset response
    getServletResponse().reset();
    //The jquery.fileDownload needs a cookie be set
    getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
    //Do the reset of your action create InputStream and return

Your action can implement ServletResponseAware to access getServletResponse()

您的操作可以实现ServletResponseAware来访问getServletResponse()

#15


0  

I found a fix that while it's not actually using ajax it does allow you to use a javascript call to request the download and then get a callback when the download actually starts. I found this helpful if the link runs a server side script that takes a little bit to compose the file before sending it. so you can alert them that it's processing, and then when it does finally send the file remove that processing notification. which is why I wanted to try to load the file via ajax to begin with so that I could have an event happen when the file is requested and another when it actually starts downloading.

我找到了一个解决方案,虽然它实际上并不使用ajax,但它确实允许您使用javascript调用来请求下载,然后在下载真正开始时获得回调。我发现,如果链接运行服务器端脚本,在发送文件之前需要编写一些文件,那么这将非常有用。你可以提醒他们它正在处理,当它最终发送文件时,删除处理通知。这就是为什么我想尝试通过ajax加载文件,这样我就可以在请求文件时发生一个事件,而另一个在实际开始下载时发生。

the js on the front page

首页上的js

function expdone()
{
    document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
   document.getElementById('exportdiv').style.display='block';
   document.getElementById('exportif').src='test2.php?arguments=data';
}

the iframe

iframe

<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>

then the other file:

其他文件:

<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
    window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>

I think there's a way to read get data using js so then no php would be needed. but I don't know it off hand and the server I'm using supports php so this works for me. thought I'd share it in case it helps anyone.

我认为有一种方法可以使用js来读取数据,这样就不需要php了。但是我现在还不知道,我使用的服务器支持php,所以这对我来说是可行的。我想分享一下,以防它对任何人都有帮助。

#16


0  

It is certain that you can not do it through Ajax call.

可以肯定的是,您不能通过Ajax调用进行此操作。

However, there is a workaround.

然而,有一个变通的办法。

Steps :

步骤:

If you are using form.submit() for downloading the file, what you can do is :

如果你正在使用form.submit()下载文件,你可以做的是:

  1. Create an ajax call from client to server and store the file stream inside the session.
  2. 从客户端到服务器创建一个ajax调用,并将文件流存储在会话中。
  3. Upon "success" being returned from server, call your form.submit() to just stream the file stream stored in the session.
  4. 在从服务器返回“success”时,调用form.submit()来对会话中存储的文件流进行流处理。

This is helpful in case when you want to decide whether or not file needs to be downloaded after making form.submit(), eg: there can be a case where on form.submit(), an exception occurs on the server side and instead of crashing, you might need to show a custom message on the client side, in such case this implementation might help.

这是有用的情况下当你想决定是否需要下载文件后form.submit(),例如:可以有情况在form.submit(),在服务器端发生异常而不是崩溃,您可能需要在客户端显示一个自定义的消息,在这样的情况下实现可能会有所帮助。