« Back to home

XPages, Bootstrap, and form validation

I’ve recently been experimenting with Bootstrap for page layout, now that it’s an official part of the XPages Extension Library for Domino. After some simple test pages, I decided to put together a standard data entry form to see how painful that was.

A typical Bootstrap form’s HTML for a single field looks like this:

<div class="form-group">
  <label for="email1">Email address</label>
  <input type="email" class="form-control" id="email1" 
    placeholder="yourid@example.com">
  <span class="help-block">Your Internet e-mail address</span>
</div>

If you have a validation error to display, and want to show a feedback icon, the HTML balloons up to something like this:

<div class="form-group has-feedback has-error">
  <label for="email1">Email address</label>
  <input type="email" class="form-control" id="email1" 
    placeholder="yourid@example.com">
  <span class="glyphicon form-control-feedback glyphicon-delete"/>
  <span class="help-block with-errors">E-mail address is required.</span>
</div>

Nothing very difficult there, and a lot easier than OneUI, but by the time you convert it into XPages elements with rendered formulae and repeat it for a dozen form fields, you’re looking at a long and complicated page.

The obvious thing to want to do is encapsulate it into a custom control. I’m not the first person to do so, but I’ve never liked passing field names and data types to custom controls. Surely we could use editable facets for that?

The first piece of the puzzle was figuring out how to obtain a reference to the component dropped into a facet, from within the custom control. I eventually discovered that the facet contents are instantiated on page load, so you can look them up in the afterPageLoad event of your custom control.

It’s not quite that simple, though. getComponent with a facet’s ID returns a container object. The actual component inside the facet is a child object, accessed through the .children property.

Suppose your custom component has a callback (facet) with an id of field1, and suppose when you use the custom component you drop a text field on it. Inside the custom component, getComponent('field1').children[0] will be the text field object.

Once I get the instantiated component (i.e. form field) during the afterPageLoad event, I store its actual ID. That way, the rest of the code in the custom component can easily fetch it with getComponent(). But where to store it? Well, you could use all kinds of cunning methods, but XPages already hands you an object whose scope is the custom control — the compositeData object!

In other words, rather than passing the custom control a field name and other information needed for it to create a field via compositeData, I pass in an actual form control by dropping it on the component’s editable area, and the component stores the information it needs in compositeData for itself.

As well as storing the editable component’s ID, I also use the addAttr() method to add any HTML5 placeholder text that’s necessary, so the page using the component doesn’t have to deal with that mess either.

The next trick is detecting when there are any validation error messages. To do that, I use the formula facesContext.getMessages().hasNext(). The state of the dropped-on field in particular can be checked via getComponent(compositeData.fieldId)fieldId is where I stashed the control’s ID during the afterPageLoad event.

The final ingredient is how to validate and trigger the errors. I use the same method I’ve written up before, a single SSJS validation function in the querySave event.

My custom control has just three properties:

  • label, for the text to put above the field,
  • placeholder, for the optional placeholder text to put inside the field, and
  • help, for any optional additional help message to display under the field.

An example of using the custom control looks like this:

<xc:cc_formfield label="Bird name:" placeholder="Your bird's name">
  <xp:this.facets>
    <xp:inputText id="birdName" xp:key="field" />
  </xp:this.facets>
</xc:cc_formfield>

That’s it. Everything else is handled for you. You bind your inputText or other control to a document in the usual way. Same goes for specifying custom formatters, default values, typeahead, and all the other form field goodies.

HThe custom controller’s code is on github. It looks a bit scary, but it makes the higher level form code much cleaner.