61

So I have a httpd server running which has links to a bunch of files. Lets say the user selects three files from a file list to download and they're located at:

mysite.com/file1 
mysite.com/file2
mysite.com/file3

When they click the download button I want them to download these three files from the links above.

My download button looks something like:

var downloadButton = new Ext.Button({
  text: "Download",
  handler: function(){
    //download the three files here
  }
});
Grammin
  • 11,808
  • 22
  • 80
  • 138
  • 1
    zip sucks if you're on, say, a phone. you can just trigger all three downloads, and chrome even recognizes this and prompts the use if they want to allow your site to "download multiple files". if you want to use zip, you can ajax in the files, zip them using jszip, and then download the zip file from the resulting JS tree object. – dandavis Aug 26 '13 at 20:20
  • What is the best way to trigger the downloads? window.open("mysite.com/file1")? – Grammin Aug 26 '13 at 20:23
  • i like using js methods (ex danml.com/js/download.js) when available, or save, which takes dataURLs. I don't typically download files though, more string generated by JS, so take my advice with a grain of salt – dandavis Aug 26 '13 at 20:27
  • A **Chrome ONLY** API, [File System Access](https://github.com/WICG/file-system-access) API , is newly added for multiple file (streaming) download. – tsh Aug 23 '21 at 09:01
  • Does this answer your question? [Download multiple files with a single action](https://stackoverflow.com/questions/2339440/download-multiple-files-with-a-single-action) – Flimm Nov 02 '21 at 12:25

13 Answers13

41

The best way to do this is to have your files zipped and link to that:

The other solution can be found here: How to make a link open multiple pages when clicked

Which states the following:

HTML:

<a href="#" class="yourlink">Download</a>

JS:

$('a.yourlink').click(function(e) {
    e.preventDefault();
    window.open('mysite.com/file1');
    window.open('mysite.com/file2');
    window.open('mysite.com/file3');
});

Having said this, I would still go with zipping the file, as this implementation requires JavaScript and can also sometimes be blocked as popups.

Community
  • 1
  • 1
T J
  • 659
  • 1
  • 8
  • 18
  • So I don't have access to the preventDefault() function is there anyway I can wait for a user to do an action on the first file before opening the second? What is happening is it goes straight to the third file and I can't download the first two. – Grammin Aug 26 '13 at 20:38
  • What kind of action are you talking about? Because im not sure you can track that. – T J Aug 26 '13 at 20:52
  • My call is window.open('mysite.com/file1', '_parent'); which overrides all the other windows (which doesn't work because I need to wait for a user interaction on each window). The reason why im using _self is because I don't want to switch windows, I want to stay on the parent window. – Grammin Aug 26 '13 at 20:52
  • Then its best you create 2 pages: mysite.com/file1.html and mysite.com/file2.html. When each loads, you can start the file download for that page. And require the user interaction. Once the interaction has been verified then load the next page. (im not sure if i explained that correctly) – T J Aug 26 '13 at 20:55
31

This was the method which worked best for me and didn't open up new tabs, but just downloaded the files/images I required:

var filesForDownload = [];
filesForDownload( { path: "/path/file1.txt", name: "file1.txt" } );
filesForDownload( { path: "/path/file2.jpg", name: "file2.jpg" } );
filesForDownload( { path: "/path/file3.png", name: "file3.png" } );
filesForDownload( { path: "/path/file4.txt", name: "file4.txt" } );

$jq('input.downloadAll').click( function( e )
{
    e.preventDefault();

    var temporaryDownloadLink = document.createElement("a");
    temporaryDownloadLink.style.display = 'none';

    document.body.appendChild( temporaryDownloadLink );

    for( var n = 0; n < filesForDownload.length; n++ )
    {
        var download = filesForDownload[n];
        temporaryDownloadLink.setAttribute( 'href', download.path );
        temporaryDownloadLink.setAttribute( 'download', download.name );

        temporaryDownloadLink.click();
    }

    document.body.removeChild( temporaryDownloadLink );
} );
Dan
  • 1,513
  • 1
  • 24
  • 34
  • 4
    I've just been using this code @Dan and I've had problem in chrome where it ignores fast clicking, so had to set a timeout. – Iain M Norman May 10 '18 at 09:56
  • @IainMNorman interesting issue, a timeout seems a good enough solution, not sure how else it could be resolved – Dan May 11 '18 at 11:08
  • 9
    Just a FYI to others reading this: The download attribute won't work for cross-origin requests (at least with the latest version of Chrome). I think it used to but was patched out – georaldc Aug 10 '18 at 22:38
  • 3
    ```temporaryDownloadLink.setAttribute('href', 'data:application/octet-stream,' + encodeURIComponent( download.path)); ``` Necessary to force the browser to download the image instead of opening it in a new tab. – JasperJ Aug 29 '21 at 13:50
  • 1
    You invoke an array O_o. Could it be that `.push` is missing on the array initialization lines? – Klesun Feb 19 '22 at 16:13
  • this is the one I am using but if more then 10 files it is skipping them and downloading only 10 files – Sharad Feb 21 '23 at 08:38
28

I fond that executing click() event on a element inside a for loop for multiple files download works only for limited number of files (10 files in my case). The only reason that would explain this behavior that made sense to me, was speed/intervals of downloads executed by click() events.

I figure out that, if I slow down execution of click() event, then I will be able to downloads all files.

This is solution that worked for me.

var urls = [
  'http://example.com/file1',
  'http://example.com/file2',
  'http://example.com/file3'
]

var interval = setInterval(download, 300, urls);

function download(urls) {
  var url = urls.pop();

  var a = document.createElement("a");
  a.setAttribute('href', url);
  a.setAttribute('download', '');
  a.setAttribute('target', '_blank');
  a.click();

  if (urls.length == 0) {
    clearInterval(interval);
  }
}

I execute download event click() every 300ms. When there is no more files to download urls.length == 0 then, I execute clearInterval on interval function to stop downloads.

Lukasz Dynowski
  • 11,169
  • 9
  • 81
  • 124
  • 4
    I know that executing code on time intervals basis should be avoidable, but sometimes you gotta do what you gotta do. – Lukasz Dynowski Jan 15 '19 at 14:14
  • I think 10 was the max number of connections configured in your browser. – CodeWriter23 Jan 07 '21 at 16:39
  • @CodeWriter23 might be so. Can you provide any link(s) where one can set `max number of connections` in Chrome and Firefox ? – Lukasz Dynowski Jan 08 '21 at 08:08
  • Lukasz: not under program control. The best answer I have is Chrome has the limit hardcoded, and Firefox it is in about:config under *network.http.max-persistent-connections-per-server* – CodeWriter23 Jan 10 '21 at 01:50
5

You can either:

  1. Zip the selected files and return the one zipped file.
  2. Open multiple pop-ups, each prompting for a download.

Note: Option one is objectively better.

And an option three: Download multiple files with a single action

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Matthew
  • 9,851
  • 4
  • 46
  • 77
3

I've solved this a different way by using window.location. It works in Chrome, which fortunately is the only browser I had to support. Might be useful to someone. I'd initally used Dan's answer, which also needed the timeout I've used here or it only downloaded one file.

var linkArray = [];
linkArray.push("http://example.com/downloadablefile1");
linkArray.push("http://example.com/downloadablefile2");
linkArray.push("http://example.com/downloadablefile3");    

function (linkArray) {
  for (var i = 0; i < linkArray.length; i++) { 
    setTimeout(function (path) { window.location = path; }, 200 + i * 200, linkArray[i]);
  }        
};
Iain M Norman
  • 2,075
  • 15
  • 30
  • This also works on Firefox, and reportedly also on Edge, but I have no interest in testing that myself (Edge is migrating to Blink themselves anyway - good riddance). – aross Mar 21 '19 at 16:29
1

Use:

<!DOCTYPE html>
<html ng-app='app'>
    <head>
        <title>
        </title>
        <link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
        <link rel="stylesheet" href="style.css">
    </head>
    <body ng-cloack>
        <div class="container" ng-controller='FirstCtrl'>
          <table class="table table-bordered table-downloads">
            <thead>
              <tr>
                <th>Select</th>
                <th>File name</th>
                <th>Downloads</th>
              </tr>
            </thead>
            <tbody>
              <tr ng-repeat = 'tableData in tableDatas'>
                <td>
                    <div class="checkbox">
                      <input type="checkbox" name="{{tableData.name}}" id="{{tableData.name}}" value="{{tableData.name}}" ng-model= 'tableData.checked' ng-change="selected()">
                    </div>
                </td>
                <td>{{tableData.fileName}}</td>
                <td>
                    <a target="_self" id="download-{{tableData.name}}" ng-href="{{tableData.filePath}}" class="btn btn-success pull-right downloadable" download>download</a>
                </td>
              </tr>
            </tbody>
          </table>
            <a class="btn btn-success pull-right" ng-click='downloadAll()'>download selected</a>

            <p>{{selectedone}}</p>
        </div>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
        <script src="script.js"></script>
    </body>
</html>

File app.js

var app = angular.module('app', []);
app.controller('FirstCtrl', ['$scope','$http', '$filter', function($scope, $http, $filter){

$scope.tableDatas = [
    {name: 'value1', fileName:'file1', filePath: 'data/file1.txt', selected: true},
    {name: 'value2', fileName:'file2', filePath: 'data/file2.txt', selected: true},
    {name: 'value3', fileName:'file3', filePath: 'data/file3.txt', selected: false},
    {name: 'value4', fileName:'file4', filePath: 'data/file4.txt', selected: true},
    {name: 'value5', fileName:'file5', filePath: 'data/file5.txt', selected: true},
    {name: 'value6', fileName:'file6', filePath: 'data/file6.txt', selected: false},
  ];
$scope.application = [];

$scope.selected = function() {
    $scope.application = $filter('filter')($scope.tableDatas, {
      checked: true
    });
}

$scope.downloadAll = function(){
    $scope.selectedone = [];
    angular.forEach($scope.application,function(val){
       $scope.selectedone.push(val.name);
       $scope.id = val.name;
       angular.element('#'+val.name).closest('tr').find('.downloadable')[0].click();
    });
}

}]);

Plunker example: https://plnkr.co/edit/XynXRS7c742JPfCA3IpE?p=preview

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
suresh
  • 220
  • 2
  • 5
1

This works in all browsers (IE11, Firefox, Microsoft Edge, Chrome and Chrome Mobile) My documents are in multiple select elements. The browsers seem to have issues when you try to do it too fast... So I used a timeout.

<select class="document">
    <option val="word.docx">some word document</option>
</select>

//user clicks a download button to download all selected documents
    $('#downloadDocumentsButton').click(function () {
        var interval = 1000;
        //select elements have class name of "document"
        $('.document').each(function (index, element) {
            var doc = $(element).val();
            if (doc) {
                setTimeout(function () {
                    window.location = doc;
                }, interval * (index + 1));
            }
        });
    });

This solution uses promises:

function downloadDocs(docs) {
    docs[0].then(function (result) {
        if (result.web) {
            window.open(result.doc);
        }
        else {
            window.location = result.doc;
        }
        if (docs.length > 1) {
            setTimeout(function () { return downloadDocs(docs.slice(1)); }, 2000);
        }
    });
}

 $('#downloadDocumentsButton').click(function () {
    var files = [];
    $('.document').each(function (index, element) {
        var doc = $(element).val();
        var ext = doc.split('.')[doc.split('.').length - 1];

        if (doc && $.inArray(ext, docTypes) > -1) {
            files.unshift(Promise.resolve({ doc: doc, web: false }));
        }
        else if (doc && ($.inArray(ext, webTypes) > -1 || ext.includes('?'))) {
            files.push(Promise.resolve({ doc: doc, web: true }));
        }
    });

    downloadDocs(files);
});
Badasahog
  • 579
  • 2
  • 19
Zach Painter
  • 180
  • 1
  • 9
1

This is the easiest way I have found to download multiple files.

$('body').on('click','.download_btn',function(){
    downloadFiles([
        ['File1.pdf', 'File1-link-here'],
        ['File2.pdf', 'File2-link-here'],
        ['File3.pdf', 'File3-link-here'],
        ['File4.pdf', 'File4-link-here']
    ]);
})
function downloadFiles(files){
    if(files.length == 0){
        return;
    }
    file = files.pop();
    var Link = $('body').append('<a href="'+file[1]+'" download="file[0]"></a>');
    Link[0].click();
    Link.remove();
    downloadFiles(files);
}

This should work for you.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

You could go for the iframe.

Note: though this is one of the few ways to download multiple files at once, without creating pop-up, this won't work with files that can be rendered in browser. Check the JavaScript code comments.

Note 2: some browsers may ask for permission to download multiple files from the same page

function download(){
  const links = ["mysite.com/file1", "mysite.com/file2", "mysite.com/file3"]
  // It only works with files that don't render in the browser
  // I.e., not video, not text, not and photo
  for(let i = 0; i < links.length; i++) {
    var frame = document.createElement("iframe");
    frame.src = links[i];
    frame["download"] = 1
    document.body.appendChild(frame);
  }
}
iframe{
  display: none;
}
<body>
  <div onclick="download()">Download</div>
</body>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ratul Hasan
  • 544
  • 6
  • 14
0

This solution works perfectly fine for me:

var downloadButton = new Ext.Button({
    text: "Download",
    handler: function () {

        /** @type {Array<string>} URLS */
        const URLS = [
            "mysite.com/file1 ",
            "mysite.com/file2",
            "mysite.com/file3",
        ];

        for (let x = 0; x < URLS.length; x++) {

            /** @type {string} URL */
            const URL = URLS[x];

            /** @type {HTMLLinkElement} LINK */
            const LINK = document.createElement('a');

            LINK.href = URL;
            LINK.setAttribute('download', 'download');

            document.body.appendChild(LINK);

            LINK.click();
            LINK.parentNode.removeChild(LINK);

            window.URL.revokeObjectURL(URL);
        }
    }
});
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Champignon
  • 37
  • 4
  • Even if it works, it is still a code dump. An explanation would be in order. E.g., what is the idea/gist? From [the Help Center](https://stackoverflow.com/help/promotion): *"...always explain why the solution you're presenting is appropriate and how it works"*. Please respond by [editing (changing) your answer](https://stackoverflow.com/posts/72605783/edit), not here in comments (but *** *** *** *** *** *** *** *** *** *** *** *** ***[without](https://meta.stackexchange.com/a/131011)*** *** *** *** "Edit:", "Update:", or similar - the answer should appear as if it was written today). – Peter Mortensen Jul 03 '23 at 15:01
0

Another possibility is using the downloads.download() API. Which is however not supported by Safari.

await Promise.all(
  browser.downloads.download({url : downloadUrl1}),
  browser.downloads.download({url : downloadUrl2})
);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Steven
  • 2,050
  • 23
  • 20
0

downloadAll(links) {
  const urls = links;
  for (let link of urls) {
    window.open(link, '_blank');
  }
}

This code defines a downloadAll function that takes an array of links as a parameter. It then iterates over each link and opens it in a new browser tab using window.open(link, '_blank'). This will trigger the file download for each link.

However, please note that to successfully download files using this method, the user's browser settings must allow pop-up windows. If pop-up blocking is enabled, the files may not be downloaded as intended.

Keep in mind that this solution relies on the browser's default behavior for opening new tabs and triggering file downloads. Different browsers may handle this differently, and there's no guarantee that all files will be downloaded simultaneously. It's also worth noting that if you're trying to download a large number of files, it may cause performance issues or exceed browser limitations.

Pramod Ukkali
  • 261
  • 2
  • 2
-2

The code you provided is an HTML document with JavaScript/jQuery code that attempts to download a file when the "Download" link is clicked. However, there are a couple of issues with the code that need to be addressed.

First, it seems that you are using jQuery syntax ($('a.yourlink')) but you haven't included the jQuery library in your HTML document. To use jQuery, you need to include the jQuery library by adding the following line within the <head> section of your HTML:

<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

Secondly, the window.open() function opens a new browser window or tab with the specified URL, but it doesn't initiate a file download. If you want to download a file, you can create an anchor element dynamically and simulate a click event on it. Here's an updated version of your code:

<!DOCTYPE html>
<html>

<head>
    <title>Download File Using JavaScript/jQuery</title>
    <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>

<body>

<a href="#" class="yourlink">Download</a>

<script>
    $('a.yourlink').click(function(e) {
        e.preventDefault();
        var fileUrl = 'http://192.168.1.175/python-script/files-new.zip';
        var fileName = 'files-new.zip';

        var link = document.createElement('a');
        link.href = fileUrl;
        link.download = fileName;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    });
</script>
</body>
</html>

In the updated code, when the "Download" link is clicked, the click event handler prevents the default behavior (navigating to the URL) using e.preventDefault(). Then, it creates a new anchor element (<a>) dynamically and sets the href attribute to the file URL and the download attribute to the desired file name. It appends the anchor element to the document body, triggers a click event on it using link.click(), and finally removes the anchor element from the document.

This approach triggers a file download in the browser when the link is clicked, using the specified URL and file name.

  • 2
    Many of your answers here, including several which you've made Community Wikis for some reason, appear likely to have been entirely or partially written by AI (e.g., ChatGPT). Please be aware that [posting AI-generated content is not allowed here](//meta.stackoverflow.com/q/421831). If you used an AI tool to assist with any answer, I would encourage you to delete it. – NotTheDr01ds Jul 03 '23 at 00:29
  • 1
    **Readers should review this answer carefully and critically, as AI-generated information often contains fundamental errors and misinformation.** If you observe quality issues and/or have reason to believe that this answer was generated by AI, please leave feedback accordingly. The moderation team can use your help to identify quality issues. – NotTheDr01ds Jul 03 '23 at 00:30