Object Expressions in XFA

Form authors tend to resolve SOM (Script Object Model) expressions inside calls to resolveNode() more often than they need to. As a result, their JavaScript code is less readable than it could be. There are alternatives coding patterns I can recommend, but first some background.

Object expressions vs SOM expressions

If you wanted to get the value of a field in a purchase order form, there are a couple of ways to get it:

xfa.form.purchaseOrder.shipTo.address.city.rawValue;

or

xfa.resolveNode
           ("xfa.form.purchaseOrder.shipTo.address.city").rawValue;

The first form is an object expression.  In the second form, the argument passed to resolveNode() is a SOM expression. These are not evaluated the same way, but they almost always return the same result.

Dotted expressions are evaluated by the JavaScript interpreter. Every time the JavaScript interpreter reaches a dot in an expression, it calls into the XFA processor to evaluate the token following the dot. De-constructing the first example:

  1. The JavaScript engine encounters “xfa”. It resolves this easily since “xfa” has been pre-registered with the engine as a known global object.
  2. JS encounters “.form”. It asks the XFA processor for the “form” property of the xfa object. XFA returns the form model object.
  3. JS encounters “.purchaseOrder”. It asks the XFA processor for the “purchaseOrder” property of the form object. XFA returns the purchaseOrder subform object.
  4. We continue to evaluate dots until we reach the “city” object. Then when JS asks XFA for the “rawValue” property of the “city” object, XFA returns a scalar value.

De-constructing the second example:

  1. The JavaScript engine encounters “xfa”, and returns the pre-registered xfa global object..
  2. JS encounters “resolveNode(…)”.It asks the xfa object to evaluate the method resolveNode(). XFA resolves the SOM expression and returns the city object
  3. JS encounters “.rawValue” and asks XFA for the rawValue property of city.

You can see we take two very different paths, but have ended up in the same place.

The main difference is that the object expression yields more readable code, but is less efficient. Resolving SOM expressions internally is more efficient than the many more round-trips from JS to the XFA engine. However, in the vast majority of cases, the performance difference is negligible in the context of overall processing. My bias is toward using object expressions for the sake of code readability – unless we know we are in an area of performance critical code.

Note that FormCalc handles object expressions differently from JavaScript. The expression:

xfa.form.purchaseOrder.shipTo.address.city.rawValue is not evaluated dot-by-dot. The FormCalc engine will pass the entire expression to XFA for evaluation as a SOM expression.

Using "#" in SOM

We use the "#" character to disambiguate properties (font, border, caption etc) from containers (fields, subforms etc). Using "#" tells the SOM parser to treat the following token as a className rather than as a named object. Take this example:

<subform name="Country">
    <border/>
    <field name="border"/>
</subform>

The expression “Country.border” is ambiguous. It could refer to either the <border> property or the field named “border”. The rules are that by default the named object takes precedence. If you want to explicitly get the border property you must use “Country.#border”.

Since the "#" character is illegal in JavaScript, any expressions that use it are usually wrapped in a call to resolveNode(). E.g.:

Country.resolveNode("#border").presence = "visible";

As it turns out, there is a more terse syntax available. JavaScript properties can be accessed by two different syntaxes. e.g. A.B can also be expressed as: A["B"]. So the expression above could also be coded as:

Country["#border"].presence = "visible";

But simpler yet, if we are not worried about ambiguity, we can code this as:

Country.border.presence = "visible";

