Télécharger un fichier de jQuery.Ajax

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 quand j'appelle l'action en utilisant le 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 la flux Binaire . Je me demande comment ouvrir la fenêtre de téléchargement de fichier avec laquelle l'utilisateur peut sauvegarder le fichier localement?

338
demandé sur Cœur 2010-12-28 13:21:28

16 réponses

Bluish est tout à fait juste à ce sujet, vous ne pouvez pas le faire par Ajax parce que JavaScript ne peut pas enregistrer des fichiers directement sur l'ordinateur d'un utilisateur (par souci de sécurité). Malheureusement pointer L'URL de la fenêtre principale à votre téléchargement de fichier signifie que vous avez peu de contrôle sur ce que l'expérience de l'utilisateur est quand un téléchargement de fichier se produit.

j'ai créé fichier jquery télécharger qui permet d'un " Ajax comme" expérience avec les téléchargements de fichiers complet avec OnSuccess et OnFailure rappels afin de fournir une meilleure expérience utilisateur. Jetez un oeil à mon post de blog sur le problème commun que le plugin résout et quelques façons de l'utiliser et aussi un démo de téléchargement de fichier jQuery en action . Voici le source

voici un cas d'utilisation simple démo en utilisant le plugin source avec des promesses. La Démo page comprend beaucoup d'autres, "meilleur UX" exemples ainsi.

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

selon les navigateurs que vous devez prendre en charge, vous pouvez être en mesure d'utiliser https://github.com/eligrey/FileSaver.js / qui permet un contrôle plus explicite que la méthode IFRAME utilisée par jQuery pour le téléchargement de fichiers.

590
répondu John Culviner 2017-05-23 12:03:09

personne n'a posté ce la solution de @Pekka ... donc je vais le poster. Il peut aider quelqu'un.

vous ne pouvez pas et n'avez pas besoin de faire cela par Ajax. Il suffit d'utiliser

window.location="download.action?para1=value1...."
185
répondu bluish 2017-05-23 11:33:24

vous pouvez avec HTML5

NB: les données de fichier retournées doivent être encodées en base64 car vous ne pouvez pas encoder les données binaires en JSON

dans ma réponse AJAX 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 de ici et doit être utilisé 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 se débarrasse de filedata pour être sauvegardé. Cependant, je n'ai pas tout à fait compris comment on pourrait implémenter une solution de repli HTML4

29
répondu Luke Madhanga 2017-05-23 12:03:09

1. Framework agnostic: Servlet Download file as attachment

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

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

2. Struts2 Framework: action Download file as attachment

<!-- 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 <s:a> tag pointant avec OGNL à une URL créé avec <s:url> tag:

<!-- 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 ce qui précède cas, vous besoin pour écrire le Content-Disposition en-tête à la réponse , en précisant que le fichier doit être téléchargé ( attachment ) et non ouvert par le navigateur ( inline ). Vous besoin pour spécifier le Type de contenu aussi, et vous pouvez vouloir ajouter le nom et la longueur du fichier (pour aider le navigateur à tracer 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 comme un Servlet, un hack pour le streaming direct , par exemple), vous n'avez pas besoin d'écrire directement quoi que ce soit à la réponse; il suffit d'utiliser le Stream result type et de le configurer dans les 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 agnostic (/Struts2 framework): Servlet (/Action) ouvrir le fichier à l'intérieur du navigateur

si vous voulez ouvrir le fichier à l'intérieur du navigateur, au lieu de le télécharger, le Content-disposition doit être défini à 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 le "discuté" target="_blank":

<!-- 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>
24
répondu Andrea Ligios 2017-05-23 11:47:36

j'ai créé peu de fonction comme solution de contournement (inspiré par @JohnCulviner plugin):

// 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');
});
21
répondu ndpu 2014-01-16 14:55:40

Ok, basé sur le code de ndpu heres une version améliorée (je pense) d'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 params sont envoyés en tant que params post appropriés comme s'ils provenaient d'une entrée plutôt que d'une chaîne encodée json comme dans l'exemple précédent.

mise en garde: méfiez-vous du risque d'injection variable sur ces formulaires. Il pourrait y avoir un moyen plus sûr de coder ces variables. Sinon contempler échapper.

15
répondu Shayne 2014-10-17 02:45:49

j'ai affronté le même problème et l'ai résolu avec succès. Mon cas d'utilisation est présent.

