Téléchargez un fichier par jQuery.Ajax

420

J'ai une action Struts2 côté serveur pour le téléchargement de fichiers.

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

Cependant, lorsque j'appelle l'action à l'aide de jQuery:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

dans Firebug, je vois que les données sont récupérées avec le flux binaire . Je me demande comment ouvrir le fenêtre de téléchargement de fichier avec laquelle l'utilisateur peut enregistrer le fichier localement?

hguser
la source
1
Je l'ai marqué comme doublon malgré la différence de plate-forme, car pour autant que je puisse voir, la solution est la même (vous ne pouvez pas et n'avez pas besoin de le faire via Ajax).
Pekka
1
donc, sans ajax, utilisez simplement window.location = "download.action? para1 = value1 ...."?
hguser

Réponses:

676

Mise à jour des navigateurs modernes 2019

C'est l'approche que je recommanderais maintenant avec quelques mises en garde:

  • Un navigateur relativement moderne est requis
  • Si le fichier devrait être très volumineux, vous devriez probablement faire quelque chose de similaire à l'approche d'origine (iframe et cookie) car certaines des opérations ci-dessous pourraient probablement consommer de la mémoire système au moins aussi grande que le fichier en cours de téléchargement et / ou un autre processeur intéressant. Effets secondaires.

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012 Approche originale basée sur jQuery / iframe / Cookie

Bluish a tout à fait raison, vous ne pouvez pas le faire via Ajax car JavaScript ne peut pas enregistrer les fichiers directement sur l'ordinateur d'un utilisateur (par souci de sécurité). Malheureusement, pointer l' URL de la fenêtre principale vers le téléchargement de votre fichier signifie que vous avez peu de contrôle sur l'expérience utilisateur lorsqu'un téléchargement de fichier se produit.

J'ai créé jQuery File Download qui permet une expérience "Ajax like" avec des téléchargements de fichiers complets avec les rappels OnSuccess et OnFailure pour offrir une meilleure expérience utilisateur. Jetez un œil à mon article de blog sur le problème commun que le plugin résout et sur certaines façons de l'utiliser, ainsi qu'une démonstration de jQuery File Download en action . Voici la source

Voici une démo de cas d'utilisation simple utilisant la source du plugin avec des promesses. La page de démonstration comprend également de nombreux autres exemples de «meilleure expérience utilisateur».

$.fileDownload('some/file.pdf')
    .done(function () { alert('File download a success!'); })
    .fail(function () { alert('File download failed!'); });

Selon les navigateurs dont vous avez besoin pour prendre en charge, vous pourrez peut-être utiliser https://github.com/eligrey/FileSaver.js/ qui permet un contrôle plus explicite que la méthode IFRAME que jQuery File Download utilise.

John Culviner
la source
69
J'adore ce que vous avez construit, mais je pense que pour obtenir plus de crédit StackOverFlow, votre réponse ici devrait contenir un peu plus de détails. Plus précisément sur la façon dont vous avez résolu le problème.
AnthonyVO
14
ce serait bien si vous mentionniez exactement comment ce "plugin" contourne la limitation, plutôt que de nous forcer à aller sur votre blog / source de plugin pour le voir. par exemple, est-il plutôt publié dans un iframe? faut-il plutôt que le script distant enregistre le fichier et lui renvoie une URL?
Kevin B
2
@asgerhallas Bien sûr, mais c'est complètement inutile si ce lien disparaît.
Kevin B
26
Je suis d'accord, un blog est un bien meilleur endroit pour placer une longue description de la façon d'utiliser votre plugin et comment il fonctionne. mais vous auriez pu au moins donner un bref aperçu de la façon dont ce plugin résout le problème. Par exemple, cela résout le problème en demandant au serveur de définir un cookie et en demandant à votre javascript de rechercher en permanence le cookie jusqu'à ce qu'il existe. Une fois qu'il existe, nous pouvons supposer que le téléchargement est terminé. Avec ce type d'informations, on pourrait facilement lancer leur propre solution très rapidement, et la réponse ne repose plus à 100% sur votre blog / plugin / jquery et peut être appliquée à d'autres bibliothèques.
Kevin B
1
Royi, si je comprends bien, AJAX ne peut jamais prendre en charge les téléchargements de fichiers qui entraînent une fenêtre de téléchargement de fichier à enregistrer sur le disque. Avez-vous trouvé un moyen que je ne connais pas?
John Culviner
228

