Baixe um arquivo por jQuery.Ajax

420

Eu tenho uma ação do Struts2 no lado do servidor para baixar arquivos.

<action name="download" class="com.xxx.DownAction">
    <result name="success" type="stream">
        <param name="contentType">text/plain</param>
        <param name="inputName">imageStream</param>
        <param name="contentDisposition">attachment;filename={fileName}</param>
        <param name="bufferSize">1024</param>
    </result>
</action>

No entanto, quando eu chamo a ação usando o jQuery:

$.post(
  "/download.action",{
    para1:value1,
    para2:value2
    ....
  },function(data){
      console.info(data);
   }
);

no Firebug, vejo que os dados são recuperados com o fluxo binário . Gostaria de saber como abrir a janela de download de arquivos com a qual o usuário pode salvar o arquivo localmente?

hguser
fonte
1
Marquei-o como duplicado, apesar da diferença de plataforma, porque, pelo que vejo, a solução é a mesma (você não pode e não precisa fazer isso pelo Ajax).
Pekka
1
portanto, sem o ajax, basta usar o window.location = "download.action? para1 = value1 ...."?
Hguser

Respostas:

676

Atualização dos navegadores modernos de 2019

Esta é a abordagem que eu recomendaria agora com algumas ressalvas:

  • É necessário um navegador relativamente moderno
  • Se o arquivo deve ser muito grande, você provavelmente deve fazer algo semelhante à abordagem original (iframe e cookie), porque algumas das operações abaixo provavelmente consumirão memória do sistema pelo menos tão grande quanto o arquivo que está sendo baixado e / ou outra CPU interessante efeitos colaterais.

fetch('https://jsonplaceholder.typicode.com/todos/1')
  .then(resp => resp.blob())
  .then(blob => {
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    // the filename you want
    a.download = 'todo-1.json';
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);
    alert('your file has downloaded!'); // or you know, something with better UX...
  })
  .catch(() => alert('oh no!'));

2012 Abordagem baseada em jQuery / iframe / cookie original em 2012

Bluish está completamente certo sobre isso, você não pode fazer isso através do Ajax porque o JavaScript não pode salvar arquivos diretamente no computador do usuário (por questões de segurança). Infelizmente, apontar o URL da janela principal para o download do arquivo significa que você tem pouco controle sobre a experiência do usuário quando ocorre o download do arquivo.

Criei o jQuery File Download, que permite uma experiência "semelhante ao Ajax" com downloads de arquivos completos com retornos de chamada OnSuccess e OnFailure para proporcionar uma melhor experiência ao usuário. Dê uma olhada no meu post no blog sobre o problema comum que o plug-in resolve e algumas maneiras de usá-lo e também uma demonstração do jQuery File Download em ação . Aqui está a fonte

Aqui está uma demonstração simples de caso de uso usando a fonte do plugin com promessas. A página de demonstração inclui muitos outros exemplos de 'melhor UX'.

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

Dependendo de quais navegadores você precisa oferecer suporte, você poderá usar https://github.com/eligrey/FileSaver.js/, que permite um controle mais explícito do que o método IFRAME que o jQuery File Download usa.

John Culviner
fonte
69
Adoro o que você construiu, mas suspeito que, para obter mais crédito no StackOverFlow, sua resposta aqui deve conter um pouco mais de detalhes. Especificamente sobre como você resolveu o problema.
AnthonyVO
14
seria bom se você mencionasse exatamente como esse "plugin" contorna a limitação, em vez de nos forçar a ir ao seu blog / fonte de plugins para vê-lo. por exemplo, é postado em um iframe? Em vez disso, é necessário que o script remoto salve o arquivo e retorne um URL para ele?
Kevin B
2
@asgerhallas Claro, mas isso é completamente inútil se o link desaparecer.
Kevin B
26
Concordo que um blog é um lugar muito melhor para colocar uma longa descrição de como usar seu plugin e como ele funciona. mas você poderia, pelo menos, fornecer uma breve visão geral de como esse plug-in resolve o problema. Por exemplo, isso resolve o problema fazendo com que o servidor defina um cookie e seu javascript procure continuamente o cookie até que ele exista. Uma vez que exista, podemos assumir que o download está completo. Com esse tipo de informação, é possível criar facilmente sua própria solução rapidamente, e a resposta não depende mais de 100% do seu blog / plugin / jquery e pode ser aplicada a outras bibliotecas.
Kevin B
1
Royi, a meu ver, o AJAX nunca pode suportar downloads de arquivos que resultam em um pop-up de download de arquivos para salvar em disco. Você encontrou uma maneira que eu não conheço?
John Culviner
228