postez les données JSON au serveur et recevez un fichier excel. Ce fichier excel est créé par le serveur et retourné en réponse au client. Télécharger cette réponse comme un fichier avec 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

  • affichage d'un tableau comme JSON à la serveur utilisant XMLHttpRequest.
  • après avoir récupéré du contenu sous forme de blob(binaire), nous créons une URL téléchargeable et nous l'attachons au lien invisible" a", puis nous la cliquons. J'ai fait un POST de demande ici. Au lieu de cela, vous pouvez aller pour une simple obtenir trop. Nous ne pouvons pas télécharger le fichier par Ajax, doit utiliser XMLHttpRequest.

ici, nous avons besoin de définir avec soin peu de choses du côté du serveur. J'ai mis quelques headers en Python Django HttpResponse. Vous avez besoin de 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 à plus d'un. Vous devez le configurer en fonction de votre type de fichier. Vous pouvez utiliser cette technique pour télécharger toute sorte de fichiers.

12
répondu Naren Yellavula 2016-08-09 12:44:30

le moyen simple de faire le téléchargement d'un fichier par le navigateur est de faire la demande comme cela:

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

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

11
répondu João Marcos 2017-03-16 09:42:37

voici ce que j'ai fait, pur javascript et html. Ne pas le tester, 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");
});

utilisant juste des composants qui est pris en charge dans tous les navigateurs pas d'autres bibliothèque.

enter image description here enter image description here

Voici mon côté serveur Code du contrôleur JAVA Spring.

@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();
        }
    }
    }
8
répondu manukyanv07 2017-09-04 00:07:23

ajouter quelques autres choses à la réponse ci-dessus pour le téléchargement d'un fichier

ci-dessous est un code java spring qui génère byte Array

@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 dans le code javascript en utilisant FileSaver.JS ,peut 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);

le fichier ci-dessus sera téléchargé

3
répondu dario nascimento 2015-12-24 11:21:52
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
3
répondu EL missaoui habib 2016-04-12 16:32:07

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 fenêtre .emplacement partie. La méthode du contrôleur ressemble à:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end
2
répondu aarkerio 2017-05-31 19:07:52

Ok donc voici le code de travail lorsque vous utilisez MVC et vous obtenez votre fichier à partir d'un contrôleur

disons que vous avez votre tableau d'octets declare et populate, la seule chose que vous devez faire est d'utiliser la fonction de fichier (en utilisant le système.Web.Mvc)

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

et puis, dans le même contrôleur, ajouter 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);
    }

et alors vous serez en mesure d'appeler votre contrôleur de télécharger et d'obtenir le "succès" ou" échec "rappel

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

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });
1
répondu Yannick Richard 2014-01-30 18:12:50

si vous souhaitez utiliser le téléchargement de fichiers jQuery , veuillez prendre note de cela pour IE. Vous devez réinitialiser la réponse ou il ne sera pas télécharger

    //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 implémenter ServletResponseAware pour accéder getServletResponse()

0
répondu Alireza Fattahi 2015-05-11 07:46:29

j'ai trouvé une correction que bien qu'il ne soit pas réellement en utilisant ajax, il ne vous permet d'utiliser un appel javascript pour demander le téléchargement et puis obtenir un rappel lorsque le téléchargement commence réellement. J'ai trouvé cela utile si le lien exécute un script côté serveur qui prend un peu de temps pour composer le fichier avant de l'envoyer. donc vous pouvez les alerter que c'est en cours de traitement, et puis quand il envoie finalement le fichier supprimer cette notification de traitement. c'est pourquoi j'ai voulu essayer de charger le fichier via ajax pour commencer de sorte que je pourrais avoir un événement se produire quand le fichier est demandé et un autre quand il commence effectivement le téléchargement.

le js sur la page d'accueil

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

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 de main et le serveur que j'utilise prend en charge php donc cela fonctionne m'. je pensais le partager au cas où ça aiderait quelqu'un.

0
répondu Kit Ramos 2018-01-23 01:10:47

il est certain que vous ne pouvez pas le faire par appel Ajax.

cependant, il y a une solution.

Suit :

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

  1. créer un appel ajax du client au serveur et stocker le flux de fichier à l'intérieur de la session.
  2. après le retour de" success " du serveur, appelez votre formulaire.submit() juste flux le flux de fichier stocké dans la session.

ceci est utile dans le cas où vous voulez décider si le fichier doit être téléchargé ou non après avoir fait le formulaire.submit(), par exemple: il peut y avoir un cas où sur le formulaire.submit (), une exception se produit du côté du serveur et au lieu de s'écraser, vous pourriez avoir besoin d'afficher un message personnalisé du côté du client, dans un tel cas cette implémentation pourrait aider.

0
répondu Aman Srivastava 2018-05-28 06:24:55