In an application I was extending, I wanted to use a Navigator component that worked very dynamically. It was to replace nested repeat controls. The Navigator has a RepeatTreeNode and it seemed a reasonable approach to nest them. So the structure I was building was:

Navigator
RepeatTreeNode (var=”level1″) whose children are BasicContainerNodes whose label is #{level1}
RepeatTreeNode computed based on #{level1} whose children are BasicLeafNodes.

Before we go any further, this is mainly a dump of what I’ve found out through investigation, which might be relevant to others hitting the same problem and might be relevant for someone trying to code a solution. I didn’t find one, so I’m stepping back and coding the application differently, so I don’t need to find a solution. Consequently, there are no answers here, just explanations of how it’s working and where the challenges lie.

Phase 1

The attempts typically failed with the following stack trace, with the message “‘level1’ not found”:

I tried scaling it back to just the BasicContainerNode, but that failed with the following stack trace. I realised that’s because a container node (obviously!) expects children, which I didn’t have.

With just that one level and using BasicLeafNodes instead of BasicContainerNodes, it worked fine.

Phase 2

The first level repeat has content that won’t change dynamically, so I tried computing it on page load instead. I made the mistake of setting the “style” property of the BasicContainerNode to computed on page load. That fails with another “‘level1’ not found” error. That needed to be dynamic.

But with level 1 computed on page load and level 2 computed dynamically, it still failed with the same error and the first stack trace above.

Trying to compute both on page load (the second level wouldn’t change often either), I got a different stack trace, but still the same “‘level1’ not found” error:

That prompted me to look at the Java class corresponding to the custom control I was using. For anyone who’s worked with XPages for a while, it’s always a good idea to have a look at those files and see how the XML on the source pane corresponds to the code that actually gets run by the server. The files can be found in Package Explorer in the “Local” source folder (after building the project), in the “xsp” package. That was very informative and probably explains the reason it’s failing. It also implies why it might not be possible to have nested RepeatTreeNodes at all.

Explanation

With a normal Repeat Control, if you look at those files, you’ll see a UIComponent object returned from a method starting “create…” and then having the ID of the relevant Repeat Control, e.g. createRepeat1. You’ll see a separate UIComponent for all controls you added within the repeat as well as the nested repeat. You’ll see all take “parent” as a parameter – the parent component. That’s why it’s a component tree.

Then if you go to the top of that class, you’ll see private static final ComponentInfo[] s_infos and some definitions with the component ID as a comment beside each. If a component has no children, it’s defined with ComponentInfo.EMPTY_NORMAL. Otherwise it creates a new ComponentInfo object, with an int array for all children – the number in the int array is the number in the comment + 1, so if it says “3” in the array, look for the comment entry for “2” etc. This is how each component knows which its parent is.

Going back to the Java class for the custom control with the Navigator and RepeatTreeNodes, you’ll see it’s very different. The Navigator and all nodes are a single component. Although it appears to be a tree in the XML, it’s not separate components, just a single component. Although at the bottom of the method it adds children to their parents, the code above it computes each child independently.

The RepeatTreeNode has a setVar() method to give it the variable name, but I’m always reminded of a blog post from Tim Tripcony “the reason panel data sources can’t be accessed outside the panel“. That blog post is critical to understanding XPages and understanding the component tree processing. If you haven’t read it, go and read it now.

Now think about that “level1” the errors were complaining about not finding – the var of the RepeatTreeNode. With a normal Repeat Control, that var will get put in requestScope, so anything that references it will find the value it needs. What happens with the RepeatTreeNode?

If everything is computed on page load, and you run debug, and check the keys in requestScope, “level1″ doesn’t get set at any point during the method creating the Navigator. That’s why it errors when both are computed on page load – because it can’t compute the value property of the inner RepeatTreeNode, which needs to happen at page load. With nested repeats, you would need to set repeatControls=”true” on the outer repeat. But there is no such setting for a RepeatTreeNode.

With the inner RepeatTreeNode set to computed dynamically, it doesn’t try to compute the value property on page load, so doesn’t error. It doesn’t have a problem until trying to get the value in the Render Response phase. Here we need to go to Extension Library source code (thankfully open sourced) and the com.ibm.xsp.extlib.tree.complex.RepeatTreeNode class. As expected, if you run in debug and add a debug point at the place of the error, you’ll see requestScope does have a “level1” variable there, but it’s null. Hence the error. It’s scoped, but not scoped for all descendants.

So where does it get added and removed from requestScope? I added a debug point in the getLabel() method of BasicComplexTreeNode, one of the antecedents of the ComplexContainerNode, to see what happens when the BasicContainerNode that is a direct child of the first RepeatTreeNode has its label set. At that point, requestScope does have “level1” and it does have the required value. Moving one level up the stack trace at that point explains why. That’s called from TreeNodeWrapper.getLabel():

The key parts are highlighted. They’re in most of the methods in the TreeNodeWrapper and, if you looks at those methods, they set var and indexVar in requestScope. This means those variables are only available to direct children, but are not available to further descendants.

Looking back to hose those are created explains how it knows var and indexVar. Specifically starting in the iterateChildren() method of the RepeatTreeNode. It creates a RepeatIterator, adding each immediate child as a RepeatNode (an instance of the TreeNodeWrapper class). The var and indexVar get passed into the constructors of those classes.

It looks from TreeNodeWrapper.iterateChildren there’s an attempt to pass the var down the line to descendants, but it doesn’t seem to work and I can’t work out how obviously to fix it. As it happens, I’m taking an alternative route, just using a single level of repeat nodes. Nonetheless, this investigation may others (and even myself) who run into the same problem in the future.

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.