Descargue y abra el archivo PDF usando Ajax

98

Tengo una clase de acción que genera un PDF. El contentTypeestá configurado apropiadamente.

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

Llamo a esto a action través de una llamada Ajax. No sé la forma de enviar esta transmisión al navegador. Probé algunas cosas pero nada funcionó.

$.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));
    }
});

Lo anterior da el error:

Su navegador envió una solicitud que este servidor no pudo entender.

Nayn
fuente

Respuestas:

37

No necesitas necesariamente Ajax para esto. Solo un <a>enlace es suficiente si configura content-dispositiona attachmenten el código del lado del servidor. De esta manera, la página principal permanecerá abierta, si esa fuera su principal preocupación (¿por qué habría elegido innecesariamente Ajax para esto de otra manera?). Además, no hay forma de manejar esto de manera asincrónica. PDF no son datos de caracteres. Son datos binarios. No puedes hacer cosas como estas $(element).load(). Desea utilizar una solicitud completamente nueva para esto. Para eso <a href="pdfservlet/filename.pdf">pdf</a>es perfectamente adecuado.

Para ayudarlo más con el código del lado del servidor, deberá contar más sobre el lenguaje utilizado y publicar un extracto de los intentos de código.

BalusC
fuente
7
Una vez más: no necesitas Ajax para esto. Solo está buscando problemas. PDF son datos binarios, no datos de caracteres como HTML o JSON.
BalusC
3
var url = contextPath + "/xyz/blahBlah.action"; url + = url + "?" + params; intente {var child = window.open (url); child.focus (); } catch (e) {}
Nayn
5
En algunos navegadores, window.open permanecerá abierto y en blanco, lo que puede resultar molesto para los usuarios finales. Entonces, tampoco use window.open para esto. Si content-dispositionestá configurado en attachment, solo obtendrá un Save asdiálogo. La página principal no se modificará. Solo <a href="pdfservlet/filename.pdf">pdf</a>o a <form action="pdfservlet/filename.pdf"><input type="submit"></form>es más que suficiente.
BalusC
5
Hay una longitud de URL limitada. Y el autor pregunta sobre POST.
Edward Olamisan
3
De acuerdo con @EdwardOlamisan, esta no es una respuesta correcta ya que el autor estaba tratando de POSTdatos.
Adamj
122

Así es como hice que esto funcionara

$.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>

Respuesta actualizada usando download.js

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

Mayur Padshala
fuente
29
¿Funciona en cromo? Solo puedo ver un pdf en blanco.
Tarun Gupta
1
Sí, funciona en todos los navegadores modernos. Si ve un pdf en blanco, intente ejecutar la URL ajax en una nueva pestaña. Si también aparece una pantalla en blanco, podría haber un problema con el PDF. Si ve un archivo pdf allí y no en el archivo descargado, hágamelo saber en mi correo electrónico. :)
Mayur Padshala
5
Este (elemento de anclaje) en realidad no me funcionó en IE 11, Edge y Firefox. cambiar el éxito a solo usar "window.open (URL.createObjectURL (blob))" funcionó.
JimiSweden
3
Se descarga el archivo pdf pero no hay contenido disponible. He guardado el byte [] en el lado del servidor y el contenido pdf está disponible. por favor sugiera.
Awanish Kumar
4
Se descarga un archivo pdf en blanco.
Farukh
31

Realmente no creo que ninguna de las respuestas anteriores haya detectado el problema del póster original. Todos suponen una solicitud GET mientras el autor intentaba PUBLICAR datos y obtener una descarga en respuesta.

En el curso de la búsqueda de una mejor respuesta, encontramos este complemento jQuery para solicitar descargas de archivos similares a Ajax .

En su "corazón" crea un formulario HTML "temporal" que contiene los datos dados como campos de entrada. Este formulario se adjunta al documento y se publica en la URL deseada. Inmediatamente después de eso, el formulario se elimina nuevamente:

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

La respuesta de Update Mayur parece bastante prometedora y muy simple en comparación con el complemento jQuery al que me referí.

chiccodoro
fuente
9

Así es como resuelvo este problema.
La respuesta de Jonathan Modif en este post me ayudó mucho.
El siguiente ejemplo está simplificado.

Para obtener más detalles, el código fuente anterior puede descargar un archivo mediante una solicitud JQuery Ajax (GET, POST, PUT, etc.) . También ayuda a cargar parámetros como JSON y a cambiar el tipo de contenido a application / json (mi predeterminado) .

La fuente 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 formulario simple con dos entradas de texto, una selección y un elemento de botón.

La fuente de la página 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 evento al hacer clic en un botón. Crea un objeto AjaxDownloadFile. La fuente de la clase AjaxDownloadFile se encuentra a continuación.

La fuente de la clase 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();
    }
};

