back

Enabling Flex components
to talk to each other

by Aaron Pedersen and James Polanco

A common challenge facing many new and even experienced Flex developers is enabling Flex components to talk to one another in the most cost-effective way. To enable components to communicate, each component must be aware of the other component's existence so it can transfer data. Yet as developers, we want to follow object-oriented programming (OOP) principles where each component is as self-contained as possible. This is where developers often face an OOP conundrum: How can two Flex components exchange data without each one knowing the other exists?

There are many ways to solve this problem, but for this article, I examine three different approaches in Adobe Flex, including how they work, what problems they solve, and what limitations they present.

It's all about the events

One possible solution to this OOP problem is using the Flash Event Model to enable components to react to each other's events. The Flash Event Model enables components to dispatch event objects that contain information when an action occurs, such as the click of a button or a selection change in a ComboBox. The Event Model also allows for other components and methods to listen to specific events so that when a change occurs in the dispatching component and the event is dispatched, the listener is informed of the change.

The challenge with this model is that the listener needs to know what component to listen for so it can receive the event when the change occurs. For example, in our application we have a List component and a DataGrid component. When the end user changes the selection in the List, we want the DataGrid to update its content to reflect the change to the List. To do this, we have to write a bit of code in the MXML file that contains the DataGrid and the List. When the List dispatches the Change Event, the code updates the DataGrid's dataProvider. To do this, our code needs to have access to the List's reference and the DataGrid's reference so we can use the addEventListener() method to listen to the change.

<mx:Script>
    <![CDATA[
        import mx.events.ListEvent;
        
        [Bindable] public var dataOne:Array;
[Bindable] public var dataTwo:Array;
                
        public function handleChangeEvent(event:ListEvent):void {
            myDataGrid.dataProvider = dataTwo;
        }
    ]]>
</mx:Script>
<mx:List id="myList" change=handleChangeEvent(event)" />
<mx:DataGrid id="myDataGrid" dataProvider="{dataOne}" />

If the List and the DataGrid are located in the same MXML file, like the example above, this is easy functionality to create. We simply write a bit of code to register to the List's selection change and then update the DataGrid. This becomes harder to solve when either the List or the DataGrid is not in the same MXML file or when the location of the components changes.

For example, let's say that our List is now in a sidebar of our application that is always displayed, but the DataGrid is only available in a specific tab view. How does our code know the location of the DataGrid and the List? A common but misguided solution is to start putting the code in the DataGrid MXML file and then referencing the List by walking up the parent hierarchy, such as:

parent.parent.parent.myList.addEventListener()

Another common solution that is a little better but still not ideal is referencing the application and then walking down the hierarchy:

Application.application.sidebar.myList.addEventListener()

These kinds of solutions work in the short term but create brittle code that is easy to corrupt with the smallest change. What happens if you need to move the List or the DataGrid and the path is no longer the same? In this case, the compiler would throw an error because the path is no longer valid. At least you would know that the code is broken, but this is not a good experience as you continue to build your application. This is where most developers start looking for different ways to solve this problem.

Taking it to the top

The major architecture flaw in the first approach is the lack of a common top-level touchpoint to broker all communication through. We want to continue to leverage the Flash Event Model, but we need to identify a single point of contact that allows our application the flexibility to rearrange components within the application without affecting the established communication path. And where better to look than the top of the Adobe Flash Player Display List.

The Display List is a hierarchical representation of what is displayed within Adobe Flash Player. At the top of the Display List is the stage, which all other visual components are decedents of. No matter where your components are located in the application, the stage can be accessed by its stage property. The stage is a great starting point if you are working in Adobe Flash Professional or on an ActionScript project, but in Flex, we don't have to travel that far up the Display List to target the application object. The application object is the grandchild of the stage and the top for building in Flex. All components will be decedents of the application. The application can be accessed from any component by using the following syntax:

Application.application

In the following example, we have our component listen to the application for a ListEvent and update the DataGrid's data when the event is dispatched through the application:

<mx:Script>
    <![CDATA[
        import mx.events.ListEvent;

[Bindable] public var dataOne:Array;
[Bindable] public var dataTwo:Array;

        
        public function subscribeToListChange():void {
            Application.application.addEventListener(ListEvent.CHANGE, handleListChange);
        }
        
        public function handleListChange(event:ListEvent):void {
            myDataGrid.dataProvider = dataTwo;
        }
    ]]>
</mx:Script>
<mx:DataGrid id="myDataGrid" dataProvider="{dataOne}" />

One difference between this approach and the one using the default List Change Event is that we must make sure our dispatched event's bubble property is set to True or our application object will never receive the event. Bubbling is the process that enables events to be passed up the Display List. By default, Flex events are only sent out by the broadcaster to their direct subscribers. But if we set the bubble property of the event to True, the event will be passed up the chain until it reaches the top of the stage. By using bubbling, we can tap into our Application.application and listen for our events, knowing that when our List dispatches our bubbled event, the application will pass it on.

