« January 2007 | Main

June 08, 2007

Cairngorm View Notifications

As Flex continues to grow as the choice RIA technology, many developers start investigating and apply JEE patterns to Flex implementations. Cairngorm is a Model-View-Controller framework - endorsed by Adobe - that is widely popular within the community. Cairngorm, however, is not ready for 100% "out-of-the-box" implementations of production applications.  The current versions of Caingorm (version 2.2 or before) need to be "tweaked" for production use. One of those areas encompasses the subject of "view notifications".

A recent blog article entry by Derrick Grigg articulates this very well; in the blog entry Derrick discusses an issue that every cairngorm developer encounters at some point.

The basic premise of the Cairngorm Model-View-Controller framework is:

  1. Model is a global repository for data
  2. Views use databinding to "watch" Model data changes and show formatted data.
    Views allow user interactions and dispatch user gestures as events
  3. Controller forwards events to commands for processing
    Commands update model data
  4. .... back to (1).


Consider the Cairngorm approach where a Command instance employs a Delegate class as "proxy" to the remote data services. The command instance "submits" itself as a responder to the delegate. The delegate, in turn, simply uses the specified responder [via the async token] to allow data service responses to go directly to event handlers within the command.

But how can the view be directly notified of the data service response and status? In the "real world" databinding solutions espoused by Adobe is not always sufficient...

However, the solution proposed in that blog requires several compensations:

  1. Views must employ/register private view controllers with the front controller,
  2. Command must dispatch new "notify view" events thru the front Controller to the view controller


This solution creates a proliferation of view controllers and is useful ONLY when multiple views want to listen to the same event and be notified simultaneously. A custom event bridge or a traditional databinding approach would be more practical for such situations [but that is another article]. In fact, in most scenarios, the view simply wants the notification directly and singularly.

As a result, ViewControllers are not the solution! There is a simpler - and better - solution to the issue; a solution that we have used successfully on many Universal Mind projects.

!! Use callbacks to serve as proxy responders for a view's cairngorm events !!

Question: "Can you say that in English?"
Answer:    Allow the view to submit itself as a responder to the event processor [e.g. command].

This solution is consistent with the approach used by the command where the command, in turn, submits itself as a responder to the delegate. The view attaches itself (via a proxy) as part of a queued chain of responders to the actual dataservices request/response.

How is this "chaining" of responders easily done? Well, when we dispatch events to commands, we create custom events. So we simply need to package a responder along with the normal event information that is dispatched to the Cairngorm framework.  If all custom events have a constructor attribute to allow transporting the responder also...

The command instance can temporarily cache the view responder/callback. Sometime later - after the asynchronous data service call responds, the following can happen:

  1. the data service responses can be handled by the command to update the ModelLocator directly, trigger databindings to update, and
  2. the data service responses or OTHER responses can also be forwarded by the command to the responder as to "notify" the view.

Question: "You mean my views all have to support some responder interface or be a subclass of
               some view parent class?"
Answer:   "No!  Yikes, that would be awful!"

Instead of requiring an abomination of view inheritance where all your mxml classes have to extend some base class, the view will instead create an instance of a CallBack class that will serve as a proxy responder class. Much like addEventListener(<event type>, <event handler>,...), the callback uses syntax like "new Callback(<result handler>, ,<fault handler>=null)".

Notice how the callback class does NOT know any details of the view itself. It simply has functions that may be called during an event response. And notice that the view does NOT know anything about who handles the event or how the event is processed. This "separation of concerns" is what MVC is all about. Don't forget to include this OPTIONAL callback instance as part of the event that will be dispatched.

So now let us look at some code. Below we see an example of functionality created to load user details from the server (or in a local cache). We use a custom event 'LoadUserDetailsEvent' class that accepts an optional "responder" parameter.



 private function loadUserDetails(user:UserVO):void {
       
    // notice how we "wrap" our view-level event handlers in a proxy responder instance
    // here we use something like public class Callbacks implements IResponder { ... }
    var responder : IResponder= new Responder(onResults_loadUserDetails,onFault_loadUserDetails);

    // Now simply dispatch [via controller] to the specific-command that is registered to "handle" this event
    CairngormEventDispatcher.dispatchEvent( new LoadUserDetailsEvent(user,responder) );
   }

   private function onResults_loadUserDetails(info:Object):void {
      // This is the "responder"-like result handler
      // that is called asynchronously when the command is is notified of the dataservice response
      // or synchronously if the user details were stored in a local in-memory cache

      var details : UserDetailsVO = (info as UserDetailsVO);
      ....
   }

   <mx:Button click="loadUserDetails(this.currentUser)" />

 

To summarize the above code snippet as psuedo-code, you (a) use a callback proxy wrapper class, (b) package the callback instance inside the event, and (c) extend the command base class to support the caching of the callback/view responder. And voila' you are done!

This is VERY clean:

  1. your views are still standard mxml or as classes with aggregates and composite controls,
  2. event dispatching is the same [with an optional callback] and
  3. you do NOT have to create crude view controllers to handle view notifications
  4. each view instance can have its OWN, separate callback
  5. views can then support  modal operations, undos, reverts, commits, etc... completely independent of each other.

This solution will soon be presented to the Adobe Cairngorm committee as a candidate for incorporation into the base framework. Additionally, we will be providing this "extension" and others as a open-source extension to cairngorm. If you are interested in this and other extensions please send emails to ThomasB@UniversalMind.com or visit blog.universalmind.com.