This WebForm Server friendly form demonstrates one way you can track what changes a user makes to a form. This approach is WebForm Server friendly because it does not use any Viewer-only event model options such as focused and focuseditem. Instead, the form records when the user changes the value of a field, or triggers a button to add or delete rows in a table. These events are captured using a combination of XForms actions and XFDL computes found in the form's input items, and are stored in a separate changeTrackingData instance in the XForms model. The changes captured in the changeTrackingData instance can be viewed via a table found on page 3 of the form.
In order for this change tracking mechanism to be re-purposed in another form you must add the following pieces to the new form :
1. The changeTrackingData instance must be copied to the default XForms model.
changeTrackingData XForms Data Instance
<xforms:instance id="changeTrackingData" xmlns="">
<data>
<changeCount>0</changeCount>
<changeList>
</changeList>
<templateChange>
<change>
<timeStamp></timeStamp>
<type></type>
<id></id>
<xpathRef></xpathRef>
<data></data>
</change>
</templateChange>
</data>
</xforms:instance>
The data being tracked in this sample are the type of change (data, table_add, or table_delete), the id of the xforms control used to make the change (if available), the full xpath reference of the node being changed in the model, the new contents of the node (or info about what row was added or deleted), and when the change occurred. But you can customize this data as you see fit.
2. A generic change tracking compute must be added to each field that wraps an xforms:textarea that has a fully qualified xpath reference. If you want to use this compute in other types of xforms controls (xforms:select1, xforms:input, etc...), the compute must be updated appropriately.
Generic onChange Compute For xforms:textarea
<custom:onChange xfdl:compute="(toggle(value) == '1') 

? set('instance("changeTrackingData")/templateChange/change/xpathRef', getAttr('xforms:textarea', 'option', 'xforms:ref'), '', 'xforms') 

+. ((getAttr('xforms:textarea', 'option', 'xforms:id') != '') 

? set('instance("changeTrackingData")/templateChange/change/id', getAttr('xforms:textarea', 'option', 'xforms:id'), '', 'xforms') 

: '' 

) 

+. set('instance("changeTrackingData")/templateChange/change/data', value, '', 'xforms') 

+. set('instance("changeTrackingData")/changeCount', get('instance("changeTrackingData")/changeCount', '', 'xforms') + '1', '' ,'xforms') 

: ''"></custom:onChange>
3. A "for tables" change tracking compute must be added to items using relative xpath references (such as items in tables) AND then customized for your table or other type of pane. Customization involves replacing the "instance("formData")/residents/persons/person" portion of the xpath reference being built by this compute with the nodeset of your xforms:repeat or xforms:group. Once updated, this compute can then be copied into every field in this table.
A "For Tables" onChange Compute For xforms:textarea
<custom:onChange xfdl:compute="(toggle(value) == '1') 

? set('instance("changeTrackingData")/templateChange/change/xpathRef', '*instance("formData")/residents/persons/person*[' +. xforms.getPosInSet() +. ']/' +. getAttr('xforms:textarea', 'option', 'xforms:ref'), '', 'xforms') 

+. ((getAttr('xforms:textarea', 'option', 'xforms:id') != '') 

? set('instance("changeTrackingData")/templateChange/change/id', getAttr('xforms:textarea', 'option', 'xforms:id'), '', 'xforms') 

: '' 

) 

+. set('instance("changeTrackingData")/templateChange/change/data', value, '', 'xforms') 

+. set('instance("changeTrackingData")/changeCount', get('instance("changeTrackingData")/changeCount', '', 'xforms') + '1', '' ,'xforms') 

: ''"></custom:onChange>
4. Add xforms:actions to any buttons (wrapping xforms:trigger) that you want to know are clicked. For example, a delete button for a table might have the following action.
A xforms:action To Add To Buttons
<xforms:action ev:event="DOMActivate">
<xforms:setvalue ref="instance('changeTrackingData')/templateChange/change/type">table_delete</xforms:setvalue>
<xforms:setvalue ref="instance('changeTrackingData')/templateChange/change/id">PERSON_REPEAT</xforms:setvalue>
<xforms:setvalue ref="instance('changeTrackingData')/templateChange/change/data" value="index('PERSON_REPEAT')"></xforms:setvalue>
<xforms:delete at="index('PERSON_REPEAT')" nodeset="instance('formData')/residents/persons/person"></xforms:delete>
<xforms:setfocus control="PERSON_REPEAT"></xforms:setfocus>
<xforms:setvalue ref="instance('changeTrackingData')/changeCount" value="instance('changeTrackingData')/changeCount + 1"></xforms:setvalue>
</xforms:action>
5. The invisible CHANGECOUNT1 field must be copied to every page of the form containing "change tracking" code. You will notice the previous computes and actions that you added to input items add information within "instance('changeTrackingData')/templateChange/change" and then increment the "instance('changeTrackingData')/changeCount". This invisible field watches the changeCount and when it changes is responsible for committing the latest tracking data by copying the templateChange/change node to the the changeList portion of the changeTrackingData instance.
CHANGECOUNT1 Field
<field sid="CHANGECOUNT1">
<xforms:textarea ref="instance('changeTrackingData')/changeCount">
<xforms:label></xforms:label>
<xforms:action ev:event="xforms-value-changed">
<xforms:setvalue ref="instance('changeTrackingData')/templateChange/change/timeStamp" value="now()"></xforms:setvalue>
<xforms:setvalue if="instance('changeTrackingData')/templateChange/change/type = ''" ref="instance('changeTrackingData')/templateChange/change/type">data</xforms:setvalue>
<xforms:insert at="last()" context="instance('changeTrackingData')/changeList" nodeset="instance('changeTrackingData')/changeList/change" origin="instance('changeTrackingData')/templateChange/change" position="after"></xforms:insert>
<xforms:setvalue ref="instance('changeTrackingData')/templateChange/change/type"></xforms:setvalue>
<xforms:setvalue ref="instance('changeTrackingData')/templateChange/change/xpathRef"></xforms:setvalue>
<xforms:setvalue ref="instance('changeTrackingData')/templateChange/change/id"></xforms:setvalue>
</xforms:action>
</xforms:textarea>
<itemlocation>
<x>486</x>
<y>45</y>
<width>111</width>
</itemlocation>
<label>Change Count</label>
</field>
Notes
Hopefully this invisible field will spark some creative ideas of your own. Not only is it an example of how you can modularize common code by placing it in a single field that other form events can trigger, but it also demonstrates how to bridge the gap between XFDL and XForms. Sometimes you need a feature only available in XForms (like inserting a row in a repeating section of your instance) but you are responding to a form event within an XFDL compute. This approach allows you to get the best of both worlds.
This samples shows how you can use the xforms.getAttr function from within an XFDL compute in order to get information about the XForms control that the current XFDL item wraps. If you look at the onChange compute you can see how it can be used to get the ref and id attributes of an xforms:textarea.