1

In my PrimeFaces app I'm generating a file for download. The generation is kicked off and then I go into a waiting loop until it's ready. It now appears that this loop is being initiated twice. Here's the code:

JSF:

<p:commandButton id="downloadBtn" value="Download" disabled="#{bean.selectedRow == null}"
    onclick="blockUI()"
    actionListener="#{bean.generateFileAndDownloadWhenReady()}"/>

<script type="text/javascript">
  function downloadFileWhenReady(key) { 
    window.setTimeout(function () {
            checkForDownloadReady([{name:"key",value: key }]);                            
        }, 1000);
  }

  function doTheActualDownload(key) {
    // handle the download
  }
</script>

Java:

public void generateFileAndDownloadWhenReady() {
    key = kickoffFileGeneration();
    RequestContext.getCurrentInstance().execute("downloadFileWhenReady('" + key + "');");
}


public void checkForDownloadReady() {
    String key = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("key");
    STATUS status = FileCache.getFileStatus(key);

    if (status == STATUS.READY) {
        RequestContext.getCurrentInstance().execute("doTheActualDownload(" + key + ");");

    } else if (status == STATUS.FAILED) {
        logger.error("Failure creating file. Wail loop will end.");

    } else if (status == STATUS.OPEN){
        RequestContext.getCurrentInstance().execute("downloadFileWhenReady('" + key + "');");
    }
}

Here's a flowchart to hopefully clarify the logical flow: Flowchart

By placing a breakpoint in generateFileAndDownloadWhenReady(), I can see it's only being called once, but a breakpoint in downloadFileWhenReady() is being called twice. By using a long loop delay I know the second call isn't coming from the loop. Also, the call to kickoffFileGeneration() is pretty quick -- it's spinning off a new thread for the actual work. So why is it being called twice and how do I stop it?

[update: 5/24/17]
I suspect that, being a complete novice with JSF, I'm not using the actionListeners and remoteCommands properly. @BalusC has several responses & articles like this and this, which suggest it might be a scoping problem.

And to answer the question of why I don't just simply return the file from the commandButton actionListener, it's because it can take a few minutes to generate the .zip file. Therefore I'm avoiding blocking the UI by spinning off a worker thread, and I'm also using a <p:blockUI> element to entertain the user.

Didjit
  • 785
  • 2
  • 8
  • 26
  • Then it is even better to learn 'push' instead of implementing your own polling like mechanism and better to not create your own threads. Search howto do async things in modern webapps – Kukeltje May 24 '17 at 21:23
  • Thanks for the tip @Kukeltje. I did look into it but read that 'push' isn't supported in all browsers so I'd have to have a polling fallback anyway. I'm also not a fan of polling, so I'll keep that in mind for the next time. (Also, this is a low traffic private app, which hopefully makes it less of a sin.) ;-) – Didjit May 25 '17 at 14:19
  • Define 'all browsers'... current FF, Chrome and IE support it. Amd if not they fall back to long polling, exactely what you are trying to implement – Kukeltje May 25 '17 at 16:47

1 Answers1

0

In case anyone else runs into this, I solved it by moving the javascript callback from the bean into the oncomplete attribute of the commandButton. It now looks like this:

JSF:

<p:commandButton id="downloadBtn" value="Download" disabled="#{bean.selectedRow == null}"
       onclick="blockUI()" oncomplete="downloadFileWhenReady()"
       actionListener="#{bean.generateFile()}"/>

<p:remoteCommand name="checkForDownloadReady" actionListener="#{bean.checkForDownloadReady}"/>

<!-- Hidden button to contain the fileDownload element -->
<p:commandButton id="downloadFile" ajax="false" style="display:none"  onclick="releaseUI();">
    <p:fileDownload value="#{bean.file}" contentDisposition="attachment"/>
</p:commandButton>

<script type="text/javascript">
    function downloadFileWhenReady() {
        window.setTimeout(function () {checkForDownloadReady();}, 1000);
    }        
    function doTheActualDownload() {
        document.getElementById('the_form:downloadFile').click();
    }
    function blockUI() {
        PF('blocker').show();
    }
    function releaseUI() {
        PF('blocker').hide();
    }
</script>

Java:

private String key;

public void generateFile() {
    key = kickoffFileGeneration();  // spawns worker thread, creates file in FileCache
}

public void checkForDownloadReady() {
    STATUS status = FileCache.getFileStatus(key);

    if (status == STATUS.READY) {
        FileCache.setFileStatus(key, STATUS.HANDLED);
        RequestContext.getCurrentInstance().execute("doTheActualDownload()");

    } else if (status == STATUS.FAILED) {
        logger.error("Failure creating file. Wait loop will end.");

    } else if (status == STATUS.OPEN || status == STATUS.NEW){
        RequestContext.getCurrentInstance().execute("downloadFileWhenReady()");
    } 
}
Didjit
  • 785
  • 2
  • 8
  • 26