Because the Flex default ListEvent.CHANGE events are not set to bubble, we need to dispatch our own event when our required change happens. In our example, we need to listen for the ListEvent dispatched from List ComboBox and create a new ListEvent with bubbling enabled so it can get to the top:

<mx:Script>
    <![CDATA[
        import mx.events.ListEvent;
        
        public function handleChangeEvent(event:ListEvent):void {
            var event:ListEvent = new ListEvent(event.type, true);
                dispatchEvent(event);
        }
    ]]>
</mx:Script>
<mx:List change="handleChangeEvent(event)" />

Note the flexibility of this solution. Our application now has the ability to move components to different layers of our application without rewriting the event code because no matter where the components live, their events will bubble to the application. In addition, new components can be introduced that also listen to the same event and can then update based on the same user interaction. Our components are now completely decoupled.

There are performance considerations with this approach. Because we are relying on bubbling to communicate our event all the way up the Display List, Flash Player must broadcast an event all they way through the ancestor chain of the component that dispatched the event. If our component is buried deep inside many HBoxes, VBoxes, and Canvas containers, then our event could bubble through hundreds of objects before it reaches the top. If our application is communication-heavy, the overall performance of the application could degrade when a large number of messages need to be passed on. This is a factor developers must consider when choosing this solution.

Bringing in a third-party mediator

Another solution is implementing the Mediator pattern. The Mediator pattern enables communication by providing a specialized object that is solely responsible for routing data to the right target. This specialized object is called a Mediator and is the single point of contact for all event communication between components, similar to our top-level Display List object described earlier. In many ways, a Mediator works like a news aggregator site. Everyone posts news, blogs, or updates to an aggregator site, and then subscribers can pick and choose which news topics they want to read about. The trick is that users know to go to one aggregator site to get all their content instead of spending all day surfing the web looking for all the different bits of information they need.

In our case, instead of news we post data to a Mediator object. Then users who are interested in that data can register with the Mediator, and the Mediator will let them know when that data arrives. The idea is that we declare a common object that everyone in the system can access and then relay information to it.

This is a common issue that our company, DevelopmentArc, faces in day-to-day development, so we created a utility called the EventBroker that is designed to be a global mediator. The EventBroker has two important static methods that anyone can access: subscribe() and broadcast(). Through this API, we can broadcast events through the EventBroker, and anyone who has subscribed to them will get updates.

Looking back at our DataGrid and List example, we can write code in the DataGrid's parent MXML file that looks like this:

<mx:Script>
        <![CDATA[
        import mx.events.ListEvent;
        import com.developmentarc.framework.utils.EventBroker;

[Bindable] public var dataOne:Array;
[Bindable] public var dataTwo:Array;

        
        public function subscribeToListChange():void {
            EventBroker.subscribe(ListEvent.CHANGE, handleListChange);
        }
        
        public function handleListChange(event:ListEvent):void {
            myDataGrid.dataProvider = dataTwo;
        }
    ]]>
</mx:Script>
<mx:DataGrid id="myDataGrid" dataProvider="{dataOne}" />

In our List's parent MXML, we would add this code:

<mx:Script>
    <![CDATA[
        import mx.events.ListEvent;
        import com.developmentarc.framework.utils.EventBroker;
        
        public function handleChangeEvent(event:ListEvent):void {
            EventBroker.broadcast(event);
        }
    ]]>
</mx:Script>
<mx:List change="handleChangeEvent(event)" />

Here we are registering the Change Event from the List and then passing the ListEvent to the EventBroker. The EventBroker then looks for anyone who has subscribed to that event type and calls the method that was provided during the subscription process. In our example, the DataGrid parent MXML is listening for a Change event, and the method handleListChange() is called. Note: At this point, we can move our component, which contains our DataGrid, anywhere within our application, and the communication bridge is not broken.

This is a very basic example of how mediation works. In a real application, we would want to create a custom event that has much more meaning then just a Change Event. Many components dispatch Change Events, and if your application needs to track List changes and ComboBox changes, then you need to create custom events for the different user actions, such as ChangedPriceSelectorEvent. This helps differentiate the change so your code can respond correctly.

DevelopmentArc's EventBroker is available in our MIT licensed open source library. We have many more examples of how to use the EventBroker, and because it is an open source project, you can even look at how we built the EventBroker.

Conclusion

As we mentioned earlier, there are many different ways to get Flex components to communicate with one another. All the approaches described in this article will work for your application. The hard part is identifying when to use each one. We encourage you to step back and evaluate the situation at hand and your application as a whole. Think about the future and the scenarios you will face with your application as it matures over time, and implement the solution that provides you the most flexibility yet meets your immediate needs.

 

Upcoming Flex Events

    360 | Flex Conference
     Indianapolis, IN
     May 18 - 20, 2009

 

‹ Back



James Polanco and Aaron Pedersen are the founding partners of DevelopmentArc.  They both focus on web application architecture and development using Flex, AIR, and Ajax technologies to create solutions for their client needs.  Both James and Aaron write about technology on the DevelopmentArc blog, have written for the Adobe Edge and have spoken at multiple Adobe MAX conferences about Flex and other Flash related technologies.