An Introduction to Robotlegs AS3 Part 1: Context and Mediators

We are presented with lots of choices these days when it comes to frameworks for Actionscript 3 development. This is a good sign. The open source community is alive and vibrant, and tools that make development easier are a good thing. Over the course of the last year Robotlegs AS3 has seen rapid growth in adoption. It is being leveraged by major media corporations, independent game developers, startups, and enterprises of all sizes.

robotlegs.jpg

This is the first in a series of Robotlegs articles that will appear here on InsideRIA. In the coming weeks, more articles in the series will detail core Robotlegs concepts and work through some more advanced concepts involving third party utilities and libraries built to interact with them.

This article uses Flex. Don’t worry straight AS3 developers, the second part uses an example that is pure AS3.

What is Robotlegs?

Simply put, Robotlegs is a mechanism for wiring your objects together. ObjectA needs to talk to ObjectB. ObjectA doesn’t want, or need, to know that ObjectB even exists. How can they communicate? The simple answer is through Events. With Flash, we have a native event system that is used to facilitate this sort of communication. Likely you use Events on a daily basis. Objects on the display list communicate via events, and event bubbling allows distant objects to receive messages from other display objects. What about objects that aren’t on the display list? This is where a framework such as Robotlegs can really make life easier for you.

At its core, Robotlegs is a set of modular utilities and interfaces that provide tools to ease these communication tasks, reduce repetitive coding tasks, and manage dependency injection within your application. In addition to this core set of tools, Robotlegs provides a lightly prescriptive MVC+S (Model, View, Controller, and Service) implementation to get you started. If you have any experience with PureMVC you will quickly recognize the use of Mediators and Commands within the Robotlegs MVC+S implementation. If not, don’t worry, we will be looking at these classes in more depth soon.

This article is going to give an overview of Robotlegs through a simple “Hello World” example. You will probably look at the example and say, “Eh? I could have done this in a single MXML file without all the hassle!” While probably true with a trivial example, keep in mind that on a large project this structure quickly becomes invaluable. That is the benefit of development with a framework in general. It allows us to effectively communicate concepts and understand a code base much quicker than a slapped together application with no patterns and practices.

This is not going an exhaustive dissection of Robotlegs, but hopefully it is enough to spark your interest. I’ve posted resources at the end of the article for further investigation. That said, let’s look at some code!

Basic Structure of a Robotlegs MVC+S Application

A typical Robotlegs MVC+S application is composed of several parts:

  • Context – the Context is the bootstrapping mechanism that initializes dependency injection and the various core utilities that Robotlegs uses.
  • Mediators – Mediators manage communication between your application’s view components and other objects within your application.
  • Commands – Commands represent individual actions that your application can perform. These are typically in response to user activity, but are not limited to that use case.
  • Models – Models store data and represent the current state of your application.
  • Services – Services are your application’s gateway to the outside world.

Let’s take a look at the Context and Mediators in more depth, starting with the Context.

The Context is at the heart of your application. It provides the central event bus that your other application objects will utilize to communicate with one another. Beyond the initial loading and bootstrapping of your application, you won’t deal with the Context during regular development. It comes to life, does its job, and then quietly gets out of your way while you develop your heart out. The Context is not a Singleton. Your application can have any number of Contexts, which makes Robotlegs well suited for modular applications. We aren’t going to explore modular applications here, but it will be the subject of a future article as it is an extremely powerful tool. To start off, let’s look at the most basic Context.

HelloWorldContext.as

1
2
3
4
5
6
7
8
9
import org.robotlegs.mvcs.Context
 
public class HelloWorldContext extends Context
{
    override public function startup():void
    {
        //bootstrap here
    }
}

Inside of your Context you override the startup method. The startup() method is called when the Context has been fully initialized. Behind the scenes, prior to calling startup(), the Context is creating instances of all the core Robotlegs utilities, preparing to receive dependency injection mappings, and creating the event dispatcher that will be used to communicate amongst your application’s objects.

