Downloading PDF from server to client; issue with cookie creation

225 views
Skip to first unread message

ras5429

unread,
Jun 26, 2013, 2:55:38 PM6/26/13
to intersys...@googlegroups.com
I have a CSP page that allows a user to select/enter some parameters and generate a Zen Report.  The HTML is displayed immediately in the browser.  If the user wants to generate a PDF, they can click a button to do so.

This 'button' is actually a link:

<href="/csp/mynamespace/MyApp.DLFile.cls?file=#(reportName)#&token=#($username)#">
     <img src="PDFimage.png" >
</a>


That will call this class

Class MyApp.DLFile Extends %CSP.Page
{

  ClassMethod OnPreHTTP() As %Boolean
  {
      set file=%request.Data("file",1)
      set token=%request.Data("token",1)

      do %response.SetCookie("fileDownloadToken",token) //  explanation on this in a moment
      do %response.SetHeader("Content-disposition","attachment; filename="_File)

      quit $$$OK
  }

  ClassMethod OnPage() As %Status
  {
      set reportName %request.Data("file",1)
      set rpt = $classmethod(reportName,"%New")
      set rpt.Datasource="/csp/mynamespace/"_$username_"xmltmp.xml" //  always guaranteed to be here at this point
      set saveStatus=rpt.GenerateStream(.oFile,2)
      set status=oFile.OutputToDevice()

      quit $$$OK
}

Now, all of the above works like a charm.

The reason for setting the header is so that (once the file is downloaded) the browser's save-dialog kicks in and asks the user what they would like to do with the file.  During this PDF creation, I block access to the entire page (with JQuery blockUI) and display a message/.gif informing the user to 'Please wait...'.  The problem is recognizing when the download has completed so I can return the user's page access (via unblockUI).

So, I create a timer in Javascript when the link is initially clicked.  It watches for the existence (and value) of the cookie that is created by DLFile.cls.  Again, my current setup works fine.  The 'token' in this scenario is simply the username.

My problem is as follows.
  • I want to add a unique token by appending the username with the exact time
  • I need to modify the href for my link to include this time (so I can pass it to the class and retrieve it via %request)
  • Whenever the href is modified (after the link is clicked), the cookie is never returned from DLFile.cls
  • I also need to capture the token value before invoking DLFile.cls so that my JQuery function can ensure that the returned cookie value matches what it's expecting
  • I attempted to modify the href in several ways:
    • In the HTML href definition
    • Via a javascript function (i.e. href="javascript:modifyLink()")
    • In my JQuery function (shown below) that initializes the timer 



$(document).ready(function() {

    $('#PDFLink').click(function() {
         blockUIForDownload();
    });
});

var fileDownloadCheckTimer;

function blockUIForDownload() {
  var token #(..QuoteJS($username))#+new Date().getTime();

  $('#PDFLink').prop("href","/csp/mynamesapce/MyApp.DLFile.cls?file="+#(..QuoteJS(reportName))#+"&token="+token);

  $.blockUI({message: '<h1><img src="images/busy.gif"/> Generating your PDF...</h1>'});

  fileDownloadCheckTimer=window.setInterval(function() {
      var cookieValue=$.cookie('fileDownloadToken');

      if(cookieValue==token)
      finishDownload();
  },1000);
}

function finishDownload() {
  window.clearInterval(fileDownloadCheckTimer);
  $.cookie('fileDownloadToken',null);
  $.unblockUI();
}

I can't figure out why the cookie isn't available in my situation.  I guess maybe I don't fully understand the sequence of events or something...

I would greatly appreciate any insight!

Thanks
~Ryan


Jason Warner

unread,
Jun 26, 2013, 3:10:19 PM6/26/13
to intersys...@googlegroups.com
The problem is that by the time you are trying to modify the href
parameter, the browser has already evaluated the location of the link.
You will either need to use the preventDefault() method to stop the
link from going through, or you can change the link before the click
takes place.

One way to accomplish that might be to change the parameter in the
onhover and then subsequently after every click. This would set the
values for the next click and not the current click. You could use
$('#PDFLink').data("token", token) to add an HTML5 data-token attribute
to your href and retrieve the token to check with var token =
$('#PDFLink').data("token").

The other way to do this would be to create a response with a content
type of 'application/pdf' that would stream the pdf file back to the
browser. You would then set the window.location to the appropriate url
and when the browser sees the proper content-type, it will kick off the
save dialog. This would be my preferred method instead of trying to
create a potentially fragile jQuery solution.

Jason

On Wednesday, June 26, 2013 12:55:38 PM, ras5429 wrote:
> I have a CSP page that allows a user to select/enter some parameters
> and generate a Zen Report. The HTML is displayed immediately in the
> browser. If the user wants to generate a PDF, they can click a button
> to do so.
>
> *_This 'button' is actually a link:_*
>
> <a
> href="/csp/mynamespace/MyApp.DLF*i*le.cls?file=#(reportName)#&token=#($username)#">
> <img src="PDFimage.png" >
> </a>
>
>
> *_That will call this class_*
> * I want to add a unique token by appending the username with the
> exact time
> * I need to modify the href for my link to include this time (so I
> can pass it to the class and retrieve it via %request)
> * Whenever the href is modified (after the link is clicked), the
> cookie is never returned from DLFile.cls
> * I also need to capture the token value before invoking DLFile.cls
> so that my JQuery function can ensure that the returned cookie
> value matches what it's expecting
> * I attempted to modify the href in several ways:
> o In the HTML href definition
> o Via a javascript function (i.e. href="javascript:modifyLink()")
> o In my JQuery function (shown below) that initializes the timer
> --
> You received this message because you are subscribed to the Google
> Groups "InterSystems: Zen Community" group.
> To post to this group, send email to InterSys...@googlegroups.com
> To unsubscribe from this group, send email to
> InterSystems-Z...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/InterSystems-ZEN?hl=en
> Zen Community Terms and Conditions:
> http://groups.google.com/group/InterSystems-ZEN/web/community-terms-and-conditions
> ---
> You received this message because you are subscribed to the Google
> Groups "InterSystems: Zen Community" group.
> To unsubscribe from this group and stop receiving emails from it, send
> an email to intersystems-z...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

ras5429

unread,
Jun 26, 2013, 3:54:17 PM6/26/13
to intersys...@googlegroups.com

The problem is that by the time you are trying to modify the href
parameter, the browser has already evaluated the location of the link.
You will either need to use the preventDefault() method to stop the
link from going through, or you can change the link before the click
takes place.


I can't seem to wrap my head around this... if I modify the link (with an additional parameter) I am able to capture it in my class and retrieve it via the %request object.  I am just not able to get the cookie to appear back on the client side.  How does the browser's interpretation of the link prior to the link being adjusted/used affect the return of the cookie from server to client?

I tried to use the preventDefault() method, then I modified the link, and then I directed to the modified link.  I am still having the same issue. 


The other way to do this would be to create a response with a content
type of 'application/pdf' that would stream the pdf file back to the
browser. You would then set the window.location to the appropriate url
and when the browser sees the proper content-type, it will kick off the
save dialog. This would be my preferred method instead of trying to
create a potentially fragile jQuery solution.


Isn't this what I'm already doing?  I don't think that this will help me with my intention of blocking the UI and then unblocking when the download has completed...?

Thank you very much for your help!

~Ryan
 

Dale du Preez

unread,
Jun 27, 2013, 8:21:20 AM6/27/13
to intersys...@googlegroups.com
Hi Ryan,

Is there a reason you don't want to do something like the following?

<a href="#" onclick="setTimeout(function() { zenPage.loadReport('reportName','parm1','parm2'); }, 0); return false;" />

ClientMethod loadReport(reportName,parm1,parm2) [ Language = javascript ]
{
    // assign a token based on the current time and username (assuming it's sent from the server)
    var token = (new Date().getTime()) + '....' + zenPage.username;
    var link = 'MyApp.DLReport.zen?REPORT=' + encodeURIComponent(reportName) + '&TOKEN=' + encodeURIComponent(token);
    // launch UI blocker and timeout checking for token in cookies.
    // change page location (this ought to load the attachment, but I would want to test to confirm!)
    // the actual location change might also need to be delayed using setTimeout() to allow for more control over the return false for the click handler
    document.location = link;
Message has been deleted
Message has been deleted

ras5429

unread,
Jun 27, 2013, 10:45:43 AM6/27/13
to intersys...@googlegroups.com
Thank you Dale!

Your response has lead me to a solution.

Upon further investigation, what has fixed my problem is the use of

document.location=link;

to download my file.  

My previous attempts at adjusting the href for my link and using that (to download the file) are what was causing the issue with the returned cookie... but, I'm not entirely sure why...

Jason Warner

unread,
Jun 27, 2013, 12:28:49 PM6/27/13
to intersys...@googlegroups.com
The reason you weren't getting the proper cookie back is due to the order of how the browser fires events and when the jQuery events are activated. When you click a link, the browser already has the information from the link. Any changes you make to the link will be available on the next click of that link, but not on the current click. This is why you would have to do an event.preventDefault() in jQuery to stop the event bubble and then use something like your document.location = link solution.

The reason that document.location = link works is that you are doing the work of constructing the link and then telling the browser to navigate to the location you stored in link. When the browser sees that it is an 'application/pdf' content type, it fires off the save dialog. I think this is a better solution than trying to modify the <a> tag with jQuery.

Jason


On 6/27/2013 8:41 AM, ras5429 wrote:
Upon further investigation, what has fixed my problem is the use of

document.location=link;

to download my file.  

My previous attempts at adjusting the href for my link and using that (to download the file) are what was causing the issue with the returned cookie... but, I'm not entirely sure why...
Reply all
Reply to author
Forward
0 new messages