Requirements
Prerequisite knowledge                                     
You should be familiar with ActionScript 3 and object-oriented terminology and principles. Some experience with frameworks is useful, but not required. Be sure you have first read Part 1: Context and mediators.
 
Required products
Flash Builder (Download trial)
 
Sample files
 
User level
Intermediate
 
This is the second part of my introductory series covering Robotlegs AS3. In the first part in the series you learned what Robotlegs AS3 is and had a brief "HelloWorld" introduction to the Robotlegs Context and Mediator classes. If you missed it, check out Part 1: Context and mediators. This article expands on those concepts and introduces models.
 
What is a model?
 
Model classes encapsulate your application's data and provide an API to access and manipulate that data. The other classes in your application will make requests of models via this API. When data on the model is updated, the model dispatches events that the other classes within your application can react to. Models are appropriate for capturing the domain logic, such as performing calculations or other manipulations. An example of this might be a shopping cart. When an item is added to the shopping cart model, the new total for all of the items in the cart is calculated. The new total is then stored on the model for access by other classes within the application. By placing this logic within your models, you ensure that it isn't scattered across the entire application and know exactly where to look to see how and when your data is being manipulated.
 
In addition to controlling access to data, models maintain the state of your application. State, in the sense of the data model, is not the same concept as a Flex state, which relates to controlling the appearance of your application. They are certainly related, but with a model, consider a list of objects. You want to keep track of which of these objects is selected, so the data model has a selected property which is updated with the currently selected item. Other areas of your application can now access this property to discover which item is selected and react accordingly.
 
Models can be written to be portable. Along with Services, this is something to keep in mind when you develop your models. There are many common sets of data that can easily transport between one application and the next. As an example, think of a UserLoginModel or a ShoppingCartModel. Portability takes a bit more thought and energy, but no more than writing the same code over again for each project does.
 
The model deserves a lot of attention. It is the core of your application. The visual components get all the ooos and aaahs, but as a developer you know that data is the man behind the curtain. It is our jobs, as developers, to curate the data and deliver it to those beautiful interface items accurately. This is why isolating domain logic in the model is so important. By isolating it, you have made it easier to locate, update, and maintain. With the basic definition of the model in hand, let's look at a small example application.
 
On to the code!
 
This pure ActionScript 3 application makes use of Keith Peters's awesome Minimal Comps library. Don't worry, if you love Flex (and how could you not?) the next example will be a Flex app, but Minimal Comps makes it so easy to make quick examples with ActionScript 3, and Robotlegs is just as easy to use with a straight ActionScript application as it is with Flex. So let's take a look at the application and take it apart to examine its pieces.
 
Above is the application we will be examining. It is a simple list of authors with a quote displayed for the author that is selected in the list. You can download the archived Flash Builder project, which includes the Robotlegs 1.1 and MinimalComps SWC files as well, at the top of this article page.
 
SimpleListExample.as
 
public class SimpleListExample extends Sprite { public function SimpleListExample() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; } }
This is the main file of the application, the entry point. It is a simple Sprite. This is a Robotlegs application, so the first thing we need to do is create a Context:
 
SimpleListExampleContext.as
 
public class SimpleListExampleContext extends Context { public function SimpleListExampleContext(contextView:DisplayObjectContainer) { super(contextView); } override public function startup():void { injector.mapSingleton(AuthorModel); mediatorMap.mapView(ListView, ListViewMediator); mediatorMap.mapView(QuoteTextArea, QuoteTextAreaMediator); mediatorMap.mapView(SimpleListExample, ApplicationMediator); } }
It is common in Flex applications to leave out the constructor completely as the contextView is set inside the MXML declaration. Since this is an ActionScript application, you want to pass the contextView into the constructor so that it can be set immediately. Now let's create an instance of the SimpleListExampleContext in the main application view:
 
SimpleListExample.as
 
public class SimpleListExample extends Sprite { public var context:SimpleListExampleContext; public function SimpleListExample() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; context = new SimpleListExampleContext(this); } }
You should notice the context variable. It is important to have a reference to your context held in memory. If you were to simply create a new instance of SimpleListExampleContext in the constructor without placing it into a variable, it would be garbage-collected at the whim of Flash Player. This can cause some seriously confusing hours of troubleshooting! With Flex, you simply declare the context in MXML which holds on to the reference without needing the variable, unless you create the context in the Script tag. It will require a reference there just like the above ActionScript example.
 
