I have been playing around with Flex on and off based on my project requirements. Recently I ran into a problem; that is to build a Dynamic Tree which does lazy loading for performance. I looked around the internet to see how others have done it. I found number of examples that showed how to build a Tree in flex using a XML file. It works great if we have a static XML. But in my case I wanted to build a tree that dynamically builds itself based on a dynamic XML it gets from the server.
So, Here is how approached it.
Solution in Brief:
1. Servlet: I created a servlet that returned an XML file with the Nodes for requested branch.
2. Flex Component: Flex would call the servlet to get the XML data and render a tree node based on that XML. As user would click on nodes Flex would make calls to the server to get the XML data and append it to the Node as children.
Solution defined:
Here is a quick look at our finished Tree:
1. Servlet: Since I am focusing on the Flex stuff here, I am going to describe the servlet part briefly.
Here if we hit the servlet for the first time it should give us a XML that represents the First Nodes. In this case something like:
<?xml version="1.0" encoding="UTF-8" ?> <TREEDATA> <NODE ID="cars" LABEL="Cars" HASCHILDREN="true" /> </TREEDATA>
As we hit the server with the id we get the children node xml for instance, here if we request the server with id=”cars” we get xml like:
<?xml version="1.0" encoding="UTF-8" ?> <TREEDATA> <NODE ID="acura" LABEL="Acura" HASCHILDREN="true" /> <NODE ID="lexus" LABEL="Lexus" HASCHILDREN="true" /> <NODE ID="saturn" LABEL="Saturn" HASCHILDREN="false" /> </TREEDATA>
<mx:HTTPService id="getCarInfo" url = "http://localhost:8080/Demo/getTreeData" result="treeDataResultHandler(event)" resultFormat="e4x" showBusyCursor = "true"/> <mx:Tree id="carTree" dataProvider="{treeData}" dataDescriptor="{new MyCustomDescriptor()}" labelField="@LABEL" itemOpening="loadChildren(event)" width="434" x="10" y="10" height="266"/>
private function treeDataResultHandler(event: ResultEvent):void{ //Get the XML Data from the server var xmlResult:XML = new XML(event.result); var clickedNode:XML = nodeMap[event.token.message.messageId]; //if we find that result was for a clicked node then we append the resulting node as children //else we know its first call then we just add all at the main level. if (clickedNode != null){ clickedNode.appendChild(xmlResult.NODE); treeData.itemUpdated(clickedNode); } else { for each(var nodeElement:XML in xmlResult.NODE) treeData.addItem(nodeElement); } }
I hope the comments in the code should explains itself.
One of the interesting thing to look at is the mx:Tree component. We define the dataDescriptor=”{new MyCustomDescriptor()}”
The Code is pasted below. We define the label for the node in the tree by doing: labelField=”@LABEL”. This makes the LABEL attribute form the XML to show up as a node Label of the tree.
The MAGIC happens in this code itemOpening=”loadChildren(event)” . When flex tries to open a children, this function will be called, lets see what it does below:
private function loadChildren(event:TreeEvent):void{ var node:XML = XML(event.item); var params: Object = new Object(); params.id = node.@ID; var a:AsyncToken = getCarInfo.send(params); nodeMap[a.message.messageId] = node; }
In this function, we basically get the ID attribute from the selected node then pass that as a parameter to the servlet using the “getCarInfo” service.
We add the messageId to the nodeMap so when the result handler is invoked later we know where to append the children nodes to.
Now to the custom Descriptor that extends the DefaultDataDescriptor
package { import mx.collections.ICollectionView; import mx.collections.XMLListCollection; import mx.controls.treeClasses.DefaultDataDescriptor; public class MyCustomDescriptor extends DefaultDataDescriptor { //We will do a lazy load here override public function hasChildren(node:Object, model:Object=null):Boolean { var rc:Boolean = false; if (node.@HASCHILDREN == "true") { rc = true; } return rc; } override public function isBranch(node:Object, model:Object=null):Boolean { var rc:Boolean = false; if (node.@HASCHILDREN == "true") { rc = true; } return rc; } override public function getChildren(node:Object, model:Object=null):ICollectionView { //We only render the NODE as node for the Tree so we will filter the XML to only give the NODE items return new XMLListCollection(node.NODE); } } }
Tree Flex component uses an object of the above class to determine how the tree is rendered. we override the functions that makes the tree to show the > indicator if it has any children or if it is a leaf node.
I also override the “getChildren” function and I filter only “NODE” xml element to as children this is because we can have other xml element that may not represent a tree node, in other implementation, i have node, that holds more data about the node, I didn’t want it to be rendered as a node.
So here is the simple implementation of a lazy loading dynamic Flex tree. Below is the complete Flex mxml Content.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="init()"> <mx:Script> <![CDATA[ import mx.rpc.events.ResultEvent; import mx.rpc.AsyncToken; import mx.events.TreeEvent; import mx.collections.ArrayCollection; [Bindable] private var treeData:ArrayCollection = new ArrayCollection(); private var nodeMap:Dictionary = new Dictionary(); private function init():void{ getCarInfo.send(); } private function treeDataResultHandler(event: ResultEvent):void{ //Get the XML Data from the server var xmlResult:XML = new XML(event.result); var clickedNode:XML = nodeMap[event.token.message.messageId]; //if we find that result was for a clicked node then we append the resulting node as children //else we know its first call then we just add all at the main level. if (clickedNode != null){ clickedNode.appendChild(xmlResult.NODE); treeData.itemUpdated(clickedNode); } else { for each(var nodeElement:XML in xmlResult.NODE) treeData.addItem(nodeElement); } } private function loadChildren(event:TreeEvent):void{ var node:XML = XML(event.item); var params: Object = new Object(); params.id = node.@ID; var a:AsyncToken = getCarInfo.send(params); nodeMap[a.message.messageId] = node; } ]]> </mx:Script> <mx:HTTPService id="getCarInfo" url = "http://localhost:8080/Demo/getTreeData" result="treeDataResultHandler(event)" resultFormat="e4x" showBusyCursor = "true"/> <mx:Tree id="carTree" dataProvider="{treeData}" dataDescriptor="{new MyCustomDescriptor()}" labelField="@LABEL" itemOpening="loadChildren(event)" width="434" x="10" y="10" height="266"/> </mx:Application>
Can you please post java side code for servlet also.I am working on the same task with java and flex.Post or email me whatever you like but make it as soon as possible.Thanks in advance.
Hey Can you please mail me servlet code also for reading and returning back xml. Its Urgent .
@navjot87 and @Hardeep,
The server side code will depend on your implementation. For this sample, I had just hard coded values so it would return right xml back. Basically the flex part would make a call to server like http://localhost:8080/Demo/getTreeData if no id parameter is passed then it would return the root information in this case the 1st image in the Step1, after that as you click on the nodes, it would make calls to get children of that for in this case, if they click on “Cars” Flex would make a call to http://localhost:8080/Demo/getTreeData?id=“cars”, then it should return xml you see in 2nd image in Step 1.
I will have to look through my old computer to see if i still have the code. Even if i did i think it will be little use to you as this implementation would not work for yours. Hope this helps.
i did the same way you did and returned the same xml format as you said.But with your code its working fine for one time expansion of node.If we close and expand the same node again then its again making backend call and loading the same children again and chidren becomes doubles .what to do Please help me .
How to make differnce between already loaded and unloaded nodes.
hey you have written in last para of your article that you are flipping loadedChildren boolean .i want that dat .Where is your actual implementation code ?Actually dat logic is not in your article
Please help me .
hey its done i have tried to add that boolean value dynamically and its working .Thanks for your article.