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:
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):
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.