Robotlegs, AS3-Signals and the SignalCommandMap Example

Robotlegs and AS3-Signals play really well together. Both apply solid object-oriented principles to accomplish their respective goals. Signals is extremely well suited for automated Dependency Injection. By combining Signals and Robotlegs you are able to eliminate the use of Flash Events in the framework layer of your application. Eliminating Events means eliminating the ambiguity that can accompany Events and their String registry based approach to the Observer pattern. Signals provides a strongly-typed object-oriented approach to this same pattern.

With the standard Robotlegs MVCS implementation you leverage the events provided by Actionscript 3 to communicate amongst the various actors of an application. From models and services dispatching notifications of their actions to triggering commands, events are a core piece of the implementation. To facilitate the use of Signals within MVCS it was necessary to create an extension to allow for Signals to be registered as Command triggers. This need spawned the SignalCommandMap utility.

The SignalCommandMap extends the normal MVCS context and creates a SignalContext. The SignalContext instantiates and provides access to the SignalCommandMap alongside the other maps that are standard to Robotlegs. The SignalCommandMap allows you to map Signal classes and instances to commands that will be executed when the Signal dispatch() method is called. The value objects that are passed in the dispatch are then injected into the command alongside any other mapped injections you have created.

Let’s take a look at a simple example that makes use of the SignalCommandMap and discuss some of the underlying code to see how it works:

Get Adobe Flash player

Source available on Github (zip)

The example is a menu that allows you to add food which is displayed in a list. The total cost of your selected items is displayed and you can remove items from your order. First let’s get started by taking a look at the context of the Application, which extends SignalContext:

SignalCafeContext.as Bootstraps the Application
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SignalCafeContext extends SignalContext
{
    override public function startup():void
    {
      injector.mapSingleton(FoodOrderModel);
      injector.mapSingleton(FoodOrderUpdated);
      injector.mapSingleton(FoodItemAddedToOrder);
 
      signalCommandMap.mapSignalClass(AddFoodItemToOrder, AddFoodItemToOrderCommand);
      signalCommandMap.mapSignalClass(FoodItemSelected, FoodItemSelectedCommand);
      signalCommandMap.mapSignalClass(RemoveAllOfSelectedItem, RemoveAllSelectedItemCommand);
      signalCommandMap.mapSignalClass(NoFoodItemSelected, NoFoodItemSelectedCommand);
 
      mediatorMap.mapView(FoodSelectionView, FoodSelectionViewMediator);
      mediatorMap.mapView(CurrentOrderView, CurrentOrderViewMediator);
      mediatorMap.mapView(FoodOrderSummaryView, FoodOrderSummaryViewMediator);
      mediatorMap.mapView(FoodItemRemovalView, FoodItemRemovalViewMediator);
    }
}

A SignalContext is structured exactly the same as a standard Robotlegs MVCS Context class. You override the startup() method and bootstrap your application. The major difference here is that you are mapping signals to commands instead of mapping events to commands. With the SignalCommandMap you are not restricted to just using Signals for triggering commands. You could mix events and Signals liberally as your needs or requirements dictated. As a warning, this could quickly became confusing and choosing one or the other might be a saner choice.

SignalCafeContext is mapping 4 Signals to 4 commands. The Signals are typed extensions of the base Signal class. This is necessary to provide Robotlegs (and more specifically in this case SwiftSuspenders) object types to differentiate for the purposes of injection. You could use named injections, but this would rapidly riddle your application with strings that would need to be laboriously checked for accuracy. Avoiding strings in favor of compiler-checked object types is always a good choice.

The strongly typed Signals are relatively simple. They contain a constructor and no additional methods or properties are added to the Signal. You can of course add properties and methods to the extended Signals,, but it wasn’t necessary for this example. The SignalCommandMap will accept any class that implements the ISignal interface. Let’s take a look at one of the commands triggered by a Signal mapped in the SignalCafeContext:

AddFoodItemToOrder.as triggers the AddFoodItemToOrderCommand
1
2
3
4
5
6
7
public class AddFoodItemToOrder extends Signal
{
	public function AddFoodItemToOrder()
	{
		super(FoodType);
	}
}

That is the whole of the extended Signal. As mentioned, it is not very complex at all. You will notice that super(FoodType) is being called. Signals take constructor arguments consisting of classes or interfaces that will be used as the value objects transmitted via the dispatch() method. In this case we are using FoodType as this value to ensure that our Signal will carry the payload that the mapped command is expecting.

Command signals are named a bit differently than standard reaction Signals. A Signal that is triggering a command is “requesting action” or specifying an action that needs to occur. Standard signals are typically past tense, or informative, describing an action that has occurred. By using this standard it is much easier to differentiate quickly what each Signal’s purpose is. This can help with overall clarity within your application.

The AddFoodItemToOrder signal is dispatched by the FoodSelectionViewMediator which is mediating the DropDownList of FoodTypes and the “Add Some Food” button:

FoodSelectionViewMediator.as mediates a view and dispatches a Signal when the user has clicked the “Add Some Food” button
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	public class FoodSelectionViewMediator extends Mediator
	{
		[Inject]
		public var view:FoodSelectionView;
 
		[Inject]
		public var addItem:AddFoodItemToOrder;
 
		override public function onRegister():void
		{
			view.itemTypeAdded.add(handleItemTypeAdded);
		}
 
		protected function handleItemTypeAdded(itemType:FoodType):void
		{
			addItem.dispatch(itemType);
		}
	}

