Gérer le téléchargement de fichiers depuis ajax post

393

J'ai une application javascript qui envoie des requêtes ajax POST à ​​une certaine URL. La réponse peut être une chaîne JSON ou un fichier (en tant que pièce jointe). Je peux facilement détecter Content-Type et Content-Disposition dans mon appel ajax, mais une fois que je détecte que la réponse contient un fichier, comment proposer au client de le télécharger? J'ai lu un certain nombre de sujets similaires ici, mais aucun d'entre eux ne fournit la réponse que je recherche.

S'il vous plaît, s'il vous plaît, ne postez pas de réponses suggérant que je ne devrais pas utiliser ajax pour cela ou que je devrais rediriger le navigateur, car rien de tout cela n'est une option. L'utilisation d'un formulaire HTML simple n'est pas non plus une option. Ce dont j'ai besoin, c'est d'afficher une boîte de dialogue de téléchargement au client. Est-ce possible de le faire et comment?

Pavle Predic
la source
Pour ceux qui ont lu cet article, lisez cet article: stackoverflow.com/questions/20830309/…
sobhan
J'ai supprimé votre solution de la question. Vous êtes invités à le poster en tant que message de réponse ci-dessous, mais il n'appartient pas au message de question.
Martijn Pieters

Réponses:

111

Créez un formulaire, utilisez la méthode POST, soumettez le formulaire - il n'y a pas besoin d'un iframe. Lorsque la page du serveur répond à la demande, écrivez un en-tête de réponse pour le type MIME du fichier, et il présentera une boîte de dialogue de téléchargement - je l'ai fait plusieurs fois.

Vous voulez un type d'application / de téléchargement de contenu - recherchez simplement comment fournir un téléchargement pour la langue que vous utilisez.


la source
35
Comme indiqué dans la question: "L'utilisation d'un formulaire HTML brut n'est pas non plus une option."
Pavle Predic
13
Non, car l'utilisation d'un POST normal ferait naviguer le navigateur vers l'URL du POST. Je ne veux pas quitter la page. Je souhaite exécuter la demande en arrière-plan, traiter la réponse et la présenter au client.
Pavle Predic du
6
Si le serveur renvoie des en-têtes comme l'autre réponse, il s'ouvre dans une nouvelle fenêtre - je l'ai déjà fait. Il ne s'éloignerait que si votre script côté serveur
1
@PavlePredic avez-vous fini par trouver comment gérer les deux scénarios de réponse, c'est-à-dire la réponse texte JSON ou la réponse du fichier de téléchargement?
Utilisateur Web
9
La réponse n'est pas claire et la solution proposée ne fonctionne pas.
stack247
531

N'abandonnez pas si vite, car cela peut être fait (dans les navigateurs modernes) en utilisant des parties de FileAPI:

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
    if (this.status === 200) {
        var filename = "";
        var disposition = xhr.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, '');
        }
        var type = xhr.getResponseHeader('Content-Type');

        var blob;
        if (typeof File === 'function') {
            try {
                blob = new File([this.response], filename, { type: type });
            } catch (e) { /* Edge */ }
        }
        if (typeof blob === 'undefined') {
            blob = new Blob([this.response], { type: type });
        }

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

Voici l'ancienne version utilisant jQuery.ajax. Il pourrait altérer les données binaires lorsque la réponse est convertie en une chaîne d'un jeu de caractères.

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        var filename = "";
        var disposition = xhr.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, '');
        }

        var type = xhr.getResponseHeader('Content-Type');
        var blob = new Blob([response], { type: type });

        if (typeof window.navigator.msSaveBlob !== 'undefined') {
            // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed."
            window.navigator.msSaveBlob(blob, filename);
        } else {
            var URL = window.URL || window.webkitURL;
            var downloadUrl = URL.createObjectURL(blob);

            if (filename) {
                // use HTML5 a[download] attribute to specify filename
                var a = document.createElement("a");
                // safari doesn't support this yet
                if (typeof a.download === 'undefined') {
                    window.location.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});
Jonathan Amend
la source
1
Merci beaucoup ! J'ai dû ajouter 'Content-disposition' à la fois à 'Access-Control-Expose-Headers' et à 'Access-Control-Allow-Headers' dans ma réponse HTTP pour que cela fonctionne, cependant.
JulienD
1
Cela ne fonctionne pas lorsque le fichier dépasse 500 Mo. Peut-être devrions-nous utiliser une autre API?
hirra
Qu'en est-il de la suppression d'un élément du DOM dans la partie nettoyage (et pas seulement de l'URL)? document.body.removeChild(a);
Scoregraphic
@hirra utilise responceType "blob" au lieu de "arraybuffer" et réécrit la fonction de rappel onload de cette façon, ce var blob est this.response (var blob = this.response;) doncvar blob =this.responce; /** if (typeof File === 'function') { try { blob = new File([this.response], filename, { type: type }); } catch (e) { /* Edge */ } } if (typeof blob === 'undefined') { blob = new Blob([this.response], { type: type }); } */
Chris Tobba
1
C'est une solution parfaite. Juste un petit changement. Dans Typescript, j'avais besoin window.location.href = downloadUrlau lieu dewindow.location = downloadUrl
michal.jakubeczy
39

