A Robotlegs Flash Site Tutorial

Robotlegs is a light-weight, pure AS3 micro-architecture that has developed some intense devotion amongst the Flash community recently. Created by Shaun Smith, it provides a mechanism for wiring your objects together in a decoupled way, and through the use of automated metadata based dependency injection, Robotlegs removes boilerplate code in an application.

If you’re familiar with PureMVC, you’ll find lots of similarities in Robotlegs and then some – cool features such as Dependency Injection. In this tutorial, I shall demonstrate how to build a typical Flash site with Robotlegs, similar to the one in my PureMVC Flash Site tutorial. The application begins by loading an xml document, parsing that data, and finally displaying a Flash site based on the dynamic data.


View Demo

Download Source Files

Using Robotlegs with Flash CS3 / CS4

Since Robotlegs relies on custom metadata for its dependency injection and Flash CS3/CS4 don’t support compiling custom metadata, Robotlegs is more suited for a workflow that doesn’t rely on the Flash IDE for compilation. However, the bundled SwiftSuspenders DI solution does provide a way to configure injection points using XML, allowing for Robotlegs usage in Flash CS3/CS4 compiled projects. Helmut Granda has written a detailed tutorial on how to configure the SwiftSuspenders XML_CONFIG to enable this.

Prerequisites

This tutorial shall assume that you are familiar with setting up an Actionscript only project in Flex/Flash Builder, FDT or Flash Develop. You must also be comfortable with compiling your AS3 project using the MXMLC compiler. If you must use the Flash IDE, please follow the suggested links above to set up your environment to support the dependency injection solution.

1. Examine the asset.fla

In this example, the FLA is purely used to generate a SWC containing the font assets which will be imported into the application.

2. The RobotlegsFlashSite document class

The document class registers the Georgia and Helvetica fonts by creating the respective font instances. It also creates an instance of the ApplicationContext class.

package
{
  import Georgia;
  import Helvetica;

  import flash.display.*;
  import flash.text.Font;

  import com.hubflanger.robotlegsdemo.ApplicationContext;

  public class RobotlegsFlashSite extends Sprite
  {
    protected var context:ApplicationContext;

    public function RobotlegsFlashSite()
    {
      stage.scaleMode = StageScaleMode.NO_SCALE;

      var georgia:Font = new Georgia();
      var helvetica:Font = new Helvetica();

      context = new ApplicationContext(this);
    }
  }
}

3. The ApplicationContext class

The Context lies in the heart of a Robotlegs implementation, providing the mechanism by which the implementation’s tiers will communicate. The Context provides three functions within an application: initialization, de-initialization, and creates the central event bus for communication. Although an application is not limited to a single Context, for many use cases one Context is sufficient.

The ApplicationContext class extends the base Context class in the Robotlegs framework. Its constructor accepts an instance of the RobotlegsFlashSite document class which is assigned to the variable contextView. Later on, we shall see how contextView is accessed throughout the application.

In a Robotlegs implementation, we typically override the startup() method to set up all the mappings for dependency injection, views and events. Here, we map a Singleton of SiteModel and an instance of SiteDataService which implements the ISiteDataService interface to the Injector. This makes them available to other classes in the application via the [Inject] metadata.

We then map 3 view classes (Header, Navigation and SectionContainer) to their respective Mediators by calling the mapView() method of the MediatorMap instance. In Robotlegs, a Mediator performs similar functions as ones in PureMVC. A Mediator provides a bridge between the framework and the View Component it’s responsible for. It passes along framework events to its view component and sends framework events in response to events dispatched by its view component. By putting application-specific logic in the Mediator, we de-couple the view component from the application’s implementation.

Finally, we call the mapEvent() method of the CommandMap instance to map 3 events to their respective Commands. The ContextEvent.STARTUP_COMPLETE event is dispatched by the framework upon completing execution of the Context’s startup() method. Here, we map the LoadDataCommand class to handle this event.

