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:
{ id: 'myButtonID', xtype: 'button', text: 'Save business', handler: function() { 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.
//Listener on Onload of window screen addBeforeUnloadHandler(); function addBeforeUnloadHandler() { window.onbeforeunload = function(){ return 'Warning currently an action is submitted to server, if you leave the page, the unsaved work could be lost!';} ; } function removeBeforeUnloadHandler() { window.onbeforeunload = null; }
Then, we define several objects:
// The messageBox containing the progress bar var pleaseWaitDialog = null; // The runner of Sencha's timer var runner = new Ext.util.TaskRunner(); // The requestID generated on client side 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.
// Progression Task var updateWaitDialogProgressionTask = { run:updateWaitDialogProgression, interval: 1000 }
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.
// Method called at each task's execution var updateWaitDialogProgression = function() { if (null == pleaseWaitDialog) { return; } // end-if // Call the server in order get the pregresssion state of requestID previously sent Ext.Ajax.request( { url: 'myController.do?action=handleGetProgression', method: 'GET', params: { requestID:requestId }, success: function (response, options) { var jsonData = Ext.decode(response.responseText); var currentprogress = jsonData.data; if (currentprogress >= 100) { pleaseWaitDialog.updateProgress(1, ""); runner.stop(updateWaitDialogProgressionTask); pleaseWaitDialog.hide(); removeBeforeUnloadHandler(); Ext.MessageBox.alert('Message title', 'The task is finished correctly!!'); } else if (currentprogress > -1) { pleaseWaitDialog.updateProgress(currentprogress / 100, ""); } else if (currentprogress == -1) { runner.stop(updateWaitDialogProgressionTask); pleaseWaitDialog.hide(); Ext.MessageBox.alert('Message title', 'A error is occured in server side!!!'); } // end-if }, // Error, failure in server side failure: function (response, options) { runner.stop(updateWaitDialogProgressionTask); pleaseWaitDialog.hide(); 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.
// Callback to: // - send a request for the "handleSaveBusiness" action to the SERVER // - start the timer on CLIENT side function saveBusinessAndStartRunner() { pleaseWaitDialog = Ext.MessageBox.show( { title: 'Alert', progressText: '- JavaBlog.fr - ProgressionBar with Sencha', width: 300, progress: true, closable: false } ); // Start the timer runner.start(updateWaitDialogProgressionTask); // Send a ajax request to the Save Business action Ext.Ajax.request( { url: 'myController.do?action=handleSaveBusiness', params : { requestID:requestId }, success: function(response, opts) { }, failure: function (response, options) { // displayExceptionAlert(); } } ); }
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:
// BUSINESS public final static ConcurrentHashMap<String, Float> progressionByRequestId = new ConcurrentHashMap<String, Float>();
The POJO to receive the parameters sent from CLIENT to SERVER:
// POJO to receive the parameters sent from CLIENT to SERVER private class SaveBusinessCommand { // Identifiy the request and is used to display the progression bar to the user. private String requestID; public final String getRequestID() { return requestID; } public final void setRequestID(String requestID) { this.requestID = requestID; } }
The POJO returned to CLIENT in JSON format:
// POJO returned to CLIENT in JSON format private final class SaveBusinessCommandResultsForExJS { private boolean success; public SaveBusinessCommandResultsForExJS(boolean sucess) { this.success = sucess; } public final boolean isSuccess() { return success; } public final void setSuccess(boolean success) { 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:
// Callback to get the progression state for a requestID sent by the CLIENT public ModelAndView handleGetProgression(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, InterruptedException { logActionName("handleGetProgression"); SaveBusinessCommand command = null; { command = new SaveBusinessCommand(); ServletRequestDataBinder binder = new ServletRequestDataBinder(command); binder.bind(request); } Float progression = progressionByRequestId.get(command.getRequestID()); if(progression==null){ progression = 0f; } try { Map<String, Object> model = new HashMap<String, Object>(); model.put("success", "true"); model.put("data", "" + Math.round(progression)); return new ModelAndView("jsonView", model); } catch (Throwable exc) { return handleException(exc, "handleGetProgression"); } // end-try }
The Callback for the handleSaveBusiness action:
// Callback for the handleSaveBusiness action public ModelAndView handleSaveBusiness(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, InterruptedException { logActionName("handleSaveBusiness"); SaveBusinessCommand command = null; { command = new SaveBusinessCommand(); ServletRequestDataBinder binder = new ServletRequestDataBinder(command); binder.bind(request); } // RequestID String requestID = command.getRequestID(); progressionByRequestId.put(requestID, 0f); { try { progressionByRequestId.put(requestID, 0f); { // Incrementation // Calculating for each iteration the advancement // 25 corresponding to the range for this "for" loop // => In the business, we can assign a range of progression to each block of code (for others) float divBy = 10; // number of iterations in "for" loop float progression = 25 / divBy; for (int i = 0; i < divBy; i++) { try { progressionByRequestId.put(requestID, progressionByRequestId.get(requestID) + progression); long numMillisecondsToSleep = 1000; // 1 second Thread.sleep(numMillisecondsToSleep); } catch (InterruptedException e) { } } } progressionByRequestId.put(requestID, 25f); { // Incrementation // Calculating for each iteration the advancement // 75 corresponding to the range for this "for" loop // => In the business, we can assign a range of progression to each block of code (for others) float divBy = 20; // number of iterations in "for" loop float progression = 75 / divBy; for (int i = 0; i < divBy; i++) { try { progressionByRequestId.put(requestID, progressionByRequestId.get(requestID) + progression); long numMillisecondsToSleep = 1000; // 1 second Thread.sleep(numMillisecondsToSleep); } catch (InterruptedException e) { } } } progressionByRequestId.put(requestID, 100f); } catch (Exception e) { progressionByRequestId.put(requestID, -1f); String errorMessage = "An error occured"; if (logger.isErrorEnabled()) { logger.error(errorMessage,e); } // end-if } // end-try } // POJO to send to CLIENT in JSON format SaveBusinessCommandResultsForExJS output = new SaveBusinessCommandResultsForExJS(true); try { progressionByRequestId.put(command.getRequestID(), 100f); { Map<String, Object> model = new HashMap<String, Object>(); model.put("success", "true"); model.put("data", output); return new ModelAndView("jsonView", model); } } catch (Throwable exc) { return handleException(exc, "handleSaveBusiness"); } // end-try }
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