Friday, July 16, 2010

Ajax and Aspect-Oriented Programming

I'm working on an ajax application which contains a number of different widgets which can manipulate data. In many cases, multiple widgets depend on the same data. I've been thinking of ways to notify all relevant widgets when a piece of data changes, to let them know that they need to fetch new data and refresh themselves.

Related to this is the question of caching. If widgetA fetches data from the server, and widgetB depends on the same data, widgetB should be able to access the data that widgetA just fetched.

So what I'm moving toward is this:

A single "service" object which contains ajax accessor methods.

A "cache" wrapper around the service object which intercepts calls, decides if cached data can be used, and also decides when listeners need to be notified of new data.

Here's my Cache object so far.


Cache = function(options){
var self = {};
var dataSource = options.dataSource;
var log = UNAB.Debug.NamedLogger("UNAB.Cache");
var aopCallbacks = {};

var aopCallbackCheck = function(name){
log.debug("aopCallbackCheck called for: " + name);
if(aopCallbacks[name]){
aopCallbacks[name]();
}
}

$jq.each(options.dataSource, function(key){
self[key] = function(){
// wrap callbacks with a function which will invoke an aop handler
var processedArguments = UNAB.Util.makeArray(arguments, function(arg){
if(typeof arg === "function"){
return function(){
arg.apply(null, arguments);
aopCallbackCheck(key);
}
}else{
return arg;
}
})
dataSource[key].apply(null, processedArguments);
};
// add a "Cached" method to the object, which will just pass values straight through,
// possibly use the cache, and not invoke any aop stuff
self[key + "Cached"] = dataSource[key];
});

self.addAopCallback = function(options){
aopCallbacks[options.name] = options.fn;
}

return self;
}


How it works is this:

You pass your data accessor object to the cache in the constructor:

wrappedDataServiceObject = Cache({dataSource: dataServiceObject})


then you can call all of the methods that were available on dataServiceObject on wrappedDataServiceObject. But wrappedDataServiceObject has some addional stuff. For every method xxx, wrappedDataServiceObject adds a method xxxCached. I haven't worked out just what I'm going to do with this. For now method xxx does the same thing as method xxxCached.

Additionally, you can register listeners which get run whenever a non-cached method's callback gets called. The callback is wrapped with a new function which calls the callback, but also looks for any aopCallbacks you've registered. This way, you can broadcast to all listeners that new data has been checked. Note -- this happens only on method xxx, not on method xxxCached.


wrappedDataServiceObject.addAopCallback({
name:"deleteContact",
fn: function(){
$jq(".contactsChangedListener").trigger("contactsChanged");
}
})


Things in my app were beginning to look a little spaghetti-ish, and I was getting weird endless loop problems. Hopefully organizing stuff this way will make things easier to manage.

No comments: