Manipular o download de arquivos da postagem do ajax

393

Eu tenho um aplicativo javascript que envia solicitações de ajax POST para um determinado URL. A resposta pode ser uma sequência JSON ou um arquivo (como anexo). Posso detectar com facilidade o Tipo de Conteúdo e a Disposição de Conteúdo na minha chamada ajax, mas depois que detecto que a resposta contém um arquivo, como ofereço ao cliente o download? Eu li vários tópicos semelhantes aqui, mas nenhum deles fornece a resposta que estou procurando.

Por favor, por favor, não poste respostas sugerindo que eu não deveria usar o ajax para isso ou que eu deveria redirecionar o navegador, porque nada disso é uma opção. Usar um formulário HTML simples também não é uma opção. O que eu preciso é mostrar uma caixa de diálogo de download para o cliente. Isso pode ser feito e como?

Pavle Predic
fonte
Para aqueles que leem este artigo, leia este post: stackoverflow.com/questions/20830309/…
sobhan
Eu removi sua solução da pergunta. Você pode publicá-lo como uma resposta abaixo, mas não pertence à pergunta.
Martijn Pieters

Respostas:

111

Crie um formulário, use o método POST, envie o formulário - não há necessidade de um iframe. Quando a página do servidor responder à solicitação, escreva um cabeçalho de resposta para o tipo MIME do arquivo e ele apresentará uma caixa de diálogo de download - já fiz isso várias vezes.

Você deseja o tipo de conteúdo de aplicativo / download - basta pesquisar como fornecer um download para qualquer idioma que esteja usando.


fonte
35
Conforme indicado na pergunta: "Usar um formulário HTML simples também não é uma opção".
Pavle Predic
13
Não, porque o uso de um POST comum navegaria o navegador para o URL do POST. Não quero sair da página. Quero executar a solicitação em segundo plano, processar a resposta e apresentá-la ao cliente.
Pavle Predic
6
Se o servidor enviar cabeçalhos de volta como a outra resposta, ele será aberto em uma nova janela - eu já fiz isso antes. Ele só iria navegar para fora se o seu script do lado do servidor devolveu o código HTML
11
@PavlePredic você acabou descobrindo como gerenciar os dois cenários de resposta, como resposta de texto JSON ou resposta de arquivo de download?
Usuário da Web
9
A resposta não é clara e a solução proposta não funciona.
stack247
531

Não desista tão rapidamente, porque isso pode ser feito (em navegadores modernos) usando partes da FileAPI:

var xhr = new XMLHttpRequest();
xhr.open('POST', url, true);
xhr.responseType = 'arraybuffer';
xhr.onload = function () {
    if (this.status === 200) {
        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;
        if (typeof File === 'function') {
            try {
                blob = new File([this.response], filename, { type: type });
            } catch (e) { /* Edge */ }
        }
        if (typeof blob === 'undefined') {
            blob = new Blob([this.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.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
};
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.send($.param(params));

Aqui está a versão antiga usando jQuery.ajax. Pode manipular dados binários quando a resposta é convertida em uma sequência de algum conjunto de caracteres.

$.ajax({
    type: "POST",
    url: url,
    data: params,
    success: function(response, status, xhr) {
        // check for a filename
        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.href = downloadUrl;
                } else {
                    a.href = downloadUrl;
                    a.download = filename;
                    document.body.appendChild(a);
                    a.click();
                }
            } else {
                window.location.href = downloadUrl;
            }

            setTimeout(function () { URL.revokeObjectURL(downloadUrl); }, 100); // cleanup
        }
    }
});
Jonathan Amend
fonte
11
Muito obrigado ! Eu tive que adicionar 'disposição de conteúdo' a 'acesso-controle-expor-cabeçalhos' e 'acesso-controle-permitir-cabeçalhos' na minha resposta HTTP para que ele funcionasse.
JulienD
11
Não funciona quando o arquivo tem mais de 500 mb, talvez devêssemos usar outra API?
Hirra
Que tal remover o elemento a do DOM na parte de limpeza (e não apenas o URL)? document.body.removeChild(a);
Scoregraphic 5/09/19
@hirra uso responceType "bolha" em vez de "ArrayBuffer" e reescrever a função onload chamada de retorno que forma, que var blob é this.response (var blob = this.response;) de modovar blob =this.responce; /** if (typeof File === 'function') { try { blob = new File([this.response], filename, { type: type }); } catch (e) { /* Edge */ } } if (typeof blob === 'undefined') { blob = new Blob([this.response], { type: type }); } */
Chris Tobba
11
Esta é a solução perfeita. Apenas uma pequena mudança. No TypeScript eu precisava em window.location.href = downloadUrlvez dewindow.location = downloadUrl
michal.jakubeczy
39

Eu enfrentei o mesmo problema e resolvi-o com sucesso. Meu caso de uso é esse.

" Publique dados JSON no servidor e receba um arquivo do Excel. Esse arquivo do Excel é criado pelo servidor e retornado como resposta ao cliente. Faça o download dessa resposta como um arquivo com nome personalizado no navegador "

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

O snippet acima está apenas seguindo

  • Postando uma matriz como JSON no servidor usando XMLHttpRequest.
  • Após buscar o conteúdo como um blob (binário), estamos criando um URL para download e anexando-o ao link "a" invisível e clicando nele.

Aqui, precisamos definir cuidadosamente algumas coisas no lado do servidor. Eu defini alguns cabeçalhos no Python Django HttpResponse. Você precisa configurá-los de acordo se usar outras linguagens de programação.

# In python django code
response = HttpResponse(file_content, content_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")

Desde que eu baixei xls (excel) aqui, ajustei contentType para acima de um. Você precisa configurá-lo de acordo com o seu tipo de arquivo. Você pode usar esta técnica para baixar qualquer tipo de arquivo.

Naren Yellavula
fonte
33

Qual idioma do servidor você está usando? No meu aplicativo, posso baixar facilmente um arquivo de uma chamada AJAX, definindo os cabeçalhos corretos na resposta do PHP:

Definindo cabeçalhos do lado do servidor

header("HTTP/1.1 200 OK");
header("Pragma: public");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");

// The optional second 'replace' parameter indicates whether the header
// should replace a previous similar header, or add a second header of
// the same type. By default it will replace, but if you pass in FALSE
// as the second argument you can force multiple headers of the same type.
header("Cache-Control: private", false);

header("Content-type: " . $mimeType);

// $strFileName is, of course, the filename of the file being downloaded. 
// This won't have to be the same name as the actual file.
header("Content-Disposition: attachment; filename=\"{$strFileName}\""); 

header("Content-Transfer-Encoding: binary");
header("Content-Length: " . mb_strlen($strFile));

// $strFile is a binary representation of the file that is being downloaded.
echo $strFile;

Na verdade, isso 'redirecionará' o navegador para esta página de download, mas como @ahren já disse em seu comentário, ele não sairá da página atual.

É tudo sobre como definir os cabeçalhos corretos, por isso tenho certeza que você encontrará uma solução adequada para a linguagem do lado do servidor que estiver usando, se não for PHP.

Manipulando o Lado do Cliente de Resposta

Supondo que você já saiba como fazer uma chamada AJAX, no lado do cliente você executa uma solicitação AJAX para o servidor. O servidor gera um link a partir do qual esse arquivo pode ser baixado, por exemplo, o URL 'forward' para o qual você deseja apontar. Por exemplo, o servidor responde com:

{
    status: 1, // ok
    // unique one-time download token, not required of course
    message: 'http://yourwebsite.com/getdownload/ska08912dsa'
}

Ao processar a resposta, você injeta um iframeno seu corpo e define o iframeSRC para a URL que você acabou de receber assim (usando jQuery para facilitar o exemplo):

$("body").append("<iframe src='" + data.message +
  "' style='display: none;' ></iframe>");

Se você definiu os cabeçalhos corretos, como mostrado acima, o iframe forçará uma caixa de diálogo de download sem sair do navegador da página atual.

Nota

Adição extra em relação à sua pergunta; Eu acho que é melhor sempre retornar JSON ao solicitar coisas com a tecnologia AJAX. Depois de receber a resposta JSON, você pode decidir do lado do cliente o que fazer com ela. Talvez, por exemplo, mais tarde, você queira que o usuário clique em um link de download para o URL em vez de forçar o download diretamente, na sua configuração atual, você precisará atualizar o cliente e o servidor para fazer isso.

Robin van Baalen
fonte
24

Para aqueles que procuram uma solução de uma perspectiva angular, isso funcionou para mim:

$http.post(
  'url',
  {},
  {responseType: 'arraybuffer'}
).then(function (response) {
  var headers = response.headers();
  var blob = new Blob([response.data],{type:headers['content-type']});
  var link = document.createElement('a');
  link.href = window.URL.createObjectURL(blob);
  link.download = "Filename";
  link.click();
});
Tim Hettler
fonte
Isso ajudou, mas preciso preservar o nome do arquivo original. Vejo o nome do arquivo nos cabeçalhos de resposta em "Disposição de conteúdo", mas não consigo encontrar esse valor no objeto de resposta no código. A configuração link.download = ""resulta em um nome de arquivo guid aleatório e link.download = nullresulta em um arquivo chamado "null".
Marie
@ Marie, você pode gravar o nome do arquivo no momento do upload usando a propriedade INPUTdo elemento HTMLInputElement.files. Consulte Os documentos MDN na entrada do arquivo para obter mais detalhes.
Tim Hettler 23/01
O tamanho do blob é limitado: stackoverflow.com/questions/28307789/…
user0800
22

Aqui está como eu consegui este trabalho https://stackoverflow.com/a/27563953/2845977

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

Resposta atualizada usando o download.js

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

Mayur Padshala
fonte
Obrigado por isso, eu apenas usei isso hoje. Fantástico
Ryan Wilson
Oi, preciso do jQuery 3.0> para que isso funcione?
gbade_
Também estou recebendo um pdf em branco com os dois exemplos que você deu. Estou tentando fazer o download de um arquivo pdf.
Gbade_
@gbade_ Não, você não precisa de nenhuma versão específica do jQuery. Deve funcionar bem com todas as versões. Você verificou se o PDF que você está baixando possui cabeçalhos CORS corretos? Quaisquer erros no console pode ajudar a depuração
Mayur Padshala
Isso funcionou para mim usando download.js:success: function (response, status, request) { download(response, "filename.txt", "application/text"); }
Sga
12

Vejo que você já descobriu uma solução, no entanto, eu só queria adicionar algumas informações que podem ajudar alguém tentando conseguir a mesma coisa com grandes solicitações de POST.

Eu tive o mesmo problema há algumas semanas atrás, na verdade, não é possível obter um download "limpo" através do AJAX, o Filament Group criou um plugin jQuery que funciona exatamente como você já descobriu, é chamado de arquivo jQuery Baixe, no entanto, há uma desvantagem nessa técnica.

Se você estiver enviando grandes solicitações através do AJAX (digamos, arquivos + 1 MB), isso afetará negativamente a capacidade de resposta. Em conexões lentas com a Internet, você terá que esperar muito até que a solicitação seja enviada e também aguardar o download do arquivo. Não é como um instante "clique" => "pop-up" => "início do download". É mais como "clique" => "aguarde até que os dados sejam enviados" => "aguarde a resposta" => "download start", o que faz com que o arquivo apareça dobrar de tamanho, porque você terá que aguardar o envio da solicitação através do AJAX e recupere-o como um arquivo para download.

Se você estiver trabalhando com arquivos pequenos <1 MB, não perceberá isso. Mas, como descobri no meu próprio aplicativo, para tamanhos de arquivo maiores, é quase insuportável.

Meu aplicativo permite que os usuários exportem imagens geradas dinamicamente, essas imagens são enviadas através de solicitações POST no formato base64 para o servidor (é a única maneira possível), processadas e enviadas de volta aos usuários na forma de arquivos .png, .jpg, base64 seqüências de caracteres para imagens + 1 MB são enormes, forçando os usuários a esperar mais do que o necessário para o arquivo iniciar o download. Em conexões lentas com a Internet, pode ser realmente irritante.

Minha solução para isso foi gravar temporariamente o arquivo no servidor, assim que estiver pronto, gerar dinamicamente um link para o arquivo na forma de um botão que muda entre os estados "Aguarde ..." e "Download" e, ao mesmo tempo, Na hora, imprima a imagem base64 em uma janela pop-up para que os usuários possam "clicar com o botão direito" e salvá-la. Isso torna todo o tempo de espera mais suportável para os usuários e também acelera as coisas.

Atualização 30 de setembro de 2014:

Meses se passaram desde que publiquei isso, finalmente encontrei uma abordagem melhor para acelerar as coisas ao trabalhar com grandes seqüências de caracteres base64. Agora, armazeno as seqüências base64 no banco de dados (usando campos longtext ou longblog), depois passo seu ID de registro pelo jQuery File Download, finalmente no arquivo de script de download, consulta o banco de dados usando esse ID para puxar a sequência base64 e passá-la através a função de download.

Faça o download do exemplo de script:

<?php
// Record ID
$downloadID = (int)$_POST['id'];
// Query Data (this example uses CodeIgniter)
$data       = $CI->MyQueries->GetDownload( $downloadID );
// base64 tags are replaced by [removed], so we strip them out
$base64     = base64_decode( preg_replace('#\[removed\]#', '', $data[0]->image) );
// This example is for base64 images
$imgsize    = getimagesize( $base64 );
// Set content headers
header('Content-Disposition: attachment; filename="my-file.png"');
header('Content-type: '.$imgsize['mime']);
// Force download
echo $base64;
?>

Sei que isso está muito além do que o OP pediu, mas achei que seria bom atualizar minha resposta com minhas descobertas. Quando eu estava procurando soluções para o meu problema, li muitos tópicos "Download de dados do AJAX POST" que não me deram a resposta que eu estava procurando, espero que essas informações ajudem alguém que deseja obter algo assim.

José SAYAGO
fonte
O jQuery File Downloadúnico me redireciona para o URL. Eu chamo-lhe assim: jQuery.download("api/ide/download-this-file.php", {filePath: path2Down}, "POST");.
Casper
5

Quero apontar algumas dificuldades que surgem ao usar a técnica na resposta aceita, ou seja, ao usar um formulário:

  1. Você não pode definir cabeçalhos na solicitação. Se o seu esquema de autenticação envolver cabeçalhos, um Json-Web-Token passado no cabeçalho de Autorização, você terá que encontrar outra maneira de enviá-lo, por exemplo, como um parâmetro de consulta.

  2. Você não pode realmente dizer quando a solicitação foi concluída. Bem, você pode usar um cookie que é definido na resposta, como feito por jquery.fileDownload , mas é MUITO perfeito. Ele não funcionará para solicitações simultâneas e será interrompido se uma resposta nunca chegar.

  3. Se o servidor responder com um erro, o usuário será redirecionado para a página de erro.

  4. Você pode usar apenas os tipos de conteúdo suportados por um formulário . O que significa que você não pode usar JSON.

Acabei usando o método de salvar o arquivo no S3 e enviar uma URL pré-assinada para obter o arquivo.

tepez
fonte
5

Para aqueles que procuram uma abordagem mais moderna, você pode usar o fetch API. O exemplo a seguir mostra como baixar um arquivo de planilha. Isso é feito facilmente com o seguinte código.

fetch(url, {
    body: JSON.stringify(data),
    method: 'POST',
    headers: {
        'Content-Type': 'application/json; charset=utf-8'
    },
})
.then(response => response.blob())
.then(response => {
    const blob = new Blob([response], {type: 'application/application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'});
    const downloadUrl = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = downloadUrl;
    a.download = "file.xlsx";
    document.body.appendChild(a);
    a.click();
})

Acredito que essa abordagem seja muito mais fácil de entender do que outras XMLHttpRequestsoluções. Além disso, possui uma sintaxe semelhante à jQueryabordagem, sem a necessidade de adicionar bibliotecas adicionais.

Obviamente, eu recomendaria verificar em qual navegador você está desenvolvendo, pois essa nova abordagem não funcionará no IE. Você pode encontrar a lista completa de compatibilidade do navegador no seguinte [link] [1].

Importante : Neste exemplo, estou enviando uma solicitação JSON para um servidor atendendo ao determinado url. Isso urldeve ser definido, no meu exemplo, estou assumindo que você conhece esta parte. Além disso, considere os cabeçalhos necessários para que sua solicitação funcione. Como estou enviando um JSON, devo adicionar o Content-Typecabeçalho e configurá-lo application/json; charset=utf-8como, para informar ao servidor o tipo de solicitação que ele receberá.

Alain Cruz
fonte
11
Impressionante! Para abrir isso em uma nova guia em vez de em um pop-up de download: use `` `const window = open (downloadUrl," _blank "); if (window! == null) window.focus (); ``
andy
Existe uma maneira de fazer isso para vários conjuntos de dados? Por exemplo, volte {fileOne: data, fileTwo: data, fileThree: data} da chamada da API e gere três arquivos baixados ao mesmo tempo? Obrigado!
il0v3d0g 04/06
Hmmm, eu não tentei isso. Mas você sempre pode compactar as imagens em um arquivo zip e baixá-lo. Vou verificar se é possível.
Alain Cruz
4

Aqui está minha solução usando um formulário oculto temporário.

//Create an hidden form
var form = $('<form>', {'method': 'POST', 'action': this.href}).hide();

//Add params
var params = { ...your params... };
$.each(params, function (k, v) {
    form.append($('<input>', {'type': 'hidden', 'name': k, 'value': v}));
});

//Make it part of the document and submit
$('body').append(form);
form.submit();

//Clean up
form.remove();

Observe que eu uso massivamente o JQuery, mas você pode fazer o mesmo com o JS nativo.

Ludovic Martin
fonte
3

Como já foi dito, você pode criar e enviar um formulário para fazer o download por meio de uma solicitação POST. No entanto, você não precisa fazer isso manualmente.

Uma biblioteca realmente simples para fazer exatamente isso é o jquery.redirect . Ele fornece uma API semelhante ao jQuery.postmétodo padrão :

$.redirect(url, [values, [method, [target]]])
KurtPreston
fonte
3

Esta é uma pergunta de 3 anos, mas eu tive o mesmo problema hoje. Procurei sua solução editada, mas acho que ela pode sacrificar o desempenho, pois precisa fazer uma solicitação dupla. Portanto, se alguém precisar de outra solução que não implique em chamar o serviço duas vezes, é assim que eu fiz:

<form id="export-csv-form" method="POST" action="/the/path/to/file">
    <input type="hidden" name="anyValueToPassTheServer" value="">
</form>

Este formulário é usado apenas para chamar o serviço e evitar o uso de um window.location (). Depois disso, basta enviar um formulário a partir do jquery para chamar o serviço e obter o arquivo. É bem simples, mas dessa maneira você pode fazer um download usando um POST . Agora que isso poderia ser mais fácil se o serviço para o qual você está ligando for um GET , mas esse não é o meu caso.

Jairo Miranda
fonte
11
Esta não é uma pós ajax como a questão está usando ajax
Nidhin David
é apenas uma solução para o problema acima, mas não para chamadas ajax.
Nomi Ali
1

Eu usei este FileSaver.js . No meu caso com arquivos csv, fiz isso (em coffescript):

  $.ajax
    url: "url-to-server"
    data: "data-to-send"
    success: (csvData)->
      blob = new Blob([csvData], { type: 'text/csv' })
      saveAs(blob, "filename.csv")

Para os casos mais complicados, os dados devem ser processados ​​corretamente. Sob o capô, o FileSaver.js implementa a mesma abordagem da resposta de Jonathan Amend .

Armando
fonte
11
..mas você pode baixar arquivos normalmente no iOS?
Alex Marshall
1

Para que Jonathan Amends responda ao trabalho no Edge, fiz as seguintes alterações:

var blob = typeof File === 'function'
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

para isso

var f = typeof File+"";
var blob = f === 'function' && Modernizr.fileapi
    ? new File([this.response], filename, { type: type })
    : new Blob([this.response], { type: type });

Eu preferia ter postado isso como um comentário, mas não tenho reputação suficiente para isso

fstrandner
fonte
0

Aqui está minha solução, reunida de diferentes fontes: Implementação do lado do servidor:

    String contentType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
    // Set headers
    response.setHeader("content-disposition", "attachment; filename =" + fileName);
    response.setContentType(contentType);
    // Copy file to output stream
    ServletOutputStream servletOutputStream = response.getOutputStream();
    try (InputStream inputStream = new FileInputStream(file)) {
        IOUtils.copy(inputStream, servletOutputStream);
    } finally {
        servletOutputStream.flush();
        Utils.closeQuitely(servletOutputStream);
        fileToDownload = null;
    }

Implementação do lado do cliente (usando jquery):

$.ajax({
type: 'POST',
contentType: 'application/json',
    url: <download file url>,
    data: JSON.stringify(postObject),
    error: function(XMLHttpRequest, textStatus, errorThrown) {
        alert(errorThrown);
    },
    success: function(message, textStatus, response) {
       var header = response.getResponseHeader('Content-Disposition');
       var fileName = header.split("=")[1];
       var blob = new Blob([message]);
       var link = document.createElement('a');
       link.href = window.URL.createObjectURL(blob);
       link.download = fileName;
       link.click();
    }
});   
Dvs Prajapati
fonte
0

existe outra solução para baixar uma página da web no ajax. Mas estou me referindo a uma página que deve primeiro ser processada e depois baixada.

Primeiro, você precisa separar o processamento da página do download dos resultados.

1) Apenas os cálculos da página são feitos na chamada ajax.

$ .post ("CalculusPage.php", {calculusFunction: true, ID: 29, data1: "a", data2: "b"},

       função (dados, status) 
       {
            if (status == "sucesso") 
            {
                / * 2) Na resposta, é baixada a página que usa os cálculos anteriores. Por exemplo, isso pode ser uma página que imprime os resultados de uma tabela calculada na chamada ajax. * /
                window.location.href = DownloadPage.php + "? ID =" + 29;
            }               
       }
);

// Por exemplo: no CalculusPage.php

    if (! vazio ($ _ POST ["calculusFunction"]))) 
    {
        $ ID = $ _POST ["ID"];

        $ query = "INSERIR EM ExamplePage (dados1, dados2) VALORES ('". $ _ POST ["dados1"]. "', '". $ _ POST ["dados2"]. "') WHERE id =". $ ID;
        ...
    }

// Por exemplo: no DownloadPage.php

    $ ID = $ _GET ["ID"];

    $ sede = "SELECT * FROM ExamplePage WHERE id =". $ ID;
    ...

    $ filename = "Export_Data.xls";
    cabeçalho ("Tipo de conteúdo: application / vnd.ms-excel");
    header ("Disposição de conteúdo: inline; filename = $ filename");

    ...

Espero que esta solução possa ser útil para muitos, como foi para mim.

netluke
fonte
0

Se a resposta for um buffer de matriz , tente isso no evento onsuccess no Ajax:

 if (event.data instanceof ArrayBuffer) {
          var binary = '';
          var bytes = new Uint8Array(event.data);
          for (var i = 0; i < bytes.byteLength; i++) {
              binary += String.fromCharCode(bytes[i])
          }
          $("#some_id").append("<li><img src=\"data:image/png;base64," + window.btoa(binary) + "\"/></span></li>");
          return;
      }
  • onde event.data é a resposta recebida na função de sucesso do evento xhr.
Abhishek Sinha
fonte
0

Abaixo está minha solução para baixar vários arquivos, dependendo de uma lista que consiste em alguns IDs e procurando no banco de dados, os arquivos serão determinados e prontos para download - se houver. Estou chamando a ação C # MVC para cada arquivo usando o Ajax.

E sim, como outros disseram, é possível fazer isso no jQuery Ajax. Fiz isso com sucesso no Ajax e estou sempre enviando a resposta 200.

Então, esta é a chave:

  success: function (data, textStatus, xhr) {

E este é o meu código:

var i = 0;
var max = 0;
function DownloadMultipleFiles() {
            if ($(".dataTables_scrollBody>tr.selected").length > 0) {
                var list = [];
                showPreloader();
                $(".dataTables_scrollBody>tr.selected").each(function (e) {
                    var element = $(this);
                    var orderid = element.data("orderid");
                    var iscustom = element.data("iscustom");
                    var orderlineid = element.data("orderlineid");
                    var folderPath = "";
                    var fileName = "";

                    list.push({ orderId: orderid, isCustomOrderLine: iscustom, orderLineId: orderlineid, folderPath: folderPath, fileName: fileName });
                });
                i = 0;
                max = list.length;
                DownloadFile(list);
            }
        }

Então chamando:

function DownloadFile(list) {
        $.ajax({
            url: '@Url.Action("OpenFile","OrderLines")',
            type: "post",
            data: list[i],
            xhrFields: {
                responseType: 'blob'
            },
            beforeSend: function (xhr) {
                xhr.setRequestHeader("RequestVerificationToken",
                    $('input:hidden[name="__RequestVerificationToken"]').val());

            },
            success: function (data, textStatus, xhr) {
                // check for a filename
                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 a = document.createElement('a');
                    var url = window.URL.createObjectURL(data);
                    a.href = url;
                    a.download = filename;
                    document.body.append(a);
                    a.click();
                    a.remove();
                    window.URL.revokeObjectURL(url);
                }
                else {
                    getErrorToastMessage("Production file for order line " + list[i].orderLineId + " does not exist");
                }
                i = i + 1;
                if (i < max) {
                    DownloadFile(list);
                }
            },
            error: function (XMLHttpRequest, textStatus, errorThrown) {

            },
            complete: function () {
                if(i===max)
                hidePreloader();
            }
        });
    }

C # MVC:

 [HttpPost]
 [ValidateAntiForgeryToken]
public IActionResult OpenFile(OrderLineSimpleModel model)
        {
            byte[] file = null;

            try
            {
                if (model != null)
                {
                    //code for getting file from api - part is missing here as not important for this example
                    file = apiHandler.Get<byte[]>(downloadApiUrl, token);

                    var contentDispositionHeader = new System.Net.Mime.ContentDisposition
                    {
                        Inline = true,
                        FileName = fileName
                    };
                    //    Response.Headers.Add("Content-Disposition", contentDispositionHeader.ToString() + "; attachment");
                    Response.Headers.Add("Content-Type", "application/pdf");
                    Response.Headers.Add("Content-Disposition", "attachment; filename=" + fileName);
                    Response.Headers.Add("Content-Transfer-Encoding", "binary");
                    Response.Headers.Add("Content-Length", file.Length.ToString());

                }
            }
            catch (Exception ex)
            {
                this.logger.LogError(ex, "Error getting pdf", null);
                return Ok();
            }

            return File(file, System.Net.Mime.MediaTypeNames.Application.Pdf);
        }

Desde que você retorne a resposta 200, o sucesso no Ajax pode funcionar com ele, você pode verificar se o arquivo realmente existe ou não, pois a linha abaixo nesse caso seria falsa e você pode informar o usuário sobre isso:

 if (disposition && disposition.indexOf('attachment') !== -1) {
cócegas
fonte
0

Eu precisava de uma solução semelhante à do @ alain-cruz, mas no nuxt / vue com vários downloads. Eu sei que os navegadores bloqueiam vários downloads de arquivos e também tenho uma API que retorna um conjunto de dados formatados em csv. Se alguém puder me ajudar a melhorar isso, seria ótimo, mas está funcionando para mim até agora.

A API retorna:

data : {
  body: {
    fileOne: ""col1", "col2", "datarow1.1", "datarow1.2"...so on",
    fileTwo: ""col1", "col2"..."
  }
}

page.vue:

<template>
  <b-link @click.prevent="handleFileExport">Export<b-link>
</template>

export default = {
   data() {
     return {
       fileNames: ['fileOne', 'fileTwo'],
     }
   },
  computed: {
    ...mapState({
       fileOne: (state) => state.exportFile.fileOne,
       fileTwo: (state) => state.exportFile.fileTwo,
    }),
  },
  method: {
    handleExport() {
      //exportFileAction in store/exportFile needs to return promise
      this.$store.dispatch('exportFile/exportFileAction', paramsToSend)
        .then(async (response) => {
           const downloadPrep = this.fileNames.map(async (fileName) => {
           // using lodash to get computed data by the file name
           const currentData = await _.get(this, `${fileName}`);
           const currentFileName = fileName;
           return { currentData, currentFileName };
         });
         const response = await Promise.all(downloadPrep);
         return response;
       })
       .then(async (data) => {
         data.forEach(({ currentData, currentFileName }) => {
           this.forceFileDownload(currentData, currentFileName);
         });
       })
       .catch(console.error);
    },
    forceFileDownload(data, fileName) {
     const url = window.URL
         .createObjectURL(new Blob([data], { type: 'text/csv;charset=utf-8;' }));
     const link = document.createElement('a');
     link.href = url;
     link.setAttribute('download', `${fileName}.csv`);
     document.body.appendChild(link);
     link.click();
   },
}
il0v3d0g
fonte