This is something that keeps coming up on different projects, so I’d like to share how I usually do this. There are some examples out there which try to describe a very generic solution which can be used in almost any case. However, such solutions very quickly become very complex. To keep things simple, I have tried to keep this example fairly specific but adaptable to a wide range of circumstances. Specifically this example is using a fixed hierarchy structure with a fixed number of levels, each of which will be represented by a different table.
If you have not read the first 3 parts ( 1, 2, 3 ) of this tutorial yet, I recommend you do that first, to help with the understanding of this post, which explains how to automatically navigate to the currently selected item in the tree lookup. This is something which is quite easy to do in a traditional lookup but requires a bit more work when dealing with trees.
Searching, searching…
We’ll need a way to find and automatically expand all ancestor tree items based on the currently selected shelf. We have the shelf record ID in the books, and we can use that to find the shelf, then the rack, aisle and all the way up to the library.
However, the tree is not loaded all at once, but on demand as we expand items. Thus, we have to expand from the top down, and narrow down to the correct record as we go.
Add a new method called findShelf to the EB_ShelfLookup form:
public void findShelf( EB_ShelfRefRecId _shelfRecId ) { EB_Shelves shelves; EB_Racks racks; EB_Aisles aisles; int idx; FormTreeItem item; select firstOnly RecId from shelves where shelves.RecId == _shelfRecId join RecId from racks where racks.RecId == shelves.RackRecId join RecId, LibraryRecId from aisles where aisles.RecId == racks.AisleRecId;
The tree keeps the RecId for each record in the data() property for the items. All we need in order to find our way through the tree is the record ID for each level, and we retrieve that through a simple joined query. Note that we don’t actually need to retrieve anything from the library table, we just need the RecId which we can find in EB_Aisles.LibraryRecId.
Next, we find the library item at the root-level of the tree.
// Look for the library idx = Tree.getChild( 0 ); while( idx != 0 ) { item = Tree.getItem( idx ); if( item.data() == aisles.LibraryRecId ) { Tree.expand( idx, FormTreeExpand::Expand ); break; } idx = Tree.getNextSibling( idx ); }
We start by getting the first child of the zero-index, which gives us the index of the first root-level item. We then get the item for that index and compare it to the library record ID. If it is a match, we expand that item and break out of the loop. Otherwise we grab the next sibling item until we’ve seen them all.
Expanding the item will in turn load the child items. This happens behind the scenes, so we don’t have to worry about it and can proceed to searching for the correct aisle:
if( idx != 0 ) { // Look for the aisle idx = Tree.getChild( idx ); while( idx != 0 ) { item = Tree.getItem( idx ); if( item.data() == aisles.RecId ) { Tree.expand( idx, FormTreeExpand::Expand ); break; } idx = Tree.getNextSibling( idx ); }
We start out by verifying that we did indeed find the library. Unless we have an inconsistent tree structure we will always find the ancestor items, but it is good practice to confirm. The structure of the loop is the same, but we start by getting the first child item of the library item and we compare against the aisle record ID instead.
Now we repeat for the rack:
if( idx != 0 ) { // Look for the rack idx = Tree.getChild( idx ); while( idx != 0 ) { item = Tree.getItem( idx ); if( item.data() == racks.RecId ) { Tree.expand( idx, FormTreeExpand::Expand ); break; } idx = Tree.getNextSibling( idx ); }
Finally we get to the shelf:
if( idx != 0 ) { // Look for the shelf idx = Tree.getChild( idx ); while( idx != 0 ) { item = Tree.getItem( idx ); if( item.data() == shelves.RecId ) { Tree.select( idx ); break; } idx = Tree.getNextSibling( idx ); } } } } }
Again, the loop works the same as above, but instead of expanding the item we will select it. After that we can return from the method.
Hooking it up
In order for this to work automatically when the lookup is opened, we need to make a few changes to the form’s init() method as well.
public void init() { FormReferenceGroupControl callerControl; super(); imageListAppl = new ImageListAppl( 32, 32 ); imageListAppl.add( 12039 ); imageListAppl.add( 12040 ); imageListAppl.add( 12041 ); imageListAppl.add( 12047 ); Tree.setImagelist( imageListAppl.imageList() ); EB_Libraries::buildTree( Tree, imageListAppl ); callerControl = element.selectTarget(); if( callerControl != null ) { this.findShelf( callerControl.value() ); } }
I have highlighted the differences with red indicators. When a lookup is called from a reference group, we can get the reference group control from element.selectTarget(). The value() property contains the reference record ID being looked up, in our case the shelf record ID. We just pass this directly to findShelf().
Open the form and open the lookup for one of the books to confirm that the correct shelf has been selected.
Wrapping up
That’s all for now. I hope you have found this tutorial useful.
There will be several ways the code can be made faster, more robust or more elegant, but I have tried to make it clear how this is all working and how it fits together. Feel free to make whatever tweaks or improvements you want.
An XPO with all the application objects disussed in this tutorial can be found here.