Thursday, May 5, 2011

Lazy loading tree with jQuery, Force.com, jsTree and REST JavaScript toolkit




I recently took part in my first CloudSpokes challenge and was lucky enough to be the winner. The challenge was to use the Salesforce REST JavaScript toolkit to create a tree component that represented the parent/child relationship of an object to itself (think parent account/account relationship). Another requirement was that it would load “lazily”, only loading one level of children at a time. This would allow for a quick overall page load if there was a deep and complex tree that needed to be rendered. Here is a link to the challenge.  

Below is an overview of my entry and my thought process. You can download the code in its entirety here.

What you will need

I decided to use jsTree to render my tree in. I had never used it before but it looked to have all the things I needed in order to render a fully functional tree. The documentation and files can be downloaded here:


I also had to download the REST JavaScript tool kit. This is a pretty slick toolkit created by Pat Patterson that makes using the REST API via JavaScript a breeze. You get the toolkit here:


Once I got both of these libraries I zipped them up and loaded them to my dev org as a static resource that I would reference later in my component.

Add all the files into a zip and load it to Salesforce

Lastly I created a custom object to run it on. This was a simple custom object named Parent Object with a lookup relationship to itself.

Parent Object 


Define lookup to itself
Now on to the code…


I decided to create my tree as a custom component so I would be able to use it across any object that has a relationship with itself. To learn more about custom components check out the Visualforce developer’s guide.


The first step was to define my custom component and all the attributes that would need to be passed to it to make it dynamic. We will see these referenced later in the code.




 

 

 

 
 


Next, we need to include the libraries from the static resource we uploaded earlier.




    

Then we define the div that the tree will be rendered in once the page loads.


Now we can get to the actual code that queries Salesforce and renders the parent/child tree. The following code is all written in JavaScript and I am leaving out the script tag in my demo because Blogger does not like it. The first thing I did was convert the attributes to JavaScript variables. This is not a necessary step. I just find it easier when I do not have to type the merge syntax around my variables throughout my code.


/////////////////////////////////////////////////
        //
        // VARS
        //
        //////////////////////////////////////////////////
         
      var objectToQuery = '{!objectToQuery}';
        var parentId = '{!parentId}';  
        var fieldToQuery = '{!childField}';
        var childRelationshipName = "{!childRelationshipName}";
        var parentRecordName = "{!parentRecordName}";

Since jsTree uses jQuery we need to get a reference to jQuery that works.  I also needed to load the jsTree themes and get an instance of the REST API Client.


//////////////////////////////////////////////////
        //
        // Get a reference to jQuery that we can work with
        //
        //////////////////////////////////////////////////
        
        $j = jQuery.noConflict();
        
        ///////////////////////////////////////
        //
        // Load the themes
        //
        ///////////////////////////////////////
        
        $j.jstree._themes = "{!URLFOR($Resource.restJavascript, '/rest/themes/')}";
        
        ///////////////////////////////////////
        //
        // Get an instance of the REST API client
        //
        ///////////////////////////////////////
        
     var client = new forcetk.Client('{!$Api.Session_ID}');

Next I created an instance of a tree and stick it in the “tree” div we created earlier.  You will see that three major things are defined here. (1) The plugins needed. (2) The parent node name, state and id. Notice that I use the id of the parent object as the id. I will reference that later as I build nodes for the tree. (3) The themes for the different nodes. These themes are what define what icons will show up for the different types of nodes and what type of children they can have.


//////////////////////////////////////////////////
        //
        // Instantiate the inital jsTree
        //
        /////////////////////////////////////////////////
        
        $j("#tree").jstree({ 
          "plugins" : [ "themes", "json_data", "ui", "crrm", "dnd",  "types" ],
        
             "json_data" : {
                 "data" : [
                     { 
                         "data" : parentRecordName,
                         "state": "closed", 
                         "attr" : { "id" : parentId }                     
                     },
                 ]
             },
             "types" : {
     "max_depth" : -2,
     "max_children" : -2,
     "type_attr" : "rel",
     "valid_children" : [ "default" ],
            
              ////////////////////////////////
              // Define the themes
              ////////////////////////////////
              
              "types" : {
    
       "children" : {
        "icon" : { 
         "image" : "{!URLFOR($Resource.restJavascript, '/rest/folder.jpg')}" 
        },
        "valid_children" : ["noChildren","children" ]
       },
       "noChildren" : {
        "icon" : { 
         "image" : "{!URLFOR($Resource.restJavascript, '/rest/leaf.jpg')}" 
        },
        "valid_children" : ["noChildren","children" ]
       },
       
       "default" : {
        "icon" : { 
         "image" : "{!URLFOR($Resource.restJavascript, '/rest/home.jpg')}" 
        },
        "valid_children" : [ "noChildren","children" ]
       }
     }
    } 
 });
         

The next two functions are the ones that query Salesforce for data. “firstNodeLoad” is called on page load and loads the children of the parent object. We will see this called later in my code. The second one called “loadNode” is used for every subsequent call and accepts an Id of a Salesforce object and queries for that object’s children.


