Hierarchy

Hierarchies with Remote Objects and jsTree

Visualforce Remote Objects were introduced in Spring ’14 (API version 30) as a new and different way to perform create, read, update, and delete (CRUD) operations on records directly from Visualforce.  In this article I document a way to use Remote Objects with jsTree to manage hierarchies of Salesforce records.

Background

Unlike JavaScript Remoting, Remote Objects do not require any code to be written in an Apex Controller.  The remoteObject, remoteObjectModel, and remoteObjectField components are used to declare the remote objects and then JavaScript is used to perform the operations.  Because all of the operations are written as pure JavaScript, Remote Objects integrate with JavaScript libraries seamlessly.

The jsTree jQuery plugin provides interactive trees that can be used to manage hierarchies.  The jsTree site provides a good overview, API documentation and a nice demo for learning more about how jsTree works.  The basic implementation is to create and configure an instance of a jstree, set up event listeners to listen for events generated by interactions with the tree, and write code to directly interact with the tree.

The Page

The page consists of a single tree and a section for messages written to the page.  The tree uses the default jsTree styling; however, it can be configured fairly easily to use different configurations and styles.

The Page

The Code

The code consists of a single Visualforce page (available on GitHub).  The page contains a tree of nodes, with each node representing a record in Salesforce.  The page allows users to rename, create new, move (drag and drop), and delete nodes on the tree.  All changes to the tree result in changes to the actual records via Remote Objects.  The JavaScript is written in a way that is Object type independent.  It can be used with any Object that has a Lookup relationship to itself.  I used the Account object for simplicity because it has that relationship out of the box via the ParentId.

Remote Object Declaration

The remoteObject components are used to declare the Remote Objects.

The jsShortHand of sfNode is used to make the JavaScript clearly independent of any Object type.  Since all Objects have an Id and Name, they are safe to reference and aren’t given a jsShortHand.  The ParentId is given the Parent jsShortHand.  All that would need to be done to use this page with a different Object would be to change the “Account” and the “ParentId” values to the name of the Object and its Lookup field to itself, respectively.

Configuring the jsTree

The jsTree is configured to use a context menu (e.g., right click) to allow users to view a record in Salesforce, rename a record, create a new record, and delete a record.  Event listeners are configured to take action on rename, move, and selection of a node.

Loading Nodes

The retrieve method is used to load the nodes.  On the initial tree load, the node id passed from the jsTree framework is “#” and all  top-level (have no parent) nodes are loaded, but none of their children are loaded.  Child nodes are loaded on demand when a node is expanded.  The jsTree framework calls the loadNodes anytime a node is clicked for expansion and passes in the node that was clicked, so that the nodeId will be equal to the Salesforce record’s Id.  In this case, the query is fairly simple; however, much more complex criteria can be specified as documented here.

If an invalid value is specified in the where clause an “INVALID_QUERY_FILTER_OPERATOR” error is generated.  If you specify an invalid field an error message such as the following is generated: “Invalid criteria specified for retrieval. ValidationError [code=11, message=Data does not match any schemas from "oneOf", path=/where, schemaKey=null]”.  It isn’t always obvious when looking at the JavaScript that invalid criteria is being specified.  For example, you may have criteria specified such as { limit: 25, where: {AccountId: someJsObj.someField} }.  At a quick glance it may look as if the criteria is specifying that the AccountId should have the value of someJsObj.someField. However, on closer inspection the conditional operator is missing.  The correct criteria would be { limit: 25, where: { AccountId: {eq: someJsObj.someField} } }.

The limit of the number of nodes returned from a retrieve call is documented as 100.  If you leave it unspecified, 20 records are returned.  An “offset” clause can be used to get more nodes in a separate query.  For simplicity’s sake, I did not implement that.

Moving a Node