Ninguém postou esta solução da @ Pekka ... então eu a postarei. Pode ajudar alguém.

Você não precisa fazer isso através do Ajax. Apenas use

window.location="download.action?para1=value1...."
azulado
fonte
4
Nice one ... como eu estava lutando com a manipulação do arquivo de download rápido e usando jquery ajax..and esta solução funciona perfeitamente para mim .. + 1
swapnesh
45
Note que isto requer que o servidor esteja definindo um valor de cabeçalho Content-Disposition da 'anexo', caso contrário, o navegador irá redirecionar para (e exibição) o conteúdo da resposta
brichins
21
Ou use alternativamente window.open(<url>, '_blank');para garantir que o download não substitua o conteúdo atual do navegador (independentemente do cabeçalho Disposição de conteúdo).
19620 Christopher King
4
O problema com esta solução é que, se a operação falhar / o servidor retornar um erro, sua página será redirecionada para a página de erro. Para resolver isso, use a solução iFrame
kofifus
4
O verdadeiro problema com esta solução - a pergunta é sobre POSTsolicitação.
Atomosk 28/05/19
35

Você pode com HTML5

NB: Os dados do arquivo retornados DEVEM ser codificados em base64 porque você não pode codificar JSON dados binários

Na minha AJAXresposta, tenho uma estrutura de dados que se parece com isso:

{
    result: 'OK',
    download: {
        mimetype: string(mimetype in the form 'major/minor'),
        filename: string(the name of the file to download),
        data: base64(the binary data as base64 to download)
    }
}

Isso significa que eu posso fazer o seguinte para salvar um arquivo via AJAX

var a = document.createElement('a');
if (window.URL && window.Blob && ('download' in a) && window.atob) {
    // Do it the HTML5 compliant way
    var blob = base64ToBlob(result.download.data, result.download.mimetype);
    var url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = result.download.filename;
    a.click();
    window.URL.revokeObjectURL(url);
}

A função base64ToBlob foi retirada daqui e deve ser usada em conformidade com esta função

function base64ToBlob(base64, mimetype, slicesize) {
    if (!window.atob || !window.Uint8Array) {
        // The current browser doesn't have the atob function. Cannot continue
        return null;
    }
    mimetype = mimetype || '';
    slicesize = slicesize || 512;
    var bytechars = atob(base64);
    var bytearrays = [];
    for (var offset = 0; offset < bytechars.length; offset += slicesize) {
        var slice = bytechars.slice(offset, offset + slicesize);
        var bytenums = new Array(slice.length);
        for (var i = 0; i < slice.length; i++) {
            bytenums[i] = slice.charCodeAt(i);
        }
        var bytearray = new Uint8Array(bytenums);
        bytearrays[bytearrays.length] = bytearray;
    }
    return new Blob(bytearrays, {type: mimetype});
};

Isso é bom se o servidor estiver despejando dados arquivados para serem salvos. No entanto, eu ainda não descobri como implementar um fallback HTML4

Luke Madhanga
fonte
1
O a.click()parece não funcionar no firefox ... Alguma idéia?
Bigpony
Em alguns navegadores, pode ser necessário adicionar o aao dom para que este código funcione e / ou remova a revokeObjectURLparte:document.body.appendChild(a)
bigpony
salvou o meu dia (e possivelmente um trabalho também :)) Não é um especialista em javascript por qualquer medida ... mais cara de java. No entanto, não tenho idéia do porquê um simples "createObjectURL (novo Blob ([atob (base64)])))" não funciona! Simplesmente não, enquanto todo instinto diz que deve. Grrr ...
apil.tamang
na linha var bytechars = atob(base64)emite um erro JavaScript runtime error: InvalidCharacterError. Estou usando a versão 75.0.3770.142 do Chrome, mas não sei o que há de errado aqui.
Muflix 7/08/19
27

