Easiest way to open a download window without navigating away from the page
Asked Answered
D

16

159

What is the best cross browser way to open a download dialog (let's assume we can set content-disposion:attachment in the headers) without navigating away from the current page, or opening popups, which doesn't work well in Internet Explorer(IE) 6.

Deterrence answered 30/6, 2009 at 22:40 Comment(0)
F
145

7 years have passed and I don't know whether it works for IE6 or not, but this prompts OpenFileDialog in FF and Chrome.

var file_path = 'host/path/file.ext';
var a = document.createElement('A');
a.href = file_path;
a.download = file_path.substr(file_path.lastIndexOf('/') + 1);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
Felid answered 9/3, 2017 at 13:37 Comment(16)
First thank you for this solution, but I found a bug if removeChild(a) the zip will unzip error with zip is broken, so remove this code solve itObligation
, but A tag will more and more, so how do I check download is done?Obligation
safari has a problem with a.click();Tatiania
had to change the line var a = document.createElement('A'); to let a = document.createElement('a'); to get it to work in angular 2 typescriptShroyer
+1 There is no apparent need to add the a element to document.body for it to work as intended. Also, tried this approach in Chrome (64.0.3282.167) and Safari (11.0.3) -- worked in both cases.Lepidopteran
this is not working for firefox. how to download in firefox ?Subordinary
@Manoj Rana - I have checked on FF 58.0.2 (64-bit) it is working. It won't work on any FF if you remove 2 lines document.body.appendChild(a); document.body.removeChild(a);Felid
To make it work on Edge 16 the header, from where the file comes, should contains Content-Type: application/octet-stream and Content-Disposition: attachment.Figural
this doesnt work anymore developers.google.com/web/updates/2018/02/…Barathea
@user1933131 chrome removes only for cross-originGuild
@Figural thanks for your comment! It solved my problem for Edge 44, although I didn't need the Content-Disposition header for Edge 42.Maricruzmaridel
Great answers. Additional Question for your solution. It is possible to use a different Download-Filename ?Fraya
@Fraya you can do it on a server sideFelid
@PauliusDragunas It still works. But it does not work for cross-origin urls.Dingdong
Does not work also for untrusted(self-signed) SSLCosgrove
do you know how to unit test this with jest and Testing Library?Rasure
A
247

This javascript is nice that it doesn't open a new window or tab.

window.location.assign(url);
Aesthete answered 26/8, 2011 at 16:52 Comment(7)
This is the same as window.location = url; “Whenever a new value is assigned to the location object, a document will be loaded using the URL as if window.location.assign() had been called with the modified URL” - developer.mozilla.org/en-US/docs/Web/API/window.locationHelicograph
This causes WebSocket connection to disconnect.Araxes
I have used the same solution but it opens file in same tab instead of opening a download dialog.Misjudge
it's same to window.open(url, '_self') if the url is for download page then.Cornucopia
When using IE11 I found that this caused JS to stop. So for IE 11 I used window.open(url, '_blank') which did open another tab, however that tab closed when it worked out the file was a download. This kept the JS running.Warehouse
this is the best answer i've come across. I use typescript, axios and react and this is just what I needed. I just want to amend that I needed this code to complete the work ``` . let blob = new Blob([response.data], { type: 'text/csv' }); let link = window.URL.createObjectURL(blob); window.location.assign(link);```Seamaid
Thanks, it's smart and working perfectly, no need to create temporary <href>Discouragement
F
145

7 years have passed and I don't know whether it works for IE6 or not, but this prompts OpenFileDialog in FF and Chrome.

var file_path = 'host/path/file.ext';
var a = document.createElement('A');
a.href = file_path;
a.download = file_path.substr(file_path.lastIndexOf('/') + 1);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
Felid answered 9/3, 2017 at 13:37 Comment(16)
First thank you for this solution, but I found a bug if removeChild(a) the zip will unzip error with zip is broken, so remove this code solve itObligation
, but A tag will more and more, so how do I check download is done?Obligation
safari has a problem with a.click();Tatiania
had to change the line var a = document.createElement('A'); to let a = document.createElement('a'); to get it to work in angular 2 typescriptShroyer
+1 There is no apparent need to add the a element to document.body for it to work as intended. Also, tried this approach in Chrome (64.0.3282.167) and Safari (11.0.3) -- worked in both cases.Lepidopteran
this is not working for firefox. how to download in firefox ?Subordinary
@Manoj Rana - I have checked on FF 58.0.2 (64-bit) it is working. It won't work on any FF if you remove 2 lines document.body.appendChild(a); document.body.removeChild(a);Felid
To make it work on Edge 16 the header, from where the file comes, should contains Content-Type: application/octet-stream and Content-Disposition: attachment.Figural
this doesnt work anymore developers.google.com/web/updates/2018/02/…Barathea
@user1933131 chrome removes only for cross-originGuild
@Figural thanks for your comment! It solved my problem for Edge 44, although I didn't need the Content-Disposition header for Edge 42.Maricruzmaridel
Great answers. Additional Question for your solution. It is possible to use a different Download-Filename ?Fraya
@Fraya you can do it on a server sideFelid
@PauliusDragunas It still works. But it does not work for cross-origin urls.Dingdong
Does not work also for untrusted(self-signed) SSLCosgrove
do you know how to unit test this with jest and Testing Library?Rasure
L
51

I know the question was asked 7 years and 9 months ago but many posted solutions doesn't seem to work, for example using an <iframe> works only with FireFox and doesn't work with Chrome.

Best solution:

The best working solution to open a file download pop-up in JavaScript is to use a HTML link element, with no need to append the link element to the document.body as stated in other answers.

You can use the following function:

function downloadFile(filePath){
    var link=document.createElement('a');
    link.href = filePath;
    link.download = filePath.substr(filePath.lastIndexOf('/') + 1);
    link.click();
}

In my application, I am using it this way:

downloadFile('report/xls/myCustomReport.xlsx');

Working Demo:

function downloadFile(filePath) {
  var link = document.createElement('a');
  link.href = filePath;
  link.download = filePath.substr(filePath.lastIndexOf('/') + 1);
  link.click();
}

downloadFile("http://www.adobe.com/content/dam/Adobe/en/accessibility/pdfs/accessing-pdf-sr.pdf");

Note:

  • You have to use the link.download attribute so the browser doesn't open the file in a new tab and fires the download pop-up.
  • This was tested with several file types (docx, xlsx, png, pdf, ...).
Luther answered 20/4, 2017 at 15:5 Comment(9)
What would be the best way to display a loading gif until the file has downloaded?Outofdoor
@Outofdoor Well in this case it won't be easy to track the download process, but one trick can be to show this gif animation on the link click and hide it after a timeout, using this code: link.onclick = function() { document.body.innerText = "The file is being downloaded ..."; setTimeout(function() { document.body.innerText = ""; }, 2000); }, you can see it working in this fiddle, but keep in mind that it's not a recommended way to do it, it would be better handled if we were using Ajax.Setser
it's not work in firefox. how to download in firefox ?Subordinary
@ManojRana For firefox, you can use an iframe see this answer.Setser
This solution works in Chrome, Safari and Firefox for me :)Scurrilous
this looks like the best approach for my caseGlauce
Thanks @cнŝdk! I deleted my previous comment, as I found that there are other things that PWA breaks while on the way to document.createElement(). If I clean them up, the method still works.Bag
@Bag cool, glad to hear that it works for you.Setser
Nice and simple, only works for same origin downloads: developer.chrome.com/blog/…Autobiography
H
33

