Cairngorm and the Presentation Model - Part 2 : Sample application
In the first part of this series, we've shortly discussed the reasons why we may need a separate presentation layer for applications, and what pattern we'll use for this : the Presentation Model pattern.
Now it's time to try to implement this pattern in a real life application. Paul Williams already gave a basic, non cairngorm example on his blog. My goal here is to imagine how it could be done in a simple yet real cairngorm application.
Again, it does not pretend to tell what your Presentation Model implementation should be. I'll try to discuss what it could be, and what are the main issues one may encounter with such an implementation.
Application overview

This sample is a simple library application example. On the left side, you can browse some books and create a new one. When you select one, its details appear in the right panel, letting you (the user) edit it, and may be delete it.
From a view components standpoint, you have three view MXML files : the main application file CairngormPM (my root Application sub class), one view for the left panel called BookListPanel, and one view for the right panel called BookDetailsPanel.
FYI, the application communicates with a PHP Service through a RemoteObject, but that has little to do with our concerns. What you should note for now is that the application does communicate with an application server.
The service uses two Value Objects : Book and Author, which will be mapped in AS3 through the usual [RemoteClass] metadata.
The business layer
My source code is divided into four main packages : model, view, control, and business. I know most people place the business package into the model package, but I'm used to do it this way.
This business package is the only package we won't discuss here. It contains only two classes : LibraryServiceDelegate (a business delegate for my whole service), and Services, my ServiceLocator.
So there's nothing particular here, the presentation layer has nothing to do with it.
The control package
In this package, I have my FrontController class which does its usual job, and two sub packages :
- The events package, which just defines two Event sub-classes BookEvent and AuthorEvent. In other words I have one event for each Value Object. Each Event subclass defines various types (String constants) for each of the CRUD operations.
- The command package, of course, contains all my Commands. I have created a Command super class which looks like this
package control.command { import model.ModelLocator; import model.domain.LibraryModel; import mx.controls.Alert; import mx.rpc.events.FaultEvent; /** * * @author davidderaedt * * My Commands super class. * * It sets a reference to our application's domain Model, * and a default implementation for a responder's fault handler. */ public class AbstractLibraryCommand { protected var libraryModel:LibraryModel = ModelLocator.getInstance().libraryModel; public function fault(info:Object):void { var faultEvt:FaultEvent = info as FaultEvent; Alert.show(faultEvt.fault.toString(), "ERREUR"); } } }
Again, there's nothing particular here. Just note that I've added a reference to my application main domain model class, which we'll discuss next.
The model package
Ok, we're getting closer to our subject now ! :)
My model package contains my ModelLocator, and two sub packages :
- A presentation package : the one that will contain our presentation model classes
- A domain package : our traditional model
The domain model
Borre Wessel and others have suggested that a ModelLocator should really just be a locator, i.e. not the place to store actual data but rather to find other objects where it's stored. While this is not directly related to the Presentation Model, this is very important, since it reduces the role of a class (the ModelLocator) that has often been considered as evil, being a singleton (please excuse me for not getting into the details here).
So, here I have one separated domain model class, called LibraryModel.
package model.domain { import model.domain.vo.Author; import model.domain.vo.Book; import mx.collections.ArrayCollection; /** * * @author davidderaedt * * Our application Domain Model classes structures our application data * and defines its behavior. * * In bigger applications, the Domain Model would probably be scattered * into several pieces (eg BooksModel, AuthorModel, ...). * */ [Bindable] public class LibraryModel { private var _currentBook:Book; public var bookCollec:ArrayCollection; public var authorCollec:ArrayCollection; public var currentAuthor:Author; public function set currentBook(pBook:Book):void { _currentBook = pBook; currentAuthor = getAuthorById( pBook.idauthor); } public function get currentBook():Book { return _currentBook; } public function getAuthorById(pId:int):Author { for (var i:int = 0 ; i < authorCollec.length ; i++) { var a:Author = authorCollec.getItemAt(i) as Author; if(a.idauthor==pId) return a; } return null; } public function addBook(pBook:Book):void { bookCollec.addItem(pBook); } public function removeBook(pBook:Book):void { var i:int = bookCollec.getItemIndex(pBook); bookCollec.removeItemAt(i); } public function replaceBooks(pOldBook:Book, pNewBook:Book):void { var i:int = bookCollec.getItemIndex(pOldBook); bookCollec.setItemAt(pNewBook, i); } public function replaceCurrentBook(pNewBook:Book):void { replaceBooks(currentBook, pNewBook); currentBook = pNewBook; } } }
In this example, I've added some responsibilities to this model object : it does not just store the application data (thus defining its state) it also adds some data logic (behavior), and can be thought of as a business object. I guess some people don't like having this kind of logic here, and will let others (the commands) do the job. I have to admit I'm not sure to realize all the consequences of this choice yet.
In bigger applications, we would probably break this into more classes, but in this application, the model is pretty simple.
I've also placed my vo package here, which just contains our two ValueObjects : Book and Author.
The presentation model
So, here we are. This is the place where we'll create our Presentation Models, which are supposed to help us in encapsulating the presentation data and logic of our application.
In the first part of this post, we've seen that, in the Presentation Model pattern, we'll have one PM for each view. We've also seen that the views will know their PMs and that the PMs will not know their view. Note the pattern, as described by Martin Fowler, allows to do it the other way round. It's just more convenient this way for us Flex developers.
The first thing I did here, following Paul Williams example, is create a Presentation Model super class, that will encapsulate all common behavior shared by every concrete Presentation Model.
package model.presentation { import flash.events.EventDispatcher; /** * * @author davidderaedt * * Super class for Presentation Models * * Common behavior for Presentation Models should be placed here. * For example, a way to handle the various "show" events of a view * (borrowed from Borre Wessel & Paul Williams's PresentationModel example). * * Added EventDispatcher inheritance, because your views will want * to observe their PMs and update accordingly. * */ public class AbstractPM extends EventDispatcher { private var firstShow:Boolean = true ; public function AbstractPM() { } public function handleShow():void { if( firstShow ) { handleFirstShow(); firstShow = false ; } else handleSubsequentShows(); } protected function handleFirstShow():void { // to be overriden } protected function handleSubsequentShows():void { // to be overriden } } }
As you see, I've shamelessly copy/pasted the handleshow behavior, but I've also added the inheritance of EventDispatcher. Even if I didn't use it in this sample application, we can imagine that our view may want to observe their Presentation Model. And maybe Presentation Models will want to listen to each others (more on that later) ?
As we'll end up with (more or less) one PM per view, we can imagine our presentation layer as an hierarchy, a pyramid of PMs, just like the views. Here, the MainPM, related to the main application view, will have two children (not in the display list sense of course, but in terms of composition) : the two PMs related to the two child views. Our MainPM will have one property for each PM. Of course, if the two panels had children views with corresponding PMs, then the panels PMs would also have a reference to the children PMs, and so on.
package model.presentation { import control.events.AuthorEvent; import control.events.BookEvent; import model.domain.LibraryModel; /** * * @author davidderaedt * * The root Presentation Model, related to the root view * (most of the time, that's your Application MXML file). * * It has the responsibility of creating is "child" * Presentation Models, ie the Presentation Models * related to its child views. * */ [Bindable] public class MainPM extends AbstractPM { public var libraryModel:LibraryModel; public var listeLivrePM:BookListPM; public var ficheLivrePM:BookDetailsPM; public function MainPM(pLibraryModel:LibraryModel) { libraryModel = pLibraryModel; listeLivrePM = new BookListPM( libraryModel); ficheLivrePM = new BookDetailsPM( libraryModel); } override protected function handleFirstShow():void { new AuthorEvent(AuthorEvent.EVENT_AUTHOR_GET_ALL).dispatch(); new BookEvent( BookEvent.EVENT_BOOK_GET_ALL).dispatch(); } } }
So each PM has references to the PMs of the children of its related view. It will probably instanciate them in its constructor, just like I did here, and pass it some data.
Update: added a forgotten dispatch() in the deleteBook method
package model.presentation { import control.events.BookEvent; import model.domain.LibraryModel; import model.domain.vo.Book; [Bindable] public class BookDetailsPM extends AbstractPM { public var libraryModel:LibraryModel; public function BookDetailsPM(pModel:LibraryModel) { libraryModel = pModel; } public function deleteBook():void { new BookEvent(BookEvent.EVENT_BOOK_DELETE, libraryModel.currentBook).dispatch(); } public function updateBook(pLivre:Book):void { new BookEvent( BookEvent.EVENT_BOOK_UPDATE, pLivre).dispatch(); } } }
There are three important things to note here.
First, you'll notice that the PM has public methods that will dispatch cairngorm events. This has two consequences:
- Our views will now call these methods instead of dispatching events or cairngorm events. This will de-couple our views from cairngorm, and eliminate the need of [Event] metadata
- Our PMs are then coupled to cairngorm. Of course, we can imagine having an application Event superclass (maybe implementing an interface) that would limit this coupling, or having events bubbling the PM hierarchy to the main PM that would be the only PM coupled with cairngorm.
Second, our PM may directly update the domain model, in order to let the commands only do the remote operation jobs.
package model.presentation { import control.events.BookEvent; import model.domain.LibraryModel; import model.domain.vo.Book; /** * * @author davidderaedt * * The presentation Model for our Book List * * Notice how its setBook method directly * updates the model. * */ [Bindable] public class BookListPM extends AbstractPM { public var libraryModel:LibraryModel; public function BookListPM(pModel:LibraryModel) { libraryModel = pModel; } public function createBook():void { new BookEvent( BookEvent.EVENT_BOOK_CREATE).dispatch(); } public function getAllBooks():void { new BookEvent( BookEvent.EVENT_BOOK_GET_ALL).dispatch(); } public function setBook(pLivre:Book):void { // here libraryModel.currentBook = pLivre; } } }
Third, the PM exposes my domain object (library model) as a public property. This is probably the biggest issue that this implementation has raised, so I'll talk about this in my conclusion.
A simpler ModelLocator
As a consequence, our ModelLocator is now very dumb. It's a place to :
- Instanciate the application's domain and presentation models
- Get these models, essentially for commands
package model { import com.adobe.cairngorm.model.IModelLocator; import model.domain.LibraryModel; import model.presentation.MainPM; import model.domain.vo.*; /** * * @author davidderaedt * * The ModelLocator is used as a way to access your main * Domain Model(s) and the root PresentationModel. * We don't need / want anything more here. * */ [Bindable] public class ModelLocator implements IModelLocator { private static var instance:ModelLocator ; // Our main Presentation Model public var mainPM:MainPM ; // Our Domain Model public var libraryModel:LibraryModel; public static function getInstance():ModelLocator { if( instance==null) { instance = new ModelLocator(); // Our Models get instanciated at the time // our ModelLocator is instanciated instance.libraryModel = new LibraryModel(); instance.mainPM = new MainPM( instance.libraryModel); } return instance ; } } }
The view layer
This layer is the one which benefits the most of our PMs, because it pulls a lot of responsabilities out of the views.
Our views are simpler than before, even if it's not obvious with this sample application. Remember that you'll put all your navigation logic in the PMs, such as BrowserManager communications, i18n, states and transitions, enabling/disabling parts of the views, and so on.
<?xml version="1.0" encoding="utf-8"?> <mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300" layout="absolute" title="Books"> <mx:Script> <![CDATA[ import model.domain.vo.Book; import model.presentation.BookListPM; [Bindable] public var bookListPM:BookListPM; ]]> </mx:Script> <mx:DataGrid left="0" top="0" right="0" bottom="0" id="dg" dataProvider="{bookListPM.libraryModel.bookCollec}" change=" bookListPM.setBook( event.target.selectedItem as Book)"> <mx:columns> <mx:DataGridColumn headerText="Title" dataField="title"/> <mx:DataGridColumn headerText="Price" dataField="price"/> <mx:DataGridColumn headerText="Date" dataField="date"/> </mx:columns> </mx:DataGrid> <mx:ControlBar horizontalAlign="center"> <mx:Button label="Update List" click="bookListPM.getAllBooks();"/> <mx:Button label="Create New Book" click="bookListPM.createBook()"/> </mx:ControlBar> </mx:Panel>
The views are now much smaller. They are very closely related to their PM though, as they can't do anything wthout him. They represent its data, call its methods, and may also observe its events to update accordingly.
Some things will stay in the views, though. For example, the instanciation of other dynamic views, Alerts, and of course all rendering logic.
<?xml version="1.0" encoding="utf-8"?> <mx:Panel title="Selected Book" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="400" height="300" xmlns:vo="model.domain.vo.*"> <mx:Script> <![CDATA[ import model.domain.vo.Author; import mx.events.CloseEvent; import mx.controls.Alert; import model.presentation.BookDetailsPM; import mx.collections.ArrayCollection; import control.events.BookEvent; [Bindable] public var bookDetailsPM:BookDetailsPM; // What about this ? private function getAuthorName(pItem:Object):String { var author:Author = pItem as Author; return author.firstname+" "+author.lastname ; } private function askDeleteConfirmation():void { Alert.show("Are you sure you want to delete this Book ?", "Warning", Alert.YES|Alert.NO, null, deleteConfirmationAnswer); } private function deleteConfirmationAnswer(pEvt:CloseEvent):void { if(pEvt.detail == Alert.YES) { bookDetailsPM.deleteBook(); } } ]]> </mx:Script> <vo:Book id="newBook"> <vo:idauthor>{ authorsCMB.selectedItem.idauteur}</vo:idauthor> <vo:date>{ DateField.dateToString( dateDF.selectedDate, "YYYY-MM-DD" )}</vo:date> <vo:idbook>{bookDetailsPM.libraryModel.currentBook.idbook}</vo:idbook> <vo:is_public> { publicCB.selected}</vo:is_public> <vo:price> {Number( priceTi.text )}</vo:price> <vo:title>{ titleTi.text }</vo:title> </vo:Book> <mx:Form x="10" y="10"> <mx:FormItem label="ID"> <mx:Label text="{ bookDetailsPM.libraryModel.currentBook.idbook}"/> </mx:FormItem> <mx:FormItem label="Title"> <mx:TextInput id="titleTi" text="{bookDetailsPM.libraryModel.currentBook.title}"/> </mx:FormItem> <mx:FormItem label="Published"> <mx:DateField id="dateDF" formatString="DD/MM/YYYY" selectedDate="{ DateField.stringToDate(bookDetailsPM.libraryModel.currentBook.date, 'YYYY-MM-DD')}"/> </mx:FormItem> <mx:FormItem label="Author"> <mx:ComboBox id="authorsCMB" dataProvider="{bookDetailsPM.libraryModel.authorCollec}" labelFunction="getAuthorName" selectedItem="{bookDetailsPM.libraryModel.currentAuthor}"/> </mx:FormItem> <mx:FormItem label="Price"> <mx:TextInput id="priceTi" text="{ bookDetailsPM.libraryModel.currentBook.price}"/> </mx:FormItem> <mx:FormItem> <mx:CheckBox id="publicCB" selected="{ bookDetailsPM.libraryModel.currentBook.is_public}" label="On Sale ?"/> </mx:FormItem> </mx:Form> <mx:ControlBar horizontalAlign="center"> <mx:Button label="Save" click=" bookDetailsPM.updateBook( newBook)" /> <mx:Button label="Delete" click=" askDeleteConfirmation();"/> </mx:ControlBar> </mx:Panel>
There are some things, however, for which I think it's harder to guess who it belongs to, such as this getAuthorName() method.
Finally, we can take a look at our main application MXML file, which is very simple too. It just instanciate cairngorm's ModelLocator, FrontController and ServiceLocator, and populates its child views with their PMs. Notice that it also communicates with its own PM, MainPM, as it calls its handleshow method when it triggers its creationcomplete Event.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:view="view.*" layout="absolute" xmlns:control="control.*" xmlns:business="business.*" creationComplete="appModel.mainPM.handleShow()"> <mx:Script> <![CDATA[ import control.events.AuthorEvent; import model.ModelLocator; [Bindable] public var appModel:ModelLocator = ModelLocator.getInstance() ; ]]> </mx:Script> <control:FController id="controller" /> <business:Services id="services" /> <view:BookListPanel x="10" y="10" bookListPM="{ appModel.mainPM.listeLivrePM}"/> <view:BookDetailsPanel x="418" y="10" bookDetailsPM="{ appModel.mainPM.ficheLivrePM}"/> </mx:Application>
In the third and last part of this series, I'll expose the various questions raised during the development of this application.
| Attachment | Size |
|---|---|
| CairngormPM.zip | 35.85 KB |


Anonymous on September 02nd 2008
bookDetailsPM.libraryModel.currentBook.titleandbookDetailsPM.libraryModel.currentBook.is_public. This is no different from the view binding directlyModelLocator.instance.libraryModel.currentBook.title, etc. The only difference is that instead of being dependent on the global variableModelLocator, the views in your example are dependent onBookDetailsPM, a small change (although getting rid of the dependence of the global variable is a benefit). To really reap the benefits of the Presentation Model pattern your views should never bind to properties or call methods on anything else than the presentation model object. For example, in the book details example, the view should bind to properties likebookDetailsPM.title,bookDetailsPM.is_public. These should be implemented inBookDetailsPMasreturn libraryModel.currentBook.titleandreturn libraryModel.currentBook.is_public. It may seem like a pointless exercise to duplicate theBookproperties inBookDetailsPMwhen you could just access the book directly as you have done in the example, but the benefit is that if the interface ofBookorLibraryModelchanges you don't necessarily have to change the view class, and if the view needs to change you don't necessarily have to change theBookclass, you only have to change the presentation model to adapt the changes on one side to support the needs of the other side. That is not all, however, you should also move things like thegetAuthorNamemethod and any creation of model object into the presentation model object, again to insulate the view from changes in the rest of the application. The presentation model object should take contain all view logic, all decisions about what to display, etc. and the view should just display it, listen for events (which it should only handle by calling methods on the presentation model, calls that the presentation model then should translate into events or whatever mechanism the application uses for messaging).david_deraedt on September 02nd 2008
Theo (not verified) on September 03rd 2008
nakliyat (not verified) on January 06th 2009
flexflip (not verified) on January 15th 2009
Darren (not verified) on September 25th 2009
I'm not sure if I have this right - are you saying that you still inject a BookDetailsPM object into the Presentation Model but you create a wrapper class for it and the only way the View can interact with the BookDetailsPM object is through the exposed methods of the wrapper class? So if there's any changes to the structure of the BookDetailsPM object, modifications will most often only needed to be made to the wrapper class?
Alex (not verified) on December 10th 2008
Alex (not verified) on December 11th 2008
david_deraedt on December 12th 2008
Elad Elrom (not verified) on December 13th 2008
david_deraedt on December 15th 2008
flexflip (not verified) on January 12th 2009
david_deraedt on January 12th 2009
Leonardo Diaz (not verified) on September 09th 2009
first I must say thanks for showing me this pattern I didn't know it. It has help me a lot. I'm new at flex. I was trying to use cairgorm but it goes against my preferences when coding (Don't like the singletons everywhere). So I have use the pattern with Spring Actionscript and it really made my app better I can inject all the needed objects to the mxml views. Now I will face a problem, I have some modules and they can be duplicated on the "stage" so some parent PM must have some public method the add at runtime the new view and its PM. I think this behavior can be added to the AbstractPM, but don't know certainly the implications when binding to and array on AS. About the subject of the public domain model, I prefer to add the needed properties to the PM, as a facade of the model ones. I know its more code and sometimes duplicated, but I prefer agains coupling.