télécharger le fichier à l'aide d'une requête ajax

95

Je veux envoyer une "demande de téléchargement ajax" lorsque je clique sur un bouton, j'ai donc essayé de cette manière:

javascript:

var xhr = new XMLHttpRequest();
xhr.open("GET", "download.php");
xhr.send();

download.php:

<?
header("Cache-Control: public");
header("Content-Description: File Transfer");
header("Content-Disposition: attachment; filename= file.txt");
header("Content-Transfer-Encoding: binary");    
readfile("file.txt");
?>

mais ne fonctionne pas comme prévu, comment puis-je faire? Merci d'avance

Manuel Di Iorio
la source
2
Cela ne fonctionnera pas, voir [cette question] [1]. [1]: stackoverflow.com/questions/8771342/…
olegsv
2
Dolocation.href='download.php';
Musa

Réponses:

92

Mise à jour du 27 avril 2015

L' attribut de téléchargement est à venir dans la scène HTML5 . Il est pris en charge dans Firefox et Chrome, et sera bientôt disponible sur IE11. Selon vos besoins, vous pouvez l'utiliser à la place d'une requête AJAX (ou en utilisantwindow.location ) tant que le fichier que vous souhaitez télécharger est sur la même origine que votre site.

Vous pouvez toujours faire la demande AJAX / window.locationune solution de secours en utilisant du JavaScript pour tester si elle downloadest prise en charge et si ce n'est pas le cas, en la basculant en appel window.location.

Réponse originale

Une requête AJAX ne peut pas ouvrir l'invite de téléchargement car vous devez physiquement accéder au fichier pour demander le téléchargement. Au lieu de cela, vous pouvez utiliser une fonction de réussite pour accéder à download.php. Cela ouvrira l'invite de téléchargement mais ne changera pas la page actuelle.

$.ajax({
    url: 'download.php',
    type: 'POST',
    success: function() {
        window.location = 'download.php';
    }
});

Même si cela répond à la question, il est préférable d'utiliser window.locationet d'éviter complètement la requête AJAX.

Steven Lambert
la source
39
Cela n'appelle-t-il pas le lien deux fois? Je suis dans un bateau similaire ... Je passe beaucoup d'informations de sécurité dans les en-têtes et je suis capable d'analyser l'objet fichier dans la fonction de réussite, mais je ne sais pas comment déclencher une invite de téléchargement.
user1447679
3
Il appelle la page deux fois, donc si vous interrogez une base de données dans cette page, cela signifie 2 voyages vers DB.
mmmmmm
1
@ user1447679 voir pour une solution alternative: stackoverflow.com/questions/38665947/…
John
1
Laissez-moi vous expliquer comment cela m'a aidé ... l'exemple aurait pu être plus complet. avec "download.php? get_file = true" ou quelque chose ... J'ai une fonction ajax qui vérifie les erreurs sur une soumission de formulaire et crée ensuite un fichier csv. Si la vérification des erreurs échoue, il doit revenir en expliquant pourquoi elle a échoué. S'il crée le CSV, il dit au parent "allez-y et récupérez le fichier". Je le fais en publiant dans le fichier ajax avec la variable form, puis en publiant un paramètre DIFFÉRENT dans le même fichier en disant "donnez-moi le fichier que vous venez de créer" (le chemin / nom est codé en dur dans le fichier ajax).
Scott
4
Mais il enverra la demande 2 fois, ce n'est pas correct
Dharmendrasinh Chudasama
44

Pour que le navigateur télécharge un fichier, vous devez faire la demande comme ça:

 function downloadFile(urlToSend) {
     var req = new XMLHttpRequest();
     req.open("GET", urlToSend, true);
     req.responseType = "blob";
     req.onload = function (event) {
         var blob = req.response;
         var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
         var link=document.createElement('a');
         link.href=window.URL.createObjectURL(blob);
         link.download=fileName;
         link.click();
     };

     req.send();
 }
João Marcos
la source
7
Cela fonctionne pour moi, mais dans Firefox, je devais d'abord mettre une balise <a> dans le DOM, et la référencer comme mon lien plutôt que d'en créer une à la volée pour que le fichier se télécharge automatiquement.
Erik Donohoo
fonctionne mais que se passe-t-il si le fichier est en cours de création pendant l'exécution? cela ne fonctionne pas comme lorsque le fichier est déjà créé.
Diego
@Taha J'ai testé cela sur Edge et cela a semblé fonctionner. Je ne sais pas pour IE cependant. Mon client ne cible pas les utilisateurs d'IE ;-) Suis-je chanceux? : D
Sнаđошƒаӽ
1
@ErikDonohoo Vous pouvez également créer la <a>balise avec JS, "à la volée", mais vous devez l'ajouter document.body. Vous voulez certainement le cacher en même temps.
Márton Tamás
Merci @ João, je recherchais cette solution depuis très longtemps.
Abhishek Gharai le
43

En fait, vous n'avez pas du tout besoin d'ajax pour cela. Si vous définissez simplement "download.php" comme href sur le bouton, ou, s'il ne s'agit pas d'un lien, utilisez:

window.location = 'download.php';