Personne n'a posté cette solution @ Pekka ... alors je vais la poster. Cela peut aider quelqu'un.

Vous n'avez pas besoin de le faire via Ajax. Utilisez simplement

window.location="download.action?para1=value1...."
bleuâtre
la source
4
Bien joué ... car j'avais du mal à gérer l'invite de téléchargement et à utiliser jquery ajax..et cette solution fonctionne parfaitement pour moi .. + 1
swapnesh
45
Notez que cela nécessite que le serveur définisse une valeur d'en-tête Content-Disposition de «pièce jointe», sinon le navigateur redirigera vers (et affichera) le contenu de la réponse
brichins
21
Ou utilisez également window.open(<url>, '_blank');pour vous assurer que le téléchargement ne remplacera pas le contenu de votre navigateur actuel (quel que soit l'en-tête Content-Disposition).
Christopher King
4
Le problème avec cette solution est que si l'opération échoue / que le serveur renvoie une erreur, votre page sera redirigée vers la page d'erreur. Pour résoudre ce problème, utilisez la solution iFrame
kofifus
4
Le vrai problème avec cette solution - la question concerne la POSTdemande.
Atomosk
35

Vous pouvez avec HTML5

NB: Les données de fichier renvoyées DOIVENT être codées en base64 car vous ne pouvez pas coder en JSON des données binaires

Dans ma AJAXréponse, j'ai une structure de données qui ressemble à ceci:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

Cela signifie que je peux faire ce qui suit pour enregistrer un fichier via AJAX

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

La fonction base64ToBlob a été prise à partir d' ici et doit être utilisée conformément à cette fonction

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

C'est bien si votre serveur vide les fichiers à enregistrer. Cependant, je n'ai pas tout à fait compris comment on implémenterait une solution de secours HTML4

Luke Madhanga
la source
1
Le a.click()ne semble pas fonctionner sous firefox ... Une idée?
bigpony
Dans certains navigateurs, vous devrez peut-être ajouter le aau dom pour que ce code fonctionne et / ou supprimer la revokeObjectURLpartie:document.body.appendChild(a)
bigpony
sauvé ma journée (et peut-être aussi un travail :)) Pas un expert javascript par tous les moyens ... plus java guy. Cependant, je n'ai aucune idée pourquoi un simple "createObjectURL (new Blob ([atob (base64)]))" "ne fonctionne pas! Ce n'est tout simplement pas le cas, alors que tout instinct le dit. grrr ...
apil.tamang
à la ligne, var bytechars = atob(base64)il lance une erreur JavaScript runtime error: InvalidCharacterError. J'utilise la version 75.0.3770.142 de Chrome, mais je ne sais pas ce qui ne va pas ici.
Muflix
27

1. Framework agnostique: fichier de téléchargement de servlet en pièce jointe

<!-- with JS -->
<a href="javascript:window.location='downloadServlet?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadServlet?param1=value1" >download</a>

2. Struts2 Framework: fichier de téléchargement d'actions en pièce jointe

<!-- with JS -->
<a href="javascript:window.location='downloadAction.action?param1=value1'">
    download
</a>

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

Il serait préférable d'utiliser une <s:a>balise pointant avec OGNL vers une URL créée avec une <s:url>balise:

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

Dans les cas ci-dessus, vous devez écrire l'en - tête Content-Disposition dans la réponse , en spécifiant que le fichier doit être téléchargé ( attachment) et non ouvert par le navigateur ( inline). Vous devez spécifier le type de contenu et vous pouvez ajouter le nom et la longueur du fichier (pour aider le navigateur à dessiner une barre de progression réaliste).

Par exemple, lors du téléchargement d'un ZIP:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

Avec Struts2 (sauf si vous utilisez l'action en tant que servlet, un hack pour le streaming direct , par exemple), vous n'avez pas besoin d'écrire directement quoi que ce soit dans la réponse; simplement utiliser le type de résultat Stream et le configurer dans struts.xml fonctionnera: EXEMPLE

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3. Framework agnostique (framework / Struts2): fichier d'ouverture Servlet (/ Action) dans le navigateur

Si vous souhaitez ouvrir le fichier à l'intérieur du navigateur, au lieu de le télécharger, la disposition de contenu doit être définie sur inline , mais la cible ne peut pas être l'emplacement actuel de la fenêtre; vous devez cibler une nouvelle fenêtre créée par javascript, un <iframe>dans la page, ou une nouvelle fenêtre créée à la volée avec la cible "discutée" = "_ vide":

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>
Andrea Ligios
la source
2
Monsieur, Votre contribution: "Content-Disposition", "en ligne; .... a sauvé la journée du pauvre codeur :)
Vedran Maricevic.
1
C'est la seule réponse qui mentionne "window.open" (l'un des commentaires le mentionne).
Andrew Koster
Cela ne fonctionne pas si vous avez beaucoup de paramètres, car vous obtiendrez une too long urlerreur.
Muflix
25

