Lately I‘ve been thinking of trees. Maybe it‘s because it is autumnin the northern hemisphere. So it seems an appropriate time to talkabout Flex Tree controls. I‘m going to publish a small series on theFlex 2.0 Tree component as there is a lot of ground to cover. In thisseries I‘ll present information on Tree dataProviders, itemRenderers,and drag-and-drop.
The Tree usesheirarchical data. That is, data that has levels: nodes that arebranches and leaves. The DataGrid, by contrast, presentsnon-heirarchical data as does the List control.
XMLListCollectionis an excellent dataProvider for the Tree and is the most commonly usedclass for this purpose. You can also use ArrayCollection and I‘ll gointo more detail on that later in this article.
XML is a good because it is, by nature, heirarchical. Consider this simple XML:
[Bindable]
var company:XML =
<node>
<node label="Finance" dept="200">
<node label="John H" />
<node label="Sam K" />
</node>
<node label="Engineering" dept="300">
<node label="Erin M" />
<node label="Ann B" />
</node>
<node label="Operations" dept="400" isBranch="true" />
</node>
Youcan easily see the Tree structure in that XML. But also noticesomething else: all of the elements in the XML are <node />elements. Every element is the same in that it has the name "node" andit has a label; branches and leaves as well. If you need to have a nodethat should be a branch but has no leaves, use isBranch="true" to tellthe Tree the node should be treated like a branch.
The Treelikes this very much. You can give that XML to the Tree and you willsee that structure without any fuss. But is this realistic to ask thatall of the nodes in the data be the same? Consider this XML:
[Bindable]
var company:XML =
<list>
<department title="Finance" code="200">
<employee name="John H" />
<employee name="Sam K" />
</department>
<department title="Engineering" code="300">
<employee name="Erin M" />
<employee name="Ann B" />
</department>
<department title="Operations" code="400" isBranch="true" />
</list>
ThisXML is not uniform. If you give this to a Tree component you will getthe XML dumped in the control because the Tree doesn‘t know what to dowith it without extra instructions.
This XML can be displayed in the Tree with the help of a labelFunction.
private function treeLabel( item:Object ) : String
{
var node:XML = XML(item);
if( node.localName() == "department" )
return node.@title;
else
return node.@name;
}
The labelFunction simply returns the value of the name or title attribute depending on the type of node it is.
EarlierI mentioned XMLListCollection as a good dataProvider for the Tree, butI‘ve been using XML so far. Here is the proper way to supply the Treewith data:
[Bindable]
var companyList:XMLListCollection = new XMLListCollection( company.department );
...
<mx:Tree dataProvider="{companyList}" labelFunction="treeLabel" />
AnXMLListCollection is better as a dataProvider because the Tree canmanipulate it (for editing, drag-and-drop, etc.) and because changes tothe XMLListCollection are reflected in the Tree. That is, if I were tochange the company XML object, you would not see the change in theTree. If change the XMLListCollection, companyList, then not only wouldthe underlying XML be changed, but so would the Tree.
UseXMLListCollection to supply the Tree with its data; you can change thecollection and both the Tree and underlying XML will get changed, too.
If you cannot supply a very uniform XML structure to the Tree, use alabelFunction (or itemRenderer, but that‘s coming later) to supply thelabel for the display.
Youcan also use an ArrayCollection for the Tree. You can make anArrayCollection heirarchical by embedding one ArrayCollection insideanother:
[Bindable]
private var companyData:ArrayCollection = new ArrayCollection(
[ {type:"department", title:"Finance", children:new ArrayCollection(
[ {type:"employee", name:"John H"},
{type:"employee", name:"Sam K"} ] ) },
{type:"department", title:"Engineering",children: new ArrayCollection(
[ {type:"employee", name:"Erin M"},
{type:"employee", name:"Ann B"} ] ) },
{type:"department", title:"Operations",children: new ArrayCollection()}
] );
With this structure you‘ll notice that whenever a node has children, the name of the field is children - the Tree looks for this to identify branches from leaves.
You will also need a labelFunction with this data, too, so the Tree knows what to display on each node:
private function treeLabel( item:Object ) : String
{
if( item.type == "department" )
return item.title;
else
return item.name;
}
Youmake changes to the Tree through the dataProvider, not through the Treecontrol itself. When you want to add a node you add it to thedataProvider and the Tree will be changed automatically.
Toadd a node to the XMLListCollection you need to have a handle on theparent node. For example, to add a new department which is a top-levelnode, you can do that like this:
var newNode:XML = <department title="Administration" code="500" >
<employee name="Mark C" />
</department>;
companyList.addItem(newNode);
Here is how to add a new employee to the existing Operations department:
var newNode:XML = <employee name="Beth T" />;
var dept:XMLList =company.department.(@title == "Operations");
if( dept.length() > 0 ) {
dept[0].appendChild(newNode);
}
Once you identify a specific node and have its XML equivalent, you can use the appendChild() method.
Toadd a node to the ArrayCollection you just append it to whatever partof the structure requires the node. Here‘s how to add a new department(top-level) node:
var newNode:Object = {type:"department",title:"Administration"};
var newEmployee:Object = {type:"employee",name:"Mark C"};
newNode.children = new ArrayCollection( [newEmployee] );
companyData.addItem(newNode);
Here is how to add a new employee to the existing Operations department:
var newNode:Object = {type:"employee", name:"Beth T"};
for(var i:Number=0; i < companyData.length; i++) {
var item:Object = companyData.getItemAt(i);
if( item.title == "Operations" ) {
var children:ArrayCollection = item.children;
children.addItem(newNode);
companyData.itemUpdated(item);
empName.text = "";
break;
}
}
As you can see, using an ArrayCollection to add a node is a bit more complicated than using XML.
Ifyou know you are removing a top-level node you can do that through theXMLListCollection‘s removeItemAt() method - but you have to know theindex of the item. In the following example, all you know is the name,"Operations", so you have to loop through the nodes and when a match isfound, remove the item.
var deptTitle:String = "Operations";
for(var i:Number=0; i < companyData.length; i++) {
var item:XML = XML(companyData.getItemAt(i));
if( item.@title == deptTitle ) {
companyData.removeItemAt(i);
break;
}
}
Removing the selected top-level node little easier:
var index:Number = tree.selectedIndex;
companyData.removeItemAt(index);
Here is how to remove a leaf node:
var node:XML = XML(tree.selectedItem);
if( node == null ) return;
if( node.localName() != "employee" ) return;
var children:XMLList = XMLList(node.parent()).children();
for(var i:Number=0; i < children.length(); i++) {
if( children[i].@name == node.@name ) {
delete children[i];
}
}
Thesame technique applies for ArrayCollection in that you have to searchfor the item, but once you find it you can use the ArrayCollection removeItemAt() method since the index is always valid for an ArrayCollection.
Here‘s how to remove a leaf node if the Tree‘s dataProvider is an ArrayCollection:
var node:Object = tree.selectedItem;
if( node == null ) return;
if( node.type != "employee" ) return;
var children:ArrayCollection = node.parent().children() as ArrayCollection;
for(var i:Number=0; i < children.length; i++) {
if( children[i].name == node.name ) {
children.removeItemAt(i);
break;
}
}
tis not possible to give a Collection a node of the Tree data and havethe Collection remove it - you must hunt for it and delete it yourself.
The next article will cover drag and drop - within a tree, from a tree, and onto a tree. Stay tuned.
聯(lián)系客服