Завантажте та відкрийте файл PDF за допомогою Ajax


98

У мене є клас дій, який генерує PDF. Набір contentTypeвстановлений належним чином.

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

Я називаю це action через дзвінок Ajax. Я не знаю, як доставити цей потік у браузер. Я спробував кілька речей, але нічого не вийшло.

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

Вищезазначене дає помилку:

Ваш браузер надіслав запит, який цей сервер не міг зрозуміти.

Відповіді:


37

Для цього вам не обов’язково потрібен Аякс. Просто <a>посилання досить , якщо ви встановите content-dispositionна attachmentосторонь сервера коду. Таким чином батьківська сторінка просто залишатиметься відкритою, якщо це було вашим головним занепокоєнням (чому б ви без потреби вибрали для цього Ajax?). До того ж, жодним чином неможливо впоратися з цим приємно асинхронно. PDF - це не символьні дані. Це двійкові дані. Ви не можете робити подібні речі $(element).load(). Ви хочете використовувати для цього абсолютно новий запит. Для цього <a href="pdfservlet/filename.pdf">pdf</a>цілком підходить.

Щоб допомогти вам більше з кодом на стороні сервера, вам потрібно буде розповісти більше про використовувану мову та опублікувати фрагмент спроб коду.


7
Ще раз: вам для цього не потрібен Ajax. Це лише прохання про неприємності. PDF - це двійкові дані, а не символьні дані, такі як HTML або JSON.
BalusC

3
var url = contextPath + "/xyz/blahBlah.action"; url + = url + "?" + параметри; спробуйте {var child = window.open (url); дитина.фокус (); } catch (e) {}
Nayn

5
У деяких браузерах window.open залишатиметься відкритим і порожнім, що може дратувати користувачів. Отже, також НЕ використовуйте для цього window.open. Якщо content-dispositionвстановлено значення attachment, ви просто отримаєте Save asдіалог. Батьківська сторінка залишиться незмінною. Просто <a href="pdfservlet/filename.pdf">pdf</a>або a <form action="pdfservlet/filename.pdf"><input type="submit"></form>- більш ніж достатньо.
BalusC

5
Довжина URL-адреси обмежена. А автор запитує про POST.
Едвард Оламісан,

3
Погодьтеся з @EdwardOlamisan, це не правильна відповідь, оскільки автор намагався надати POSTдані.
adamj

122

Ось як я отримав цю роботу

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

Оновлена ​​відповідь за допомогою download.js

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


29
Це працює на хромі? Я бачу лише порожній файл PDF.
Тарун Гупта

1
Так, це працює на всіх сучасних браузерах. Якщо ви бачите порожній файл PDF, спробуйте запустити URL-адресу ajax на новій вкладці. Якщо ви також отримаєте порожній екран, можливо, проблема в самому PDF-файлі. Якщо ви бачите там файл PDF, а не у завантаженому файлі, повідомте мене про це на електронну пошту. :)
Mayur Padshala

5
Цей (якірний елемент) насправді не працював у мене в IE 11, Edge та Firefox. зміна успіху на просто використання "window.open (URL.createObjectURL (blob))" справді спрацювала.
JimiSweden

3
файл PDF завантажується, але вміст недоступний. у мене є байт збереження [] на стороні сервера, і вміст PDF доступний. плз пропоную.
Awanish Kumar

4
завантажується порожній файл PDF.
Фарух

31

Я насправді не думаю, що будь-яка з минулих відповідей виявила проблему оригінального плаката. Усі вони припускають запит GET, поки плакат намагався розмістити дані та отримати відповідь на завантаження.

Під час пошуку кращої відповіді ми знайшли цей плагін jQuery для запиту завантаження файлів, схожих на Ajax .

У своєму "серці" він створює "тимчасову" форму HTML, що містить дані як поля введення. Ця форма додається до документа та розміщується за потрібною URL-адресою. Відразу після цього форму знову видаляють:

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

Відповідь оновлення Mayur виглядає досить багатообіцяючою та дуже простою порівняно з плагіном jQuery, про який я згадував.


9

Ось як я вирішую цю проблему.
Відповідь Джонатана Аменда на цей пост мені дуже допомогла.
Приклад нижче спрощений.