An sfNode Remote Object model is created with values for the Id and the Parent and then the update function is called.  A callback function is specified to the update method.  Technically, it is not necessary to specify a callback function.  However, it is a recommended best practice to implement a callback function to handle any possible errors.  If there are any errors generated by the operation, including errors such as validation rule errors, the err variable will be populated and can be displayed to the end user.

Renaming a Node

The rename event handler follows the same pattern as the move event handler of creating an sfNode model and calling update.

Creating a Node

The createNode function creates an sfNode model with the parent set and a default name and then calls the create function to create the record in Salesforce. In the create’s callback function the node is appended to its parent’s children and displayed in edit mode so that the user has the ability to rename it immediately.

Deleting a Node

The deleteNode function creates an sfNode model and calls the delete function on it. The node.id field is specified as the Id of the record to delete.  The callback function deletes the node from the tree.  The tree is then refreshed and if the deleted node had children its children are moved to the top/parent-less level.  If there is an error deleting the record in Salesforce, including validation errors, the err variable will be populated.

Conclusion

Visualforce Remote objects provide a simple, elegant way to build JavaScript centric apps or pages inside of Salesforce.  The Limitations of Remote Objects page in the Visualforce Developer’s Guide lists some of the limitations.  The Best Practices for Using Remote Objects page is a great resource that outlines best practices and when to use or not use Remote Objects.

All code is available on GitHub.

20 thoughts on “Hierarchies with Remote Objects and jsTree

  1. Thanks Peter,

    I was working to build a UI that present hierarchical data (parent child relationship) basically listing all opportunities in account , before I do that, you nailed it.

    Thanks for saving my time here, I like the explanation, very well written

    -Harshit

      1. Hi Peter, can you provide full coding for the same, it includes, apex class and page also.
        Because of, i don’t know, how to implement…….pls help me for the same.

  2. Amazing work, i love how you have made it generic to any self referencing object. Have you considered wrapping it up in a Visualforce Component? I imagine this might have a some challenges including the remote object tags, perhaps they would need to live outside the component somehow. Anyway, just a thought, great work!

    1. Thanks, Andy!

      I had the same thought on the component. I briefly played around with making it a component, but I was unable to set the attributes of the remoteObject tags dynamically. There might be some way to use dynamic components, though. It feels like there should be a way to do it.

  3. Peter,
    This is excellent stuff! I made me fell in love with remote object and jQuery. Thanks for posting this article.

    Is it possible to build similar tree structure on two related object. Example Account and Cases (Related to AccountId and also have Self-relation Parent CaseId). Please let me know if there is any limitations.

    Thanks again!

    1. Yes, you can do that. The jsTree plugin allows you to define and create different types of nodes and you can use more than one type of remote object on a Visualforce page.

  4. Hi Peter
    Is it possible to build a scrollable tree? Though I am populating records using offset, they are not showing up in the tree. How can I populate the records dynamically to jsTree after when I scroll to the bottom of the page? 100 record limitation is forcing me to use this.

    I have built the tree for two objects, both having Hierarchies, thanks!

    1. You could create a scrollable tree. I don’t recall if scrolling is supported as part of the jsTree API. You can look at the documentation on http://www.jstree.com/. If it isn’t, you could still do it with your own JavaScript. Search for infinite scroll. There are solutions out there that you could adapt.

  5. Excellent! Worked like a charm.
    I have a question.. I’m trying to implement the JsTree like a filebrowser where I have just one root (parent – Account) object and then respective children underneath. The loadNodes function loads all the objects initially when Id is #. However, I do have a parent Id with me, for which I want to create a file browser jsTree structure. How can I accomplish that.. Will appreciate your reply.. Thnx!

  6. This looks great. The latest version of jsTree does not have lib/jquery.js nor lib folder,will this still work? Thanks.

  7. Hi,

    I need urgent help. My users are unable to see the folder structure on the left , they are getting insufficient Privileges. I am unable to figure out what permission they need in order to see the folder structure. As an administrator I am able to see – thanks

Leave a Reply

Your email address will not be published. Required fields are marked *