Event propagation

When events are triggered, there are three phases in which Flex checks whether there are event listeners. These phases occur in the following order:

  • Capturing

  • Targeting

  • Bubbling

During each of these phases, the nodes have a chance to react to the event. For example, assume the user clicks a Button control that is inside a VBox container. During the capturing phase, Flex checks the Application object and the VBox for listeners to handle the event. Flex then triggers the Button’s listeners in the target phase. In the bubbling phase, the VBox and then the Application are again given a chance to handle the event but now in the reverse order from the order in which they were checked in the capturing phase.

In ActionScript 3.0, you can register event listeners on a target node and on any node along the event flow. Not all events, however, participate in all three phases of the event flow. Some types of events are dispatched directly to the target node and participate in neither the capturing nor the bubbling phases. All events can be captured unless they are dispatched from the top node.

Other events may target objects that are not on the display list, such as events dispatched to an instance of the Socket class. These event objects flow directly to the target node, without participating in the capturing or bubbling phases. You can also cancel an event as it flows through the event model so that even though it was supposed to continue to the other phases, you stopped it from doing so. You can do this only if the cancelable property is set to true.

Capturing and bubbling happen as the Event object moves from node to node in the display list: parent-to-child for capturing and child-to-parent for bubbling. This process has nothing to do with the inheritance hierarchy. Only DisplayObject objects (visual objects such as containers and controls) can have a capturing phase and a bubbling phase in addition to the targeting phase.

Mouse events and keyboard events are among those that bubble. Any event can be captured, but no DisplayObject objects listen during the capturing phase unless you explicitly instruct them to do so. In other words, capturing is disabled by default.

When a faceless event dispatcher, such as a Validator, dispatches an event, there is only a targeting phase, because there is no visual display list for the Event object to capture or bubble through.

About the target and currentTarget properties

Every Event object has a target and a currentTarget property that help you to keep track of where it is in the process of propagation. The target property refers to the dispatcher of the event. The currentTarget property refers to the current node that is being examined for event listeners.

When you handle a mouse event such as MouseEvent.CLICK by writing a listener on some component, the event.target property does not necessarily refer to that component; it is often a subcomponent, such as the Button control’s UITextField, that defines the label.

When Flash Player or Adobe® AIR™ dispatches an event, it dispatches the event from the frontmost object under the mouse. Because children are in front of parents, that means the player or AIR might dispatch the event from an internal subcomponent, such as the UITextField of a Button.

The event.target property is set to the object that dispatched the event (in this case, UITextField), not the object that is being listened to (in most cases, you have a Button control listen for a click event).

MouseEvent events bubble up the parent chain, and can be handled on any ancestor. As the event bubbles, the value of the event.target property stays the same (UITextField), but the value of the event.currentTarget property is set at each level to be the ancestor that is handling the event. Eventually, the currentTarget will be Button, at which time the Button control’s event listener will handle the event. For this reason, you should use the event.currentTarget property rather than the event.target property; for example:

<mx:Button label="OK" click="trace(event.currentTarget.label)"/>

In this case, in the Button event’s click event listener, the event.currentTarget property always refers to the Button, while event.target might be either the Button or its UITextField, depending on where on the Button control the user clicked.

Capturing phase

In the capturing phase, Flex examines an event target’s ancestors in the display list to see which ones are registered as a listener for the event. Flex starts with the root ancestor and continues down the display list to the direct ancestor of the target. In most cases, the root ancestors are the Stage, then the SystemManager, and then the Application object.

For example, if you have an application with a Panel container that contains a TitleWindow container, which in turn contains a Button control, the structure appears as follows:

Application 
    Panel 
        TitleWindow 
            Button

If your listener is on the click event of the Button control, the following steps occur during the capturing phase if capturing is enabled:

  1. Check the Application container for click event listeners.

  2. Check the Panel container for click event listeners.

  3. Check the TitleWindow container for click event listeners.

During the capturing phase, Flex changes the value of the currentTarget property on the Event object to match the current node whose listener is being called. The target property continues to refer to the dispatcher of the event.

By default, no container listens during the capturing phase. The default value of the use_capture argument is false. The only way to add a listener during this phase is to pass true for the use_capture argument when calling the addEventListener() method, as the following example shows:

myPanel.addEventListener(MouseEvent.MOUSE_DOWN, clickHandler, true);

If you add an event listener inline with MXML, Flex sets this argument to false; you cannot override it.

If you set the use_capture argument to true—in other words, if an event is propagated through the capturing phase—the event can still bubble, but capture phase listeners will not react to it. If you want your event to traverse both the capturing and bubbling phases, you must call addEventListener() twice: once with use_capture set to true, and then again with use_capture set to false.

The capturing phase is very rarely used, and it can also be computationally intensive. By contrast, bubbling is much more common.

