The AJAX control system of the YUI-EXT-MVC Controller has been tested and made available, Controller.js. Currently, it is mostly a simplified wrapper around YUIs Connection Manager, allowing the same functionality with additional support for registering callbacks and defining the expected response type. The Controller also contains logic to force all developer specified callbacks through a common set of wrapper functions by using the CustomEvents of Connection Manager. This allows for generic AJAX abort/failure logic and better validation of successful requests.
The Controller class has 4 public methods: get,
pget,
post, and
registerAjaxCallback. The first three methods require similar parameters to Connection Manager
s asyncRequest, except they: separate the passthrough argument
into a 5th parameter instead of (or in addition to) a member of the callback object, handle providing a function in place of the callback
object, support an array of strings for the post data
of pget
and post
functions instead of a string (will join that array with &
), and provides basic parameter type validation. The registerAjaxCallback
method allows the developer to register a success (and failure) callback function to a requestId
and define the expected type of response data: text, json, or xml. The type constants are publicly available on the Controller object.
The bulk of the work, however, is done in the private send
and success
functions.
Example 1: Request.Send
send: function(m, url, cb, args, data) { // configure request object; will be placed into the YUIObject.argument value var cfg = _YL.isObject(cb) ? cb : {}; if (_YL.isFunction(cb)) {cfg.success = cb;} // the callback object is a success function if (! _YL.isString(cfg.requestId)) {cfg.requestId =ajaxRequest+ Number.getUnique();} if (_YL.isFunction(cfg.success)) { // success function declares at call-time; register it _that.registerAjaxCallback(cfg.requestId, cfg.type, cfg.success, cfg.failure); } if (! _YL.isObject(cfg.scope)) {cfg.scope = _that;} if (! _YL.isNumber(cfg.timeout)) {cfg.timeout= _DEFAULT_TIMEOUT;} if (_YL.isDefined(cfg.argument)) { if (args) {cfg.argument = [cfg.argument, args];} // arguments defined twice; attempt to correct } else { cfg.argument = args; } // this request has callbacks if (_idCache[cfg.requestId]) { cfg.failure = _idCache[cfg.requestId].failure; cfg.success = _idCache[cfg.requestId].success; cfg.type = _idCache[cfg.requestId].type; cfg.url = url; if (data) {cfg.url +=?+ data;} } else { // todo: this should be logged as a warning - no success or failure callback defined for requestId } _YUC.asyncRequest(m, url, {argument: cfg, timeout: cfg.timeout}, data); }
The send
function first creates a request configuration object (from the YUI callback object, if available) and normalizes it. The configuration object defines: a requestId
, a scope
, a timeout
, a type
, and a success
callback (although, fire-and-forget requests can still be made without a success
callback). Additionally, a failure
callback and argument
object can be provided. If the argument
parameter of the callback object is defined, and the args
is provided, then both are added to an array, which will be the new callback argument
. If the success callback function is provided at the time of execution (not by registerAjaxCallback
), then registerAjaxCallback
is called to ensure that call-time defined AJAX requests use the same logic as registered ones. This allows developers the flexibility to register AJAX callbacks in advanced, if they prefer, or to continue to define callbacks at call-time. All this configuration data is provided to Container Managers
asyncRequest as the
argument of the callback object, so that it will be made available for the
success and
failure callbacks.
Example 2: Request.Success
onSuccess: function(eventType, YUIObj) { var data = YUIObj[0], cfg = data.argument; // retrieve response values, test response type, and initialize local variables var doc = (data.responseXML), // parens are necessary for certain browsers txt = (data.responseText), hdr = (data.getResponseHeader), contentType = (hdr && hdr[Content-Type]) ? hdr[Content-Type] :, isJSON = _YL.isDefined(txt) && (contentType.has(_that.TYPE_JSON,application/json) ||{=== txt.charAt(0)), isXML = _YL.isDefined(doc) && contentType.has(_that.TYPE_XML,application/xml), type = cfg.type, response = null; _assertType(type, _that.TYPE_JSON, isJSON); _assertType(type, _that.TYPE_XML, isXML); // is this a JSON response? if (isJSON) { response = txt.toJsonObject(); } // is this an XML repsonse? else if (isXML) { response = doc; } // default to text else { response = txt; } if (_YL.isFunction(cfg.success)) { cfg.success.call(this, response, cfg.argument, cfg); } },
Once a successful AJAX request returns, the success method executes. First it finds the configuration object, then it looks through the response headers and searches the
Content-Type to determine if it is a JSON or XML response, otherwise the response is assumed to be textual. Next we compare the received response type to that defined on the configuration object, throwing an exception when they don
t match. The TYPE_JSON will return a JSON object, while TYPE_XML will return the repsonseXML
, and both TYPE_TEXT and TYPE_UNKNOWN will return the responseText
. If there is a success callback defined on the configuration object, then it will be called (with the correct scope) passing through the response as the first parameter, the argument
as the second, and, just in case the developer needs to know something extra about the AJAX request, the configuration object as the third parameter.
So in summary, the benefit of using the Controller classes AJAX system is that it simplifies YUI Connection Manager and a developer can pre-register callbacks, ensuring the type of the expected response. It is available at http://code.google.com/p/yui-ext-mvc/source/browse/trunk/assets/js/mvc/lib/controller.js. In the future I will be adding command pattern logic for fetching JSON and HTML data from the server, which will leverage the AJAX system we discussed today.