J1.5

Handling out of sequence responses in MooTools Ajax

From Joomla! Documentation

Revision as of 08:10, 19 September 2010 by Batch1211 (talk | contribs)

The "J1.5" namespace is an archived namespace. This page contains information for a Joomla! version which is no longer supported. It exists only as a historical reference, it will not be improved and its content may be incomplete and/or contain broken links.

It is easy to forget that the initial "A" in "Ajax" stands for "asynchronous", which means that once the user has initiated an Ajax request, she is free to do other things in the user interface, including making other Ajax requests. As a web application designer you need to take into consideration that responses to these requests may not arrive back in the same order they were made. If not handled correctly this can have unfortunate consequences for the user. For example, in a typical Ajax implementation where the user selects a country from a drop-down list of countries and is then presented with a drop-down list of regions within that country, an Ajax call is made whenever the country is changed. The response to this request will be a list of regions within the selected country. But suppose the user makes a series of fairly rapid selection changes, something that is easier to do using the keyboard that with the mouse. Say the user hits the "U" key and the first country that comes up is "Uganda". This isn't what the user wanted, so she hits the "U" key repeatedly until she gets "United Kingdom", then once more to get the one she really wanted: "United States". This causes a whole series of Ajax calls to be made in sequence, with the last being the call for "United States". But suppose the response to her "United Kingdom" request is delayed for some reason. This might be due to server load, or it could be down to network delay. But for whatever reason, it arrives after the "United States" response. Then the user will be presented with a list of "United Kingdom" regions even though her selection was "United States".

This sort of behaviour is clearly undesirable. Various remedies for this have been suggested, including some that involve the server executing requests in strict order (which ignores the possibility that a network delay on the return leg could still cause the responses to arrive back at the client out of sequence).

However, the solution suggested below has the virtue of being quite simple, it does not require any special coding on the server and it copes with all classes of delayed response, regardless of their cause. The idea is to maintain a simple queue containing all the requests that have been made and to only output the results in the correct sequence, regardless of the order in which they arrive.

There needs to be some way to determine if a particular request has been completed or not. To do this you can extend the MooTools Ajax class like this:

// Extend the Mootools Ajax class.
Ajax = Ajax.extend({

    initialize: function( url, options ) {
        this.parent( url, options );
        this.ready = false;
        this.output = '';
    },

    onComplete: function() {
    	this.ready = true;
    	this.parent();
    }

});

This adds two additional properties to the Ajax class:

  • ready is a Boolean that flags whether the response is available or not; and
  • output which is used to hold the response.

Having extended the Ajax class, you now need to set up a simple queue mechanism to hold the request information. Each new request will be pushed into the queue and completed requests will be popped from the queue. The trick is to make sure that if a request completes before older requests have been completed it remains in the queue and does not get removed out of sequence.

The queue takes the form of a simple array, declared in global scope:

var AJAX_QUEUE = [];

The window.addEvent code is modified so that the Ajax object is pushed onto the queue before the request is made:

window.addEvent( 'domready', function() {

	$('drop-down').addEvent( 'change', function() {

		$( 'ajax-container' ).empty().addClass( 'ajax-loading' );
		var url = 'index.php?option=com_component&args=whatever';
		var a = new Ajax( url, {
			method: 'get',
			onComplete: function( response ){

// Ajax onComplete function code goes here.

			}
		});
		AJAX_QUEUE.push( a );
		a.request();
	});
});

Then you need to add some extra code to the onComplete function that will pop completed requests from the queue, but only in the correct order:

// Save the response in the Ajax object.
this.output = response;

// Process the request queue.
while ( AJAX_QUEUE.length ) {

	// If the oldest request in the queue is not ready, do nothing.
	if ( !AJAX_QUEUE[0].ready ) break;

	// Pop the request from the queue.
	r = AJAX_QUEUE.shift();

	// Only output to document when queue is empty.
	if ( !AJAX_QUEUE.length ) {
		$('ajax-container').removeClass('ajax-loading').setHTML(r.output);
	}

}

Note that the output is only placed in the document when the queue is finally empty, so the user never sees results from intermediate requests.

If you have multiple Ajax fields on a single page, then you will need to give each one its own queue, but the coding remains essentially the same.