2

In a submit function of a custom form, I would like to output a pdf which can be downloaded by the user/browser when clicking on 'Get PDF'.

The following code is doing the job only once, because after clicking on the 'Get PDF' button, all the buttons of the form does not work anymore. Clicking on any of them doesn't produce any effect.

Any Idea?

  static public function submitPdf(array &$form, FormStateInterface $form_state) {            
    if ($pdf_content = my_function_to_output_a_pdf_as_string()) {            
      $response = new Response($pdf_content);            
      $disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'my_pdf.pdf');            
      $response->headers->set('Content-type', "application/pdf");            
      $response->headers->set('Content-Disposition', $disposition);            
      //$response->headers->set('Content-Transfer-Encoding', 'binary');            
      $response->headers->set('Content-length', strlen($pdf_content));            
      $form_state->setResponse($response);  
      //Thanks to kiamlaluno, it is working slighty better if I remove the following line (setRebuild())          
      //$form_state->setRebuild(TRUE);            
    }            
  }            

Edit to add in order to provide more context: The form is made of +/- 30 numeric fields and the user can play with them to produce some calculated results. If he wants, he can click at any time on the "get pdf" button to generate a pdf which will contains all his inputs and the calculated result. In other words, he can fill the form->get the pdf->continue to fill->get another pdf-> etc

7
  • 2
    It's not possible to rebuild a form and send a response for an attachment at the same time.
    – apaderno
    Nov 6 '20 at 16:09
  • Thank you for your help. I have removed $form_state->setRebuild(TRUE); and something better happens: I could use another submit, but none of any of the PDF buttons (sharing the same submit function) was working ... maybe I should send result without using $form_state->setResponse() ?
    – Baud
    Nov 6 '20 at 16:37
  • 1
    Why not hit a controller that returns a binary file response?
    – Kevin
    Nov 25 '20 at 13:42
  • A binary file response could do the job indeed. Code can be found here: stefvanlooveren.me/blog/serve-pdf-download-controller-drupal-8 Nov 25 '20 at 15:34
  • Thank you for your idea but binary file response doesn't change anything (I tested it). I can create a controller to download a pdf, but this is not what was asked. I do not want to be redirected to another page to download the pdf. The user should click on "get pdf" to download it but there are other buttons that he could use after this download. In the provided code, if the user cllick the "get pdf" button, he will get the pdf as expected but after that, the form is unusable... this is my problem.
    – Baud
    Nov 25 '20 at 20:42
6
+50

Provide a controller to download the PDF as discussed in the comments:

https://stefvanlooveren.me/blog/serve-pdf-download-controller-drupal-8

<?php
namespace Drupal\MYMODULE\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

/**
 * Class PdfGeneratorController.
 */
class PdfGeneratorController extends ControllerBase {

  public function Render($uuid) {

    // In my case, I render a file by UUID
    // This actually does not matter too much here
    // String below contains the url of the file
    $uri = 'private://'.$uuid.'.pdf';

    $headers = array(
      'Content-Type'     => 'application/pdf',
      'Content-Disposition' => 'attachment;filename="download"');

    return new BinaryFileResponse($uri, 200, $headers, true);
  }
}

Then add to the button link the HTML5 Download Attribute to download the file in the background without redirecting.

See https://stackoverflow.com/questions/20830309/download-file-using-an-ajax-request

If the PDF file is created dynamically this can be integrated in a Drupal Ajax form workflow by generating the file on user request and providing the button link to the custom built PDF file via Ajax.

5
  • Many thanks for your help, but I am not sure to understand. Did you mean that (1) The user click on a button "Generate PDF". When the pdf is generated/saved on the disk (in the submit function) a new link is displayed (After Form Rebuild) and the user can (2) click on "Download the pdf" (link with the download attribute)? Is it what you mean?
    – Baud
    Nov 26 '20 at 15:15
  • 1
    Your question is unclear about how dynamic the PDF is, but yes, if this is your use case then combine server-side Ajax and client-side HTML5 functionality. The user doesn't necessarily need to click a button to trigger the Ajax, you could add a trigger to other dialog elements as well so that you can display the download button when you have enough user data.
    – 4k4
    Nov 26 '20 at 15:32
  • I have added more context to explain how dynamic the pdf is. Note that I have done it on a D7 version... it was working well (I used the $pdf->send() function of the PHP WkHtmlToPdf in my submit function. But this approach doesn't work in D8! It seems that the only working proposal (4k4) is a 2 clicks process. Remember that my code is working once... Maybe I can adapt it to send a response (the pdf) and redirect back to the same form (I can store all the values to populate the the form when reloaded) But I don't know if this is feasible... sorry to throw so many ideas in all directions.
    – Baud
    Nov 26 '20 at 22:00
  • 2
    Yes, this can go in all possible directions. Focusing more server-side by for example implementing some kind of autosave feature storing the values on the server so that the controller can generate the PDF on the fly with these values. Or more client-side collecting the values in javascript and sending them with the request to the controller. My answer is for the key part in all of these solutions how to output the PDF without redirect.
    – 4k4
    Nov 27 '20 at 7:26
  • It took me some times, but I have accepted your answer. The download attribute made the tricks. Many thanks for your help!
    – Baud
    Nov 28 '20 at 20:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.