1. Independente da estrutura: Servlet baixando arquivo como anexo

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

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

2. Struts2 Framework: Ação baixando arquivo como anexo

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

<!-- without JS -->
<a href="downloadAction.action?param1=value1" >download</a>

Seria melhor usar a <s:a>tag apontando com OGNL para um URL criado com a <s:url>tag:

<!-- without JS, with Struts tags: THE RIGHT WAY -->    
<s:url action="downloadAction.action" var="url">
    <s:param name="param1">value1</s:param>
</s:ulr>
<s:a href="%{url}" >download</s:a>

Nos casos acima, você precisa gravar o cabeçalho Disposição de Conteúdo na resposta , especificando que o arquivo precisa ser baixado ( attachment) e não aberto pelo navegador ( inline). Você também precisa especificar o Tipo de conteúdo e adicionar o nome e o comprimento do arquivo (para ajudar o navegador a desenhar uma barra de progresso realista).

Por exemplo, ao baixar um ZIP:

response.setContentType("application/zip");
response.addHeader("Content-Disposition", 
                   "attachment; filename=\"name of my file.zip\"");
response.setHeader("Content-Length", myFile.length()); // or myByte[].length...

Com o Struts2 (a menos que você esteja usando a Ação como um Servlet, um hack para transmissão direta , por exemplo), você não precisa escrever nada diretamente na resposta; simplesmente usar o tipo de resultado Stream e configurá-lo em struts.xml funcionará: EXEMPLO

<result name="success" type="stream">
   <param name="contentType">application/zip</param>
   <param name="contentDisposition">attachment;filename="${fileName}"</param>
   <param name="contentLength">${fileLength}</param>
</result>

3. Agnóstico da estrutura (estrutura / Struts2): arquivo de abertura do servlet (/ Ação) dentro do navegador

Se você deseja abrir o arquivo dentro do navegador, em vez de baixá-lo, a disposição do Conteúdo deve ser configurada para embutida , mas o destino não pode ser o local atual da janela; você deve direcionar uma nova janela criada por javascript, uma <iframe>na página ou uma nova janela criada on-the-fly com o "discutido" target = "_ blank":

<!-- From a parent page into an IFrame without javascript -->   
<a href="downloadServlet?param1=value1" target="iFrameName">
    download
</a>

<!-- In a new window without javascript --> 
<a href="downloadServlet?param1=value1" target="_blank">
    download
</a>

<!-- In a new window with javascript -->    
<a href="javascript:window.open('downloadServlet?param1=value1');" >
    download
</a>
Andrea Ligios
fonte
2
Senhor, sua opinião: "Content-Disposition", "inline; .... salvou o dia do pobre codificador :)
Vedran Maricevic.
1
Esta é a única resposta que menciona "window.open" (um dos comentários menciona).
Andrew Koster
Não funciona se você tiver muitos parâmetros, pois receberá um too long urlerro.
Muflix 7/08/19
25

A maneira simples de fazer o navegador baixar um arquivo é fazer a solicitação assim:

 function downloadFile(urlToSend) {
     var req = new XMLHttpRequest();
     req.open("GET", urlToSend, true);
     req.responseType = "blob";
     req.onload = function (event) {
         var blob = req.response;
         var fileName = req.getResponseHeader("fileName") //if you have the fileName header available
         var link=document.createElement('a');
         link.href=window.URL.createObjectURL(blob);
         link.download=fileName;
         link.click();
     };

     req.send();
 }

Isso abre o pop-up de download do navegador.

João Marcos
fonte
3
Obrigado, eu usei esta solução. Funcionou como um encanto. Além disso, se você não receber um blob da resposta, basta criar um novo blob.
Fabio.sang #
6
Uma versão melhorada com o link de
iniciando com
O link de @startsWith_R realmente ajuda se você estiver trabalhando com IE11
alexventuraio
Obrigado, funcionou para mim!
Zaki Mohammed
23

Criei pouca função como solução alternativa (inspirada no plugin @JohnCulviner):

// creates iframe and form in it with hidden field,
// then submit form with provided data
// url - form url
// data - data to form field
// input_name - form hidden input name

function ajax_download(url, data, input_name) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" +
                  "<input type=hidden name='" + input_name + "' value='" +
                  JSON.stringify(data) +"'/></form>" +
                  "</body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Demonstração com evento de clique:

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2}, 'dataname');
});
ndpu
fonte
Isso envia os dados de uma maneira muito estranha para o servidor. Gostaria de saber se poderia ser alterado para criar POST compatível?
precisa
16

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. Fiz uma solicitação POST aqui. Em vez disso, você pode optar por um simples GET também. Não podemos baixar o arquivo através do Ajax, é necessário usar XMLHttpRequest.

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
"Não podemos baixar o arquivo através do Ajax, é necessário usar XMLHttpRequest". XMLHttpRequest é AJAX por definição. Caso contrário, ótima solução para navegadores da web modernos. Para o IE, que não oferece suporte HTMLAnchorElement.download, estou pensando em combiná-lo com o método proprietário msSaveOrOpenBlob .
Tsahi Asher
15

Ok, com base no código do ndpu, aqui está uma versão melhorada (eu acho) do ajax_download; -

function ajax_download(url, data) {
    var $iframe,
        iframe_doc,
        iframe_html;

    if (($iframe = $('#download_iframe')).length === 0) {
        $iframe = $("<iframe id='download_iframe'" +
                    " style='display: none' src='about:blank'></iframe>"
                   ).appendTo("body");
    }

    iframe_doc = $iframe[0].contentWindow || $iframe[0].contentDocument;
    if (iframe_doc.document) {
        iframe_doc = iframe_doc.document;
    }

    iframe_html = "<html><head></head><body><form method='POST' action='" +
                  url +"'>" 

    Object.keys(data).forEach(function(key){
        iframe_html += "<input type='hidden' name='"+key+"' value='"+data[key]+"'>";

    });

        iframe_html +="</form></body></html>";

    iframe_doc.open();
    iframe_doc.write(iframe_html);
    $(iframe_doc).find('form').submit();
}

Use isso assim; -

$('#someid').on('click', function() {
    ajax_download('/download.action', {'para1': 1, 'para2': 2});
});

Os parâmetros são enviados como pós-parâmetros adequados, como se viessem de uma entrada, e não como uma string codificada por json, como no exemplo anterior.

CAVEAT: Tenha cuidado com o potencial de injeção variável nesses formulários. Pode haver uma maneira mais segura de codificar essas variáveis. Como alternativa, considere escapar deles.

Shayne
fonte
Este é um exemplo de trabalho. Obrigado. É possível fazer isso sem iframe, mas sem window.location?
Marek Bar
Suponho que você possa simplesmente anexar o formulário oculto na parte inferior do DOM. Também vale a pena explorar o uso do dom Shadow, embora isso não seja necessariamente bem suportado em navegadores mais antigos.
quer
Neste código, estou recebendo esse erro. Uncaught SecurityError: Blocked a frame with origin "http://foo.bar.com" from accessing a frame with origin "null". The frame requesting access has a protocol of "http", the frame being accessed has a protocol of "data". Protocols must match.
vazio
Como posso mapear este formulário para alguma classe de modelo? Eu tenho: @ResourceMapping() public void downloadFile(final ResourceRequest request, final ResourceResponse response, @ModelAttribute("downForm") FormModel model) mas não está funcionando ..
bartex9 12/01
nulo: Isso provavelmente seria algum tipo de problema de segurança entre origens. Provavelmente é uma questão de estouro de pilha inteira. @ bartex9: Isso dependeria muito do tipo de estrutura que você está usando. Mas o princípio seria a de tomar o nome eo caminho e armazenar que, embora empurrando o próprio arquivo em uma web área acessível do sistema de arquivos, ou algo como Amazon S3 para alta disponibilidade
Shayne
8

Aqui está o que eu fiz, javascript puro e html. Não o testou, mas isso deve funcionar em todos os navegadores.

