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.

ishot-2

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>
VN:F [1.9.3_1094]
Rating: 0.0/10 (0 votes cast)
VN:F [1.9.3_1094]
Rating: 0 (from 0 votes)
Share and Enjoy:
  • Print
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay

Categories: Code Sample, Flex, Salesforce

Leave a comment

Comments Feed4 Comments

  1. Nir Levy

    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.

    VA:F [1.9.3_1094]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.3_1094]
    Rating: 0 (from 0 votes)
  2. jeffdonthemic

    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!

    VA:F [1.9.3_1094]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.3_1094]
    Rating: 0 (from 0 votes)
  3. Bobby

    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.

    VA:F [1.9.3_1094]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.3_1094]
    Rating: 0 (from 0 votes)
  4. jeffdonthemic

    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.

    VA:F [1.9.3_1094]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.3_1094]
    Rating: 0 (from 0 votes)

Leave a comment

Feed

http://blog.jeffdouglas.com / Flex Salesforce.com Tree Control

WordPress Appliance - Powered by TurnKey Linux