When the AddFoodItemToOrder Signal’s dispatch(aFoodTypeInstance) is called the AddFoodItemToOrderCommand is triggered:

AddFoodItemToOrderCommand.as is executed in response to AddFoodItemToOrder Signal dispatch
1
2
3
4
5
6
7
8
9
10
11
12
13
public class AddFoodItemToOrderCommand extends SignalCommand
{
	[Inject]
	public var itemType:FoodType;
 
	[Inject]
	public var model:FoodOrderModel;
 
	override public function execute():void
	{
		model.addItemToOrder(itemType);
	}
}

AddFoodItemToOrderCommand has two public properties that are marked for injection. The itemType property is a FoodType object. As you will recall, FoodType is the parameter that was passed to the AddFoodItemToOrder Signal super constructor. The AddFoodItemToOrder Signal dispatch() included an instance of a FoodType object which will be injected into the AddFoodItemToOrderCommand alongside any other injections that have been mapped and specified. In this case we are also injecting the FoodOrderModel instance that was mapped as a singleton in the SignalCafeContext.

It is important to note that the parameters passed via the Signals dispatch() method will be instantly mapped and unmapped when the command is executed. If you’ve previously mapped a class that is being delivered via a Signal command mapping, that mapping will be overwritten and removed. To avoid this, favor using typed value objects as Signal parameters.

The AddFoodItemToOrderCommand has a simple job. It accesses the FoodOrderModel instance and calls its addItemToOrder method adding the FoodType parameter that was dispatched with the Signal to trigger the command.

That covers the basics of using the SignalCommandMap within a Robotlegs application. There are options that this example doesn’t cover. I would recommend looking through the SignalCommandMap unit test suite for a thorough review of its capabilities. There are several more commands and Signals in the example for you to look over as well. Additionally the example uses Signals within views and the model to dispatch notifications. Outside of binding and UI events, no events are being passed within the application.

Hopefully this will give you the basic understanding of using the SignalCommandMap in a Robotlegs application. Let me know if you have any criticisms, comments, suggestions, or ideas regarding the utility’s implementation at http://knowledge.robotlegs.org

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!

  • Samac

    Maybe it is just me (most likely), I’m working to understand this and I’m using RobotLegs 1.4 & as3Signals 0.8 and when I try to compile the piece, I receive the following error:

    ReferenceError: Error #1065: Variable org.robotlegs.base:ContextBase::injector is not defined.

    Now if I load the Robotlegs that was originally with your piece, the error is gone.  I noticed that the version of Robotlegs are different.  Is there something I should do to correct for this.  I would like to use the latest of all SWCs.

  • jmp909

    i’ve managed to get this sort of working with: 
    as3-signals-v0.8.swcrobotlegs-framework-v1.5.2.swcsignals-extension-SignalsCommandMap.swcSwiftSuspenders-v1.5.1.swchowever note the following..

    I had to add a constructor function to the FoodItemSelected signal

    public class FoodItemSelected extends Signal { public function FoodItemSelected() { super(FoodItem); } }

    this removes the following error that was occurring with other versions:

    [Fault] exception, information=Error: Injector is missing a rule to handle injection into property “item” of object “[object FoodItemSelectedCommand]“. Target dependency: “org.robotlegs.examples.signalcommands.model.vo::FoodItem”, named “”

    but there’s a warning on the Injector…

    AddFoodItemToOrder::FoodSelectionView::addFoodItem_clickHandlerFoodSelectionViewMediator::handleItemTypeAddedAddFoodItemToOrderCommand::executeFoodOrderModel::addItemToOrderCurrentOrderViewMediator::updateOnItemAddedFoodItemSelectedCommand::executeCurrentOrderViewMediator::updateOnOrderUpdatedFoodSelectionView::addFoodItem_clickHandlerFoodSelectionViewMediator::handleItemTypeAddedWarning: Injector already has a rule for type “org.robotlegs.examples.signalcommands.model.enums::FoodType”, named “”. If you have overwritten this mapping intentionally you can use “injector.unmap()” prior to your replacement mapping in order to avoid seeing this message.

    I’m not sure what’s happening there then. The application works now, but there’s obviously still an issue

    j

  • jmp909

    sorry about that.. line breaks not showing.. those other parts are my trace outputs to follow the function signal flow.. the warning is:   

    Warning: Injector already has a rule for type “org.robotlegs.examples.signalcommands.model.enums::FoodType”, named “”. If you have overwritten this mapping intentionally you can use “injector.unmap()” prior to your replacement mapping in order to avoid seeing this message. 

  • jmp909

    Ok i updated my SignalsCommandMap swc to the v0.4 version and along with my constructor fix in FoodItemSelected everything works without warnings now

    SWCs used: 
    as3-signals-v0.8.swc, robotlegs-framework-v1.5.2.swc, 
    signals-extension-SignalsCommandMap-v0.4.swc, 
    SwiftSuspenders-v1.5.1.swcHope this is useful to people

  • mike

    Is this a flex only sample? I’ve tried to work this into a pure as3 example but extending SignalContext says super() accepts 0 parameters. If thats the case how do u pass in a context view? Great article btw :)

  • mike

    this is an old post but just in case ppl are still reading – did you find a solution for this? I have the same problem trying to build a pure as3, robotlegs-signals example. cheers