web-dev-qa-db-fra.com

Télécharger plusieurs fichiers en une seule action

Je ne sais pas si cela est possible avec les technologies Web standard.

Je veux que l'utilisateur puisse télécharger plusieurs fichiers en une seule action. C’est-à-dire cochez les cases en regard des fichiers, puis récupérez tous les fichiers cochés.

Est-il possible - si oui, quelle stratégie de base recommandez-vous? Je sais que je peux utiliser la technologie comète pour créer des événements côté serveur qui déclenchent une réponse Http, mais j'espère qu'il existe un moyen plus simple. 

86
Ankur

HTTP ne prend pas en charge plus d’un téléchargement de fichier à la fois.

Il y a deux solutions:

  • Ouvrez x nombre de fenêtres pour lancer les téléchargements de fichiers (cela se ferait avec JavaScript)
  • solution préféréecréer un script pour compresser les fichiers
47
Jacob Relkin

Vous pouvez créer un ensemble temporaire d'iframes masqués, lancer le téléchargement via GET ou POST à l'intérieur d'eux, attendre le démarrage des téléchargements et supprimer les iframes:

<!DOCTYPE HTML>
<html>
<body>
  <button id="download">Download</button> 

  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
  <script type="text/javascript">

     $('#download').click(function() {
       download('http://nogin.info/cv.doc','http://nogin.info/cv.doc');
     });

     var download = function() {
       for(var i=0; i<arguments.length; i++) {
         var iframe = $('<iframe style="visibility: collapse;"></iframe>');
         $('body').append(iframe);
         var content = iframe[0].contentDocument;
         var form = '<form action="' + arguments[i] + '" method="GET"></form>';
         content.write(form);
         $('form', content).submit();
         setTimeout((function(iframe) {
           return function() { 
             iframe.remove(); 
           }
         })(iframe), 2000);
       }
     }      

  </script>
</body>
</html>

Ou, sans jQuery:

 function download(...urls) {
    urls.forEach(url => {
      let iframe = document.createElement('iframe');
      iframe.style.visibility = 'collapse';
      document.body.append(iframe);

      iframe.contentDocument.write(
        `<form action="${url.replace(/\"/g, '"')}" method="GET"></form>`
      );
      iframe.contentDocument.forms[0].submit();

      setTimeout(() => iframe.remove(), 2000);
    });
  }
49
Dmitry Nogin

Cette solution fonctionne sur plusieurs navigateurs et ne déclenche pas d’avertissements. Plutôt que de créer une iframe, nous créons ici un lien pour chaque fichier. Cela empêche les messages d'avertissement de surgir.

Pour gérer la partie en boucle, nous utilisons setTimeout, ce qui est nécessaire pour que cela fonctionne dans IE.

/**
 * Download a list of files.
 * @author speedplane
 */
function download_files(files) {
  function download_next(i) {
    if (i >= files.length) {
      return;
    }
    var a = document.createElement('a');
    a.href = files[i].download;
    a.target = '_parent';
    // Use a.download if available, it prevents plugins from opening.
    if ('download' in a) {
      a.download = files[i].filename;
    }
    // Add a to the doc for click to work.
    (document.body || document.documentElement).appendChild(a);
    if (a.click) {
      a.click(); // The click method is supported by most browsers.
    } else {
      $(a).click(); // Backup using jquery
    }
    // Delete the temporary link.
    a.parentNode.removeChild(a);
    // Download the next file with a small timeout. The timeout is necessary
    // for IE, which will otherwise only download the first file.
    setTimeout(function() {
      download_next(i + 1);
    }, 500);
  }
  // Initiate the first download.
  download_next(0);
}
<script>
  // Here's a live example that downloads three test text files:
  function do_dl() {
    download_files([
      { download: "http://www.nt.az/reg.txt", filename: "regs.txt" },
      { download: "https://www.w3.org/TR/PNG/iso_8859-1.txt", filename: "standards.txt" },
      { download: "http://qiime.org/_static/Examples/File_Formats/Example_Mapping_File.txt", filename: "example.txt" },
    ]);
  };
