How To Use An ArrayCollection As The Data Provider For The Flex 2 Tree Control

Flex 2 provides a Tree control that can be used to display hierarchical data. The examples of using the Tree control in the Flex 2.0 Developer's Guide use XML as the data provider. However, I needed to populate a Tree control using data pulled from a database. The data provider for a Tree Control can be either XML (including an XMLList and XMLListCollection), an array of items, an object that contains an array, or an ArrayCollection. I decided to use an ArrayCollection rather than XML as I figured it would be simpler to create the ArrayCollection using data from my database rather than create the complex XML.

However, I found very few examples of using an ArrayCollection as the data provider for the Tree control. So after I figured out how to get the ArrayCollection to work, I decided to post what I learned to hopefully help others. A demonstration application is located here: /flex/consultantTree/consultantTree.html (right click on the app to view the source).

References

  1. Flex 2 Developer's Guide - Tree Control; http://livedocs.macromedia.com/flex/2/docs/00000600.html#397087
  2. Flex 2 Language Reference - Tree Control; http://livedocs.macromedia.com/flex/2/langref/mx/controls/Tree.html
  3. Working With Tree Controls, http://www.adobe.com/devnet/flex/quickstart/working_with_tree/
  4. Using a Hierarchy of Components to Populate a Flex Tree Control,
    http://cyberdust.wordpress.com/2006/09/29/using-a-hierarchy-of-components-to-populate-a-flex-tree-control/
  5. Building Flex Tree MXML From ColdFusion Queries, Ben Forta,
    http://www.forta.com/blog/index.cfm/2006/8/24/Building-Flex-Tree-MXML-From-ColdFusion-Queries (note this reference provides an example of creating complex XML using results from a database query)

Creating The ArrayCollection

To better follow the discussion of the ArrayCollection used as the Tree's data provider, you may want to open the demonstration application and play with it. Seeing the data displayed in the Tree may be helpful in understanding what makes up the ArrayCollection.

My Flex application calls a CFC function named getAllConsultants, which is in the ConsultantGateway CFC. This function returns an array of Consultant objects (see Consultant.cfc). A Consultant has several attributes, one of which is named children. The children attribute is an array that stores Area objects (see Area.cfc). Areas represent a consultant's areas of expertise. A consultant will have 1 or more areas of expertise. So the children attribute of the Consultant CFC stores all the Area objects for that consultant.

Why is the attribute named children? Good question. If you review pages 415-416 of the Flex 2 Developer's Guide, you will see that the object that contains an array of items that you want to display in the tree, must be named children. So as the Tree control processes each Consultant object in the ArrayCollection, it will create a branch under the Consultant for each Area stored in the Consultant's children attribute.

Each Area object stored in the Consultant's children array also has an attribute named children that is an array. Stored in this children array are one or more Role objects (see Role.cfc). For each area of expertise a consultant has, the consultant is willing to perform one or more different roles. As the Tree control processes each Area, it will create a branch below the Area for each Role stored in the children object.

So the array returned by the function getAllConsultants has a hierarchy of objects: Consultant - Array of Area objects (named children) - Array of Role objects (named children). I use this array to create the ArrayCollection that is the Tree's data provider (see function handleGetAllConsultants in the consultantTree.mxml file).

Side Note: When you are going to return CFC objects to Flex, make sure you specify a CFProperty tag for each CFC attribute. In the CFProperty tag be sure to set a value for type.

Creating The Tree Control

The ArrayCollection is specified as the Tree control's data provider. Additionally, I needed to use the labelFunction attribute of the Tree control. This attribute specifies a function to call to determine what to show as the "label" in the Tree control. If you examine the treeLabel function you'll notice that I have a series of if statements to determine which object field's value to return for the label. When the Tree control calls this function it passes an Object representing the current branch or leaf. So if this object has a fullName field (the fullName field is not null), then the value stored in the fullName field is what should be used for the label, else if the object has a description field then use the value stored in the description field for the label.

Manipulating The Tree Control

The ArrayCollection is also the data provider for a DataGrid control. When the user selects a row in the DataGrid, Flex calls the dgChangeHandler function. In function dgChangeHandler I use a property and a method of the Tree control to change the Tree control's appearance.

The Tree control property firstVisibleItem can be used to set which Tree item is displayed in the top row of the tree. You set this property equal to the object of the ArrayCollection that you want to be the top row of the Tree. I just use the ArrayCollection's getItemAt method to get the item the user selected in the DataGrid.

