Wednesday, April 20, 2005

Using XPath preceding-sibling in InfoPath rules

Several of our InfoPath forms have a repeating table that contains details about the entity being edited, e.g. an order with zero, one or more order lines for the products that makes up the order.

In this example I will outline how to add a rule to the repeating table that assigns incremental values to an element in a row based on the value of the preceding row's same element. I will also outline how to add a rule to the form's 'Open behavior' to set the seed value for this element in the first row of the table. This will ensure that the rules of the table will work as expected.

The XML used in this example has this structure:
<PaymentPlan>
<PaymentPlanRow>
<Year/><Product/><Amount/>
</PaymentPlanRow>
<PaymentPlanRow>
<Year/><Product/><Amount/>
</PaymentPlanRow>
</PaymentPlan>


To add a rule to the 'new table row' event, start by opening the properties dialog for the repeating table and click 'Rules'. Do not add the rule to the table cell, ensure that you add it to the table. Add a rule called 'SetRowYear' with a condition that the 'Year' field is blank. Then add an action of type 'Set a field's value' to calculate the next value for the 'Year' element. Enter this formula to look up the value of the preceding row's 'Year' element, convert it to a number and add one:

number(preceding-sibling::PaymentPlanRow[1]/Year) + 1

The XPath preceding-sibling axis consists of all nodes that have the same parent as the current node, from the current node and up to the start. The following-sibling axis consists of the nodes from the current node and down to the end of the parent's child nodes set. The important thing to notice is the index into the node set: [1], as when XPath is used to get a value from a set of nodes, it will always take the first node in document order when an index is not applied. Thus, if you do not specify the node set index, the value returned will always be that of the first row in the table, not the preceding row. Note that the index is relative from the current node; i.e. preceding-sibling[1] is the previous row, while preceding-sibling[2] is the row before that again.

There are several ways to set the value for the first row's 'Year' field to ensure that the above formula has a seed value. I have used a rule when the form is opened. Start by opening the 'Form Options' dialog and go to the 'Open and Save' folder, then click on 'Rules' in the 'Open Behavior' section. Add a rule called 'SetInitialYear' with a condition that the 'Year' field is blank. The condition will ensure that the value will not be set when opening an existing form. Then add an action of type 'Set a field's value' to calculate the seed value for the 'Year' element. Enter this formula to use the current year:

substring(today(); 1; 4)

These examples shows how you can use rules to perform node set operations, calculations and setting of values, without having to use form programming and without having to manipulate the XML DOM with JScript.

7 comments:

Anonymous said...

Wonderful... this post was still useful 2 years later. Thanks!

Unknown said...

And 4 years later too!
thanks

Anonymous said...

Even now!

Unknown said...

+1 for still being very very useful!

Anonymous said...

... and of course this is still useful --- even in 2011...

Jeremy Rose said...

Proving useful in 2012! Thank you!

Anonymous said...

How do you count the values in a preceding column of a horizontal repeating table independent of the overall table. I have a horizontal repeating table with several fields as option buttons. The option buttons are set up such that each give a different value 1-3. As a user adds columns I want to count the number of 1's-3's seperate of the other columns. I have tried the preceding-sibling but can't seem to get it to work.

Count((Count(preceding-sibling::field1)+1)[.=1])... etc.