Targeting phase

In the targeting phase, Flex invokes the event dispatcher’s listeners. No other nodes on the display list are examined for event listeners. The values of the currentTarget and the target properties on the Event object during the targeting phase are the same.

Bubbling phase

In the bubbling phase, Flex examines an event’s ancestors for event listeners. Flex starts with the dispatcher’s immediate ancestor and continues up the display list to the root ancestor. This is the reverse of the capturing phase.

For example, if you have an application with a Panel container that contains a TitleWindow container that contains a Button control, the structure appears as follows:

Application 
    Panel 
        TitleWindow 
            Button

If your listener is on the click event of the Button control, the following steps occur during the bubble phase if bubbling is enabled:

  1. Check the TitleWindow container for click event listeners.

  2. Check the Panel container for click event listeners.

  3. Check the Application container for click event listeners.

An event only bubbles if its bubbles property is set to true. Mouse events and keyboard events are among those that bubble; it is less common for higher-level events that are dispatched by Flex to bubble. Events that can be bubbled include change, click, doubleClick, keyDown, keyUp, mouseDown, and mouseUp. To determine whether an event bubbles, see the event’s entry in the ActionScript 3.0 Reference for the Adobe Flash Platform.

During the bubbling phase, Flex changes the value of the currentTarget property on the Event object to match the current node whose listener is being called. The target property continues to refer to the dispatcher of the event.

When Flex invokes an event listener, the Event object might have actually been dispatched by an object deeper in the display list. The object that originally dispatched the event is the target. The object that the event is currently bubbling through is the currentTarget. So, you should generally use the currentTarget property instead of the target property when referring to the current object in your event listeners.

If you set the useCapture property to true—in other words, if an event is propagated through the capturing phase—then it does not bubble, regardless of its default bubbling behavior. If you want your event to traverse both the capturing and bubbling phases, you must call addEventListener() twice: once with useCapture set to true, and then again with useCapture set to false.

An event only bubbles up the parent’s chain of ancestors in the display list. Siblings, such as two Button controls inside the same container, do not intercept each other’s events.

Detecting the event phase

You can determine what phase you are in by using the Event object’s eventPhase property. This property contains an integer that represents one of the following constants:

  • 1 — Capturing phase (CAPTURING_PHASE)

  • 2 — Targeting phase (AT_TARGET)

  • 3 — Bubbling phase (BUBBLING_PHASE)

The following example displays the current phase and information about the current target:

<?xml version="1.0"?>
<!-- events/DisplayCurrentTargetInfo.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <fx:Script><![CDATA[
        import mx.controls.Alert;

        private function showInfo(e:MouseEvent):void {
            Alert.show("Phase: " + e.eventPhase + "\n" + 
                "ID: " + e.currentTarget.id + "\n" + 
                "Label: " + e.currentTarget.label + "\n" + 
                "Font Size: " + e.currentTarget.getStyle("fontSize"), "Current Target Info");
        }
    ]]></fx:Script>

    <s:Button id="b1" label="Click Me" click="showInfo(event)"/>

</s:Application>

The executing SWF file for the previous example is shown below:

Stopping propagation

During any phase, you can stop the traversal of the display list by calling one of the following methods on the Event object:

You can call either the event’s stopPropagation() method or the stopImmediatePropagation() method to prevent an Event object from continuing on its way through the event flow. The two methods are nearly identical and differ only in whether the current node’s remaining event listeners are allowed to execute. The stopPropagation() method prevents the Event object from moving on to the next node, but only after any other event listeners on the current node are allowed to execute.

The stopImmediatePropagation() method also prevents the Event objects from moving on to the next node, but it does not allow any other event listeners on the current node to execute.

The following example creates a TitleWindow container inside a Panel container. Both containers are registered to listen for a mouseDown event. As a result, if you click on the TitleWindow container, the showAlert() method is called twice unless you add a call to the stopImmediatePropagation() method, as the following example shows:

