resolveNode vs. resolveNodes

Today I’d like to poke at a design pattern I see fairly often.  The code looks like this:

var sum = 0;
var numberOfDetails = po._detail.count;
for(var i = 0; i < numberOfDetails; i++) {
    var nodeName = "po.detail[" + i + "].subtotal";
    sum += this.resolveNode(nodeName).rawValue;
}
            

Notice how inside the loop we’re constructing a SOM expression to use for resolveNode(). The constructed SOM expression will look something like: po.detail[2].subtotal.

Let’s look at the alternative:

var sum = 0;
var details = this.resolveNodes("po.detail[*].subtotal");
for (var i = 0; i < details.length; i++) {
    sum += details.item(i).rawValue;
}

The second variation is easier to read, easier to code and will be processed more efficiently.  Why didn’t the author code it this way to start with? Likely either: a) they didn’t realize that a single SOM expression could find all their nodes or b) they didn’t realize there is both resolveNode() and resolveNodes().  And since I don’t know the reason, let’s unpack both of those topics.

SOM expression to return multiple nodes

Look again at the second SOM expression: po.detail[*].subtotal. As this SOM expression is processed, it will first find all the detail subforms under po, and then for each of those detail subforms it will check for a child named subtotal and add it to the returned list.  I suppose what’s surprising is the realization that the wildcard can go anywhere in the SOM expression.  In fact, it’s permissible to have multiple wildcard expressions.  Suppose I had a form with a list of family members who each had a list of pets.  To get a list of all the pet names, I could use this expression: family.member[*].pet[*].petName

resolveNode vs resolveNodes

The difference between these methods is in what result you’re expecting. resolveNode() expects to process a selection that returns a single node. If you pass it an expression that returns multiple nodes, it will cause an error.  The return value of resolveNode() is either a node or null.

resolveNodes() expects to return a list of nodes.  Its return value is a nodelist — which could be empty.  The nodelist has two methods for traversal: list.length (return the number of nodes in the list) and list.item(n) return the nth item.

And while I’m on the topic, there are a couple other interesting things about these methods.  They begin their search from the context of the node they’re called from.  That means the result of total.resolveNode() will be different from the result of xfa.resolveNode().  For example, suppose my form/data has the structure:

<purchaseOrder>
  <po>
    <item>
      <subtotal/>
    <item>
    <item>
      <subtotal/>
    <item>
    <item>
      <subtotal/>
    <item>
    <total/>
  </po>
</purchaseOrder>

 

For the total calculation, we want a list of all the subtotal fields.  If we anchor the search from xfa, it looks like:

xfa.resolveNodes("form.purchaseOrder.po.item[*].subtotal");

If we anchor it from within the total calculation, it looks like:

total.resolveNodes("item[*].subtotal");

The second variation is preferred.  It’s easier to read, and since it references fewer nodes, it’s more durable — less susceptible to breaking if a node gets renamed or a subform added/removed. 

By the way, Niall O’Donovan (active contributor in the forums and a regular commenter) has written a blog post that does a great job of explaining SOM expression: http://www.assuredynamics.com/index.php/2011/05/som-expressions/. Niall has a gift for explaining the basics of the technology. The post includes a very spiffy sample form that helps visualize SOM expressions. (he also constructed SOM expressions, but he’s certainly not the only one…)

The Deep End

There are a few more things that could be said to round out the picture for those of you who like to dive one layer deeper.

The .all property

The object model has a couple of properties that can be used in place of a wildcard SOM expression. The .all property will return a list of all sibling nodes that have the same name. I could also have used the .all property in my original sum calculation:

var sum = 0;
var details = po.detail.all;
for (var i = 0; i < details.length; i++) {
    sum += details.item(i).subtotal.rawValue;
}

Of course, .all is not as powerful as [*].  We can’t code po.detail.all.subtotal.  The other problem with this calculation is that it presumes there’s at least on instance of the detail subform.  If there are no instances, the script will fail.

We also have the .classAll property to return all sibling nodes of the same type. If you use .classAll on a field, you will get a list of all the sibling fields. For example po.detail.classAll is the same as po.resolveNodes("$.#subform[*]")

Evaluation in scope

A relative SOM expression will search through the entire scope to find the result. In the example: total.resolveNode("item[*].subtotal") we will search for item among the children of all the ancestors in our hierarchy.  Specifically, the search will check the children of: total, po, purchaseOrder, form and xfa.  Of course, we stop searching as soon as we find a match. In this case we’d stop at po.  If you want to prevent this hierachy search, then you can anchor the search with a reference to "$" (the current node).  In our example that would look like:

total.resolveNodes("$.parent.item[*].detail");

Rule of Thumb

If you find yourself constructing a SOM expression with string concatenation, I’d encourage you to have a second look and see if there is an easier way to get the result. I think it’s rare to need to construct an expression.  The one exception is if you’re building an expression that uses a predicate.  But other than that case there is almost always an alternative.