The Tree control method expandItem is used to open a branch so its children are visible. This method takes an argument that identifies which item in the ArrayCollection represents the item the Tree control should expand.

Conclusions

After completing this work, I think it may have been easier to have ColdFusion create the XML rather than a complex array of objects. Having to use "children" for the name of an attribute of my CFC just makes me uncomfortable. I want my attribute names to represent what is being stored (eg lastName, email). I did like being able to leverage my CFC objects as part of the solution.

My reference number 5 above (Ben Forta's blog entry) describes how to create a complex XML file from a flat database query result. His solution may be something I try the next time I need to populate a Tree control using information extracted from a database.

Additionally, my solution uses too many queries and creation of CFC objects. If I had several hundred consultants, the time lag it would take to get the data into Flex would not be acceptable. Right now I am only pulling in 40 consultants (there are approximately 200 queries and 800 objects being created to get all the areas and roles for these consultants) and it is taking approx. 15-30 seconds to pull the data from CF into Flex. Clearly, my solution is not going to scale well.

Lastly, there needs to be much better documentation for the Tree control. This control is one of more complex ones that Flex offers. However, few of the Tree control's properties and methods are explained well and there are only limited examples. I never could figure out how to close a Tree item (yes I see the documentation states that expandItem can close a branch, but I never got that to work.) Hopefully, the Flex books that will be published in 2007 will provide more complete examples of using the Tree control. If you know of a good Tree control example, please post a comment.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Thanks to "bod" at the FlexCoders Yahoo Group, I got the Tree open item to close:


   consultantTree.expandItem(consultantTree.openItems[0], false);

The openItems property is an array that contains a reference to each open item and the second argument of false tells expandItem to close it.
# Posted By Bruce | 1/3/07 5:40 AM
This is very unbelievable cool. Would you be able to tell me if something, hopefully much simpler, like this can be used with the XMLList and HTTPService to load a tree at runtime, as in the example I'm trying to get work here, Bruce:

http://shawngibson.com/DataProviderExternal/DataPr...

I am a newbie, but I'm trying to grasp what I suppose is a very complex subject.

Shawn
# Posted By shawn gibson | 3/6/07 10:46 AM
Check out the references above. There is an example of using XML as the data provider for a tree control in the Developer's Guide.
# Posted By Bruce | 3/6/07 1:24 PM
I'm new to flex and trying to get the code running. Can you post a copy of the database, maybe that's where I am getting lost.

Thanks!
# Posted By Lara | 10/3/07 11:16 AM
Lara - I'm sorry, but I cannot post a copy of the database as it has some proprietary information in it that I don't have time to clean out. Additional, this database was built by someone else and the schema is very messed up.

The key to using an array collection as the data provider is that each object in the array collection needs a children attribute. The children attribute is an array of child objects (in my example this is an array of areas of expertise objects).

Try building a simple example without a database backend (you can hard-code the Array Collection).
# Posted By Bruce | 10/3/07 2:11 PM
There seems to be a problem with multi-threading or something like that. When you select the first item in the table control, and then use the Down Arrow to change from one consultant to another, and keep the down arrow pressed all the way to the bottom, the tree is completely messed up.
I'm trying to implement a search in a tree, but search in all the nodes, not just the top nodes. When I try to reposition the top node, some times it does not work.
This control seems to be buggy.

Here's my search code:
public var xmllistDescendants:XMLList;
public function findNodeByText(sId:String):void
    {

    xmllistDescendants = myXMLObject.descendants().(@label.toString().search(sId) > -1);
   

    expandParents(xmllistDescendants[1]);
    schemaTree.selectedItem = xmllistDescendants[1];
    schemaTree.firstVisibleItem = xmllistDescendants[1];   
   
    }//findNodeById


]]>

Any ideas?
# Posted By Raramuri | 10/10/07 6:28 PM
Excellent post. I went at this a different way and chose to implement a custom tree descriptor. I have a main arraycollection that has all the objects in flat format, and some methods that pull specific objects such as pulling child objects for a given object. This has worked extremely well for me. Hopefully it helps someone else as well.

