YUI EXT MVC Controller

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 Managers 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 dont 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.