I always add a target="_blank" to the download link. This will open a new window, but as soon as the user clicks save, the new window is closed.

Hypogeum answered 1/7, 2009 at 12:3 Comment(1)
This is the best answer. In Internet Explorer, adding the 'target="_blank"' to a link that is to be downloadedwill stop the browser from navigating away (where "HTML1300: Navigation occurred" is printed), and thus can leave the page in an inconsistent state.Gapeworm
I
26

Put this in the HTML head section, setting the url var to the URL of the file to be downloaded:

<script type="text/javascript">  
function startDownload()  
{  
     var url='http://server/folder/file.ext';    
     window.open(url, 'Download');  
}  
</script>

Then put this in the body, which will start the download automatically after 5 seconds:

<script type="text/javascript">  
setTimeout('startDownload()', 5000); //starts download after 5 seconds  
</script> 

(From here.)

Ivoryivorywhite answered 30/6, 2009 at 22:44 Comment(5)
that doesnt work, because in IE6, if the user clicks "save" the file is saved, but the popup stays open. This is not acceptable.Deterrence
this code is not working in safari can you help me to resolved in safari please.Thickskinned
It is opening in New Window in Browser. Is it possible to download automatically.??? ThanksCongruent
This no longer works in the year of our Lord 2022Sophistic
This worked for me but I started getting weird glitchy behavior in my debug browser when I'd attempt to open multiple files (one after the other) somewhat rapidly.Woodsman
A
19