Once your Context class has been created, your application needs a reference to it. In a Flex 4 Spark Application, this is generally done in the main MXML file that extends Application as seen below.

HelloWorld.mxml

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0"?>
<s:Application
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx" 
        xmlns:local="*">
    <fx:Declarations>
        <local:HelloWorldContext contextView="{this}"/>
    </fx:Declarations>
</s:Application>

Because the Context is a non-visual class, it must be placed in the Declarations tag. You should also note the contextView property bound to the application itself. The context view is the root view of the Context, and is used to provide automated assistance with view component mediation. We will look at that shortly when we discuss the relationships between views and mediators.

That is it for the Context. As mentioned, it has a very brief, but critical, role in the lifecycle of your application. With your Context ready, we can now add a few view components and get them speaking to one another via Robotlegs. Let’s take a look at Mediators and their relationship to your application’s view components now.

Mediators sit between your view components and the rest of the application. Put simply, Mediators listen for events. Your view components dispatch events when the user interacts with them or they are updated in some other way. These events need to be captured and delivered to the rest of the application. Perhaps the user has clicked a save button and some information needs to get sent to a server. The mediator listens for this event to occur, and when it does, the mediator gathers the appropriate information and sends an event that the rest of the application can utilize to perform some unit of work on the data. Likewise, a Mediator also listens to events from the rest of the application. If some data has been received from the server, parsed, and an event dispatched from a service class, the Mediator is the place where you will listen for that event and update its view component with the new data. Here is a view that will receive a Mediator.

MessageView.mxml

1
2
3
4
5
6
<?xml version="1.0"?>
<s:TextArea
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx">
</s:TextArea>

This class is short. It is nothing more than a simple extension of a TextArea. Why not just use a TextArea? Dependency injection works better with unambiguous classes. What this means is that by extending TextArea into our new MessageView class, we are creating a specific view component for the dependency injection to act upon. This is important if our application were to have several TextAreas that served different purposes. By dividing up our classes in this way, we are clearly defining the intent of the class and allowing for the dependency injection tools to do their jobs effectively. With the MessageView, we might also add some additional functionality in the future. For this example, it will remain a simple TextArea, but you get the idea. Now we’ll look at the mediator for the MessageView component.

MessageViewMediator.as

1
2
3
4
5
6
7
8
9
10
11
12
import org.robotlegs.mvcs.Mediator;
 
public class MessageViewMediator extends Mediator
{
    [Inject]
    public var view:MessageView;
 
    override public function onRegister():void
    {
         trace("I am registered!");
    }
}

The MessageViewMediator has two interesting features at this point. Right away, you will notice the first use of the [Inject] metadata tag. This tag is used by Robotlegs to identify properties and methods that it needs to perform injection on. With a mediator, the mediated view is always available for injection when the mediator is created. You don’t need to make any special consideration for mapping the view for injection. That is taken care of for you when you map the view to its mediator. We will look at that in a moment, but first let’s take a look at the second interesting feature of the basic mediator which is the onRegister() method.

The onRegister() method is the hook provided to you when the mediator has been fully initialized. The injections have occurred, the view is ready, and it is where you typically add your event listeners for both the view component and the application. You will typically override this method in every Robotlegs mediator you create.

Now that you have a view component and a mediator, they need to be registered, or mapped, with the Context. This is achieved through the MediatorMap. As the name suggests, the MediatorMap is a utility for mapping mediators to view components within the Context. In addition the MediatorMap by default listens to the contextView for ADDED_TO_STAGE and REMOVED_FROM_STAGE events to automatically create and destroy mediators as their corresponding view components are added and removed from the display list. It is worth noting that in graphics intensive applications with many display objects (1000s), this automatic mediation can be a performance issue. In a typical application it is very convenient and rarely causes performance issues. Here is how we map the MessageView to its MessageViewMediator within the HelloWorldContext.

1
2
3
4
override public function startup():void
{
    mediatorMap.mapView(MessageView, MessageViewMediator);
}

