download file using an ajax request
Asked Answered
R

14

117

I want to send an "ajax download request" when I click on a button, so I tried in this way:

javascript:

var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();

download.php:

<?
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename= file.txt");
header("Content-Transfer-Encoding: binary");    
readfile("file.txt");
?>

but doesn't work as expected, how can I do ? Thank you in advance

Ringed answered 29/12, 2013 at 21:35 Comment(4)
This will not work, see [this question][1]. [1]: #8771842Hemiterpene
Do location.href='download.php';Pentastich
try this https://mcmap.net/q/42089/-show-a-quot-please-wait-quot-message-or-a-progress-bar-while-files-downloadMammary
When you need this, it does feel like it's a common thing to ask for, and sadly with no elegant solutions.Cardew
T
105

Update April 27, 2015

Up and coming to the HTML5 scene is the download attribute. It's supported in Firefox and Chrome, and soon to come to IE11. Depending on your needs, you could use it instead of an AJAX request (or using window.location) so long as the file you want to download is on the same origin as your site.

You could always make the AJAX request/window.location a fallback by using some JavaScript to test if download is supported and if not, switching it to call window.location.

Original answer

You can't have an AJAX request open the download prompt since you physically have to navigate to the file to prompt for download. Instead, you could use a success function to navigate to download.php. This will open the download prompt but won't change the current page.

$.ajax({
    url: 'download.php',
    type: 'POST',
    success: function() {
        window.location = 'download.php';
    }
});

Even though this answers the question, it's better to just use window.location and avoid the AJAX request entirely.

Tiffany answered 29/12, 2013 at 21:39 Comment(9)
As @ManuelDiIorio said, a simple window.location resolves the question. So I think the reply from Jelle Kralt below answers better the question.Islander
Doesn't this call the link twice? I'm in a similar boat... I'm passing a lot of security information in headers, and able to parse the file object in the success function, but don't know how to trigger a download prompt.Brookins
It does call the page twice, so if you are querying a database in that page, this means 2 trips to DB.Reeding
@Brookins see for an alternative solution: #38666447Elliellicott
Let me explain how this helped me... the example could have been more complete. with "download.php?get_file=true" or something... I have an ajax function that does some error checking on a form submission and then creates a csv file. If the error check fails, it has to come back with why it failed. If it creates the CSV it is telling the parent that "go ahead and fetch the file". I do that by posting to the ajax file with the form variable then posting a DIFFERENT parameter to the same file saying "hand me the file you just created" (path/name is hard coded into the ajax file).Scissure
But it will send request 2 times, that is not properRambow
So I see some comments where there is complaint that this is not useful but that is not always the case. Consider an S3 presigned url with customer provided keys on encryption. You still have to provide custom headers on the get to download the object so simply setting window.locaion = signedurl will not work you need to send the encryption key and other info in custom AWS specific headers so this is worthy of an upvote.Pitfall
This is a bad idea. it call the url twice. It makes no sense at all. Why dont you execulte window.location = 'download.php' without the ajax overhead?Cand
Several years later and its still not supported in IE11, sad times.Asteroid
S
62

To make the browser downloads a file you need 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();
 }
Swamy answered 15/3, 2017 at 16:49 Comment(8)
This works for me, but in firefox, I needed to first put an <a> tag in the DOM, and reference it as my link rather than create one on the fly in order for the file to download automatically.Koa
works but what happens if the file is creating during execution? it doesnt work like when the file is created already.Orchardist
@Taha I tested this on Edge and it seemed to work. Don't know about IE though. My client doesn't target IE users ;-) Am I lucky? :DRelieve
@ErikDonohoo You can create the <a> tag with JS, "on the fly" as well, but gotta append it to document.body. You certainly want to hide it at the same time.Autosuggestion
Thanks @João, I was looking for this solution from a very long time.Fachini
Great example! The advantage of this approach is that one can react to the completion of the request. Minor detail: I think it is more common to have the file name as part of Content-Disposition response header.Unending
Remember to add: link.remove() after link.click()Restrain
If you want to download text files remember do this: var blob = new Blob([req.responseText], { type: 'text/csv', })Restrain
F
48

You actually don't need ajax at all for this. If you just set "download.php" as the href on the button, or, if it's not a link use:

window.location = 'download.php';

The browser should recognise the binary download and not load the actual page but just serve the file as a download.

Fonda answered 29/12, 2013 at 21:46 Comment(6)
The programming language you're using to change window.location is JavaScript.Patricapatrice
You're right @mikemaccana, I actually meant ajax :).Fonda
Have been hunting high and low for a solution and this is so elegant and perfect. Thank you so much.Philipps
Of course, this solution will only work if it is a static file that already exists.Uttica
If the server responds with an error though there won't be any way to stay on your main page without being redirected to an error page by the browser. At least this is what Chrome does when the result of window.location returns 404.Eventempered
wow I overthought it. I just added my file name as a GET param and bingo. Thanks!Wellmannered
H
22

