Intercepting tab events in a Flex TabNavigator

Intercepting tab events in a Flex TabNavigator

Posted 8 May 2009 by Russ in Adobe Flex. Tags: , .

Flex 3’s TabNavigator component has no simple way for us to listen for an event on the tabs, such as a mouseover or a click. So a custom component is required and it’s a nice simple one…

Flex takes care of switching the views in a TabNavigator when you click on the tabs, but what if you want to detect that user activity and do something else? Maybe you want to update something else in your application. For this you need to extend the TabNavigator class and this example explains how to do that.

For this example, we’ll detect clicks on each tab, and also the mouseover, mouseout, mousedown and mouseup events. When any of these events occur, our example will update a TextArea element to indicate that the event has been captured and you can see this in the example below by mousing over and clicking on any of the tabs in the TabNavigator.
You can download and edit the source code, or right-click on the demo below and choose View Source to see how it all fits together.

You need Flash Player 10 to view this content. You can download this for free from the Adobe website. If you’re using a feed reader you could try loading this post in your web browser.

A custom event

For this example I’ve created a custom event, called TabEvent. This is a simple extension of the Event class that has a couple of static constants and an event property. We’ll fire a TabEvent each time a tab is interacted with and use the event property to record which event actually took place.

The constants are there for us to use when we create a new TabEvent, which we’ll see when we look at our application MXML. For this example there are two constants that match the two types of event we want to capture: a mousevent (TAB_MOUSE_ACTIVITY) and a click event (TAB_ITEMCLICK_ACTIVITY):

package events
{
  import flash.events.Event;

  public class TabEvent extends Event
  {

    /**
    * Constants for each type of TabEvent
    */
    public static const TAB_MOUSE_ACTIVITY:String = "tabEventTabMouseActivity";
    public static const TAB_ITEMCLICK_ACTIVITY:String = "tabEventTabITemClickActivity";

Next we have public variable called event, which takes an Event object. We use this to record what type of event has occurred (a MouseEvent for example) and the constructor function assigns this property when a new TabEvent is created. We want the event to bubble up to the root of the application so we set the bubbles property to true.

    /**
    * event property to hold the event that occurred on the Tab
    */
    public var event:Event;

    /**
    * Calls the constructor class of the super class and assigns the received event to the event property
    */
    public function TabEvent(type:String, event:Event, bubbles:Boolean=true, cancelable:Boolean=false)
    {
      super(type, bubbles, cancelable);
      this.event = event;
    }

  }
}

The custom TabNavigator component

Next we have our custom component, which extends the TabNavigator class and is therefore called ExtendedTabNavigator. The first thing we have is the constructor function that simply calls the constructor of the super class (ie the TabNavigator class) and adds an event listener for the FlexEvent.CREATION_COMPLETE event).

