Téléchargez et ouvrez un fichier PDF en utilisant Ajax

98

J'ai une classe d'action qui génère un PDF. Le contentTypeest réglé de manière appropriée.

public class MyAction extends ActionSupport 
{
   public String execute() {
    ...
    ...
    File report = signedPdfExporter.generateReport(xyzData, props);

    inputStream = new FileInputStream(report);
    contentDisposition = "attachment=\"" + report.getName() + "\"";
    contentType = "application/pdf";
    return SUCCESS;
   }
}

J'appelle cela action via un appel Ajax. Je ne connais pas la manière de fournir ce flux au navigateur. J'ai essayé quelques trucs mais rien n'a fonctionné.

$.ajax({
    type: "POST",
    url: url,
    data: wireIdList,
    cache: false,
    success: function(response)
    {
        alert('got response');
        window.open(response);
    },
    error: function (XMLHttpRequest, textStatus, errorThrown) 
    {
        alert('Error occurred while opening fax template' 
              + getAjaxErrorString(textStatus, errorThrown));
    }
});

Ce qui précède donne l'erreur:

Votre navigateur a envoyé une demande que ce serveur n'a pas pu comprendre.

Nayn
la source

Réponses:

37

Vous n'avez pas nécessairement besoin d'Ajax pour cela. Un simple <a>lien suffit si vous définissez le content-dispositionsur attachmentdans le code côté serveur. De cette façon, la page parente restera simplement ouverte, si c'était votre principale préoccupation (pourquoi auriez-vous inutilement choisi Ajax pour cela autrement?). De plus, il n'y a aucun moyen de gérer cela de manière acynchrone. Les PDF ne sont pas des données de caractères. Ce sont des données binaires. Vous ne pouvez pas faire des trucs comme $(element).load(). Vous souhaitez utiliser une toute nouvelle demande pour cela. Pour cela, cela <a href="pdfservlet/filename.pdf">pdf</a>convient parfaitement.

Pour vous aider davantage avec le code côté serveur, vous devrez en dire plus sur la langue utilisée et publier un extrait des tentatives de code.

BalusC
la source
7
Encore une fois: vous n'avez pas besoin d'Ajax pour cela. Cela ne demande que des ennuis. Les PDF sont des données binaires, pas des données de caractères comme HTML ou JSON.
BalusC
3
var url = chemin_contexte + "/xyz/blahBlah.action"; url + = url + "?" + params; essayez {var enfant = window.open (url); child.focus (); } catch (e) {}
Nayn
5
Dans certains navigateurs, window.open restera ouvert et vide, ce qui peut être gênant pour les utilisateurs finaux. Aussi, n'utilisez PAS window.open pour cela. Si le content-dispositionest réglé sur attachment, vous n'obtiendrez qu'un Save asdialogue. La page parente restera inchangée. Juste <a href="pdfservlet/filename.pdf">pdf</a>ou un <form action="pdfservlet/filename.pdf"><input type="submit"></form>est plus que suffisant.
BalusC
5
Il y a une longueur d'URL limitée. Et l'auteur pose des questions sur POST.
Edward Olamisan
3
D'accord avec @EdwardOlamisan, ce n'est pas une réponse correcte car l'auteur essayait de POSTdonnées.
adamj
122

Voici comment cela fonctionne