J'ai fait face au même problème et l'ai résolu avec succès. Mon cas d'utilisation est le suivant.

" Publiez les données JSON sur le serveur et recevez un fichier Excel. Ce fichier Excel est créé par le serveur et renvoyé comme réponse au client. Téléchargez cette réponse sous forme de fichier avec un nom personnalisé dans le navigateur "

$("#my-button").on("click", function(){

// Data to post
data = {
    ids: [1, 2, 3, 4, 5]
};

// Use XMLHttpRequest instead of Jquery $ajax
xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    var a;
    if (xhttp.readyState === 4 && xhttp.status === 200) {
        // Trick for making downloadable link
        a = document.createElement('a');
        a.href = window.URL.createObjectURL(xhttp.response);
        // Give filename you wish to download
        a.download = "test-file.xls";
        a.style.display = 'none';
        document.body.appendChild(a);
        a.click();
    }
};
// Post data to URL which handles post request
xhttp.open("POST", excelDownloadUrl);
xhttp.setRequestHeader("Content-Type", "application/json");
// You should set responseType as blob for binary responses
xhttp.responseType = 'blob';
xhttp.send(JSON.stringify(data));
});

L'extrait ci-dessus ne fait que suivre

  • Publication d'un tableau au format JSON sur le serveur à l'aide de XMLHttpRequest.
  • Après avoir récupéré le contenu en tant qu'objet blob (binaire), nous créons une URL téléchargeable et l'attachons à un lien "a" invisible, puis en cliquant dessus.

Ici, nous devons soigneusement définir quelques éléments côté serveur. J'ai défini quelques en-têtes dans Python Django HttpResponse. Vous devez les définir en conséquence si vous utilisez d'autres langages de programmation.

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Depuis que je télécharge xls (excel) ici, j'ai ajusté contentType au dessus. Vous devez le définir en fonction de votre type de fichier. Vous pouvez utiliser cette technique pour télécharger tout type de fichiers.

Naren Yellavula
la source
33

Quelle langue côté serveur utilisez-vous? Dans mon application, je peux facilement télécharger un fichier à partir d'un appel AJAX en définissant les en-têtes corrects dans la réponse de PHP:

Définition des en-têtes côté serveur

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

En fait, cela «redirigera» le navigateur vers cette page de téléchargement, mais comme @ahren l'a déjà dit dans son commentaire, il ne s'éloignera pas de la page actuelle.

Il s'agit de définir les en-têtes corrects, donc je suis sûr que vous trouverez une solution appropriée pour le langage côté serveur que vous utilisez si ce n'est pas PHP.

Gestion du côté client de réponse

En supposant que vous savez déjà comment passer un appel AJAX, du côté client, vous exécutez une demande AJAX vers le serveur. Le serveur génère ensuite un lien à partir duquel ce fichier peut être téléchargé, par exemple l'URL "de transfert" vers laquelle vous souhaitez pointer. Par exemple, le serveur répond avec:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

Lors du traitement de la réponse, vous injectez un iframedans votre corps et définissez le iframeSRC de l'URL que vous venez de recevoir comme ceci (en utilisant jQuery pour la facilité de cet exemple):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

Si vous avez défini les en-têtes corrects comme indiqué ci-dessus, l'iframe forcera une boîte de dialogue de téléchargement sans éloigner le navigateur de la page actuelle.