This application will have two views: a list of names that can be selected and a text area that displays a quote from the selected item in the list. Those two views are called:
 
  • ListView
  • QuoteTextArea
 
No sense being too creative with the naming scheme here. Both these views are simple sub-classes of base Minimal Comps classes. Here they are:
 
ListView.as
 
public class ListView extends List { public function ListView(parent:DisplayObjectContainer) { super(parent); } }
QuoteTextArea.as
 
public class QuoteTextArea extends TextArea { public function QuoteTextArea(parent:DisplayObjectContainer) { super(parent); } }
This application has only one each of these. It would be fully functional if we simply used the base List and TextArea classes. Why bother with the sub-classes at all? This is a habit—and a good one when dealing with dependency injection. If we know that we are going to mediate a view class, we sub-class it and give it a name that represents its purpose. When we do it this way, it can be easily isolated for mediation.
 
Now all we need to do is get these two views onto the Stage as children of the main application. My preferred approach to this in ActionScript applications is to place a createChildren method on the main view and call it from a mediator. Here is the main view with the createChildren() method:
 
SimpleListExample.as
 
public class SimpleListExample extends Sprite { private var hbox:HBox; private var list:ListView; private var quoteText:QuoteTextArea; public var context:SimpleListExampleContext; public function SimpleListExample() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; context = new SimpleListExampleContext(this); } /** * Called from ApplicationMediator's onRegister() */ public function createChildren():void { hbox = new HBox(this,0,0); addChild(hbox); list = new ListView(hbox); list.alternateRows = true; quoteText = new QuoteTextArea(hbox); quoteText.editable = false; quoteText.selectable = false; } }
You might be wondering why not just add the views in the constructor? I prefer to let Robotlegs get started before adding the children. By calling createChildren() from the ApplicationMediator, we are 100 percent certain that all of the primary application bootstrapping is done and we are good to go. Here is the ApplicationMediator:
 
ApplicationMediator.as
 
public class ApplicationMediator extends Mediator { [Inject] public var view:SimpleListExample; override public function onRegister():void { view.createChildren(); } }
The mediator doesn't have any more than the one responsibility in this simple example, but in your apps, mediating the view that is the contextView is a handy mechanism. After the mediator has been created, it needs to be mapped. This is an easy step to forget but one to think back on when you hit Debug and your mediator just doesn't respond. The odds are it isn't mapped, or its view component has never been ADDED_TO_STAGE. The startup method in your context will now look like this:
 
SimpleListExampleContext.as
 
override public function startup():void { mediatorMap.mapView(SimpleListExample, ApplicationMediator); }
When you run the application now, you should see an empty list and text area. Now we need to get the views some data. We are going to put some static data in a Model class that our view component's mediators will be able to access. Here is the basic model:
 
AuthorModel.as
 
public class AuthorModel extends Actor { private var _list:Array; public function get list():Array { if(!_list) initializeList(); return _list; } protected function initializeList():void { var twain:Author = new Author("Twain"); var poe:Author = new Author("Poe"); var plato:Author = new Author("Plato"); var fowler:Author = new Author("Fowler"); twain.quote = "Why, I have known clergymen, good men, kind-hearted, liberal, sincere" + ", and all that, who did not know the meaning of a 'flush.' It is enough " + "to make one ashamed of one's species."; fowler.quote = "Any fool can write code that a computer can understand. " + "Good programmers write code that humans can understand."; poe.quote = "Deep into that darkness peering, long I stood there, wondering, " + "fearing, doubting, dreaming dreams no mortal ever dared to dream before."; plato.quote = "All things will be produced in superior quantity and quality, and with greater ease, " + "when each man works at a single occupation, in accordance with his natural gifts, " + "and at the right moment, without meddling with anything else. "; _list = [twain,fowler,poe,plato]; } }
Right away you notice that the AuthorModel extends Actor. Actor is a convenience class provided with MVCS. It provides the appropriate code for injecting the IEventDispatcher used to communicate in the context as well as a dispatch() method to send events through that dispatcher. It is not strictly necessary to extend Actor, but you will need to provide the IEventDispatcher injection point yourself if you do not. The Model in Robotlegs MVC+S is purely conceptual; there is no Model class to extend and the naming convention is simply to express the intent of the class.
 
