An Introduction to Robotlegs AS3 Part 2: Models

This is the second part in the introductory series covering Robotlegs AS3 here on InsideRIA. In the first part in the series we learned what Robotlegs AS3 is and had a brief “HelloWorld” introduction to the Robotlegs Context and Mediator classes. This article is going to expand on those concepts and introduce Models.

robotlegs.jpg

If you missed it, here is Part One: Context and Mediators

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 will be calculated. The new total will then be 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. Think of, as an example, 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 AS3 application is making 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 AS3, 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.

SimpleListExample.zip

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. Here is the archived Flash Builder project (zip 168k), which includes the Robotlegs 1.1 and MinimalComps SWC files as well.

SimpleListExample.as

1
2
3
4
5
6
7
8
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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. In an Actionscript application you want to pass the contextView into the constructor so that it can be set immediately. Now lets create an instance of the SimpleListExampleContext in the main application view:

SimpleListExample.as

1
2
3
4
5
6
7
8
9
10
11
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 Flash Players whim. This can cause some seriously confusing hours of trouble shooting! With Flex, you simply declare the context in MXML which holds onto 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 will display 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. Both of these views are simple sub-classes of base Minimal Comps classes. Here they are:

ListView.as

1
2
3
4
5
6
7
public class ListView extends List
{
  public function ListView(parent:DisplayObjectContainer)
  {
    super(parent);
  }
}

QuoteTextArea.as

1
2
3
4
5
6
7
public class QuoteTextArea extends TextArea
{
  public function QuoteTextArea(parent:DisplayObjectContainer)
  {
    super(parent);
  }
}

This application only has one of 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 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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% certain all of the primary application bootstrapping is done and we are good to go. Here is the ApplicationMediator:

ApplicationMediator.as

1
2
3
4
5
6
7
8
9
10
public class ApplicationMediator extends Mediator
{
  [Inject]
  public var view:SimpleListExample;
 
  override public function onRegister():void
  {
    view.createChildren();
  }
}
Note that if you find that injections are not working the likely cause is that you need to add the keep-as3-metadata+=Inject, PostConstruct argument to your compiler options. Check here for more details.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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

1
2
3
4
5
6
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, we 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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

1
2
3
4
5
6
7
8
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 ABOVE the 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 ApplicationMediators 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.

view-requests-from-model.png

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. There is still 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 will keep 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 need to also 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 lets make that first:

SelectedAuthorEvent.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 updated the AuthorModel to keep track of the selected Author and notify the application when the it has changed:

AuthorModel.as

1
2
3
4
5
6
7
8
9
10
11
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

1
2
3
4
5
6
7
8
9
10
11
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 updated 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 lets get it mediated:

QuoteTextAreaMediator.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.

view-to-model-to-view.png

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

1
2
3
4
5
6
7
8
9
override public function startup():void
{
    injector.mapSingleton(AuthorModel);
 
    mediatorMap.mapView(ListView, ListViewMediator);
    mediatorMap.mapView(QuoteTextArea, QuoteTextAreaMediator);
 
    mediatorMap.mapView(SimpleListExample, ApplicationMediator);
}

With that you 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 an 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 in junks all across your application it becomes a murky stew making it difficult to quickly isolate trouble spots or add functionality to the application.

A few pages in a blog post is a very brief introduction to models. I highly recommend diving into some research on the M in MVC. In the next example will take a look at Services, and see how they 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 this 25 minute screencast. 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. Additionally I participated in writing the forthcoming Flex 4 in Action book, which has 22 pages of Robotlegs material.

Want to make more money and love what you do? If you answered "Yes!" then you will enjoy my Consultancy Masterclass Sketchnotes.

Brennan charges $1,799 for his masterclass and it is worth every penny. If you are on the fence about the class, or would like to look at the "essence" of the class in 10 pages of lovingly illustrated sketchnotes, click below and they are yours for $9.99 suggested minimum price. It is a steal when you consider how these concepts can boost your revenues as a freelancer or consultant!

"Awesome! I love these." - Brennan Dunn

Get the DRM Free PDF Now!

100% satisfaction gurantee. If you aren't happy with these notes, for any reason, just let me know and I will refund your money!

  • crusy

    Hi, why (and how??) do you inject the views into their mediators? This does not work here. But: There is a variable viewComponent in MediatorBase which seems to be the actual view…

  • http://joelhooks.com Joel Hooks

    Mediators serve as a view controller and access the API of the view. There is some more detail here. By default the view is injected by type. Above, the ApplicationMediator is injected with the following :

      [Inject]  public var view:SimpleListExample;

    As you noticed the viewComponent property is also a reference to your view. You'd just need to cast it to the appropriate type to make use of it. Injecting the view by type is just a convenience.

    What exactly doesn't work?

  • crusy

    Well, it does not get injected :-) “view” is null in onRegister while viewComponent isn't. So I use

    private function get _view():SimpleListExample
    {
    return viewComponent as SimpleListExample;
    }

    for convenience

  • crusy

    OK, I have it: The compiler removes the “[Inject]“s. One has to specify the compiler option

    -keep-as3-metadata=Inject,PostConstruct

    to make your example work :-)

  • http://joelhooks.com Joel Hooks

    Ah ya, good catch, I will add it to the text.

  • http://joelhooks.com Joel Hooks

    Are you using Robotlegs from source or a SWC? With a SWC this shouldn't be needed…

  • http://twitter.com/ruprict Ruprict

    So, in this example you don't use data binding at all. In fact, the view really doesn't know anything about the model (which, I am guessing, is intentional) In other frameworks (CG3), the view knows about it's Presentation Model (which is injected) and binds to attributes on that model (or something like that, doing this from memory) Since Adobe will bore one to tears extolling the virtues of its Data Binding capabilities, what is the RL take on data binding? Does this question even make sense?

  • http://joelhooks.com Joel Hooks

    This particular example is AS3 and doesn't have access to Flex's data binding solution. I personally bind to values from my models in views when I am using Flex. I lean towards using presentation models in these cases, but not the framework managed type you'd use with Parsley or Swiz, but rather PMs to wrap value objects. Prior to that I'd just add presentation values to my VOs, but that makes these dirty VOs that don't match the data from the server. Does this answer even make sense? ;)

  • http://twitter.com/ruprict Ruprict

    Fair point, I forgot this wasn't flex (duh) Maybe an example in an upcoming posts (these are great, btw)

  • http://joelhooks.com Joel Hooks

    https://github.com/joelhooks/r…

    The FlickrImageGallery example is Flex and I use data binding. It is still one of my favorites.

    https://github.com/joelhooks/r…

    The address book is probably my favorite. I built it for a workshop and cover it in Flex 4 in Action Chapter 19. The F4IA version differs a bit, but it is the same conceptually. The book contents are a bit like this series, but have had the benefit of editing by the publisher :>

  • Tim

    Thanks for these series! Don't stop ;)

  • Worksk

    very cool, thank you…