Reusable fields and form validation

Creating a field on a form with Bootstrap requires quite some HTML. Here's a basic structure for a form field in a horizontal layout as described here:

<form class="form-horizontal">
  <div class="form-group">
    <label class="col-sm-2 control-label" for="inputEmail">Email</label>
    <div class="col-sm-10 controls">
      <input type="text" id="inputEmail" placeholder="Email" />
    </div>
  </div>
</form>

If we want to create this structure from an XPage, we need to make a couple of changes. With XPages we don't need to create a form tag: that's done automatically (and probably on a whole different level in the DOM). Luckily the form-horizontal class we need to define a horizontal form can also be applied to other elements. So the HTML above translated for use on an XPage might look like this:

<div class="form-horizontal">
  <div class="form-group">
    <xp:label styleClass="col-sm-2 control-label" for="inputEmail" value="Email" />
    <div class="col-sm-10">
      <xp:inputText type="text" id="inputEmail">
        <xp:this.attrs>
          <xp:attr name="placeholder" value="Email"></xp:attr>
        </xp:this.attrs>
      </xp:inputText>
    </div>
  </div>
</div>

(note that I used the "attrs" atrribute to add the placeholder attribute to the input control)

So for every single field on your form you'll need all this HTML... Of course you can just copy-paste that a number of times, but there's an easier and better way: use a reusable 'Bootstrap field' custom control.

A better approach...

Start by copying the code above (starting at the 'control-group' div) into a custom control and adding custom properties for everything that is dynamic. I created the following properties:

  • dataSource (Object, required: click the folder icon next to type and select Data Sources > Object data source)
  • fieldName (String, required)
  • fieldLabel (String, required)
  • placeholder (String, optional)
  • helpText (String, optional)
The names of the variables are self-explaining. Next, we need to make the contents of the control dynamic by referencing the custom control properties. Using that same method we also create a dynamic field binding of the input control (<xp:inputText>), something that Brad Balassaitis recently also blogged about.

At the end your custom control source looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

  <xp:div styleClass="form-group">

    <xp:label styleClass="col-sm-2 control-label" for="inputText1" value="${compositeData.fieldLabel}" />
    
    <div class="col-sm-10">
    
      <xp:inputText type="text" id="inputText1" loaded="${!empty compositeData.placeholder}" 
        value="#{compositeData.dataSource[compositeData.fieldName]}"
        required="${compositeData.required}">
          <xp:this.attrs>
            <xp:attr name="placeholder" value="${compositeData.placeholder}"></xp:attr>
          </xp:this.attrs>

      </xp:inputText>

      <xp:text escape="true" id="computedField1"
        styleClass="help-block" value="${compositeData.helpText}"
        loaded="${!empty compositeData.helpText}">
      </xp:text>
    </div>

  </xp:div>

</xp:view>

And looks like this in a browser:

Guess what you have to enter here...

So for every field you now only need to add another instance of the custom control, change the properties and you're done. The sample will only work for text fields but you can extend that for every other field type you need. A couple of important remarks here:

  • To reference a document data source in the calling XPage use the following syntax: dataSource="#{document1}"
  • I'm using the Expression Language's empty keyword to only load certain controls if a text is provided. See here for a couple of other EL examples.

(by the way: resize this page and see how the field layout changes due to Bootstrap's responsive features)

Form validation

Bootstrap has built-in classes that you can use for form validation: adding the 'has-error' class to the form-group DIV will mark it as having an error.

Every input control on an XPage has an isValid() method. The return value of this method is false if, well, a field isn't valid... A field will be marked as invalid if it didn't pass any of the validators that are attached to it.

What if we combine these two pieces of information? We can end up with a reusable custom controls that automatically marks fields that are mandatory but weren't filled in. Here are the steps to do it:

  • Add a 'required' property to the custom control to indicate that the field is required.
  • Make the styleClass attribute of the control-group  div computed.
  • Add a <xp:validateRequired> validator if needed.
  • Only show the help text if a field isn't marked as an error (using the isValid() method).
  • Add an <xp:message> control to show the error message.
Here's what the custom control now looks like:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

  <xp:div>
    <xp:this.styleClass><![CDATA[#{javascript:"form-group" + ( getComponent("inputText1").isValid() ? "" : " has-error" )}]]></xp:this.styleClass>

    <xp:label styleClass="col-sm-2 control-label" for="inputText1" value="${compositeData.fieldLabel}" />
    <div class="col-sm-10">
      <xp:inputText type="text" id="inputText1" loaded="${!empty compositeData.placeholder}" 
        value="#{compositeData.dataSource[compositeData.fieldName]}"
        required="${compositeData.required}">
          <xp:this.attrs>
            <xp:attr name="placeholder" value="${compositeData.placeholder}"></xp:attr>
          </xp:this.attrs>
          
          <xp:this.validators>
            <xp:validateRequired message="#{javascript:compositeData.fieldLabel + ' is required'}"></xp:validateRequired>
          </xp:this.validators>

      </xp:inputText>
      
      <xp:text escape="true" id="computedField1" styleClass="help-block" value="${compositeData.helpText}">
        <xp:this.rendered><![CDATA[#{javascript:getComponent("inputText1").isValid() && compositeData.helpText != null}]]></xp:this.rendered>
      </xp:text>
      
      <xp:message id="message1" for="inputText1" styleClass="help-block"></xp:message>

    </div>

  </xp:div>

</xp:view>

This updated version acts like this (click Save to see the error styling):

This is where you're supposed to enter an email address

UPDATE

The error class will style most Bootstrap field. One field type where it doesn't work is a Select2 enabled field. Brad Balassaitis wrote an article describing how you also have an error class on those. Read it here.