package Code.TreeDescriptors
{
   import mx.collections.ICollectionView;
   import mx.controls.treeClasses.ITreeDataDescriptor;

   import Code.DAO.*;
   import Code.Utils.*;
   import mx.collections.ArrayCollection;
   import mx.collections.Sort;
   import mx.collections.SortField;
   
   public class SiteMapTreeDescriptor implements ITreeDataDescriptor
   {

       // The getChildren method requires the node to be an Object
    // with a children field.
    // If the field contains an ArrayCollection, it returns the field
    // Otherwise, it wraps the field in an ArrayCollection.
    public function getChildren(node:Object, model:Object=null):ICollectionView
    {
    try
    {
    var objSiteNode:SiteNode = node as SiteNode;
   
    var aryChildren:ArrayCollection = SiteNodeUtils.Instance.GetChildrenOfNode(objSiteNode);

    return aryChildren;
   
    }
    catch (e:Error) {
    trace("[Descriptor] exception checking for getChildren");
    }
    return null;
    }
   
      
      // The hasChildren method Returns true if the node actually has children.
    public function hasChildren(node:Object, model:Object=null):Boolean
    {
    return isBranch(node, model);
    }
      
    public function isBranch(node:Object, model:Object=null):Boolean
    {
    try {
    var objSiteNode:SiteNode = node as SiteNode;
       return (SiteNodeUtils.Instance.GetChildrenOfNode(objSiteNode).length>0);
    }
    catch (e:Error) {
    trace("[Descriptor] exception checking for isBranch");
    }
    return false;
    }
      
      // The getData method simply returns the node as an Object.
      public function getData(node:Object, model:Object=null):Object
      {
         try {
    return node;
    }
    catch (e:Error)
    {
    }
    return null;
      }
      
       // The addChildAt method does the following:
    // If the parent parameter is null or undefined, inserts
    // the child parameter as the first child of the model parameter.
    // If the parent parameter is an Object and has a children field,
    // adds the child parameter to it at the index parameter location.
    // It does not add a child to a terminal node if it does not have
    // a children field.
      public function addChildAt(parent:Object, newChild:Object, index:int, model:Object=null):Boolean
      {
         return false;
      }
      
      // The removeChildAt method does the following:
    // If the parent parameter is null or undefined, removes
    // the child at the specified index in the model.
    // If the parent parameter is an Object and has a children field,
    // removes the child at the index parameter location in the parent.
      public function removeChildAt(parent:Object, child:Object, index:int, model:Object=null):Boolean
      {
         return false;
      }
      
   }
}
# Posted By JTtheGeek | 4/4/08 6:13 PM
Bruce, I've been using your blog as a key reference to some things I am working on and I'm glad to say that I managed to get some fixes to few problems I encountered.

I have couple of questions, one directly related to this thread and one..umm..maybe not:

1. Does the tree component have any limitation on the amount of data that it can display? I mean, I am currently using a data provider (bound to the tree) that has thousands of objects and one of those objects has several thousand children. I am unable to view all the children when I add them to the particular child of the main parent. To make things clearer, I am putting forth the diagram below:

<parent 1>
<child 1>
<subchild 1>
<subchild 2>
<subchild 3>
.
.
.
.
.
.
<subchild 1X00>
</child 1>
<child 2>
.
.
.
.
<child 1X00>
</parent 1>

I am able to see <child 1> through <child 1X00> but unable to see till <subchild 1X00>. I do refresh the tree every time the data provider undergoes an update (the data provider being an XMLListCollection but then, I am assuming the same issue might happen if it were to be replaced by an ArrayCollection as well.)

2. I am getting this error message if I were to deploy the files on a WebSphere Application Server 5.1 with JDK 1.4.1 using Spring 2.5 and Hibernate as the framework for communicating with Java:

"MessageBrokerServlet failed to initialize due to runtime exception: flex.messaging.config.ConfigurationException: The minimum required Java version was not found. Please install JDK 1.4.2 or above. Current version is 1.4.1."

Is there any way to counter this situation without making the upgrade for JDK? I understand this solution is simple but for me, it is not a realistic possibility due to certain reasons.

I would be grateful if anyone could provide solutions to these.
# Posted By Mohan | 6/26/08 2:58 AM
Hi Bruce,

Thanks for your very detailed article as well as the example with the code. I just tried to work with
tree control (flex 3) with my data also being fetched from the database in an array collection.

So your sample code did help me a lot. As far as naming the children attribute goes, in Flex 3, there
is the option of writing your own custom class descriptor where in you can use your overridden names.

This is described in pages 184-188 of the Flex 3 developers guide. This is especially useful when your backend
code is not in your control. With this , using either array or xml for heirarchical data is pretty much the
same.

Thanks a ton anyways.
# Posted By Deepak Surti | 1/2/09 7:53 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1.002. Contact Blog Owner