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 don
t 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.