Grammin
Grammin

Reputation: 12205

How can I let a user download multiple files when a button is clicked?

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

Upvotes: 70

Views: 235922

Answers (13)

Paul
Paul

Reputation: 45

The native JavaScript solution could be something like this:

document.getElementById("download-example-csv-files").onclick = function(event) {
  event.preventDefault()
  document.getElementById("download-example-csv-example").click()
  setTimeout(() => document.getElementById("download-example-csv-database").click(), 2500)
};
<p>
  <a id="download-example-csv-example" style="display: none;" href="{% static 'giftplanner–csv-example-files/Buddy_Butler_Copy-Paste-Vorlage.csv' %}"></a>
  <a id="download-example-csv-database" style="display: none;" href="{% static 'giftplanner–csv-example-files/Buddy_Butler_Muster-Datenbank.csv' %}"></a>
  <a id="download-example-csv-files" href="#">Muster-CSV Download</a>
</p>

The href I added with Django syntax. Could be common URLs of course.

Upvotes: 0

Matthew
Matthew

Reputation: 9949

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

Upvotes: 5

Nikhilesh Sharma
Nikhilesh Sharma

Reputation: 21

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.

Upvotes: 1

suresh
suresh

Reputation: 230

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

Upvotes: 1

Ratul Hasan
Ratul Hasan

Reputation: 600

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>

Upvotes: 2

Champignon
Champignon

Reputation: 67

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

Upvotes: 0

Steven
Steven

Reputation: 2275

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

Upvotes: -1

Pramod Ukkali
Pramod Ukkali

Reputation: 281

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.

Upvotes: 0

Lukasz Dynowski
Lukasz Dynowski

Reputation: 13640

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.

Upvotes: 32

Zach Painter
Zach Painter

Reputation: 198

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

Upvotes: 2

Iain M Norman
Iain M Norman

Reputation: 2085

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

Upvotes: 4

Dan
Dan

Reputation: 1571

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

Upvotes: 32

T J
T J

Reputation: 749

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.

Upvotes: 44

Related Questions