I've been looking for a good way to use javascript to initiate the download of a file, just as this question suggests. However these answers not been helpful. I then did some xbrowser testing and have found that an iframe works best on all modern browsers IE>8.

downloadUrl = "http://example.com/download/file.zip";
var downloadFrame = document.createElement("iframe"); 
downloadFrame.setAttribute('src',downloadUrl);
downloadFrame.setAttribute('class',"screenReaderText"); 
document.body.appendChild(downloadFrame); 

class="screenReaderText" is my class to style content that is present but not viewable.

css:

.screenReaderText { 
  border: 0; 
  clip: rect(0 0 0 0); 
  height: 1px; 
  margin: -1px; 
  overflow: hidden; 
  padding: 0; 
  position: absolute; 
  width: 1px; 
}

same as .visuallyHidden in html5boilerplate

I prefer this to the javascript window.open method because if the link is broken the iframe method simply doesn't do anything as opposed to redirecting to a blank page saying the file could not be opened.

window.open(downloadUrl, 'download_window', 'toolbar=0,location=no,directories=0,status=0,scrollbars=0,resizeable=0,width=1,height=1,top=0,left=0');
window.focus();
Aglow answered 1/8, 2012 at 21:5 Comment(1)
Nice. The hiding styles seem a bit excessive. But I guess you REALLY don't want to see that frame. :)Eyelash
D
9

Using HTML5 Blob Object-URL File API:

NOTE. Please note that this will not work in StackOverflow Snippets, but will work in the browser or JSFiddle

/**
 * Save a text as file using HTML <a> temporary element and Blob
 * @see https://stackoverflow.com/questions/49988202/macos-webview-download-a-html5-blob-file
 * @param fileName String
 * @param fileContents String JSON String
 * @author Loreto Parisi
*/
var saveBlobAsFile = function(fileName, fileContents) {
    if (typeof(Blob) != 'undefined') { // using Blob
      var textFileAsBlob = new Blob([fileContents], {
        type: 'text/plain'
      });
      var url;
      var downloadLink = document.createElement("a");
      downloadLink.download = fileName;
      if (window.webkitURL != null) {
        url = window.webkitURL.createObjectURL(textFileAsBlob);
        downloadLink.href = url
        console.log(downloadLink.href)
      } else {
        url = window.URL.createObjectURL(textFileAsBlob);
        downloadLink.href = url
        //downloadLink.onclick = document.body.removeChild(event.target);
        downloadLink.style.display = "none";
        document.body.appendChild(downloadLink);
      }
      downloadLink.click();
      window.URL.revokeObjectURL(url);
    } else {
      var pp = document.createElement('a');
      pp.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileContents));
      pp.setAttribute('download', fileName);
      pp.onclick = document.body.removeChild(event.target);
      pp.click();
    }
  } //saveBlobAsFile

<script>
  /**
   * Save a text as file using HTML <a> temporary element and Blob
   * @see https://stackoverflow.com/questions/49988202/macos-webview-download-a-html5-blob-file
   * @param fileName String
   * @param fileContents String JSON String
   * @author Loreto Parisi
   */
  var saveBlobAsFile = function(fileName, fileContents) {
    if (typeof(Blob) != 'undefined') { // using Blob
      var textFileAsBlob = new Blob([fileContents], {
        type: 'text/plain'
      });
      var url;
      var downloadLink = document.createElement("a");
      downloadLink.download = fileName;
      if (window.webkitURL != null) {
        url = window.webkitURL.createObjectURL(textFileAsBlob);
        downloadLink.href = url
        console.log(downloadLink.href)
      } else {
        url = window.URL.createObjectURL(textFileAsBlob);
        downloadLink.href = url
        //downloadLink.onclick = document.body.removeChild(event.target);
        downloadLink.style.display = "none";
        document.body.appendChild(downloadLink);
      }
      downloadLink.click();
      window.URL.revokeObjectURL(url);
    } else {
      var pp = document.createElement('a');
      pp.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileContents));
      pp.setAttribute('download', fileName);
      pp.onclick = document.body.removeChild(event.target);
      pp.click();
    }
  } //saveBlobAsFile

  function download() {
    console.log("download");
    var jsonObject = {
      "name": "John",
      "age": 31,
      "city": "New York"
    };
    var fileContents = JSON.stringify(jsonObject, null, 2);
    var fileName = "data.json";

    saveBlobAsFile(fileName, fileContents);
    return false;
  }
</script>
<button id="download" onclick="download()">Download JSON</button>
Dialogize answered 24/4, 2018 at 8:35 Comment(4)
I feel this is the best way to do it !!Congest
It is also a good practice to call URL.revokeObjectURL(url), when the file is no longer needed to free up the memoryPeart
This method is really only appropriate for smallish files, as it's likely that the entire file will sit in-memory.Stinker
@Stinker that's definitively correct, the blob will sit in the Browser's memory. It should be resident in the memory reserved to the Tab when the code is running, hence it should be offloaded when the tab is in background, but as soon as that Tab is in foreground the memory will be loaded back.Dialogize
S
6

Modifying the location of window might cause some issue especially when you have a persistent connection like websocket. So I always resort to good old iframe solution.

HTML

<input type="button" onclick="downloadButtonClicked()" value="Download"/>
...
...
...
<iframe style="display:none;" name="hiddenIframe" id="hiddenIframe"></iframe>

Javascript

function downloadButtonClicked() {
    // Simulate a link click
    var url = 'your_download_url_here';
    var elem = document.createElement('a');
    elem.href = url;
    elem.target = 'hiddenIframe';
    elem.click();
}
Shonna answered 2/11, 2016 at 3:49 Comment(0)
D
5

If the link is to a valid file url, simply assigning window.location.href will work.

However, sometimes the link is not valid, and an iFrame is required.

Do your normal event.preventDefault to prevent the window from opening, and if you are using jQuery, this will work:

$('<iframe>').attr('src', downloadThing.attr('href')).appendTo('body').on("load", function() {
   $(this).remove();
});
Devaluate answered 5/8, 2013 at 18:19 Comment(0)
S
5

Best solution as per new chrome specification https://developers.google.com/web/updates/2018/02/chrome-65-deprecations

Vanilla JavaScript

public static downloadFile(url: string): void {
     const xmlHttp = new XMLHttpRequest();
     xmlHttp.onreadystatechange = () => {
       if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
         const blobUrl = window.URL.createObjectURL(xmlHttp.response);
         const e = document.createElement('a');
         e.href = blobUrl;
         e.download = blobUrl.substr(blobUrl.lastIndexOf('/') + 1);
         document.body.appendChild(e);
         e.click();
         document.body.removeChild(e);
       }
     };
     xmlHttp.responseType = 'blob';
     xmlHttp.open('GET', url, true);
     xmlHttp.send(null);
   }

If you're using angular try this.

async downloadBrochure(url: string) {
    try {
      const res = await this.httpClient.get(url, { responseType: 'blob' }).toPromise();
      this.downloadFile(res);
    } catch (e) {
      console.log(e.body.message);
    }
  }

  downloadFile(data) {
    const url = window.URL.createObjectURL(data);
    const e = document.createElement('a');
    e.href = url;
    e.download = url.substr(url.lastIndexOf('/') + 1);
    document.body.appendChild(e);
    e.click();
    document.body.removeChild(e);
  }
Shooter answered 9/5, 2021 at 9:56 Comment(0)
D
3

After hours of trying, the function is born :) I had a scenario where I had to display loader in time while the file is preparing for download:

Working in Chrome, Safari and Firefox

