Skip to Content

Follow the path

image

Image 1. Modified context structure of Component Controller.

First, what is the problem with non-recursive children after all? Ok, let us start simple. Imaging, that in our introspector example we want to display argument types of methods via pair of Master-Detail tables. So let us alter original context of Component Controller and add Arguments node right directly recursive node Entries.

Obviously, this node will be populated only for method entries and left empty for class entries. Also I modified a bit Entries node itself – new value attribute Method of type java.lang.reflect.Method is introduced to hold reference for further arguments introspection. Arguments node contains 2 attributes – type of method argument, and Memo to assign some notes to argument (in our artificial example it could be either JavaDoc, or XDoclet tag, or design-by-contract’s pre-conditions).

Here is the code for supplyArguments function:

public void supplyArguments ( IPrivateTreeTable.IArgumentsNode node, IPrivateTreeTable.IEntriesElement parentElement ) { //@@begin supplyArguments(IWDNode,IWDNodeElement) final Method method = parentElement.getMethod(); // Bypass class entries where method == null if ( null == method ) return; final Class[] types = method.getParameterTypes(); for (int i = 0; i < types.length; i++) { final IPublicTreeTable.IArgumentsElement elArg = node.createArgumentsElement(); elArg.setType( formatArgType( types[i] ) ); node.addElement( elArg ); } //@@end } .... //@@begin others ... private static String formatArgType(final Class clazz) { if ( !clazz.isArray() ) return clazz.getName(); else { final StringBuffer str = new StringBuffer(16); Class c; for (c = clazz; c.isArray(); c = c.getComponentType() ) { str.append( "[]"); } str.insert(0, c.getName() ); return str.toString(); } } ... //@@end

 

Now let us map this node in view controller, add second table bound to mapped Arguments node, build compile and run. If you are trying to repeat the sequence above yourself (rather then using final example project) you’ll be quite surprised with behavior – the details table is never populated. And supply function for arguments contains no errors. By the way, it is never called for method entries, you may check this via debugger. So what is the reason?

The actual problem lays in handling of lead selection in WebDynpro. For recursive nodes lead selection corresponds to lead selection at first level only. As far as first level in our example is an introspecting class entry there are no arguments on first level. And it doesn’t matter really how deep you scroll in class hierarchy – the lead selection stays on root class entry. Ok, it is not 100% correct technically, every recursive node has lead selection at every level, but UI controls in WebDynpro navigate in context in this way.

So, how WebDynpro helps us in this case? First, we always may discover currently selected path via call to IWDNodeElement IWDNode.getTreeSelection(). Check the following code:

private IPrivateTreeTableCV.IEntriesElement currentEntry() { try { IWDNodeElement el; for ( el = wdContext.nodeEntries().getTreeSelection(); !( el == null || el instanceof IPrivateTreeTableCV.IEntriesElement); el = el.node().getParentElement() ); return (IPrivateTreeTableCV.IEntriesElement)el; } catch (final WDContextException ex) { return null; } }

 

You may wonder why for loop was introduced in code. Actually, if you have only one non-recursive child you rarely went into troubles even without recursion. However, if there are at least 2 children you may receive unexpected behavior of this method. The method IWDNode.getTreeSelection scans not only recursive nodes, but non-recursive children as well. So, in fact, if you have initialized lead selection in Arguments sub-node, you will get it element as tree selection. Hence we traverse back to first parent of necessary type (IPrivateTreeTableCV.IEntriesElement in our case).

Ok, we know now how to get selected entry. Next we need to display related Arguments node. Here is the algorithm:

  1. Create singleton node CurrentArguments as immediate child of context node with same cardinality and attributes.
  2. Alter supplier / disposer of node to copy elements back & force between nodes.
  3. Track lead selection in master table, invalidate / validate CurrentArguments node to reflect selection changes.
  4. Re-bind table and cell editors to CurrentArguments source.

image

Image 2. Modified context structure of View Controller.

The first step is trivial, see image for details. The second step is implemented as below:

public void wdDoInit() { //@@begin wdDoInit() wdContext.currentContextElement().setViewportSize(10); final IWDNodeInfo niCurrentArgs = wdContext.nodeCurrentArguments().getNodeInfo(); niCurrentArgs.setCollectionSupplier ( new IWDNodeCollectionSupplier() { public void supplyElements( final IWDNode node, final IWDNodeElement parentElement) { final IPrivateTreeTableCV.IEntriesElement current = currentEntry(); if ( null == current ) return; WDCopyService.copyElements ( current.nodeArguments(), node ); } } ); niCurrentArgs.setCollectionDisposer ( new IWDNodeCollectionDisposer() { public void disposeElements(final IWDNode node) { if ( null == _lastEntry ) return; WDCopyService.copyElements ( node, _lastEntry.nodeArguments() ); } } ); //@@end }

 

Note that we referring here to currentEntry() method to resolve currently selected path in tree for supplier while node collection supplier may be called at any time. On other hand disposer relies on private view controller variable. This variable will be modified in step [3] and the reasons to track last selection in separate variable will be discussed there. Also please pay attention how using of WDCopyService shrinks copiyng elements logic to (almost) one-liner code.

To perform step 3 we declare OnLeadSelect action handler and provide logic for handling row selection. We must consider, that in action handler we have already modified context – the selected row in Entries node already modified here. But our code in disposer should copy values back from CurrentAttributes to previously selected entry. Therefore we save corresponding node element in private controller variable _lastEntry. The order here is very important: first we invalidate CurrentAttributes node to save values back, only then we update _lastEntry variable to newly selected node element:

public void onActionSelectEntry(com.sap.tc.webdynpro.progmodel.api.IWDCustomEvent wdEvent ) { //@@begin onActionSelectEntry(ServerEvent) final IPrivateTreeTableCV.IEntriesElement current = currentEntry(); // Do nothing if selecting same row if ( _lastEntry == current ) return; // Invalidate node to propagate values back wdContext.nodeCurrentArguments().invalidate(); _lastEntry = current; //@@end }

 

If you are using supplementary example project you may notice that Memo attribute is visualized via IWDInputField editor, so you may confirm that values are copied correctly in both directions.

Moving target

The second question this post tries to address leads from the WebDynpro forum topic Selection in table with Master Column. Here I’ve advised an algorithm to select an element in recursive node and synchronize table “view port” (visible area) to make selected row immediately visible to user without scrolling. However, the description provided was plain wrong, and I will fix this error here.

So first we will create IWDTree UI control to emulate entry selection occurring somewhere outside TreeTable. Please read Tree control in WebDynpro by http://www.sdn.sap.com/irj/sdn/weblogs?blog=/pub/u/22470 [original link is broken] [original link is broken] [original link is broken] [original link is broken] [original link is broken] [original link is broken] regarding IWDTree UI control bound to recursive node. Next we bind action handlers of IWDTreeNodeType to the same action handlers of IWDTable and IWDTreeByNestingTableColumn. Sure, we have to add parameters mapping in wdDoModifyView as well:

... if ( firstTime ) { ... final IWDTreeNodeType tntEntries = (IWDTreeNodeType)view.getElement("tntEntries"); tntEntries.mappingOfOnLoadChildren().addSourceMapping ( /* IWDTreeNodeType.IWDOnLoadChildren.PATH, */ "path", "parent" ); } ...

 

Off-topic rant: I have to introduce new read-only calculated attribute IsNotLeaf in view Entries node that logically negate IsLeaf attribute from same element. I don’t know driving forces behind WebDynpro developers, but having hasChildren in IWDTreeNodeType and introducing IsLeaf for TreeByNestingTableColumn is quite… well… illogical.

To programmatically control table view port we add 2 context attributes of type integer: FirstRow for firstVisibleRow in table control, and ViewportSize for visibleRowCount property. We initialize VieportSize to 10 (or any other constant) in wdDoInit method of view.

Next we write function to synchronize view port. This function accept element that should be supplied from “path” parameter of OnAction tree node action handler (same as OnLeadSelect for table):

private int sizeOf(final IPrivateTreeTableCV.IEntriesElement el) { if ( !el.getIsExpanded() ) return 1; int size = 1; final IPrivateTreeTableCV.IEntriesNode nChildren = el.nodeSubEntries(); for (int i = nChildren.size() - 1; i >= 0; i--) size += sizeOf( nChildren.getEntriesElementAt(i) ); return size; } private void syncVieport(final IPrivateTreeTableCV.IEntriesElement path) { final IPrivateTreeTableCV.IContextElement ctx = wdContext.currentContextElement(); final int size = ctx.getViewportSize(); int level = -1; int offset = 0; for (IWDNodeElement el = path; el != ctx; el = el.node().getParentElement() ) { int sizeOfPrevSiblings = 0; final IPrivateTreeTableCV.IEntriesNode node = (IPrivateTreeTableCV.IEntriesNode)el.node(); for (int j = el.index() - 1; j >= 0; j--) sizeOfPrevSiblings += sizeOf( node.getEntriesElementAt(j) ); offset += sizeOfPrevSiblings + 1; level++; } offset += level; if ( ctx.getFirstRow() <= offset && offset < ctx.getFirstRow() + size ) return; final int page = offset / size; ctx.setFirstRow( page * size ); }

 

The algorithm is trivial. First we traverse from element in question to root adding offset of every entry element on path to total offset. Offset of entry element equals depends on size of all previous siblings (depending whether or not they are expanded). Also we calculate depth (level number) of our element in hierarchy. We have to increase total offset by this value (while every level has entry, and this entry shifts our target entry by one). The only thing left is to assign FirstRow attribute. We take table pagination onto account here: we calculate at what page our element will be visible and scroll to this page.

What’s next

That’s all for today. Next time we will discuss how to use TreeTable to display categorized list of objects, where every category is created as grouping by certain objects attribute. Stay tuned!

You may download sample project file here. Also you’d probably like to check Master of Columns, Part I of this installment.

To report this post you need to login first.

2 Comments

You must be Logged on to comment or reply to a post.

  1. Finbar Callaghan
    Hey a large portion of your text is being truncated on the right. It seems a shame to lose some of the content, considering you have done all the hard work.
    (0) 
    1. Valery Silaev Post author
      Finbar,

      Thanks for pointing this out.

      Suddenly SDN software starts to escape closing HTML tags after code blocks, hence this formatting error arised.

      Valery

      (0) 

Leave a Reply