Showing real progress in XPages

One of the actions in an XPage application I’m building might take some while to complete. The action is triggered by a button so I added a loading indicator image that is shown on the button’s onStart event and hidden in the onComplete event. The problem with this was that it gives me no clue as to how long it will take and if it’s making any progress at all. So I searched for a way to display a real progress bar.

I investigated a solution involving a partial refresh of a part of the page while another partial refresh is running. This seemed a no-go: the 2nd partial refresh will not start/ complete until the first is finished. I also looked into threads and Eclipse jobs: but found that too complex for just a simple progressbar.

The solution I came up with includes a couple of components. In the (long-running) code I store the current progress (a number from 1 to 100) in a variable in the sessionScope. When the long-running code is started I use the onStart event to call a client-side function that uses the JavaScript setInterval() function to periodically call another XPage using a dojo.xhrGet(). That second XPage (getProgress.xsp) has only one purpose: it returns the progress from the variable in the sessionScope as JSON to the browser. This progress is then used to display a dijit.ProgressBar() with the updated progress. In the onComplete event of the long-running eventHandler I cancel the periodic checking.

Enough talk: demo or it didn’t happen.

You can download the demo database here. As far as I know the code will only work on 8.5.3. The demo page uses the built-in Dojo Claro theme. Oh,  and the messages are optional!

