XForms Everywhere

8/31/2006

Orbeon PresentationServer 3.5 Milestone 1

Filed under: News — Erik Bruchez @ 10:16 am

We released today a milestone build, Orbeon PresentationServer 3.5 Milestone 1 (OPS 3.5 M1 in short). We haven’t had anything but nighly builds since OPS 3.0.1, so this was really long overdue!

What’s new in OPS 3.5 M1? The answer is: a lot, particularly in the realm of XForms! Check the preliminary list of changes for more details. Also, check out the updated online examples. In particular, we have made some pretty cool changes to the detail view of the DMV Forms example.

Note that this is not a release of OPS 3.5 final: this is a step towards OPS 3.5, but since we do not yet have all the 3.5 features in we did not want to call this build “beta” just yet. We hope to produce the next stable build in about a month, hopefully a beta or even a final build. Besides bug-fixing, 3.5 final will include a reorganized example portal and directory structure that should help you get started much faster on your first XForms application.

You can get OPS 3.5 M1 from the ObjectWeb Forge. The source code is as always available from the CVS repository.

8/30/2006

Professional Web 2.0 Programming Going to the Press!

Filed under: News — Erik Bruchez @ 7:56 pm

On Sunday night, the authors of the upcoming Professional Web 2.0 Programming submitted the last updates to their chapters. Alex and myself are quite relieved to be done with a task that took our weekends and evenings!

This book owes everything to Eric van der Vlist, who provided the vision, outline, and much more over those last few months. The co-authors are Danny Ayers, Joe Fawcett, and of course Alessandro Vernet and myself from Orbeon. Our tough tech reviewer was Micah Dubinko, and on the Wrox side we were helped by Sara Shlaer (editor) and Jim Minatel (managing editor).

We hope you will enjoy the book when it comes out!

8/29/2006

XForms Tip: Dynamic Language Switching

Filed under: General — Erik Bruchez @ 4:25 pm



Recently, I was booking a flight on an airline’s web site, when the application asked for my departing country (Switzerland). As soon as I performed my selection, the page reloaded in German. Since my 10 years of studying German did not quite make me fluent, I looked for a way to switch back to English or French and surely I found reassuring “en” and “fr” links at the top of the page. I chose “en”, which took me to an English page all right, but with all my booking information cleared! The browser’s back button at that point resulted in an error message and I had to start over.

This anecdote leads right to this post’s topic! With XForms, you can implement language switching very quickly and at the same time provide a user experience that I don’t remember having seen in any web application so far. So let’s look at the basic ideas:

  • The key is to store your text resources in separate XML files, one for each language. Name the files with suffixes such as _en or _fr using the standard language codes. The English resources can look like this:

    <resources>
      <titles>
        <main-title>Notice of Change of Address</main-title>
        ...
      </titles>
      <labels>
        <last-name>Last Name</last-name>
        ...
      </labels>
      <alerts>
        <last-name>Must contain letters...</last-name>
        ...
      </alerts>
    </resources>
    

  • Store the current language in a special XForms instance called language-instance.

    <xforms:instance id="language-instance">
        <language xmlns="">en</language>
    </xforms:instance>
    
  • Load the appropriate text resource into an instance called resources-instance using an XForms submission during the page’s initialization, by reacting to the xforms-ready event. You can also provide a default, in this case English:

    <xforms:instance id="resources-instance" src="resources_en.xml"/>
    
  • Refer to resources using xforms:output, xforms:label, etc.:

    <xforms:output value="instance('resources-instance')/titles/main-title"/>
    <xforms:input id="dmv-last-name-control" ref="dmv:name/dmv:last-name">
        <xforms:label ref="instance('resources-instance')/labels/last-name"/>
        <xforms:alert ref="instance('resources-instance')/alerts/last-name"/>
    </xforms:input>
    

    At this point, your application is internationalized with all your text resources are now separate from the form itself.

  • Now, to make things really cool, provide a dropdown menu with the list of available languages, for example:

    <xforms:select1 ref="instance('language-instance')">
        <xforms:label ref="instance('resources-instance')/labels/language-choice"/>
        <xforms:item>
            <xforms:label>en</xforms:label>
            <xforms:value>en</xforms:value>
        </xforms:item>
        <xforms:item>
            <xforms:label>fr</xforms:label>
            <xforms:value>fr</xforms:value>
        </xforms:item>
        <xforms:send ev:event="xforms-value-changed" submission="update-language-submission"/>
    </xforms:select1>
    
  • Finally, here is the submission that updates the language resource:

    <xforms:submission id="update-language-submission" ref="instance('language-instance')"
            action="/forms/service/resources/{{.}}" validate="false"
            method="get" replace="instance" instance="resources-instance"/>
    

