20.11.06

AOP + JavaScript

This weekend a "friend" asked me to support my claim that Aspect Oriented Programming is easy in JavaScript. I wrote this based on someone else's post back in September, to deal with a situation I was having with jQuery's plugins. I would've used his code, but being the code nazi that I am (thanks for my nickname, Max) I changed it to my liking:
/**
* Provides the ability to execute functions (advices) before,
* after or around any other method, without having to
* change the source of such methods
*
* @author Francisco Brito
* @example
* new Advice(jQuery.fn).before('write', jQuery.fn.validate);
* new
Advice(jQuery.fn).after('validate',
* function (){ alert('foobar!'); });

* @yields
* $(node).validate();
* anonymous(); // alerts 'foobar!'
* $(node).write();

*/
function Advice(object) {
return {
/**
* Executes function 'f' before 'method'.
* @example
* new Advice(node).before('onchange', function (){
* alert('executes before the onchange');
* return true;
* });
*
*/
before : function(method, f) {
var original = object[method];
object[method] = function() {
if (f.apply(object, arguments) != Advice.BREAK){
return original.apply(object, arguments);
}
};
},
/**
* Executes function 'f' after 'method'.
* @example
* new Advice(node).after('onchange', function (){
* alert('executes after the onchange');
* });
*
*/
after : function(method, f) {
var original = object[method];
object[method] = function() {
if (original.apply(object, arguments) != Advice.BREAK) {
return f.apply(object, arguments);
}
}
},
/**
* Executes function f, from where "this.yield()" can be
* called to wrap the original
* @example
* new Aspect(jQuery.fn).around('validate', function (){
* alert('foo');
* this.yield();
* alert('bar');
* });
*/
around : function(method, f) {
var original = object[method];
object[method] = function() {
object.yield = original;
return f.apply(object, arguments);
}
},
BREAK = 'Aspect.BREAK'
};
}

A few things to note:
  • There's no way to unattach the advice (undo the code)
  • The "new Advice" is attached to an object. This can be a node, a prototype or any object with methods.
  • The "after" method doesn't keep the return value. I didn't need it.
  • The methods you're replacing must exist before the advice is defined. I bet this doesn't have to be like this but for now it is.
  • Don't get confused
For the jQueryans out there, this might help you organize the functions on your multiple-inherited nodes. Yes, I know it's object composition, not multiple inheritance, but for people more familiar with OOP, it's close enough.

No comments: