In a previous post, I have introduced the Timer and Task components of Sencha framework.
In this example more advanced, we will use the components timer/task of Sencha in order to show a progression bar in client side after the submitting of an business action to server. Each request sent by a client is identified by a requestID in order to get and display the progression’s state to the user. We will use the ConcurrentHashMap class in order to store the progression of each request sent by the client. ConcurrentHashMap class is a simple alternative for HashMap, and it offers a means of improving concurrency beyond that of normal HashMaps in order to eliminate the need for a synchronized blocks.
CLIENT SIDE:
First, we will start will create a button in order to send a request to server and start the timer:
6 | saveBusinessAndStartRunner(); |

Here, as explained in a previous post we will use the onbeforeunload javascript event in order to detect an exit (close, reload, click clink) of current page during the processing of user’s request on server side.
2 | addBeforeUnloadHandler(); |
3 | function addBeforeUnloadHandler() { |
4 | window.onbeforeunload = function (){ return 'Warning currently an action is submitted to server, if you leave the page, the unsaved work could be lost!' ;} ; |
6 | function removeBeforeUnloadHandler() { |
7 | window.onbeforeunload = null ; |
Then, we define several objects:
2 | var pleaseWaitDialog = null ; |
4 | var runner = new Ext.util.TaskRunner(); |
6 | var requestId = Math.floor(Math.random()*9999999); |
Here, it is the progression task updateWaitDialogProgressionTask which will be started by the timer. This task will call a method updateWaitDialogProgression sending the request to server in order to get the progression’ state of client request.
2 | var updateWaitDialogProgressionTask = { |
3 | run:updateWaitDialogProgression, |
On client side, the following method is the most important code. This method updateWaitDialogProgression is called by the progression task updateWaitDialogProgressionTask.
It will send a request to server and decode the JSON response in order to update the progress bar with the current state on server side.
02 | var updateWaitDialogProgression = function () { |
03 | if ( null == pleaseWaitDialog) { |
10 | url: 'myController.do?action=handleGetProgression' , |
16 | success: function (response, options) { |
17 | var jsonData = Ext.decode(response.responseText); |
18 | var currentprogress = jsonData.data; |
19 | if (currentprogress >= 100) { |
20 | pleaseWaitDialog.updateProgress(1, "" ); |
21 | runner.stop(updateWaitDialogProgressionTask); |
22 | pleaseWaitDialog.hide(); |
23 | removeBeforeUnloadHandler(); |
24 | Ext.MessageBox.alert( 'Message title' , 'The task is finished correctly!!' ); |
26 | } else if (currentprogress > -1) { |
27 | pleaseWaitDialog.updateProgress(currentprogress / 100, "" ); |
29 | } else if (currentprogress == -1) { |
30 | runner.stop(updateWaitDialogProgressionTask); |
31 | pleaseWaitDialog.hide(); |
32 | Ext.MessageBox.alert( 'Message title' , 'A error is occured in server side!!!' ); |
37 | failure: function (response, options) { |
38 | runner.stop(updateWaitDialogProgressionTask); |
39 | pleaseWaitDialog.hide(); |
40 | displayExceptionAlert(); |
At last, the following function is the callback method the above button to send a request for the “handleSaveBusiness” action to the server and start the timer on client side.
04 | function saveBusinessAndStartRunner() { |
05 | pleaseWaitDialog = Ext.MessageBox.show( |
08 | progressText: '- JavaBlog.fr - ProgressionBar with Sencha' , |
15 | runner.start(updateWaitDialogProgressionTask); |
20 | url: 'myController.do?action=handleSaveBusiness' , |
24 | success: function (response, opts) { |
26 | failure: function (response, options) { |
SERVER SIDE:
On the server side, we will use the controllers based on Spring MVC.
The Map containing the states for all request sent by clients:
2 | public final static ConcurrentHashMap<String, Float> progressionByRequestId = new ConcurrentHashMap<String, Float>(); |
The POJO to receive the parameters sent from CLIENT to SERVER:
02 | private class SaveBusinessCommand { |
04 | private String requestID; |
05 | public final String getRequestID() { |
08 | public final void setRequestID(String requestID) { |
09 | this .requestID = requestID; |
The POJO returned to CLIENT in JSON format:
02 | private final class SaveBusinessCommandResultsForExJS { |
03 | private boolean success; |
05 | public SaveBusinessCommandResultsForExJS( boolean sucess) { |
06 | this .success = sucess; |
08 | public final boolean isSuccess() { |
11 | public final void setSuccess( boolean success) { |
12 | this .success = success; |
The Callback for the handleGetProgression action. This callback is used to get the progression state for a requestID sent by the CLIENT:
02 | public ModelAndView handleGetProgression(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, InterruptedException { |
03 | logActionName( "handleGetProgression" ); |
04 | SaveBusinessCommand command = null ; |
06 | command = new SaveBusinessCommand(); |
07 | ServletRequestDataBinder binder = new ServletRequestDataBinder(command); |
11 | Float progression = progressionByRequestId.get(command.getRequestID()); |
12 | if (progression== null ){ |
16 | Map<String, Object> model = new HashMap<String, Object>(); |
17 | model.put( "success" , "true" ); |
18 | model.put( "data" , "" + Math.round(progression)); |
19 | return new ModelAndView( "jsonView" , model); |
20 | } catch (Throwable exc) { |
21 | return handleException(exc, "handleGetProgression" ); |
The Callback for the handleSaveBusiness action:
02 | public ModelAndView handleSaveBusiness(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, InterruptedException { |
03 | logActionName( "handleSaveBusiness" ); |
05 | SaveBusinessCommand command = null ; |
07 | command = new SaveBusinessCommand(); |
08 | ServletRequestDataBinder binder = new ServletRequestDataBinder(command); |
13 | String requestID = command.getRequestID(); |
15 | progressionByRequestId.put(requestID, 0f); |
18 | progressionByRequestId.put(requestID, 0f); |
25 | float progression = 25 / divBy; |
26 | for ( int i = 0 ; i < divBy; i++) { |
28 | progressionByRequestId.put(requestID, progressionByRequestId.get(requestID) + progression); |
29 | long numMillisecondsToSleep = 1000 ; |
30 | Thread.sleep(numMillisecondsToSleep); |
31 | } catch (InterruptedException e) { |
36 | progressionByRequestId.put(requestID, 25f); |
43 | float progression = 75 / divBy; |
44 | for ( int i = 0 ; i < divBy; i++) { |
46 | progressionByRequestId.put(requestID, progressionByRequestId.get(requestID) + progression); |
47 | long numMillisecondsToSleep = 1000 ; |
48 | Thread.sleep(numMillisecondsToSleep); |
49 | } catch (InterruptedException e) { |
54 | progressionByRequestId.put(requestID, 100f); |
56 | } catch (Exception e) { |
57 | progressionByRequestId.put(requestID, -1f); |
58 | String errorMessage = "An error occured" ; |
59 | if (logger.isErrorEnabled()) { |
60 | logger.error(errorMessage,e); |
66 | SaveBusinessCommandResultsForExJS output = new SaveBusinessCommandResultsForExJS( true ); |
68 | progressionByRequestId.put(command.getRequestID(), 100f); |
70 | Map<String, Object> model = new HashMap<String, Object>(); |
71 | model.put( "success" , "true" ); |
72 | model.put( "data" , output); |
73 | return new ModelAndView( "jsonView" , model); |
75 | } catch (Throwable exc) { |
76 | return handleException(exc, "handleSaveBusiness" ); |
EXECUTION:
After clicking on the button:

The progressbar displays the progression of action’s processing on server side: 25%

The progressbar displays the progression of action’s processing on server side: 75%

If during the processing action on server side, the user tries to exit the current page, (for example with a “Refresh”):

…then the code will display a confirmation message:

Click on “Cancel” button…
When the request is done, the following message is displayed and the detection of page’s exiting is disabled:

…so, if the user tries again to exit the current page, no message is displayed again.
So, in this post, we have seen an example of implementation of Sencha components used for a progressbar. There are other application of Timers: Lock a business object on server from client side (via cyclic ajax request).
Download zip file: sencha_timer_progressbas_part2.zip
Best regards,
Huseyin OZVEREN
Related