//////////////////////////////////////////////////
    //
    // Load nodes
    //
    /////////////////////////////////////////////////    

 function firstNodeLoad() {
     
     var query = "SELECT Id," +fieldToQuery +", Name, (Select Id From "+childRelationshipName+") FROM " + objectToQuery + " WHERE " +fieldToQuery +" = '" + parentId + "'";
     client.query(query,parseNode);
     
    }
    
 function loadNode(parentIdPassed) {
    
     var query = "SELECT Id," +fieldToQuery +", Name, (Select Id From "+childRelationshipName+") FROM " + objectToQuery + " WHERE " +fieldToQuery +" = '" + parentIdPassed + "'";
      client.query(query,parseNode);
    
    }
    
    
Both of those functions call “parseNode” when their API call is complete and pass their results into it. This function parses the response and builds a node for each child found for that parent. It also determines if those children have children. 

//////////////////////////////////////////////////
    //
    // Parse the REST repsonse
    //
    /////////////////////////////////////////////////
    
    function parseNode(response) {
       
        for(var i=0; i < response.records.length; i++) {
         
         var hasChildren = false;
       
/// Determine if they have children
if(response.records[i][childRelationshipName]!= null) {
          
               hasChildren = true;
          }
   
   addNode(response.records[i][fieldToQuery],response.records[i].Name,response.records[i].Id,hasChildren);
  }
         
    } 
        
The final function “addNode” is called as each child is parsed in the parseNode function. This function takes all the information about that child and creates a node or “leaf” on the tree for the child. It defines things like style, id, closed, etc. You will notice that I use the record id for the id of the leaf. This way I will be able to quickly add children to it when I need to.
//////////////////////////////////////////////////
    //
    // Add each node
    //
    /////////////////////////////////////////////////   
    
    function addNode(localParentId,nodeName,nodeId,hasChildren) {
      
      ////////////////////////////////////////////
      //
      // Create a new node on the tree
      //
      ////////////////////////////////////////////
      
      if(hasChildren) {
   
    /// If it has children we load the node a little differently
    /// give it a different image, theme and make it clickable
    
    $j("#tree").jstree("create", $j("#"+localParentId), "inside",  { "data" : nodeName ,"state" : "closed","attr" : { "id" : nodeId,"rel":"children"}},null, true);

    
   } else {
   
    // Nodes with no trees cannot be opened and 
    // have a different image
      
       $j("#tree").jstree("create", $j("#"+localParentId), "inside",  { "data" : nodeName ,"state" : "leaf","attr" : { "id" : nodeId,"rel":"noChildren"}},null, true);
   
   }
   
 }
 
Lastly I need to bind some events to the nodes that are created in the tree.

The first event was to load any child nodes of a leaf if it was opened up by the user. This was a major requirement of the challenge. Up until now in our code we only query one level of the tree. I accomplished this by using the “open_node.jstree” method that is part of jsTree. Basically every time a node is opened I call the “loadNode” method on the children of that node and load up any children they may have.
//////////////////////////////////////////////////
    //
    // Add the logic to query if there are children
    //
    /////////////////////////////////////////////////
 
 $j("#tree").bind("open_node.jstree", function (e,data) {
  
  /// Make sure it is not the parent node 
  
  if(data.args[0].attr("id") != parentId) { 
  
   // Get the current node
           
           var thisNode = $j('#'+data.args[0].attr("id"));
          
           /// This makes sure that it does
           /// not load the childeren more than once
           
           if(thisNode.find('li').length == 0) {
           
            /// Load all the child nodes
            
             loadNode(data.args[0].attr("id"));
           
            }
  }   
    })
Then I also delegated the click of links (record names) within the tree to open up the record they represented in a new window.

//////////////////////////////////////////////////
    //
    // Setting up the link to the object on the label 
    // of a tree node
    //
    //////////////////////////////////////////////////

   $j("#tree").delegate("a","click", function (e) {
    
    /// Dont open a window for ultimate parent
    if($j(this).parent().attr("id") != parentId) {
    
     window.open("/" + $j(this).parent().attr("id"),"mywindow","status=1,toolbar=1,location=1,menubar=1,resizable=1,scrollbars=1");

    }
  });
 



Finally when it is all done I call the “firstNodeLoad” method to kick it all off.
////////////////////////////////////////////////
 //
 // Load the first node on window load
 //
 ////////////////////////////////////////////////
 
 window.onload = firstNodeLoad;


All I had to do now was include this component in a Visualforce page. You will see that we call the "lazyLoadingTree" component and then pass into it the attributes required to render the tree. This component could be called on any object that had a child/parent relationship with itself.



 









Then include that page in a standard page layout and I get this pretty little tree that loads “lazily”.




Hopefully this was helpful. It was my first time working with jsTree and I am a bit of a jQuery hack. As always would love to hear any feedback/suggestions. Thanks again to Pat Patterson for putting the toolkit together as well as CloudSpokes for putting on some fun challenges!



You can download the code in its entirety here.


No comments:

Post a Comment