Painless publish/subscribe with YUI custom events
We're using YUI now at Seesmic. We were using my Fleegix.js library, but as more developers come on board I think it's better to use something mainstream. YUI also has a large array of well-tested UI elements (Overlay, TabView, etc.) that make rapid development easier.
So far the transition has been relatively painless. YUI is massively frameworky and kind of ponderous -- but it is well documented, and it's pretty easy to get it to do what you need it to.
The glaring exception so far has been their implementation of publish/subscribe. Sure, YUI has custom events . Any toolkit for building real apps (as opposed to Ajaxy pages or sites) needs it for keeping UI components decoupled.
Sadly, in the case of YUI, pub/sub is a bit of a mess -- custom events is a weird mish-mosh of function, scope, and params flying around, with no over-arching design that I can discern.
I keep thinking that the Yahoo (no, I won't put that fucking exclamation mark on the end -- that's! completely! stupid!) JS guys have to be smart, but when I see something like this I can't help but wonder what drugs they were on. The over-the-top ad-hoc-ness bears a strong resemblance to a good old-fashioned game of Fizzbin .
I have used other pub/sub systems that don't suck. Dojo , for all its other imperfections, has a really uber eventing system with really nice pub/sub -- which is of course why I cloned its API for Fleegix.js's event.publish and event.subscribe .
Thankfully, JavaScript's meta-programming facilities make it pretty easy to consign YUI's Bizarro World custom-events API to code-purgatory. A simple wrapper around the CustomEvent stuff gives you a simple and sane pub/sub interface.
Here's the code:
YAHOO.util.Event.channels = {};
YAHOO.util.Event.subscribe =
function
(channelName, obj, listenMethod) {
var
getSubscriberMethod =
function
() {
return
function
(type, args, scopeObj) {
scopeObj[listenMethod].apply(scopeObj, args);
}
}
if
(!
this
.channels[channelName]) {
this
.createChannel(channelName);
}
// Create a subscription for the passed object
this
.channels[channelName].subscribe(getSubscriberMethod(), obj);
};
YAHOO.util.Event.publish =
function
(channelName, paramObj) {
if
(!
this
.channels[channelName]) {
this
.createChannel(channelName);
}
this
.channels[channelName].fire(paramObj);
};
YAHOO.util.Event.createChannel =
function
(channelName) {
this
.channels[channelName] =
new
YAHOO.util.CustomEvent(channelName,
this
);
};
This creates two methods you can use in the YAHOO.util.Event namespace -- publish , and subscribe . Calling either one of these will automatically create the specified channel if it doesn't exist.
You use it like this:
var
subscriber =
new
function
() {
YAHOO.util.Event.subscribe(
'someChannelName'
,
this
,
'handlePublish'
);
this
.handlePublish =
function
(obj) {
alert(obj.message);
};
}
var
publisher =
new
function
() {
this
.sendMessage =
function
(msg) {
YAHOO.util.Event.publish(
'someChannelName'
,
{message: msg});
}
};
publisher.sendMessage(
'foo'
);
// Alerts 'foo'
This is a really minimal example, but you get the idea.
Basically this just lets you send an arbitrary package of stuff (data, functions -- anything you can stick in a JavaScript object) to a named channel, and all the interested objects listening on that channel will receive it. Nice, simple pub/sub -- easy as pie.
I haven't really banged on this very heavily, so there may be some bumps in the road, but so far it's working like a champ -- publishing messages across the app, and staying out of the way.
Obviously too, there's a tiny bit of overhead added working through this wrapper -- but pub/sub is for macro-level events that function across your entire app, not micro-eventing within single components. The performance implications of using this wrapper should be pretty negligible.
It's a small price to pay for avoiding a game of Fizzbin.