Creé esta clase para agregarla a mi biblioteca JS. Es reutilizable. Espero que ayude.

George Siggouroglou
fuente
2
BlobEl objeto es compatible con IE10 +.
aplastar
Tuve que configurar el responseTypede xhr en arraybuffero blobpara que esto funcione. (De lo contrario, esto funciona muy bien)
tjklemz
Tenía exactamente la misma pregunta. Toda la gente que responde "simplemente conviértalo en un enlace" no ayuda al OP. Si su contenido es dinámico y el enlace al que va es dinámico, tiene que consultarlo todo ... la respuesta para mí fue poner un formulario en la página con todas las entradas ocultas (no se muestra al usuario) y luego rellénelo y envíelo con jquery. Funciona genial.
Scott
Esta es una gran respuesta, pero por alguna razón, sigo recibiendo PDF vacío roto. No puedo entenderlo. Cuando devuelvo el mismo conjunto de bytes a través de la API, está bien, por lo que tiene algo que ver con la respuesta de MVC. Yo uso el tipo de respuesta FileResult: File (bytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Jurijs Kastanovs
Aclaración: si abre la URL a través de la barra de direcciones, el archivo se abre correctamente. Si uso AJAX + blob para obtener el archivo, el archivo tiene un formato incorrecto.
Jurijs Kastanovs
7

Lo que funcionó para mí es el siguiente código, ya que la función del servidor está recuperando 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
fuente
Esto funciona para mí perfectamente en el momento de este comentario y el Chrome más nuevo
Loredra L
6

Puede usar este complemento que crea un formulario, lo envía y luego lo elimina de la página.

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

Esto funcionó para mí. Encontré este complemento aquí

Ijas Ameenudeen
fuente
Este complemento simplemente crea un formulario, lo envía y luego lo elimina de la página. (si alguien se pregunta)
crush
4

El siguiente código funcionó para mí

//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
fuente
4

Para solucionar el problema de PDF en blanco en la solicitud posterior para obtener datos de transmisión como PDF, debemos agregar el tipo de respuesta como 'arraybuffer' o 'blob' en la solicitud

$.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
fuente
3

Sobre la respuesta dada por Mayur Padshala esta es la lógica correcta para descargar un archivo pdf a través de ajax, pero como otros informan en los comentarios, esta solución es de hecho descarga un pdf en blanco.

La razón de esto se explica en la respuesta aceptada de esta pregunta : jQuery tiene algunos problemas para cargar datos binarios usando solicitudes AJAX, ya que aún no implementa algunas capacidades HTML5 XHR v2, vea esta solicitud de mejora y esta discusión .

Entonces, usar HTMLHTTPRequestel código debería verse así:

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
fuente
2

cree un iframe oculto, luego en su código ajax arriba:

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

y quitar el window.open(response);

qalhat
fuente
Esta solución funcionó a las mil maravillas. Estoy llamando a un script del lado del servidor que realiza una llamada curl a un servicio que recupera el archivo a través de curl. Esto funciona muy bien ya que puedo soltar un gif de carga y deshabilitar el enlace de solicitud.
eggmatters
1
Esta solución funciona para solicitudes GET, no para solicitudes POST como en la publicación original.
chiccodoro
2

Este fragmento es para usuarios de angular js que enfrentarán el mismo problema. Tenga en cuenta que el archivo de respuesta se descarga mediante un evento de clic programado. En este caso, los encabezados fueron enviados por el servidor que contiene el nombre del archivo y el contenido / tipo.

$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
fuente
Escriba una explicación de su respuesta.
Gufran Hasan
1

¿Tienes que hacerlo con Ajax? ¿No sería posible cargarlo en un iframe?

Emil Vikström
fuente
1
Estoy comprobando si esto se podría hacer con Ajax. Si es técnicamente imposible o un enfoque inferior, cambiaría a otros enfoques.
Nayn
1

Espero que esto le ahorre algunas horas y le evite un dolor de cabeza. Me tomó un tiempo darme cuenta de esto, pero hacer una solicitud regular $ .ajax () arruinó mi archivo PDF, mientras que solicitarlo a través de la barra de direcciones funcionó perfectamente. La solución fue esta:

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

Luego use XMLHttpRequest en lugar de la solicitud $ .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
fuente
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
fuente
0

Si tiene que trabajar con flujo de archivos (por lo que no hay PDF guardado físicamente) como lo hacemos nosotros y desea descargar el PDF sin volver a cargar la página, la siguiente función funciona para nosotros:

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

Debido a target = "pdf-download-output" , la respuesta se escribe en el iframe y por lo tanto no se ejecuta la recarga de la página, pero el pdf-response-stream se genera en el navegador como una descarga.

George Maharis
fuente
lo siento, pero ¿cómo se obtiene el valor transferData?
Kate