package com.hubflanger.robotlegsdemo
{
  import com.hubflanger.robotlegsdemo.model.SiteModel;
  import com.hubflanger.robotlegsdemo.controller.*;
  import com.hubflanger.robotlegsdemo.service.*;
  import com.hubflanger.robotlegsdemo.events.*;
  import com.hubflanger.robotlegsdemo.view.*;
  import com.hubflanger.robotlegsdemo.view.components.*;

  import flash.display.DisplayObjectContainer;

  import org.robotlegs.mvcs.Context;
  import org.robotlegs.base.ContextEvent;

  public class ApplicationContext extends Context
  {
    public function ApplicationContext(contextView:DisplayObjectContainer)
    {
      super(contextView);
    }

    override public function startup():void
    {
      injector.mapSingleton(SiteModel);

      injector.mapClass(ISiteDataService, SiteDataService);

      mediatorMap.mapView(Header, HeaderMediator);
      mediatorMap.mapView(Navigation, NavigationMediator);
      mediatorMap.mapView(SectionContainer, SectionContainerMediator);

      commandMap.mapEvent(ContextEvent.STARTUP_COMPLETE, LoadDataCommand, ContextEvent, true);
      commandMap.mapEvent(SystemEvent.INIT_VIEW, InitViewCommand, SystemEvent, true);
      commandMap.mapEvent(UserEvent.NAV_CLICK, SectionSelectedCommand, UserEvent, false);

      super.startup();
    }
  }
}

4. The LoadDataCommand class

As intended by the command mapping, the LoadDataCommand command responds to the Context’s STARTUP_COMPLETE event. It creates an instance of SiteDataService using the [Inject] metadata. In its execute() method, we call the loadData() method of the SiteDataService instance to initiate the loading of xml data.

package com.hubflanger.robotlegsdemo.controller
{
  import com.hubflanger.robotlegsdemo.service.ISiteDataService;

  import org.robotlegs.mvcs.Command;

  public class LoadDataCommand extends Command
  {
    [Inject]
    public var siteDataService:ISiteDataService;

    override public function execute():void
    {
      siteDataService.loadData();
    }
  }
}

5. The SiteDataService class

In Robotlegs, Model and Service classes extend the base Actor class to inherit functionality for communicating with the framework. A Model encapsulates and provides an API for data and sends event notifications when the data model has been changed. A Service communicates with the outside world through web services or file access and dispatches system events in response to external events.

In the SiteDataService class, we access the SiteModel singleton via the [Inject] metadata and assign it to the model variable. The loadData() method creates a URLLoader instance and loads the xml.

The loadCompleteHandler event handler parses the xml data, creates SectionVO objects for each section node and stores them in the SiteModel singleton. It then dispatches a SystemEvent.INIT_VIEW event to the framework, instructing the application to start its view construction.

package com.hubflanger.robotlegsdemo.service
{
  import com.hubflanger.robotlegsdemo.model.vo.SectionVO;
  import com.hubflanger.robotlegsdemo.model.SiteModel;
  import com.hubflanger.robotlegsdemo.events.SystemEvent;

  import flash.events.Event;
  import flash.net.*;

  import org.robotlegs.mvcs.Actor;

  public class SiteDataService extends Actor implements ISiteDataService
  {
    [Inject]
    public var model:SiteModel;

    public function SiteDataService()
    {
      //
    }

    public function loadData():void
    {
      var loader:URLLoader = new URLLoader();
      loader.addEventListener(Event.COMPLETE, loadCompleteHandler);
      loader.load(new URLRequest("assets/data.xml"));
    }

    private function loadCompleteHandler(event:Event):void
    {
      var loader:URLLoader = event.target as URLLoader;
      loader.removeEventListener(Event.COMPLETE, loadCompleteHandler);

      var xml:XML = new XML(event.target.data);
      model.header = xml.header.text();

      var sections:XMLList = xml.sections.section;

      for each (var section:XML in sections) {
        var sectionVO:SectionVO = new SectionVO(section.@id,
                            section.label.toString(),
                            section.content.toString());

        model.sectionsList.push(sectionVO);
        model.sectionsHash[sectionVO.id] = sectionVO;
      }

      model.defaultSection = model.sectionsList[0].id;

      dispatch(new SystemEvent(SystemEvent.INIT_VIEW, false));
    }
  }
}

6. The InitViewCommand class