Для отримання детальної інформації, наведений вище вихідний код може завантажити файл за допомогою запиту JQuery Ajax (GET, POST, PUT тощо) . Це також допомагає завантажувати параметри як JSON і змінювати тип вмісту на application / json (за замовчуванням) .

Джерело html :

<form method="POST">
    <input type="text" name="startDate"/>
    <input type="text" name="endDate"/>
    <input type="text" name="startDate"/>
    <select name="reportTimeDetail">
        <option value="1">1</option>
    </select>
    <button type="submit"> Submit</button>
</form>  

Проста форма з двома вхідними текстами, одним виділенням та елементом кнопки.

Джерело сторінки javascript :

<script type="text/javascript" src="JQuery 1.11.0 link"></script>
<script type="text/javascript">
    // File Download on form submition.
    $(document).on("ready", function(){
        $("form button").on("click", function (event) {
            event.stopPropagation(); // Do not propagate the event.

            // Create an object that will manage to download the file.
            new AjaxDownloadFile({
                url: "url that returns a file",
                data: JSON.stringify($("form").serializeObject())
            });

            return false; // Do not submit the form.
        });
    });
</script>  

Проста подія при натисканні кнопки. Він створює об'єкт AjaxDownloadFile. Джерело класу AjaxDownloadFile знаходиться нижче.

AjaxDownloadFile клас Джерело:

var AjaxDownloadFile = function (configurationSettings) {
    // Standard settings.
    this.settings = {
        // JQuery AJAX default attributes.
        url: "",
        type: "POST",
        headers: {
            "Content-Type": "application/json; charset=UTF-8"
        },
        data: {},
        // Custom events.
        onSuccessStart: function (response, status, xhr, self) {
        },
        onSuccessFinish: function (response, status, xhr, self, filename) {
        },
        onErrorOccured: function (response, status, xhr, self) {
        }
    };
    this.download = function () {
        var self = this;
        $.ajax({
            type: this.settings.type,
            url: this.settings.url,
            headers: this.settings.headers,
            data: this.settings.data,
            success: function (response, status, xhr) {
                // Start custom event.
                self.settings.onSuccessStart(response, status, xhr, self);

                // Check if a filename is existing on the response headers.
                var filename = "";
                var disposition = xhr.getResponseHeader("Content-Disposition");
                if (disposition && disposition.indexOf("attachment") !== -1) {
                    var filenameRegex = /filename[^;=\n]*=(([""]).*?\2|[^;\n]*)/;
                    var matches = filenameRegex.exec(disposition);
                    if (matches != null && matches[1])
                        filename = matches[1].replace(/[""]/g, "");
                }

                var type = xhr.getResponseHeader("Content-Type");
                var blob = new Blob([response], {type: type});

                if (typeof window.navigator.msSaveBlob !== "undefined") {
                    // IE workaround for "HTML7007: One or more blob URLs were revoked by closing the blob for which they were created. These URLs will no longer resolve as the data backing the URL has been freed.
                    window.navigator.msSaveBlob(blob, filename);
                } else {
                    var URL = window.URL || window.webkitURL;
                    var downloadUrl = URL.createObjectURL(blob);

                    if (filename) {
                        // Use HTML5 a[download] attribute to specify filename.
                        var a = document.createElement("a");
                        // Safari doesn"t support this yet.
                        if (typeof a.download === "undefined") {
                            window.location = downloadUrl;
                        } else {
                            a.href = downloadUrl;
                            a.download = filename;
                            document.body.appendChild(a);
                            a.click();
                        }
                    } else {
                        window.location = downloadUrl;
                    }

                    setTimeout(function () {
                        URL.revokeObjectURL(downloadUrl);
                    }, 100); // Cleanup
                }

                // Final custom event.
                self.settings.onSuccessFinish(response, status, xhr, self, filename);
            },
            error: function (response, status, xhr) {
                // Custom event to handle the error.
                self.settings.onErrorOccured(response, status, xhr, self);
            }
        });
    };
    // Constructor.
    {
        // Merge settings.
        $.extend(this.settings, configurationSettings);
        // Make the request.
        this.download();
    }
};

Я створив цей клас для додавання до своєї бібліотеки JS. Він багаторазовий. Сподіваюся, що це допомагає.


2
Blobоб'єкт підтримується в IE10 +.
розчавити

Мені довелося встановити responseTypeдля xhr arraybufferабо blobщоб це працювало. (В іншому випадку це чудово працює.)
tjklemz

