XFA 3.0: Event Propagation

One of the themes for XFA 3.0 development was to make form development easier.  More specifically, to make the common things easy — and to reduce the amount of JavaScript code needed to develop a good user experience.  There were several enhancements geared toward achieving this goal.  Today’s blog entry describes one of them.

I have seen many forms where the author wants to implement the same behaviour on many fields.  For example: an author wants the field with focus to have a different background colour. They add "enter" and "exit" events on every field to set/reset the colour.  After a while, this gets… tedious and error-prone.  Especially when you need to tweak the script that has already been added to all your fields.

To improve this situation we have enhanced the <event> element in XFA 3.0 by adding the listen attribute.

<event activity="<enum>"
       name="<name>"
       ref="SOM" 
       listen="refOnly | refAndDescendents">

To understand the new listen attribute, you need to fully understand the functionality of the existing ref attribute.  The ref attribute is a SOM expression that indicates which object is listening to the event.  By default, ref has a value of "$" : indicating the current object.  The only time Designer sets this to anything other than "$" is:

  • for pre/postSave, pre/postPrint, docReady and docClose the ref target is "$host". 
  • for pre/postSubmit the ref target is "$form"
  • for ready the ref target is "$form" or "$layout" (form:ready, layout:ready)

What you probably already realize after reading this far is that the event definition can be hosted anywhere.  e.g. the enter event of field1 can be physically located in field2 as long as the ref attribute points back to field1.

The new listen attribute modifies the behaviour of the ref attribute.  It defaults to "refOnly" (the legacy behaviour) — where the event applies to only the object pointed at by ref.  When listen is set to "refAndDescendents", the event now applies to the referenced object and all nested objects. 

Example

If we want a single enter event script to apply to all fields in the form, we construct it like this:

<event activity="enter" name="event__enter" ref="$form" listen="refAndDescendents">
  <script contentType="application/x-javascript">
     if (xfa.event.target.className == "field")
        xfa.event.target.fillColor = "100,100,255";
  </script>
</event>

Now the event and script are specified in one place and apply to all containers in the form. 

Notes about the script:

  • The context for the script is the object that hosts the event.  If this event were hosted by the root subform, the JavaScript "this" object would be the root subform.
  • The target of the script — the object pointed to by the ref attribute is represented by the xfa.event.target property.
  • The script in my example guards its logic to work only on field objects.  The event propagates to all nested subforms, fields and exclusion groups, but we want it to affect only fields.  Without this guard in place, the entire form would turn blue the first time we set focus — since we would end up setting the fillColor of the root subform to blue.

Multiple Events

What you might have inferred from this discussion is that a single field may have multiple enter (or other) events. This has always been true — it’s just that Designer’s UI supports only one of each kind of event per object.  (That’s not a criticism of Designer — it’s by far the most common case to have at most one of each kind of event).

Now that we have propagating events, it is more likely that a field might end up with multiple enter events– a global enter event defined on the root subform and a local enter event defined on a field.  That raises the question:  what order will the events fire in?  The official answer is that the order is undefined.  The XFA spec does not prescribe in what order events will fire.  The unofficial answer is that the events will fire in document order.  That implies that the globally defined event will fire before the local event, since the global event is defined higher up in document order.

The Deep End

This change brings XFA event processing in closer alignment with XML event processing: http://www.w3.org/TR/xml-events. XFA events can now "bubble" in manner similar to XML events. Note however that XML events bubble from the target node upward through the target node’s ancestors; in contrast, XFA events are dispatched in document order.