With this simple mechanism to update text resources, no page reload or data loss occurs when users change the page’s language: they will probably love it ;-)

You can see the example live in the online DMV Forms example

8/4/2006

XForms Tip: Creating a Configurable Error Summary

Filed under: General — Erik Bruchez @ 1:20 pm

Web 1.0 applications typically perform client-side validation (when they do it at all) with JavaScript libraries, often checking only minimal aspects of the form. Upon submitting the form, server-side validation is performed as well (you can never trust the client), and in case of error the filled-out form is returned to the client.

This is where most well-behaved applications provide not only a nice highlighting of incorrect fields, but also an error summary, often at the top of the form, that provides details about what data fields are incorrectly filled-out or missing. Such an error summary typically looks like this:

OPS XForms Error Summary

In Orbeon PresentationServer (OPS), the old XForms Classic implementation (the engine without Ajax support, now considered legacy code) allowed you to create and customize such an error summary very easily with XSLT. But so far the new Ajax-based XForms NG engine did not provide this feature. With Web 2.0 and Ajax, you can’t simply use cutomizable server-side XSLT upon page display as XForms Classic did: somehow, the server must provide the client with information about the current errors as the user types, since XForms provides just-in-time validation.

One way to solve the issue is to make the XForms engine aware of the concept of “error summary”. While this is reasonably in line with the XForms philosophy, it is yet another feature to add to the XForms engine, and it is harder to allow the user to customize the appearance of the summary (for example with XSLT, whether running client-side or server-side). But this solution felt clunky to us.

This is where we realized that XForms itself had almost all the tools necessary for the form author to implement a functioning error summary! The XForms engine already dispatches events when the validity of controls change (in fact, of nodes to which controls are bound, but this is a technicality), or when controls become relevant and non-relevant. If those events provide enough context information to identify which control exactly became valid or invalid, you can maintain a list of errors yourself in an XForms instance. So this is exactly what we did:

  1. We made sure that validity events and the likes are sent as you can expect. The XForms specification is currently silent for example on what you do when a control becomes relevant or non-relevant because its binding to an instance data node appears or disappears after initialization, or when repeating controls are added or removed. In such cases, we have decided to send events so as to make them as useful as possible.

  2. We provided all those notification events with enough context information to allow creating an error summary. This includes: control id, list of enclosing repeat indexes, and alert message value.

After this long introduction, let’s now look at how you create your error summary! First, define in your model an instance that contains the list of current errors, and a template for each error information:

<xforms:instance id="errors-instance">
  <errors xmlns=""/>
</xforms:instance>
<xforms:instance id="error-template">
  <error xmlns="" id="" indexes="" label="" alert=""/>
</xforms:instance>

Second, add event handlers that add and remove error information as validity events occur:

<xforms:action ev:event="xforms-invalid" if="normalize-space(event('alert')) != ''">
  <xforms:action if="not(instance('errors-instance')/error[@id = event('target-id')
        and @indexes = string-join(event('repeat-indexes'), '-')])">
      <xforms:insert context="instance('errors-instance')"
        nodeset="error" origin="instance('error-template')"/>
      <xforms:setvalue ref="instance('errors-instance')/error[index('errors-repeat')]/@id"
        value="event('target-id')"/>
      <xforms:setvalue ref="instance('errors-instance')/error[index('errors-repeat')]/@indexes"
        value="string-join(event('repeat-indexes'), '-')"/>
  </xforms:action>
  <xforms:setvalue ref="instance('errors-instance')/error[@id = event('target-id')
    and @indexes = string-join(event('repeat-indexes'), '-')]/@alert" value="event('alert')"/>
  <xforms:setvalue ref="instance('errors-instance')/error[@id = event('target-id')
    and @indexes = string-join(event('repeat-indexes'), '-')]/@label" value="event('label')"/>
</xforms:action>
<xforms:action ev:event="xforms-valid" if="instance('errors-instance')/error[@id = event('target-id')
  and @indexes = string-join(event('repeat-indexes'), '-')]">
  <xforms:delete nodeset="instance('errors-instance')/error[@id = event('target-id')
    and @indexes = string-join(event('repeat-indexes'), '-')]"/>
</xforms:action>

What the code above does is insert or update an <error> element into the list of errors when an xforms-invalid event occurs, and delete the appropriate <error> element upon receiving xforms-invalid. Note that this codes makes use of several XForms 1.1 features, including enhancements to <xforms:insert>, the use of the event() function, and conditional actions. It also uses for convenience one XPath 2.0 function, string-join(), but you could do without it especially if you don’t have nested repeats to handle. The errors instance for the image above looks like this:

<errors>
  <error id="xforms-element-133" indexes="" label="First Name" alert="Must contain..."/>
  <error id="xforms-element-227" indexes="" label="Zip Code" alert="Must contain..."/>
  <error id="xforms-element-207" indexes="" label="Street Name 1" alert="Must contain..."/>
  <error id="xforms-element-216" indexes="" label="City" alert="Must contain..."/>
</errors>

Note that the alert messages are truncated for reason of space, and that the indexes attribute is used for handling repeating controls, not shown here.

Finally, you write XForms controls that look at the list of errors and displays them in a nice way. This is one way of doing it, with <xforms:repeat>:

<xforms:group ref="instance('errors-instance')/error">
  <table class="dmv-errors-table">
    <xforms:repeat nodeset="instance('errors-instance')/error" id="errors-repeat">
      <tr>
        <th><xforms:output value="@label"/></th>
        <td>
          <xforms:output value="if (string-length(@indexes) > 0)
            then concat('(Row ', @indexes, ')') else ''"/>
        </td>
        <td><xforms:output value="@alert"/></td>
      </tr>
    </xforms:repeat>
  </table>
</xforms:group>

That’s it! The error summary looks just like the image above, and it updates just-in-time as you modify your form. The great thing is that all the code is 100% reusable between applications, and at the same time it gives you all the freedom you need to customize the appearance of the errors summary.

Incidentally, this also provides a very easy way of enabling and disabling a Save button: if errors-instance does not contain any error element and the data has been modified (as we have seen in a previous tip), then the form is valid and the Save button is enabled. Otherwise, the Save button is disabled:

<xforms:instance id="control-instance">
  <control xmlns="">
    <data-status/>
    <save-trigger/>
  </control>
</xforms:instance>
<xforms:bind nodeset="instance('control-instance')">
  <xforms:bind nodeset="save-trigger"
    readonly="not(../data-status = 'dirty'
                  and count(instance('errors-instance')/error) = 0)"/>
</xforms:bind>
<xforms:trigger ref="instance('control-instance')/save-trigger">
  <xforms:label ref="instance('resources-instance')/labels/save-document"/>
</xforms:trigger>

There are certainly ways to improve the code above, but it is a good start already. A simple new feature that comes to mind is handling “required but empty” fields in addition to invalid ones.

All of the above is now included in the DMV Forms example available in the nightly builds of OPS.

Powered by WordPress