« Back to home

Better string lists in XPages

Like Notes, XPages supports multi-valued fields. You can set a separator for multiple values, and tell XPages whether to trim whitespace from around each value.

Unfortunately, if you have (say) a list of string values, the separator ends up with no whitespace after it. This can look ugly:

Mon,Tue,Wed

Ideally we’d like to display the multiple values with a space after each comma, and also accept separators like newline or semicolon when entering data. Easy to do in the Notes client, not so easy in XPages.

The solution is to write a custom JSF converter. The interface is defined by the class javax.faces.convert.Converter. There are basically two methods you need to write: getAsObject takes a string and returns an object suitable for storage in the back end, and getAsString takes a back-end storage object and returns a string representation.

In the case of XPages, the back-end object for a list field is a Java Vector, and we can split and trim values in one step using a compiled regular expression:

private final static Pattern listPattern = 
  Pattern.compile("\\s*[,;\\n\\r]+\\s*", Pattern.MULTILINE);

public Object getAsObject (final FacesContext ctx, final UIComponent uicomp,
  final String val) {
  String[] vals = listPattern.split(val);
  return new Vector(Arrays.asList(vals));
}

The reverse process is slightly more complex. Until later this year XPages doesn’t have Java 8, so we can’t use StringJoiner. Instead, we’ll build the list ourselves, after checking that we’re getting the kind of value we expect:

public String getAsString (final FacesContext ctx, 
  final UIComponent uicomp, final Object obj) {
  if (!(obj instanceof Vector)) {
    throw new ConverterException(new 
      FacesMessage("An exception occurred converting data," +
        " expected a Vector<String>, got a " +
        obj.getClass().getCanonicalName()));
  }
  Vector<String> vals = (Vector<String>) obj;
  StringBuilder sb = new StringBuilder();
  for(String v : vals) {
    String ts = v.trim();
    if (!ts.isEmpty()) {
      if (sb.length() > 0) {
        sb.append(", ");
      }
      sb.append(ts);
    }
  }
  return sb.toString();
}

Next, unfortunately, you need to register the converter in faces-config.xml, by adding a <converter> element as a child of the top-level <faces-config> element, something like this:

<converter>
  <description>String list converter for multi-value fields</description>
  <converter-id>stringList</converter-id>
  <converter-class>com.ibm.us.meta.convert.StringList</converter-class>
</converter>

With that done, actually using the converter is pretty simple — just add it to the converter property of your inputText field.

<xp:inputText id="nameField1" value="#{document1.Users}">
  <xp:this.converter>
    <xp:converter converterId="stringList"/>
  </xp:this.converter>
</xp:inputText>

Since the converter handles everything, you don’t have to set the multipleSeparator or multipleTrim properties on the field any more.

Obviously the same technique can be used to implement number lists or date lists.