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.

4 Responses to XFA 3.0: Event Propagation

  1. Keith Gross says:

    I’ve been looking for sometime for a way to do dynamic event binding with XFA as we do within a web page or as I understand AcroForms did with setAction. It occurs to me that the new event propagation could be used to cobble something together but thought I’d ask if there is some more direct way of doing it.I was wondering if the ref attribute can be predicate expression thus binding to a choosen set of nodes.

  2. Keith:There’s no way to add new events to a form — or to change the target for an existing event. And I’m not aware of any plans to move in that direction.But if that’s what you need to do, then you could probably simulate it with the new listener capability. I’m guessing your scripts would have logic that had them “opt-in” for specific fields under certain circumstances.John

  3. Keith Gross says:

    With the increasing adoption of Reader 9.1 I’ve been looking at using event propagation in our forms framework to eliminate a lot of boiler plate code. I hacked together a proof of concept for this but having been having a problem in one area. I use xfa.event.target to get reference to the real target of the event and it generally works but in initialize events it doesn’t seem to be defined. I can probably work around this but in some cases it will complicate matters. Is this intentional, a bug or am I possibly doing something wrong?