Le navigateur doit reconnaître le téléchargement binaire et ne pas charger la page réelle, mais simplement servir le fichier en tant que téléchargement.

Jelle Kralt
la source
3
Le langage de programmation que vous utilisez pour changer window.location est JavaScript.
mikemaccana
1
Vous avez raison @mikemaccana, je voulais en fait dire ajax :).
Jelle Kralt
2
J'ai cherché une solution haut et bas et c'est tellement élégant et parfait. Merci beaucoup.
Yangshun Tay
2
Bien sûr, cette solution ne fonctionnera que s'il s'agit d'un fichier statique qui existe déjà.
krillgar le
1
Si le serveur répond par une erreur, il n'y aura aucun moyen de rester sur votre page principale sans être redirigé vers une page d'erreur par le navigateur. Au moins, c'est ce que fait Chrome lorsque le résultat de window.location renvoie 404.
Ian
20

Solution multi-navigateurs, testée sur Chrome, Firefox, Edge, IE11.

Dans le DOM, ajoutez une balise de lien caché:

<a id="target" style="display: none"></a>

Ensuite:

var req = new XMLHttpRequest();
req.open("GET", downloadUrl, true);
req.responseType = "blob";
req.setRequestHeader('my-custom-header', 'custom-value'); // adding some headers (if needed)

req.onload = function (event) {
  var blob = req.response;
  var fileName = null;
  var contentType = req.getResponseHeader("content-type");

  // IE/EDGE seems not returning some response header
  if (req.getResponseHeader("content-disposition")) {
    var contentDisposition = req.getResponseHeader("content-disposition");
    fileName = contentDisposition.substring(contentDisposition.indexOf("=")+1);
  } else {
    fileName = "unnamed." + contentType.substring(contentType.indexOf("/")+1);
  }

  if (window.navigator.msSaveOrOpenBlob) {
    // Internet Explorer
    window.navigator.msSaveOrOpenBlob(new Blob([blob], {type: contentType}), fileName);
  } else {
    var el = document.getElementById("target");
    el.href = window.URL.createObjectURL(blob);
    el.download = fileName;
    el.click();
  }
};
req.send();
Leo
la source
Bon code générique. @leo pouvez-vous améliorer ce code en ajoutant des en-têtes personnalisés comme Authorization?
vibs2006
1
Merci @leo. C'est utile. Que suggérez-vous d'ajouter window.URL.revokeObjectURL(el.href);après el.click()?
vibs2006
Le nom de fichier sera incorrect si la disposition du contenu spécifie un nom de fichier non UTF8.
Mathew Alden
14

C'est possible. Vous pouvez démarrer le téléchargement depuis une fonction ajax, par exemple juste après la création du fichier .csv.

J'ai une fonction ajax qui exporte une base de données de contacts vers un fichier .csv, et juste après la fin, il démarre automatiquement le téléchargement du fichier .csv. Donc, après avoir reçu le responseText et que tout va bien, je redirige le navigateur comme ceci:

window.location="download.php?filename=export.csv";

Mon fichier download.php ressemble à ceci:

<?php

    $file = $_GET['filename'];

    header("Cache-Control: public");
    header("Content-Description: File Transfer");
    header("Content-Disposition: attachment; filename=".$file."");
    header("Content-Transfer-Encoding: binary");
    header("Content-Type: binary/octet-stream");
    readfile($file);

?>

Il n'y a aucune actualisation de la page et le téléchargement du fichier démarre automatiquement.

REMARQUE - Testé dans les navigateurs suivants:

Chrome v37.0.2062.120 
Firefox v32.0.1
Opera v12.17
Internet Explorer v11
Pedro Sousa
la source
1
N'est-ce pas dangereux pour la sécurité?
Mickael Bergeron Néron
@ MickaelBergeronNéron Pourquoi?
Pedro Sousa
5
Je le penserais parce que n'importe qui peut appeler download.php? Filename = [quelque chose] et essayer des noms de chemins et de fichiers, en particulier les plus courants, et cela pourrait même être fait dans une boucle dans un programme ou un script.
Mickael Bergeron Néron
Est-ce que .htaccess éviterait cela?
Pedro Sousa
9
@PedroSousa .. non. htaccess contrôle l'accès à la structure de fichiers via Apache. Puisque l'accès a atteint un script PHP, htaccess arrête maintenant son devoir. C'est TRÈS une faille de sécurité car en effet, tout fichier que PHP (et l'utilisateur sous lequel il est exécuté) peut lire, donc il peut livrer dans le fichier de lecture ... Il faut toujours nettoyer le fichier demandé à lire
Prof
0