$.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();
  }
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

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
29
Fonctionne-t-il sur Chrome? Je ne peux voir qu'un pdf vierge.
Tarun Gupta
1
Oui, cela fonctionne sur tous les navigateurs modernes. Si vous voyez un pdf vierge, essayez d'exécuter l'url ajax dans un nouvel onglet. Si vous obtenez également un écran vide, il peut y avoir un problème avec le pdf lui-même. Si vous voyez un fichier pdf là-bas et pas dans le fichier téléchargé, faites-le moi savoir sur mon email. :)
Mayur Padshala
5
Cet (élément d'ancrage) ne fonctionnait pas pour moi sur IE 11, Edge et Firefox. changer le succès en utilisant simplement "window.open (URL.createObjectURL (blob))" a fonctionné.
JimiSweden
3
le fichier pdf est téléchargé mais aucun contenu n'est disponible. J'ai économisé l'octet [] du côté serveur et le contenu pdf est disponible. plz suggèrent.
Awanish Kumar
4
un fichier pdf vierge est téléchargé.
Farukh
31

Je ne pense pas vraiment qu'aucune des réponses passées ait identifié le problème de l'affiche originale. Ils supposent tous une demande GET pendant que l'affiche essayait de POSTER des données et d'obtenir un téléchargement en réponse.

Au cours de la recherche d'une meilleure réponse, nous avons trouvé ce plugin jQuery pour demander des téléchargements de fichiers de type Ajax .

Dans son "cœur", il crée un formulaire HTML "temporaire" contenant les données données comme champs d'entrée. Ce formulaire est ajouté au document et publié à l'URL souhaitée. Juste après cela, le formulaire est à nouveau supprimé:

jQuery('<form action="'+ url +'" method="'+ (method||'post') +'">'+inputs+'</form>')
    .appendTo('body').submit().remove()

La réponse de Update Mayur semble assez prometteuse et très simple par rapport au plug-in jQuery auquel j'ai fait référence.

chiccodoro
la source
9

C'est ainsi que je résous ce problème.
La réponse de Jonathan Amend sur ce post m'a beaucoup aidé.
L'exemple ci-dessous est simplifié.

Pour plus de détails, le code source ci-dessus est capable de télécharger un fichier à l'aide d'une requête JQuery Ajax (GET, POST, PUT, etc.) . Cela permet également de télécharger des paramètres au format JSON et de changer le type de contenu en application / json (par défaut) .

La source html :

<form method="POST">
    <input type="text" name="startDate"/>
    <input type="text" name="endDate"/>
    <input type="text" name="startDate"/>
    <select name="reportTimeDetail">
        <option value="1">1</option>
    </select>
    <button type="submit"> Submit</button>
</form>  

Un formulaire simple avec deux entrées de texte, une sélection et un élément bouton.

La source de la page javascript :

<script type="text/javascript" src="JQuery 1.11.0 link"></script>
<script type="text/javascript">
    // File Download on form submition.
    $(document).on("ready", function(){
        $("form button").on("click", function (event) {
            event.stopPropagation(); // Do not propagate the event.

            // Create an object that will manage to download the file.
            new AjaxDownloadFile({
                url: "url that returns a file",
                data: JSON.stringify($("form").serializeObject())
            });

            return false; // Do not submit the form.
        });
    });
</script>  

Un simple événement sur clic de bouton. Il crée un objet AjaxDownloadFile. La source de la classe AjaxDownloadFile est ci-dessous.

La source de la classe AjaxDownloadFile :

var AjaxDownloadFile = function (configurationSettings) {
    // Standard settings.
    this.settings = {
        // JQuery AJAX default attributes.
        url: "",
        type: "POST",
        headers: {
            "Content-Type": "application/json; charset=UTF-8"
        },
        data: {},
        // Custom events.
        onSuccessStart: function (response, status, xhr, self) {
        },
        onSuccessFinish: function (response, status, xhr, self, filename) {
        },
        onErrorOccured: function (response, status, xhr, self) {
        }
    };
    this.download = function () {
        var self = this;
        $.ajax({
            type: this.settings.type,
            url: this.settings.url,
            headers: this.settings.headers,
            data: this.settings.data,
            success: function (response, status, xhr) {
                // Start custom event.
                self.settings.onSuccessStart(response, status, xhr, self);

                // Check if a filename is existing on the response headers.
                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 = downloadUrl;
                        } else {
                            a.href = downloadUrl;
                            a.download = filename;
                            document.body.appendChild(a);
                            a.click();
                        }
                    } else {
                        window.location = downloadUrl;
                    }

                    setTimeout(function () {
                        URL.revokeObjectURL(downloadUrl);
                    }, 100); // Cleanup
                }

                // Final custom event.
                self.settings.onSuccessFinish(response, status, xhr, self, filename);
            },
            error: function (response, status, xhr) {
                // Custom event to handle the error.
                self.settings.onErrorOccured(response, status, xhr, self);
            }
        });
    };
    // Constructor.
    {
        // Merge settings.
        $.extend(this.settings, configurationSettings);
        // Make the request.
        this.download();
    }
};

