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… 😉

Videos der Oktober-Demo online

Der AK Vorrat hat heute Videomitschnitte der Redebeiträge der “Freiheit statt Angst”-Demonstration vom 11. Oktober 2008 veröffentlicht. Für alle, die nicht in Berlin sein konnten (wie meine Wenigkeit), zumindest ein kleines Trostpflaster.

Auszug der einer Rede von Monty Cantsin, Mitglied der Hedonistischen Internationalen:

Wir sind nach gut einem Jahr wieder hier. Und wir sind diesmal noch mehr Leute. Wir haben zusammen eine riesige Bewegung auf die Beine gestellt. Wir sind besser organisiert als je zuvor. Und wir sind verdammt viele.

Wir haben also die größte Grundrechts-, Datenschutz- und Freiheitsbewegung seit langen Jahren. Und eigentlich könnte sich ja jetzt alles zum Guten wenden….

Das tut es aber nicht. Der radikale Kahlschlag im Wald der Freiheit geht unvermindert weiter. Das Grundgesetz wird demontiert, demoliert, ja mit der Planierraupe wird es dem Erdboden gleichgemacht – als gäbe es kein Morgen mehr.

(Quelle)

Leider ging es an den Massenmedien auch wieder weitesgehend vorbei, dass in Berlin eine Demonstration für Freiheitsrechte und gegen Überwachung mit weit über 50.000 Menschen stattgefunden hat.

Goldene Fallschirme für gierige Finanzinstitutionen zu spannen und die Spareinkünfte des kleinen Mannes zu besichern, die ja wenigstens bis zum Weihnachtskonsumrausch existent bleiben müssen; das alles waren wohl wichtigere Themen, über die berichtet werden musste.

Windows binary available and Outlook

I’ve just uploaded a windows binary for guitone 0.9 – sorry that it took a little longer this time. I’ve been quite busy during the past days and having no windows machine at home doesn’t help much either 😉
Of course if there are other people willing to package guitone on windows, drop me a note. Its actually not much work. A detailed explanation and a InnoSetup installer script are already in place.

On a related note I’m working on a couple of new features for guitone. The next version will be able to create new monotone database and also create new projects from existing ones (basically a frontend for `mtn setup`). Furthermore I decided I should finally implement some workspace commands, so at least the equivalent of `mtn add` and `mtn drop` should be possible, `mtn revert` and `mtn rename` probably as well.

The monotone additions for netsync automation still not made it into trunk, mainly because I was not in the mood to finally fix the anticipated lua testing for stdio traffic (I really should not push this task further away, because the branch where the automate netsync stuff resides in diverges more and more over time…). And of course before this is not in monotone’s trunk it makes no sense to implement it in guitone either – so yeah, if you particularily wait for this feature, give me a kick in the butt so I get finally around.

You can’t keep me here

I wanna shake
I wanna wind out
I wanna leave
This mind and shout
I’ve lived
All this life
Like an ocean
In disguise

I don’t live for
Ever
You can’t keep
Me here

I wanna race
With the sundown
I want a last breath
Forgive
Every being
The bad feelings
It’s just me

I won’t wait
For answers
You can’t keep
Me here

I wanna rise
And say goodnight
Wanna take
A look on the other side
I’ve lived
All those lives
It’s been wonder
Full at night

I will live for
Ever
You can’t keep
Me here

(Source)

guitone 0.9 – “long time no see” release [updated]

I’ve released guitone-0.9 today. This is mainly a bugfix release which
contains only two new features:

  1. People can select the correct encoding for a file which is
    displayed in guitone’s diff dialog. Guitone is capable of setting
    and restoring a custom file attribute for diffed workspace files
    and automatically render the file with the correct encoding next
    time it is diffed.
  2. There is now a “Node information” dubbed info window which displays
    a couple of interesting information for selected workspace paths.

The usual download location for the new release is

http://guitone.thomaskeller.biz/g/download

Check the NEWS file there for a complete set of changes.

And before you ask – the Win32 setup file should be ready on Monday – I
don’t have access to a windows machine before… 😉

Update Unfortunately a small glitch made it into the 0.9 tarball release where qmake bailed out with a monotone error message “mtn: misuse: workspace required but not found” – this should be fixed now. Sorry for the inconvenience./Update