Flex Salesforce.com Tree Control
December 8th, 2008
One of the things that Salesforce.com is lacking is a descent tree control to display hierarchical data. I did notice a screenshot on the Winter ’09 Developer Screencast of a tree control but according to our Salesforce.com technical reps there are no plans to release this.
There is a really good Flex tutorial on developer.force.com concerning this topic, but one of their main takeways is:
NOTE: You should be very careful when using nested queries as you can easily run into governer limits.
We have a large hierarchy with thousands of nodes, so governor limits is certainly a consideration. Therefore, I decided to create a tree that loads the first two levels of the hierarchy and then lazily loads the subsequent level childen when they are clicked on.
You can run this demo on my Developer Site. You might also want to check out this post on the basics of connecting Flex to Salesforce.com.
There is a really good tutorial on Adobe’s site that has alot of great information regarding XML and trees if you are interested. I have some plan to add drag and drop capability in the near future.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | <?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundGradientAlphas="[1.0, 1.0]" backgroundGradientColors="[#F3F3EC, #F3F3EC]" creationComplete="login()" layout="horizontal" height="300" width="500"> <mx:Script> <![CDATA[ import com.salesforce.*; import com.salesforce.objects.*; import com.salesforce.results.*; import mx.collections.ArrayCollection; import mx.controls.Alert; import mx.collections.XMLListCollection; import mx.events.ListEvent; [Bindable] private var categoriesXml:XML = <list> <root> <level0 name="Categories" level="0"/> </root> </list>; [Bindable] public var sfdc:Connection = new Connection(); [Bindable] private var acClicked:ArrayCollection = new ArrayCollection(); [Bindable] private var categoriesXmlData:XMLListCollection = new XMLListCollection(categoriesXml.root); private function login():void { var lr:LoginRequest = new LoginRequest(); lr.server_url = this.parameters.server_url; lr.session_id = this.parameters.session_id; lr.callback = new AsyncResponder(loginSuccess, loginFault); sfdc.login(lr); } // gets all level 1 private function getLevel1():void { sfdc.query("Select c.Name, c.Id From Cat1__c c Order by c.Name", new AsyncResponder( function (qr:QueryResult):void { var parent:XMLList = categoriesXml.root.level0.(@name == 'Categories'); if (qr.size > 0) { for (var j:int=0;j<qr.size;j++) { var newNode:XML = <level1 level="1"/>; newNode.@id = qr.records[j].Id; newNode.@name = qr.records[j].Name; parent[0].appendChild(newNode); getLevel2(newNode.@id); } } },sfdcFailure ) ); } // gets all level 2 nodes for the selected parent private function getLevel2(parentId:String):void { sfdc.query("Select c.Id, c.Name From Cat2__c c Where c.Cat1__c = '"+parentId+"' Order By c.Name", new AsyncResponder( function (qr:QueryResult):void { var parent:XMLList = categoriesXml.root.level0.level1.(@id == parentId); if (qr.size > 0) { for (var j:int=0;j<qr.size;j++) { var newNode:XML = <level2 level="2"/>; newNode.@id = qr.records[j].Id; newNode.@name = qr.records[j].Name; parent[0].appendChild(newNode); } } },sfdcFailure ) ); } // gets all level 3 nodes for the selected parent private function getLevel3(parentId:String):void { sfdc.query("Select c.Id, c.Name From Cat3__c c Where Cat2__c = '"+parentId+"' Order By c.Name", new AsyncResponder( function (qr:QueryResult):void { var parent:XMLList = categoriesXml.root.level0.level1.level2.(@id == parentId); if (qr.size > 0) { for (var j:int=0;j<qr.size;j++) { var newNode:XML = <level3 level="3"/>; newNode.@id = qr.records[j].Id; newNode.@name = qr.records[j].Name; parent[0].appendChild(newNode); } } },sfdcFailure ) ); } // queries sfdc when node is clicked private function tree_itemClick(evt:ListEvent):void { var item:Object = Tree(evt.currentTarget).selectedItem; var node:XML = XML(item); var nodeName:String = node.@name; if (!acClicked.contains(nodeName)) { // fetch the children getLevel3(node.@id); // add the node to the list of nodes clicked acClicked.addItem(nodeName); } } private function loginSuccess(result:Object):void { getLevel1(); } private function sfdcFailure(fault:Object):void { Alert.show(fault.faultstring); } private function loginFault(fault:Object):void { Alert.show("Could not log into SFDC: "+fault.fault.faultString,"Login Error"); } ]]> </mx:Script> <mx:Tree id="tree" dataProvider="{categoriesXmlData}" itemClick="tree_itemClick(event)" labelField="@name" showRoot="false" width="100%" height="100%" left="0" top="0"/> </mx:Application> |
Categories: Code Sample, Flex, Salesforce












Hey, nice idea.
Forgive me if questions i have seem dumb but i only just taken a quick look at force.com:
1) I see you are using Cat1__c,Cat2__c,Cat3__c… I’ve seen this on other place in force.com. I this the recommended way to do hierarchy or just an example you have?
2) how/can you use this flexy thingy as a field in the edit. I want to create an object with a field to select a category and i could really find out how to do this.
Again, sorry if this seems obvious.
Nir, that is a great question. We’ve done it both ways in the past. We’ve created a separate custom object for each level of the hierarchy and then created a single custom object to maintain all levels in the hierarchy. We just have a circular reference to itself to maintain the parent-child relationship. Since you are limited to the number of custom objects, this may be a better approach.
If you want to include the control in an edit page, you’d have to have a callback from the Flex app to the Visualforce page. You can find an example of this at: http://blog.jeffdouglas.com/2008/12/09/flex-callback-example-with-visualforce/.
Good luck!
Another technique is having FLex-Javascript-Apex-Force.com integration.
In this way you can save the Governor limits since there are no webservice calls here.
I think that might work depending on the size of your resultset. Our tree has 15,000+ nodes and we were having issues with too many loops.