Le moyen simple pour que le navigateur télécharge un fichier est de 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();
 }

Cela ouvre la fenêtre de téléchargement du navigateur.

João Marcos
la source
3
Merci, j'ai utilisé cette solution. A fonctionné comme un charme. De plus, si vous n'obtenez pas d'objet blob de la réponse, créez simplement un nouvel objet blob.
fabio.sang
6
Une meilleure version avec le lien de
commenceWith_R le
Le lien de @startsWith_R aide vraiment si vous travaillez avec IE11
alexventuraio
Merci ça a marché pour moi!
Zaki Mohammed
23

J'ai créé une petite fonction comme solution de contournement (inspirée du plugin @JohnCulviner):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Démo avec événement click:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
ndpu
la source
Cela envoie les données d'une manière très étrange au serveur. Je me demande si cela pourrait être modifié pour créer un POST conforme?
Shayne
16

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 des 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. J'ai fait une demande POST ici. Au lieu de cela, vous pouvez également opter pour un GET simple. Nous ne pouvons pas télécharger le fichier via Ajax, nous devons utiliser XMLHttpRequest.

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
"Nous ne pouvons pas télécharger le fichier via Ajax, nous devons utiliser XMLHttpRequest". XMLHttpRequest est AJAX par définition. Sinon, une excellente solution pour les navigateurs Web modernes. Pour IE, qui ne prend pas en charge HTMLAnchorElement.download, je pense à le combiner avec la méthode propriétaire msSaveOrOpenBlob .
Tsahi Asher
15

Ok, basé sur le code de ndpu, voici une version améliorée (je pense) de ajax_download; -

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Utilisez ceci comme ceci; -

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

Les paramètres sont envoyés comme des paramètres de publication appropriés, comme s'ils provenaient d'une entrée plutôt que comme une chaîne codée json, comme dans l'exemple précédent.

CAVEAT: Méfiez-vous du potentiel d'injection variable sur ces formes. Il pourrait y avoir un moyen plus sûr d'encoder ces variables. Vous pouvez également envisager de leur échapper.

Shayne
la source
Ceci est un exemple de travail. Merci. Est-il possible de le faire sans iframe mais sans window.location?
Marek Bar
Je suppose que vous pouvez simplement ajouter le formulaire caché au bas du DOM. L'utilisation du dom Shadow mérite également d'être explorée, bien que ce ne soit pas nécessairement bien pris en charge sur les navigateurs plus anciens.
Shayne
Dans ce code, j'obtiens cette erreur. Uncaught SecurityError: Blocked a frame with origin "http://foo.bar.com" from accessing a frame with origin "null". The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
annulé le
Comment mapper ce formulaire à une classe de modèle? J'ai: @ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) mais ça ne marche pas ..
bartex9
vide: Ce serait probablement une sorte de problème de sécurité d'origine croisée. C'est probablement une question de débordement de pile entière en elle-même. @ bartex9: Cela dépendrait fortement du type de framework que vous utilisez. Mais le principe serait de prendre le nom et le chemin d'accès et de le stocker, tout en poussant le fichier lui-même dans une zone accessible sur le Web du système de fichiers, ou quelque chose comme amazon S3 pour une haute disponibilité
Shayne
8

Voici ce que j'ai fait, pur javascript et html. Je ne l'ai pas testé mais cela devrait fonctionner dans tous les navigateurs.

Fonction Javascript

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

En utilisant uniquement des composants pris en charge dans tous les navigateurs, aucune bibliothèque supplémentaire.

entrez la description de l'image ici entrez la description de l'image ici

