Posts in Category "Data Binding"

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