We recently re-wrote a fairly major Flex application, having an opportunity to start from scratch with lessons learned. The previous implementation used the Cairngorm framework, but not quite in line with its intent — it was purely used to in “data commands” to retrieve data from the server.
After reviewing some of the other MVC frameworks out there (PureMVC mostly), we decided to stick with Cairngorm but to expand its use to the full extent it was intended. In our mind, that meant to have Cairngorm events/commands handle all user gestures (unless they were trivial, component-local gestures like re-sorting a datagrid, etc.). This worked great, and we were pretty happy with the emerging structure of the application except for one thing: we didn’t like the ViewHelper/ViewLocator pieces. It seemed too cumbersome and overly structured for the simple task of having a command communicate with a view. We also didn’t like to control views from commands via the ModelLocator (as also suggested by the Cairngorm docs), as we had tried that in the earlier version and it quickly became a disastrous mess of spaghetti bindings that were incredibly hard to unravel, much less maintain.
To make this process simpler, we came up with a different approach: view facades. These facades are essentially ViewHelpers, but we eliminated the ViewLocator by making the facades singletons attached directly to the view. A sample makes this clearer. Consider a command GetBookDetails. We want this command to contact the server and get the details for a particular book. During this process, the view should show a progress or “loading…” view, to be switched out for the actual book details view when the loading is complete. That command could look like this:
public class GetBookDetailsCommand implements ICommand { public function execute(event:CairngormEvent):void { var bookEvent:GetBookDetailsEvent = event as GetBookDetailsEvent; BookViewFacade.getInstance().showLoadingView(); // Load the book details from the server here, using a custom HTTPServiceEvent of ours... var bookServiceEvent:HTTPServiceEvent = new HTTPServiceEvent('book_get', BookModelLocator.getInstance(), 'book', 'book'); bookServiceEvent.params = {id: bookEvent.bookId} bookServiceEvent.addCommandStatusListener(onBookResult); bookServiceEvent.dispatch(); } private function onBookResult(statusEvent:CommandStatusEvent):void { BookViewFacade.getInstance().showBookDetailsView(); } }
Notice the two calls to BookViewFacade, controlling the visible view in our BookView component. That component (BookView) would look something like this:
. . .
<view:BookViewFacade id="facade" bookView="{this}"/>
<container:ViewStack id="viewStack" width="100%" resizeToContent="true">
<container:HBox id="VIEW_LOADING" width="100%">
<control:Label htmlText="Loading..."/>
</container:HBox>
<container:VBox id="VIEW_BOOK_DETAILS" width="100%">
<control:Label htmlText="{MainModelLocator.getInstance().book.title}"/>
<control:Label htmlText="{MainModelLocator.getInstance().book.title}"/>
. . .
</container:ViewStack>
. . .Notice the declaration of the view facade BookViewFacade, where the view references itself. The facade is a singleton, implemented along these lines:
public class BookViewFacade { public var bookView:BookView; private static var instance:BookViewFacade; public function BookViewFacade():void { instance = this; } public static function getInstance():BookViewFacade { return instance; } public function showLoadingView():void { bookView.viewStack.selectedChild = bookView.VIEW_LOADING; } public function showBookDetailsView():void { bookView.viewStack.selectedChild = bookView.VIEW_BOOK_DETAILS; } }
The view is thus abstracted from the commands, but can be readily manipulated by singleton-access to the view facade and its methods. You can then easily work with views from within the Cairngorm framework, without the need for maintaining ViewLocators, etc.
ion gion | 08-Nov-08 at 11:41 pm | Permalink
Hmm … doesn’t look so cairngormish ..
The main issue here is having commands know the specific view …
Why are you doing a view facade ?
To have code behind ?
Wouldn’t a state variable(with a watcher on it) in a model from ModelLocator be enough for you to to show progress/hide progress/display list/select child/etc… and keep all that pesky view code in the view itself?
Ola Bildtsen | 09-Nov-08 at 3:30 pm | Permalink
Good questions, thanks for the feedback.
First of all, keep in mind that my demo is terribly simplified — most operations on the view facade involve much more than simply switching a view (things like resetting fields, clearing validations, etc.).
The commands don’t know anything about a particular view — they simply know about facades. So the separation of concerns is still there, plus the commands have easy access to manipulate the views thanks to the singleton logic.
Our top priority was to minimize the scripting logic in the view components. We tried using state variables/listeners on ModelLocator in the previous attempt, and quickly found that to become confusing and very hard to track and maintain. Each view component had tons of logic around the state of these variables. Refactoring code became hairy, as making tweaks to the logic around these variables potentially could change behavior you didn’t want to tweak. By having very specific methods on the view facades (with descriptive names), the view manipulation logic became very neatly contained, simple to maintain, and safer from a refactoring point of view.
Binding is a fantastic and powerful concept for many things, but it can also very easily be misused. We found that tons of bindings and listeners/logic around view manipulations to be a prime example of “bindings gone bad”.