</script>
<button onclick="do_dl();">Test downloading 3 text files.</button>

27
speedplane

Le moyen le plus simple serait de servir les multiples fichiers regroupés dans un fichier Zip.

Je suppose que vous pouvez lancer plusieurs téléchargements de fichiers à l’aide de plusieurs iframes ou popups, mais du point de vue de la convivialité, un fichier Zip est toujours préférable. Qui veut cliquer sur dix dialogues "Enregistrer sous" que le navigateur va afficher?

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


}]);

exemple de travail: https://plnkr.co/edit/XynXRS7c742JPfCA3IpE?p=preview

3
suresh

Une version jQuery de l'iframe répond:

function download(files) {
    $.each(files, function(key, value) {
        $('<iframe></iframe>')
            .hide()
            .attr('src', value)
            .appendTo($('body'))
            .load(function() {
                var that = this;
                setTimeout(function() {
                    $(that).remove();
                }, 100);
            });
    });
}
3
Yirmiyahu Fischer

Je suis d'accord pour dire qu'un fichier Zip est une solution plus simple ... Mais si vous devez envoyer plusieurs fichiers, voici la solution que j'ai proposée. Il fonctionne dans les versions IE 9 et supérieures (éventuellement une version inférieure - je ne l’ai pas testée), Firefox, Safari et Chrome. Chrome affiche un message à l'intention de l'utilisateur pour obtenir son consentement à télécharger plusieurs fichiers lors de la première utilisation de votre site.

function deleteIframe (iframe) {
    iframe.remove(); 
}
function createIFrame (fileURL) {
    var iframe = $('<iframe style="display:none"></iframe>');
    iframe[0].src= fileURL;
    $('body').append(iframe);
    timeout(deleteIframe, 60000, iframe);             
 }
 // This function allows to pass parameters to the function in a timeout that are 
 // frozen and that works in IE9
 function timeout(func, time) {
      var args = [];
      if (arguments.length >2) {
           args = Array.prototype.slice.call(arguments, 2);
      }
      return setTimeout(function(){ return func.apply(null, args); }, time);
 }
 // IE will process only the first one if we put no delay
 var wait = (isIE ? 1000 : 0);
 for (var i = 0; i < files.length; i++) {  
      timeout(createIFrame, wait*i, files[i]);
 }

Le seul effet secondaire de cette technique est que l'utilisateur verra un délai entre l'envoi et l'affichage de la boîte de dialogue de téléchargement. Pour minimiser cet effet, je vous suggère d'utiliser la technique suivante: ici et sur cette question Détecter lorsque le navigateur reçoit le téléchargement d'un fichier consistant à définir un cookie avec votre fichier pour savoir qu'il a commencé le téléchargement. Vous devrez vérifier ce cookie côté client et l'envoyer côté serveur. N'oubliez pas de définir le chemin correct pour votre cookie, sinon vous risquez de ne pas le voir. Vous devrez également adapter la solution pour le téléchargement de plusieurs fichiers.

2
Melanie

Pour améliorer la réponse de @Dmitry Nogin: cela a fonctionné dans mon cas. 

Cependant, ce n'est pas testé, car je ne suis pas sûr de savoir comment fonctionne le dialogue de fichier sur différentes combinaisons système d'exploitation/navigateur. (Ainsi le wiki de la communauté.)

<script>
$('#download').click(function () {
    download(['http://www.arcelormittal.com/ostrava/doc/cv.doc', 
              'http://www.arcelormittal.com/ostrava/doc/cv.doc']);
});

