Applying Cocoa patterns to Javascript

Before Nexopia, most of my coding experience was writing enterprise-style applications in Java. I had done a lot of website stuff on the side, but nothing all that complex. Removing the languages from the equation, these are two very different styles of development. The main hurdle I find, in coming from developing desktop applications, is that GET/POST wall. It seems to break down a lot of patterns that work great with desktop apps. So I've done my best to leave my "desktop app" brain behind and start thinking in "web". And for the most part, that's been for the best. Recently, though, I've been re-learning my desktop dev skills via Cocoa and Objective-C and found I was able to apply them to some Nexopia problems! This makes sense, as we're doing a lot more ajax and client-side stuff these days, which tends to feel more like regular application development. So, without further ado, here are some of the ideas I've moved over into our framework:

1. Events

NexopiaPanels (the ones you see whenever you create or edit a new block) are getting a real event system, thanks mainly to YUI! So, instead of creating a panel subclass or trying to attach various custom hooks into the panels, you can "subscribe" to one of 4 events: there's one that fires after the panel opens, one that fires after it saves, one that fires after it closes, and one that fires whenever a new panel is created (this last one is especially useful when dealing with unpredictable javascript loading orders between modules).

A great way to attach one of these listeners is by putting another minion on the same element that is opening the panel:

minion_name="async_panel some_other_initialization"

and then using:

NexopiaPanel.onSave(element, handler_function)

in your load handler for "some_other_initialization". You don't have to worry about whether the panel gets created before your load handler is called or not. If the panel hasn't been created, this method just sets up a listener that waits for you and attaches the handler function to the panel associated with that element when everything's ready.

2. Notification Centers

So, listening to "saved", "closed", etc. events on panels is great for a lot of things, but what if you're not sure where an event is going to come from? That's where NotificationCenters come in. Apple uses them heavily, and I'm using a very stripped down version. It uses YUI CustomEvents behind the scenes. Basically, in one area of the code, I call:

NotificationCenter.defaultNotificationCenter.subsc​ribe(event_name, event_handler)

Just as before, we're passing in a function as the event_handler which will get run when the even occurs. But unlike before, we're just registering for a particular event to occur from anywhere on the page. Somewhere else in the code, we call:

NotificationCenter.defaultNotificationCenter.fire(​event_name, event_info_hash)

where event_name is that event that we're listening for and event_info_hash is a hash of information that our listener might be interested in. This could be something about the object that is firing the event or anything else really.

Thus the only coupling we have is to do with the event_info_hash contents. Yes, if they change in one area, we would have to make sure all of the areas of the code that listen for those events are appropriately updated, but I don't see a way around it, and it's better than the alternatives - what those alternatives are, I'm not sure, but I think they involve going down a dark road of one-off hackery that's probably a lot more brittle.

3. Last but not least: Delegates!

This is another one I borrowed that is used heavily in Cocoa. It takes a bit to get used to in coming from the Java world where you try to subclass everything in order to bend it to your will. A delegate is just an object that another object defers some functionality to. And they come in handy when you want the low-coupling of a listener, but need to interact with the results of the custom code.

Back to NexopiaPanels, there was another area where we were doing a lot of one-off solutions. Suppose you want to stop a panel from opening based on some custom validation code. Or suppose you want to stop it from saving based on some custom validation code. Listeners and NotifcationCenters are fire-and-forget. So to solve this one, I'm adding the ability to set a delegate on a NexopiaPanel. As with the listeners, there are convenience methods so you don't have to worry about initialization order:

NexopiaPanel.setDelegate(summoning_element, delegate)

The delegate can define however many supported functions it wants, and where those functions are defined, we will defer to it. Right now, I have only two:

validateBeforeOpen
validateBeforePostAndClose

Each passes the inner form of the panel (if it exists) as an argument. If they return true, the panel proceeds as usual. But if validateBeforeOpen returns false, the panel won't open, and if validateBeforePostAndClose returns false, the panel won't save and won't close. This way, we can make very custom validation logic for a particular panel without having to subclass it (which is messy anyway in javascript).

That's all folks. Back to your regular programming!