Global AJAX responders in Prototype

I encountered a small, but ugly problem in our Symfony-driven project today: Unauthenticated AJAX requests, which f.e. may happen when the session timed out on the server, but the user hasn’t reloaded the page in the meantime, are also forwarded to the globally defined login module / action. This of course leaves the HTML page, which is constructed upon single HTML components, in a total mess. Ouch!

So yeah, rendering the complete login mask HTML as partial to the client is stupid, but also relatively easy to fix:

public function executeLogin($request)
{
    if ($request->isXmlHttpRequest())
    {
        // renderJSON is a custom function which json_encode's
        // the argument and sets an X-JSON header on response
        return $this->renderJSON(array("error" =>
                                    "Your session expired"));
    }
    ...
}

This was of course only half of the fix. I still had to handle this (and other) special JSON response on the browser’s side:

new Ajax.Request("mymodule/myaction", {
     onSuccess: function(response, json) {
         if (json.error)
         {
             // display the error
             alert(json.error);
             return;
         }
         // the actual callback functionality
     }
}

Uh, anyone screams “spaghetti code”? Yeah, you’re right. I quickly headed for a more general implementation, also since we can’t do that for a couple of symfony-specific prototype helpers anyways, like update_element_function, whose Javascript code gets generated by Symfony dynamically. So how can this be generalized?

Ajax.Responders to the rescue

Actually, prototype already contains some kind of “global hook-in” functionality for all Ajax requests triggered by the library: Ajax.Responders.

While this seemed to support all common callbacks (among them onCreate, onSuccess, onFailure and onComplete), some testing showed though that f.e. the onComplete callback was always called after the specific AJAX request’s onComplete callback, so this was pretty useless for me. After all, I also wanted to prevent that the specific callback gets executed when I encountered an error…

After diving through prototype’s code for some hours I found a solution. Particularily helpful here is that prototype signals every created Ajax request to the onCreate handler and gives the request and response object handling this request as arguments to it. Time to overwrite prototype’s responder code! Here is it:

Ajax.Responders.register({
    onCreate: function(request) {
        var oldRespondToReadyState = request.respondToReadyState;
        request.respondToReadyState = function(readyState) {
            var state = Ajax.Request.Events[readyState];
            var response = new Ajax.Response(this);
            var json = response.headerJSON;
            
            if (state == 'Complete' && json && json.error)
            {
                alert(json.error);
                return;
            }
            oldRespondToReadyState.call(response.request, 
                                           readyState);
        }
    }
});

Another particularily useful piece of knowledge I gathered today to let this work is how Function.prototype.call and Function.prototype.apply work (both are available since Javascript 1.3).
Basically they allow the execution of a function in scope of the object given as first parameter (there is a nice introduction available here).

If you’ve ever wanted to “send an event to some object to make its listener fire” because the listener’s code depended on the fact that the this reference points to the object the event was fired upon, you should now have a viable alternative:

Event.observe(myObj, 'click', myHandler);
// is call-wise equivalent to
myHandler.call(myObj);

No need to create custom mouse events and throw them around any longer… 😉