Cross browser solution, tested on Chrome, Firefox, Edge, IE11.

In the DOM, add an hidden link tag:

<a id="target" style="display: none"></a>

Then:

var req = new XMLHttpRequest();
req.open("GET", downloadUrl, true);
req.responseType = "blob";
req.setRequestHeader('my-custom-header', 'custom-value'); // adding some headers (if needed)

req.onload = function (event) {
  var blob = req.response;
  var fileName = null;
  var contentType = req.getResponseHeader("content-type");

  // IE/EDGE seems not returning some response header
  if (req.getResponseHeader("content-disposition")) {
    var contentDisposition = req.getResponseHeader("content-disposition");
    fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
  } else {
    fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
  }

  if (window.navigator.msSaveOrOpenBlob) {
    // Internet Explorer
    window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
  } else {
    var el = document.getElementById("target");
    el.href = window.URL.createObjectURL(blob);
    el.download = fileName;
    el.click();
  }
};
req.send();
Headed answered 5/4, 2018 at 14:5 Comment(3)
Good Generic Code. @Headed can you improve this code by adding custom headers like Authorization ?Lonnalonnard
Thanks @leo. Its helpful.Also what do you suggest addingwindow.URL.revokeObjectURL(el.href); after el.click() ?Lonnalonnard
The filename will be wrong if the content disposition specifies a non-UTF8 filename.Malenamalet
D
15

It is possible. You can have the download started from inside an ajax function, for example, just after the .csv file is created.

I have an ajax function that exports a database of contacts to a .csv file, and just after it finishes, it automatically starts the .csv file download. So, after I get the responseText and everything is Ok, I redirect browser like this:

window.location="download.php?filename=export.csv";

My download.php file looks like this:

<?php

    $file = $_GET['filename'];

    header("Cache-Control: public");
    header("Content-Description: File Transfer");
    header("Content-Disposition: attachment; filename=".$file."");
    header("Content-Transfer-Encoding: binary");
    header("Content-Type: binary/octet-stream");
    readfile($file);

?>

There is no page refresh whatsoever and the file automatically starts downloading.

NOTE - Tested in the following browsers:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Darden answered 16/9, 2014 at 10:53 Comment(7)
Isn't this dangerous security-wise?Parament
@MickaelBergeronNéron Why?Darden
I would think so because anybody can call download.php?filename=[something] and try some path and file names, especially common ones, and this could even be done inside a loop within a program or a script.Parament
wouldn't .htaccess avoid that?Darden
@PedroSousa .. no. htaccess controls access to the file structure via Apache. Since the access has reached a PHP script, htaccess now stops its duty. This IS VERY MUCH a security hole because indeed, any file that PHP (and the user it is being run under) can read, so it can deliver into the readfile... One should always sanitise the requested file to be readHali
@prof83 I guess you are right. For the sake of security, we should always sanitise files to be requested. Thanks for poiting it out, i will definitely have that into account in the future. Thanks :)Darden
Downloading period is dangerous. Using a token system or some sort of download management is required for all answers here to keep them safe. These worries are valid for every one of them that doesn't have the security/management.Gudrun
V
4

I prefer location.assign(url);

Complete syntax example:

document.location.assign('https://www.urltodocument.com/document.pdf');

developer.mozilla.org/en-US/docs/Web/API/Location.assign

Vanden answered 13/1, 2015 at 10:11 Comment(0)
S
4

For those looking a more modern approach, you can use the fetch API. The following example shows how to download a spreadsheet file. It is easily done with the following code.

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

I believe this approach to be much easier to understand than other XMLHttpRequest solutions. Also, it has a similar syntax to the jQuery approach, without the need to add any additional libraries.

Of course, I would advise checking to which browser you are developing, since this new approach won't work on IE. You can find the full browser compatibility list on the following link.

Important: In this example I am sending a JSON request to a server listening on the given url. This url must be set, on my example I am assuming you know this part. Also, consider the headers needed for your request to work. Since I am sending a JSON, I must add the Content-Type header and set it to application/json; charset=utf-8, as to let the server know the type of request it will receive.

Shovel answered 19/4, 2020 at 23:50 Comment(0)
F
2

