UPDATE: Intorspector example has been updated to support SP12
...or should I say "The Lord Of The Columns" ๐This blog entry tries to answer one of the most popular questions on SDN WebDynpro forum: how to use new SP11 TreeTable UI control. To be exact, it is same old Table UI control with extremely useful addition TreeByNestingTableColumn (Master Column). However, usage of this control requires special techniques for populating related context nodes and handling row selection
Here are several real-life examples of hierarchical context you probably want to display using TreeTable:
So, the requirement to display nested (hierarchical) structures within Table UI control is quite common. However, before SP11 developers had to resort to Tree + Table combination as workaround. Or develop some complex solutions like CAF FlexTree UI Pattern (probably, I will discuss it in my next posts). Fortunately, SP11 address this issue, but it was not accompanied with introductory article about new functionality. So let us fill this gap.
As far as this post is accompanied with sample project I don't spend a lot of time describing every mouse click, only the major points are highlighted. I'm currently using NW SP12, therefore my sample will not work with previous versions. However, you may download another sample that I has posted on WebDynpro forum. It is a server's file system viewer application that displays folders structure using Tree & TreeTable UI controls side-by-side. I can safely confirm that this sample is self-explanatory more then 10 SDN members try it out and found usable with (almost) no additional questions.
New example represent another useful tool - we will create Java class introspector, that outlines class methods and recursively iterates over inheritance chain via all implemented interfaces and super-classes starting from the given class.
First of all we have to create and populate recursive context node while table itself should be bound to recursive node. Recursive node is quite special beast in WD world. First, you have to declare regular node in context (it could be either Value or Model node). Second, you have to add Recursion node right after the node you'd like to be repeated, and set its recursiveNode property to parent node. See image below:
Image 1. Context structure of Component Controller.
Here is one important point to note. As far as our recursive node will represent properties for classes as well as methods, we have to include union of all properties for both of them. Therefore you will see that node contains Signature attribute that is valid for methods but not valid for classes. Also we need a way to differentiate between 2 type of entries. Signature is a good candidate itself, but here we will use Clazz attribute of type java.lang.Class. Any class entry will have this attribute set this allows us to discover (a) super-class or super-interfaces and (b) declared methods in supply function. Any entry that relates to method will have null for Clazz attribute.
Here is the code to populate Entries node:
public void supplyEntries(IPrivateTreeTable.IEntriesNode node, com.sap.tc.webdynpro.progmodel.api.IWDNodeElement parentElement) { //@@begin supplyEntries(IWDNode,IWDNodeElement) if ( parentElement == wdContext.currentContextElement() ) { /* Create root entry and return */ newClassEntry( _clazz, node ); return; } final Class clazz = (Class)parentElement.getAttributeValue("Clazz"); /* * During recursive roll-out we will reach methods * For method entry class is null and hence * we will not drill down deeper * */ if ( null == clazz ) return; final List parents = new ArrayList( Arrays.asList(clazz.getInterfaces()) ); if ( null != clazz.getSuperclass() ) parents.add(0, clazz.getSuperclass() ); for (final Iterator i = parents.iterator(); i.hasNext(); ) { newClassEntry( (Class)i.next(), node ); } final Method[] methods = clazz.getDeclaredMethods(); for (int i = 0; i < methods.length; i++) { final Method method = methods[i]; final IPublicTreeTable.IEntriesElement elEntry = node.createEntriesElement(); elEntry.setName( method.getName() ); elEntry.setIsLeaf( true ); elEntry.setSignature( method.toString() ); elEntry.setModifiers( method.getModifiers() ); elEntry.setIsChildrenLoaded( true ); node.addElement( elEntry ); } //@@end } //@@begin others private void newClassEntry(final Class clazz, final IPublicTreeTable.IEntriesNode node) { final IPublicTreeTable.IEntriesElement elEntry = node.createEntriesElement(); elEntry.setClazz( clazz ); elEntry.setName( clazz.getName() ); elEntry.setModifiers( clazz.getModifiers() ); node.addElement( elEntry ); } /* Pre-setup with some valid sample */ private Class _clazz = ObjectInputStream.class; //@@end
So far we have created recursive node Entries (+ recursion node SubEntries) in context of Component Controller. I always prefer to locate business logic in non-visual controllers (either Component Controller or Custom Controllers). You may consider this habit as one of best practices while in case of enhancements context and methods of these controllers could be shared between several views (and it is impossible to reuse View Controllers). Also data-retrieval logic is not polluted with display logic.
Next we have to develop our view. First of all we add Component Controller usage via Required Controllers on Properties tab for corresponding view. Next task is to map view context to component controller context. Note, that you are unable to map recursion node itself, so you have to re-create it in view controller context (you may name it as you whish, however it is better to keep names in both controllers the same for readability) and set recursiveNode property.
You may note that not all attributes are mapped some of them are created in view context. The most important is IsExpanded (boolean). It is required for TreeByNestingTableColumn UI control. Other attributes (EntryIcon & AccessIcon) convert Modifiers flag to corresponding icons.
Image 2. Context of view Controller
Next task is to create Table UI control and add Master Column:
Image 3. Insert Master Column
I have to mention one subtle difference between file system viewer and introspector samples. When I wrote the former, I do not pay attention that I may add cell editor directly to Master Column. That's why I create a separate column for file / folder name. To be fair, result looks pretty ugly. So in Introspector sample we add Caption editor directly to Master Column and enjoy professionally looking design ๐
After adding MasterColumn to table you have to map it properties to attributes of Entries context node (childrenLoaded, expanded, isLeaf). Adding the rest of columns is trivial.
Image 4. Setup Master Column
We are almost done here. The only thing left is to add LoadChildren action handler to view. Why wee need this handler at all? The answer is quite interesting.
I admit an important variation in TreeTable behavior between SP11 and SP12. If no LoadChildren action was defined for TreeTable in SP11, then control force validation of all recursive nodes, hence load time was significant (especially with file system example). SP12 handles loading differently (and more developer-friendly) even when LoadChildren is undefined: if childrenLoaded property for corresponding element returns true, no subsequent requests are sent, if it is false then request is sent lazily only when level is being expanded for first time. So if you want to port this sample to SP11 you need such action handler (or at least it is better to know it exists).
Another reason is that I'm adjusting IsLeaf attribute in this handler. Initially only method entries marked as leaves. But in Java certain interfaces are just markers (java.io.Serializable, java.lang.Cloneable etc.) with no methods / super-interfaces. Instead of verifying their nesting structure in supply function, I perform check only on demand. You might see the same behavior in Windows Explorer.
So first we create Action (pay attention to class of parent parameter and be ready to type it manually -- NetWeaver IDE does not allow to select inner class in corresponding dialog):Image 5. Adding LoadChildren action
And the related code:
1. This one for wdDoModifyView
if ( firstTime ) { final IWDTreeByNestingTableColumn master = (IWDTreeByNestingTableColumn)view.getElement("mcEntries"); master.mappingOfOnLoadChildren().addSourceMapping ( /* IWDAbstractMasterTableColumn.IWDOnLoadChildren.PATH */ "path", "parent" ); }
2. And this one for action handler:
if ( parent.getIsChildrenLoaded() ) return; parent.setIsLeaf( parent.nodeSubEntries().size() == 0); parent.setIsChildrenLoaded( true );
I don't know what the hell happens with my IDE, but it doesn't see IWDAbstractMasterTableColumn.IWDOnLoadChildren type any more. Hence corresponding reference is commented out and string contstant used instead.
Done. Well, almost done ๐ Bundled example contains logic to accept class name via URL parameter and use this class for introspection. Actual invocation URL looks like http://localhost:50000/webdynpro/dispatcher/local/SDN_TREE_TABLE/Introspect?clazz=java.util.Calendar. Sure you may use other existing class as parameter to check its' internals.
Download projects mentioned here:
File System Viewer
Class Introspector
Till next time when we discuss how to handle row selection in TreeTable and selecting specific row from code.
Image 6. Introspecting java.lang.String class