javascript - with - js download ajax




Baixe um arquivo por jQuery.Ajax (11)

Eu tenho uma ação Struts2 no lado do servidor para download de 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 os dados serem recuperados com o fluxo binário . Gostaria de saber como abrir a janela de download de arquivos com o qual o usuário pode salvar o arquivo localmente?


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

No entanto, existe uma solução alternativa.

Passos :

Se você estiver usando form.submit () para fazer o download do 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 arquivo armazenado na sessão.

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


A maneira simples de fazer o navegador fazer o download de um arquivo é fazer o pedido 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.


Aqui está o que eu fiz, javascript puro e html. Não testei, 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 que são suportados em todos os navegadores, não há bibliotecas adicionais.

Aqui está o código do 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();
        }
    }
    }

Eu criei pouca função como solução alternativa (inspirada pelo 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');
});

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

" Poste dados JSON no servidor e receba um arquivo excel. Esse arquivo excel é criado pelo servidor e retornado como uma 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 trecho acima é apenas o seguinte

  • Postando uma matriz como JSON para o servidor usando XMLHttpRequest.
  • Depois de 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. Eu 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, devemos usar XMLHttpRequest.

Aqui precisamos definir cuidadosamente algumas coisas no lado do servidor. Eu coloquei alguns cabeçalhos no Python Django HttpResponse. Você precisa configurá-los adequadamente 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 o xls (excel) aqui, ajustei o contentType para acima de um. Você precisa configurá-lo de acordo com o seu tipo de arquivo. Você pode usar essa técnica para baixar qualquer tipo de arquivo.


No Rails, eu faço assim:

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 se parece com:

# GET /files/{:id}/download_file/
def download_file
    send_file(@file.file,
          :disposition => 'attachment',
          :url_based_filename => false)
end

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

digamos que você tenha sua matriz de bytes declarar e preencher, a única coisa que você precisa fazer é usar a função File (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 essas duas 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, em seguida, você poderá chamar seu controlador para fazer o download e obter o retorno de chamada de "sucesso" ou "falha"

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

            },
            failCallback: function (html, url) {
            //insert fail code
            }
        });

Se você quiser usar o download do arquivo jQuery, observe isso para o IE. Você precisa redefinir a resposta ou não fará download

    //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 implementar o ServletResponseAware para acessar getServletResponse()


1. Framework agnóstico: arquivo de download de servlet como anexo

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

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

2. Framework Struts2: arquivo de download de ação 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 <s:a> tag <s:a> apontando com OGNL para uma URL criada com a tag <s:url> :

<!-- 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 escrever o cabeçalho Content-Disposition 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 o Action como um Servlet, um hack para streaming direto , por exemplo), você não precisa escrever diretamente nada na resposta; simplesmente usando o tipo de resultado Stream e configurando-o em struts.xml funcionará: EXAMPLE

<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. Quadro agnóstico (/ framework Struts2): Servlet (/ Action) abrindo arquivo dentro do navegador

Se você deseja abrir o arquivo dentro do navegador, em vez de baixá-lo, a disposição de conteúdo deve ser definida como inline , mas o destino não pode ser o local da janela atual; você deve segmentar uma nova janela criada por javascript, um <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>

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 resposta AJAX eu 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 tirada here 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 seu servidor está descarregando arquivos para serem salvos. No entanto, ainda não consegui descobrir como implementar um fallback HTML4


function downloadURI(uri, name) 
{
    var link = document.createElement("a");
    link.download = name;
    link.href = uri;
    link.click();
}




download