The model currently supplies a list of Authors that I admire, with a quote from each of them. The Author class is a simple value object to hold these properties, as you can see below:
 
Author.as
 
public class Author { public var name:String; public var quote:String; public function Author(name:String) { this.name = name; } /** * Minimal comps took issue with toString(); * @return * */ public function get label():String { return name; } }
With a model stuffed full of data, we need to deliver that data to our list for display. The first step is to map the model for injection. The second step is to mediate the ListView and transfer the data from our model to the list. Your context's startup() method will look like this after mapping the model:
 
SimpleListExampleContext.as
 
override public function startup():void { injector.mapSingleton(AuthorModel); mediatorMap.mapView(SimpleListExample, ApplicationMediator); }
To map the model, or any other class that you would like to inject, you will use the context's injector. The injector has several methods for mapping classes and values for injection. The mapSingleton() method is very common for mapping a simple class for injection. It should be noted that the term singleton in this context is not referring to a Singleton in the strict sense of the design pattern. In this case, mapSingleton() means that the injector will create and supply one single instance of the AuthorModel whenever it is asked to do so. You can have as many instances of AuthorModel as you want, but the injector will only create and inject the one you asked for with the above mapping. There are several other methods for mapping with the injector, and I highly recommend reading Till Schneideriet's documentation for the SwiftSuspenders Injector. By default this is what Robotlegs uses. The Robotlegs Best Practices documentation also discusses using the Injector. In addition to the documentation, I will be discussing the other injection mapping options in future articles.
 
With the model mapped for injection, we are one step closer to getting the Authors into their list. To do that, we will mediate the ListView and inject the AuthorModel into the mediator. Here is the mediator for
 
ListView:ListViewMediator.as
 
public class ListViewMediator extends Mediator { [Inject] public var view:ListView; [Inject] public var authorModel:AuthorModel; override public function onRegister():void { view.items = authorModel.list; } }
Outside of the [Inject] tag for the mediated view components, we haven't looked at the use of the [Inject] tag or discussed how Robotlegs provides access to dependency injection for your classes. In the ListViewMediator you will see the view being injected. In addition to the view, there is a public property called authorModel of the type AuthorModel marked with the [Inject] tag as well. When you place the [Inject] tag over a public property, when the class is created by Robotlegs, it will "inject" that property based on the rules you have mapped. Note that if you haven't supplied a rule for the injection, you will receive a runtime error directing you to the problem. If this occurs, be sure to check your mapping. In this case, we have already used mapSingleton() to prepare AuthorModel for injection.
 
In the onRegister() method of the mediator that is run when the mediator has been fully constructed and provided injection, we are accessing the model and assigning the list property to the items property of the view. Nice! We should see the items in the list now.
 
..well...
 
...oh, ya!...
 
First we need to map the mediator:
 
SimpleListExampleContext.as
 
override public function startup():void { injector.mapSingleton(AuthorModel); mediatorMap.mapView(ListView, ListViewMediator); mediatorMap.mapView(SimpleListExample, ApplicationMediator); }
There we go. The ListView is mediated. Take note of the order of the mappings. The ListView mediator mapping is abovethe SimpleListExample (main view) mapping. The SimpleListExample is the main view, as well as the contextView. The contextView is treated a bit differently when its class is mapped. Instead of listening for the ADDED_TO_STAGE event to occur, the contextView is immediately mediated. Since the contextView is generally already on the Stage, we can't reliably listen for an event that might never occur. Since we want ListView to be mediated when it is added to Stage, we make sure that it is mapped before the ApplicationMediator's onRegister() ever has a chance to be called. If you recall, the ApplicationMediator's onRegister() is where we tell the main view to add its children, including the ListView.
 
fig01
 
So with the ListView being populated with data, you can now select from the list of Authors in the list. Pretty exciting, no? Try to contain yourself for a bit. You still have a ways to go.
 
Now we want to fill the QuoteTextArea with some text—preferably a quote from the selected Author. To do that, we will be making additions to the AuthorModel so that it keeps track of the selected Author. When an Author is selected in the ListView, the ListViewMediator will update the AuthorModel. The AuthorModel will then send an event to notify anybody that might be listening that an Author was selected. We will also need to create a mediator for the QuoteTextArea so that it can listen for that event and update with the quote from the selected Author. We know we will need the event, so let's make that first:
 
