Quantcast
Channel: Developer Notes » Software Development
Viewing all articles
Browse latest Browse all 12

Show dynamic progress of time-consuming process in Seam/RichFaces

$
0
0

Sometimes you need to start really time-consuming task on the server, but doing that with simple h:commandAction which will re-render page after it is done is not a good idea. Showing a4j:status (which could be blocking – see “avoid concurrent call to conversation“) – is a bit better and will work for relatively short tasks (like 5 – 15 seconds, matter of taste actually) but still not good solution for really long tasks (something more than 1 minute)

For really long tasks I recommend to show the progress indicator (it’s also known usability fact that user will think that task with dynamic progress-bar is faster than the same task but without progress-bar)

There is a quite good component to do it with Richfaces – it’s progressBar component, the only problem to do it with Seam is that you should initiate progress (by calling the action) and that action should immediately return and start some background process.  It’s always bad practice to use Thread’s in the web-containers, in Seam we have alternative Asynchronous tasks, but.. the problem with them is that they actually will be running in completely separate scope (they don’t know nothing about your conversation scope)

The trick here is to pass all the required parameters to asynchronous method and use them for returning the results too (in the conversation). That way we are not crazy about memory issues, since as long the long computation process will be ended the memory will be released (the reference to the object will be only in the initiator – the conversation scoped bean)

So, the solution may look like that.

  1. Define the PrgressBean.java which will hold all required initialization parameters for the process and methods to access the progress-state
  2. add “progressBean” attribute (+getter) in your action-class (ConversationBean.java)
  3. define methods to start process which will make a call to asynchronous method in other “LongProcess.java” and pass the progress bean to it
  4. add rich:progressBar and start/stop buttons in “commandPanel” (could be done inside progressBar only), + “updatedResults” – here you can show intermediate and final results during the process (optional)

In short – it is all you need to show very informative long-running process progress.

Future Notes:

Richfaces has few other ways to build your own progressBean – it is a4j:poll and a4j:push components. rich:progressBean actually utilize “pooling” approach, in most cases it’s quite enough to periodically update progress/results for the user, so actually there is no much sense to write your own  approach using a4j:poll.  a4j:push maybe quite good alternative to rich:progressBar since it use much less traffic and doesn’t update JSF tree (so it’s potentially a better alternative). I think you can easily adapt described approach to use a4j:push – you just need to add few pieces  (addListener method and send events to the listener during the process)

Code-Snippets

Java Code:

@Name("longProcess")
@AutoCreate
public class LongProcess {
    private ProgressBean progress
    @Asynchronous
    public void startProcess(ProgressBean progress) {
        this.progress = progress;
        this.progress.setInProgress(true);
        try {
            runProcess();
        } finally {
            this.progress.setInProgress(false);
        }
    }

   private void runProcess(){
     //perform your time-consuming operations here and periodically update the progress status
....
     progress.setPprogress(progressValue);
....
     if (progress.shouldStop()){
        //finish long process and return
     }
....
   }
}

@Name("conversationBean")
@Scope(ScopeType.CONVERSATION)
public class ConversationBean  {
    private ProgressBean progressBean = new ProgressBean();
    @In LongProcess longProcess;

    public void startProcess(){
        if (!progressBean.isInProgress()) {
           progressBean = createNewProgressBean(); //initialize it with required parameters here
           longProcess.startProcess(progressBean);
        }
    }
    public ProgressBean getProgressBean(){
        return progressBean;
    }
    public void stopProcess(){
       progressBean.stop(); //update the internal state of progress-bean so long-process will check it and stop
    }
}

Richfaces/JSF code

<h:panelGroup id="commandPanel">
<a4j:commandButton action="#{conversationBean.startProcess}"
        value="Start" onclick="this.disabled=true;"
        rendered="#{!conversationBean.progressBean.inProgress}"
        reRender="commandPanel,proggressPanel,updatedResults">
</a4j:commandButton>
<a4j:commandButton action="#{conversationBean.stopProcess}"
        value="Stop" onclick="this.disabled=true;"
        reRender="commandPanel,proggressPanel,updatedResults"
        rendered="#{suggestedEventController.progressBean.inProgress}">
</a4j:commandButton>
</h:panelGroup>

<a4j:outputPanel id="proggressPanel">
<rich:progressBar value="#{conversationBean.progressBean.progress}"
                  label="#{conversationBean.progressBean.progress} %"
                  enabled="#{conversationBean.progressBean.inProgress}"
                  minValue="-1" maxValue="100"
                  interval="#{conversationBean.updateRate}"
                  reRender="#{conversationBean.shouldUpdateTable ? 'updatedResults':'anyEmptyComponent'}"
                  reRenderAfterComplete="proggressPanel, updatedResults, commandPanel">
    <f:facet name="initial">
        <h:outputText value="&amp;lt; Click to start"/>
       <!-- we also may show here button to start process as in RichFaces example (I use separate commandPanel instead) -->
    </f:facet>
    <f:facet name="complete">
        <h:outputText value="Process Completed"/>
      <!-- we also may show here button to restart process as in RichFaces example -->
    </f:facet>
</rich:progressBar>

<h:panelGroup id="updatedResults">
      <!-- it could be used to show results (for example list of processed or generated rows) -->
     <rich:dataTable value="#{conversationBean.progressBean.items}>
     </rich:dataTable>
</h:panelGroup>
</a4j:outputPanel>

Please take a notice at 2 parameters used in rich:progressBar
interval="#{conversationBean.updateRate}"
it use a method conversationBean.updateRate to determine the update rate. I’s optional and could be just hardcoded to some value like “1000″ (1 second). Can be used to dynamically set it to appropriate value, so your progress bar will not be updated to often and can be even changed during a process to fit to your real update rate

reRender="#{conversationBean.shouldUpdateTable ? 'updatedResults':'anyEmptyComponent'}"
As you see reRender here use dynamic condition, so conversationBean has a control other it and can skip heavy “updatedResults” update to save a traffic



Viewing all articles
Browse latest Browse all 12

Trending Articles