[javascript] Easiest way to open a download window without navigating away from the page

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.

This question is related to javascript

The answer is


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:

_x000D_
_x000D_
function downloadFile(filePath) {_x000D_
  var link = document.createElement('a');_x000D_
  link.href = filePath;_x000D_
  link.download = filePath.substr(filePath.lastIndexOf('/') + 1);_x000D_
  link.click();_x000D_
}_x000D_
_x000D_
downloadFile("http://www.adobe.com/content/dam/Adobe/en/accessibility/pdfs/accessing-pdf-sr.pdf");
_x000D_
_x000D_
_x000D_

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, ...).

A small/hidden iframe can work for this purpose.

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


Using HTML5 Blob Object-URL File API:

/**
 * 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 downloadLink = document.createElement("a");
        downloadLink.download = fileName;
        if (window.webkitURL != null) {
            downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);
        }
        else {
            downloadLink.href = window.URL.createObjectURL(textFileAsBlob);
            downloadLink.onclick = document.body.removeChild(event.target);
            downloadLink.style.display = "none";
            document.body.appendChild(downloadLink);
        }
        downloadLink.click();
    } 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

_x000D_
_x000D_
/**_x000D_
 * Save a text as file using HTML <a> temporary element and Blob_x000D_
 * @see https://stackoverflow.com/questions/49988202/macos-webview-download-a-html5-blob-file_x000D_
 * @param fileName String_x000D_
 * @param fileContents String JSON String_x000D_
 * @author Loreto Parisi_x000D_
 */_x000D_
var saveBlobAsFile = function(fileName, fileContents) {_x000D_
  if (typeof(Blob) != 'undefined') { // using Blob_x000D_
    var textFileAsBlob = new Blob([fileContents], {_x000D_
      type: 'text/plain'_x000D_
    });_x000D_
    var downloadLink = document.createElement("a");_x000D_
    downloadLink.download = fileName;_x000D_
    if (window.webkitURL != null) {_x000D_
      downloadLink.href = window.webkitURL.createObjectURL(textFileAsBlob);_x000D_
    } else {_x000D_
      downloadLink.href = window.URL.createObjectURL(textFileAsBlob);_x000D_
      downloadLink.onclick = document.body.removeChild(event.target);_x000D_
      downloadLink.style.display = "none";_x000D_
      document.body.appendChild(downloadLink);_x000D_
    }_x000D_
    downloadLink.click();_x000D_
  } else {_x000D_
    var pp = document.createElement('a');_x000D_
    pp.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(fileContents));_x000D_
    pp.setAttribute('download', fileName);_x000D_
    pp.onclick = document.body.removeChild(event.target);_x000D_
    pp.click();_x000D_
  }_x000D_
} //saveBlobAsFile_x000D_
_x000D_
var jsonObject = {_x000D_
  "name": "John",_x000D_
  "age": 31,_x000D_
  "city": "New York"_x000D_
};_x000D_
var fileContents = JSON.stringify(jsonObject, null, 2);_x000D_
var fileName = "data.json";_x000D_
_x000D_
saveBlobAsFile(fileName, fileContents)
_x000D_
_x000D_
_x000D_


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

window.location.assign(url);

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();
}

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


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)
        }
    });

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();
});

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();

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.


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.)