Voici mon code de contrôleur JAVA Spring côté serveur.

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }
manukyanv07
la source
il semble que votre événement de chargement ne soit pas appelé pour le contenu de la pièce jointe à disposition de contenu (car rien n'est chargé dans l'iframe), s'il fonctionne pour vous (vous obtenez le console.log), veuillez publier un échantillon
kofifus
Voici un rapide violon jsfiddle.net/y2xezyoj qui déclenche le chargement de l'événement dès que le fichier pdf est chargé dans l'iframe .. ce violon ne se télécharge pas car la clé de téléchargement se trouve du côté serveur "response.setHeader (" Content -disposition "," attachment ; filename = \ "" + fileName + ".xlsx \" ");"
manukyanv07
1
oui, cela fonctionnera dans ce cas, mais si le fichier est téléchargé, c'est-à-dire que le serveur envoie Content-Disposition: attachment, alors l'événement de chargement ne se déclenchera pas, ce qui était mon point
kofifus
Vous avez tout à fait raison. L'événement de chargement est déclenché juste après la fin du serveur. Le traitement commence à envoyer le fichier. C'est ce que je cherchais, 1- bloquer le bouton et afficher le traitement afin que l'utilisateur puisse avoir une rétroaction que les choses se passent. 2 - Ensuite, lorsque le serveur a terminé le traitement et sur le point d'envoyer le fichier 3- (l'événement de chargement est déclenché) où je déverrouille le bouton et supprime le spinner de traitement 4 - l'utilisateur affiche maintenant un fichier de sauvegarde ou le navigateur commence à le télécharger dans l'emplacement de téléchargement défini. Désolé pour mon anglais.
manukyanv07
5
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
EL missaoui habib
la source
Pourriez-vous expliquer votre réponse? Cela aiderait les autres à comprendre ce que vous avez fait afin qu'ils puissent appliquer vos techniques à leur situation.
Wai Ha Lee
2
Juste un avertissement: Safari et IE ne prennent pas en charge l' downloadattribut, donc votre fichier finira par avoir le nom "Inconnu"
Yangshun Tay
4

Dans Rails, je le fais de cette façon:

function download_file(file_id) {
  let url       = '/files/' + file_id + '/download_file';
    $.ajax({
    type: 'GET',
    url: url,
    processData: false,
    success: function (data) {
       window.location = url;
    },
    error: function (xhr) {
     console.log(' Error:  >>>> ' + JSON.stringify(xhr));
    }
   });
 }

L'astuce est la partie window.location . La méthode du contrôleur ressemble à ceci:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end
aarkerio
la source
2
Question rapide, cela ne générera-t-il pas le fichier deux fois? Une fois que vous avez envoyé la demande ajax. Ensuite, vous redirigez également la page vers la même URL. Comment pouvons-nous éliminer cela?
coderhs
Pas dans mon cas. Je ne l'ai cependant testé que sur Chrome.
aarkerio
Comme les codeurs le déclarent déjà correctement, l'action est appelée deux fois.
Sven
Ça m'appelle deux fois aussi.
CSquared
4

Utilisation window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open

Par exemple, vous pouvez placer cette ligne de code dans un gestionnaire de clics:

window.open('/file.txt', '_blank');

Il ouvrira un nouvel onglet (en raison du nom de fenêtre '_blank') et cet onglet ouvrira l'URL.

Votre code côté serveur devrait également avoir quelque chose comme ceci:

res.set('Content-Disposition', 'attachment; filename=file.txt');

Et de cette façon, le navigateur devrait inviter l'utilisateur à enregistrer le fichier sur le disque, au lieu de simplement lui montrer le fichier. Il fermera également automatiquement l'onglet qu'il vient d'ouvrir.

Andrew Koster
la source
4

J'essaie de télécharger un fichier CSV, puis je fais quelque chose une fois le téléchargement terminé. J'ai donc besoin de mettre en œuvre uncallback fonction .

En utilisant window.location="..." n'est pas une bonne idée car je ne peux pas utiliser le programme après avoir terminé le téléchargement. Quelque chose comme ça, changez d'en-tête donc ce n'est pas une bonne idée.

fetchest une bonne alternative mais ne peut pas prendre en charge IE 11 . Et window.URL.createObjectURLne peut pas prendre en charge IE 11. Vous pouvez vous référer à ce .

Ceci est mon code, il est similaire au code de Shahrukh Alam. Mais vous devez faire attention à ce que cela window.URL.createObjectURLcrée des fuites de mémoire. Vous pouvez vous référer à cela . Lorsque la réponse est arrivée, les données seront stockées dans la mémoire du navigateur. Donc, avant de cliquer sur le alien, le fichier a été téléchargé. Cela signifie que vous pouvez faire n'importe quoi après le téléchargement.

$.ajax({
    url: 'your download url',
    type: 'GET',
}).done(function (data, textStatus, request) {
    // csv => Blob
    var blob = new Blob([data]);

    // the file name from server.
    var fileName = request.getResponseHeader('fileName');

    if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
    window.navigator.msSaveOrOpenBlob(blob, fileName);
    } else { // for others
    var url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);

    //Do something after download 
    ...

    }
}).then(after_download)
}
kyakya
la source
4