Remarque

Ajout supplémentaire par rapport à votre question; Je pense qu'il est préférable de toujours retourner JSON lorsque vous demandez des trucs avec la technologie AJAX. Après avoir reçu la réponse JSON, vous pouvez ensuite décider côté client quoi en faire. Par exemple, vous souhaiterez peut-être plus tard que l'utilisateur clique sur un lien de téléchargement vers l'URL au lieu de forcer le téléchargement directement, dans votre configuration actuelle, vous devrez mettre à jour le client et le serveur pour ce faire.

Robin van Baalen
la source
24

Pour ceux qui recherchent une solution d'un point de vue angulaire, cela a fonctionné pour moi:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});
Tim Hettler
la source
Cela a aidé, mais je dois conserver le nom de fichier d'origine. Je vois le nom de fichier dans les en-têtes de réponse sous "Content-Disposition", mais je ne trouve pas cette valeur dans l'objet de réponse dans le code. La définition link.download = ""entraîne un nom de fichier guid aléatoire et link.download = nullun fichier nommé "null".
Marie
@Marie, vous pouvez enregistrer le nom du fichier au moment du téléchargement en utilisant la propriété de l' INPUTélément HTMLInputElement.files. Voir Les documents MDN sur l'entrée de fichier pour plus de détails.
Tim Hettler
La taille des
blobs
22

Voici comment j'ai obtenu ce fonctionnement https://stackoverflow.com/a/27563953/2845977

$.ajax({
  url: '<URL_TO_FILE>',
  success: function(data) {
    var blob=new Blob([data]);
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="<FILENAME_TO_SAVE_WITH_EXTENSION>";
    link.click();
  }
});

Réponse mise à jour à l'aide de download.js

$.ajax({
  url: '<URL_TO_FILE>',
  success: download.bind(true, "<FILENAME_TO_SAVE_WITH_EXTENSION>", "<FILE_MIME_TYPE>")
});

Mayur Padshala
la source
Merci pour cela, je viens de l'utiliser aujourd'hui. Fantastique
Ryan Wilson
Salut, dois-je avoir jQuery 3.0> pour que cela fonctionne?
gbade_
Je reçois également un pdf vierge avec les deux exemples que vous avez donnés. J'essaie de l'obtenir pour télécharger un fichier pdf.
gbade_
@gbade_ Non, vous n'avez besoin d'aucune version spécifique de jQuery. Devrait fonctionner correctement avec toutes les versions. Avez-vous vérifié si le PDF que vous téléchargez contient des en-têtes CORS corrects? Toute erreur sur la console pourrait aider au débogage
Mayur Padshala
Cela a fonctionné pour moi en utilisant download.js:success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Sga
12

Je vois que vous avez déjà trouvé une solution, mais je voulais juste ajouter quelques informations qui pourraient aider quelqu'un à essayer de faire la même chose avec de grandes demandes POST.

J'ai eu le même problème il y a quelques semaines, en effet, il n'est pas possible de réaliser un téléchargement "propre" via AJAX, le Filament Group a créé un plugin jQuery qui fonctionne exactement comme vous l'avez déjà découvert, il s'appelle jQuery File Téléchargez cependant il y a un inconvénient à cette technique.

Si vous envoyez de grosses demandes via AJAX (par exemple, des fichiers + 1 Mo), cela aura un impact négatif sur la réactivité. Dans les connexions Internet lentes, vous devrez attendre beaucoup jusqu'à ce que la demande soit envoyée et également attendre le téléchargement du fichier. Ce n'est pas comme un "clic" instantané => "popup" => "début de téléchargement". Cela ressemble plus à "cliquer" => "attendre que les données soient envoyées" => "attendre la réponse" => "démarrer le téléchargement" ce qui fait apparaître le fichier deux fois sa taille car vous devrez attendre que la demande soit envoyée via AJAX et récupérez-le sous forme de fichier téléchargeable.

Si vous travaillez avec des fichiers de petite taille <1 Mo, vous ne le remarquerez pas. Mais comme je l'ai découvert dans ma propre application, pour des fichiers plus volumineux, c'est presque insupportable.