var download = function (ar) {
    var prevfun=function(){};
    ar.forEach(function(address) {  
        var pp=prevfun;
        var fun=function() {
                var iframe = $('<iframe style="visibility: collapse;"></iframe>');
                $('body').append(iframe);
                var content = iframe[0].contentDocument;
                var form = '<form action="' + address + '" method="POST"></form>';
                content.write(form);
                $(form).submit();
                setTimeout(function() {    
                    $(document).one('mousemove', function() { //<--slightly hacky!
                        iframe.remove();
                        pp();
                    });
                },2000);
        }
        prevfun=fun; 
      });
      prevfun();   
}
</script>
1
Karel Bílek

Voici comment je le fais. J'ouvre plusieurs fichiers Zip mais aussi d'autres types de données (j'exporte le projet au format PDF et simultanément de nombreux fichiers ZIP avec document).

Je viens de copier une partie de mon code… .. l'appel d'un bouton dans une liste:

$url_pdf = "pdf.php?id=7";
$url_Zip1 = "Zip.php?id=8";
$url_Zip2 = "Zip.php?id=9";
$btn_pdf = "<a href=\"javascript:;\" onClick=\"return open_multiple('','".$url_pdf.",".$url_Zip1.",".$url_Zip2."');\">\n";
$btn_pdf .= "<img src=\"../../../images/icones/pdf.png\" alt=\"Ver\">\n";
$btn_pdf .= "</a>\n"

Donc, un appel de base à une routine JS (Vanilla rules!). voici la routine JS:

 function open_multiple(base,url_publication)
 {
     // URL of pages to open are coma separated
     tab_url = url_publication.split(",");
     var nb = tab_url.length;
     // Loop against URL    
     for (var x = 0; x < nb; x++)
     {
        window.open(tab_url[x]);
      }

     // Base is the dest of the caller page as
     // sometimes I need it to refresh
     if (base != "")
      {
        window.location.href  = base;
      }
   }

L'astuce consiste à ne PAS donner le lien direct du fichier Zip mais à l'envoyer au navigateur. Comme ça:

  $type_mime = "application/Zip, application/x-compressed-Zip";
 $the_mime = "Content-type: ".$type_mime;
 $tdoc_size = filesize ($the_Zip_path);
 $the_length = "Content-Length: " . $tdoc_size;
 $tdoc_nom = "Pesquisa.Zip";
 $the_content_disposition = "Content-Disposition: attachment; filename=\"".$tdoc_nom."\"";

  header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
  header("Expires: Sat, 26 Jul 1997 05:00:00 GMT");   // Date in the past
  header($the_mime);
  header($the_length);
  header($the_content_disposition);

  // Clear the cache or some "sh..." will be added
  ob_clean();
  flush();
  readfile($the_Zip_path);
  exit();
0
Peter
           <p class="style1">



<a onclick="downloadAll(window.links)">Balance Sheet Year 2014-2015</a>

</p>

<script>
 var links = [
  'pdfs/IMG.pdf',
  'pdfs/IMG_0001.pdf',
 'pdfs/IMG_0002.pdf',
 'pdfs/IMG_0003.pdf',
'pdfs/IMG_0004.pdf',
'pdfs/IMG_0005.pdf',
 'pdfs/IMG_0006.pdf'

];

function downloadAll(urls) {
  var link = document.createElement('a');

  link.setAttribute('download','Balance Sheet Year 2014-2015');
  link.style.display = 'none';

  document.body.appendChild(link);

  for (var i = 0; i < urls.length; i++) {
    link.setAttribute('href', urls[i]);
    link.click();
  }

  document.body.removeChild(link);
}
</script>
0
user5598894

Je cherche une solution pour le faire, mais décompresser les fichiers en javascript n'était pas aussi propre que je l'aimais. J'ai décidé d'encapsuler les fichiers dans un seul fichier SVG. 

Si vous avez les fichiers stockés sur le serveur (ce que je ne fais pas), vous pouvez simplement définir le href dans le SVG.

Dans mon cas, je vais convertir les fichiers en base64 et les intégrer au SVG.

Edit: Le SVG a très bien fonctionné. Si vous ne voulez télécharger que les fichiers, Zip sera peut-être mieux. Si vous voulez afficher les fichiers, SVG semble supérieur.

0
VectorVortec

Ci-dessous le code 100% de travail. 

Étape 1 : coller le code ci-dessous dans index.html file

<!DOCTYPE html>
<html ng-app="ang">

<head>
    <title>Angular Test</title>
    <meta charset="utf-8" />
</head>

<body>
    <div ng-controller="myController">
        <button ng-click="files()">Download All</button>
    </div>

    <script src="angular.min.js"></script>
    <script src="index.js"></script>
</body>

</html>

Étape 2 : coller le code ci-dessous dans index.js fichier

"use strict";

var x = angular.module('ang', []);

    x.controller('myController', function ($scope, $http) {
        var arr = [
            {file:"http://localhost/angularProject/w3logo.jpg", fileName: "imageone"},
            {file:"http://localhost/angularProject/cv.doc", fileName: "imagetwo"},
            {file:"http://localhost/angularProject/91.png", fileName: "imagethree"}
        ];

        $scope.files = function() {
            angular.forEach(arr, function(val, key) {
                $http.get(val.file)
                .then(function onSuccess(response) {
                    console.log('res', response);
                    var link = document.createElement('a');
                    link.setAttribute('download', val.fileName);
                    link.setAttribute('href', val.file);
                    link.style.display = 'none';
                    document.body.appendChild(link);
                    link.click(); 
                    document.body.removeChild(link);
                })
                .catch(function onError(error) {
                    console.log('error', error);
                })
            })
        };
    });

NOTE: Assurez-vous que les trois fichiers à télécharger seront placés dans le même dossier avec les fichiers angularProject/index.html ou angularProject/index.js.

0
solanki...

Obtenir la liste des url avec un appel ajax puis utiliser plugin jquery pour télécharger plusieurs fichiers en parallèle.

  $.ajax({
        type: "POST",
        url: URL,
        contentType: "application/json; charset=utf-8",
        dataType: "json",
        data: data,
        async: true,
        cache: false,
        beforeSend: function () {
            blockUI("body");
        },
        complete: function () { unblockUI("body"); },
        success: function (data) {
           //here data --> contains list of urls with comma seperated
            var listUrls= data.DownloadFilePaths.split(',');
            listUrls.forEach(function (url) {
                $.fileDownload(url);
            });
            return false; 
        },
        error: function (result) {
            $('#mdlNoDataExist').modal('show');
        }

    });
0
Ali

Lorsque vous utilisez des composants Ajax, il est possible de lancer plusieurs téléchargements. Vous devez donc utiliser https://cwiki.Apache.org/confluence/display/WICKET/AJAX+update+and+file+download+in+one+blow

Ajouter une instance de AJAXDownload à votre page ou autre. Créez un AjaxButton et remplacez onSubmit. Créez un AbstractAjaxTimerBehavior et démarrez le téléchargement.

        button = new AjaxButton("button2") {

        private static final long serialVersionUID = 1L;

        @Override
        protected void onSubmit(AjaxRequestTarget target, Form<?> form)
        {
            MultiSitePage.this.info(this);
            target.add(form);

            form.add(new AbstractAjaxTimerBehavior(Duration.milliseconds(1)) {

                @Override
                protected void onTimer(AjaxRequestTarget target) {
                    download.initiate(target);
                }

            });     
        }

Bon téléchargement!

0
stritzi

Cela fonctionne dans tous les navigateurs (IE11, Firefox, Edge, Chrome et Chrome Mobile). Mes documents se trouvent dans plusieurs éléments sélectionnés. Les navigateurs semblent avoir des problèmes lorsque vous essayez de le faire trop rapidement ... J'ai donc utilisé un délai d'attente.

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

C'est une solution qui utilise des promesses:

 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);
    });
0
Zach Painter