locating XPage components with XspQuery
Category xpages java
Several years ago, I wrote a utility Java class designed to make it easy to search for components within the current XPage instance based on various criteria. I've found it enormously useful, and, apparently, so has Keith Strickland, because he added it to org.openntf.xsp.extlib, complete with a few refinements. As an example of how you might use this, examine the following line of code:
Just in case the above isn't self explanatory, this line of code returns an iterable collection of all components in the tree that extend the
The basic premise upon which this utility class operates is that you feed it one or more "filters", then call
Here are all the filters it currently supports, expressed as method signatures:
These filter methods behave, respectively, as follows:
In addition to the filters listed above, this class supports several convenience methods, each of which call
As with
So you could, for example, update the server-side value of a single field from client-side JavaScript via JSON-RPC:
Or empty the values of all fields on the page:
Finally, Keith added a class called
You can view the source of all the files in this package here. If you want to use this functionality without installing the entire extension library this project represents, just open the history page for each class in the package, then click the "Raw" button, and save the .java file that button redirects you to.
Several years ago, I wrote a utility Java class designed to make it easy to search for components within the current XPage instance based on various criteria. I've found it enormously useful, and, apparently, so has Keith Strickland, because he added it to org.openntf.xsp.extlib, complete with a few refinements. As an example of how you might use this, examine the following line of code:
List<UIComponent> requiredFields = new XspQuery()
.addInstanceOf(UIInput.class)
.addEquals("required", true)
.locate();
Just in case the above isn't self explanatory, this line of code returns an iterable collection of all components in the tree that extend the
UIInput class and have a value of true for the required attribute... in other words, all required fields on the page.The basic premise upon which this utility class operates is that you feed it one or more "filters", then call
locate(), optionally passing it a context component. It starts at that context component (or the view root, if you didn't specify one), and spiders through all its descendants (its children, their children, and so on) looking for any that match all the criteria you specified. It flattens pointers to any matches it finds into a single List, which you can then process however you want. In short, it's effectively a server-side equivalent of dojo.query.Here are all the filters it currently supports, expressed as method signatures:
public XspQuery addContains(String propertyName, Object content);
public XspQuery addEndsWith(String propertyName, String content);
public XspQuery addEquals(String propertyName, Object content);
public XspQuery addInstanceOf(Class extends UIComponent> klass);
public XspQuery addStartsWith(String propertyName, String content);
These filter methods behave, respectively, as follows:
- The value of the specified property contains the specified text
- The value of the specified property ends with the specified text
- The value of the specified property is equal to the specified object
- The component can be cast as an instance of the specified class
- The value of the specified property begins with the specified text
Disclaimer 1: each of these filter addition methods returns theXspQueryinstance against which they're called as an optional convenience: as demonstrated above, returning the current object from each method allows chaining of the query's construction from declaration of the object iself straight through to the eventual execution of the query. As a general rule, you don't want to do this in Java. In theory, any method of any class could throw anException. When you chain methods (instead of performing each operation on its own line), it can be more difficult to debug problems when they arise, because it's not immediately apparent which of the methods you called failed in the line flagged as the point of failure. In the case ofXspQuery, none of its chainable methods perform any operations that could fail (with the possible exception -- sorry for the pun -- of an out of memory error), so it's almost guaranteed that if anything does fail, it's in the call tolocate()at the end. This type of chaining is nearly always going to be safe. But if you're concerned that this will cause you to form bad habits elsewhere, it would be wise to restructure the above example as follows:
XspQuery requiredFieldLocator = new XspQuery();
requiredFieldLocator.addInstanceOf(UIInput.class);
requiredFieldLocator.addEquals("required", true);
List<UIComponent> requiredFields = requiredFieldLocator.locate();
This obviously looks and feels more verbose, but ensures that if any individual operation fails, you know right away which operation failed. That's why it's best to stay in the habit of not chaining method calls. But in case you're comfortable that chaining your uses ofXspQuerywon't be a bad influence on your overall coding style, the terser syntax is supported and relatively safe.
Disclaimer 2: the tree navigation used byXspQueryis based on the children (and facets) of each component it examines. So if your page contains repeat controls, it will only find one instance of each descendant thereof, unless you've setrepeatControlstotrue, which you should never do unless you're content with preventing said repeat from becoming completely unaware of any changes to the collection to which the repeat is bound. So just keep that in mind if you're trying to useXspQueryto mess with components inside a repeat... and, if you are, there's almost definitely a better way.
In addition to the filters listed above, this class supports several convenience methods, each of which call
locate() internally:public UIComponent byClientId(String id, UIComponent root);
public UIComponent byId(String id, UIComponent root);
public UIInput locateField(String id, UIComponent root);
public List locateInputs(UIComponent root);
As with
locate(), each of these use the view root as the context component if an alternative is not passed to the method, and they behave, respectively, as follows:- Returns the component whose client ID (as rendered in the markup sent to the browser) matches the specified text
- Returns the first component whose server ID matches the specified text
- Returns the first editable field (instance of
UIInput) whose server ID matches the specified text - Returns all editable fields
So you could, for example, update the server-side value of a single field from client-side JavaScript via JSON-RPC:
new org.openntf.xsp.extlib.query.XspQuery()
.byClientId(componentId)
.getValueBinding("value")
.setValue(newValue);
Or empty the values of all fields on the page:
for (UIInput eachField : new XspQuery().locateInputs()) {
ValueBinding vb = eachField.getValueBinding("value");
if (!(vb == null || vb instanceof ValueBindingEx)) {
vb.setValue(null);
}
}
Finally, Keith added a class called
ComponentTreeMassage, which is a wrapper for a bunch of convenient static methods (meaning that you can just call them directly against the class itself without having to construct a separate instance each time) that use XspQuery to do all sorts of crazycool things... the easiest way to sum up all of what this class facilitates is to equate it to server-side DOM manipulation. Basically, if you want to construct (or restructure) your component tree on the fly, instead of defining the entire page at design time, this is the way you want to do it.You can view the source of all the files in this package here. If you want to use this functionality without installing the entire extension library this project represents, just open the history page for each class in the package, then click the "Raw" button, and save the .java file that button redirects you to.



