Solutions

Solutions to Flash and Flex problems and questions.

AdvancedDataGrid highlights wrong row

Problem:

You have an AdvancedDataGrid which is highlighting the wrong row when you navigate using the mouse or keyboard.

Solution:

It is likely that you have put duplicate data in the dataProvider for your AdvancedDataGrid. It is illegal to put the same instance anywhere in the hierarchy of data that you assign to the dataProvider property. The regular DataGrid also has this limitation, which you can read about in the DataGrid highlights wrong row Solution. You can objects with identical data, but they must be different instances of the same object.

The only solution is to create unique objects for any data you want to display in an AdvancedDataGrid.

Further explanation, along with a SWF example and source code:

Example:

You can clearly see the incorrect behavior here. View Source is enabled and you can view this in its own page AdvancedDataGrid highlights wrong row example. Roll over the first child row and the AdvancedDataGrid will highlight the third child row:

Example SWF should load here if you have JavaScript enabled.

The data for this grid is defined here. Notice that obj1 is a child of both par1 and par2.

  1.         var array:Array = [];
  2.         var obj1:Object = {title:"The Ham", author:"Harold James"};
  3.         var obj2:Object = {title:"Hoodilly's", author:"Harold James"};
  4.         var obj3:Object = {title:"Kill-a-man-jaro", author:"Chuck Johnson"};
  5.  
  6.         var par1:Object = {category:"Fiction", children:[obj1, obj2]};
  7.         var par2:Object = {category:"Crime", children:[obj1, obj3]};
  8.         array.push(par1, par2);
  9.         dupData = new HierarchicalData(array);
  10.         dupData.childrenField = "children";

Explanation:

The explanation is identical to DataGrid. The AdvancedDataGrid indexes its data using a UID that is created for each row of data in the dataProvider. This code is actually in the HierarchicalCollectionView class which AdvancedDataGrid uses to manage its hierarchical data and present a view of the data to the user. The HierarchicalCollectionView gets the UID for each row by calling UIDUtil.getUID(item:Object). The UID returned is a String. See the entry on UIDUtil for more information on the generation method. This method will return the same UID when it is passed the same object.

