Comments? Feedback?

This wiki does not yet support public comments (a limitation of Google Sites), so we encourage you to post your comments either:

On Twitter by responding to @orbeon.

On our community mailing list: subscribe sending an email to ops-users-subscribe@ow2.org (content of subject/body doesn't matter), you'll get a response with the email to use to send your message to the community mailing list.

Recent site activity

How-to guides‎ > ‎XForms Logic‎ > ‎

Perform external validation


The problem

You can write validation "rules" directly in XForms using the <xforms:bind> mechanism. However, there are cases where you'd like to implement validation in a service, which you want to call from XForms. Let's call this external validation. In most cases, you will want to write your validation rules in XForms, but you might want to use external validation in the following cases:

  • You already have a service that implement validation, and you want to reuse this code.
  • The type of validation you need to perform cannot be done easily (or at all) using just type constraints, and XPath expressions.

Let's assume you want to delegate the validation of the following registration form to a service:



The problems are:
  • What should the service return?
    • How to encode errors that are for a specific field?
    • How to encode errors that a global to the form (not specific to a field)?
  • How to get XForms to consider the fields invalid based on the response from the service?

The solution

As it is often the case, there are multiple possible approaches, each with its benefits and drawbacks. Let's explore a solution based on the idea of annotating the original instance.

The validation response

The instance to which the controls are bound is:

<user-info>
    <first-name>Tom</first-name>
    <last-name>Smith</last-name>
    <customer-number>1234-4567-7890</customer-number>
</user-info>

This instance is submitted to a validation service, which returns a document that has the following form:
<v:validation-result xmlns:v="http://www.example.com/validation">
    <v:data>
        <user-info>
            <first-name>Tom</first-name>
            <last-name>Smith</last-name>
            <customer-number v:alert="This customer number is unknown to the system"
               
v:valid="false">1234-4567-7890</customer-number>
        </user-info>
    </v:data>
    <v:global-errors>
        <v:global-error v:alert="The first and last name do not match an existing customer"/>
    </v:global-errors>
</v:validation-result>

In this document:
  • All the elements and attributes related to the validation are in a distinct namespace declared with: xmlns:v="http://www.example.com/validation". Pick your own namespace here, making sure to choose a namespace which is distinct from other namespaces you are using in you forms.
  • The input document has been included included under /v:validation-result/v:data, and has been annotated as follows:
    • Valid elements are unchanged.
    • Invalid elements have an additional v:valid="true" attribute.
    • Invalid elements have an additional v:alert attribute which contains the error message.
  • Global errors are found in /v:validation-result/v:global-errors/v:global-error. The above example shows only one validation error, but there could be many.

Calling the service

You call the service by triggering the following submission:

<xforms:submission id="validation-submission" ref="instance('user-info')"
        resource="/apps/xforms-sandbox/samples/howto/external-validation-response.xml"
        method="post" replace="instance" instance="validation-result">
    <xforms:delete ev:event="xforms-submit" nodeset="//@v:*"/>
    <xforms:action ev:event="xforms-submit-done">
        <xforms:insert nodeset="." origin="instance('validation-result')/v:data/*"/>
        <xforms:dispatch name="fr-visit-all" targetid="error-summary"/>
    </xforms:action>
</xforms:submission>

This submission:
  • Replaces the instance to validate with the annotated instance when the service returns.
  • Tell the error summary to show all the errors when the service returns (if you indeed using the error summary component).
  • Removes annotations from the instance before sending it to the service. (The instance might contain annotations after being validated once.)


Marking fields as invalid

You tell XForms that all the nodes with v:valid="false" are invalid:

<xforms:bind nodeset="//*" constraint="not(@v:valid = 'false')"/>

Then for each field, you reference the message included in v:alert attribute as the alert. For instance, for first name:

<xforms:input ref="first-name">
    <xforms:label>First name</xforms:label>
    <xforms:alert ref="@v:alert"/>
</xforms:input>

If your validation service does not return user-friendly messages, you can have the message in-line in the XForms view, and use that message instead.

Global errors

Since global errors are not related to a control in particular, you need to tell the error summary component about those with the fr:errors element:

<fr:error-summary id="error-summary" observer="form-group">
    <fr:label>Errors in this form</fr:label>
    <fr:errors nodeset="instance('validation-result')/v:global-errors/v:global-error">
        <fr:alert ref="@v:alert"/>
    </fr:errors>
</fr:error-summary>

The error summary won't provide a link to any control for global errors, as they are not attached to a field in particular. The first error in this screenshot is a global error:

    

Handling failures of the validation service

What should you do when the validation service fails? Most likely you'll want to report a global error, rather than not show any error at all. You can do so by having an instance that contains the error to display if the validation service fails, using the same format used by the validation service:

<xforms:instance id="validation-result-submit-error">
    <v:validation-result xmlns:v="http://www.example.com/validation">
        <v:global-errors>
            <v:global-error v:alert="The document could not be validated as there has been
                a communication error; please try again or contact technical support"/>
        </v:global-errors>
    </v:validation-result>
</xforms:instance>

Then, on the submission that runs the validation service, you add a handler for xforms-submit-error, which uses the above instance and tells the error summary to show its errors:

<xforms:action ev:event="xforms-submit-error">
    <xforms:insert nodeset="instance('validation-result')"
        origin="instance('validation-result-submit-error')"/>

    <xforms:dispatch name="fr-visit-all" targetid="error-summary"/>
</xforms:action>

Constraint

This technique posses the following constraint:
  • As-is, you can't handle errors on controls bound to attributes. To handle errors on attributes, you need to encode them by making the attribute name a part of the validation attribute name. For instance, if the input document was <employee id="123"/> and you had a control bound to the id attribute, and that the value was invalid, the response could look like:

    <v:validation-result xmlns:v="http://www.example.com/validation">
        <v:data>
            <employee id="123" v:id-valid="false" v:id-alert="Id must have at least 5 digits"/>
        </v:data>
    </v:validation-result>

Run it and get the source