<?xml version="1.0"?>
<!-- events/StoppingPropagation.mxml -->
<s:Application     
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark"
    initialize="init(event);">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script>
        <![CDATA[
        import mx.controls.Alert;
        import flash.events.MouseEvent;
        import flash.events.Event;

        public function init(e:Event):void {
            p1.addEventListener(MouseEvent.MOUSE_DOWN, showAlert);
            tw1.addEventListener(MouseEvent.MOUSE_DOWN, showAlert);
            tw1.addEventListener(Event.CLOSE, closeWindow);

            p2.addEventListener(MouseEvent.MOUSE_DOWN, showAlertWithoutStoppingPropagation);
            tw2.addEventListener(MouseEvent.MOUSE_DOWN, showAlertWithoutStoppingPropagation);
            tw2.addEventListener(Event.CLOSE, closeWindow);
        }

        public function showAlert(e:Event):void {
            Alert.show("Alert!\n" + "Current Target: " + e.currentTarget + "\n" + 
                "Phase: " + e.eventPhase);
            e.stopImmediatePropagation();
        }

        public function showAlertWithoutStoppingPropagation(e:Event):void {
            Alert.show("Alert!\n" + "Current Target: " + e.currentTarget + "\n" + 
                "Phase: " + e.eventPhase);          
        }

        public function closeWindow(e:Event):void {
            p1.removeChild(tw1);
        }
        ]]>
    </fx:Script>

    <s:Panel id="p1" title="Stops Propagation">
        <mx:TitleWindow id="tw1" 
            width="300" 
            height="100" 
            showCloseButton="true" 
            title="Title Window 1">
            <s:Button label="Click Me"/>
            <s:TextArea id="ta1"/>
        </mx:TitleWindow>
    </s:Panel>

    <s:Panel id="p2" title="Does Not Stop Propagation">
        <mx:TitleWindow id="tw2" 
            width="300" 
            height="100" 
            showCloseButton="true" 
            title="Title Window 2">
            <s:Button label="Click Me"/>
            <s:TextArea id="ta2"/>
        </mx:TitleWindow>
    </s:Panel>

</s:Application>

The executing SWF file for the previous example is shown below:

Note: A call to either the Event.stopPropogation() or the Event.stopImmediatePropogation() methods does not prevent default behavior from occurring.

Event examples

In the following example, the parent container’s click handler disables the target control after the target handles the event. It shows that you can reuse the logic of a single listener (click the HGroup container) for multiple events (all the clicks).

<?xml version="1.0"?>
<!-- events/NestedHandlers.mxml -->
<s:Application 
    xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark">

    <s:layout> 
        <s:VerticalLayout/> 
    </s:layout>

    <fx:Script><![CDATA[
        public function disableControl(event:MouseEvent):void {
            // Use this same logic for all events.
            event.currentTarget.enabled = false;
        }

        public function doSomething(event:MouseEvent):void {
            b1.label = "clicked";
            ta1.text += "Something happened.";
        }

        public function doSomethingElse(event:MouseEvent):void {
            b2.label = "clicked";
            ta1.text += "Something happened again.";
        }
    ]]></fx:Script>

    <s:HGroup id="hb1" height="200" click="disableControl(event)">
        <s:Button id='b1' label="Click Me" click="doSomething(event)"/>
        <s:Button id='b2' label="Click Me" click="doSomethingElse(event)"/>
        <s:TextArea id="ta1"/>
    </s:HGroup>

    <s:Button id="resetButton" 
        label="Reset" 
        click="hb1.enabled=true;b1.enabled=true;b2.enabled=true;b1.label='Click Me';b2.label='Click Me';"/>

</s:Application>

The executing SWF file for the previous example is shown below:

By having a single listener on a parent control instead of many listeners (one on each child control), you can reduce your code size and make your applications more efficient. Reducing the number of calls to the addEventListener() method potentially reduces application startup time and memory usage.

The following example registers an event handler for the Panel container, rather than registering a listener for each link. All children of the Panel container inherit this event handler. Since Flex invokes the handler on a bubbled event, you use the target property rather than the currentTarget property. In this handler, the currentTarget property would refer to the Panel control, whereas the target property refers to the LinkButton control, which has the label that you want.

<?xml version="1.0"?>
<!-- events/SingleRegisterHandler.mxml -->
<s:Application     xmlns:fx="http://ns.adobe.com/mxml/2009"    
    xmlns:mx="library://ns.adobe.com/flex/mx"     
    xmlns:s="library://ns.adobe.com/flex/spark"
    creationComplete="createLinkHandler();">

    <s:layout>
        <s:VerticalLayout/>
    </s:layout>

    <fx:Script>
        <![CDATA[
        private function linkHandler(event:MouseEvent):void {
            try {
                var url:URLRequest = new URLRequest("http://finance.google.com/finance?q=" + 
                event.target.label);
                navigateToURL(url);
            } catch (e:Error) {
                /** 
                 *  Do nothing; just want to catch the error that occurs when a user clicks on 
                 *  the Panel and not one of the LinkButtons.
                 **/                
            }
        }
        private function createLinkHandler():void {
            p1.addEventListener(MouseEvent.CLICK,linkHandler);
        }
        ]]>
    </fx:Script>
    
    <s:Panel id="p1" title="Click on a stock ticker symbol">
        <s:layout>
            <s:HorizontalLayout/>
        </s:layout>
        <mx:LinkButton label="ADBE"/>
        <mx:LinkButton label="GE"/>
        <mx:LinkButton label="IBM"/>
        <mx:LinkButton label="INTC"/>
    </s:Panel>
</s:Application>

The executing SWF file for the previous example is shown below: