Data Binding with Predicates

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>.

18 Responses to Data Binding with Predicates

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

  2. Bruce says:

    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.

  3. 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> elementBut 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 luckJohn

  4. Bruce says:

    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?

  5. 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

  6. David Joyce says:

    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.

  7. 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

  8. David Joyce says:

    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

  9. David:Your bind expression has a couple issues. You coded:$.Parties.Party[@PartyType=’ORIG’].LastName1) 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’].LastNameJohn

  10. leia says:

    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..

  11. 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 fieldb) 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/ReaderGood luck.John

  12. Bruce says:

    Hi John, I’ve come to use the dataNode property of a bound field a lot as you have in this example. But this property is not documented. Is this just an oversight in the documentation? Is this something we can rely on in future releases of Reader?

    Thanks again for the great information

    Bruce

  13. Don Andrews says:

    Thank you, John, this is brilliant. It works great with scalar values but when I use an expression that I expect to return an array in JavaScript I can’t index the result. I have a table with a checkbox wrapped in a subform in the last cell in each row. The line

    var rows = xfa.resolveNodes(“status.Table1.Row1.(cbform.CheckBox1.rawValue==’Y’)”)

    completes successfully and rows.length equals the number of rows that were checked but attempts to reference rows[0] result in the error “rows[0] is undefined”. Is there some trick to handling the result from xfa.resolveNodes?

    • Don Andrews says:

      I guess rows.item(0) is the correct way to access the elements in the result list. Just took a while longer to figure that out.

      • Don:
        Glad you figured it out.
        As a point of style, I prefer not to anchor my calls to resolveNode(s) on xfa.
        Rather:
        var rows = status.resolveNodes(“$.Table1.Row1.(cbform.CheckBox1.rawValue==’Y’)”);

        John

  14. greennd says:

    I wish to be more clear on this topics. Your sample would be helpful but I do not get these.

    How can I get your valuable sample?