Categories
ajax download javascript jquery jsp

Download a file by jQuery.Ajax

476

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

<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:

$.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?

3

  • 1

    possible duplicate of How to download a file on clicking the name of file using PHP?

    – Pekka

    Dec 28, 2010 at 10:29

  • 1

    I marked it as a duplicate despite the platform difference, because as far as I can see the solution is the same (You can’t and don’t need to do this through Ajax).

    – Pekka

    Dec 28, 2010 at 10:30


  • 2

    so,without ajax,just use the window.location=”download.action?para1=value1….”?

    – hguser

    Dec 28, 2010 at 10:32

748

2019 modern browsers update

This is the approach I’d now recommend with a few caveats:

  • A relatively modern browser is required
  • If the file is expected to be very large you should likely do something similar to the original approach (iframe and cookie) because some of the below operations could likely consume system memory at least as large as the file being downloaded and/or other interesting CPU side effects.
fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012 Original jQuery/iframe/Cookie based approach

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.

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

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.

19

  • 74

    I love what you built but I suspect that to get more StackOverFlow credit your answer here should contain a bit more detail. Specifically on how you solved the problem.

    – AnthonyVO

    Aug 7, 2012 at 21:58

  • 15

    it would be nice if you would mention exactly how this “plugin” gets around the limitation, rather than forcing us to go to your blog/plugin source to see it. for example, is it instead posting to an iframe? is it instead requiring the remote script to save the file and return a url to it?

    – Kevin B

    Sep 23, 2013 at 19:47


  • 4

    @asgerhallas Sure, but that’s completely useless if said link goes away.

    – Kevin B

    Oct 29, 2013 at 14:06

  • 26

    I agree, a blog is a far better place to place a lengthy description of how to use your plugin and how it works. but you could have at least gave a short overview of how this plugin solves the problem. For example, this solves the problem by having the server set a cookie and having your javascript continuously look for the cookie until it exists. Once it exists, we can assume that the download is complete. With that kind of information one could easily roll their own solution very quickly, and the answer no longer relies 100% on your blog/plugin/jquery and can be applied to other libraries.

    – Kevin B

    Oct 30, 2013 at 20:10


  • 1

    Royi, as I understand it AJAX can never support file downloads that result in a file download popup to save to disk. Have you found a way that I’m unaware of?

    Jan 13, 2014 at 16:19

249

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

You don’t need to do this through Ajax. Just use

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

9

  • 4

    Nice one…as I was struggling with handling the download file prompt and using jquery ajax..and this solution works perfectly for me ..+1

    – swapnesh

    Mar 1, 2013 at 8:49

  • 49

    Note that this requires the server to be setting a Content-Disposition header value of ‘attachment’, otherwise the browser will redirect to (and display) the response content

    – brichins

    Apr 24, 2013 at 21:45

  • 24

    Or alternatively use window.open(<url>, '_blank'); to ensure that the download won’t replace your current browser content (regardless of the Content-Disposition header).

    Aug 15, 2014 at 15:38


  • 4

    The problem with this solution is that if the operation fails/the server returns an error, your page will be redirected to the error page. To solve that use the iFrame solution

    – kofifus

    Jun 30, 2015 at 23:52

  • 17

    The real problem with this solution – question is about POST request.

    – Atomosk

    May 28, 2019 at 3:45

47

You can with HTML5

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

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

{
    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

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

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

4

  • 1

    The a.click() does not seem to work in firefox… Any idea?

    – 0x777

    Nov 14, 2017 at 18:34

  • In some browsers you might need to add the a to the dom in order for this code to work and/or remove the revokeObjectURL part: document.body.appendChild(a)

    – 0x777

    Nov 14, 2017 at 21:26

  • saved my day (and possibly a job too 🙂 ) Not a javascript expert by any measure… more java guy. However, I have no idea why a simple “createObjectURL(new Blob([atob(base64)]))” doesn’t work! It simply doesn’t, while all instinct says it must. grrr…

    Mar 21, 2019 at 19:55

  • at line var bytechars = atob(base64) it throws an error JavaScript runtime error: InvalidCharacterError. I am using Chrome Version 75.0.3770.142 but i dont know, what is wrong here.

    – Muflix

    Aug 7, 2019 at 9:23