Thursday, June 26, 2008

dojo event system

dojo has a beautiful(I'd love to say too beautiful) event management system.I needed to override the default 'move' event of the TreeRPCControllerV3 as I wanted to get the 'deffered' object (another beautiful thing in dojo :)-I'd love to write about it some other day) returned by each of the 'move' while DnDing a node.And after reading couple of pages at dojo-book I got the solution-dojo event;I connected the RPCController's 'move' event using 'around' advice with a custom function that gets a default argument(a method invocation object) with some params and a proceed() method.
I'd love to copy from dojo book:

In addition to being able to call any function or method after any other function or method call, connect() can be used to call listeners before the source function is called. In Aspect Oriented Programming terminology, this is called "before advice" while the previous examples have all be "after advice". The terminology is confusing, but for a lack of anything less mind-bending or better accepted, we adopt it for the advanced cases that connect() supports.

Here's how we'd ensure that "bar" gets alerted before "foo" when is called:

dojo.event.connect("before", exampleObj, "foo", exampleObj, "bar");

As you can see, we just perpended our previous call to connect() with the word "before". In the other cases, the word "after" was the implied first argument, which we could have added if we wanted, but typing more isn't something any of us want, and most of the time "after" is what you want anyway.

The same connection using kwConnect() looks like:

type: "before",
srcObj: exampleObj,
srcFunc: "foo",
targetObj: exampleObj,
targetFunc: "bar"

Before and after advice give us tools to handle a huge range of problems, but what about when the listener and the source functions don't have the same call signatures? Or what about when you want to change the behavior of a function from someone else's code but don't want to change their code? If we take the view that any function call in our environment is an event, then shouldn't we also have an "event object" for each of them? When using dojo.event.connect(), this is exactly what happens under the covers, and we can get access to it via "around advice". Long story short, around advice allows you to wrap any function and manipulate both it's inputs and outputs. This'll let us change both the calling signatures of functions and change arguments for listeners (among other things).

Unlike the other advice types, around advice requires a little bit more cooperation from the author of the around advice function, but since you'll probably only be using it in situations where you know that you want to explicitly change a behavior, this is isn't really a problem. This example take a function foo() which takes 2 arguments and provides a default value for the second argument if one isn't passed:

function foo(arg1, arg2){
// ...

function aroundFoo(invocation){
if(invocation.args.length < 2){
// note that it's a real array, not a pseudo-arr invocation.args.push("default for arg2"); }
var result = invocation.proceed();
// we could change the result here return result; }
dojo.event.connect("around", "foo", "aroundFoo");

The aroundFoo() function must take only a single argument. This argument is the method-invocation object. This object has some useful properties (like args) and one method, proceed(). proceed() calls the wrapped function with the arguments packed in the args array and returns the result. At this point, you can further manipulate the result before returning it. If you don't return the result of proceed(), it will appear to the caller as though the wrapped function didn't return a value. At any point you could call another function to do things like log timing information. Once this connection is made, every time foo() is called aroundFoo() will check it's argument and insert a default value for arg2. Around advice is kind of like goto in C and C++: if you don't know better you can make huge messes, but when you really need it, you really need it. Despite the power of around advice, it's not very often that globally changing a function signature or return value is the best plan. More often, you'll just want to smooth over the differences in calling signatures between two functions that are being connected. As you might have come to expect by now, Dojo provides a solution for this type of impedance matching problem too. The solution is before-around and after-around advice. These advice types apply a supplied around advice function to the listener in a connection. They only apply the around advice when the listener function is being called from the connected-to source. Put another way, it's connection-specific argument and return value manipulation. To access before-around and after-around advice, just pass in another object/name pair to a normal "before" or "after" connection, like this:

var obj1 = { twoArgFunc: function(arg1, arg2){
// function expects two arguments }

var obj2 = { oneArgFunc: function(arg1){
// this function expects a two-element array // as its only parameter
} };

// we'd probably connect the functions somewhere else. Perhaps in a // different file entirely.

function aroundFunc(invocation){
var tmpArgs = [ invocation.args[0], invocation.args[1] ];
invocation.args = tmpArgs;
return invocation.proceed();

// after-around advice
dojo.event.connect( obj1, "twoArgFunc", obj2, "oneArgFunc", "aroundFunc");

Each function now gets what it expects, and the code calling obj1.twoArgFunc() never need be the wiser that any of this is happening.