Função Javascript

var iframe = document.createElement('iframe');
iframe.id = "IFRAMEID";
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.src = 'SERVERURL'+'?' + $.param($scope.filtro);
iframe.addEventListener("load", function () {
     console.log("FILE LOAD DONE.. Download should start now");
});

Usando apenas componentes suportados em todos os navegadores, não há bibliotecas adicionais.

insira a descrição da imagem aqui insira a descrição da imagem aqui

Aqui está o código do meu controlador JAVA Spring do lado do servidor.

@RequestMapping(value = "/rootto/my/xlsx", method = RequestMethod.GET)
public void downloadExcelFile(@RequestParam(value = "param1", required = false) String param1,
    HttpServletRequest request, HttpServletResponse response)
            throws ParseException {

    Workbook wb = service.getWorkbook(param1);
    if (wb != null) {
        try {
            String fileName = "myfile_" + sdf.format(new Date());
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-disposition", "attachment; filename=\"" + fileName + ".xlsx\"");
            wb.write(response.getOutputStream());
            response.getOutputStream().close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    }
manukyanv07
fonte
parece que seu evento de carregamento não é chamado para conteúdo de anexo de disposição de conteúdo (porque nada é carregado no iframe), se funcionar para você (você obtém o console.log) pls postar uma amostra
kofifus
Aqui está um violão rápido jsfiddle.net/y2xezyoj, que aciona o evento load assim que o arquivo pdf é carregado no iframe .. esse violino não é baixado porque a chave para o download está no lado do servidor "response.setHeader (" Conteúdo -disposição "," anexo ; nome do arquivo = \ "" + nome do arquivo + ".xlsx \" ");"
manukyanv07
1
sim, funcionará nesse caso, mas se o arquivo for baixado, ou seja, o servidor envia Content-Disposition: attachment, o evento load não será
acionado
Você está totalmente certo: o evento de carregamento é acionado logo após o processamento do servidor começar a enviar o arquivo. Era isso que eu estava procurando: 1 - bloqueie o botão e mostre o processamento para que o usuário possa ter um feedback de que as coisas estão acontecendo. 2 - Então, quando o servidor concluir o processamento e prestes a enviar o arquivo 3- (o evento de carregamento é acionado), onde eu destranco o botão e removo o botão giratório de processamento 4 - o usuário agora aparece com salvar arquivo ou o navegador inicia o download em o local de download definido. Desculpe meu inglês.
precisa saber é o seguinte
5
function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}
EL missaoui habib
fonte
Você poderia explicar sua resposta? Isso ajudaria outras pessoas a entender o que você fez para que pudessem aplicar suas técnicas às situações deles.
Wai Ha Lee
2
Apenas um aviso: não Safari e IE não suporta o downloadatributo, para que o seu ficheiro vai acabar por ter o nome de "Desconhecido"
Yangshun Tay
4

No Rails, faço da seguinte maneira:

function download_file(file_id) {
  let url       = '/files/' + file_id + '/download_file';
    $.ajax({
    type: 'GET',
    url: url,
    processData: false,
    success: function (data) {
       window.location = url;
    },
    error: function (xhr) {
     console.log(' Error:  >>>> ' + JSON.stringify(xhr));
    }
   });
 }

O truque é a parte window.location . O método do controlador é semelhante a:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end
aarkerio
fonte
2
Pergunta rápida, isso não gerará o arquivo duas vezes? Depois de enviar a solicitação ajax. Então você faz a página redirecionar para o mesmo URL também. Como podemos eliminar isso?
Codificadorhs #
Não no meu caso. Só o testei no Chrome.
aarkerio
Como coderhs já afirma corretamente, a ação é chamada duas vezes.
Sven
Também está sendo chamado duas vezes para mim.
CSquared
4

Use window.open https://developer.mozilla.org/en-US/docs/Web/API/Window/open

Por exemplo, você pode colocar esta linha de código em um manipulador de cliques:

window.open('/file.txt', '_blank');

Ele abrirá uma nova guia (por causa do nome da janela '_blank') e essa guia abrirá o URL.