11 thoughts to “Showing real progress in XPages”

  1. Hi Mark,

    This is indeed a very cool and usefull implementation of a statusbar. A couple years ago I did a similar thing. A customer wanted to know the progres of an archiving agent. Instead of having a xpage I used a back-end document that got updated by the agent that was running. This document was then read by a ajax call until the status of the document changed to ‘done’.

    this way the customer got a real time display of the progress of the agent.

  2. Good stuff. I went down this same exact road but was getting some inconsistent results. My interval ajax calls were queuing up and would not finish until the long running job was done. I’ll have to revisit this making sure I utilize onStart and onComplete.

    1. Thanks Mike. Two important parts to make the sample work are (1) use 8.5.3 to be able to make concurrent XPage requests and (2) redirect the AJAX calls to a separate XPage.

      Good luck!

  3. Nice! A small improvement / idea to run it with versions < 8.5.3: You could use old-school profile documents instead of a session scope variable. Then it would be possible to access the value in a web agent (which returns the JSON for the dojo.xhr request) and there is no problem with the concurrent requests.

    1. Interesting idea. You only then have the additional (probably small) overhead of running a web agent every seconds or so instead of running a compiled XPage.

      1. As far as I can say you are able to ignore this small overhead. In one of my applications I use this “technique” without any problems. But if you really run into performance problems you can easily switch to hardcoded (static) JSON responses stored in Domino’s HTML folder.

  4. I ran into the same problem too.
    So the second xpage returnes the side-effects of the first one.
    Stupid that a csjs get request cannot do this on the same xpage.
    Thanks for your solution, Mark

  5. Greate works,Mark!
    The demo runs perfectly in brower such as IE 10,FF,Chrome.But I found it doesn’t work well when running on notes client(8.5.3FP2) as a XPiNc application.I clicked the botton and the dojo progressbar begin to update ,when the ssjs done,I got a xpage error page shown as blow,then i googled this error but hasn’t found any usefull messages.Any suggestions will be appreciated,tks 🙂
    javax.faces.FacesException

    com.sun.faces.lifecycle.InvokeApplicationPhase.execute(Unknown Source)

    com.sun.faces.lifecycle.LifecycleImpl.phase(Unknown Source)

    com.sun.faces.lifecycle.LifecycleImpl.execute(Unknown Source)

    com.ibm.xsp.controller.FacesControllerImpl.execute(Unknown Source)

    com.ibm.xsp.webapp.FacesServlet.serviceView(Unknown Source)

    com.ibm.xsp.webapp.FacesServletEx.serviceView(Unknown Source)

    com.ibm.xsp.webapp.FacesServlet.service(Unknown Source)

    com.ibm.xsp.webapp.FacesServletEx.service(Unknown Source)

    com.ibm.xsp.webapp.DesignerFacesServlet.service(Unknown Source)

    com.ibm.designer.runtime.domino.adapter.ComponentModule.invokeServlet(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFComponentModule.invokeServlet(Unknown Source)

    com.ibm.designer.runtime.domino.adapter.ComponentModule$AdapterInvoker.invokeServlet(Unknown Source)

    com.ibm.designer.runtime.domino.adapter.ComponentModule$ServletInvoker.doService(Unknown Source)

    com.ibm.designer.runtime.domino.adapter.ComponentModule.doService(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService.doServiceInternal(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService.access$0(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService$NsfServiceRequest.call(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService$NsfServiceRequest.call(Unknown Source)

    java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)

    java.util.concurrent.FutureTask.run(Unknown Source)

    java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)

    java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

    java.lang.Thread.run(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService$NsfServiceThread.run(Unknown Source)

    java.lang.NullPointerException

    javax.faces.component.UIViewRoot.broadcastEvents(Unknown Source)

    javax.faces.component.UIViewRoot.processApplication(Unknown Source)

    com.sun.faces.lifecycle.InvokeApplicationPhase.execute(Unknown Source)

    com.sun.faces.lifecycle.LifecycleImpl.phase(Unknown Source)

    com.sun.faces.lifecycle.LifecycleImpl.execute(Unknown Source)

    com.ibm.xsp.controller.FacesControllerImpl.execute(Unknown Source)

    com.ibm.xsp.webapp.FacesServlet.serviceView(Unknown Source)

    com.ibm.xsp.webapp.FacesServletEx.serviceView(Unknown Source)

    com.ibm.xsp.webapp.FacesServlet.service(Unknown Source)

    com.ibm.xsp.webapp.FacesServletEx.service(Unknown Source)

    com.ibm.xsp.webapp.DesignerFacesServlet.service(Unknown Source)

    com.ibm.designer.runtime.domino.adapter.ComponentModule.invokeServlet(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFComponentModule.invokeServlet(Unknown Source)

    com.ibm.designer.runtime.domino.adapter.ComponentModule$AdapterInvoker.invokeServlet(Unknown Source)

    com.ibm.designer.runtime.domino.adapter.ComponentModule$ServletInvoker.doService(Unknown Source)

    com.ibm.designer.runtime.domino.adapter.ComponentModule.doService(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFComponentModule.doService(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService.doServiceInternal(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService.access$0(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService$NsfServiceRequest.call(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService$NsfServiceRequest.call(Unknown Source)

    java.util.concurrent.FutureTask$Sync.innerRun(Unknown Source)

    java.util.concurrent.FutureTask.run(Unknown Source)

    java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)

    java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)

    java.lang.Thread.run(Unknown Source)

    com.ibm.domino.xsp.module.nsf.NSFService$NsfServiceThread.run(Unknown Source)

    1. I thought the dojo xhrget in setInterval cause the error ,so i tried to make sure that every ajax request getting the progress json data was sent after the previouse request finish by using setTimeout .Unfortunately,it still doesn’t work.Then i tried to use the XSP.partialRefreshGet on a sperate xpage which was emberded in an iframe on demo xpage.But it also had no effect.I found that once another ajax request was sent to the server during the first long-time ajax request ,the error arise.

    2. Hi Peter,

      I did a quick test in XPiNC (in Notes 9) and get the same error you’re getting. The progress is updated correctly, but the result of the long running process isn’t loaded. Don’t have a solution for that issue. If I find one, I’ll let you know.

  6. Hi Mark,
    On Karsten Lehmann’s blog i found a new database property called xsp.session.transient.After setting xsp.session.transient=true and putting the progress data into applicationScope ,the demo worked well.Mybe notes runtime bug which should have fixed in 853 (as Philippe Riand relyed on Karsten Lehmann’s blog) cause this problem

Comments are closed.