package components
{
  import events.TabEvent;
  import flash.events.MouseEvent;
  import mx.containers.TabNavigator;
  import mx.controls.Button;
  import mx.events.FlexEvent;
  import mx.events.ItemClickEvent;

  public class ExtendedTabNavigator extends TabNavigator
  {
    public function ExtendedTabNavigator()
    {
      super();

      // add creation complete handler
      addEventListener(FlexEvent.CREATION_COMPLETE, creationCompleteHandler);
    }

That event listener triggers the creationCompleteHandler() function which itself adds two event listners to the tabBar property of the TabNavigator. We can’t do this in the constructor as it fires before the tabBar property is populated with an instance of the TabBar class, but deferring the event listener setup to this function gets around this nicely. In this example we’re going to listen for any click events (ItemClickEvent.ITEM_CLICK) and we then also add a listener for the the FlexEvent.UPDATE_COMPLETE event on the TabBar itself. We do this for the same reason we did with the creationCompleteHandler() function – we want to monitor each tab in the TabBar but until the TabBar has been updated with its dataProvider, we’re unable to do so.

    /**
    * Adds an event listener to monitor the ItemClickEvent.ITEM_CLICK on the tabBar
    * plus a listener to monitor the FlexEvent.UPDATE_COMPLETE event
    */
    private function creationCompleteHandler(ev:FlexEvent):void
    {
      tabBar.addEventListener(ItemClickEvent.ITEM_CLICK, itemClickEventHandler);
      tabBar.addEventListener(FlexEvent.UPDATE_COMPLETE, updateCompleteHandler);
    }

So when the creation of the TabBar is updated, we can add our event listeners for each mouse event we want to monitor. We could monitor as many different events as we wanted to here but for this example, we’ll just look at four common ones.

    /**
    * Adds event listeners for a selection of MouseEvents to each button in the tabBar
    */
    private function updateCompleteHandler(ev:FlexEvent):void
    {
      for each (var button:Button in tabBar.getChildren())
      {
        button.addEventListener(MouseEvent.MOUSE_OVER, mouseEventHandler);
        button.addEventListener(MouseEvent.MOUSE_OUT, mouseEventHandler);
        button.addEventListener(MouseEvent.MOUSE_DOWN, mouseEventHandler);
        button.addEventListener(MouseEvent.MOUSE_UP, mouseEventHandler);
      }
    }

We then have two event handler functions that are called by our event listeners. The first expects an ItemClickEvent and the second expects a MouseEvent but they both do exactly the same thing: create a new TabEvent, passing it the the received ItemClickEvent or MouseEvent as the event property, and then dispatching the TabEvent to be captured by the application. We could just as easily have one handler function that expected an Event and therefore could be used for any event but this example shows how to handle different types of event differently.

    /**
    * Dispatches a TabEvent.TAB_ITEMCLICK_ACTIVITY event containing the received MouseEvent
    */
    private function itemClickEventHandler(ev:ItemClickEvent):void
    {
      var tabEvent:TabEvent = new TabEvent(TabEvent.TAB_ITEMCLICK_ACTIVITY, ev);
      dispatchEvent(tabEvent);
    }

    /**
    * Dispatches a TabEvent.TAB_MOUSE_ACTIVITY event containing the received MouseEvent
    */
    private function mouseEventHandler(ev:MouseEvent):void
    {
      var tabEvent:TabEvent = new TabEvent(TabEvent.TAB_MOUSE_ACTIVITY, ev);
      dispatchEvent(tabEvent);
    }

Putting it all together – main.mxml

Now we have our extended class, we need to put it into action. Our application begins first by setting up some basic data to act as the dataProvider for our ExtendedTabNavigator. This is simply an ArrayCollection of generic Objects that each have a pageName property.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
  xmlns:mx="http://www.adobe.com/2006/mxml"
  layout="vertical"
  xmlns:components="components.*"
  width="553"
  height="320"
  creationComplete="creationCompleteHandler();"
  viewSourceURL="srcview/index.html"
  >

  <mx:Style source="/assets/styles/styles.css" />

  <mx:Script>
    <![CDATA[

    import mx.controls.Button;
    import mx.events.ItemClickEvent;
    import events.TabEvent;
    import mx.controls.Text;
    import mx.containers.VBox;
    import mx.collections.ArrayCollection;

    /**
    * An array of some basic data: objects with a pageName property
    */
    [Bindable]
    private var pages:ArrayCollection = new ArrayCollection([
      {pageName:"Page 1"},
      {pageName:"Page 2"},
      {pageName:"Page 3"},
      {pageName:"Page 4"},
      {pageName:"Page 5"}
    ]);

Next we have our creationCompleteHandler() function which is called when the application build has completed. This simply loops through our pages ArrayCollection, creates a new VBox using the createPage() function and adds that VBox to our ExtendedTabNavigator. It then adds event listeners for the two different types of TabEvent:

    /**
    * Sets up the example by calling the createPage() function for every
    * object in the pages Array, and adds event listeners
    */
    private function creationCompleteHandler():void
    {
      for each (var page:Object in pages)
      {
        var vBox:VBox = createPage(page.pageName);
        tabs.addChild(vBox);
      }

    addEventListener(TabEvent.TAB_ITEMCLICK_ACTIVITY, tabEventItemClickHandler);
    addEventListener(TabEvent.TAB_MOUSE_ACTIVITY, tabEventMouseHandler);

    }

Below this we have a createPage() function, whose job is to create a VBox with a Text object inside it. This is called by our creationCompleteHandler() function.

    /**
    * Creates a VBox with a Text object
    */
    private function createPage(pageName:String):VBox
    {
      var vBox:VBox = new VBox();
      vBox.label = pageName;

      var text:Text = new Text();
      text.text = pageName;

      vBox.addChild(text);
      return vBox;
    }

The last functions in our Script block are the event handler functions that are called by the event listeners. Both accept a TabEvent and then update the text property of one of the TextAreas in our example so we can see that an event has been received:

    /**
    * Responds to a TabEvent by sending the event details to the mouseEventDetails TextArea
    */
    private function tabEventMouseHandler(ev:TabEvent):void
    {
      var mouseEvent:MouseEvent = ev.event as MouseEvent;
      mouseEventDetails.text = mouseEvent.type + " on " + Button(mouseEvent.target).label;
    }

    /**
    * Responds to a TabEvent by sending the event details to the itemClickEventDetails TextArea
    */
    private function tabEventItemClickHandler(ev:TabEvent):void
    {
      var itemClickEvent:ItemClickEvent = ev.event as ItemClickEvent;
      itemClickEventDetails.text = "Clicked on " + itemClickEvent.label.toString();
    }

    >
  </mx:Script>

All that’s left is to add our MXML components to the view and these are simply a couple of Text elements to label up what we’re looking at, an instance of our ExtendedTabNavigator class, and two TextArea components that display the events being received. Now when you perform a mouseover, mouseout, mouseup, mousedown or click event on any of the tabs, our TextAreas show us exactly what’s happening.

<mx:Text
  text="Intercepting MouseEvents in a Flex TabNavigator"
  />

<components:ExtendedTabNavigator
  id="tabs"
  width="100%"
  height="75"
  />

 <mx:Text text="ItemClickEvent Details" />

 <mx:TextArea
 id="itemClickEventDetails"
 width="100%"
 />

<mx:Text text="MouseEvent Details" />

<mx:TextArea
  id="mouseEventDetails"
  width="100%"
  />

</mx:Application>

4 Responses to “Intercepting tab events in a Flex TabNavigator”

  1. Jonecir

    24. Aug, 2009

    Very simple, clean and efective sample.

    Reply to this comment
  2. Frank

    17. Sep, 2009

    Worked a treat, why didn’t they do it like that in the first place?

    Reply to this comment
  3. Dhanraj

    23. Oct, 2009

    Very nice and effective example

    Reply to this comment
  4. Elliot

    21. Feb, 2010

    Brilliant, thanks for making this a little easier for me.

    Reply to this comment

Leave a Reply