Mon application permet aux utilisateurs d'exporter des images générées dynamiquement, ces images sont envoyées via des requêtes POST au format base64 au serveur (c'est le seul moyen possible), puis traitées et renvoyées aux utilisateurs sous forme de fichiers .png, .jpg, base64 les chaînes d'images + 1 Mo sont énormes, ce qui oblige les utilisateurs à attendre plus que nécessaire pour que le fichier commence à télécharger. Dans les connexions Internet lentes, cela peut être très ennuyeux.

Ma solution a été d'écrire temporairement le fichier sur le serveur, une fois qu'il est prêt, de générer dynamiquement un lien vers le fichier sous la forme d'un bouton qui change entre les états "Veuillez patienter ..." et "Télécharger" et en même temps temps, imprimez l'image base64 dans une fenêtre contextuelle d'aperçu afin que les utilisateurs puissent "cliquer avec le bouton droit" et l'enregistrer. Cela rend tout le temps d'attente plus supportable pour les utilisateurs et accélère également les choses.

Mise à jour du 30 septembre 2014:

Des mois se sont écoulés depuis que j'ai posté cela, j'ai finalement trouvé une meilleure approche pour accélérer les choses lorsque je travaille avec de grandes chaînes de base64. Je stocke maintenant des chaînes base64 dans la base de données (en utilisant des champs de texte long ou de blog long), puis je passe son ID d'enregistrement via le téléchargement de fichier jQuery, enfin sur le fichier de script de téléchargement Je recherche la base de données en utilisant cet ID pour extraire la chaîne base64 et la passer la fonction de téléchargement.

Télécharger l'exemple de script:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

Je sais que c'est bien au-delà de ce que le PO a demandé, mais j'ai pensé qu'il serait bon de mettre à jour ma réponse avec mes conclusions. Lorsque je cherchais des solutions à mon problème, j'ai lu beaucoup de threads "Télécharger à partir des données AJAX POST" qui ne m'ont pas donné la réponse que je cherchais, j'espère que ces informations aideront quelqu'un qui cherche à réaliser quelque chose comme ça.

José SAYAGO
la source
Le jQuery File Downloadseul me redirige vers l'url. Je l' appelle comme ceci: jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");.
Casper
5

Je tiens à souligner certaines difficultés qui surviennent lors de l'utilisation de la technique dans la réponse acceptée, c'est-à-dire en utilisant un formulaire de post:

  1. Vous ne pouvez pas définir d'en-têtes sur la demande. Si votre schéma d'authentification implique des en-têtes, un Json-Web-Token passé dans l'en-tête d'autorisation, vous devrez trouver un autre moyen de l'envoyer, par exemple en tant que paramètre de requête.

  2. Vous ne pouvez pas vraiment dire quand la demande est terminée. Eh bien, vous pouvez utiliser un cookie qui est défini sur la réponse, comme le fait jquery.fileDownload , mais c'est loin d'être parfait. Cela ne fonctionnera pas pour les demandes simultanées et il se cassera si une réponse n'arrive jamais.

  3. Si le serveur répond avec une erreur, l'utilisateur sera redirigé vers la page d'erreur.

  4. Vous ne pouvez utiliser que les types de contenu pris en charge par un formulaire . Ce qui signifie que vous ne pouvez pas utiliser JSON.

J'ai fini par utiliser la méthode d'enregistrement du fichier sur S3 et l'envoi d'une URL pré-signée pour obtenir le fichier.

tepez
la source
5

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 les autres XMLHttpRequestsolutions. En outre, il a une syntaxe similaire à l' jQueryapproche, sans avoir besoin d'ajouter de 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] [1] suivant.