У мене було точно таке ж питання. Усі люди, які відповідають "просто зробіть посилання", не допомагає ОП. Якщо ваш вміст динамічний, а посилання, яке ви збираєтеся зробити, динамічне, вам потрібно jquery це все ... для мене відповідь полягала у тому, щоб розмістити на сторінці форму з усіма прихованими введеннями (не відображається користувачеві) та потім заповніть і надішліть з jquery. Чудово працює.
Скотт

Це чудова відповідь, але я з якихось причин просто продовжую ламати порожній PDF. Не можу зрозуміти. Коли я повертаю той самий байт через API - це добре, тому це щось пов’язане з відповіддю MVC. Я використовую тип відповіді FileResult: File (bytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Юрій Кастанов

Пояснення: якщо URL-адресу відкрито через адресний рядок - файл правильно відкрито. Якщо я використовую AJAX + BLOB для отримання файлу - файл неправильний.
Юрій Кастанов

7

Для мене спрацював наступний код, оскільки серверна функція отримує 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();
                }
});

Це мені чудово підходить під час цього коментаря та найновішого Chrome
Loredra L

6

Ви можете використовувати цей плагін, який створює форму, надсилає її, а потім видаляє зі сторінки.

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

Це працювало для мене. Знайшов цей плагін тут


Цей плагін просто створює форму, надсилає її, а потім видаляє зі сторінки. (якщо хтось цікавився)
розчавити

4

Наступний код працював для мене

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

4

Щоб виправити непорожню проблему PDF у запиті на пост, щоб отримати потокові дані, такі як PDF, нам потрібно додати тип відповіді як 'arraybuffer' або 'blob' у запит

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

3

Щодо відповіді, яку дав Майюр Падшала, це правильна логіка для завантаження PDF-файлу через ajax, але, як повідомляють інші в коментарях, це рішення справді завантажує чистий PDF-файл.

Причина цього пояснюється прийнятою відповіддю на це запитання : jQuery має деякі проблеми із завантаженням двійкових даних за допомогою запитів AJAX, оскільки він ще не реалізує деякі можливості HTML5 XHR v2, див. Цей запит на вдосконалення та це обговорення .

Тож використання HTMLHTTPRequestкоду має виглядати так:

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

2

створіть прихований iframe, а потім у коді ajax вище:

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

і видаліть window.open(response);


Це рішення спрацювало як шарм. Я викликаю скрипт на стороні сервера, який здійснює виклик curl до служби, яка отримує файл за допомогою curl. Це чудово працює, оскільки я можу скинути завантажувальний gif і відключити посилання на запит.
eggmatters

1
Це рішення працює для запитів GET, а не для запитів POST, як у вихідному повідомленні.
chiccodoro

2

Цей фрагмент призначений для користувачів angular js, які зіткнуться з тією ж проблемою. Зверніть увагу, що файл відповіді завантажується за допомогою запрограмованої події кліку. У цьому випадку заголовки були надіслані сервером, що містить ім'я файлу та вміст / тип.

$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 () {

});

Будь ласка, напишіть пояснення до своєї відповіді.
Гуфран Хасан,

1

Чи потрібно це робити з Аяксом? Хіба це не може бути можливість завантажити його в iframe?


1
Я перевіряю, чи можна це зробити за допомогою Ajax. Якщо це технічно неможливо або гірший підхід, я б перейшов до інших підходів.
Nayn

1

Сподіваюся, це заощадить кілька годин і позбавить від головного болю. Мені знадобився деякий час, щоб зрозуміти це, але звичайний запит $ .ajax () зіпсував мій PDF-файл, а запит через адресний рядок працював бездоганно. Рішення було таким:

Включіть download.js: http://danml.com/download.html

Потім використовуйте XMLHttpRequest замість запиту $ .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);

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


0

Якщо вам доводиться працювати з потоком файлів (отже, фізично не зберігається PDF), як у нас, і ви хочете завантажити PDF без перезавантаження сторінки, для нас працює наступна функція:

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

Завдяки target = "pdf-download-output" , відповідь записується у iframe, і тому не виконується перезавантаження сторінки, але pdf-response-stream виводиться у браузері як завантаження.


вибачте, але як ви отримуєте значення transferData?
Кейт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.