Detect when a component is added in a State

Problem:

You have some custom initialization that you need to perform every time a component is added using a State, such as setting focus in a particular field. You need a reliable method of determining that the component has been added.

Solution:

There are two solutions that I've found to work in this case. This first is to add a new public method to the component called activate() and then call it from a handler on the enterState Event. The second is to listen to the addedToStage and updateComplete events, set a flag in addedToStage that you check in updateComplete to run your initialization.

Here is the code, example app, and more detailed explanation.

You can view the example on a separate page and view the source (View Source doesn't work in the embedded SWF below due to a bug in Flex Builder).

Example SWF should load here if you have JavaScript enabled.

Explanation:

The first solution is easy and should be solid. The enterState Event fires after the component has been added to the stage (not during the operation), so it is safer to access subcomponents. The method, however, does not guarantee that the component has been fully updated yet. The downside to this is adding more coupling between the State and the component.

  1.     <mx:State name="solution1">
  2.       <mx:enterState>
  3.         sol1.activate();
  4.       </mx:enterState>
  5.       <mx:RemoveChild target="{noState}"/>
  6.       <mx:AddChild>
  7.         <local:Solution1VBox id="sol1"/>
  8.       </mx:AddChild>
  9.     </mx:State>

The code in the component is equally obvious:

  1.       public function activate():void
  2.       {
  3.         ti.setFocus();
  4.       }
  1.   <mx:Label text="Solution 1"/>
  2.   <mx:TextInput text="No focus here!"/>
  3.   <mx:TextInput id="ti" text="Focus here!"/>

The second solution relies on the behavior of the State AddChild operation. AddChild is true to its name, it will add and remove the component from the display list. This means that the added, addedToStage, removed, and removedFromStage methods will fire when the state is entered and exited. The trouble is that the component has not been fully created when these events fire. The solution I present here is to set a flag when the component has been added to the stage so that the component knows that it needs to run its activation code again:

  1.       protected var addedToStage:Boolean = false;
  2.       protected function addedToStageHandler(event:Event):void
  3.       {
  4.         if (event.target == this) {
  5.           addedToStage = true;
  6.         }
  7.       }

The next part of the solution uses the updateComplete event to trigger the activation code. If the addedToStage code has run since the most recent update, then we run the activation code.

  1.       protected function updateCompleteHandler(event:Event):void
  2.       {
  3.         if (addedToStage) {
  4.           ti.setFocus();
  5.           addedToStage = false;
  6.         }
  7.       }

Note that updateComplete fires every time the component is invalidated through something like invalidateDisplayList(). I put a button in the component so you can trigger updateComplete. If you remove the check for addedToStage, then invalidating the component will run the activation code again, which is not the desired behavior. See the article on updateComplete for more information about this event.

  1.   <mx:Label text="Solution 2"/>
  2.   <mx:TextInput text="No focus here!"/>
  3.   <mx:TextInput id="ti" text="Focus here!"/>
  4.   <mx:Button label="Invalidate Component" click="invalidateDisplayList()"/>

I'm not sure which solution I like better. The first is more direct and should be more resistant to changes in implementation or functionality. If you move from using AddChild to toggling visibility, then you can still call the activate() method from the enterState Event. However, this type of change will break the second solution.

Ultimately, there isn't a single, reliable event that will tell your component it has now become "viewable" so you have to code up individual solutions. If you think further about things like hiding a component by shrinking its width to 0 there just isn't a solution that would always detect everything.