Important : Dans cet exemple, j'envoie une demande JSON à un serveur écoutant le donné url. Cela urldoit être réglé, sur mon exemple, je suppose que vous connaissez cette partie. Tenez également compte des en-têtes nécessaires au bon fonctionnement de votre demande. Étant donné que 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
1
Impressionnant! Pour l'ouvrir dans un nouvel onglet au lieu d'une fenêtre de téléchargement: utilisez `` `const window = open (downloadUrl," _blank "); if (window! == null) window.focus (); ``
andy
Existe-t-il un moyen pour moi de le faire pour plusieurs ensembles de données? Exemple, récupérez {fileOne: data, fileTwo: data, fileThree: data} à partir de l'appel API et générez trois fichiers téléchargés à la fois? Merci!
il0v3d0g
Hmmm, je n'ai pas essayé ça. Mais vous pouvez toujours compresser les images dans un fichier zip et les télécharger. Je vérifierai si c'est possible.
Alain Cruz
4

Voici ma solution en utilisant un formulaire caché temporaire.

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

Notez que j'utilise massivement JQuery mais vous pouvez faire de même avec JS natif.

Ludovic Martin
la source
3

Comme d'autres l'ont indiqué, vous pouvez créer et soumettre un formulaire à télécharger via une demande POST. Cependant, vous n'avez pas à le faire manuellement.

Une bibliothèque vraiment simple pour faire exactement cela est jquery.redirect . Il fournit une API similaire à la jQuery.postméthode standard :

$.redirect(url, [values, [method, [target]]])
KurtPreston
la source
3

C'est une question vieille de 3 ans mais j'ai eu le même problème aujourd'hui. J'ai regardé votre solution modifiée mais je pense qu'elle peut sacrifier les performances car elle doit faire une double demande. Donc, si quelqu'un a besoin d'une autre solution qui n'implique pas d'appeler le service deux fois, voici comment je l'ai fait:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

Ce formulaire est juste utilisé pour appeler le service et éviter d'utiliser un window.location (). Après cela, il vous suffit de soumettre un formulaire à partir de jquery afin d'appeler le service et d'obtenir le fichier. C'est assez simple mais de cette façon, vous pouvez effectuer un téléchargement à l'aide d'un POST . Je pense maintenant que cela pourrait être plus facile si le service que vous appelez est un GET , mais ce n'est pas mon cas.

Jairo Miranda
la source
1
Ce n'est pas un message ajax car la question utilise ajax
Nidhin David
c'est juste une solution au problème ci-dessus, mais pas pour les appels ajax.
Nomi Ali
1

J'ai utilisé ce FileSaver.js . Dans mon cas avec des fichiers csv, je l'ai fait (en coffescript):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

Je pense que pour le cas le plus compliqué, les données doivent être traitées correctement. Sous le capot FileSaver.js met en œuvre la même approche que la réponse de Jonathan Amend .

Armando
la source
1
..mais pouvez-vous télécharger des fichiers généralement dans iOS?
Alex Marshall
1

Pour obtenir la réponse de Jonathan Amends pour travailler dans Edge, j'ai apporté les modifications suivantes:

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

pour ça

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

J'aurais préféré poster ceci en commentaire mais je n'ai pas assez de réputation pour ça

fstrandner
la source
0

Voici ma solution, issue de différentes sources: Implémentation côté serveur:

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

Implémentation côté client (à l'aide de jquery):

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   
Dvs Prajapati
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, statut) 
       {
            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 (! empty ($ _ POST ["calculusFunction"]))) 
    {
        $ ID = $ _POST ["ID"];

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

// Par exemple: dans DownloadPage.php

    $ ID = $ _GET ["ID"];

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

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

    ...

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

netluke
la source
0

Si la réponse est un tampon de tableau , essayez ceci sous l'événement onsuccess dans Ajax:

 if (event.data instanceof ArrayBuffer) {
          var binary = '';
          var bytes = new Uint8Array(event.data);
          for (var i = 0; i < bytes.byteLength; i++) {
              binary += String.fromCharCode(bytes[i])
          }
          $("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
          return;
      }
  • où event.data est la réponse reçue en fonction de réussite de l'événement xhr.
Abhishek Sinha
la source
0

Ci-dessous, ma solution pour télécharger plusieurs fichiers en fonction d'une liste composée de certains identifiants et rechercher dans la base de données, les fichiers seront déterminés et prêts à être téléchargés - s'ils existent. J'appelle une action C # MVC pour chaque fichier utilisant Ajax.

Et oui, comme d'autres l'ont dit, il est possible de le faire dans jQuery Ajax. Je l'ai fait avec le succès de l'Ajax et j'envoie toujours une réponse 200.

Voici donc la clé:

  success: function (data, textStatus, xhr) {

Et voici mon code:

var i = 0;
var max = 0;
function DownloadMultipleFiles() {
            if ($(".dataTables_scrollBody>tr.selected").length > 0) {
                var list = [];
                showPreloader();
                $(".dataTables_scrollBody>tr.selected").each(function (e) {
                    var element = $(this);
                    var orderid = element.data("orderid");
                    var iscustom = element.data("iscustom");
                    var orderlineid = element.data("orderlineid");
                    var folderPath = "";
                    var fileName = "";

                    list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
                });
                i = 0;
                max = list.length;
                DownloadFile(list);
            }
        }

Appelant ensuite:

function DownloadFile(list) {
        $.ajax({
            url: '@Url.Action("OpenFile","OrderLines")',
            type: "post",
            data: list[i],
            xhrFields: {
                responseType: 'blob'
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("RequestVerificationToken",
                    $('input:hidden[name="__RequestVerificationToken"]').val());

            },
            success: function (data, textStatus, xhr) {
                // check for a filename
                var filename = "";
                var disposition = xhr.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, '');
                    var a = document.createElement('a');
                    var url = window.URL.createObjectURL(data);
                    a.href = url;
                    a.download = filename;
                    document.body.append(a);
                    a.click();
                    a.remove();
                    window.URL.revokeObjectURL(url);
                }
                else {
                    getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
                }
                i = i + 1;
                if (i < max) {
                    DownloadFile(list);
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {

            },
            complete: function () {
                if(i===max)
                hidePreloader();
            }
        });
    }

C # MVC:

 [HttpPost]
 [ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
        {
            byte[] file = null;

            try
            {
                if (model != null)
                {
                    //code for getting file from api - part is missing here as not important for this example
                    file = apiHandler.Get<byte[]>(downloadApiUrl, token);

                    var contentDispositionHeader = new System.Net.Mime.ContentDisposition
                    {
                        Inline = true,
                        FileName = fileName
                    };
                    //    Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
                    Response.Headers.Add("Content-Type", "application/pdf");
                    Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
                    Response.Headers.Add("Content-Transfer-Encoding", "binary");
                    Response.Headers.Add("Content-Length", file.Length.ToString());

                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error getting pdf", null);
                return Ok();
            }

            return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
        }

Tant que vous renvoyez la réponse 200, le succès dans Ajax peut fonctionner avec, vous pouvez vérifier si le fichier existe réellement ou non car la ligne ci-dessous dans ce cas serait fausse et vous pouvez en informer l'utilisateur:

 if (disposition && disposition.indexOf('attachment') !== -1) {
chatouilleux
la source
0

J'avais besoin d'une solution similaire à celle de @ alain-cruz, mais en nuxt / vue avec plusieurs téléchargements. Je sais que les navigateurs bloquent plusieurs téléchargements de fichiers, et j'ai également une API qui renvoie un ensemble de données au format csv. J'allais utiliser JSZip au début, mais j'avais besoin du support IE, voici ma solution. Si quelqu'un peut m'aider à améliorer cela, ce serait formidable, mais cela fonctionne pour moi jusqu'à présent.

L'API renvoie:

data : {
  body: {
    fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
    fileTwo: ""col1", "col2"..."
  }
}

page.vue:

<template>
  <b-link @click.prevent="handleFileExport">Export<b-link>
</template>

export default = {
   data() {
     return {
       fileNames: ['fileOne', 'fileTwo'],
     }
   },
  computed: {
    ...mapState({
       fileOne: (state) => state.exportFile.fileOne,
       fileTwo: (state) => state.exportFile.fileTwo,
    }),
  },
  method: {
    handleExport() {
      //exportFileAction in store/exportFile needs to return promise
      this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
        .then(async (response) => {
           const downloadPrep = this.fileNames.map(async (fileName) => {
           // using lodash to get computed data by the file name
           const currentData = await _.get(this, `${fileName}`);
           const currentFileName = fileName;
           return { currentData, currentFileName };
         });
         const response = await Promise.all(downloadPrep);
         return response;
       })
       .then(async (data) => {
         data.forEach(({ currentData, currentFileName }) => {
           this.forceFileDownload(currentData, currentFileName);
         });
       })
       .catch(console.error);
    },
    forceFileDownload(data, fileName) {
     const url = window.URL
         .createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
     const link = document.createElement('a');
     link.href = url;
     link.setAttribute('download', `${fileName}.csv`);
     document.body.appendChild(link);
     link.click();
   },
}
il0v3d0g
la source