5 Messaging

Parsley 2 introduces a new generic Messaging Framework that allows you to exchange messages between objects in a fully decoupled manner. Decoupled not only means that the sender and the receiver do not have to know each other. It is equally important that the sending and receiving objects are also fully decoupled from the framework itself. This is an advantage ignored by most other Flex Frameworks (including version 1 of Parsley) where you have to use objects or static methods from the Framework API for dispatching application events or messages. Why is that important? If your objects are decoupled from the framework you can reuse them in a different context where you might want to use a different framework or no framework at all. For example you might want to wire dispatching and receiving instances programmatically for Unit Tests without the extra burden to initialize an application framework.

The Parsley Messaging Framework is generic in a sense that it does not impose a particular usage style. This is also a difference to some of the existing Flex MVC Frameworks which often advocate a certain structure and usage pattern or even offer concrete base classes for the controller, model and view parts. With Parsley 2 you are completely free to design your application architecture. In case you do use the Messaging Framework to build a classic MVC architecture you may want to read 9 Building MVC Architectures in addition to this chapter.

This chapter describes how you can configure objects for sending and receiving messages. For every configuration option examples for AS3 Metadata, MXML and XML configuration are included.

5.1 Dispatching Messages

For an object that wants to dispatch messages managed by Parsley you have the following setup options:

5.2 Receiving Messages

For an object that wants to receive and process messages managed by Parsley you have the following setup options:

5.3 Managed Events

If the class that you want to dispatch messages managed by Parsley is a regular EventDispatcher this is the most convenient option. It would work for any existing EventDispatcher implementation even if it wasn't designed with Parsley in mind. It requires two steps:

Metadata Example

[Event(name="loginSuccess",type="com.bookstore.events.LoginEvent")]
[Event(name="loginFailed",type="com.bookstore.events.LoginEvent")]
[Event(name="stateChange",type="flash.events.Event")]
[ManagedEvents("loginSuccess,loginFailure")]
public class LoginServiceImpl extends EventDispatcher implements LoginService {

    [...]
    
    private function handleLoginResult (user:User) : void {
        dispatchEvent(new LoginEvent("loginSuccess", user));
    }
    
}

In the example above the service declares three events. Two of them (loginSuccess and loginFailure) are application events and should be managed by Parsley and dispatched to all objects in the Context interested in that event. The third one is a low-level event only of interest for objects interacting directly with that service. Those objects may still register a regular event listener for that event.

The example method above shows how a result handler (that probably was registered as a handler for a remote service invocation) translates the result into an event and simply dispatches it. No FrontController.getInstance().dispatch... or anything like that. Since loginSuccess was declared as a managed event it will be passed to all MessageHandlers configured in Parlsey.

MXML Example

<Object type="{LoginServiceImpl}">
    <ManagedEvents names="['loginSuccess','loginFailure']"/>
</Object>

If you declare the managed events in MXML you can omit the [ManagedEvents] metadata tag from the previous example. Note that you still have to include the [Event] metadata tags, since those are not a configuration artifact of Parsley but a regular Flash API feature.

XML Example

<object type="com.bookstore.services.LoginServiceImpl">
    <managed-events names="loginSuccess,loginFailure"/>
</object>

As always very simliar to MXML configuration apart from several notation differences.

5.4 Injected MessageDispatchers

Sometimes you don't want to work with events for your application messages. Somehow several event semantics may not make much sense in a particular scenario. Application events managed by Parsley cannot "bubble", stopPropagation would not have any effect in the Parsley message processing sequence and for fully decoupled messaging you may even want to avoid that the message receiver can get hold of the message dispatcher through event.target.

In those cases Parsley offers the option to use any class as an application message, whether it extends flash.events.Event or not. You can then request the framework to inject a message dispatcher function that you can use for your custom application messages. Assuming you created the following simple message class:

class LoginMessage {

    public var user:User;
    
    public var role:String;
    
    function LoginMessage (user:User, role:String) {
        this.user = user;
        this.role = role;
    }
    
}

You can then use it in a service like this:

public class LoginServiceImpl implements LoginService {

    [MessageDispatcher]
    public var dispatcher:Function;

    [...]
    
    private function handleLoginResult (user:User) : void {
        dispatcher(new LoginMessage(user));
    }
    
}

Now your service does not extend EventDispatcher. Instead it declares a variable of type Function annotated with the [MessageDispatcher] tag which instructs Parsley to inject a message dispatcher function on object creation. You can then simply pass any kind of object to this dispatcher function.

MXML Example

<Object type="{LoginServiceImpl}">
    <MessageDispatcher property="dispatcher"/>
</Object>

XML Example

<object type="com.bookstore.services.LoginServiceImpl">
    <message-dispatcher property="dispatcher"/>
</object>

If you don't want to use Metadata tags you can also request the dispatcher injection with MXML or XML configuration.

5.5 MessageHandlers

Message Handlers are the most common approach for the receiving side. You can declare methods to be invoked when a particular application message gets dispatched. In the most simple case the method will simply be selected by parameter type:

Metadata Example

[MessageHandler]
public function handleLogin (message:LoginMessage) : void {

In this case the method will be invoked whenever a message of a matching type (or subtype) gets dispatched.

MXML Example

<Object type="{LoginAction}">
    <MessageHandler method="handleLogin"/> 
</Object>

XML Example

<object type="com.bookstore.actions.LoginAction">
    <message-handler method="handleLogin"/> 
</object>

There is also a variant where you split properties of the message class to arguments of the message handler method:

[MessageHandler(type="com.bookstore.events.LoginMessage",messageProperties="user,role"]
public function handleLogin (user:User, role:String) : void {

Note that in this case you also have to declare the message type since it cannot be detected from the parameter type.

Finally you may encounter a situation where selection by message type is not sufficient. If you dispatch the same message type in different scenarios and application states you may want to further refine the message selection process. See 5.8 Using Selectors for details.

5.6 MessageBindings

Message Bindings are simply a shortcut, where you want to bind a property of a class to a property of a message, that should be updated whenever a message of a matching type is dispatched. In the following example the user property of the example will be set to the value of the user property of the LoginMessage instance whenever such a message is dispatched.

Metadata Example

[MessageBinding(messageProperty="user",type="com.bookstore.events.LoginMessage")]
public var user:User;

MXML Example

<Object type="{LoginServiceImpl}">
    <MessageBinding 
        targetProperty="user" 
        messageProperty="user"
        type="{LoginMessage}"
    />
</Object>

XML Example

<object type="com.bookstore.services.LoginServiceImpl">
    <message-binding 
        target-property="user" 
        message-property="user"
        type="com.bookstore.events.LoginMessage"
    />
</object>

As with MessageHandlers you may want to use Selectors with MessageBindings. See 5.8 Using Selectors for details.

5.7 MessageInterceptors

This is the third and final option for the receiving side. Interceptors may come in handy when you want to decide whether or not the message should be passed to handlers and bindings based on application state or user decisions. Interceptors have the following characteristics:

A simple example where you might want to use such an interceptor, is when you have an application that can be used without being logged in, but does include some actions which will not be accessible without login. In that case an interceptor could suspend the message processing, present a login dialog and resume the message processing after successful login.

Another example would be to show a simple warning before any delete operation is actually performed like in the following example:

public class DeleteItemInterceptor implements ActionInterceptor {
 
    [MessageInterceptor(type="com.bookstore.events.ShoppingCartDeleteEvent")]
    public function interceptDeleteEvent (processor:MessageProcessor) : void {
        var listener:Function = function (event:CloseEvent) : void {
            if (event.detail = Alert.OK) {
                processor.proceed();
            }  
        };      
        Alert.show("Do you really want to delete this item?", "Warning", 
            Alert.OK | Alert.CANCEL, null, listener);
    }
 
}

When the user hits cancel, the MessageProcessor never resumes and no subsequent handler or bindings will be executed.

Like with MessageBindings you have to declare the message type since it cannot be detected from the method signature. Interceptor methods must always have a single parameter of type MessageProcessor. Again you can use MXML or XML instead of Metadata tags for declaring the interceptor methods:

MXML Example

<Object type="{LoginServiceImpl}">
    <MessageInterceptor 
        method="interceptDeleteEvent" 
        type="{ShoppingCartDeleteEvent}"
    />
</Object>

XML Example

<object type="com.bookstore.services.LoginServiceImpl">
    <message-interceptor
        method="interceptDeleteEvent"
        type="com.bookstore.events.ShoppingCartDeleteEvent"
    />
</object>

It is recommended to use interceptors sparingly as it is the only feature of the Messaging Framework that ties you to the Parsley API, as we have to pass a MessageProcessor instance to you so that you are able to rewind or proceed with message processing.

5.8 Using Selectors

In the examples for the sections about MessageHandlers, MessageBindings and MessageInterceptors the matching methods or properties were always determined solely by the type (class) of the message. Sometimes that may not be sufficient if you dispatch the same message type in different scenarios or application states. In such a case you can refine the selection process with custom selectors.

If you are using events the type property of the Event class can serve as a selector:

[MessageHandler(selector="loginSuccess")]
public function handleLogin (message:LoginEvent) : void {
    [...]
}

[MessageHandler(selector="loginFailure")]
public function handleError (message:LoginEvent) : void {
    [...]
}

In the example above the handleLogin method will only be invoked when the type property of the LoginEvent instance has the value loginSuccess.

For custom message types that do not extend flash.events.Event there is no default selector property, but it can be easily declared with the [Selector] metadata tag on a property of the message class:

class LoginMessage {

    public var user:User;
    
    [Selector]
    public var role:String;
    
    [...]
}

Now you can select message handlers based on the role of the user that logged in:

Metadata Example

[MessageHandler(selector="admin")]
public function handleAdminLogin (message:LoginMessage) : void {

MXML Example

<Object type="{AdminLoginAction}">
    <MessageHandler method="handleAdminLogin" selector="admin"/> 
</Object>

XML Example

<object type="com.bookstore.actions.AdminLoginAction">
    <message-handler method="handleAdminLogin" selector="admin"/> 
</object>

5.9 Using the MessageRouter programmatically

In normal application code you should try to avoid to directly interact with the Parsley API to keep your classes decoupled from the framework. But if in some edge cases or if you want to extend the framework or build another framework on top of it, you may want to register message handlers or bindings programmatically. The MessageRouter interface contains the following methods for regristration:

function registerMessageHandler (targetInstance:Object, targetMethod:String, 
    messageType:Class = null, messageProperties:Array = null, selector:* = undefined) 
                                                                       : MessageTarget;
            
function registerMessageBinding (targetInstance:Object, targetProperty:String, 
    messageType:Class, messageProperty:String, selector:* = undefined) : MessageTarget;
    
function registerMessageInterceptor (targetInstance:Object, targetMethod:String, 
    messageType:Class = null, selector:* = undefined) : MessageTarget;

The parameters correspond to the attributes of the Metadata, MXML and XML tags you saw in earlier sections of this chapter. To get hold of the MessageRouter instance you can inject a Context instance into your class. The messageRouter property of a Context instance always refers to the active MessageRouter:

class SomeExoticClass {

    [Inject]
    public var context:Context;
    
    [PostConstruct]
    public function init () : void {
        context.messageRouter.registerMessageHandler(this, "handleLogin", LoginMessage);    
    }
}

When you place an [Inject] metadata tag on a property of type Context Parsley will always inject the Context instance this class is managed in.

Finally you can also use the MessageRouter to dispatch messages:

context.messageRouter.dispatchMessage(new LoginMessage(user, "admin"));