After my initial post about this, I had a discussion with John Snyders of HardLikeSoftware fame, and it turns out there is a better and more efficient way to maintain the tree state. While what I outlined definitely works, what follows is the “preferred method” as it uses more of the built in interfaces. I have therefore implemented this version of the code for APEX 19.1.
Note: Because the code in this post uses the region.call method and the JavaScript Initialization Attribute, it is likely not to work with versions of APEX prior to 18.2.
The Setup
A couple of house keeping items to mention first.
- Make sure you give the tree a unique and meaningful static id. This is just general good practice for any region, but especially if you’re going to be using any type of JavaScript to control it. It also makes your code more readable.
- Make sure the tree region has a Node Value Column assigned that is a unique key. The reason for this is two-fold:
- If the Node Value is not unique you’ll get into a situation where the actual nodes and their state may clash. For instance, if two nodes have the same Value and one is expanded and the other isn’t, when you save and then reinstate the tree state, you may get unexpected results.
- If the Node Value is not set at all, then the NodeAdapter will not be able to identify a given node and save/reinstate its state.
Saving the Expansion State
As before, the first step is to save the current expansion state of the tree any time you navigate away from the page containing it. Do so by creating a Dynamic Action triggered on Page Unload and execute the following JavaScript:
var ids = apex.region("emp").call("getExpandedNodeIds").join(":");
apex.storage.getScopedSessionStorage({usePageId: true, useAppId: true}).setItem("treeExpansionState", ids);
- Line 1: Notice that this time, we’re going to be using the apex.region interface to get a handle on the tree using its static id. Once we have a handle on the tree object, we call the getExpandedNodeIds method and then join the elements of that array into a single string using a colon (‘:’).
- Note: The character you use to join the elements of the array should be chosen carefully. Don’t use a character that might appear as part of the Node Value.
- Line 2: We’re still using the apex.storage namespace to save the values to the browser, but instead of setting the cookie directly, it uses the getScopedSessionStorage wrapper. In the code above, the call is using both PageId and AppId to prefix the session key named treeExpansionState.
Assuming we are running an application with an ID of 1234 and the tree exists on page 100, the session storage variable name would appear as:
100.1234.treeExpansionState
Something to note though; If your tree is defined on a global page and displayed on various other pages (as is the case with the APEX ORDS REST Workshop), you’ll want to set usePageId: false. Otherwise, even though the tree state is being saved, its context will be page specific and will only be reinstated when viewing the tree component from that page. Setting the value to false will omit the page id from the key name so that it will not be page specific.
Reinstating the Expansion State
In my prior example I used an onLoad Dynamic Action to reinstate the tree to its former expansion state. While this is perfectly viable, it’s not the most efficient. Instead it’s preferable to us the JavaScript Initialization Code for the tree object. To do this, navigate to the Attributes section of the tree region and use the following code in the Advanced > JavaScript Initialization Code region:
function(options) {
options.makeNodeAdapter = function(data, types, hasIdentity) {
var a,
ids = apex.storage.getScopedSessionStorage( { usePageId: true, useAppId: true} ).getItem("treeExpansionState");
if ( ids ) {
ids = ids.split(":");
}
a = $.apex.treeView.makeDefaultNodeAdapter( data, types, hasIdentity, ids );
return a;
}
return options;
}
The Initialization Code block takes in an options object for the tree, modifies it and then returns it. In our case, we’re modifying the default NodeAdapter.
- Line 2 sets up the function to that will be called to return the
NodeAdapter
- Line 3 creates a variable that will hold the finished adapter
- Line 4 uses
apex.storage
to retrieve the value of thetreeExpansionState
key. - Lines 5-7 check to see if the the Key actually contained anything, and if it does, splits the string back into an array that will be used by the
NodeAdapter
- Line 8 creates the
NodeAdapter
by using the details passed into the function as well as the array ids which holds the list of expanded nodes - Line 9 returns the adaptor
- Line 11 returns the
options
object back to the tree
By using the Advanced JavaScript Initialization Code, the tree state is set as the tree is drawn instead being a second step after the page is “ready”. Doing it this way, you can integrate it with any other logic that might affect the tree’s options object.
I hope by making this second post I haven’t confused the issue. As I said – Both should work but this version is cleaner and more efficient.