The problem
You have a table, such as:

You want users to be able to edit this data. You determine that putting form controls inside the table would not use the space in an efficient way, and instead want to enable users to edit this data by clicking on an
edit trigger you show on every row which shows a dialog, such as:

When users click on
OK, their changes are taken into account, but they are ignored when they click on
cancel, or click on the
x at top-right of the dialog.
The solution
Imagine you have a main instance with the data shown in the table:
<xforms:instance id="planets">
<planets>
<planet>
<name>Mercury</name>
<description>Mercury (0.4 AU) is the closest planet...<description>
</planet>
<planet>
<name>Venus</name>
<description>Venus (0.7 AU) is close in size to Earth...</description>
</planet>
</planets>
</xforms:instance>
Because you want users to be able to close the dialog without saving changes:
- You need to copy the information corresponding to the row being editing into another instance before opening the dialog.
- The controls in the dialog will point to that other instance.
- When users close the dialog with OK (instead of cancel), you copy back the changes to the main instance.
This example stores the planet being edited within
instance('ui')/edited-planet. The
ui instance is declared as:
<xforms:instance id="ui">
<ui>
<edited-planet/>
</ui>
</xforms:instance>
The
edit trigger that does the copying and opens the dialog is declared as:
<xforms:trigger appearance="minimal">
<xforms:label>Edit</xforms:label>
<xforms:action ev:event="DOMActivate">
<xxforms:variable name="current-planet" select="."/>
<xforms:insert context="instance('ui')/edited-planet"
origin="instance('planets')/planet[name = $current-planet/name]"/>
<xxforms:show dialog="edit-planet-dialog"/>
</xforms:action>
</xforms:trigger>
The
xxforms:show action refers to a dialog id (
edit-planet-dialog), which looks like:
<xxforms:dialog id="edit-planet-dialog">
<xforms:label>Edit Planet</xforms:label>
<xforms:group ref="instance('ui')/edited-planet/planet">
...
<xforms:output value="name">
<xforms:label>Planet</xforms:label>
</xforms:output>
<xforms:textarea ref="description" class="edit-planet-textarea">
<xforms:label>Description</xforms:label>
</xforms:textarea>
...
</xforms:group>
</xxforms:dialog>
The
xforms:group in the dialog points to
instance('ui')/edited-planet/planet, which is the planet that was copied from the main instance when the dialog was opened. When the
OK button is clicked, you want to copy the data back to the main instance. In this case, only description is being edited, so an
xforms:setvalue copying the description is enough:
<xforms:trigger>
<xforms:label>OK</xforms:label>
<xforms:setvalue ev:event="DOMActivate"
ref="instance('planets')/planet[name = context()/name]/description"
value="context()/description"/>
</xforms:trigger>
You also want to close the dialog when
OK is pressed. But since you want to do this for both the
OK and
cancel triggers, instead of adding an event handler on both triggers, you can exploit event bubbling and place only one handler on an ancestor of both triggers, here directly under the
xxforms:dialog:
<xxforms:dialog id="edit-planet-dialog">
<xforms:label>Edit Planet</xforms:label>
<xxforms:hide ev:event="DOMActivate" dialog="edit-planet-dialog"/>
...
Also when the dialog closes, you want to remove the planet you copied in
instance('ui')/edited-planet. You want to do this when the dialog is closed after users press
OK or
cancel, but also when they click on the
x at the top-right of the dialog. You can do this by reacting to the
xxforms-dialog-close event which is dispatched to the dialog in all 3 cases:
<xxforms:dialog id="edit-planet-dialog">
<xforms:label>Edit Planet</xforms:label>
<xforms:delete ev:event="xxforms-dialog-close" nodeset="instance('ui')/edited-planet/planet"/>
...
Run it and get the source