YUI-EXT-MVC Controller, Adding Command AJAX Pattern

Todays article covers a new feature added to the AJAX control system of the YUI-EXT-MVC Controller (Controller.js). This feature leverages the command pattern, as originally proposed in Data Singleton For Ajax Request, to retrieve and cache data from the server. A new call method was added to the Controller object, which sends only one request to the server to fetch AJAX data, but can be called repeatedly to retrieve data. A callback function should be provided as the second parameter of the call method, which will be executed instantly, if the data is already retrieved, or as soon as the AJAX request returns. Ideally, all requests should be registered via the registerAjaxCallback before using the call method.

In this version of "controller.js", the configuration object became more important and has been slightly restructured. The new configuration system will first use whatever is passed at call-time into the AJAX methods, then it will use whatever is defined from the registerAjaxCallback configuration, and lastly will use default values. This provides the most flexibility for developers crafting AJAX requests. The biggest advantage to the configuration change, is that the developer can now define all aspects of a request once, using registerAjaxCallback. This becomes important when using the command pattern, because most of the time, these requests dont change.

Take for example, the custom categories widget on Mint.com. There are many places throughout the product that JavaScript code needs to know something about the categories, but categories are dynamic and can be changed by the user. To ensure that the JavaScript always has the same data as the server, we only update the category data object on the server, completely retrieving the category data object each time it is changed by the user. However, rather then retrieve it right away, we first invalidate the object, so when the data is needed next, the object will be re-fetched from the server. The request to fetch the categories is something like "/getJsonData.action/dataName=categories" and never changes, so I would define it in the following way:

Example 1: Using registerAjaxCallback For Categories

Core.Controller.registerAjaxCallback(categories, Core.Controller.TYPE_JSON, {url: /getJsonData.action/dataName=categories, success: function(categoryJson ) {
	// do something each time the data is fetch with the categoryJson 
}});

In Example 1, the url is specified in the configuration provided to registerAjaxCallback and a callback provided. The callback provided here will be executed each time the category data is retrieved, but is not necessary, if the developer intends to provide a callback function with each use of the call method. The following is a how to use on-demand access to the categories data object:

Example 2: Using call for Categories

Core.Controller.call(categories, function(categoryJson) {
	// do something with the categoryJson object
});

The first parameter is the requestId, and the second is the callback method. A third parameter may be provided to the call method, which is the URL, if it has changed from what was originally registered with registerAjaxCallback. Here is the call method definition:

Example 3: The call Method

call: function(rId, fx, url) {
	var cfg = _registeredConfigurationMap[rId];

	if (! cfg) {
		_YL.throwError(Core.Controlller.call - the provided requestId= + rId +  is not yet registered);
	}

	var callback = _YL.isFunction(fx) ? fx : cfg.success; // execute provided function, default to success function

	// data is cached, go ahead an immediately execute the callback function
	if (cfg.response) {
		callback.call(this, cfg.response, cfg.argument, cfg);
	}
	// data is invalid or is being requested
	else {
		// date is not yet being fetched, fetch it
		if (! cfg.isSending) {
			_R.send(get, url || cfg.url, cfg);
		}

		// a new callback was provided; Wait until until the request has finished to execute.
		if (callback === fx) {
			_YL.callLazy(function() {
				_that.call(rId, fx, url);
			}, function() {return ! _registeredConfigurationMap[rId].isSending;});
		}
	}
},

The method first ensures that a configuration object exists for the provided requestId, throwing an exception when it is not available. Then it determines the callback method to execute, using the provided function by default and the success function otherwise. Two new parameters are now being managed by the configuration object: response and isSending. The response parameter is set when the AJAX request returns and is where the data is cached; it will be set to null before sending a request to the server or if the invalidate method is called. The isSending parameter is a boolean, that is true while waiting for the AJAX response. If the response parameter is set, then we have cached data and the callback function should be immediately executed (the callback signature is the same as any other AJAX response). However, when the data is not cached, the function first checks to see if it is being retrieved (isSending is true when fetching data), sending a get request when it is not already being retrieved. Then if a new callback function was provided to the call method, it waits until the AJAX response is received to re-execute itself; this time it the response should be set and that code path executed.

As usual, the source code is available at Controller.js. I have created a demo page to test all AJAX functions of the Controller, including some simple examples of call. This was a difficult project to describe, so please let me know if you have any problems or questions.