Comment TÉLÉCHARGER un fichier après l'avoir reçu par AJAX

C'est pratique lorsque le fichier est créé depuis longtemps et que vous devez montrer PRELOADER

Exemple lors de la soumission d'un formulaire Web:

<script>
$(function () {
    $('form').submit(function () {
        $('#loader').show();
        $.ajax({
            url: $(this).attr('action'),
            data: $(this).serialize(),
            dataType: 'binary',
            xhrFields: {
                'responseType': 'blob'
            },
            success: function(data, status, xhr) {
                $('#loader').hide();
                // if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
                //     var reader = new FileReader();
                //     reader.readAsText(data);
                //     reader.onload = function() {alert(reader.result);};
                //     return;
                // }
                var link = document.createElement('a'),
                    filename = 'file.xlsx';
                // if(xhr.getResponseHeader('Content-Disposition')){//filename 
                //     filename = xhr.getResponseHeader('Content-Disposition');
                //     filename=filename.match(/filename="(.*?)"/)[1];
                //     filename=decodeURIComponent(escape(filename));
                // }
                link.href = URL.createObjectURL(data);
                link.download = filename;
                link.click();
            }
        });
        return false;
    });
});
</script>

La fonctionnalité optionnelle est commentée pour simplifier l'exemple.

Pas besoin de créer des fichiers temporaires sur le serveur.

Sur jQuery v2.2.4 OK. Il y aura une erreur sur l'ancienne version:

Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').
Mike S
la source
Pour obtenir le nom de fichier de Content-Disposition, cette correspondance a fonctionné pour moi: filename.match(/filename=(.*)/)[1](sans les guillemets doubles ou le point d'interrogation) - regex101.com/r/2AsD4y/2 . Cependant, votre solution était la seule solution qui a fonctionné après beaucoup de recherches.
jstuardo
3

Ajouter d'autres éléments à la réponse ci-dessus pour télécharger un fichier

Ci-dessous est un code java spring qui génère un tableau d'octets

@RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
    public ResponseEntity<byte[]> downloadReport(
            @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {

        OutputStream out = new ByteArrayOutputStream();
        // write something to output stream
        HttpHeaders respHeaders = new HttpHeaders();
        respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        respHeaders.add("X-File-Name", name);
        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
        return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
    }

Maintenant, en code javascript en utilisant FileSaver.js, vous pouvez télécharger un fichier avec le code ci-dessous

var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 201) {
        var res = this.response;
        var fileName=this.getResponseHeader('X-File-Name');
        var data = new Blob([res]);
        saveAs(data, fileName); //this from FileSaver.js
    }
}    
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);

Ce qui précède téléchargera le fichier

dario nascimento
la source
2

Ok, voici donc le code de travail lors de l'utilisation de MVC et vous obtenez votre fichier à partir d'un contrôleur

disons que votre tableau d'octets déclare et remplit, la seule chose que vous devez faire est d'utiliser la fonction File (en utilisant System.Web.Mvc)

byte[] bytes = .... insert your bytes in the array
return File(bytes, System.Net.Mime.MediaTypeNames.Application.Octet, "nameoffile.exe");

puis, dans le même contrôleur, ajoutez ces 2 fonctions

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

puis vous pourrez appeler votre contrôleur pour télécharger et obtenir le rappel "succès" ou "échec"

