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:
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.
- JS encounters “.form”. It asks the XFA processor for the “form” property of the xfa object. XFA returns the form model object.
- JS encounters “.purchaseOrder”. It asks the XFA processor for the “purchaseOrder” property of the form object. XFA returns the purchaseOrder subform object.
- 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:
- JS encounters “resolveNode(…)”.It asks the xfa object to evaluate the method resolveNode(). XFA resolves the SOM expression and returns the city object
- 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.
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:
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”.
Country.resolveNode("#border").presence = "visible";
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:
"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:
<event name="event__click" activity="click">
<submit format="xml" textEncoding="UTF-8"
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.
You will often see references to "#subform". As in:
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:
We don’t need the  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.
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)
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
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:
The form looks like this:
order.price.rawValue * order.quantity.rawValue;
order.price.rawValue * 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
in order to find field objects.
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.