
The goal of this sample is to illustrate some of the expressive power of XForms binds and how an XPath nodeset in a bind is used at runtime to evaluate the result of a bind.
In this example the goal is to count the number of occurrences of specific text entries in a homogeneous set of XML elements. By homogeneous I just mean that each element has the same tag name and they are all siblings of each other. Like this:
<xforms:instance id="formData" xmlns="">
<data>
<fruits>
<item>apple</item>
<item>orange</item>
<item>banana</item>
<item>apple</item>
<item>apple</item>
<item>banana</item>
</fruits>
</data>
</xforms:instance>
Our goal is to count up each fruit type and store the result in the following nodes (I will refer to them as the "counting buckets"):
<xforms:instance id="variables" xmlns="">
<data>
<counts>
<apples value="apple"></apples>
<oranges value="orange"></oranges>
<bananas value="banana"></bananas>
</counts>
</data>
</xforms:instance>
So how do we do this? Well, to get started, we need to know about the XPath "count" function, which will tell us how many elements there are in a given nodeset. We can use an XForms bind for each of one of the counting buckets, which counts up that specific type of fruit, like this:
<xforms:bind calculate="count(instance('formData')/fruits/item[. = 'apple'])" nodeset="instance('variables')/counts/apples"/>
<xforms:bind calculate="count(instance('formData')/fruits/item[. = 'orange'])" nodeset="instance('variables')/counts/oranges"/>
<xforms:bind calculate="count(instance('formData')/fruits/item[. = 'banana'])" nodeset="instance('variables')/counts/bananas"/>
The "calculate" model item property (MIP) will evaluate the XPath we give it and store the output in the nodeset. Each of these binds counts up all the elements whose item is one of the fruit types. Ok, so that works, but wait, we could make use of the attribute on the "counting bucket" elements to indicate what they are counting by just referring to it in the bind nodeset, this will let us avoid hard coding the item that is being counted in the bind logic and keep that information in the data. To do this, we need to know about the "current()" function, an XForms function that will give us back the current nodeset context. When we use "current()" in a bind model item property (like calculate) we get back the node in the bind's nodeset that is currently being evaluated.
So if we change the binds like so:
<xforms:bind calculate="count(instance('formData')/fruits/item[. = current()/@value])" nodeset="instance('variables')/counts/apples"/>
<xforms:bind calculate="count(instance('formData')/fruits/item[. = current()/@value])" nodeset="instance('variables')/counts/oranges"/>
<xforms:bind calculate="count(instance('formData')/fruits/item[. = current()/@value])" nodeset="instance('variables')/counts/bananas"/>
We get the same functionality, but we've leveraged the value attribute of the counting buckets. Note that the "." in the XPath gives you the element referenced just to the left of the predicate, while the current() function gives you the element in the nodeset.
Now hold on to your hats, we're going to take it a step further! Once we make the adjustment above, note that the contents of the calculate is identical in all 3 binds. What this means is that we can combine the three binds together as long as we can make a nodeset that points to all the counting buckets. As it happens, we can make that nodeset, in one of two ways:
The "|" operator in XPath computes the union of its operands, which must be nodesets. We can use it like so:
instance('variables')/counts/apples | instance('variables')/counts/oranges | instance('variables')/counts/bananas
or, since these counting bucket elements all happen to be siblings of each other and have no other extraneous siblings we want to avoid selecting, we can use the "*" which is a wildcard, like so:
instance('variables')/counts/*
Which brings us to this one bind that does all we need:
<xforms:bind calculate="count(instance('formData')/fruits/item[. = current()/@value])" nodeset="instance('variables')/counts/*"/>
This bind provides an additional degree of flexibility, because if you think about it, if you need to add another item to be counted up (say, pears) you can do this without touching the bind, all you need to do is add a new "counting bucket".
Why it works: when you create a bind with a nodeset that selects more than one node (as our wildcard "*" XPath does), the bind will get evaluated for EACH of the elements that are in that nodeset. And for each element, relative references which might be in the bind's model item properties will be evaluated relative to that element (in this case our use of "current()" in the calculate MIP).
Attached you'll find a sample form which demonstrates the final bind.