Data Binding with Predicates

| 11 Comments

Have you ever come across “inverted XML”? Take this example:

<addresses>
  <row>
    <column name="first name">Halla</column> 
    <column name="last name">Ayers</column> 
    <column name="street address">7466 Etiam Avenue</column>
    <column name="city">Arlington</column>
    <column name="state">BC</column>
    <column name="postal">T4K4O7</column>
   </row>
</addresses>

The question is, how do we bind a “firstName” field to the data value: <column name=”first name”>?

We would much prefer to deal with this data in the form:

<addresses>
  <row>
    <firstName>Halla</firstname> 
    <lastName>Ayers</lastName> 
    <streetAddress>7466 Etiam Avenue</streetAddress>
    <city>Arlington</city>
    <state>BC</state>
    <postal>T4K4O7</postal> 
  </row>
</addresses>

Then we would bind our firstName field to addresses.row.firstName. Easy.

But we do not always have control over the format of the data that we receive. If we are given the inverted form of the XML we have two choices:

  1. Convert to non-inverted XML using XSLT
  2. Bind to the data using predicate expressions

My preference is to avoid XSLT when at all possible. It would be very tricky with this example, because the names have spaces in them, so cannot be re-used as XML element names. Instead we use predicate expressions for binding.

Predicates

We find predicate filtering in both XPath and E4X.
The XPath expression to identify the “first name” value from the sample is:

addresses/row/column[@name=="first name"]

The E4X expression is:

addresses.row.column.(@name=="first name")

The XFA SOM expression is either:

addresses.row.column.[name=="first name"]

Or

addresses.row.column.(name.value=="first name")

The difference between the two XFA variations is that when brackets [] are used, the expression inside the brackets is evaluated as Formcalc. When parenthesis () are used, the expression is evaluated as JavaScript.

In all these examples, predicates are evaluated the same way. We take each candidate instance of the <column> element and evaluate the predicate expression in the context of that element. When the expression returns true, the element is selected.

Data Binding

This sample form (and sample data) has fields with binding expressions that display the set of addresses.

The (repeatable) address subform binds to addresses.row[*]

The firstName field binds to $.column.[name == "last name"]

The lastName field binds to $.column.(name.value == "last name")

Data Creation Problem

One significant issue with using predicates for binding is that we cannot easily create new instance data. i.e. it is not possible to infer the XML structure that corresponds to the binding expression.  If we were to simply create a new instance of the address subform, the data we create would look like:

<row>
  <column name=""/>
  <column name=""/>
  <column name=""/>
  <column name=""/>
  <column name=""/>
  <column name=""/>
  <column name=""/>
</row>;

To get around that, we create our (empty) data before we create a new instance of the address subform. The script looks like this:

addresses.addRow::click - (JavaScript, client)
// define the data for a new row
var vNewRowData =
  <row>
    <column name="first name"/>
    <column name="last name"/>
    <column name="street address"/>
    <column name="city"/>
    <column name="state"/>
    <column name="country"/>
    <column name="postal"/>
  </row>;

// load the new row of data into the data DOM
addresses.dataNode.loadXML(vNewRowData.toString(), false, false);

// add a new instance of the address subform,
// and tell it to bind to the new data 
_address.addInstance(true);

Notice that the address row has been specified according to the data schema and has been inserted into our data DOM before the address subform was created.  Then when we create a new instance of the address subform, it will bind to the data we pre-created.

The Deep End

Using this same data set, it is interesting to apply predicates in a slightly different way. In this example I wanted my form to display the US addresses first and the Canadian Addresses second. The form has separate subform definitions for each. If we had been working with the non-inverted form of the data this would have resulted in a pretty simple binding expression: $.row.[country == "US"]

But because of the inverted form of the data, the binding expression gets very complicated. We want to qualify the row element based on the column where name="country" and the value is "US". To get there we use a nested predicate to bind the row:

$.row.[ $.resolveNode("column.[name==""country""]") == "US" ]

The outer predicate filters <row> and the inner predicate filters <column>.

11 Comments

Very interesting: I didn't know predicate expressions could be nested.

Hi John, Thanks again for your very helpful posts.

I have started using the predicates in my listbox bindings but was hoping to be able to update the predicate expression at run time. That is filter the selection depending on other data entered into the form. I had hoped I could update the bindItems property of the listbox but this does not appear to be accessable from script? Is there a way of updating the predicate expression at run time.

Bruce:

I don't think you can get there from here.
Scripting against <bindItems> will not work because:
a) In order to re-bind your list, you need to call xfa.form.remerge()
b) Calling remerge() (reinitializing the form) will discard any changes made to the <bindItems> element

But there are other ways to get this done.
working from a specific example... Suppose I want to populate a listbox with english/french phrases based on:

<phrases>
<phrase lang="english">hello</phrase>
<phrase lang="french">bonjour</phrase>
<phrase lang="english">goodbye</phrase>
<phrase lang="french">au revoir</phrase>
</phrases>

Your current approach would attempt to choose one or the other by changing the predicate expression to one of:
$record.phrases.phrase.[lang=="english"]
or
$record.phrases.phrase.[lang=="french"]

But assuming that there is a form field holding the value of language, you could also build a single predicate expression based on the language field:
$record.phrases.phrase.[lang==$form.root.language]

But even with that expression, you still need to call xfa.form.remerge() before your list will repopulate.

Alternatively, you could populate your list with script that accesses the data directly:

var vList = xfa.record.phrases.resolveNodes("$.phrase.[lang==$form.root.language]");
this.clearItems();
for (var i=0; i<vList.length; i++)
this.addItem(vList.item(i).value);

good luck

John

Thanks John, I got my bindings working so as my user types a name into a textbox the people who match so far are displayed in a listbox (that becomes visible), they can then select the name once the list is small enough or scroll though and find it.

They seem happy with the way that worked.

My next binding problem, that I was hoping to get some help with is handling choice elements in my schema, for example.

<xs:element name="projects">
<xs:complexType>
<xs:choice>
<xs:element name="project1" type="project1Type" minOccurs="0" maxOccurs="unbounded" />
<xs:element name="project2" type="project2Type" minOccurs="0" maxOccurs="unbounded" />
</xs:choice>
</xs:complexType>
</xs:element>

I was then hoping to get a list of all the recommended projects using the expression;

$record.projects.*.[recommended == "1"]

Where recommended is an element in project1Type and project2Type.

But this returns a "Malformed SOM expression" error. Is there a syntax that should work?

Bruce:

It should work as you've specified it. Did the error message provide any more details on the nature of the failure?
One possible reason for the failure is that the data for one of your projects does not specify the recommended element. If this is the case, you could guard against that with an if block in your predicate expression:

$record.projects.*.[if (exists(recommended)) then recommended == "1" else 0 endif]

good luck.

John

Hi John,

Do these bindings work in Adobe LiveCycle Designer (client) or is it only on the server side?
I've tried these in Designer but I get validation errors...

Thanks.

David:

Predicate binding should work equally well on both client (Reader) and server (LiveCycle Forms/Output). Since LiveCycle Designer is the same tool for both client and server, it should behave the same way in Designer for both targets.

John

Hi John,

Thanks for the feedback. I'm just having a little difficulty in doing the binding in Designer. This bind tag produces a warning complaining that "The default binding value does not correspond to a data connection":

<bind match="dataRef" ref="$.Parties.Party[@PartyType='ORIG'].LastName"/>

This binding tag produces no warning:

<bind match="dataRef" ref="$.Parties.Party[0].LastName"/>

Where am I going wrong with the binding syntax in the first example?

Thanks again

David:
Your bind expression has a couple issues. You coded:
$.Parties.Party[@PartyType='ORIG'].LastName

1) You're missing the "." before the open brace of the predicate expression.
2) The attribute name does not include the "@" symbol.
3) Use "==" instead of "="

Try:
$.Parties.Party.[PartyType=='ORIG'].LastName

John

Hi John,

I'm having problems on multiple fields binding..

If I give 2 fields with same predicate binding,

For e.g.
$.TextField.(key.value=="fname")

Only the first field will get the data populated..

Leia:

Unfortunately you've hit a limitation on predicate binding. We can bind multiple fields to the same data value when it's clear that the binding expression returns a single result. But with predicate binding it is possible for the expression to return a list of nodes. Whenever we bind to a list of nodes we exclude those nodes that have already been bound.

That means you have two choices:
a) calculate your second field from the first field
b) transform your data with xslt. i.e. invert it from:
<TextField key="fname">Leia</TextField>
to
<fname>Leia</fname>

Going forward, there are some enhancements to data binding available in LiveCycle ES2 that could help you. But it will be a while before we see those changes in Acrobat/Reader

Good luck.
John

Leave a comment

About this Entry

This page contains a single entry by John Brinkman published on October 30, 2008 9:42 AM.

Numeric Constraint: no zeros was the previous entry in this blog.

Loop Through Subform Instances is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.