SelectedAuthorEvent.as
 
public class SelectedAuthorEvent extends Event { public static const SELECTED:String = "authorSelected"; private var _author:Author; public function get author():Author { return _author; } public function SelectedAuthorEvent(type:String, author:Author = null, bubbles:Boolean = false, cancelable:Boolean = false) { super(type, bubbles, cancelable); _author = author; } override public function clone():Event { return new SelectedAuthorEvent(type, author, bubbles, cancelable) } }
The event is rather unremarkable. It is a typical custom event with a single constant, SELECTED, for a type and an optional author parameter. Now that we have the event, we need to update the AuthorModel to keep track of the selected Author and notify the application when it has changed:
 
AuthorModel.as
 
private var _selected:Author; public function get selected():Author { return _selected; } public function set selected(value:Author):void { _selected = value; dispatch(new SelectedAuthorEvent(SelectedAuthorEvent.SELECTED, selected)); }
With the above code added to the AuthorModel, we are effectively able to track the currently selected Author. To set this property on the model, we will update the ListViewMediator to listen for a selection event from the ListView and update the model when it occurs:
 
ListViewMediator.as
 
override public function onRegister():void { view.items = authorModel.list; addViewListener(Event.SELECT, handleSelected) } private function handleSelected(event:Event):void { authorModel.selected = view.selectedItem as Author; }
Inside of the ListViewMediator's onRegister() we will use the addViewListener() method to add an event listener on the view for Event.SELECT to be handled by the handleSelected method. Now, when an item in the list is selected, the event handler method will access the selected property of the AuthorModel and update it with the item that was selected. That value will be set, and the AuthorModel will dispatch the event notifying the rest of the application that this has occurred.
 
"Why not just dispatch the selected event here and be done with it?"
 
You very well could, and if your application is this simple it is probably the appropriate way to go. There aren't many apps this simple in my life, and this is an example of how to use a model, so that is the reason we are taking the seemingly long way here. And now that we have traveled down that path, we still need to get that text into the QuoteTextArea, so let's get it mediated:
 
QuoteTextAreaMediator.as
 
public class QuoteTextAreaMediator extends Mediator { [Inject] public var view:QuoteTextArea; override public function onRegister():void { addContextListener(SelectedAuthorEvent.SELECTED, handleSelectedAuthorChanged) } private function handleSelectedAuthorChanged(event:SelectedAuthorEvent):void { var author:Author = event.author; view.text = author.quote; } }
So there it is. The QuoteTextArea now has a connection to the rest of the application. In its onRegister we will use the addContextListener() to listen for the SelectedAuthorEvent.SELECTED and handle it with handleSelectedAuthorChanged:

fig02
The handleSelectedAuthorChanged method takes the information from that event and updates the view's text property with the quote from the selected Author. After we get that mediator mapped, we will have achieved the extent of this example's functionality:
 
SimpleListExampleContext.as
 
override public function startup():void { injector.mapSingleton(AuthorModel); mediatorMap.mapView(ListView, ListViewMediator); mediatorMap.mapView(QuoteTextArea, QuoteTextAreaMediator); mediatorMap.mapView(SimpleListExample, ApplicationMediator); }
You now have an example that covers the basics of using models in a Robotlegs application. The guardians of your data, models, play an extremely important part in your applications. By utilizing models as the access point for your data, you isolate where the data is stored and manipulated. When you sit down to solve a problem, you know where to look for issues regarding data and its subsequent representation of the state of your application. If your data is scattered across your application, it becomes a murky stew making it difficult to isolate trouble spots quickly or add functionality to the application.
 
This has been a very brief introduction to models. I highly recommend diving into some research on the M in MVC. In the next part, I will take a look how services are used within a Robotlegs application. After services, we will have a look at commands to complete the Robotlegs MVC+S implementation before moving on to some more advanced topics.
 
If you can't wait, there are articles on my blog (and lots of others across the internets) dealing with various Robotlegs topics including a 25-minute screencast of mine. John Lindquist has a Hello World screencast on his blog. Additionally there is a best-practices document that has proven helpful for many. You can always hit the Robotlegs Knowledge Base for help and support. It has an active group of community volunteers, including myself, that diligently answer questions regarding all things Robotlegs. For an in-depth discussion of these topics and more, see ActionScript Developer's Guide to Robotlegs.
 
 
Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.