With that done, all that is left is to add the MessageView to the HelloWorld.mxml.

HelloWorld.mxml

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0"?>
<s:Application
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        xmlns:local="*">
    <fx:Declarations>
        <local:HelloWorldContext contextView="{this}"/>
    </fx:Declarations>
    <local:MessageView top="40" width="100%" height="100%"/>
</s:Application>

Now, when you run your application in the debugger, you will see “I am registered!” printed to the console. Congratulations, you have a fully functional Robotlegs application. Granted, that functionality is limited to almost nothing right now, but we can change that. To give our application something to do besides just bootstrap, we will add a button called HelloButton that simply extends the Spark Button class, as well as a mediator for HelloButton called… HelloButtonMediator.

HelloButton.mxml

1
2
3
4
5
6
<?xml version="1.0"?>
<s:Button
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx">
</s:Button>

HelloButtonMediator.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import flash.events.MouseEvent;
 
import org.robotlegs.mvcs.Mediator;
 
public class HelloButtonMediator extends Mediator
{
    [Inject]
    public var view:HelloButton;
 
    override public function onRegister():void
    {
 
    }
 
}

Right now the HelloButtonMediator looks exactly like the MessageViewMediator above, except the classes are different. Your Context startup method will look like this once you have added the mapping for the HelloButton and its mediator.

1
2
3
4
5
override public function startup():void
{
    mediatorMap.mapView(MessageView, MessageViewMediator);
    mediatorMap.mapView(HelloButton, HelloButtonMediator);
}

You will also add the button to HelloWorld.mxml so that it will be added to the display list. You will probably want to add something wittier to the HelloButton’s label property, but I will leave that up to you.

HelloWorld.mxml

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0"?>
<s:Application
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark"
        xmlns:mx="library://ns.adobe.com/flex/mx"
        xmlns:local="*">
    <fx:Declarations>
        <local:HelloWorldContext contextView="{this}"/>
    </fx:Declarations>
    <local:HelloButton label="Say Hello"/>
    <local:MessageView top="40" width="100%" height="100%"/>
</s:Application>

At this point we have two fully mediated view components that are just dying to talk. I’m not one to deny my objects their desires, so lets do just that; starting with the custom event they will use to communicate.

HelloWorldMessageEvent.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class HelloWorldMessageEvent extends Event
{
    static const MESSAGE_DISPATCHED:String = "messageDispatched";
 
    private var _message:String;
    public function get message():String
    {
        return _message;
    }
 
    public function HelloWorldMessageEvent(type:String, message:String, bubbles:Boolean = false, cancelable:Boolean = false)
    {
        super(type, bubbles, cancelable);
        _message = message;
    }
 
    override public function clone():Event
    {
        return new HelloWorldMessageEvent(type, message, bubbles, cancelable)
    }
}

This is a simple custom event. Be sure to override the clone() method in your custom events. Events cannot be redispatched, relayed, or bubbled without this method. I make it a habit to always override clone() in all my custom events. After being burned with a couple hours of head scratching debugging, you will too.

What we want to do is update the MessageView when the user clicks the HelloButton. The HelloButtonMediator needs to listen for the MouseEvent.CLICK on the HelloButton and then dispatch the HelloWorldMessageEvent to the application. It doesn’t know who will respond to this event. It doesn’t care who responds to the event. The HelloButtonMediator has done its job.

HelloButtonMediator.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HelloButtonMediator extends Mediator
{
    [Inject]
    public var view:HelloButton;
 
    override public function onRegister():void
    {
        addViewListener(MouseEvent.CLICK, handleMouseClick)
    }
 
    private function handleMouseClick(event:MouseEvent):void
    {
        dispatch(new HelloWorldMessageEvent(HelloWorldMessageEvent.MESSAGE_DISPATCHED, "Hello World"));
    }
}

With the view listener added to the HelloButtonMediator, we are now dispatching an event to the application. The next step is to do something with that event. The MessageViewMediator seems like the logical choice.

