May
06

1

Cairngorm - 2 Events

In the previous tutorial we covered the use of the ModelLocator and showed how it can be used to decouple a tightly bound application. Here we are going to cover the Cairngorm event model. Flex uses a Bubble/Capture event model which has some limitations. The biggest drawback is that you can only listen to events fired by your children and their descendants. Cairngorm provides a Publish/Subscribe model that lets you further decouple your application. Let’s say you you have a menu in a navigation bar on the left side of your application and another menu in another navigation bar on the right side of your application. Let’s further suppose that those two menus each need to know the state of the other. In native Flex, the highest common parent would need to listen for events thrown from each menu, capture those events and, as in the example presented in the last tutorial, pass the event data back down the chain on the other side. We just decoupled this type of binding for data, and now we find it emerging again for events. Events are fleeting and it seems a bit much to declare every event as a property on our model. So let’s use the CairngormEventDispatcher to get us around this problem.

Again we are going to cover the Flex-native way of accomplishing this, and compare it to the Cairngorm way of doing things. Since embracing Cairngorm, my applications have used a mix of Bubble/Capture and Publish/Subscribe event handling. You will find in your own coding that there are strong arguments for and against using each. As you build applications, you will find out which is right for you, when.

Let’s start with how Flex would keep two sibling (or cousin) menus aware of each other. Building on the last tutorial, we will use the ModelLocator method to populate some ComboBoxes. Once they’re populated, we’ll use these ComboBoxes to send events. Let’s start at the top, our highest common parent - in this case, it’s the Application. In order for the Application to handle the events, it needs to attach some listeners for MENU_CHANGE. The listener assignments are in the init function. There is one assignment for each of the two sides. In this case, those sides are the leftPanel and rightPanel declared here in MXML:

// Tutorial_2_Events.mxml
    
    
<mx:Script>
        <!
[CDATA[
    
        
public static const MENU_EVENT String "menuEvent";
    
        public function 
init() : void
        {
            SampleModel
.getInstance().listData = new ArrayCollectionlistData );
            
            
// Register the listeners and handlers for the Bubble/Capture events.
            
leftPanel.addEventListenerMENU_EVENTleftBubbleHandler );
            
rightPanel.addEventListenerMENU_EVENTrightBubbleHandler );
        
}
        
        ]]
>
    </
mx:Script>
    
    
    <
mx:HBox horizontalGap="20">
    
        <
AngryPanelT2L1 id="leftPanel" />    
        
        <
AngryPanelT2L1 id="rightPanel" />    
    
    </
mx:HBox


Next we will move to the bottom - this is where the events are generated. We start by capturing the change event on the ComboBox - we will call a function and eventually fire a new event of type MENU_CHANGE. In the last line below, you can see the assignment of the change event to our bubbleChange function:

// AngryPanelT2L2.mxml
        
    
<mx:Script>
    <!
[CDATA[
       
        
public function bubbleChange() : void
        {
            
var event DataEvent = new DataEventTutorial_2_Events.MENU_EVENTtrue );
            
event.data bubbleBox.selectedLabel;
            
            
bubbleBox.dispatchEventevent );
        
}
        
    ]]
>
    </
mx:Script>    
        
    <
mx:ComboBox 
        id
="bubbleBox"
        
dataProvider="{SampleModel.getInstance().listData}" 
        
labelField="title" 
        
rowCount="6" 
        
change="bubbleChange()" /> 


Inside the bubbleChange function, we are dispatching a generic Flash DataEvent of type MENU_CHANGE. You will notice we are using the bubbleBox (our ComboBox) to dispatch the new event. This provides scope to the event, so we can later tell which side sent the event. The bubbleBox is passed as the event.target to our event handler. One more thing to notice is that when we create our event, its bubbles flag it set to “true.” Bubbling is mostly considered rude, but to simplify this demo I decided to make an exception. If bubbles was set to “false,” we would be forced to catch the MENU_CHANGE event in every parent, and redispatch it all the way to the top.

Back to the application now for the event handling. Below are the two event handlers that listen for MENU_CHANGE events. Each of these event handlers catches an event, pulls out the valuable data - in our case, the selectedLabel - and, using the binding method covered in the last tutorial, forces the label down the other side:

// AngryPanelT2L2.mxml
    
    
public function leftBubbleHandlerevent DataEvent ) : void
    {
        
// Catch the event and use bindings to push that value down 
        // the chain to the other side
        
rightPanel.eventData event.data;
    
}
    
    
public function rightBubbleHandlerevent DataEvent ) : void
    {
        
// Catch the event and use bindings to push that value down 
        // the chain to the other side
        
leftPanel.eventData    event.data;
    


This is not good code. The Application has attached listeners for events it doesn’t need to be bothered with. It needs to know what to pull out of the event, and it needs to change values on its children, who in turn need to update their children. Since we did away with tight bindings in the last tutorial, perhaps you are thinking we should set values on a model. This would be cleaner, and would do away with the tightly bound relationships, but we can go one step further.

Cairngorm provides a Publish/Subscribe event model. This means only the objects interested in specific events need to listen and handle them. Making our Application free of all event listening and handling. This event model is more like a direct connection to your target(s). This model has many advantages, one of which is being able to have multiple handlers for a single event.

This example starts with the ComboBox dispatching the “change” event. Again, look at the last line, where the assignment of the “change” event is bound to an event handler. The only difference from last time is that we are calling a different function:

// AngryPanelT2L2.mxml
        
    
<mx:ComboBox 
        id
="publishBox"
        
dataProvider="{SampleModel.getInstance().listData}" 
        
labelField="title" 
        
rowCount="6"
        
change="publishChange()"/> 



The difference, in the Cairngorm example, is that all the event handling is done in the class that contains the ComboBoxes. Here, we are adding an event listener for MENU_CHANGE to the CairngormEventDispatcher - this is similar to what we did, using event registration, in the Application for the native Flex DataEvent. Here, events are dispatched and assigned to handlers through the CairngormEventDispatcher. This is what provides the Publish/Subscribe functionality:

// AngryPanelT2L2.mxml

    
public function init() : void
    {
        CairngormEventDispatcher
.getInstance().addEventListener
                                 
Tutorial_2_Events.MENU_EVENTpublishHandler );
    


The two functions below are all it takes to dispatch and handle the Cairngorm events. There is an event handler for the native Flex “change” event which lets us know its time to dispatch an CairngormEvent. You will notice that the event being constructed is a CairngormEvent, not a DataEvent. CairngormEvent has a “data” field. This field comes in very handy. Unless your event has some substantial data, you can typically get away with using the vanilla CairngormEvent. In the native example, the ComboBox dispatched the event so that it would wind up as the “event.target.” In this case, we set the data field to the ComboBox:

// AngryPanelT2L2.mxml
    
    
public function publishChange() : void
    {
        
var event CairngormEvent = new CairngormEventTutorial_2_Events.MENU_EVENT );
        
event.data publishBox;
        
CairngormEventDispatcher.getInstance().dispatchEventevent );
    
}
    
    
public function publishHandlerevent CairngormEvent ) : void
    {
        
// Since we have multiple instances of the same class recieving Cairngorm events
        // we need to figure out who threw the event and take appropriate action
        
if( event.data != publishBox )
        
{
            publishLabel
.text "Recieved: " +  ComboBoxevent.data ).selectedLabel;
        
}
    } 


The publishHandler in this case is being passed a CairngormEvent, not a DataEvent. Since we are using the same class on both sides of our example application, we need a way to figure out who sent the event. As I mentioned before you can have multiple handlers for each event. And in this example, both sides of the application receive the MENU_CHANGE event. In order to tell which side sent the event, we compare the “event.data” to the publishBox in our class. If the publishBox is not a child of the class, that means the event came from the other side.


Conclusion

This example shows how you can use the Flex native and the Cairngorm event models side by side. Each is suited for various types of functionality, and each has its advantages and disadvantages. Once you are comfortable with both models, your applications will become cleaner and easier to maintain. Happy coding!

Next lesson - Commands

Comments

I like browsing your blog because you constantly bring new and awesome insights, I feel that I should at least say thanks for your hard work.

- Henry

Posted by rachat de credit on Nov 28, 2010

Enter a comment - moderated

Add Comment