Cairngorm, Commands, Views and Harikiri
One of the things that really leaves me scratching my head when it comes to Cairngorm is updating a view based on something happening in a command that changes the value or state of the model. I know with release of Cairngorm that coincided with the release of Flex 2 the ViewHelper and ViewLocator were essentially deprecated in favour of using Flex's excellent data binding in conjunction with the ModelLocator. I have no issues with that concept, it works well most of the time .... well maybe some off the time. Things can start to get really ugly, really fast when you start introducing use cases that are beyond anything but the simplest, and developers like me start contemplating harakiri.
Background
Before I go to far into proposing a solution it would be good to quickly look at what the currently proposed options are. Alex Uhlmann has put together some excellent articles on how to update Views from Commands, below are three basic concepts and a proposal pulled from his articles.
- The concept of saving a id in a command to reference the VO being updated.
We just need to send the unique identifier to the Command, since the Command itself needs to retrieve a StockQuote object from the manager object. So the GetStockQuoteEvent class gets another quoteId property. In GetStockQuoteCommand we need to store the unique identifier as an instance property since the command needs to retrieve and update the correct StockQuote object in an onResult or onFault handler.
- The concept of a value in the VO to track the state of the value object.
Usually, while a remote service is being processed, the UI is supposed to react to it with i.e. displaying a progress or disabling certain UI components to let the user know that further requests cannot be dispatched. In our use case we'd like to disable the "Get Quote" button while a remote service call is being processed. We can achieve this easily with adding a property to our representing model object from iteration 2 StockQuote. Let's call it isPending and make it a Boolean where our view can bind to.
- The concept of using data binding to update the view based on changes to the model and/or value objects.
Basically, your views bind to properties that are retrieved from your ModelLocator. These properties can be changed from your Commands, other business logic or other views and once changed; all listening views are updated seamlessly.
- Proposal to use events from the model to update the view, but notes many limitations.
I see two drawbacks using this approach. First of all, it’s more code to write and most importantly, to read. This is because your views need to subscribe manually to events and define event handlers. It might also affect your models since some of them might need extra EventDispatcher functionality. Secondly, we need to make sure that our model objects exist when the view is subscribing to the event.
The first three concepts essentially boil down to using data binding and the model locator and/or value objects to communicate to the view that something visually needs to change to reflect a change in the data model. This is the method that seems to be favoured by Adobe Consulting, and for many things it works perfectly, ie binding a display form to a value object. The proposal with events from the model has no traction, and in that article, Alex points to the Observable pattern but it is essentially the same as using data binding with a ChangeWatcher (if you ask me), which is what concepts 1-3 can be boiled down to.
Scenario
Now the we have a quick understanding of the options available, let's take a quick look at the implementation and where it starts to get ugly. For this example let's say we have a value object, I'll call it a 'Widget' for agrument's sake, that we need to have saved on a server somewhere. The user clicks a save button, the view changes state to reflect the fact the Widget is being saved (the form gets disabled and a progress indicator is displayed to show something is happening) and the process of getting the server to perform the update starts (all the normal Cairngorm process and flow here). The server responds back that either, a) the Widget was saved, or b) something went wrong and the Widget was not saved. The Command that was used to perform the save (since it implements the Responder interface) handles the result or fault. Everything is straight forward so far.
Use case 1
If the Widget saved successfully the progress indicator needs to goes away, and the view that was editing the Widget is enabled to allow the user to continue working.
Use case 2
If the Widget was not saved (an error occurred), the progress indicator needs to go away, an Alert needs to get displayed to tell the user something bad happened, and something on the view that was editing the Widget needs to reflect the fact that an error occurred (some sort of visual cue).
Implementation
Using data binding and the ModelLocator (the combination of the first three concepts above) the implementation would look something like this. Create a property in the model
called 'widget' that contains a Widget instance. The Widget has a property state with a value of -1, 0, or 1 depending on whether or not the Widget was saved (I use three values to give the Widget a stateless mode, ie the user has not yet tried to save the Widget, or the Widget is in the process of being saved).
package com.dgrigg.vo
{
import com.adobe.cairngorm.vo.ValueObject;
public class WidgetVO implements ValueObject
{
//values to reflect state
public static const DEFAULT:int = 0;
public static const SAVED:int = 1;
public static const ERROR:int = -1;
[Bindable]
public var id: int = 0;
[Bindable]
public var name: String;
[Bindable]
public var description: String;
[Bindable]
public var state: int = DEFAULT;
public function WidgetVO()
{
}
}
}
I can use databinding and a ChangeWatcher to watch the value and when it changes to 1 I know the Widget was saved, a value of -1 and I know it failed. The ChangeWatcher will dispatch an event that the view is listening for. This gets ugly in two places. In the Command I need to determine what has happened and update the state of the Widget accordingly.
//code in the Command that is handling the server response
private function onResult(event:ResultEvent):void
{
//do some stuff
widget.state = WidgetVO.SAVED;
}
private function onFault(event:FaultEvent):void
{
//do some stuff
widget.state = WidgetVO.ERROR;
}
I also need some type of conditional statement in the handling method in the view to deal with the different states the Widget could have, just looks and seems ugly to me.
//code in the View that is editing the Widget
...
watcher = ChangeWatcher.watch(model.widget, 'widgetSaved', widgetStateHandler);
...
private function widgetStateHandler(event:PropertyChangeEvent):void
{
if (event.newValue == WidgetVO.SAVED)
{
//change the state to reflect the widget was saved
}
else if (event.newValue == WidgetVO.ERROR)
{
//handle an error
}
else if (event.newValue == WidgetVO.DEFAULT)
{
//do nothing
}
}
This solution seems very fragile to me. The state of the Widget is being handled in multiple places and it seems like a disaster waiting to happen. If the view is bound to a selected widget (ie the widget can change at runtime), then this solution gets worse because you need to add in handling the case when the widget is changed, you need to unwatch the old widget and then watch the new widget. Another twist on this solution could be the Observable (and here) pattern but it is essentially putting lipstick on a pig, it looks and smells exactly like the databinding/ChangeWatcher solution.
A better solution
Ideally there must be a way for the view to listen to an event to indicate the state change in the widget. Jon at Firemoss had an interesting idea of dispatching events when commands have affected the model and having the view(s) listen for the events, but even by his own admission it was a little hackish and not an ideal solution. Thankfully Jay posted a comment about a solution he had to solve this problem and Joe was kind enough to share it.
The basic concept is a ViewController, that is instantiated in the View. The view registers listeners to the events the ViewController dispatches. The ViewController listens to Cairngorm Events dispatched from the FrontController (which orginate in the result/fault handlers in a Command) and then dispatches out Flash Events, that the view is listening for. This seems like a nice clean solution, it nicely keeps the separation between the view and the controller/command and it gets around having large conditional statements in the View to deal with Model changes. To quote Jon
It's a bit like ViewHelper, but backwards enough where it works implicitly instead of explicitly.
Using the ViewController still leaves one open to the issue of dealing with the case where many Widgets are open in different views. All instances of the ViewController will 'hear' the events whenever the FrontController dispatches an event to note a change in the state of a widget, and all the views will react, whether or not they are the view that is bound to that particular Widget. A simple solution for this is to pass along a unique id that is tied to the View and stored in the Command (something Alex proposed above in a slightly different fashion). The ViewController would receive the view's id as part of the event it hears and then pass that id along in the event it dispatches that the view receives. The view could do a simple check to determine if it is the view that should be affected, and then act accordingly.
Thanks to Jay (the originator of the idea) and Joe, nice job and kudos for hopefully saving many people who use Cairngorm from committing harakiri when it comes time to update views based on model state changes.
Apr 14, 2007 at 11:57 AM
I can't thank you enough for posting this. I've been worrying about this for the last week. I've been Googling trying to find out if the ViewLocator, ViewHelper had been deprecated , had posted to the mailing lists and was around the corner from my wit's end! Thank you so much for putting this here at he perfect time!
M
Jul 03, 2007 at 1:15 PM
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.
, ...), the callback class is new Callback(, =null).
But how can the view be notified of the data service response and status? In the "real world" databinding is not always sufficient... as articulated above in your blog.
Your solution requires the command to dispatch another event to the front Controller to allow a view to be notified via the view Controller.
Here is another - I think better - solution to the issue. Allow the view to submit itself as a responder to the command [which in turn submits itself as a responder to the delegate]. How is this "chaining" of responders handled easily?
When we dispatch events to commands, we create custom events. If that all custom events have a constructor attribute to allow transporting the responder also... then voila' the command can 1) cache the responder, 2) data service responses can be handled by the view to update the ModelLocator, and 3) data service responses can also be handled by the command to "notify" the responder.
You mean my views all have to support some responder interface? Yikes that would be awful!
Instead allow the view to create an instance of a CallBack class; this class requires a result handler at a minimum. So much like addEventListener(
Now include this OPTIONAL callback instance as part of the event attributes. Extend the command to support cache of the view responder. And voila' you are done!
This is VERY clean: 1) views are standard aggregates and 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.
Jul 05, 2007 at 8:40 AM
Thomas, you make some good points and your strategy is good however I have one issue with it. Views are not the place to be making adjustments to data coming back from a remote call and/or adjusting data in the model to reflect the returned data. One or both of those scenarios will happen almost every time a remote method is invoked. I do agree it is a little cumbersome to have the ApplicationController dispatch an extra event and listen for it, but I feel it provides a much cleaner solution because the command/responder is responsible for dealing with the data and prepping it before the view ever 'sees' it. A view should never have responsibility for manipulating the data model. Having that logic bound in a View is inviting a host of issues.
Jul 19, 2007 at 4:38 PM
Hi,
this really sounds nice and I have already implemented this with success. However, my Cairngorm applications already have quite enough of files so adding yet another one is a bit of a turnoff. Do you usually have a ViewController for each of your views? Or just the ones where simple data binding is limiting?
Regarding comment 3: I want to understand better what you mean when you say "an extra event". I think what you are saying is that if you have an event/command sequence like "getConfigEvent->getConfigCommand" the getConfigCommand need to broadcast "an extra event" when data is received and ready. Isn't that what you are talking about because you don't want the view to bind directly to the data - you want the ViewLocator to wait for this extra "dataReadyEvent" and call some update function on the view. Right?
And one more question: When you want to trigger a method in a view do you make the frontcontroller command update the model, and the ViewController call the method? Or do you just make the ViewController update the model before calling the method?
Your method looks like a good idea, but also looks like more code :)
Great article, much appreciated.
Sammi
Jul 20, 2007 at 8:07 AM
Hey Sammi,
I only use the ViewControllers for views where simple data binding is not enough. Regarding comment 3 and the extra event, you are correct, an extra event needs to be dispatched upon the 'result' or 'fault' from the 'command'.
I would either update the model from within the command or allow the ViewController to do the update and then have the ViewController fire the event to notify the View. I don't think I have ever used the FrontController for updating the model, I use it only for handling application wide events.
Yes, it is some extra files, unfortunately OOP has a habit of doing that, but I think in the long run you end up with a more scalable and robust solution.
Jul 25, 2007 at 10:31 AM
Maybe a dumb question - but that is because I am dumb. I really like this approach but I am still trying to think of ways to minimize code. What would happen I I have just ONE view controller. That controller broadcasts normal events for every FrontController event, or at least those that views needs. Then I insert this "global" ViewController in each of the views that needs to listen for one of the events. I assume this is considered bad practice - but is there any overhead in it? My view might just need to listen for one event, but the ViewController is registered for 100. Do you understand what I am saying??
Jul 26, 2007 at 11:32 AM
You could use just one view controller. That would be essentially the same idea as the Front Controller. If you are using one I would recommend making it a singleton so you don't have multiple instances, which would create some overhead. It could get a little messy with handling all the various methods and callbacks for 100 views, but it would work.