Seu código do servidor também deve ter algo como isto:

res.set('Content-Disposition', 'attachment; filename=file.txt');

Dessa forma, o navegador deve solicitar ao usuário que salve o arquivo no disco, em vez de apenas mostrar o arquivo. Também fechará automaticamente a guia que acabou de abrir.

Andrew Koster
fonte
4

Tento baixar um arquivo CSV e, em seguida, faço algo após a conclusão do download. Então, eu preciso implementar uma adequadacallback função .

Usando window.location="..." não é uma boa ideia, porque não consigo operar o programa após concluir o download. Algo assim, mude o cabeçalho para que não seja uma boa ideia.

fetché uma boa alternativa, no entanto , não suporta o IE 11 . E window.URL.createObjectURLnão suporta o IE 11. Você pode consultar isso .

Este é o meu código, é semelhante ao código de Shahrukh Alam. Mas você deve tomar cuidado para window.URL.createObjectURLtalvez criar vazamentos de memória. Você pode consultar isso . Quando a resposta chegar, os dados serão armazenados na memória do navegador. Portanto, antes de clicar no alink, o arquivo foi baixado. Isso significa que você pode fazer qualquer coisa após o download.

$.ajax({
    url: 'your download url',
    type: 'GET',
}).done(function (data, textStatus, request) {
    // csv => Blob
    var blob = new Blob([data]);

    // the file name from server.
    var fileName = request.getResponseHeader('fileName');

    if (window.navigator && window.navigator.msSaveOrOpenBlob) { // for IE
    window.navigator.msSaveOrOpenBlob(blob, fileName);
    } else { // for others
    var url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.style.display = 'none';
    a.href = url;
    a.download = fileName;
    document.body.appendChild(a);
    a.click();
    window.URL.revokeObjectURL(url);

    //Do something after download 
    ...

    }
}).then(after_download)
}
kyakya
fonte
4

Como baixar um arquivo após recebê-lo pelo AJAX

É conveniente quando o arquivo é criado por um longo tempo e você precisa mostrar PRELOADER

Exemplo ao enviar um formulário da web:

<script>
$(function () {
    $('form').submit(function () {
        $('#loader').show();
        $.ajax({
            url: $(this).attr('action'),
            data: $(this).serialize(),
            dataType: 'binary',
            xhrFields: {
                'responseType': 'blob'
            },
            success: function(data, status, xhr) {
                $('#loader').hide();
                // if(data.type.indexOf('text/html') != -1){//If instead of a file you get an error page
                //     var reader = new FileReader();
                //     reader.readAsText(data);
                //     reader.onload = function() {alert(reader.result);};
                //     return;
                // }
                var link = document.createElement('a'),
                    filename = 'file.xlsx';
                // if(xhr.getResponseHeader('Content-Disposition')){//filename 
                //     filename = xhr.getResponseHeader('Content-Disposition');
                //     filename=filename.match(/filename="(.*?)"/)[1];
                //     filename=decodeURIComponent(escape(filename));
                // }
                link.href = URL.createObjectURL(data);
                link.download = filename;
                link.click();
            }
        });
        return false;
    });
});
</script>

O funcional opcional é comentado para simplificar o exemplo.

Não há necessidade de criar arquivos temporários no servidor.

No jQuery v2.2.4, clique em OK. Haverá um erro na versão antiga:

Uncaught DOMException: Failed to read the 'responseText' property from 'XMLHttpRequest': The value is only accessible if the object's 'responseType' is '' or 'text' (was 'blob').
Mike S
fonte
Para obter o nome do arquivo no Content-Disposition, essa correspondência funcionou para mim: filename.match(/filename=(.*)/)[1](sem as aspas duplas ou o ponto de interrogação) - regex101.com/r/2AsD4y/2 . No entanto, sua solução foi a única solução que funcionou após muita pesquisa.
jstuardo
3

Adicionando mais algumas coisas à resposta acima para baixar um arquivo

Abaixo está um código java spring que gera uma matriz de bytes

