Tim Huegdon has a great post about a really rather sweet piece of code called AjaxEvent over at his site, nefariousdesigns. Developed with Mark Aidan Thomas, AjaxEvent is an object that wraps around the YUI Connection and CustomEvents that makes managing multiple, distinct ajax calls really easy.
This is a rather belated post and is sort of a continuation of my last post about Aspect Oriented Programming. I’ve made yet another tabbed interface widgety thing (like the ‘tubes needs another one) that uses AOP to add additonal features. A tabbed interface widget provided a nice problem for me to experiment with AOP. You can see the results which allows tabs in a widget to be draggable and closeable too. Built with a dash YUI goodness of course.
The actual ‘problem’ wasn’t actually anything to do with the core behaviour of a tabbed interface. After all, the mechanics of such a problem is pretty simple; hide one element, show another. No, I wanted to make the tabs draggable and closeable, much like an OS tab that can be seen in Firefox or Eclipse. Using the YUI library, making the tab draggable is relatively straight forward but it did result in some duplicated code which I didn’t like. That was the ‘problem’.
Essentially, it was like this: My original code had two objects, the YAHOO.NT.TabsModule and YAHOO.NT.Tab. The TabsModule was the managing object for each of the Tab objects. If I wanted to make it draggable I need Tab to extend YAHOO.util.DDProxy. No problem there except of course there has to be additonal logic in the Tab object to do so and also manage the dragging ‘n’ dropping. Subclassing Tab would have worked but again code would be duplicated in those methods that needed the changes. But then you get into the trouble of class trees. For instance, DraggableTab subclasses Tab and so does Closeable Tab. But what if I wanted a Draggable and Closeable Tab? That’d be a little trickier to do while still maintaining some sense of clean code.
The rather clean solution turns out to be AOP. Quickly, AOP allows us to easily add new code to existing code without editing it. It does this by essentially wrapping the original code so that the new code runs ‘before’,‘after’ or ‘around the original code. These are the so called ‘aspects’. So it turns out that to make a Tab draggable I’d need to add new methods (my own as well as YAHOO.util.DDProxy) as well as fiddle with a few existing Tabs methods:
Here I’ve outlined which methods have been aspected (is that the right term?) and what the new code adds to the original.
YAHOO.NT.TabsModule.init is after aspected by both YAHOO.NT.DraggableTabs and YAHOO.NT.CloseableTabs – both adds a new CustomEvent which listens to all existing Tab instances
YAHOO.NT.TabsModule.selectTab is before aspected by YAHOO.NT.CloseableTabs – adds logic to detect to close tab.
YAHOO.NT.Tabs.initTab is after aspected by both YAHOO.NT.DraggableTabs and YAHOO.NT.CloseableTabs – adds new configuration logic, custom event creation
YAHOO.NT.Tabs.activateTab and YAHOO.NT.Tabs.deactivateTab are both after aspected by YAHOO.NT.CloseableTabs – adds logic to show/hide close icon.
AOP is a way of changing the behaviour of existing code without modifying it. It does this by providing, what AOP calls, advice at certain join points. In plain english, it provides a way of adding additional behaviour (advice) at certain points in your program (join points). For example, adding logging functionality to your code is a standard example. Generally, you don’t want every method in your code to contain logging code. It’s not really part of the core base code. AOP provides an easy way of doing without modifying your code. But because of the way AOP works, you can add as many behaviours as you want to, to any object.
What and how?
AOP provides three aspects called before, after and around. This means that you can add additonal behaviour before, after or around certain points in your code of your own choosing. I’ve created a static class called YAHOO.Abstract.Aspect that I use to add advices to other objects. It’s a very simple one and just adds the three advices to an method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
For instance, if I wanted to add a before advice, I’d do something like the following :
After advice is similar:
Adding around advice is slightly different as you have to provide two methods, one for before and one for after:
1 2 3 4 5 6 7
All arguments are passed transparently to the original methods and for all after methods, the return value of the original method is passed in.
Let’s see how easy it is using the standard logging example. Its an simple but probably unpractical example.
This example add a simple logging behaviour for every method of a specified object. When a method is called, it is logged and so are its arguments. It also displays the methods return value, if any. For this example, it makes use of Firebug and some of its console functions, specificially group and groupEnd. These basically indent and outdent grouped console.log outputs. So how do we write the behaviour? Well, before a method is called, we want to start a new console group and also output which method was called and also any arguments if any were passed. Something like this:
1 2 3 4
Then when a method has returned we want to log that the method has returned and any return value it may returned.
1 2 3 4 5
In AOP terminology these would be termed “before” and “after” advice. But since there is also “around”, we can use that instead so we only modify each method once.
So, the full code to add this logging behaviour to any object would be like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
The above code creates a new namespace so we don’t pollute the global namespace and a new method called trace. All it does it receive a target object and adds the around advice to each method that it finds. It’s also possible to extend this so that it receives another parameter which would state a whitelist or blacklist of methods to advise (or not). The actual call to advise any object, let’s say the Dom object of YUI :
You can see the output on the example page.
What else can AOP be used for?
Profiling – very similar to the trace, but instead maintains a timing of how long each method took.
Event firing on attribute changes – if you use getters and setters and when a set method has been called, automatically fire a valueChanged custom event. In practice, can really only be used when external entities change an objects property (Its not wise for an object to call its own setter method).
Validation of methods arguments – validate passed arguments to a method, specifically setter methods though any type of method can be used.
Convertors – bridge output from one object to another incompatible object
Provide an alternate to subclassing – use mixins instead of subclassing to add more and more behaviours while reducing amount of code duplication. I’ve used this for a widget and its really useful. I hope to be posting about that in a few days.
Is it useful?
Where else is it used?