Baixe e abra o arquivo PDF usando Ajax

98

Tenho uma classe de ação que gera um PDF. O contentTypeestá definido corretamente.

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

Eu chamo isso action por meio de uma chamada Ajax. Não sei como entregar este stream ao navegador. Tentei algumas coisas, mas nada funcionou.

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

O acima dá o erro:

Seu navegador enviou uma solicitação que este servidor não conseguiu entender.

Nayn
fonte

Respostas:

37

Você não precisa necessariamente do Ajax para isso. Apenas um <a>link é suficiente se você definir content-dispositioncomo attachmentno código do servidor. Dessa forma, a página pai permanecerá aberta, se essa for sua principal preocupação (por que você teria escolhido Ajax desnecessariamente para isso?). Além disso, não há como lidar com isso de maneira acíncrona. PDF não são dados de caracteres. São dados binários. Você não pode fazer coisas assim $(element).load(). Você deseja usar um pedido completamente novo para isso. Pois isso <a href="pdfservlet/filename.pdf">pdf</a>é perfeitamente adequado.

Para ajudá-lo mais com o código do lado do servidor, você precisará informar mais sobre a linguagem usada e postar um trecho das tentativas de código.

BalusC
fonte
7
Mais uma vez: você não precisa do Ajax para isso. Ele está apenas pedindo problemas. PDF são dados binários, não dados de caracteres como HTML ou JSON.
BalusC
3
var url = contextPath + "/xyz/blahBlah.action"; url + = url + "?" + params; experimente {var child = window.open (url); child.focus (); } catch (e) {}
Nayn
5
Em alguns navegadores, o window.open permanecerá aberto e em branco, o que pode ser irritante para os usuários finais. Portanto, NÃO use window.open para isso. Se content-dispositionestiver definido como attachment, você obterá apenas um Save asdiálogo. A página pai permanecerá inalterada. Apenas <a href="pdfservlet/filename.pdf">pdf</a>ou a <form action="pdfservlet/filename.pdf"><input type="submit"></form>é mais do que suficiente.
BalusC
5
O tamanho do URL é limitado. E o autor está perguntando sobre o POST.
Edward Olamisan
3
Concordo com @EdwardOlamisan, esta não é uma resposta correta, pois o autor estava tentando obter os POSTdados.
adamj
122

Aqui está como eu fiz isso funcionar

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

Resposta atualizada usando download.js

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

Mayur Padshala
fonte
29
Funciona no Chrome? Só consigo ver um pdf em branco.
Tarun Gupta
1
Sim, funciona em todos os navegadores modernos. Se você vir um pdf em branco, tente executar o url ajax em uma nova guia. Se você receber uma tela em branco, pode haver um problema com o próprio pdf. Se você vir um arquivo pdf lá e não no arquivo baixado, me avise no meu e-mail. :)
Mayur Padshala
5
Este (elemento âncora) realmente não funcionou para mim no IE 11, Edge e Firefox. mudar o sucesso para apenas usar "window.open (URL.createObjectURL (blob))" funcionou.
JimiSweden
3
arquivo pdf é baixado, mas nenhum conteúdo está disponível. salvei byte [] no lado do servidor e o conteúdo em pdf está disponível. por favor, sugira.
Awanish Kumar
4
arquivo pdf em branco é baixado.
Farukh
31

Eu realmente não acho que nenhuma das respostas anteriores tenha identificado o problema do pôster original. Todos eles presumem uma solicitação GET enquanto o autor da postagem estava tentando POSTAR os dados e obter um download em resposta.

No decorrer da busca por uma resposta melhor, encontramos este plug-in jQuery para solicitação de downloads de arquivos do tipo Ajax .

Em seu "coração", ele cria um formulário HTML "temporário" contendo os dados fornecidos como campos de entrada. Este formulário é anexado ao documento e postado no URL desejado. Logo em seguida, o formulário é removido novamente:

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

Atualizar a resposta de Mayur parece muito promissor e muito simples em comparação com o plug-in jQuery a que me referi.

chiccodoro
fonte
9

É assim que eu resolvo esse problema.
A resposta de Jonathan Amend neste post me ajudou muito.
O exemplo abaixo é simplificado.

Para mais detalhes, o código-fonte acima é capaz de baixar um arquivo usando uma solicitação JQuery Ajax (GET, POST, PUT etc) . Também ajuda a carregar parâmetros como JSON e a alterar o tipo de conteúdo para application / json (meu padrão) .

A fonte 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>  

Um formulário simples com dois textos de entrada, um selecionar e um elemento de botão.

A fonte da 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>  

Um evento simples no clique do botão. Ele cria um objeto AjaxDownloadFile. A fonte da classe AjaxDownloadFile está abaixo.

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

