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:
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.
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
The Robotlegs, AS3-Signals and the SignalCommandMap Example by Joel Hooks, unless otherwise expressly stated, is licensed under a Creative Commons Attribution 3.0 United States License.