The InitViewCommand command responds to the SystemEvent.INIT_VIEW event by instantiating 3 view components and adding them as children to the main display container. In Robotlegs, the mappings in the MediatorMap as shown in ApplicationContext allows a Mediator to be automatically created when its view component is instantiated.

package com.hubflanger.robotlegsdemo.controller
{
  import com.hubflanger.robotlegsdemo.model.SiteModel;
  import com.hubflanger.robotlegsdemo.view.components.*;

  import org.robotlegs.mvcs.Command;

  public class InitViewCommand extends Command
  {
    [Inject]
    public var model:SiteModel;

    override public function execute():void
    {
      contextView.addChild(new SectionContainer());
      contextView.addChild(new Header());
      contextView.addChild(new Navigation());
    }
  }
}

6. The NavigationMediator class

We won’t be getting into in-depth discussions of the view classes except to say they’re pretty generic display containers extending the Sprite class. Instead, we shall take a look at the NavigationMediator and see how it handles Mouse Click events dispatched by the NavButton instances in the Navigation view component.

Typically, one takes advantage of the onRegister() handler which is invoked when the Mediator has been registered with the framework, to perform set up tasks on the view component and map events with their event handlers. Here, we invoke the view component’s init() method and pass the SiteModel‘s sectionsList array so that the Navigation view component will dynamically create a list of NavButton instances. Then we invoke the mapListener() method of the EventMap instance to map a SystemEvent.SECTION_CHANGED framework event and a NavButton.CLICK user event with their respective event handlers. Finally, it calls the dispatch() method sending a UserEvent.NAV_CLICK custom event with the default section’s ID to the framework.

The navClickHandler() responds to NavButton.CLICK events bubbled from the NavButton instances. It in turn dispatches a UserEvent.NAV_CLICK event to the framework. This is how user interactions with display objects are commonly handled in Robotlegs.

As you can see, NavigationMediator extends the base Mediator class of the Robotlegs framework and inherits from it a bunch of useful features. In addition to dependency injection, the EventMap instance is a built-in property and the dispatch() method allows the Mediator to communicate events to the framework.

package com.hubflanger.robotlegsdemo.view
{
  import com.hubflanger.robotlegsdemo.model.SiteModel;
  import com.hubflanger.robotlegsdemo.view.components.Navigation;
  import com.hubflanger.robotlegsdemo.view.components.NavButton;
  import com.hubflanger.robotlegsdemo.events.*;

  import flash.events.*;

  import org.robotlegs.mvcs.Mediator;

  public class NavigationMediator extends Mediator
  {
    [Inject]
    public var model:SiteModel;

    [Inject]
    public var nav:Navigation;

    public function NavigationMediator()
    {
      //
    }

    override public function onRegister():void
    {
      nav.init(model.sectionsList);

      eventMap.mapListener(eventDispatcher, SystemEvent.SECTION_CHANGED, sectionChangeHandler);
      eventMap.mapListener(nav, NavButton.CLICK, navClickHandler);

      dispatch(new UserEvent(UserEvent.NAV_CLICK, model.defaultSection));
    }

    private function navClickHandler(event:Event):void
    {
      event.stopPropagation();
      var btn:NavButton = event.target as NavButton;

      if (!btn.isSelected)
        dispatch(new UserEvent(UserEvent.NAV_CLICK, btn.id));
    }

    private function sectionChangeHandler(event:SystemEvent):void
    {
      nav.update(model.currentSection);
    }
  }
}

7. The SectionSelectedCommand class

Earlier in ApplicationContext, we see that the SectionSelectedCommand is mapped to the UserEvent.NAV_CLICK event. It assigns the event’s id‘s property to the currentSection property of the SiteModel instance.

package com.hubflanger.robotlegsdemo.controller
{
  import com.hubflanger.robotlegsdemo.model.SiteModel;
  import com.hubflanger.robotlegsdemo.events.UserEvent;

  import org.robotlegs.mvcs.Command;

  public class SectionSelectedCommand extends Command
  {
    [Inject]
    public var model:SiteModel;

    [Inject]
    public var event:UserEvent;

    override public function execute():void
    {
      model.currentSection = event.id;
    }
  }
}

8. SiteModel’s currentSection setter

In the currentSection setter, upon updating the value of the _currentSection variable, the Model dispatches a SystemEvent.SECTION_CHANGED framework event. This event announces to the application that the Model has been changed and the views will then respond accordingly by updating their display states.

public function set currentSection(value:String):void
{
  _currentSection = value;
  dispatch(new SystemEvent(SystemEvent.SECTION_CHANGED, false));
}

8. NavigationMediator’s sectionChangeHandler

The NavigationMediator‘s sectionChangeHandler() responds to the SystemEvent.SECTION_CHANGED event by invoking the view component’s update() method passing along the Model’s currentSection property. This causes the NavButton instances to update their display states to reflect the section selected.

9. The SectionContainerMediator class

The SectionContainerMediator is set up in a fashion similar to NavigationMediator. Here, we override the onRegister() handler to invoke the init() method of the view component passing the sectionsHash property of the SiteModel instance. It also maps sectionChangeHandler() to handle the SystemEvent.SECTION_CHANGED framework event.

As in NavigationMediator, the sectionChangeHandler() invokes the update() method of the view container passing the currentSection property of the SiteModel instance. This causes the SectionContainer view component to update the display of its content according to the section selected.

package com.hubflanger.robotlegsdemo.view
{
  import com.hubflanger.robotlegsdemo.model.SiteModel;
  import com.hubflanger.robotlegsdemo.view.components.SectionContainer;
  import com.hubflanger.robotlegsdemo.events.SystemEvent;

  import org.robotlegs.mvcs.Mediator;

  public class SectionContainerMediator extends Mediator
  {
    [Inject]
    public var model:SiteModel;

    [Inject]
    public var container:SectionContainer;

    public function SectionContainerMediator()
    {
      //
    }

    override public function onRegister():void
    {
      container.init(model.sectionsHash);
      eventMap.mapListener(eventDispatcher, SystemEvent.SECTION_CHANGED, sectionChangeHandler);
    }

    private function sectionChangeHandler(event:SystemEvent):void
    {
      container.update(model.currentSection);
    }
  }
}

Conclusion

There you have it – a simple Robotlegs implementation in a nutshell. In addition to the features used in this demo, there are many more available to you which I have not mentioned. Please consult the Robotlegs documentation for more details.

As someone who has worked with PureMVC extensively, I find Robotlegs a joy to use. Its dependency injection solution is elegant and removes a lot of the clunkiness that one often has to deal with in PureMVC, such as casting your datatypes:

var proxy:MyProxy = facade.retrieveProxy( MyProxy.NAME ) as MyProxy;

Also, unlike PureMVC, all events are Flash Events and there isn’t the confusion of when one must use a Notification or an Event. Although in Robotlegs, one must still distinguish between framework events and user events dispatched by the view components, it’s much easier to grasp conceptually, in my opinion. That said, I highly recommend that you give Robotlegs a try.

Share/Save/Bookmark

8 Responses to “A Robotlegs Flash Site Tutorial”

  1. tjn Says:

    Thank you very much for this comprehensive tutorial!

  2. What Makes a Good PhotoShop Tutorial? Says:

    [...] A Robotlegs Flash Site Tutorial | hubflanger [...]

  3. Johnny Says:

    Nice stuff, but at the end the website’s code remains so complicated for so little results… Anyone who is not into flash would say we are crazy to do just a “typical flash website”. It seems obfuscated by the envelope. Don’t take it personal, it’s just that I had a 3 months trip and remembered how easy it is to wire an application in Code Igniter, and how painful it can be in flash.

    Why not separate your website system from the actual website, as in a “Code Igniter” fashion? I know it would be looking more like a framework than a nutshell. What about the menus, sitemap, swfadress support, page title, modularity of each section, flexible models that support both XML and AMF or even remote models. What about indexing the content and SEO? This is just a preview of what a proper -flash website- need just to start where all the PHP frameworks are beginning; and this is what a flash developer needs to NOT code since it is supposed to be the base of a -website.- This also where this kind of “typical website” should not be in flash, unless you have proper multimedia and design prerequisites.

    I think RobotlLegs is nice in theory, but in practice we are still in need for a proper tool that would humanize the process of small application wiring and configuration. Coding should be focused on features and usability, it seems now, with all that MVC frenzy, we are wasting so much time and energy on the envelope, forgetting the heart of the content and usability…

    Flex was a good idea, but it seems to work great for large teams and not-so-sexy applications. It seems a lot of work to get a Flex webapp, to an appealing, immersive level. I think this is what sparked all the “roots down” flex-compatible frameworks. Yep I know: FlashCatalyst, but it still focused with Adobe’s narrow vision…

    PS: Don’t forget to remove the Spam comments!

  4. eco_bach Says:

    Johnny,
    Not sure I follow your logic and argument(s) completely. Are you against use of all frameworks, or just Robotlegs in particular? I would argue that one of Robotlegs strengths is the speed and simplicity of small application wiring. I’ve used PureMVC successfully on many commercial projects the past year, that made use of swfaddress, amfphp, and where both SEO and usability were determining factors in every project implementation decision. And I’m looking forward to using Robotlegs in my next project.

  5. Glidias Says:

    Johnny is right when it comes to seperation.

    I find that most people would use Robotlegs solely to handle the application-side of things and glue the stuff together with other parts of your Flash site with other services (whether within Flash or outside it). For one thing, a Robotlegs Command shouldn’t really prescribe what kind of view Sprite classes should exist (eg. a CreateViewCommand with new Header(), new Footer(), etc. is a waste of time and obviously contrived). That should be handled elsewhere through teh Flash IDE, some markup langauge, or some other deployment solution such as some other framework/engine that helps deploy those content for you, and Robotlegs merely mediates that. When it comes to full Flash site deployment of pages, SEO, swfAddress, transition management between pages, assets, etc. I’d normally use something like Gaia FLash framework and possibly my own Flash markup language rendering platform and other Gaia extensions (though if you already have a comparable PureMVC/RObotlegs site template that does that for you, it’s no harm using that as well) . As for content management, ModX for online content management (if required) is what I’ve also considered to glue online SEO, Gaia, and the application side end of a Flash website with RobotLegs, .In RL, i tr y my best to keep my view implementation/deployment and application layer completely seperate from each other.

    The thing is, with RobotLegs, it’s possible to create useful services that do more than just the above contrived example. Though it’s possible to create a website framework on top of Robotlegs. it’s no point reinventing the wheel from scratch when there are already better solutions out there. Most people would use Robotlegs to act as the glue for any applications/widgets within a site (whether it’s a hybrid or full flash site).

  6. Matan Uberstein Says:

    Hey All, I’ve built a website framework with Robotlegs that focuses on a modular layout of code and plenty of features that help speed of production as well as quality. It’s called HandBones and you can find it on Github – http://github.com/Matan/HandBones – HandBones is configured via a external xml file where you configure how your site works, the goal is that you never “hard code” any navigational information into your swfs. Also Google tracking is configure here and many other things.

    One of the main feature is that your site is broken into multiple swfs, namely your “shell” (which is your main swf) and your pages. Your pages each start their own tier of MVCS, which make it really easy to code. Essentially each page is it own little application, but still form part of your “primary application” (shell). Key thing here is that your pages will not cross compile into each other nor into the shell. So you can freely use the Flash IDE to create your assets and compile them directly into swf.
    HandBones also comes bundles with AssetLoader – http://github.com/Matan/AssetLoader – which handles all loading operation within your site, so in most cases the site is cached before the user even moved off the home page.

    There are more features and useful things, but I’ll stop there. Check out the wiki pages. :D

  7. derek knox Says:

    @hubflinger

    Great post. I’m just now delving into various texts, videos, and tutorials explaining RobotLegs use. This has been a great help. Thanks alot!

    @Glidias

    I think the main purpose of this post was to showcase how to wire RobotLegs. Not to educate people how to develop a (very basic) website using Flash/AS3. I think using a site as an example makes it easier to picture and follow the flow of the code. It also helps to envision what the wiring of RobotLegs is actually achieving.

    @Matan Uberstein

    Sounds like your project could possibly grow some legs, with a quick overview it seems very much like Gaia. Theres obviously always room for improvement though.

  8. Philip Says:

    Thanks hubflanger, useful and informative post. I’ve been using PureMVC and your comparisons have been helpful.

Leave a Reply