In a de-coupled environment, its not always easy to let various components communicate with each other. In order to make this task slightly easier Spring Actionscript includes the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way. class.
The EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way. is a final class with only static methods. In Actionscript it is not possible to define an interface with static methods. In order to still provide an interface there does exist the IEventBus Describes an object that is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled manner.. This interface is implemented by the EventBusFacade Basic implementation of the <code>IEventBus</code> interface that acts as a facade for the static <code>EventBus</code> class., which re-routes all of its methods to their EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way. equivalent. This way it is easier to mock up an IEventBus Describes an object that is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled manner. for unit testing purposes.
There are a number of different ways to subscribe to events that are dispatched through the EventBus: The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way.
The first is to listen to all events that are dispatched through the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way.. This is possible through the EventBus.addListener Adds a listener to the AppSettings. method. This method expects an IEventBusListener Interface to be implemented by all objects that want to register themselves as listeners to all events dispatched from the event bus. as an argument. The IEventBusListener Interface to be implemented by all objects that want to register themselves as listeners to all events dispatched from the event bus. looks like this:
public interface IEventBusListener {
function onEvent(event:Event):void;
}Every event dispatched by the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way. will be passed into the onEvent Handles an <code>Event</code> received from the event bus. method.
The second method is to only listen for events of a specific type. Use the EventBus.addEventListener Adds the given listener function as an event handler to the given event type. for this task. The addEventListener Adds the given listener function as an event handler to the given event type. method expects two arguments, the first is a string representing the event type, and the second is a Function instance which will be invoked with every event of the specified type.
Instead of a Function it is also possible to supply a proxy instead. This is what the addEventListenerProxy Adds a proxied event handler as a listener to the specified event type. method is for. Instead of a Function this expects a MethodInvoker instance. The MethodInvoker class is part of the as3-commons-reflect package.
The last option is to add a listener for events of a certain class. To get this to happen use the addEventClassListener Adds a listener function for events of a specific <code>Class</code>. or addEventClassListenerProxy Adds a proxied event handler as a listener for events of a specific <code>Class</code>. methods. The same arguments apply to these as for their addEventListener Adds the given listener function as an event handler to the given event type. and addEventListenerProxy Adds a proxied event handler as a listener to the specified event type. neighbours, except they expect a Class instance instead of a type.
All these methods naturally have a removal counterpart: removeListener Removes a listener from the AppSettings object., removeEventListener Removes the given listener function as an event handler from the given event type., removeEventListenerProxy Removes a proxied event handler as a listener from the specified event type., removeEventClassListener Removes a listener function for events of a specific Class. and removeEventClassListenerProxy Removes a proxied event handler as a listener for events of a specific <code>Class</code>..
To clear all types of registered eventlisteners at once simply call the removeAll() Clears the entire <code>IEventBus</code> by removing all types of listeners. method.
This is the easiest part, to dispatch an event through the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way. invoke either the dispatchEvent <p>When the <code>eventBusDispatching</code> property is set to <code>true</code> the specified <code>Event</code> will also be dispatched through the assigned <code>IEventBus</code> instance.</p> or dispatch Starts dispatching this event sequence. methods. The former expects an Event instance while the latter expects a string that indicates a certain type of Event. This event will be created by this method and subsequently dispatched. For example:
EventBus.dispatchEvent(new MyCustomEvent("myCustomEventType"));or
EventBus.dispatch("myCustomEventType");To prevent a lot of boilerplate code consisting of long lists of addEventListener* calls, Spring Actionscript includes an alternative way to define event handlers for events that are dispatched through the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way..
Instead of adding specific listeners functions through the addEventListener calls simply add a bit of metadata to the particular method:
[EventHandler] protected function saveUserHandler(event:Event):void { //implementation ommitted... }
This is the most basic way of annotating a method and immediately implies certain assumptions. In this case it is assumed that the saveUser method will handle an event of the type "saveUser". Another valid method name for this handler could also be:
[EventHandler] protected function saveUser(event:Event):void { //implementation ommitted... }
If, for some reason, this naming convention is not desired then add the name of the type of the specific event to the metadata:
[EventHandler(name="saveUser")] protected function saveTheUserNow(event:Event):void { //implementation ommitted... }
By default, an event handler function is expected to have a single argument of type Event (or any subclass). It is however possible to let Spring Actionscript match the properties of the specified event with the arguments of the event handler. For instance, if the event passed to the saveUser event is not an ordinary event, but a subclass called SaveUserEvent with a property that is called "user" of the type "User". In this case an event handler with the following signature will work just a well:
[EventHandler]
protected function saveUser(user:User):void {
//implementation ommitted...
}The property/argument matching is done by type, so this will fail in the case where an event has multiple properties of the same kind. If the SaveUserEvent should have two user properties, called userA or userB for example, the matching will fail. In that case it is possible to define the properties by name directly in the metadata:
[EventHandler(properties="userA,userB")]
protected function saveUser(userA:User, userB:User):void {
//implementation ommitted...
}To annotate a handler for a certain event Class, define the fully qualified class name like this:
[EventHandler(clazz="com.classes.events.UserEvent")] protected function saveTheUserNow(event:UserEvent):void { //implementation ommitted... }
To enable the processing of these kinds of annotations it is necessary to add an instance of the EventHandlerMetaDataPostProcessor to the application context. The easiest way to do so is to add this bit of XML to the configuration:
<object id="eventhandlerProcessor" class="org.springextensions.actionscript.ioc.factory.config.EventHandlerMetaDataPostProcessor"/>
This will automatically register the processor with the application context.
Do not forget to add the EventHandler metadata to the compiler arguments: -keep-as3-metadata+=EventHandler. Failing to do so will prevent the EventHandlerMetaDataPostProcessor to do its work.
Credit where credit is due: The [EventHandler] handling is similar to the [Mediate] metadata as first introduced by Chris Scott in the Swiz Framework, the Spring Actionscript team both acknowledges and appreciates his work.
On the other side of the spectrum there is event dispatching and a way to avoid having to call EventBus.dispatch(someEvent) Starts dispatching this event sequence. a zillion times. For this particular goal there is the [RouteEvents] metadata which can be handled by the RouteEventsMetaDataPostProcessor. Let's take a look at simple example, imagine a class that is able to dispatch a number of events:
[Event(name="eventName1",type="...")]
[Event(name="eventName2",type="...")]
[Event(name="eventName3",type="...")]
public class MyClass {
//implementation omitted..
}To let Spring Actionscript catch all these events, and redispatch them through the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way., all that is necessary is the addition of the [RouteEvents] metadata:
[RouteEvents]
[Event(name="eventName1",type="...")]
[Event(name="eventName2",type="...")]
[Event(name="eventName3",type="...")]
public class MyClass {
//implementation omitted...
}If not all events need to be re-routed then use the 'events' argument of the [RouteEvents] metadata:
[RouteEvents(events="eventName1,eventName2")]
[Event(name="eventName1",type="...")]
[Event(name="eventName2",type="...")]
[Event(name="eventName3",type="...")]
public class MyClass {
//implementation omitted...
}And that's all there is to it, now to enable this functionality add this object definition to the XML configuration:
<object id="routeEventsProcessor" class="org.springextensions.actionscript.ioc.factory.config.RouteEventsMetaDataPostProcessor"/>
This will automatically register the processor with the application context.
Do not forget to add the RouteEvents metadata to the compiler arguments: -keep-as3-metadata+=RouteEvents. Failing to do so will prevent the RouteEventsMetaDataPostProcessor to do its work.
The combination of the [RouteEvents] and [EventHandler] enables a developer to leverage the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way. without directly depending on it.
Leveraging the above described classes and techniques Spring Actionscript offers a small metadata driven MVC micro-framework. These are the key components that this framework provides:
Fully metadata driven
Works with regular Flash events (flash.events.Event)
No need to implement Spring Actionscript interfaces or inherit from base classes
Plays nice in multi-module environments
The framework uses metadata tags in event and command classes to define mappings between them and the application context as a class factory for the commands.
First add this object definition to the application context XML configuration:
<object id="mvcFactory" class="org.springextensions.actionscript.core.mvc.MVCControllerObjectFactoryPostProcessor"/>
The MVCControllerObjectFactoryPostProcessor <code>IObjectFactoryPostProcessor</code> that checks the specified <code>IConfigurableListableObjectFactory</code> if it contains an <code>MVCRouteEventsMetaDataPostProcessor</code>. is an IObjectFactoryPostProcessor <p>Allows for custom modification of an application context's objects definitions, adapting the objects property values of the context's underlying object factory. implementation that takes care of all the metadata processing and event/command mapping.
This factory postprocessor first checks for the existence of an IMVCEventObjectPostProcessor Marker interface for an object postprocessor that processes [RouteEvent] metadata used by an <code>IController</code> interface. implementation in the application context, when none is found it creates an instance of the MVCRouteEventsMetaDataProcessor <code>IMetaDataProcessor</code> implementation that examines objects (that have been annotated with the <code>[RouteMVCEvents]</code> metadata) and reroutes their specified events through the <code>EventBus</code> so they can be picked up the an <code>IController</code> instance that, in turn, can execute any associated commands. class and adds it to the application context singleton cache. This allows for the events postprocessor to be overridden by another implementation.
After that the factory postprocessor checks if an IController
Describes an object that acts as a registry for command classes that need to be
instantiated and executed in response to the dispatching of an event.
implementation exists in the application context, of not a
Controller instance is created and added to the
context so, if necessary, the IController
Describes an object that acts as a registry for command classes that need to be
instantiated and executed in response to the dispatching of an event.
instance can be overridden again.
This takes care of the preparatory work, now all that remains is defining the event/command mappings.
Annotating components and classes that dispatch events that need to be mapped to a command is almost the same as annotating them for dispatch through the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way. (the EventBus The <code>EventBus</code> is used as a publish/subscribe event mechanism that lets objects communicate with eachother in a loosely coupled way. is used by this MVC framework as the main communication pipeline). So, let's have a simple example:
[RouteMVCEvents] [Event(name="someEventType","flash.events.Event")] public class SomeClassThatDispatchesMVCEvents { //implementation ommitted... }
This means that [RouteEvents] and [RouteMVCEvents] metadata can be combined. (A class could even be annotated with both types of metadata.)
This is all that is necessary for the event side of things of the MVC functionality.
To define a command that is to be triggered by the aforementioned event can be annotated in its simplest form like this:
[Command(eventType="someEventType")] public class CommandClass { function execute():void { //implementation ommitted... } //further implementation ommitted... }
When the command is triggered by a certain event Class, define the class like this:
[Command(eventClass="com.events.CustomEvent")] public class CommandClass { function execute():void { //implementation ommitted... } //further implementation ommitted... }
The command class does not need to implement any kind of interfaces or inherit from any base classes. By default the MVC framework will assume that a method called execute needs to be invoked. When a different method name is needed this can be defined in the metadata tag:
[Command(eventType="someEventType",executeMethod="process")] public class CommandClass { function process():void { //implementation ommitted... } //further implementation ommitted... }
When the execution method needs to receive a reference to the event instance it will be automatically mapped:
[Command(eventType="someEventType",executeMethod="process")] public class CommandClass { function process(event:Event):void { //implementation ommitted... } //further implementation ommitted... }
Properties on the event can be automatically mapped to the method arguments as well. Imagine for instance this event class:
public class CustomEvent extends flash.events.Event {
public static const EVENT_ID:String = "someEventType";
public var user:User;
//further implementation ommitted...
}If the command class has this definition, the property will be mapped automatically:
[Command(eventType="someEventType",executeMethod="process")]
public class CommandClass {
function process(user:User):void {
//implementation ommitted...
}
//further implementation ommitted...
}The event properties can also be mapped to properties on the command:
[Command(eventType="someEventType",executeMethod="process")]
public class CommandClass {
public var user:User;
function process():void {
//implementation ommitted...
}
//further implementation ommitted...
}The mapped event properties can also be explicitly named:
[Command(eventType="someEventType",executeMethod="process",properties="userA,userB")]
public class CommandClass {
function process(userA:User, userB:User):void {
//implementation ommitted...
}
//further implementation ommitted...
}Now all that is left is to add object definitions for each command that is used to the application context XML configuration:
<object id="myCommand" class="com.classes.commands.CommandClass"/>
Or, alternatively, every command class can be annotated with the [Component] metadata and thus will be picked up by the class scanning system:
[Component]
[Command(eventType="someEventType",executeMethod="process")]
public class CommandClass {
}For further information on the class scanning system see section 'The Component scanner and class scanning system'
The application context can now be used as the main command classfactory, leveraging all of its rich DI functionality. Services, models and other dependencies can be easily defined and injected.