(As I write this, I realize that some of the script in my samples is not careful enough. Any general purpose re-usable code that references the subform.border property really must use the #border notation.)

As I said, in the case where you are not worried about ambiguity, you do not need to qualify with the “#” symbol. In the case with field properties we don’t really need to worry about name conflicts. Unlike subforms, fields do not have children with user-defined names. (ok, that’s not true, but let’s pretend it is for now.) You should be able to code “field.border” without worrying about ambiguity.

Accessing field.property instead of field["#property"] is possible as long as the property does not have an unlimited number of occurrences. This means that we cannot refer to “field.event” – because fields can have any number of <event> elements.

We’ll work through this case with an example. I have seen code that changes the email address of a submit button that looks something like:

this.resolveNode("SubmitButton.#event[0].#submit").target =  
                                       "mailto:" + email.rawValue;

For starters, this could be expressed more tersely as:

SubmitButton["#event"].submit.target = "mailto:" + email.rawValue;

But do not do this.  This is fragile code. It assumes there is only one event, and addresses the first <event> element. To
make this easier, Designer has started populating the name property on the <event> element:

<field name="SubmitButton"> 
  <event name="event__click" activity="click"> 
    <submit format="xml" textEncoding="UTF-8" 
                               target="mailto:foo@foo.com"/> 
  </event>
</field>

As a result, the script can now be reliably be coded as:

SubmitButton.event__click.submit.target = "mailto:" + email.rawValue;

Finally, to close the loop on what I said above: “fields do not have children with user-defined names.”. As you see, events (and a couple other properties) can have arbitrary names. But we shouldn’t have to worry about name collisions. There is no interface in designer for naming these properties. When Designer names them, it generates unique names. As long as nobody has modified their XML source and changed their event to look like <event name="border">, then field.border remains unambiguous.

Transparent Subforms

You will often see references to "#subform[0]". As in:

xfa[0].form[0].form1[0].#subform[0].Button1[0]

The reason you see “#subform” in this expression is because the subform is nameless.

This is the most explicit possible SOM expression that leads us to Button1.

However, this expression also gets us there:

xfa.form.form1.Button1

We don’t need the [0] references because by default, object expression will always return the first occurrence.

The reason we can omit the #subform is because nameless subforms are treated as transparent.

In this example that means that all the children of the nameless subform may be referenced as if they were children of form1.

Performance

As noted above, SOM parser evaluation is faster than JavaScript object evaluation.  If you have an expression that must be executed many, many times, then it is better to wrap it in resolveNode() or use FormCalc.  However, for most forms with modest amounts of script, the difference in overall form performance is negligible. Most of the time ease-of-scripting and code readability are more important.

However, whether using SOM or not, reducing evaluations is good practise.

Beware of coding patterns that look like this:

xfa.form.form1.header.address.Button1.presence = "hidden";
xfa.form.form1.header.address.Button2.presence = "hidden";
xfa.form.form1.header.address.text1.presence = "hidden";
xfa.form.form1.header.address.text2.presence = "hidden";
xfa.form.form1.header.address.text3.presence = "hidden";

In cases like these, it is more efficient (and readable) to resolve a variable to the common parent node e.g.:

var addr = xfa.form.form1.header.address;
addr.Button1.presence = "hidden";
addr.Button2.presence = "hidden";
addr.text1.presence = "hidden";
addr.text2.presence = "hidden";
addr.text3.presence = "hidden";

When resolveNode() is Necessary

Some parts of SOM syntax are not supported by the object evaluation of our script engines. I can think of two examples:

  • SOM predicates (see previous blog entry)
  • The elipsis operator (..) that recurses down the hierarchy finding nodes. While it is handled by FormCalc, it is not legal in JavaScript.

Here is a sample script that shows how these can be combined using resolveNode() to make all hidden fields in a nested subform visible:

var vHiddenList = xfa.resolveNodes('xfa.form..address.#field.[presence == "hidden"]');
for (i=0; i<vHiddenList.length;i++)
  vHiddenList.item(i).presence = "visible";

The Deep End

Differences

When comparing object expressions to SOM evaluation I said: “These are not evaluated the same way, but they almost always return the same result.”

You might want to know under what circumstances they will return different results.  The difference between object expressions and SOM evaluation is that SOM evaluation will take into account the relative occurrence numbers (index) of objects, while object expressions cannot. In the example below we have fields bound to this data:

<order>

  <price>10.00</price>

  <quantity>2</price>

  <price>20.00</price>

  <quantity>3</quantity>

</order>

The form looks like this:

<subform name="order">

   <field name="price"/>

   <field name="quantity"/>

   <field name="subtotal">

      <calculate>

         <script contentType="application/x-javascript">

           order.price.rawValue * order.quantity.rawValue;

         </script>

      </calculate>

   </field>

   <field name="price"/>

   <field name="quantity"/>

   <field name="subtotal">

      <calculate>

         <script contentType="application/x-javascript">

           order.price.rawValue * order.quantity.rawValue

         </script>

      </calculate>

   </field>

</subform>

The calculations for the subtotal fields will both return the result “20.00”. This is because when we evaluate the expression: order.price and order.quantity from the context of the subtotal field, the JavaScript engine will always return the first occurrence of the price and quantity fields – for both occurrences of the subtotal field. However, we could change the calculation to:

subtotal.resolveNode("order.price").rawValue *

   subtotal.resolveNode("order.quantity").rawValue

In this case the second occurrence of the subtotal field would evaluate to “60.00”. This is because the occurrence number of the subtotal field causes the SOM evaluator to return the second occurrence of the price and quantity fields. If we were using FormCalc, then the expression order.price * order.quantity would return the expected result, since FormCalc uses SOM natively.  Fortunately, we don’t encounter this pattern very often, so most form authors can assume that there is no difference between SOM evaluation and object expression.

Naked Field references

Based on what I’ve described above, you might wonder how we are able to resolve “order.price” in JavaScript. How does JS resolve a naked reference to “order”? We can’t register all field names with the JavaScript interpreter. If you were used to coding in Acroforms, you used expressions like

this.getField("order")

in order to find field objects.

The way the XFA processor works is that when JavaScript encounters an unrecognized object, it tries to resolve it as a property of “this”. In our example, “order” is really evaluated as “this.order”. Technically the subtotal field doesn’t have a property called “order”, but XFA does a check to see if there is an object called “order” that is in scope of subtotal.

While this is really great for keeping the code easy to write and readable, it has a side-effect that causes grief. When encountering an unknown token, the JavaScript engine first asks the XFA processor to resolve the token, and if not found, then checks JavaScript variables.  This order of evaluation causes issues for a calculation script that looks like:

var x = 42;

field.rawValue = x;

In this example. the field will get populated with the value of the x property (this.x).  This is one of the reasons why I normally prefix my variable names with a "v", in order to reduce the likelihood of a conflict with a property name.

Note that FormCalc allows variables to take precedence over properties.  In FormCalc the script above would behave as expected and populate the field with "42".  The reason for the evaluation order in JavaScript is due to a limitation with the engine when this feature was first implemented.  As far as we know, that limitation is now gone.  As some point we would like to change the order of evaluation for JavaScript — but would do so in a safe way so that older forms continue to behave as they do today.