Tuesday, May 23, 2006

XForms Tip: Enabling and Disabling Your “Save” button

Many forms have a "Save" button. From a usability perspective, it makes sense to disable that button when saving is not allowed. There are two obvious scenarios where this arises: when the form has been modified but contains invalid data, and when the user has not modified any data at all. We show here how to handle the latter case.

What we want to do is enable the "Save" button only when the data in the form is "dirty", in other words when it has changed. For this purpose, we use a special control XForms instance containing a <dirty> element which will contain the string true when the data is dirty, and a <save-trigger element with an XForms binding to tell the "Save" button to be disabled when the data is dirty:

<xforms:instance id="control-instance">
    <control xmlns="">
<xforms:bind nodeset="instance('control-instance')/save-trigger" readonly="not(../dirty = 'true')"/>

The "Save" button is then simply bound to the <save-trigger> element, and will automatically follow the readonly property as it changes on the <save-trigger> element:

<xforms:trigger ref="instance('control-instance')/save-trigger">
    <xforms:send ev:event="DOMActivate" submission="save-submission"/>

XForms provides you with a very simple way of detecting value changes in a form control: the xforms-value-changed event. Now the nice thing with XML events is that you can register event handlers at any point in an XML hierachy. This means that you can put a single handler detecting any value change in your entire form:

  <xforms:setvalue ev:event="xforms-value-changed" ref="instance('control-instance')/dirty">true</xforms:setvalue>
  <-- Other form controls are nested here -->

And that's it: the "Save" button starts as disabled, and switches automatically to enabled when any value in the form changes.

To wrap this up, you can revert the button to being disabled when saving succeeds:

<xforms:submission id="save-submission" method="post" replace="none" action="/save-document">
    <xforms:setvalue ev:event="xforms-submit-done" ref="instance('control-instance')/dirty">false</xforms:setvalue>

Finally, note that you can also detect XML instance insertions and deletions if you use <xforms:insert> and <xforms:delete>. This time, you add two event listeners in your XForms model:

<xforms:model id="main-model" schema="oxf:/examples/forms/detail/form-schema.xsd">
    <xforms:setvalue ev:event="xforms-insert" ref="instance('control-instance')/dirty">true</xforms:setvalue>
    <xforms:setvalue ev:event="xforms-delete" ref="instance('control-instance')/dirty">true</xforms:setvalue>

A perfect version of this would not only detect controls value changes, but actually compare all the current values of the form with the initial values of the form (or the values just after saving). However, for most practical purposes, the solution presented here is just good enough. In fact, it may even be considered more intuitive to the user, who otherwise may wonder why his "Save" button is disabled when he in fact has interacted with the form.

1 comment:

  1. Here is a link to a full version of the code: