33

I'm working with AngularJS and I'm trying to generate a PDF in php. This is what I have in my controller.js:

$scope.downloadPDF = function(){
    $http.post('/download-pdf', { fid: $routeParams.fid })
        .success(function(data, status, headers, config){
            console.log("success");
        })
        .error(function(data, status, headers, config){
            console.log("error");
        });
};

In my php file I have the following to create a PDF with FPDF library:

function download_pdf()
{
    $id = $_POST['fid'];

    $pdf = new FPDF();

    $pdf->AddPage();
    $pdf->SetFont('Arial','B',16);
    $pdf->Cell(40,10,'Hello World!');

    header('Content-Type: application/pdf');
    header('Content-Disposition: attachment; filename=' . $id . '.pdf');

    $pdf->Output('Order123.pdf', 'D');
}

But the request is responding this instead of open a save dialog to save my pdf.

%PDF-1.3 3 0 obj <> endobj 4 0 obj <> stream x3Rðâ2Ð35W(çr QÐw3T04Ó30PISp êZ*[¤(hx¤æää+çå¤(j*dÔ7W endstream endobj 1 0 obj < endobj 5 0 obj < endobj 2 0 obj << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI] /Font << /F1 5 0 R > /XObject << > > endobj 6 0 obj << /Producer (FPDF 1.7) /CreationDate (D:20150611094522) > endobj 7 0 obj << /Type /Catalog /Pages 1 0 R > endobj xref 0 8 0000000000 65535 f 0000000228 00000 n 0000000416 00000 n 0000000009 00000 n 0000000087 00000 n 0000000315 00000 n 0000000520 00000 n 0000000595 00000 n trailer << /Size 8 /Root 7 0 R /Info 6 0 R > startxref 644 %%EOF

I've used the PHPExcel library and this worked:

$objWriter = PHPExcel_IOFactory::createWriter($ea, 'Excel2007');

// We'll be outputting an excel file
header('Content-type: application/vnd.ms-excel');

// It will be called Submission on [date_now].xls
header('Content-Disposition: attachment; filename="' . $filename . '.xls' . '"');

// Write file to the browser
$objWriter->save('php://output');

Now how can I make this work for my PDF?

UPDATE:

I've edited my code to this:

$pdf = new FPDF();

$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello World!');

$filename = DXS_VKGROUP_PLUGIN_LIB_DIR . 'uploads/' . $_POST['fid'] . '.pdf';

$pdf->Output($filename, 'F'); // Save file locally

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize($filename));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="' . $filename . '"');

$handle = fopen($filename, 'rb');
fpassthru($handle);
fclose($handle);

The file is saved locally but the download doesn't work. It doesn't get my a dialog to save the pdf. What am I doing wrong?

UPDATE 2

I've now tried to change application-download in application/pdf. He saves the file locally but I don't get a download dialog box.

The response looks like this (when I check Network in Chrome):

%PDF-1.3 3 0 obj <> endobj 4 0 obj <> stream x3Rðâ2Ð35W(çr QÐw3T04Ó30PISp êZ*[¤(hx¤æää+çå¤(j*dÔ7W endstream endobj 1 0 obj < endobj 5 0 obj < endobj 2 0 obj << /ProcSet [/PDF /Text /ImageB /ImageC /ImageI] /Font << /F1 5 0 R > /XObject << > > endobj 6 0 obj << /Producer (FPDF 1.7) /CreationDate (D:20150617090832) > endobj 7 0 obj << /Type /Catalog /Pages 1 0 R > endobj xref 0 8 0000000000 65535 f 0000000228 00000 n 0000000416 00000 n 0000000009 00000 n 0000000087 00000 n 0000000315 00000 n 0000000520 00000 n 0000000595 00000 n trailer << /Size 8 /Root 7 0 R /Info 6 0 R > startxref 644 %%EOF

nielsv
  • 6,540
  • 35
  • 111
  • 215
  • 3
    If your web browser settings doesn't have "Ask where to save each file before downloading" (Chrome) option or similar(Firefox - Always ask where to save files) ticked , then the browser won't show the Save As dialog even if you set the header of the file as `application-download` or `Content-Disposition: attachment` . The file will start downloading automatically. Something like https://github.com/eligrey/FileSaver.js might be helpful in this case – Prayag Verma Jun 18 '15 at 19:13
  • What happens if you use `header('Content-Type: application/octet-stream');` instead of `application/pdf` ? – Darren Jun 23 '15 at 05:19
  • @Darren I checked , that too leads to file download process starting automatically without any prompt to save it – Prayag Verma Jun 23 '15 at 11:01
  • Did you try: [this](http://stackoverflow.com/questions/20904151/download-text-csv-content-as-files-from-server-in-angular) or [this](http://stackoverflow.com/questions/20300547/download-csv-file-from-web-api-in-angular-js)? – Denys Denysiuk Jun 24 '15 at 09:00
  • Why did you let the bounty expire? Are none of the answers useful, or ... ? – Ja͢ck Jun 25 '15 at 22:22

8 Answers8

6

UPDATE

The method of using FileSaver.js also doesn't work , there seems to be no way to forcefully invoke the native Save As Dialog via JavaScript alone , only exception being the saveAs execCommand for IE. Check Does execCommand SaveAs work in Firefox?

Include FileSaver.js as dependency in your project

Change the downloadPDF function as follows

 $scope.downloadPDF = function() {
    $http.post('/download-pdf', {
        fid: $routeParams.fid
      }, {
        responseType: 'arraybuffer'
      })
      .success(function(data, status, headers, config) {
        // Convert response data to Blob
        var file = new Blob([data], {
          type: 'application/pdf'
        });
        // Call the File Saver method to save the above Blob,this will show Save As dialog
        saveAs(file, "test.pdf");
      })
      .error(function(data, status, headers, config) {
        console.log("error");
      });

 };

The Blob object will work in most modern browser but there is limited support for IE < 10 , check https://github.com/eligrey/FileSaver.js#supported-browsers for polyfills

Also remove Content-Disposition: attachment and Content-Type: application-download headers as we don't want browser to natively handle the download process

Here is a working demo http://plnkr.co/edit/9r1tehQ94t5vkqZfZuQC?p=preview , it shows GET request to a PDF

Community
  • 1
  • 1
Prayag Verma
  • 5,581
  • 3
  • 30
  • 56
3

Let me suggest you one thing.

By AJAX only you cannot download any file. You need to create .zip file first with ajax call and after that gaining that file path you have to open that file using any command.

Here one example that works for me: :)

$http.post('www.abc.com/zipcreate', {'id': id}).then(function (zipurl) {
                    $window.location = zipurl;
            });

Surely, It works. Try once. :)

EDIT:

Or you can use

$window.location = 'abc.com/link';

in 'abc.com/link' : use below code:

  header("Content-type: application/octet-stream");
  header("Content-Disposition: attachment; filename=".$name);
  header("Pragma: no-cache");
  header("Expires: 0");
  readfile($file);
  exit;

You will get your file in download-list.

Ronak Patel
  • 3,324
  • 4
  • 21
  • 31
  • Why would they need to create a zip file? Wouldn't that make it harder for the user to actually open the document? – Ja͢ck Jun 24 '15 at 09:44
2

According to the documentation you can set the 2nd parameter to D:

$pdf->Output('Order123.pdf', 'D');

If you want to handle all the details yourself using a saved file, this is how I've presented PDFs to the browser for download from PHP:

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize('file.pdf'));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="file.pdf"');
$handle = fopen('file.pdf', 'rb');
fpassthru($handle);
fclose($handle);

Update:

Please verify that the PDF file is actually being made. Does the web server process have write access to that path? Is FPDF being included properly? I made this on a test VM and it presented a file 1.pdf for download, which opened and contained "Hello World!":

<?php

require('/var/www/html/fpdf17/fpdf.php');

$_POST = array('fid' => '1');

$pdf = new FPDF();

$pdf->AddPage();
$pdf->SetFont('Arial', 'B', 16);
$pdf->Cell(40, 10, 'Hello World!');

$filename = '/tmp/' . $_POST['fid'] . '.pdf';

$pdf->Output($filename, 'F'); // Save file locally

header('Pragma: public');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Content-Type: application-download');
header('Content-Length: ' . filesize($filename));
header('Content-Transfer-Encoding: binary');
header('Content-Disposition: attachment; filename="' . basename($filename) . '"');

$handle = fopen($filename, 'rb');
fpassthru($handle);
fclose($handle);

unlink($filename);

?>
mike.k
  • 3,277
  • 1
  • 12
  • 18
  • I've tried the D parameter, my topic was wrong. But still outputting the same as stated in topic. – nielsv Jun 11 '15 at 14:09
  • In that case, try with the `F` parameter to save a local file, then pass that file to the browser then way I showed. At the end you can use `unlink($filename);` to remove it. – mike.k Jun 11 '15 at 15:21
  • Try `header('Content-Disposition: attachment; filename="' . basename($filename) . '"');` because you cannot use a filename with a full folder path. Do you send any headers to the browser prior to that code? – mike.k Jun 12 '15 at 12:08
  • Stil same result. No I don't send any headers to the browser prior .. Tried in firefox/chrome – nielsv Jun 12 '15 at 12:32
  • The file does get saved and I can see and open it (when I don't unlink it). But the browser doesn't open a download dialog ... . – nielsv Jun 15 '15 at 07:46
  • Try to change Content-type to application/pdf – mike.k Jun 15 '15 at 09:34
  • Still the same, I've updated my topic. Sorry for editing your post. Was editing wrong .. – nielsv Jun 17 '15 at 09:16
  • Using that last set of headers in my answer, both Firefox 34 and IE 11 present a Save As option, just Chrome doesn't seem to and proceeds to save automatically, perhaps that is it's only behavior and I've read around that others also have this issue. – mike.k Jun 23 '15 at 15:39
  • Just saw Chrome has an option in it's settings to ask where to download: chrome://settings/search#download – mike.k Jun 23 '15 at 15:42
2

With ajax it is not possible.
Instead what you can do is, hit your action through javascript form submit.

e.g., document.getElementById(formID).action= actionName.

document.getElementById(formID).submit();  

Here the formID is your form's id and the actionName will be '/download-pdf' as you have specified.

In your action class - set response headers

getResponse().setContentType("application/pdf");
getResponse().setHeader("Content-Disposition",  " attachment; filename= "+"pdffile.pdf");

This will cause your file to be downloaded with filename = pdffile.pdf.

Waqar
  • 428
  • 4
  • 17
2
header('Content-Type: application/octet-stream');
header("Content-Transfer-Encoding: Binary");
header("Content-disposition: attachment; filename=\"" . basename($file_url) . "\"");

Or using BLOB:

 $scope.downloadPDF = function() {
    $http.post('/download-pdf', {
        fid: $routeParams.fid
      }, {
        responseType: 'arraybuffer'
      })
      .success(function(data, status, headers, config) {

        var file = new Blob([data], {
          type: 'application/pdf'
        });
        var url = URL.createObjectURL(blob);
        window.location.href = url;
      })
      .error(function(data, status, headers, config) {
        console.log("error");
      });

 };

Or create a element with download attribute:

$scope.downloadPDF = function() {
    $http.get('http://46.101.139.188/demo.pdf', {
      }, {
        responseType: 'arraybuffer'
      })
      .success(function(data, status, headers, config) {

        var file = new Blob([data], {
          type: 'application/pdf'
        });
        var url = URL.createObjectURL(file);
        var a = document.createElement('a');
        a.setAttribute('download', 'download');
        a.setAttribute('href', url);

         var event = new MouseEvent('click', {
          'view': window,
          'bubbles': true,
          'cancelable': true
        });
        a.dispatchEvent(event);
      })
      .error(function(data, status, headers, config) {
        console.log("error");
      });
 };

plnkr demo

Bogdan Kuštan
  • 5,427
  • 1
  • 21
  • 30
1

This is not possible with (only) AJAX.

There are two main techniques you could use here, both without using JavaScript to perform the actual download. The general idea is that you redirect the browser to a resource that will provide the desired document; if you're redirecting to a download it will not leave the page but show the Save dialog instead. A similar topic is discussed in this question.

GET Request

location.href = '/download-pdf?fid=' + encodeURIComponent($routeParams.fid);

Adjust your server-side script to use $_GET['fid'].

POST Request

The server creates a resource and responds with a URL where the resource can be found:

$http.post('/download-pdf', { fid: $routeParams.fid })
    .success(function(data, status, headers, config) {
        // follow location provided by server
        location.href = data.redirect_url;
        console.log("success");
    })
    .error(function(data, status, headers, config){
        console.log("error");
    });

Adjust your server-side script to return an appropriate JSON response after creating the new resource.

Community
  • 1
  • 1
Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
0

I did it in the following manner and it works well for me:

$(document).ready(function () {
            $('#generaPdf').click(function (e) {
            e.preventDefault();
            var name = $('#name').val();
            $.ajax
                ({
                    type: "POST",
                    url: "pdf.php",
                    data: { "name": name },
                    success: function (data) {
                        alert("todo ok")
                        location.href = "pdf.php"
                        // $('.result').html(data);
                        // $('#contactform')[0].reset();
                    }
                });
            });
        });

My html:

<button type="submit" class="btn btn-primary" id="generaPdf"><i class="fas fa-link"></i> Generar documento para impresión</button>
camille
  • 16,432
  • 18
  • 38
  • 60
-1

Send the document to a given destination: browser, file or string. In the case of browser, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.

The method first calls Close() if necessary to terminate the document.

Parameters

name:

The name of the file. If not specified, the document will be sent to the browser (destination I) with the name doc.pdf.

dest

Destination where to send the document. It can take one of the following values:

  • I: send the file inline to the browser. The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.
  • D: send to the browser and force a file download with the name given by name.
  • F: save to a local file with the name given by name (may include a path).
  • S: return the document as a string. name is ignored.
Yaco Zaragoza
  • 429
  • 1
  • 7
  • 18