HierarchicalCollectionView stores open nodes by UID in the openNodes property which is publicly accessible. It also stores the parents of nodes in an internal property (also defined as an Object which you can access through public function getParentItem(node:Object):*. This method converts the node:Object into a UID using UIDUtil and then looks up the parent in its internal property.

The situation is also similar when you try to highlight a row in the AdvancedDataGrid.

DataGrid highlights wrong row

Problem:

You have a DataGrid which is highlighting the wrong row when you navigate using the mouse or keyboard.

Solution:

It is likely that you have put duplicate data in the dataProvider for your DataGrid. It is illegal to put the same instance into the collection of data that you assign to the dataProvider property. You can create duplicate objects, but they must be different instances of the same object. It is valid if the fields are identical, but not the objects themselves.

The only solution to this is to create unique objects for your DataGrid.

Further explanation, along with a SWF example and source code:

Example:

You can clearly see the incorrect behavior here. View Source is enabled and you can view this in its own page DataGrid highlights wrong row example. Roll over the first row and the DataGrid will highlight the third row:

Example SWF should load here if you have JavaScript enabled.

This is the definition of the data. Notice that obj1 is inserted into the array twice:

  1.         var array:Array = [];
  2.         var obj1:Object = {title:"The Ham", author:"Harold James"};
  3.         var obj2:Object = {title:"Hoodilly's", author:"Harold James"};
  4.         var obj3:Object = {title:"Kill-a-man-jaro", author:"Chuck Johnson"};
  5.         array.push(obj1, obj2, obj1, obj3);
  6.         dupData = new ArrayCollection(array);

Explanation:

The DataGrid indexes its data using a UID that is created for each row of data in the dataProvider. The DataGrid gets the UID for each row by calling UIDUtil.getUID(item:Object). The UID returned is a String. See the entry on UIDUtil for more information on the generation method.

The DataGrid and its associated classes keep several hashes that are indexed by the UID of the row. These are instances of Object that contain the UIDs. For example, ListBaseContentHolder has a visibleData hash that maps the row to item renderer when the row is visible. ListBase (parent class of DataGrid) also stores the UIDs of objects such as the highlightUID and caretUID.

Clearly code such as this, in DataGridBase, can only return a single item renderer for each UID and therefore will behave incorrectly when your data contains duplicate instances:

  1.     override protected function UIDToItemRenderer(uid:String):IListItemRenderer
  2.     {
  3.         var r:IListItemRenderer = visibleData[uid];
  4.         if (!r)
  5.         {
  6.             if (lockedRowContent)
  7.                 r = lockedRowContent.visibleData[uid];
  8.         }
  9.         if (!r)
  10.         {
  11.             if (lockedColumnContent)
  12.                 r = lockedColumnContent.visibleData[uid];
  13.         }
  14.         if (!r)
  15.         {
  16.             if (lockedColumnAndRowContent)
  17.                 r = lockedColumnAndRowContent.visibleData[uid];
  18.         }
  19.         return r;
  20.     }

This isn't the only example, there is a great deal of code relating to lists that relies on this assumption.

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.

Filter HierarchicalCollectionView parent and child branches

Problem

You want to apply a filter function to a HierarchicalCollectionView to filter some data that is only in the parent or only in the children.

Solution

Any function applied to the filterFunction of HierarchicalCollectionView is applied to all of the elements in the collection. It can filter items at different levels of the hierarchy as long as the method itself understands the hierarchy.

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.

This is the definition of the AdvancedDataGrid. Nothing special here:

  1.   <mx:AdvancedDataGrid id="adg"
  2.     dataProvider="{hd}"
  3.     width="100%" height="100%">
  4.     <mx:columns>
  5.       <mx:AdvancedDataGridColumn
  6.         dataField="title"/>
  7.     </mx:columns>
  8.   </mx:AdvancedDataGrid>

The filter function is applied like this:

  1.         // Set the filter function and refresh the collection view.
  2.         adg.hierarchicalCollectionView.filterFunction = myFilter;
  3.         adg.hierarchicalCollectionView.refresh();

And it uses the CheckBoxes defined here to enable and disable its operation:

  1.     <mx:CheckBox id="tParents" label="Parents that start with 'T'" change="criteriaChanged()"/>
  2.     <mx:CheckBox id="fourChildren" label="Parents with at least four children" change="criteriaChanged()"/>
  3.     <mx:CheckBox id="adChild" label="Children named 'A' or 'D'" change="criteriaChanged()"/>

Finally, here is the filter code. This would be simpler if you have a single filter you need to apply, but the example shows some dynamic behavior. The filter should return true for any item that is included in the display, and false otherwise. Notice that if you return false for a parent item, then it and all of its children will be filtered out. If you return true for a parent item, then you can still filter its children.

  1.       protected function myFilter(item:Object):Boolean
  2.       {
  3.         var title:String = item.title.toString();
  4.  
  5.         // If the tParent filter is selected and this item it a parent
  6.         if (tParents.selected && item.parent) {
  7.           // Then fail if the name doesn't start with a "T".
  8.           if (title.substr(0,1).toUpperCase() != "T") {
  9.             return false;
  10.           }
  11.         }
  12.  
  13.         // If the fourParent filter is selected and this item is a parent
  14.         if (fourChildren.selected && item.parent) {
  15.           // Then fail if the parent doesn't have at least four children.
  16.           if (item.children.length < 4) {
  17.             return false;
  18.           }
  19.         }
  20.  
  21.         // if the adChild filter is selected and this is a child (not a parent)
  22.         if (adChild.selected && !item.parent) {
  23.           // Then fail if the child is not an "A" or a "D"
  24.           if (title.toUpperCase() != "A" && title.toUpperCase() != "D") {
  25.             return false;
  26.           }
  27.         }
  28.  
  29.         // Either all filters are disabled, or they all passed (or don't apply to this node)
  30.         return true;
  31.       }

To filter a parent based on the data in its children, you simply have to process the children when the filter is asked about the parent. There must be some way for you to perform this operation on the parent. In this case, I have simply added a property to the parent object called parent and set it to true. There is nothing special about this property. You could also check the children property for null or check its length. In many cases, you will have a typed object that is the parent that is different than the type of the children.

Explanation

HierarchicalCollectionView applies the filterFunction to the main data source and also to all of the open nodes in the hierarchy. When you apply a filter function and then refresh the list (don't forget the filter isn't applied until you call refresh()), it loops through all the open nodes and sets the filterFunction property and calls refresh() on each of them. Closed nodes are not processed until they are open. When the HierarchicalCollectionView opens a node, it applies the filterFunction set on itself to the node. This will wipe out any filters you may have tried to set yourself, which is one reason why you can only have a single filter on the entire hierarchy.

NOTE: I can't redistribute the source code to the Flex Data Visualization package, so I was not able to bring in source code for HierarchicalCollectionView.

How to check for unset style properties

Problem:

You have a component which supports a style and you want to check if the style has been set in a style sheet or on the component directly.

Solution:

When you get the value of the style, you need to store it in an untyped variable. This will allow you to check for undefined. If you store the value in an int, uint or Boolean, for example, it will be converted from undefined to the default value of that type (0, 0, or false, respectively).

Here is a simple implementation, if you don't mind losing your type.

var myStyle:* = getStyle("myStyle");
if (myStyle == undefined) {
 myStyle = 42; // appropriate default
}

Or if you're picky, you can make two variables:

var myStyleUntyped:* = getStyle("myStyle");
var myStyle:int = 42; // appropriate default
if (myStyle != undefined) {
 myStyle = myStyleUntyped;
}

Of course, there are better mechanisms for setting style defaults, and you should probably use one of those instead of hard coding the default value when you access the style (which you may have to do repeatedly).

You could also store the value in a variable typed Object. In my testing, I found that this stores null instead of undefined because an Object cannot be undefined. Only an untyped variable or an nonexistent property can be undefined.

Explanation:

Actionscript has strict rules about what values can be assigned to variables of specific types. The undefined and null values, in particular, cannot be assigned to many of the basic types. Only an untyped variable can be undefined and only variables that represent classes which are descendants of Object can be null. Numeric variables such as int and uint always have a value. The Number type can be NaN, but is never null or undefined. The String type can be null, but not undefined. The Boolean type must always be true or false and is never null or undefined.

These rules help limit the number of edge cases in the language and the impact of uninitialized variables, but can also be confusing.

PopUpButton failing inside ViewStack container

Problem

You have a PopUpButton that is failing with a null error unexpectedly.

Solution

Reinitialize the popUp property of the PopUpButton every time it receives the addedToStage event. You can save a reference to the class in your parent component.

Explanation

The PopUpButton performs some cleanup when it receives the removedFromStage handler because it assumes that it is done for business. However, sometimes the Container class will cause this behavior at runtime unexpectedly. The PopUpButton will be immediately placed back on the Stage, but it has already destroyed its popUp component and thrown away its reference.

  1.     private function removedFromStageHandler(event:Event):void
  2.     {
  3.         // Ensure we've unregistered ourselves from PopupManager, else
  4.         // we'll be leaked.
  5.         if (_popUp) {
  6.             PopUpManager.removePopUp(_popUp);
  7.             _popUp = null;
  8.         }
  9.     }

This wouldn't be such a problem, but the PopUpButton has no way of recovering this component and most of the methods do not check if the popUp property is null. For example, the private function displayPopUp(show:Boolean):void method checks if popUp is null if show is true, but not if it is false:

  1.     private function displayPopUp(show:Boolean):void
  2.     {

...

  1.         if (show)
  2.         {
  3.             if (getPopUp() == null)
  4.                 return;

...

  1.         }
  2.         else
  3.         {
  4.             point = _popUp.parent.globalToLocal(point);

BOOM.

Removed from stage handler called unexpectedly

Problem

You have a handler attached to the removedFromStage event on your component and this handler is being called unexpectedly. Sometimes called during component initialization and sometimes during resizing.

Solution

The removedFromStage event is dispatched when a component is removed from another component which is on the Stage. This can occur in Flex due to an implementation detail in the Container class. The Container class is the parent class for Canvas, VBox, HBox and Tile. In certain instances, the Container class will decide that it needs to move all of its children into a special child called the contentPane. When this re-parenting operation is performed, the Event.REMOVED_FROM_STAGE event will be dispatched to the child components.

More information about this process can be found in the Flex 3 Anatomy page on Container in the sub-section explaining the contentPane property.

Skipping super.data = value in item renderer when value is null

Problem

I am seeing some runtime errors (RTEs) with the data or listData properties when I use my DataGrid or AdvancedDataGrid with a custom item renderer.

Solution

Verify that your code always preserves the value that the grid sets data and listData to in your setter.

Explanation

This is critical because the data grids assume that when they set the data and listData properties that their value will be retained. If you are overriding an IDropInListItemRenderer these components assume that the data and listData properties will stay synchronized.

This can happen for several reasons, two of which I have seen implemented or implemented myself. The first reason is that you expect data to be a particular class and you are checking this assumption. If the value passed in is not the right type, then you store a null instead of the passed in value (or ignore the new value).

protected var _mydata:MyType;
override public function set data(value:Object):void
{
  if (value is MyType) {
    super.data = value;
    _mydata = value;
  } else {
    super.data = null;
    _mydata = null;
  }
}

The solution is for this code to always set super.data regardless of the type of value

protected var _mydata:MyType;
override public function set data(value:Object):void
{
  super.data = value;
  if (value is MyType) {
    _mydata = value;
  } else {
    _mydata = null;
  }
}

A similar problem can occur if you are checking for null and not updating the super.data value. Just remember, always store and report the actual value that data is set to, even if you will decide to ignore it in your renderer. This is important for both fully custom item renderers (when you subclass UIComponent for example) and when you subclass an existing item renderer (such as Label, which implements IDataRenderer, IDropInListItemRenderer, and IListItemRenderer).

Using roll over color on DataGrid without selection

Problem

You need to have a DataGrid which needs to show a roll over highlight but is not selectable.

Solution

Set selectable to true and override public function isItemSelectable(data:Object) in DataGrid and return false.

Explanation

The mouseOverHandler for DataGrid is implemented in ListBase. The first lines of code are:

  1.     protected function mouseOverHandler(event:MouseEvent):void
  2.     {
  3.         var evt:ListEvent;
  4.        
  5.         if (!enabled || !selectable)
  6.             return;

This presents a problem. The subsequent code tests that the userRollOver style is set and the data is not null. Two solutions present themselves. Either override the mouseOverHandler and remove this check, or set selectable and override other code which looks at selectable.

It turns out that there is a short, public function called isItemSelectable(data:Object) that is trivial to override. It checks selectable for true and data for null. We can simply override it and return false.