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 https://knowledge.robotlegs.org

  • https://dnalot.com/ Jon Toland

    That's hot. Could SignalsCommandMap throw an error if it tries to map a dispatched payload that was already mapped in the Context? I think that might aver unpleasant surprises.

  • Will

    When instantiating a custom class extended from SignalContext I am unable to pass the contextView & autoStartup parameters usually passable to a regular Context. I there a reason for this?

  • https://twitter.com/robpenner robpenner

    I fixed the SignalContext constructor issue here:
    https://github.com/robertpenner/signals-extensio…

  • https://joelhooks.com Joel Hooks

    That would definitely be the way to go. Unfortunately until SwiftSuspenders and Robotlegs hit the next dot-release the IInjector API isn't there to accomplish it. It will be more robust in the near future.

  • https://dnalot.com/ Jonathan Toland

    That's hot. Could SignalsCommandMap throw an error if it tries to map a dispatched payload that was already mapped in the Context? I think that might aver unpleasant surprises.

  • Will

    When instantiating a custom class extended from SignalContext I am unable to pass the contextView & autoStartup parameters usually passable to a regular Context. I there a reason for this?

  • https://twitter.com/robpenner robpenner

    I fixed the SignalContext constructor issue here:
    https://github.com/robertpenner/signals-extensio…

  • https://joelhooks.com Joel Hooks

    That would definitely be the way to go. Unfortunately until SwiftSuspenders and Robotlegs hit the next dot-release the IInjector API isn't there to accomplish it. It will be more robust in the near future.

  • Will

    Shouldn't the constructor for AddFoodItemToOrder accept a Class as a parameter since you are calling the super's constructor with super(FoodType)?

  • Will

    Wait… I get it… nevermind :/

  • https://joelhooks.com Joel Hooks

    it *is* kinda hidden in there, but at the same time I don't want they value type changed and it kicks a RTE quickly if a wrong type is passed.

  • Will

    Shouldn't the constructor for AddFoodItemToOrder accept a Class as a parameter since you are calling the super's constructor with super(FoodType)?

  • Will

    Wait… I get it… nevermind :/

  • https://joelhooks.com Joel Hooks

    it *is* kinda hidden in there, but at the same time I don't want they value type changed and it kicks a RTE quickly if a wrong type is passed.

  • sjhewitt

    I came across a small problem (caused by the use of the .constructor property) when trying to inject a ListCollectionView into a Command, so thought I'd write a post about the changes I made to SignalCommandMap to overcome it. Any thoughts are welcome!
    https://blog.sjhewitt.co.uk/2010/02/robotlegs-an…

  • sjhewitt

    I came across a small problem (caused by the use of the .constructor property) when trying to inject a ListCollectionView into a Command, so thought I'd write a post about the changes I made to SignalCommandMap to overcome it and try to improve the argument injection. Any thoughts are welcome!
    https://blog.sjhewitt.co.uk/2010/02/robotlegs-an…

  • https://dnalot.com/ Jon Toland

    Your first proposition sounds interesting. In practice we (CommandMap too) should be calling IReflector#getClass. Till's implementation actually has the same bug but it's already fixed in Injector#injectInto so I'll file a bug and/or patch right away. I recently proposed CommandMap#executedCommand and wonder whether it could be modified to accommodate SignalCommandMap#routeSignalToCommand too:

    https://gist.github.com/11597e33a30351a2e2ad

    Could your second proposal work without explicit naming injections? Maybe Joel could implicitly name repeated types ordinally. Thus your Command would be like:

    https://gist.github.com/310118

  • Will

    Should signals be used when a mediator simply needs to respond to a change in the model? I assume in order to do this the signal will need to be registered as a singleton using injector.mapSingleton(MySignal) as a Command is not needed in this use case.

  • sjhewitt

    I thought about using a reflector, but i would still run into the problem of not being able to inject to interfaces. Personally, i think the contract created using Signal.valueClasses should be honoured when it is set, otherwise falling back to the usual method.

    Implicitly named injectors could be used, but does add quite a bit more complexity to the signal routing. It could also be possible to create an IInjectedSignal interface that extends ISignal and adds a command something like:
    mapValues(injector, valueObjects) and unmapValues(injector, valueObjects)
    Then the routeSignalToCommand works like this:
    https://gist.github.com/310237

  • https://dnalot.com/ Jonathan Toland

    Your first proposition sounds interesting. In practice we (CommandMap too) should be calling IReflector#getClass. Till's implementation actually has the same bug but it's already fixed in Injector#injectInto so I'll file a bug and/or patch right away. I recently proposed CommandMap#executedCommand and wonder whether it could be modified to accommodate SignalCommandMap#routeSignalToCommand too:

    https://gist.github.com/11597e33a30351a2e2ad

    Could your second proposal work without explicit naming injections? Maybe Joel could implicitly name repeated types ordinally. Thus your Command would be like:

    https://gist.github.com/310118

  • Will

    Should signals be used when a mediator simply needs to respond to a change in the model? I assume in order to do this the signal will need to be registered as a singleton using injector.mapSingleton(MySignal) as a Command is not needed in this use case.

  • sjhewitt

    I thought about using a reflector, but i would still run into the problem of not being able to inject to interfaces. Personally, i think the contract created using Signal.valueClasses should be honoured when it is set, otherwise falling back to the usual method.

    Implicitly named injectors could be used, but does add quite a bit more complexity to the signal routing. It could also be possible to create an IInjectedSignal interface that extends ISignal and adds a command something like:
    mapValues(injector, valueObjects) and unmapValues(injector, valueObjects)
    Then the routeSignalToCommand works like this:
    https://gist.github.com/310237

  • Nikos

    Best RL eg YET :)

  • Nikos

    Hi again, if I run this with flex 4 I get this when I try and add a food item :(

    Error: Injector is missing a rule to handle injection into target [object FoodItemSelectedCommand]. Target dependency: org.robotlegs.examples.signalcommands.model.vo::FoodItem

  • https://joelhooks.com Joel Hooks

    Hi Nikos, I am traveling currently and can't trouble shoot your issue. Be sure to check your versions. It is likely some weird combination of older RL or SwiftSuspenders compiled into the utility.

  • Nikos

    No probs, I don't think you publish a swc for your own RL singal adabpter code?

  • Nikos

    Im using
    robotlegs-framework-v1.1.2.swc

  • Nikos

    with SwiftSuspenders-v1.5.1.swc

  • Nikos

    fixed:
    had to add
    injector.mapSingleton(FoodItem);
    to context

  • https://joelhooks.com Joel Hooks

    That isn't proper, something is wrong with the versions. If I had to
    guess I would say the signal command map sec has RL classes compiled
    into it that are older

  • Willdady

    Joel. Do you have any plans on updating the SignalCommapMap to include detain and release like the current RL 1.3 CommandMap? Would be greate to bring it inline with the current release.

  • https://joelhooks.com Joel Hooks

    I actually don't have any plans to. Stray's advice on Twitter is pretty good. I hate to be lazy about it, but need to be right now. I do need to do a round of maintenance soon, so if you wanted to update the API I could get it in there.

  • Tobur

    It whould be great if you update Signas API.
    Thx

  • https://twitter.com/flexcomponent Bryan, Choi

    Hum…. I want to get this with modular….okay… Thanks a lot.

  • https://www.boatseatpedestal.net/ Boat seat pedestal

    Coming home from very lonely places, all of us go a little mad: whether from great personal success, or just an all-night drive, we are the sole survivors of a world no one else has ever seen.

  • Nikos

    how do you get the mediator to listen to a signal from somethings not injected into the mediator. I want to get a mediator to do something from a command

  • https://joelhooks.com Joel Hooks

    The simplest solution is to map the signal class for injection as a

    singleton. Then you can inject it wherever you need it.

  • zeflasher

    Just wondering will this work with NativeSignal?

  • Gordon

    hi,I add injector.mapSingleton(FoodItem); to the context, the food item can be added to list, but can not remove it, did you fix this bug

  • Gordon

    I use the SwiftSuspenders-v1.6.0.swc, robotlegs 1.40, as3-signals 0.8

  • Jmerrill_2001

    Very cool – after some pain, I got this working (pain because I am still a RL newbie). Your example files helped, I have this working now. Curious, why isn't this part of the standard Robotlegs download? Is it because Signals is optional in RL?

  • https://twitter.com/stunberg Scott Langeberg

    When do I decide to use mapSignal() vs. mapSignalClass()? I would assume the use of mapSignal() by default, as it expects type ISignal, versus mapSignalClass(), which uses Class?

    Looks great. Thanks for the work!

  • https://twitter.com/stunberg Scott Langeberg

    When do I decide to use mapSignal() vs. mapSignalClass()? I would assume the use of mapSignal() by default, as it expects type ISignal, versus mapSignalClass(), which uses Class?

    Looks great. Thanks for the work!

  • https://joelhooks.com Joel Hooks

    mapSignalClass creates the actual instance and maps it for injection in other classes based on the type (class) you feed it.

  • Dave Marr

    bump. I was having trouble triggering a command off a flex app's “applicationComplete” event.

  • Owen

    Hi,

    I'm not sure if I'm using the latest version of the code – it's the same one as Stray has on her gitub though, but I think I've found a bug. If you use unmapSignalClass, the reference to the signal in signalClassMap is retained. If you then remap the signal class the old reference is used, rather than a new one being created. This in turn means that the command does not get fired when the signal is sent.

    I think fixed it by adding: delete signalClassMap[signalClass]; into the unmapSignalClass method, but it might be that I'm using it wrong!

    Cheers,

    Owen

  • https://profiles.google.com/dadmyshitsays Sam Rivello

    Download a free AS3-Signals Presentation PDF and AS3-Only project here!

    https://www.blog.rivellomultime…

    -Samuel Asher Rivello
    https://www.RivelloMultimediaCo…

  • Masuland

    Hey Joel,

    I'd like to create a Login Example with Robotlegs 1.4 & AS3-Signals 0.8, but, for now it only works with Robotlegs 1.0 & AS3-Signals 0.6 … see:

    https://code.google.com/p/masul… or
    https://code.google.com/p/masul…

    Is AS3-Signal … see:
    https://github.com/robertpenne…
    … and the Signals-extension for Robotlegs … see:
    https://github.com/robotlegs/s…
    … still supported from the Robotlegs – Community?

    Thanks,
    masu

  • Sebastian

    Hi Joel, would you say that this example has the latest robotlegs/signals release? Or is there another link/example?

  • https://joelhooks.com Joel Hooks

    I'd say this example might use a previous release and you should definitely acquire the latest versions of the libraries for your projects. The example itself is still valid.