Thursday, August 7th, 2008
Enjoying the Observer pattern with custom events
I created an introductory example discussing custom events as an implementation of the Observer pattern. I posted this on my personal blog first and quickly got a port from my Prototype version to Malte’s jQuery port (and now we have a DOMAssistant and Appcelerator versions). I hope others keep them coming so we can aggregate a simple example of custom events and how it works across various toolkits:
The example dynamically adds functionality based on checkboxes to simulate events that could change processing and uses the fire()
and observe()
methods from Prototype.
I strap on behaviour to a list of names. When you click on the name, something can happen. To do this I could have done the following:
$$('ul#leftchoices li').each(function(el) { el.observe('click', function(e) { if ($('colorchange').checked) { changeColor(); } if ($('contentchange').checked) { changeContent(el.id); } }); });
In the click event itself, I do various checks and kick off behaviour. That can be fine for small actions, but what if you want to do more? This is when I prefer to abstract out the action and just fire an event:
$$('ul#leftchoices li').each(function(el) { el.observe('click', function(e) { el.fire('selected:choice'); }); });
At this point, this bit of code becomes dumb. It doesn’t know what to do when you select the item, and it just hopes that somewhere, someone is listening. That is where the observers come in, such as this one that changes the main content based on the selected name:
$('contentchange').onchange = function(e) { if (e.target.checked) { document.observe('selected:choice', changeContent); } else { document.stopObserving('selected:choice', changeContent); } }
When someone clicks on the checkbox, this method is fired and an observer is either added, or taken away.
Once you start building applications with this in mind, you may find a bit of a sea change. You start to think about the various events as a public API that you can easily expose to observers. Gone is the simple ability to look at one method and see what is happening, but the loose coupling gives you the ability to easily layer in your architecture. Based on some settings, behaviour can be dynamically added. You could even expose these events in a way that makes it easier for Greasemonkey hackers to come in and work with your application. All in all, a win-win for anything more than a simple example.
Although this example uses Prototype, you could do the same with the other top class JavaScript libraries. In fact, if someone wants to port this example to Dojo, jQuery, Mootools, YUI, or anything else, send me the files and I will put them up so we can all compare how custom events are done in the various toolkits.





This kind of pattern makes working with isolated JS components really nice. You separate your concerns, and are more easily able to test isolated parts of your code. We’ve been doing this for a while here at Motionbox and developed a library to help out with functionality like this ( http://github.com/tobowers/motionbox-eventhandler/tree/master ). Our custom events can also carry a payload with them (any data or objects you want). Our general theory is any important event should fire off an event (maybe in the global scope). Then, later you have a rich event ecosystem which you can build on top of.
We have something similar in JS.Class — Observable is a JavaScript implementation of Ruby’s Observable module. It’s more general-purpose in that it allows any JavaScript object to fire events, not just DOM objects. This is what Ojay’s custom event system is based on.
http://jsclass.jcoglan.com/observable.html
http://ojay.othermedia.org/articles/observable.html
It’s always irked me slightly that we seem to get the Observer pattern so closely tied up with the DOM on the web, and I see many custom DOM events that don’t really belong in the DOM at all.
Ryan Johnson has had an implementation for quite a while now, built on prototype
http://livepipe.net/core
Dojo’s dojo.connect is a very similair concept too
http://dojotoolkit.org/book/dojo-book-0-9/part-3-programmatic-dijit-and-dojo/event-system/simple-connections-dojo-connect
The separating events into larger “observers” is a very nice idea, and I have started implementing it in my own projects now. However, it would be nice to see a link to some large projects that have implemented this on a larger scale so I can dig through the code, get some ideas and such. If anyone has some links to large projects, or advanced tutorials on the subject, I would love to see them. Thanks.
http://www.two-birds.de is completely based on a complex timeout-interval-wait-observer stack to check for the availability of depending files for the on-demand loading mechanism. Its tb.observer.observe() function checks for changes in DOM or JS data elements, but lacks event hooks so far. I will add a twoBirds implementation of the page for comparision. BRB. :-)
I ported this to Appcelerator (http://www.appcelerator.org) – a live example is posted at http://winnerbyfall.appspot.com/test.html. Check it out and make sure to view source it — Appcelerator’s Web Expression Language is a truly innovative approach to event-driven UI programming for the web.
There’s nothing revolutionary about this. I was wondering what the hype was about so I ported it for MooTools: http://ibolmo.com/sandbox/events.html
It’s 1.2 compatible, but same strategy has been employed pre 1.0 days.
This time I galloped over the cliffs edge – normalizing my observer to handle events, DOM element attributes and nomal JS Objects in one function call will take some time… ;-)
I´ll be back though. My apologies for promising more than I can do on a short time.
Kevin,
LOVE the fact that it has almost zero javascript! Nice work!
In attempt to show some code for setting up custom events for various libraries, in another project I’ve done, I used the adapter pattern for plugging in various libraries (dojo, jquery, mootools, prototype, yui), and in each adapter is an example of how to setup a custom event with that library. I was surprised how it varied from each library, but each worked well. You can see code examples in the source on google code:
http://code.google.com/p/slikcalc/source/browse/tags/slikcalc/1.0/slikcalc/src/adapters/?r=48