Criei esta classe para adicionar à minha biblioteca JS. É reutilizável. Espero que ajude.

George Siggouroglou
fonte
2
Blobobjeto é compatível com IE10 +.
esmagamento de
Tive que definir o responseTypede xhr para arraybufferou blobpara que isso funcionasse. (Caso contrário, funciona muito bem.)
tjklemz
Eu tinha exatamente a mesma pergunta. Todas as pessoas respondendo "basta fazer um link" não ajuda o OP. Se o seu conteúdo é dinâmico e o link que você está indo é dinâmico, você tem que fazer o jquery tudo ... a resposta para mim foi colocar um formulário na página com todas as entradas ocultas (não é mostrado ao usuário) e em seguida, preencha-o e envie-o com jquery. Funciona bem.
Scott
Esta é uma ótima resposta, mas por algum motivo, continuo recebendo PDFs vazios e corrompidos. Não consigo descobrir. Quando eu retorno o mesmo conjunto de bytes por meio da API - tudo bem, então é algo a ver com a resposta do MVC. Eu uso o tipo de resposta FileResult: File (bytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Jurijs Kastanovs
Esclarecimento: se abrir a URL através da barra de endereços - o arquivo está aberto corretamente. Se eu usar AJAX + blob para obter o arquivo - o arquivo está malformado.
Jurijs Kastanovs
7

O que funcionou para mim foi o seguinte código, pois a função do 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
fonte
Isso funciona perfeitamente para mim no momento deste comentário e do Chrome mais recente
Loredra L
6

Você pode usar este plug-in que cria um formulário, o envia e, em seguida, o remove da 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
);

Isso funcionou para mim. Encontrou este plugin aqui

Ijas Ameenudeen
fonte
Este plug-in apenas cria um formulário, o envia e, a seguir, o remove da página. (se alguém estiver se perguntando)
esmagar
4

O código a seguir funcionou para mim

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

Para corrigir o problema do PDF em branco na pós-solicitação para obter dados de fluxo como PDF, precisamos adicionar o tipo de resposta como 'arraybuffer' ou 'blob' na solicitação

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

Em relação à resposta dada por Mayur Padshala esta é a lógica correta para baixar um arquivo pdf via ajax, mas como outros relatam nos comentários esta solução é de fato baixar um pdf em branco.

O motivo para isso é explicado na resposta aceita a esta pergunta : jQuery tem alguns problemas ao carregar dados binários usando solicitações AJAX, pois ainda não implementa alguns recursos de HTML5 XHR v2, consulte esta solicitação de aprimoramento e esta discussão .

Portanto, o uso HTMLHTTPRequestdo código deve ser assim:

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

crie um iframe oculto e, em seguida, em seu código ajax acima:

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

e remova o window.open(response);

qalhat
fonte
Esta solução funcionou perfeitamente. Estou chamando um script do lado do servidor que faz uma chamada curl para um serviço que busca o arquivo via curl. Isso funciona muito bem, pois posso descartar um gif de carregamento e desativar o link de solicitação.
Eggmatters
1
Esta solução funciona para solicitações GET e não para solicitações POST como na postagem original.
chiccodoro
2

Este snippet é para usuários do Angular js que enfrentarão o mesmo problema. Observe que o arquivo de resposta é baixado usando um evento de clique programado. Neste caso, os cabeçalhos foram enviados pelo servidor contendo o nome do arquivo e o conteúdo / 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
fonte
Por favor, escreva alguma explicação para sua resposta.
Gufran Hasan,
1

Você tem que fazer isso com Ajax? Não seria uma possibilidade carregá-lo em um iframe?

Emil Vikström
fonte
1
Estou verificando se isso pode ser feito com Ajax. Se for tecnicamente impossível ou uma abordagem inferior, eu mudaria para outras abordagens.
Nayn
1

Espero que isso economize algumas horas e evite uma dor de cabeça. Levei um tempo para descobrir isso, mas fazer uma solicitação $ .ajax () regular estragou meu arquivo PDF, enquanto solicitá-lo por meio da barra de endereços funcionou perfeitamente. A solução foi esta:

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

Em seguida, use XMLHttpRequest em vez da solicitação $ .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
fonte
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
fonte
0

Se você tiver que trabalhar com fluxo de arquivo (portanto, nenhum PDF salvo fisicamente) como fazemos e quiser baixar o PDF sem recarregar a página, a seguinte função funciona para nós:

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

Devido ao target = "pdf-download-output" , a resposta é gravada no iframe e, portanto, nenhuma recarga de página é executada, mas o fluxo de resposta de pdf é gerado no navegador como um download.

George Maharis
fonte
desculpe, mas como você obtém o valor transferData?
Kate