@Joao Marcos solution works for me but I had to modify the code to make it work on IE, below if what the code looks like

       downloadFile(url,filename) {
        var that = this;
        const extension =  url.split('/').pop().split('?')[0].split('.').pop();

        var req = new XMLHttpRequest();
        req.open("GET", url, true);
        req.responseType = "blob";
        req.onload = function (event) {
            const fileName = `${filename}.${extension}`;
            const blob = req.response;

            if (window.navigator.msSaveBlob) { // IE
                window.navigator.msSaveOrOpenBlob(blob, fileName);
            } 
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);                
            link.download = fileName;
            link.click();
            URL.revokeObjectURL(link.href);

        };

        req.send();
    },
Filagree answered 12/6, 2020 at 23:35 Comment(0)
L
0

Decoding a filename from the header is a little bit more complex...

    var filename = "default.pdf";
    var disposition = req.getResponseHeader('Content-Disposition');

    if (disposition && disposition.indexOf('attachment') !== -1) 
    {
       var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
       var matches = filenameRegex.exec(disposition);

       if (matches != null && matches[1]) 
           filename = matches[1].replace(/['"]/g, '');
    }
Lorrin answered 20/3, 2017 at 23:28 Comment(1)
Please format your entire code block and provide some additional explanation to your process for future reader benefit.Fenn
H
0

This solution is not very different from those above, but for me it works very well and i think it's clean.

I suggest to base64 encode the file server side (base64_encode(), if you are using PHP) and send the base64 encoded data to the client

On the client you do this:

 let blob = this.dataURItoBlob(THE_MIME_TYPE + "," + response.file);
 let uri = URL.createObjectURL(blob);
 let link = document.createElement("a");
 link.download = THE_FILE_NAME,
 link.href = uri;
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);

This code puts the encoded data in a link and simulates a click on the link, then it removes it.

Huynh answered 3/12, 2018 at 12:33 Comment(1)
You can just make the a tag hidden and populate the href dynamically. no need to add and removeCave
E
0

Your needs are covered by window.location('download.php');
But I think that you need to pass the file to be downloaded, not always download the same file, and that's why you are using a request, one option is to create a php file as simple as showfile.php and do a request like

var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);

showfile.php

<?php
$file = $_GET["file"] 
echo $file;

where file is the file name passed via Get or Post in the request and then catch the response in a function simply

if(ajaxRequest.readyState == 4){
                        var file = ajaxRequest.responseText;
                        window.location = 'downfile.php?file=' + file;  
                    }
                }
Excurrent answered 12/4, 2019 at 15:58 Comment(0)
R
0

there is another solution to download a web page in ajax. But I am referring to a page that must first be processed and then downloaded.

First you need to separate the page processing from the results download.

1) Only the page calculations are made in the ajax call.

$.post("CalculusPage.php", { calculusFunction: true, ID: 29, data1: "a", data2: "b" },

       function(data, status) 
       {
            if (status == "success") 
            {
                /* 2) In the answer the page that uses the previous calculations is downloaded. For example, this can be a page that prints the results of a table calculated in the ajax call. */
                window.location.href = DownloadPage.php+"?ID="+29;
            }               
       }
);

// For example: in the CalculusPage.php

    if ( !empty($_POST["calculusFunction"]) ) 
    {
        $ID = $_POST["ID"];

        $query = "INSERT INTO ExamplePage (data1, data2) VALUES ('".$_POST["data1"]."', '".$_POST["data2"]."') WHERE id = ".$ID;
        ...
    }

// For example: in the DownloadPage.php

    $ID = $_GET["ID"];

    $sede = "SELECT * FROM ExamplePage WHERE id = ".$ID;
    ...

    $filename="Export_Data.xls";
    header("Content-Type: application/vnd.ms-excel");
    header("Content-Disposition: inline; filename=$filename");

    ...

I hope this solution can be useful for many, as it was for me.

Resht answered 21/5, 2019 at 15:21 Comment(0)
I
0

this works for me

var dataObj = {
somekey:"someValue"
}
     $.ajax({
        method: "POST",
        url: "/someController/someMethod",
        data: dataObj,
        success: function (response) {
            const blob = new Blob([response], { type: 'text/csv' });
            const downloadUrl = URL.createObjectURL(blob);
            const a = document.createElement("a");
            a.href = downloadUrl;
            a.download = "file.csv";
            document.body.appendChild(a);
            a.click();
        }
    });
Indelicacy answered 16/4, 2021 at 23:34 Comment(0)
A
0
  var req = $.ajax({
  type: 'GET',
  cache: false,
  url: "ScaricaXmlbyLink",
  data: data,
  success: function (response) {
      const blob = new Blob([response], { type: 'text/xml' });
      const downloadUrl = URL.createObjectURL(blob);
      const a = document.createElement("a");
      a.href = downloadUrl;
      a.download = "file.xml";
      document.body.appendChild(a);
      a.click();
  }

});

Acinus answered 2/11, 2023 at 16:50 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Chive

© 2022 - 2024 — McMap. All rights reserved.