46

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,224
  • 22
  • 76
  • 135
  • 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

10 Answers10

33

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
  • 550
  • 1
  • 5
  • 15
  • 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
26

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,444
  • 1
  • 25
  • 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
  • 2
    ```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
  • 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
18

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
  • 8,351
  • 5
  • 64
  • 98
  • 3
    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
4

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.

Edit Found an option three: https://stackoverflow.com/a/9425731/1803682

Community
  • 1
  • 1
Matthew
  • 9,651
  • 4
  • 43
  • 75
2

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,057
  • 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
    <!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>


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

Badasahog
  • 348
  • 1
  • 18
suresh
  • 200
  • 1
  • 4
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
  • 348
  • 1
  • 18
Zach Painter
  • 152
  • 1
  • 9
0

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 be work for you. Thank You.

0

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 js comment

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"]
  // only works with files that don't render in browser
  // ie: not video, not text, not 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>
Ratul Hasan
  • 419
  • 5
  • 12
-1
//It is possible when using Tampermonkey (Firefox or Chrome).
//They added the GM_Download command.
//You can use it like this download multiple files One time:


// ==UserScript==
// @name        
// @description 
// @match       
// @grant       
// @grant       GM_download
function setup_reader(file) {
    var name = file.name;
    var reader = new FileReader();
    reader.onload = function (e) {
        var bin = e.target.result; //get file content
        var lines = bin.split('\n');
        for (var line = 0; line < lines.length; line++) {
            console.log(lines[line]);
            GM_download(lines[line], line + '.jpg');
        }
    }
    // reader.readAsBinaryString(file);
    reader.readAsText(file, 'utf-8');//
}

NoName
  • 11