J'ai créé cette classe pour l'ajouter à ma bibliothèque JS. Il est réutilisable. J'espère que cela pourra aider.

George Siggouroglou
la source
2
Blobobjet est pris en charge dans IE10 +.
écraser
J'ai dû régler le responseTypexhr sur arraybufferou blobpour que cela fonctionne. (Sinon, cela fonctionne très bien.)
tjklemz
J'avais exactement la même question. Toutes les personnes qui répondent "faites-en simplement un lien" n'aident pas le PO. Si votre contenu est dynamique et que le lien vers lequel vous vous dirigez est dynamique, vous devez tout interroger ... la réponse pour moi était de mettre un formulaire sur la page avec toutes les entrées cachées (non montrées à l'utilisateur) et puis remplissez-le et soumettez-le avec jquery. Fonctionne très bien.
Scott
C'est une excellente réponse, mais pour une raison quelconque, je continue à recevoir des PDF vides et cassés. Je ne peux pas le comprendre. Lorsque je retourne le même jeu d'octets via l'API, c'est bien, donc c'est quelque chose à voir avec la réponse MVC. J'utilise le type de réponse FileResult: File (bytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Jurijs Kastanovs
Clarification: si vous ouvrez l'URL via la barre d'adresse - le fichier est correctement ouvert. Si j'utilise AJAX + blob pour obtenir le fichier - le fichier est mal formé.
Jurijs Kastanovs
7

Ce qui a fonctionné pour moi est le code suivant, car la fonction serveur récupère File(memoryStream.GetBuffer(), "application/pdf", "fileName.pdf");:

$http.get( fullUrl, { responseType: 'arraybuffer' })
            .success(function (response) {
                var blob = new Blob([response], { type: 'application/pdf' });

                if (window.navigator && window.navigator.msSaveOrOpenBlob) {
                    window.navigator.msSaveOrOpenBlob(blob); // for IE
                }
                else {
                    var fileURL = URL.createObjectURL(blob);
                    var newWin = window.open(fileURL);
                    newWin.focus();
                    newWin.reload();
                }
});
ParPar
la source
Cela fonctionne parfaitement pour moi au moment de ce commentaire et du dernier Chrome
Loredra L
6

Vous pouvez utiliser ce plugin qui crée un formulaire, le soumet, puis le supprime de la page.

jQuery.download = function(url, data, method) {
    //url and data options required
    if (url && data) {
        //data can be string of parameters or array/object
        data = typeof data == 'string' ? data : jQuery.param(data);
        //split params into form inputs
        var inputs = '';
        jQuery.each(data.split('&'), function() {
            var pair = this.split('=');
            inputs += '<input type="hidden" name="' + pair[0] +
                '" value="' + pair[1] + '" />';
        });
        //send request
        jQuery('<form action="' + url +
                '" method="' + (method || 'post') + '">' + inputs + '</form>')
            .appendTo('body').submit().remove();
    };
};


$.download(
    '/export.php',
    'filename=mySpreadsheet&format=xls&content=' + spreadsheetData
);

Cela a fonctionné pour moi. J'ai trouvé ce plugin ici

Ijas Ameenudeen
la source
Ce plugin crée simplement un formulaire, le soumet, puis le supprime de la page. (si quelqu'un se demandait)
écraser le
4

Le code suivant a fonctionné pour moi

//Parameter to be passed
var data = 'reportid=R3823&isSQL=1&filter=[]';
var xhr = new XMLHttpRequest();
xhr.open("POST", "Reporting.jsp"); //url.It can pdf file path
xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
xhr.responseType = "blob";
xhr.onload = function () {
    if (this.status === 200) {
        var blob = new Blob([xhr.response]);
        const url = window.URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url;
        a.download = 'myFile.pdf';
        a.click();
        setTimeout(function () {
            // For Firefox it is necessary to delay revoking the ObjectURL
            window.URL.revokeObjectURL(data)
                , 100
        })
    }
};
xhr.send(data);
MemZ
la source
4

Pour résoudre le problème du PDF vierge dans la demande de publication afin d'obtenir des données de flux telles que PDF, nous devons ajouter un type de réponse comme `` arraybuffer '' ou `` blob '' dans la demande.

$.ajax({
  url: '<URL>',
  type: "POST",
  dataType: 'arraybuffer',
  success: function(data) {
    let blob = new Blob([data], {type: 'arraybuffer'});
    let link = document.createElement('a');
    let objectURL = window.URL.createObjectURL(blob);
    link.href = objectURL;
    link.target = '_self';
    link.download = "fileName.pdf";
    (document.body || document.documentElement).appendChild(link);
    link.click();
    setTimeout(()=>{
        window.URL.revokeObjectURL(objectURL);
        link.remove();
    }, 100);
  }
});
Ninja
la source
3

Concernant la réponse donnée par Mayur Padshala, c'est la bonne logique pour télécharger un fichier pdf via ajax mais comme d'autres le rapportent dans les commentaires, cette solution consiste en effet à télécharger un pdf vierge.

La raison en est expliquée dans la réponse acceptée à cette question : jQuery a quelques problèmes pour charger des données binaires à l'aide de requêtes AJAX, car il n'implémente pas encore certaines fonctionnalités HTML5 XHR v2, voir cette demande d' amélioration et cette discussion .

Donc, l'utilisation HTMLHTTPRequestdu code devrait ressembler à ceci:

var req = new XMLHttpRequest();
req.open("POST", "URL", true);
req.responseType = "blob";
req.onload = function (event) {
    var blob = req.response;
    var link=document.createElement('a');
    link.href=window.URL.createObjectURL(blob);
    link.download="name_for_the_file_to_save_with_extention";
    link.click();
}
Vpant
la source
2

créez un iframe caché, puis dans votre code ajax ci-dessus:

url: document.getElementById('myiframeid').src = your_server_side_url ,

et retirez le window.open(response);

qalhat
la source
Cette solution a fonctionné comme un charme. J'appelle un script côté serveur qui fait un appel curl à un service qui récupère le fichier via curl. Cela fonctionne très bien car je peux déposer un gif de chargement et désactiver le lien de demande.
Eggmatters
1
Cette solution fonctionne pour les demandes GET et non pour les demandes POST comme dans la publication d'origine.
chiccodoro
2

Cet extrait est destiné aux utilisateurs angular js qui seront confrontés au même problème, notez que le fichier de réponse est téléchargé en utilisant un événement de clic programmé. Dans ce cas, les en-têtes ont été envoyés par le serveur contenant le nom de fichier et le contenu / type.

$http({
    method: 'POST', 
    url: 'DownloadAttachment_URL',
    data: { 'fileRef': 'filename.pdf' }, //I'm sending filename as a param
    headers: { 'Authorization': $localStorage.jwt === undefined ? jwt : $localStorage.jwt },
    responseType: 'arraybuffer',
}).success(function (data, status, headers, config) {
    headers = headers();
    var filename = headers['x-filename'];
    var contentType = headers['content-type'];
    var linkElement = document.createElement('a');
    try {
        var blob = new Blob([data], { type: contentType });
        var url = window.URL.createObjectURL(blob);

        linkElement.setAttribute('href', url);
        linkElement.setAttribute("download", filename);

        var clickEvent = new MouseEvent("click", {
            "view": window,
            "bubbles": true,
            "cancelable": false
        });
        linkElement.dispatchEvent(clickEvent);
    } catch (ex) {
        console.log(ex);
    }
}).error(function (data, status, headers, config) {
}).finally(function () {

});
Gihan Sandaru
la source
Veuillez expliquer votre réponse.
Gufran Hasan
1

Devez-vous le faire avec Ajax? Ne serait-il pas possible de le charger dans une iframe?

Emil Vikström
la source
1
Je suis en train de vérifier si, cela pourrait être fait avec Ajax. Si c'est techniquement impossible ou une approche inférieure, je passerais à d'autres approches.
Nayn le
1

J'espère que cela vous fera gagner quelques heures et vous épargnera un mal de tête. Cela m'a pris un certain temps pour comprendre cela, mais faire une requête $ .ajax () régulière a ruiné mon fichier PDF, tout en le demandant via la barre d'adresse fonctionnait parfaitement. La solution était la suivante:

Incluez download.js: http://danml.com/download.html

Utilisez ensuite XMLHttpRequest au lieu de la requête $ .ajax ().

    var ajax = new XMLHttpRequest(); 

    ajax.open("GET", '/Admin/GetPdf' + id, true); 
    ajax.onreadystatechange = function(data) { 
        if (this.readyState == 4)
        {
            if (this.status == 200)
            {
                download(this.response, "report.pdf", "application/pdf");

            }
            else if (this.responseText != "")
            {
                alert(this.responseText);
            }
        }
        else if (this.readyState == 2)
        {
            if (this.status == 200)
            {
                this.responseType = "blob";
            }
            else
            {
                this.responseType = "text";
            }
        }
    };

    ajax.send(null);
Jurijs Kastanovs
la source
0

var xhr;
var beforeSend = function(){
    $('#pleasewaitDL').modal('show');
}
$(function () {
    $('#print_brochure_link').click(function(){
        beforeSend();
        xhr = new XMLHttpRequest();
        xhr.open("GET",$('#preparedPrintModalForm').attr('action'), true); 
        xhr.responseType = "blob";
        xhr.onload = function (e) {
            if (this.status === 200) {
                var file = window.URL.createObjectURL(this.response);
                var a = document.createElement("a");
                a.href = file;
                a.download = this.response.name || "Property Brochure";
                console.log(file);
                document.body.appendChild(a);
                a.click();
                
                window.onfocus = function () {                     
                  document.body.removeChild(a)
                }
                $('#pleasewaitDL').modal('hide');
            };
        };
        xhr.send($('#preparedPrintModalForm').serialize());
    });
    $('#pleasewaitDLCancel').click(function() {
        xhr.abort();
    });
});

POGSNET
la source
0

Si vous devez travailler avec un flux de fichiers (donc pas de PDF enregistré physiquement) comme nous le faisons et que vous souhaitez télécharger le PDF sans rechargement de page, la fonction suivante fonctionne pour nous:

HTML

<div id="download-helper-hidden-container" style="display:none">
     <form id="download-helper-form" target="pdf-download-output" method="post">
            <input type="hidden" name="downloadHelperTransferData" id="downloadHelperTransferData" />
     </form>
     <iframe id="pdf-helper-output" name="pdf-download-output"></iframe>
</div>

Javascript

var form = document.getElementById('download-helper-form');
$("#downloadHelperTransferData").val(transferData);
form.action = "ServerSideFunctionWhichWritesPdfBytesToResponse";
form.submit();

En raison de target = "pdf-download-output" , la réponse est écrite dans l'iframe et par conséquent aucun rechargement de page n'est exécuté, mais le flux de réponse pdf est sorti dans le navigateur sous forme de téléchargement.

George Maharis
la source
désolé, mais comment obtenez-vous la valeur de transferData?
Kate