Saturday, April 11, 2009

ExtJS Tip : Recursively Opening Nodes in a TreePanel

There was one client who requested an improvement that stuck in the Ajax File Storage UI package that I developed.

This client wanted the user interface to open a particular folder based on the url. For example, http://mysite.com/filemanager/?folder_id=100 should return a page that launches the UI and opens the folder with id 100.

This is a perfectly valid request but there were a few challenges, namely ;
  • the treepanel nodes are loaded using ajax
  • the nodes or folders to open could be several levels deep
The solution had a back-end and a front-end component.

The back-end component involved generating a list of folder_id's that led to the target folder_id. If the target folder is 3 levels deep, this folder list would have 3 folder_ids. Luckily this wasn't too hard to do in OpenACS as the heirarchy of folders in the file storage package is easy enough to get from the database.

The front-end solution is a javascript function that goes thru this list and opens each folder based on the id's on that list. Just in case it is useful to someone using the ExtJS treepanel, here's the code for that function.

    asyncExpand : function(x) {
var treepanel = Ext.getCmp('treepanel');
var node = treepanel.getNodeById(this.config.initOpenFolder);
if(!node) {
var x = x+1;
var nextnodeid = this.config.pathToFolder[x];
var nextnode = treepanel.getNodeById(nextnodeid);
nextnode.on("expand",this.asyncExpand.createDelegate(this,[x]), this, {single:true});
nextnode.expand(true);
} else {
node.select()
node.fireEvent("click",node);
}
}


You will notice that it is an example of a recursive function or a function that calls itself. Allow me to break it down for you.



var treepanel = Ext.getCmp('treepanel');



Of course you need a treepanel, the one I use just happens to have the id "treepanel"



var node = treepanel.getNodeById(this.config.initOpenFolder);



this.config.initOpenFolder is the variable that holds the folder_id that the UI should open into by default. The script calls getNodeById in the hopes of getting a reference to that folder on the treepanel



if(!node) {
var x = x+1;
var nextnodeid = this.config.pathToFolder[x];
var nextnode = treepanel.getNodeById(nextnodeid);
nextnode.on("expand",this.asyncExpand.createDelegate(this,[x]), this, {single:true});
nextnode.expand(true);
} else {
node.select()
node.fireEvent("click",node);
}

If the node does not exist on the first level of the tree, we presume that it maybe on succeeding levels. This is where we make use of the folder_id list that the back-end should create for you, this list should be a javascript array.

In this case it is this.config.pathTofolder. This part of the function ...
  • gets the next id on the list
  • gets a reference to the node with that id
  • assigns a function to the expand event of the node to call asyncExpand with the index of the next folder_id on the list.

When asyncExpand is called again, it will check to see if a node with a folder_id that matches this.config.initOpenFolder exists. If not, it will get a reference to, assign a listener to the expand event and call the expand function of the node with the given index (x) .

The process repeats until the folder with id equal to this.config.initOpenFolder is found.