MessageViewMediator.as

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MessageViewMediator extends Mediator
{
    [Inject]
    public var view:MessageView;
 
    override public function onRegister():void
    {
        addContextListener(HelloWorldMessageEvent.MESSAGE_DISPATCHED, handleMessage)
    }
 
    private function handleMessage(event:HelloWorldMessageEvent):void
    {
        view.text = event.message;
    }
}

…and with that, we have a Hello World! Seems like the long way to get there, but it will pay off in your non-trivial applications. In the next part in this series we will explore models and commands followed by an article on services. In addition to the core of Robotlegs, we will look in detail at some of the utilities that are currently available to make use of tools such as AS3-Signals and modular application development. It is an exciting time to be developing with Robotlegs and I look forward to sharing some of my enthusiasm here on InsideRIA over the coming weeks.

If you can’t wait, there are articles on my blog (and lots of others across the internets) dealing with various Robotlegs topics. John Lindquist has a Hello World screencast on his blog (watching him use FDT is interesting in and of itself). 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.

Part 2: Models can be found here.

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!

  • Thomas Burleson

    @Joel,

    I think an additional feature to simplify MessageViewMediator.as would be the use of another metadata tag (similar to that found in Swiz):

    [Inject]
    public var view : MessageView;

    [EventHandler(event="HelloWorldMessageEvent.MESSAGE_DISPATCHED",properties="message")]
    public function handleMessage(message:String):void {
    if (view != null) view.text = message;
    }

  • http://joelhooks.com Joel Hooks

    I appreciate why people enjoy the convenience of metadata, but I must say I am not a fan of it. That may seem to contradict my love of Robotlegs, but if you look at examples I've written you will quickly notice that all that is ever used is [Inject] with no properties.

    Here are some thoughts on metadata that I've posted previously.

  • Tim

    I'm new to this framework and i'm trying this one out. To bad i'm getting an error that my view is null:

    TypeError: Error #1009: Cannot access a property or method of a null object reference.
    at org.example.helloworld.mediator::MessageViewMediator/handleMessage()[C:UsersTimAdobe Flash Builder Burrito PreviewHelloRobotLegssrcorgexamplehelloworldmediatorMessageViewMediator.as:20]
    at org.robotlegs.base::EventMap/routeEventToListener()[/Development/Projects/Robotlegs/robotlegs-framework/src/org/robotlegs/base/EventMap.as:181]
    at Function/<anonymous>()[/Development/Projects/Robotlegs/robotlegs-framework/src/org/robotlegs/base/EventMap.as:107]
    at flash.events::EventDispatcher/dispatchEventFunction()
    at flash.events::EventDispatcher/dispatchEvent()
    at org.robotlegs.mvcs::Mediator/dispatch()[/Development/Projects/Robotlegs/robotlegs-framework/src/org/robotlegs/mvcs/Mediator.as:89]
    at org.example.helloworld.mediator::HelloButtonMediator/handleMouseClick()[C:UsersTimAdobe Flash Builder Burrito PreviewHelloRobotLegssrcorgexamplehelloworldmediatorHelloButtonMediator.as:18]
    at org.robotlegs.base::EventMap/routeEventToListener()[/Development/Projects/Robotlegs/robotlegs-framework/src/org/robotlegs/base/EventMap.as:181]
    at Function/<anonymous>()[/Development/Projects/Robotlegs/robotlegs-framework/src/org/robotlegs/base/EventMap.as:107]

    How can I fix this? If I throw an alert I see that my view is null :/? I think I have missed something…

    p.s. I can run the examples from the robotlegs.org site.</anonymous></anonymous>

  • http://joelhooks.com Joel Hooks

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

    Can you check here and see if the injection issues might be solved via the suggestions? It might be that the metadata is being stripped on compile.

  • Tim

    Joel thanks for the FAQ >_<, it was a typo! Long working days and doing this on the side isn't the way to develop!

  • Ronak

    Its really encouaging effort form you man…Thanks. :)

  • Johnyflex

    Hi Joel,

    First, i would like to say thanks for this tutorial, it is very useful!

    I have a problem, with mediators, when i would like to mediatorMap for a view, which addChild to context at runtime.
    So, i have a DummyView and DummyMediator. I mediatorMap this in the context:
    mediatorMap.mapView(DummyView , DummyMediator;

    I add this view to a context at runtime:
    var myContext:SkinnableContainer = contextView as SkinnableContainer;

    var newTitleWindow:NewProblemPanel = PopUpManager.createPopUp(myContext, DummyView , true) as DummyView ;
    PopUpManager.centerPopUp(newTitleWindow);The problem:The konstructor and an override public function onRegister():void method not called in DummyMediator. What am i doing wrong?Thanks,Johny//Flex 4, 4.5 SDK

  • Johnyflex

    OK, i have fixed this problem with manually mediate:
    mediatorMap.createMediator( newTitleWindow );

    The new problem is that newTitleWindow  has a couple of view components with seperate mediator and the onRegister() method never call in theese mediators.

    I read about this issue with RobotLegs:
    http://knowledge.robotlegs.org…

    But i dont understand, what should i do with Flex System Manager.  Can you some help me?

    Thanks,
    Johny

  • amar shukla

    Hi Joel,

    Just started exploring your framework and got a very small confusion. In my simple app I am having a view containing a Login Form with two TextInputs and 1 button. Everything is fine but problem lies here.
                    [Inject] public var loginView:LoginView; override public function onRegister():void { loginView.mySubmitButton.addEventListener(MouseEvent.CLICK, doLogin); }
    How could I use addViewListener(MouseEvent.CLICK, doLogin) in this scenario ?
    & whats the difference if instead of using addViewListener I use addEventListener like I have used ? Please explain a bit so that I can have a better idea about the framework :)

    Thanks & Regards!
    Amar Shukla

  • http://joelhooks.com Joel Hooks

    you will want to use the eventMap – http://api.robotlegs.org/org/robotlegs/base/EventMap.html

    In this particular scenario however I would have the loginView dispatch an event such as LoginSubmitEvent.SUBMIT that you can listen for with addViewListener. My general rule with mediators is NOT to dig into the mediated component. Don’t try to listen for sub component events. Either mediate those components separately or have the mediated view handle their event and dispatch an event for the Mediator to respond to.

  • amar shukla

    Thanks for quick reply Joel :)

    I think having mediator for every single component is not a good idea so in such cases dispatching events from views would be better.

  • Johnyflashflex

    Joel, can you help me a bit, please?

    Thanks & Regards!
    JohnyFlex

  • http://joelhooks.com Joel Hooks

    Yoyou might look into the popup contents having its own context (modular)

  • http://joelhooks.com Joel Hooks

    Yoyou might look into the popup contents having its own context (modular)

  • Johnyflashflex

    Thanks!

    So, i should create a context for the newTitleWindow (newTitleWindowContext)?

  • http://joelhooks.com Joel Hooks

    I’d probably make something more specific to your content, but essentially yes.

    http://joelhooks.com/2010/05/02/modular-robotlegs/

    That will give you an overview of the general approach. In this case, I don’t know that you’d need the weight of the utility, but you’d need to get the new context a child of your main injector.

  • Johnyflashflex

    Ok, thanks a lot Joel!

  • Anna

    You always inject a view as a public var, why not private? Is there a difference?

  • http://joelhooks.com Joel Hooks

    Injections occur externally. The Injector has no access to private member variables. I know some folks that have done interesting byte code manipulations to make it possible, but with RL, that isn’t the case.

    Joel
    817.675.6031 m
    @jhooks (https://twitter.com/#!/jhooks)
    http://joelhooks.com

  • Anna

     I don’t know if I understand correctly, but this is what I want: if I want to inject the MessageView in my mediator like this: [Injector]private var myMessageView:MessageView; .

    I see it is possible like that, but are their some drawbacks??