$.fileDownload(mvcUrl('name of the controller'), {
            httpMethod: 'POST',
            successCallback: function (url) {
            //insert success code

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });
Yannick Richard
la source
1

J'ai trouvé un correctif qui, bien qu'il n'utilise pas réellement ajax, vous permet d'utiliser un appel javascript pour demander le téléchargement, puis d'obtenir un rappel lorsque le téléchargement démarre réellement. J'ai trouvé cela utile si le lien exécute un script côté serveur qui prend un peu pour composer le fichier avant de l'envoyer. afin que vous puissiez les alerter en cours de traitement, puis quand il envoie enfin le fichier, supprimez cette notification de traitement. c'est pourquoi je voulais essayer de charger le fichier via ajax pour commencer afin que je puisse avoir un événement quand le fichier est demandé et un autre quand il commence réellement à télécharger.

les js en première page

function expdone()
{
    document.getElementById('exportdiv').style.display='none';
}
function expgo()
{
   document.getElementById('exportdiv').style.display='block';
   document.getElementById('exportif').src='test2.php?arguments=data';
}

l'iframe

<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>

puis l'autre fichier:

<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
    window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>

Je pense qu'il y a un moyen de lire obtenir des données en utilisant js, alors aucun php ne serait nécessaire. mais je ne le sais pas et le serveur que j'utilise supporte le php donc cela fonctionne pour moi. pensais que je le partagerais au cas où cela aiderait quelqu'un.

Kit Ramos
la source
0

Si vous souhaitez utiliser jQuery File Download, veuillez le noter pour IE. Vous devez réinitialiser la réponse ou elle ne sera pas téléchargée

    //The IE will only work if you reset response
    getServletResponse().reset();
    //The jquery.fileDownload needs a cookie be set
    getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
    //Do the reset of your action create InputStream and return

Votre action peut mettre ServletResponseAware en œuvre pour accédergetServletResponse()

Alireza Fattahi
la source
0

Il est certain que vous ne pouvez pas le faire via un appel Ajax.

Cependant, il existe une solution de contournement.

Pas :

Si vous utilisez form.submit () pour télécharger le fichier, ce que vous pouvez faire est:

  1. Créez un appel ajax du client vers le serveur et stockez le flux de fichiers dans la session.
  2. Une fois le "succès" renvoyé par le serveur, appelez votre form.submit () pour simplement diffuser le flux de fichiers stocké dans la session.

Ceci est utile dans le cas où vous souhaitez décider si le fichier doit être téléchargé ou non après avoir créé form.submit (), par exemple: il peut y avoir un cas où sur form.submit (), une exception se produit côté serveur et à la place de planter, vous devrez peut-être afficher un message personnalisé du côté client, dans ce cas, cette implémentation pourrait vous aider.

Aman Srivastava
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
The HTML Code:-

'<button type="button" id="GetFile">Get File!</button>'


The jQuery Code:-

'$('#GetFile').on('click', function () {
    $.ajax({
        url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
        method: 'GET',
        xhrFields: {
            responseType: 'blob'
        },
        success: function (data) {
            var a = document.createElement('a');
            var url = window.URL.createObjectURL(data);
            a.href = url;
            a.download = 'myfile.pdf';
            document.body.append(a);
            a.click();
            a.remove();
            window.URL.revokeObjectURL(url);
        }
    });
});'
Shahrukh Alam
la source
Les réponses codées uniquement doivent avoir au moins une description minimale expliquant comment le code fonctionne et pourquoi il répond à la question.
Roberto Caboni
0

Ça marche si bien dans n'importe quel navigateur (j'utilise le noyau asp.net)

            function onDownload() {

  const api = '@Url.Action("myaction", "mycontroller")'; 
  var form = new FormData(document.getElementById('form1'));

  fetch(api, { body: form, method: "POST"})
      .then(resp => resp.blob())
      .then(blob => {
          const url = window.URL.createObjectURL(blob);
        $('#linkdownload').attr('download', 'Attachement.zip');
          $('#linkdownload').attr("href", url);
          $('#linkdownload')
              .fadeIn(3000,
                  function() { });

      })
      .catch(() => alert('An error occurred'));



}
 
 <button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
 
 
 
 <a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
 
 
 <form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
 
 

        function onDownload() {
            const api = '@Url.Action("myaction", "mycontroller")'; 
            //form1 is your id form, and to get data content of form
            var form = new FormData(document.getElementById('form1'));

            fetch(api, { body: form, method: "POST"})
                .then(resp => resp.blob())
                .then(blob => {
                    const url = window.URL.createObjectURL(blob);
                    $('#linkdownload').attr('download', 'Attachments.zip');
                    $('#linkdownload').attr("href", url);
                    $('#linkdownload')
                        .fadeIn(3000,
                            function() {

                            });
                })
                .catch(() => alert('An error occurred'));                 

        }
Marinpietri
la source
-1

J'ai lutté avec ce problème pendant longtemps. Enfin, une élégante bibliothèque externe suggérée ici m'a aidé.

Eerik Sven Puudist
la source