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