Décoder un nom de fichier à partir de l'en-tête est un peu plus complexe ...

    var filename = "default.pdf";
    var disposition = req.getResponseHeader('Content-Disposition');

    if (disposition && disposition.indexOf('attachment') !== -1) 
    {
       var filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
       var matches = filenameRegex.exec(disposition);

       if (matches != null && matches[1]) 
           filename = matches[1].replace(/['"]/g, '');
    }
Lumic
la source
3
Veuillez formater l'intégralité de votre bloc de code et fournir des explications supplémentaires sur votre processus pour les futurs lecteurs.
mickmackusa
0

Cette solution n'est pas très différente de celles ci-dessus, mais pour moi cela fonctionne très bien et je pense que c'est propre.

Je suggère d'encoder en base64 le côté serveur de fichiers (base64_encode (), si vous utilisez PHP) et d'envoyer les données encodées en base64 au client

Sur le client, vous faites ceci:

 let blob = this.dataURItoBlob(THE_MIME_TYPE + "," + response.file);
 let uri = URL.createObjectURL(blob);
 let link = document.createElement("a");
 link.download = THE_FILE_NAME,
 link.href = uri;
 document.body.appendChild(link);
 link.click();
 document.body.removeChild(link);

Ce code met les données encodées dans un lien et simule un clic sur le lien, puis il le supprime.

martinéthyl
la source
Vous pouvez simplement masquer la balise a et remplir le href dynamiquement. pas besoin d'ajouter et de supprimer
BPeela
0

Vos besoins sont couverts par window.location('download.php');
Mais je pense que vous devez transmettre le fichier à télécharger, pas toujours télécharger le même fichier, et c'est pourquoi vous utilisez une requête, une option est de créer un fichier php aussi simple que showfile.php et faire une demande comme

var myfile = filetodownload.txt
var url = "shofile.php?file=" + myfile ;
ajaxRequest.open("GET", url, true);

showfile.php

<?php
$file = $_GET["file"] 
echo $file;

où fichier est le nom de fichier passé via Get ou Post dans la requête, puis capture la réponse dans une fonction simplement

if(ajaxRequest.readyState == 4){
                        var file = ajaxRequest.responseText;
                        window.location = 'downfile.php?file=' + file;  
                    }
                }
lisandro
la source
0

il existe une autre solution pour télécharger une page web en ajax. Mais je fais référence à une page qui doit d'abord être traitée puis téléchargée.

Vous devez d'abord séparer le traitement de la page du téléchargement des résultats.

1) Seuls les calculs de page sont effectués dans l'appel ajax.

$ .post ("CalculusPage.php", {calculusFunction: true, ID: 29, data1: "a", data2: "b"},

       fonction (données, état) 
       {
            if (status == "success") 
            {
                / * 2) Dans la réponse, la page qui utilise les calculs précédents est téléchargée. Par exemple, il peut s'agir d'une page qui imprime les résultats d'une table calculée dans l'appel ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Par exemple: dans le CalculusPage.php

    if (! vide ($ _ POST ["calculusFunction"])) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "INSERT INTO ExamplePage (data1, data2) VALUES ('". $ _ POST ["data1"]. "', '". $ _ POST ["data2"]. "') WHERE id =". $ ID;
        ...
    }

// Par exemple: dans le DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "SELECT * FROM ExamplePage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    header ("Content-Type: application / vnd.ms-excel");
    header ("Content-Disposition: inline; filename = $ filename");

    ...

J'espère que cette solution pourra être utile pour beaucoup, comme elle l'était pour moi.

netluke
la source
0

Pour ceux qui recherchent une approche plus moderne, vous pouvez utiliser le fetch API. L'exemple suivant montre comment télécharger un fichier de feuille de calcul. Cela se fait facilement avec le code suivant.

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

Je pense que cette approche est beaucoup plus facile à comprendre que d’autres XMLHttpRequestsolutions. En outre, il a une syntaxe similaire à l' jQueryapproche, sans qu'il soit nécessaire d'ajouter des bibliothèques supplémentaires.

Bien sûr, je vous conseillerais de vérifier dans quel navigateur vous développez, car cette nouvelle approche ne fonctionnera pas sur IE. Vous pouvez trouver la liste complète de compatibilité du navigateur sur le lien suivant .

Important : dans cet exemple, j'envoie une requête JSON à un serveur écoutant sur le donné url. Cela urldoit être défini, sur mon exemple, je suppose que vous connaissez cette partie. Tenez également compte des en-têtes nécessaires pour que votre demande fonctionne. Puisque j'envoie un JSON, je dois ajouter l'en- Content-Typetête et le définir sur application/json; charset=utf-8, afin d'indiquer au serveur le type de demande qu'il recevra.

Alain Cruz
la source
0

La solution @Joao Marcos fonctionne pour moi mais j'ai dû modifier le code pour le faire fonctionner sur IE, ci-dessous si le code ressemble

       downloadFile(url,filename) {
        var that = this;
        const extension =  url.split('/').pop().split('?')[0].split('.').pop();

        var req = new XMLHttpRequest();
        req.open("GET", url, true);
        req.responseType = "blob";
        req.onload = function (event) {
            const fileName = `${filename}.${extension}`;
            const blob = req.response;

            if (window.navigator.msSaveBlob) { // IE
                window.navigator.msSaveOrOpenBlob(blob, fileName);
            } 
            const link = document.createElement('a');
            link.href = window.URL.createObjectURL(blob);                
            link.download = fileName;
            link.click();
            URL.revokeObjectURL(link.href);

        };

        req.send();
    },
Jemil Oyebisi
la source