@RequestMapping(value = "/downloadReport", method = { RequestMethod.POST })
    public ResponseEntity<byte[]> downloadReport(
            @RequestBody final SomeObejct obj, HttpServletResponse response) throws Exception {

        OutputStream out = new ByteArrayOutputStream();
        // write something to output stream
        HttpHeaders respHeaders = new HttpHeaders();
        respHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
        respHeaders.add("X-File-Name", name);
        ByteArrayOutputStream bos = (ByteArrayOutputStream) out;
        return new ResponseEntity<byte[]>(bos.toByteArray(), respHeaders, HttpStatus.CREATED);
    }

Agora no código javascript usando o FileSaver.js, é possível baixar um arquivo com o código abaixo

var json=angular.toJson("somejsobject");
var url=apiEndPoint+'some url';
var xhr = new XMLHttpRequest();
//headers('X-File-Name')
xhr.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 201) {
        var res = this.response;
        var fileName=this.getResponseHeader('X-File-Name');
        var data = new Blob([res]);
        saveAs(data, fileName); //this from FileSaver.js
    }
}    
xhr.open('POST', url);
xhr.setRequestHeader('Authorization','Bearer ' + token);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.send(json);

O arquivo acima fará o download do arquivo

dario nascimento
fonte
2

Ok, então aqui está o código de funcionamento ao usar o MVC e você está obtendo seu arquivo de um controlador

digamos que você tenha sua matriz de bytes declarada e preenchida, a única coisa que você precisa fazer é usar a função Arquivo (usando System.Web.Mvc)

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

e depois, no mesmo controlador, adicione estas 2 funções

protected override void OnResultExecuting(ResultExecutingContext context)
    {
        CheckAndHandleFileResult(context);

        base.OnResultExecuting(context);
    }

    private const string FILE_DOWNLOAD_COOKIE_NAME = "fileDownload";

    /// <summary>
    /// If the current response is a FileResult (an MVC base class for files) then write a
    /// cookie to inform jquery.fileDownload that a successful file download has occured
    /// </summary>
    /// <param name="context"></param>
    private void CheckAndHandleFileResult(ResultExecutingContext context)
    {
        if (context.Result is FileResult)
            //jquery.fileDownload uses this cookie to determine that a file download has completed successfully
            Response.SetCookie(new HttpCookie(FILE_DOWNLOAD_COOKIE_NAME, "true") { Path = "/" });
        else
            //ensure that the cookie is removed in case someone did a file download without using jquery.fileDownload
            if (Request.Cookies[FILE_DOWNLOAD_COOKIE_NAME] != null)
                Response.Cookies[FILE_DOWNLOAD_COOKIE_NAME].Expires = DateTime.Now.AddYears(-1);
    }

e você poderá ligar para o seu controlador para fazer o download e obter o retorno de chamada "sucesso" ou "falha"

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

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });
Yannick Richard
fonte
1

Eu encontrei uma correção que, embora não esteja usando o ajax, permite que você use uma chamada javascript para solicitar o download e obtenha um retorno de chamada quando o download realmente começar. Achei isso útil se o link executar um script do lado do servidor que demore um pouco para compor o arquivo antes de enviá-lo. para que você possa alertá-los de que está processando e, quando finalmente enviar o arquivo, remova a notificação de processamento. é por isso que eu queria tentar carregar o arquivo via ajax para começar, para que um evento acontecesse quando o arquivo fosse solicitado e outro quando ele realmente iniciar o download.

os js na primeira página

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

o iframe

<div id="exportdiv" style="display:none;">
<img src="loader.gif"><br><h1>Generating Report</h1>
<iframe id="exportif" src="" style="width: 1px;height: 1px; border:0px;"></iframe>
</div>

então o outro arquivo:

<!DOCTYPE html>
<html>
<head>
<script>
function expdone()
{
    window.parent.expdone();
}
</script>
</head>
<body>
<iframe id="exportif" src="<?php echo "http://10.192.37.211/npdtracker/exportthismonth.php?arguments=".$_GET["arguments"]; ?>"></iframe>
<script>document.getElementById('exportif').onload= expdone;</script>
</body></html>