function ajaxDownload(url, filename = 'file', method = 'get', data = {}, callbackSuccess = () => {}, callbackFail = () => {}) {
    $.ajax({
        url: url,
        method: 'GET',
        xhrFields: {
            responseType: 'blob'
        },
        success: function (data) {
            // create link element
            let a = document.createElement('a'), 
                url = window.URL.createObjectURL(data);

            // initialize 
            a.href = url;
            a.download = filename;

            // append element to the body, 
            // a must, due to Firefox
            document.body.appendChild(a);

            // trigger download
            a.click();

            // delay a bit deletion of the element
            setTimeout(function(){
                window.URL.revokeObjectURL(url);
                document.body.removeChild(a);
            }, 100);

            // invoke callback if any 
            callbackSuccess(data);
        },
        error: function (err) {
            // invoke fail callback if any
            callbackFail(err)
        }
    });
Deathly answered 13/12, 2018 at 22:52 Comment(0)
S
2

How about:

<meta http-equiv="refresh" content="5;url=http://site.com/file.ext">

This way works on all browsers (i think) and let you put a message like: "If the download doesn't start in five seconds, click here."

If you need it to be with javascript.. well...

document.write('<meta http-equiv="refresh" content="5;url=http://site.com/file.ext">');

Regards

Strontianite answered 30/6, 2009 at 23:17 Comment(1)
Wow mate, this response is brilliant and exactly what I needed. Thanks from over a decade laterUchish
C
0

the absolute easiest way to do this that i found, is to have a link with the download function. I think the support for this is decent. so you literally set your link as such:

<a href="path/to/file.ext" download="filename.ext">Click to download</a>

if you have a div or something that you want to download the file by clicking on, then have your javascript generate a confirm/cancel dialog where the confirm button is your link with the download attribute styled to be a button. I had a button to download an image on my page with some jquery that worked like this:

function getSubstring(string, char1, char2) {
  return string.slice(
    string.indexOf(char1) + 1,
    string.lastIndexOf(char2)
  );
}
$(document).on("click", ".downloadImage", function(){
    var img = $(this).closest(".content").find("img").attr("src");
    var src = getSubstring(img, '=', '&');  
    $(".downloadImage").append('<div class="imgQuickDownloader"><a href="'+src+'" class="quickDL" target="_blank" download>Download</a><div class="cancelimgQuickDownloader">Cancel</div></div>');
});
$(document).on("click", ".quickDL, .cancelimgQuickDownloader",function(e){  
    e.stopImmediatePropagation();
    e.stopPropagation()
    $(this).parent().remove();
});
button {
position:relative;
}
.content img {
width:150px;
}
.imgQuickDownloader {
    width: auto;
    background-color: #DDD;
    right: 0;
    position: absolute;
    border-radius: 4px;
    cursor:default;
    white-space: nowrap;
    padding:5px
}
.imgQuickDownloader a, .imgQuickDownloader div {
    display: inline-block;
    padding: 5px;
    background-color: #4787C6;
    color: #FFF;
    border-radius: 5px;
    margin: 5px;
    cursor:pointer;
}
.imgQuickDownloader a:hover, .imgQuickDownloader div:hover {
    background-color: #333;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="content">
<img src="https://www.w3schools.com/images/myw3schoolsimage.jpg" /> <button class="downloadImage">Download</button>
</div>
Chadburn answered 7/8, 2023 at 11:16 Comment(0)
M
0

The other methods mentioned here didn't consistently open the file in a new tab for me. Adding the target attribute as shown below fixed the problem. This may help anyone else in the future if they come across a similar issue.

var link = document.createElement("a");
link.href = res;
link.target = "_blank";
link.click();
link.remove();
Malediction answered 17/9, 2023 at 8:25 Comment(0)
B
0

I wasn't able to get any of the other answers to work without navigating to the target. I also tried using a Worker but that was inhibited by cross-origin resource sharing (CORS) protections.

My solution was to use the Fetch API as follows:

function download(uri, filename = uri) {
  return fetch(new Request(uri))
    .then(response => response.blob())
    .then(blob => {
      let objectURL = URL.createObjectURL(blob);
      let link = document.createElement("a");
      link.href = objectURL;
      link.download = filename.substr(filename.lastIndexOf('/') + 1); // no path
      link.click();
      URL.revokeObjectURL(objectURL);
    });
}

This downloads the uri in question and populates its data into a Blob. It creates a URL to that blob and creates a dummy <a> element to hold it. The download attribute is where we specify this is a download. It also defines the filename to save as, which defaults to being the provided url. The filename has its path stripped since JS can't save paths. Most answers here place the element on the page, but I've found that to be unnecessary (running Firefox 124). Then we simply click the link and delete the Blob to free up the memory it consumed.

My only issue with this is that, at least for Firefox 124, it pops open the Downloads interface to tell the user that it succeeded. This is probably wise, as the user might not otherwise know there was an auto-download, but it's a little annoying for my purposes.

Boltzmann answered 11/4 at 20:53 Comment(0)
F
-1

A small/hidden iframe can work for this purpose.

That way you don't have to worry about closing the pop up.

Figural answered 1/7, 2009 at 12:13 Comment(2)
const downloadUrl = "url"; const downloadFrame = document.getElementById('download-iframe') || document.createElement("iframe"); downloadFrame.setAttribute('src',downloadUrl); downloadFrame.setAttribute('id',"download-iframe"); downloadFrame.style.display = "none"; document.body.appendChild(downloadFrame);Streak
I tried this script but the download doesn't start for me - just loads in the iframe.Luscious

© 2022 - 2024 — McMap. All rights reserved.