« Back to home

Controller-based server-side validation in XPages JavaScript

Think about your last tax return. If you have investment income, a whole additional set of questions has to be answered. If you own overseas investments, that’s another set of fields to fill out. Disposed of any stock? That’ll be more questions.

My experience is that most business forms end up like this. They start out simple, but sooner or later someone says “Well, if they answer yes to this question, we’ll need to ask them another question with three possible answers, and depending on their answer they might need to put a project code in another field…” and suddenly you’re off down the dynamic forms rat hole.

XForms makes dynamic forms pretty easy. You can use an xp:panel to replace a div element, and then use partial refresh to make the contents of the div dynamic. Because you’re refreshing the DOM tree and not just making things visible or invisible with CSS, you don’t need to have every possible section present on the form at initial page load, which helps performance.

In principle, validation of XPages forms is easy too. You select a component which represents some sort of data input field, add one or more validators, and put an error display control somewhere suitable.

The problem is that when the form has sections which appear and disappear, simple field-by-field validation can explode in complexity quite quickly. If a checkbox makes ten new fields appear in a section, then every validator on those fields must check the checkbox state before deciding whether to throw an error.

Worse, the actual document won’t necessarily have been updated if the form is being used to create a new document. So the checkbox state will need to be checked using getComponent(), which is relatively slow.

So my challenge was to find out how to concentrate all my validation logic into a single controller function, ideally to be written in server-side JavaScript — yet still to put the error messages in the appropriate error display components.

The answer turns out to be a bit like doing validation in LotusScript in the Notes client: you use the querySaveDocument event. In XPages, though, the event is associated with a dominoDocument data source rather than a form, because a single XPages form can have multiple documents associated with it.

The one piece of somewhat subtle code required is a function to create the error notifications, so here’s that code, adapted from an example by Don Mottolo:

/*jshint indent:2 */
/*exported Validation */
var Validation = function () {
  "use strict";

  var postValidationError = function (component, msg) {
    var msgObj = new javax.faces.application.FacesMessage( 
      javax.faces.application.FacesMessage.SEVERITY_ERROR, msg, msg 
      ), clientid = null; 
    try {   
      if (typeof component === 'string') {
        component = getComponent(component);
      }
      if (component) {
        clientid = component.getClientId(facesContext);
      }
      facesContext.addMessage(clientid, msgObj);
      component.setValid(false);
    } catch (e) {
      print("Exception posting error for " + clientid + "/" + component +": " + e);
    }
  };

  return { 'postError': postValidationError };
}();

(See Gist)

The component argument can either be the actual component object, or its XPages component ID. Notice that the target component is flagged as invalid, as well as having the error message added to the JSF context.

An XPage which uses the above code will start off something like this:

<xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:xc="http://www.ibm.com/xsp/custom">

  <!-- Load the validation helper -->
  <xp:this.resources>
    <xp:script src="/mx_validation.jss" clientSide="false"></xp:script>
  </xp:this.resources>

  <xp:this.data>
    <xp:dominoDocument formName="Order" 
      var="document1" action="createDocument" >
      <xp:this.querySaveDocument><![CDATA[#{javascript:
  var result = true;
  if (document1.getItemValueString("Name").isEmpty()) {
    Validation.postError("name", "You must enter your name.");
    result = false;
  }
  return result; }]]></xp:this.querySaveDocument>
    </xp:dominoDocument>
  </xp:this.data>

Here we’re validating that a single field with the id ‘name’ has a value.

Remember that if you use postError to post an error for a field, that field must have an error display component associated with it, or the page must have a multiple error display component. Otherwise the errors will never be displayed to the user, and you’ll get errors in the server log.

Obviously the entire validation function can easily be factored out of the XPage into its own SSJS library.

You can also use the querySave function to add fields to your document and perform other random computations on it, just like you would in LotusScript in the Notes client.