Eu acho que há uma maneira de ler obter dados usando js, ​​então nenhum php seria necessário. mas eu não sei de antemão e o servidor que estou usando suporta php, então isso funciona para mim. pensei em compartilhá-lo no caso de ajudar alguém.

Kit Ramos
fonte
0

Se você deseja usar o Download de arquivo jQuery, observe isso no IE. Você precisa redefinir a resposta ou ela não será baixada

    //The IE will only work if you reset response
    getServletResponse().reset();
    //The jquery.fileDownload needs a cookie be set
    getServletResponse().setHeader("Set-Cookie", "fileDownload=true; path=/");
    //Do the reset of your action create InputStream and return

Sua ação pode ser implementada ServletResponseAware para acessargetServletResponse()

Alireza Fattahi
fonte
0

É certo que você não pode fazê-lo através da chamada Ajax.

No entanto, há uma solução alternativa.

Passos :

Se você estiver usando o form.submit () para baixar o arquivo, o que você pode fazer é:

  1. Crie uma chamada ajax do cliente para o servidor e armazene o fluxo de arquivos dentro da sessão.
  2. Após o "sucesso" ser retornado do servidor, chame seu form.submit () para apenas transmitir o fluxo de arquivos armazenados na sessão.

Isso é útil no caso de você decidir se o arquivo precisa ou não ser baixado após fazer o formulário.submit (), por exemplo: pode haver um caso em que no formulário.submit () ocorre uma exceção no servidor e, em vez disso, do travamento, pode ser necessário mostrar uma mensagem personalizada no lado do cliente; nesse caso, essa implementação pode ajudar.

Aman Srivastava
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
The HTML Code:-

'<button type="button" id="GetFile">Get File!</button>'


The jQuery Code:-

'$('#GetFile').on('click', function () {
    $.ajax({
        url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/172905/test.pdf',
        method: 'GET',
        xhrFields: {
            responseType: 'blob'
        },
        success: function (data) {
            var a = document.createElement('a');
            var url = window.URL.createObjectURL(data);
            a.href = url;
            a.download = 'myfile.pdf';
            document.body.append(a);
            a.click();
            a.remove();
            window.URL.revokeObjectURL(url);
        }
    });
});'
Shahrukh Alam
fonte
As respostas somente de código devem ter pelo menos uma descrição mínima explicando como o código funciona e por que ele responde à pergunta.
Roberto Caboni
0

É isso que funciona tão bem em qualquer navegador (estou usando o núcleo do asp.net)

            function onDownload() {

  const api = '@Url.Action("myaction", "mycontroller")'; 
  var form = new FormData(document.getElementById('form1'));

  fetch(api, { body: form, method: "POST"})
      .then(resp => resp.blob())
      .then(blob => {
          const url = window.URL.createObjectURL(blob);
        $('#linkdownload').attr('download', 'Attachement.zip');
          $('#linkdownload').attr("href", url);
          $('#linkdownload')
              .fadeIn(3000,
                  function() { });

      })
      .catch(() => alert('An error occurred'));



}
 
 <button type="button" onclick="onDownload()" class="btn btn-primary btn-sm">Click to Process Files</button>
 
 
 
 <a role="button" href="#" style="display: none" class="btn btn-sm btn-secondary" id="linkdownload">Click to download Attachments</a>
 
 
 <form asp-controller="mycontroller" asp-action="myaction" id="form1"></form>
 
 

        function onDownload() {
            const api = '@Url.Action("myaction", "mycontroller")'; 
            //form1 is your id form, and to get data content of form
            var form = new FormData(document.getElementById('form1'));

            fetch(api, { body: form, method: "POST"})
                .then(resp => resp.blob())
                .then(blob => {
                    const url = window.URL.createObjectURL(blob);
                    $('#linkdownload').attr('download', 'Attachments.zip');
                    $('#linkdownload').attr("href", url);
                    $('#linkdownload')
                        .fadeIn(3000,
                            function() {

                            });
                })
                .catch(() => alert('An error occurred'));                 

        }
Marinpietri
fonte
-1

Eu lutei com esse problema por um longo tempo. Finalmente, uma elegante biblioteca externa sugerida aqui me ajudou.

Eerik Sven Puudist
fonte