Thursday, 9 April 2015

Page refresh and file download in a single breath

Functional requirement

Let's say we are supposed to display some data on page before downloading a file in web application. Think of displaying number of downloads or last time the file has been downloaded. Yes, that's a piece of cake, you may say dear reader. 

Almost a solution

I can write a Servlet that generates HttpResponse with relevant Content-Type, Content-Disposition in the header and my file's stream. Actually it would work. But what about refreshing page.

As we know there can be only one response per request. So where to put refreshed page content? 

Two ways of downloading files

I've implemented following examples:

1° Not perfect but whole navigation is enclosed within single page.
2° Perfect but additional browser window is beeing opened.



Implemented example: Single page case

Please play a little bit with working example to check how things work. You may analyse conversation with web server in your browser by debugging (F12).

You may notice that in single page case, refreshing page (F5) in browser causes downloading file once again without setting last download time. That's because browser refresh (F5) causes repeating of the last request (which is in our case request for the file).

To understand case 1° trick look at following snippet:

    <c:if test="#{onSamePageDownloadBean.readyForDownload}">        
      <script type="text/javascript">
        window.onload = function() {
          document.getElementById('downloadForm:downloadFile').click();
        }
      </script>
    </c:if>
After clicking Download button, the same page is rendered once again but with flag readyForDownload = true passed (in request). In this case on page load JavaScript triggers the server download file action by clicking hidden button in form on the same page:


    <h:form id="downloadForm">
        <h:commandButton id="downloadFile" 
                         action="#{onSamePageDownloadBean.download()}" 
                         style="display:none"/>
        <h:commandButton id="redirectAndDownload" 
                         action="#{onSamePageDownloadBean.redirectWithDownload()}" 
                         value="Download file"/>
        <h:commandButton id="back" 
                         action="index.html?faces-redirect=true" 
                         value="Back"/>
    </h:form>


Implemented example: New browser window case 

Case 2° has no tricks, it just sends two requests - first updating page, and the second with JavaScript demanding to open new window with the downloadWindow URL:

    <h:form id="downloadForm">        
      <h:commandButton id="openWindowAndDownload"
                       action="#{openWindowDownloadBean.update()}"
                       onclick="window.open('downloadWindow.xhtml');"
                       value="Download file"/>
    </h:form>

To review the two aforementioned examples see complete sources on GitHub.

No comments: