Pobierz i otwórz plik PDF za pomocą Ajax

98

Mam klasę akcji, która generuje plik PDF. contentTypeJest ustawiony prawidłowo.

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

Wołam to action przez połączenie Ajax. Nie znam sposobu dostarczenia tego strumienia do przeglądarki. Próbowałem kilku rzeczy, ale nic nie działało.

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

Powyższe podaje błąd:

Twoja przeglądarka wysłała żądanie, którego ten serwer nie mógł zrozumieć.

Nayn
źródło

Odpowiedzi:

37

Niekoniecznie potrzebujesz do tego Ajaksu. Wystarczy <a>link, jeśli ustawisz opcję content-dispositionna attachmentw kodzie po stronie serwera. W ten sposób strona nadrzędna pozostanie otwarta, jeśli to był twój główny problem (dlaczego niepotrzebnie wybrałbyś do tego Ajax?). Poza tym nie ma sposobu, aby ładnie sobie z tym poradzić, asynchronicznie. PDF nie zawiera danych znakowych. To dane binarne. Nie możesz robić takich rzeczy $(element).load(). Chcesz użyć do tego zupełnie nowego żądania. Bo to <a href="pdfservlet/filename.pdf">pdf</a>jest idealne.

Aby bardziej pomóc Ci z kodem po stronie serwera, musisz powiedzieć więcej o używanym języku i zamieścić fragmenty prób kodu.

BalusC
źródło
7
Jeszcze raz: nie potrzebujesz do tego Ajaksu. To tylko proszenie o kłopoty. PDF to dane binarne, a nie dane znakowe, takie jak HTML czy JSON.
BalusC
3
var url = contextPath + "/xyz/blahBlah.action"; url + = url + "?" + parametry; try {var child = window.open (url); child.focus (); } catch (e) {}
Nayn
5
W niektórych przeglądarkach okno window.open pozostanie otwarte i puste, co może być denerwujące dla użytkowników końcowych. Dlatego też NIE używaj do tego window.open. Jeśli content-dispositionjest ustawiony na attachment, otrzymasz po prostu Save asdialog. Strona nadrzędna pozostanie niezmieniona. Po prostu <a href="pdfservlet/filename.pdf">pdf</a>lub a <form action="pdfservlet/filename.pdf"><input type="submit"></form>jest więcej niż wystarczające.
BalusC
5
Istnieje ograniczona długość adresu URL. A autor pyta o POST.
Edward Olamisan
3
Zgadzam się z @EdwardOlamisan, to nie jest poprawna odpowiedź, ponieważ autor próbował POSTdane.
adamj
122

Oto jak to działa

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

Zaktualizowana odpowiedź za pomocą download.js

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

Mayur Padshala
źródło
29
Czy to działa na Chrome? Widzę tylko pusty plik PDF.
Tarun Gupta
1
Tak, działa we wszystkich nowoczesnych przeglądarkach. Jeśli zobaczysz pusty plik PDF, spróbuj uruchomić adres URL AJAX w nowej karcie. Jeśli pojawi się tam również pusty ekran, może to oznaczać problem z samym plikiem PDF. Jeśli zobaczysz tam plik pdf, a nie pobrany plik, daj mi znać na mój e-mail. :)
Mayur Padshala
5
Ten (element kotwicy) faktycznie nie działał dla mnie w IE 11, Edge i Firefox. zmiana sukcesu na zwykłe użycie „window.open (URL.createObjectURL (blob))” zadziałała.
JimiSweden
3
Plik pdf jest pobierany, ale żadna zawartość nie jest dostępna. mam save byte [] po stronie serwera i zawartość pdf jest dostępna. plz sugeruj.
Awanish Kumar
4
pobierany jest pusty plik pdf.
Farukh
31

Nie wydaje mi się, żeby którakolwiek z wcześniejszych odpowiedzi wskazywała na problem z oryginalnym plakatem. Wszyscy zakładają żądanie GET, podczas gdy plakat próbował POST dane i pobrać w odpowiedzi.

W trakcie poszukiwania lepszej odpowiedzi znaleźliśmy tę wtyczkę jQuery do pobierania plików w stylu Ajax .

W swoim „sercu” tworzy „tymczasowy” formularz HTML zawierający podane dane jako pola wejściowe. Ten formularz jest dołączany do dokumentu i publikowany pod żądanym adresem URL. Zaraz po tym formularz jest ponownie usuwany:

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

Odpowiedź Update Mayur wygląda całkiem obiecująco i bardzo prosto w porównaniu z wtyczką jQuery, o której mówiłem.

chiccodoro
źródło
9

W ten sposób rozwiązuję ten problem.
Odpowiedź Jonathana Amenda w tym poście bardzo mi pomogła.
Poniższy przykład jest uproszczony.

Aby uzyskać więcej informacji, powyższy kod źródłowy może pobrać plik przy użyciu żądania JQuery Ajax (GET, POST, PUT itp . ) . Pomaga również w przesyłaniu parametrów jako JSON i zmianie typu zawartości na application / json (moje domyślne) .

Html źródło:

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

Prosty formularz z dwoma tekstami wejściowymi, jednym przyciskiem wyboru i przyciskiem.

Strona javascript źródło:

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

Proste wydarzenie po kliknięciu przycisku. Tworzy obiekt AjaxDownloadFile. Źródło klasy AjaxDownloadFile znajduje się poniżej.

Klasa AjaxDownloadFile źródło:

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

Stworzyłem tę klasę, aby dodać ją do mojej biblioteki JS. Jest wielokrotnego użytku. Mam nadzieję, że to pomoże.

George Siggouroglou
źródło
2
Blobobiekt jest obsługiwany w IE10 +.
zmiażdżyć
Musiałem ustawić responseTypexhr na arraybufferlub blobaby to działało. (W przeciwnym razie działa świetnie.)
tjklemz
Miałem dokładnie to samo pytanie. Wszystkie osoby odpowiadające „po prostu zrób to link” nie pomagają OP. Jeśli Twoja treść jest dynamiczna, a link, do którego się wybierasz, jest dynamiczny, musisz to wszystko sprawdzić ... odpowiedzią dla mnie było umieszczenie formularza na stronie ze wszystkimi ukrytymi danymi wejściowymi (nie jest wyświetlany użytkownikowi) i następnie wypełnij go i prześlij za pomocą jquery. Działa świetnie.
Scott
To świetna odpowiedź, ale z jakiegoś powodu ciągle się psuje pusty plik PDF. Nie mogę tego rozgryźć. Kiedy zwracam ten sam zestaw bajtów przez API - jest w porządku, więc ma to coś wspólnego z odpowiedzią MVC. Używam typu odpowiedzi FileResult: File (bytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Jurijs Kastanovs
Wyjaśnienie: jeśli otworzysz adres URL przez pasek adresu - plik jest poprawnie otwarty. Jeśli używam AJAX + blob, aby pobrać plik - plik jest źle sformułowany.
Jurijs Kastanovs
7

U mnie zadziałał następujący kod, który pobiera funkcja serwera 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
źródło
Działa to idealnie w momencie tego komentarza i najnowszego Chrome
Loredra L,
6

Możesz użyć tej wtyczki, która tworzy formularz i przesyła go, a następnie usuwa ze strony.

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

To zadziałało dla mnie. Znalazłem tę wtyczkę tutaj

Ijas Ameenudeen
źródło
Ta wtyczka po prostu tworzy formularz i przesyła go, a następnie usuwa ze strony. (jeśli ktoś się zastanawiał)
zmiażdż
4

Poniższy kod zadziałał dla mnie

//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
źródło
4

Aby rozwiązać problem z pustym plikiem PDF w żądaniu pocztowym, aby uzyskać dane strumieniowe, takie jak PDF, musimy dodać typ odpowiedzi jako „bufor tablicy” lub „blob” w żądaniu

$.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
źródło
3

Jeśli chodzi o odpowiedź udzieloną przez Mayura Padshalę, jest to poprawna logika pobierania pliku PDF przez ajax, ale jak inni podają w komentarzach, to rozwiązanie faktycznie pobiera pusty plik PDF.

Przyczyna tego jest wyjaśniona w zaakceptowanej odpowiedzi na to pytanie : jQuery ma pewne problemy z ładowaniem danych binarnych przy użyciu żądań AJAX, ponieważ nie implementuje jeszcze niektórych funkcji HTML5 XHR v2, zobacz tę prośbę o ulepszenie i tę dyskusję .

Więc użycie HTMLHTTPRequestkodu powinno wyglądać tak:

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
źródło
2

utwórz ukrytą ramkę iframe, a następnie w swoim kodzie Ajax powyżej:

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

i usuń window.open(response);

qalhat
źródło
To rozwiązanie zadziałało jak urok. Wzywam skrypt po stronie serwera, który wywołuje curl do usługi, która pobiera plik przez curl. Działa to świetnie, ponieważ mogę upuścić ładujący gif i wyłączyć łącze z żądaniem.
eggmatters
1
To rozwiązanie działa w przypadku żądań GET, a nie żądań POST, jak w oryginalnym poście.
chiccodoro
2

Ten fragment kodu jest przeznaczony dla użytkowników angular js, którzy napotkają ten sam problem. Należy zauważyć, że plik odpowiedzi jest pobierany przy użyciu zaprogramowanego zdarzenia kliknięcia. W tym przypadku nagłówki zostały wysłane przez serwer zawierający nazwę pliku i zawartość / typ.

$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
źródło
Napisz wyjaśnienie swojej odpowiedzi.
Gufran Hasan
1

Czy musisz to zrobić z Ajaxem? Czy nie mogłaby być możliwość załadowania go w ramce iframe?

Emil Vikström
źródło
1
Sprawdzam, czy można to zrobić z Ajaxem. Jeśli jest to technicznie niemożliwe lub jest to gorsze podejście, przestawiłbym się na inne podejście.
Nayn
1

Mam nadzieję, że pozwoli to zaoszczędzić kilka godzin i uniknąć bólu głowy. Zajęło mi trochę czasu, zanim to rozgryzłem, ale wykonanie zwykłego żądania $ .ajax () zrujnowało mój plik PDF, podczas gdy żądanie go przez pasek adresu działało idealnie. Rozwiązanie było następujące:

Dołącz download.js: http://danml.com/download.html

Następnie użyj XMLHttpRequest zamiast żądania $ .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
źródło
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
źródło
0

Jeśli musisz pracować ze strumieniem plików (więc nie ma fizycznie zapisanego pliku PDF), tak jak my, i chcesz pobrać plik PDF bez ponownego ładowania strony, działa dla nas następująca funkcja:

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

Ze względu na to, że target = "pdf-download-output" , odpowiedź jest zapisywana w ramce iframe i dlatego nie jest wykonywane ponowne ładowanie strony, ale strumień odpowiedzi pdf jest wysyłany do przeglądarki jako plik do pobrania.

George Maharis
źródło
przepraszam, ale jak otrzymujesz wartość transferData?
Kate