2

Here is what I'm trying to accomplish (IE 9+, Chrome, FF, Safari) without the use of JQuery:

  1. Make an http POST call to my API endpoint with some data
  2. Server dynamically generates a PDF and returns the PDF as a binary attachment
  3. Browser does default download behavior and downloads the PDF without refreshing the page

Basically I want to get the behavior similar to <a href="test.pdf"> but for a dynamically generated PDF after making a POST call instead of a GET call.

I've tried lots of different things, but they either didn't work cross browser (such as using $window.open() with a blob URL), were blocked by popup blockers (any $window call outside of the click scope), or didn't cause the PDF to be automatically downloaded (any $http POST solution).

I finally found one solution that seems to work which creates a form using javascript and submits it.

  var form = document.createElement('form');
  form.setAttribute('method', 'post');
  form.setAttribute('action', myurl);

  var params = {foo: 'bar'};

  for(var key in params) {
      if(params.hasOwnProperty(key)) {
          var hiddenField = document.createElement('input');
          hiddenField.setAttribute('type', 'hidden');
          hiddenField.setAttribute('name', key);
          hiddenField.setAttribute('value', params[key]);
          form.appendChild(hiddenField);
       }
  }

  document.body.appendChild(form);
  form.submit();

This successfully accomplishes the 3 steps above, but now I've run into a new problem. There is no way to determine when the PDF file has been successfully downloaded. This is preventing me from removing the form and from displaying a friendly 'Please wait...' message to the user. There is also the additional problem that submitting the form cancels any outstanding ajax requests as well which isn't optimal.

I have full control over both the server and the client, so what's the best way to fix this? I don't want to have to save the PDF on the server so passing back a url and doing a second GET request from the client won't work in this case. Thanks!

Bill
  • 25,119
  • 8
  • 94
  • 125

1 Answers1

0

You can make an server response behave as a download by applying some HTTP headers:

Content-Type: application/octet-stream
Content-Disposition: attachment; filename="SOME_NAME.pdf"
Content-Transfer-Encoding: binary

If you're initiating the download through JS only (instead of having the user click a download link), then check out this question for some caveats.

Update: Syntax for POST

Better Update: Form solution with iframe target

You can detect that your server-side script has finished (and subsequently, that the download is ready to begin) by having the form target an iframe. I believe this should also fix the issue of cancelling outstanding Ajax calls, but I'm not certain. Here is the code to do it (just stick this into your code example after the for loop and before document.body.appendChild(form);):

var frame = document.createElement('iframe');
frame.setAttribute('id', 'pdfFrame');
frame.onload = function(){
    document.body.removeChild(form);
    document.body.removeChild(frame);
    alert('Download ready!');
}
document.body.appendChild(frame);
form.setAttribute('target', 'pdfFrame');

You can replace my alert with your code to remove the 'Please wait...'.

Community
  • 1
  • 1
pieman72
  • 836
  • 8
  • 14
  • Thanks. I am setting these on the server, this allows the form submit to perform the download. The linked question is a little bit different because they are issuing a GET to populate the iFrame, but I need to issue a POST. – Bill Jun 10 '14 at 17:36
  • I'm glad the headers helped with the download behavior. I've updated my answer to include some sample syntax for making a post request via Angularjs. Let me know if I've missed anything. – pieman72 Jun 11 '14 at 14:34
  • If I just do a standard post call, the call succeeds but the PDF file does not trigger the download behavior. The response contains the PDF but nothing else happens. The only way I've been able to trigger the download behavior is through the submit form approach. – Bill Jun 11 '14 at 16:19
  • Sorry, I misunderstood your previous comment. I've updated my answer again with what I now believe you're looking for. Hope it helps. – pieman72 Jun 11 '14 at 17:45
  • Thanks for the help, unfortunately Chrome fires no events when targeting an iFrame with a file download :( See http://stackoverflow.com/questions/20572734/load-event-not-firing-when-iframe-is-loaded-in-chrome. – Bill Jun 11 '14 at 18:38
  • Ended up going with the cookie solution described in http://stackoverflow.com/questions/1106377/detect-when-browser-receives-file-download. – Bill Jun 11 '14 at 19:05
  • Another day, another browser inconsistency... Glad you solved it, but sorry to hear it requires cookies. – pieman72 Jun 11 '14 at 19:38