freelance developer of javascript, python and more!

I found out about as3signals yesterday and thought it was such a neat way of dealing with events, I decided to spend the weekend refactoring my current project to take advantage of it.

I started by adding some signals to my view components to reduce the number of custom events that I was dispatching, and I now only use custom events to bubble events from ItemRenderers up to the main components. These events are then caught and a Signal is used to forward it to the Mediator. This was going so well that I decided to start integrating the SignalCommandMap developed by Joel Hooks to start using signals in the framework.

After a couple of minor problems upgrading to the latest trunk of RobotLegs, I replaced a few of my custom events and everything was going ok until I came across a problem when trying to use a command that has a ListCollectionView argument. It turns out that ListCollectionView will throw an error if you try to access the constructor property because the case is not handled in the proxy_object::getProperty method, and this is required by the SignalCommandMap to map injection values.

The SignalCommandMap uses the following method to map injector values and execute the Command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
protected function routeSignalToCommand(signal:ISignal, valueObjects:Array, commandClass:Class, oneshot:Boolean):void
{
// NOTE: Assumes no duplicated classes in valueObjects,
// and none of them are previously mapped.
var value:Object;
for each( value in valueObjects )
{
injector.mapValue( value.constructor, value );
}

var command:Object = injector.instantiate( commandClass );

for each( value in valueObjects )
{
injector.unmap( value.constructor );
}

command.execute( );

if ( oneshot )
{
unmapSignal( signal, commandClass );
}
}

As you can see, value.constructor is used to get the class for the argument. This also causes a problem if you are using an interface as the target class for the injection.

There are 2 improvements that I propose to the map:

  1. Use the Signal valueClasses property (if it has been defined on the Signal) as the source of the target classes for injection.
    This means that there is no need to call the constructor method to get the class for injection, and you can use interfaces as an injection target.
  2. Add a parameter that allows you to give names to the argument injections.
    This would allow you to inject multiple objects of the same type into the Command and use named injections to route them to the right properties.

My Updated SignalCommandMap

To achieve these two improvements, the ISignalCommandMap interface is updated to include an argumentNames parameter that allows you to pass in injection names for the arguments. You can mix named and unnamed injections by using an empty string as the name for the unnamed arguments.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.robotlegs.core
{
import org.osflash.signals.ISignal;

public interface ISignalCommandMap
{
function mapSignal(signal:ISignal, commandClass:Class, oneShot:Boolean = false, argumentNames:Array = null):void;

function mapSignalClass(signalClass:Class, commandClass:Class, oneShot:Boolean = false, argumentNames:Array = null):ISignal;

function hasSignalCommand(signal:ISignal, commandClass:Class):Boolean;

function unmapSignal(signal:ISignal, commandClass:Class):void;

function unmapSignalClass(signalClass:Class, commandClass:Class):void;
}
}

Then the routeSignalToCommand function is updated to include the argumentNames and use some helper functions to map and unmap the signal argument values

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected function routeSignalToCommand(signal:ISignal, valueObjects:Array, commandClass:Class, oneshot:Boolean, argumentNames:Array):void
{
mapValues( valueObjects, signal.valueClasses, argumentNames );

var command:Object = injector.instantiate( commandClass );

unmapValues( valueObjects, signal.valueClasses, argumentNames );

command.execute( );

if ( oneshot )
{
unmapSignal( signal, commandClass );
}
}

And then the helper functions that do the work of mapping the value objects to the injected properties. Errors are thrown if valueClasses is used and the correct number of arguments isn’t supplied.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
protected function mapValues(valueObjects:Array, valueClasses:Array, argumentNames:Array):void
{
var i:int;
var value:Object;
if(valueClasses && argumentNames)
{
if(valueObjects.length != valueClasses.length || valueObjects.length != argumentNames.length)
throw new Error("Unequal numbers of arguments");
for(i=0;i
{
injector.mapValue( valueClasses[i], valueObjects[i], argumentNames[i] );
}
}
else if(valueClasses)
{
if(valueObjects.length != valueClasses.length)
throw new Error("Unequal numbers of arguments");
for(i=0;i
{
injector.mapValue( valueClasses[i], valueObjects[i] );
}
}
else
{
for each( value in valueObjects )
{
injector.mapValue( value.constructor, value );
}
}
}

protected function unmapValues(valueObjects:Array, valueClasses:Array, argumentNames:Array):void
{
var i:int;
var value:Object;
if(valueClasses && argumentNames)
{
for(i=0;i
{
injector.unmap( valueClasses[i], argumentNames[i] );
}
}
else if(valueClasses)
{
for(i=0;i
{
injector.unmap( valueClasses[i] );
}
}
else
{
for each( value in valueObjects )
{
injector.unmap( value.constructor );
}
}
}

Now, for a Signal like this:

1
2
3
4
5
6
7
public class InjectionTestSignal extends Signal
{
public function InjectionTestSignal ()
{
super(String, String, IList, IList);
}
}

And this command:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class InjectionTestCommand extends SignalCommand
{
[Inject(name="string1")]
public var string1:String;

[Inject(name="string2")]
public var string2:String;

[Inject(name="list1")]
public var list1:IList;

[Inject(name="list2")]
public var list2:IList;

override public function execute():void
{
// Do something...
}
}

You can create the mapping as follows:

1
signalCommandMap.mapSignalClass(InjectionTestSignal, InjectionTestCommand, false, ["string1","string2","list1","list2"] );

You can download my updated SignalCommandMap here and a very very simple example here

I’ve also forked CommandSignal on github

I will try to add some tests and better examples when I have a bit more time.

§40 · February 20, 2010 · RobotLegs · Tags: , · [Print]