ShowTable of Contents
Introduction
This article is part of the
XPages Extensibility API Developers Guide.
This article describes how to create a basic user interface (UI) control for XPages; this includes creating the Java control, its tag and renderer. In particular, it describes in detail how to implement control properties and provides code samples to illustrate this. The article is written for developers who are new to the XPages Extensibility API. It assumes that you are familiar with creating Java controls for XPages. If not, it is recommended that you begin by reading the article "
Creating a Java Control in an NSF". Occasionally, the article discusses some advanced topics; these are intentionally brief and are to provide a broader context. In such cases, details are provided where further information can be found.
Some of the terminology in the article can be used interchangeably. For example: control, Java control and component. The term control is used to refer to the XPages control within the context of the palette in Domino Designer. When referring to the source code of this control, the term Java control is used, to differentiate from Custom Controls. Lastly, the term component is used equivalently, however this refers to the control within the context of the xsp-config file. For simplicity and clarity, terminology has been used consistently and its meaning should be apparent given its context.
Lastly, where code examples are provided, only the relevant code being discussed is shown.
Creating the Java control class
As the name suggests, a Java control is created using the Java programming language. This is distinct from Custom Controls which are composed of some XPage content, by aggregating existing Java controls. All of the Core Controls and Container Controls native to XPages are Java controls.
A Java control is defined as any class which extends from the JavaServer Faces (JSF) abstract component class javax.faces.component.UIComponent or any of its subclasses. However, for convenience, the component base class javax.faces.component.UIComponentBase provides a default implementation of the behaviour defined by UIComponent. It's encouraged that classes extend from UIComponentBase instead of the abstract component class directly.
When creating a Java control, you can begin by extending an existing control or any of it's superclasses. However, only some controls in the XPages runtime have been designed to be extended. If a control doesn't have to inherit the behaviour of an existing control, it should extend from UIComponentBase.
package com.example.component;
import javax.faces.component.UIComponentBase;
public class UICtrl extends UIComponentBase {
public UICtrl() {
super();
setRendererType("com.example.uictrl");
}
public String getFamily() {
return "com.example.uictrl";
}
}
Every control class should define the control's rendererType and family. These are both string values which identify the control's Renderer and the family of controls to which it belongs, for example, the Combo Box control belongs to the family defined by the UISelectOne superclass.
Beyond extending from
UIComponentBase, a control can implement several interfaces. Here are three examples of useful control interfaces.
- NamingContainer (javax.faces.component.NamingContainer)
- This is a standard JSF interface implemented by components that provide a new "namespace" for the ID's of their child components.
- FacesComponent (com.ibm.xsp.component.FacesComponent)
- This hooks directly into the XPages runtime and how the JSF tree is built. This gives an opportunity for the control to create its children or run code before or after its children are created. This is required for advanced components that need to customize the JSF tree construction, like the Repeat control.
- ThemeControl (com.ibm.xsp.stylekit.ThemeControl)
- Implemented by controls to return their default theme ID. For example, the Button control returns "Button" unless it is defined as a submit button which returns "Button.Submit".
The control class maintains the control's properties and behaviour. It should provide getter and setter methods for the control's properties. These are discussed further in the "
Properties" section. The control's behaviour should include methods for use in serialization and mapping the control to it's renderer. The mapping from a control to a renderer depends in part on the control's getFamily method and the rendererType value set in the constructor. These are discussed further in the section "
Register the control renderer".
Serialization is important in XPages as it increases server scalability by saving and restoring the page from disk. It is enabled by default in applications created in 8.5.2 or later and may be configured using the Application Properties, XPages tab, "Server page persistence" option. Every control class should support serialization using the methods: saveState and restoreState. Serialization must be implemented using these methods. saveState returns a Serializable object while restoreState is able to restore the state of a control based on the object returned by saveState.
import javax.faces.context.FacesContext;
private String str_foo;
private boolean bool_foo = false;
private boolean bool_foo_set = false;
private int int_foo = Integer.MIN_VALUE;
public void restoreState(FacesContext context, Object state) {
Object values[] = (Object[]) state;
super.restoreState(context, values[0]);
str_foo = (String) values[1];
bool_foo = ((Boolean) values[2]).booleanValue();
bool_foo_set = ((Boolean) values[3]).booleanValue();
int_foo = ((Integer) values[4]).intValue();
}
public Object saveState(FacesContext context) {
Object values[] = new Object[5];
values[0] = super.saveState(context);
values[1] = str_foo;
values[2] = bool_foo ? Boolean.TRUE : Boolean.FALSE;
values[3] = bool_foo_set ? Boolean.TRUE : Boolean.FALSE;
values[4] = new Integer(int_foo);
return values;
}
To serialize more complex data, XPages provides the class com.ibm.xsp.util.StateHolderUtil. This can be used to save and restore maps of StateHolder (javax.faces.component.StateHolder) objects, lists, etc. Any complex data should be saved and restored using the methods provided by this class. Each save has a corresponding restore method. These paired methods should be used together.
Creating the control tag
An XPages library will normally define a set of controls for use in applications. Each library should provide one or more xsp-config files. This file is used by Domino Designer at design time to get the definitions of the controls. It contains meta-information about the controls and other tags published by the library, like the list of controls and the controls' properties.
The xsp-config specification is based on the JSF faces-config.xml definition, extended with XPages specific tags. The new file format takes the place of a JSP Tag Library or the JSF 2.0 Facelets. It differs from Tag Libraries in that groups of properties can be defined to be reused by many components. It also provides other information that will be used when loading a page from file. For example, the property-extension element gives more information on the expected value of a property, so that pages can be validated as the XPage is built to compiled pages.
<faces-config>
<faces-config-extension>
<namespace-uri>http://www.ibm.com/xsp/example</namespace-uri>
<default-prefix>eg</default-prefix>
</faces-config-extension>
<component>
<description>XPages Control - UICtrl</description>
<display-name>UICtrl</display-name>
<component-type>com.example.uictrl</component-type>
<component-class>com.example.component.UICtrl</component-class>
<icon>
<small-icon>/designer/icons/Ctrl_16x16.gif</small-icon>
<large-icon>/designer/icons/Ctrl_32x32.gif</large-icon>
</icon>
<component-extension>
<component-family>com.example.uictrl</component-family>
<renderer-type>com.example.uictrl</renderer-type>
<tag-name>uictrl</tag-name>
<designer-extension>
<render-markup>
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:panel style="width:60px">
<xp:image url="img.jpg" id="image1"></xp:image>
<xp:label value="Control" id="label1">
</xp:label></xp:panel></xp:view>
</render-markup>
</designer-extension>
</component-extension>
</component>
</faces-config>
Every control's xsp-config file contains two elements: faces-config-extension and component. These define the XML tag name and namespace to which the control is contributed. As the XPages source code is XML and to avoid name conflicts between libraries, each library should feature its own namespace. For example, the core XPages controls use the reserved namespace xmlns:xp="http://www.ibm.com/xsp/core". This must be avoided by custom libraries. When used inside an XML file, a namespace is described by a prefix and a URI. The one that matters for a library is the URI. The prefix is just an XML notation convenience. A library can propose a preferred prefix, but this might not be used if it conflicts with another library.
An XPages control is defined by the component element. This includes details about the control (e.g. name, family) and its list of properties. An XPages control definition also requires extra details. These are specified in the component using the component-extension element. This contains the element designer-extension for use by Domino Designer at design time. This can specify the control's markup when added to an XPage in Design view.
For more detailed information about the xsp-config file format, see the article "
XPages Configuration File Format".
Properties
Obviously, a control exposes properties. Implementing a property has two requirements:
- Declare a property field and corresponding behaviour in the control's Java class
- Add a property element to the control's component entry in the xsp-config file
Adding a property for a Java control requires declaration of the property field in the control's Java class. The definition of properties must follow the JavaBean model by providing a getter and setter. But as JSF allows properties to be computed on-demand through value bindings, the implementation must check if a formula has been assigned to the property and execute the formula if necessary. If there are reasons that a property should never allow a computed value then the xsp-config file declaration of the property should contain <allow-run-time-binding>false</allow-run-time-binding> to prevent computed values when the page is built in Domino Designer. As a general pattern, a property should be implemented as follows.
import javax.faces.el.ValueBinding;
private String foo;
public String getFoo() {
if (foo != null) {
return foo;
}
ValueBinding vb = getValueBinding("foo");
if (vb != null) {
return (String) vb.getValue(getFacesContext());
} else {
return null;
}
}
public void setFoo(String foo) {
this.foo = foo;
}
The getter method first checks if the property field had been set explicitly (i.e. Its member is not null). If it has a value, it's returned. Otherwise, it checks if a ValueBinding had been assigned for this property and evaluates it or returns a default value.
For non-object based properties, there are some extra considerations because Java primitives cannot be null. Therefore, to check if the field has been set explicitly, you must test whether the value returned is the default value you set. For example, in the Renderer when calling the property's getter method, you should test whether the default value has been returned and if so, disregard the property value. When dealing with primitive based properties, you need to be careful when determining the default value of the property. For such properties (e.g. int or boolean), the implementation should use an Object member, like Integer or Boolean respectively. This had been made easier thanks to the Java 5 autoboxing capability (older Java implementations used an extra boolean flag to indicate whether the property had been set using the setter). Here is an example of a non-object based property and its corresponding getter and setter methods.
import javax.faces.el.ValueBinding;
private int foo = Integer.MIN_VALUE;
public int getFoo() {
if (foo != Integer.MIN_VALUE) {
return foo;
}
ValueBinding vb = getValueBinding("foo");
if (vb != null) {
Object result = vb.getValue(getFacesContext());
if (result == null) {
return Integer.MIN_VALUE;
} else {
return ((Number) result).intValue();
}
} else {
return Integer.MIN_VALUE;
}
}
public void setFoo(int foo) {
this.foo = foo;
}
The second requirement for implementing a property for a control is adding its definition to the control's entry in the xsp-config file. This addition allows the property to be set at design time in the Domino Designer Properties tab. Similar to its control's component entry, a property has a name, description and type.
<property>
<description>This is a description of the property</description>
<display-name>Foo</display-name>
<property-name>foo</property-name>
<property-class>java.lang.String</property-class>
<property-extension>
<!-- <localizable/> -->
<!-- <required>false</required> -->
<designer-extension>
<category>uictrl</category>
<editor>com.ibm.std.String</editor>
<!-- <editor-parameter/> -->
<!-- <visible/> -->
<!-- <validation-formula/> -->
</designer-extension>
</property-extension>
</property>
Similar again to the component entry, the property-extension element specifies extra details about the property. In particular, the designer-extension element is used by Domino Designer to determine details about the property at design time including the property's category for "All Properties" in the Properties tab. This is also where you can set a validation formula and editor for control properties. For more information about the property element, see "
XPages Configuration File Format - Property". To find out more about Property Editors in Domino Designer, see "
XPages Configuration File Format - Editor".
The property-class element of a property can contain any of these values.
- Java class
- e.g. javax.faces.convert.Converter
- Primitive (char, byte, short, int, long, float, double, boolean, string)
- Note: string is an alias for java.lang.String which is also treated as a primitive
- object
- Note: alias for java.lang.Object and is special because such properties allow any run time value, though the format in the file must be a String or value binding
- MethodBinding (javax.faces.el.MethodBinding)
- With the property-class javax.faces.el.MethodBinding and the property-extension <method-binding-property>true</method-binding-property> then a binding value (like #{} or ${}) is not saved as a computed value (ValueBinding) but as an expression that is explicitly invoked by the relevant control (like the server script which is invoked when you click a button)
- List (java.util.List)
- When using the List class, you can use the collection property-extension to specify that multiple values are allowed for this property in the XPage source (as opposed to only allowing a computed list). In that case, you should also define a property-item-class (indicating the type of the items in the list), the name of the add method and you should disallow runtime bindings (preventing Compute Dynamically expressions).
- For example:
<property>
<description>A list of parameters</description>
<display-name>Parameters</display-name>
<property-name>parameters</property-name>
<property-class>java.util.List</property-class>
<property-extension>
<collection-property>true</collection-property>
<property-item-class>com.ibm.xsp.complex.Parameter</property-item-class>
<property-add-method>addParameter</property-add-method>
<allow-run-time-binding>false</allow-run-time-binding>
</property-extension>
</property>
When the property-class is some Java class, it may be possible to assign a tag value to the property in the XPage source. For example, the Edit Box Converter property allows any converter tag as the property value.
<xp:inputText>
<xp:this.converter>
<xp:convertNumber integerOnly="true"></xp:convertNumber>
</xp:this.converter>
</xp:inputText>
Such tags, that can be used as property values, are known as complex types (as opposed to the primitive types like string values). Complex types must be declared in the xsp-config file within a complex-type, converter or validator element, very similar to a control's component definition. One of the most important attributes of a complex type is the base-complex-id it inherits from. It tells Domino Designer how this class can be used and assigned to control properties.
Creating the control renderer
A renderer is used to output some markup corresponding to a control to be displayed in a web browser or in the Notes client. Although it's not mandatory, it is considered bad practice for a control to generate the markup itself.
Every control renderer must extend from the JSF renderer base class javax.faces.render.Renderer or one of it's subclasses. If a renderer doesn't have to inherit the behaviour of an existing renderer, it will extend from Renderer.
The JSF renderer base class defines the methods: decode, encodeBegin, encodeChildren and encodeEnd. These are executed by the XPages runtime and are responsible for outputting some markup corresponding to a control. The decode method is used if the control is a UIInput when it copies the control value submitted from the browser during a POST request onto the control object. The methods encodeBegin, encodeChildren and encodeEnd render the markup corresponding to the beginning, children and end of the control respectively. By convention (established by the JSF implementation) if a renderer only outputs one tag and is not expected to have children then it should write that tag in the encodeEnd method.
When accessing control properties from the Renderer, it should be performed as follows. First, check whether the component parameter corresponds to the control (i.e. the control to which this Renderer is mapped) and if so call the relevant getter method. Otherwise, call the JSF component base class to access the property via the attributes Map.
package com.example.renderkit.html_basic;
import java.io.IOException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.render.Renderer;
import com.example.component.UICtrl;
public class UICtrlRenderer extends Renderer {
public void encodeBegin(FacesContext context, UIComponent component)
throws IOException {
}
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
UICtrl tcomponent = component instanceof UICtrl ? (UICtrl) component : null;
String foo;
if (tcomponent != null) {
foo = tcomponent.getFoo();
} else {
foo = (String) component.getAttributes().get("foo");
}
if (foo != null) {
// Output some markup
}
}
}
However, if the property is a primitive type when calling its corresponding getter method, you must test whether the default value has been returned indicating that it has not been set and if so disregard the property value.
UICtrl tcomponent = component instanceof UICtrl ? (UICtrl) component : null;
if (tcomponent != null) {
int foo = tcomponent.getFoo();
if (foo != Integer.MIN_VALUE) {
// Output some markup
}
} else {
Integer foo = (Integer) component.getAttributes().get("foo");
if (foo != null && foo != Integer.MIN_VALUE) {
// Output some markup
}
}
Register the control renderer
By default, every NSF application provides a faces-config.xml file (*.nsf/WebContent/WEB-INF/faces-config.xml). However, the filename "faces-config.xml" is only permitted in the application and must be changed when creating an XPages library.
An XPages library should provide a faces-config file that mostly contains the association between controls and renderers, per render-kit. This file is part of the JSF specification and every entry described can be used. However, in practice you will only need to set the render-kit renderers.
A render-kit is a collection of renderers typically specialized for some combination of client and / or markup language. It's a JSF delegation mechanism that selects the class to use to render the markup for a control.
Here is an example of a faces-config file for a control.
<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
<render-kit>
<!-- <render-kit-id>HTML_BASIC</render-kit-id> -->
<renderer>
<component-family>com.example.uictrl</component-family>
<renderer-type>com.example.uictrl</renderer-type>
<renderer-class>com.example.renderkit.html_basic.CtrlRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
If the render-kit-id is not defined, the default (HTML_BASIC) will be used. The render-kit-id defines the render-kit or platform where a renderer is to be applied. This can be either HTML_RCP or HTML_BASIC for the Notes client or the web browser respectively. However, since HTML_RCP inherits from HTML_BASIC, any renderer defined for the web browser will be used in the Notes client. If you wish to provide a renderer to be used only in the Notes client, you would specify HTML_RCP as the render-kit-id. The component-family, renderer-type and render-kit-id together uniquely identify a renderer (renderer-class) in the JSF runtime.
A control's renderer is identified using the control's component-family and renderer-type. These should produce the same values returned when the getFamily and getRendererType methods are invoked on an instance of the control class. The renderer-type is unique to a control and the renderer implementation usually handles only one control.
The mapping from a Java control to its corresponding renderer is done by means of the control's getFamily and getRendererType methods and the faces-config file which lists the renderer-class for a given component-family and renderer-type. The JSF runtime reads the faces-config file, creates the renderer instance and the UIComponentBase superclass invokes the renderer.
Distribution
An XPages library exposes a set of controls and complex types that can be used in any application. Those artifacts are defined in the XPages registry which is a catalog used by Domino Designer to fill the Controls palette and display the Properties tab.
An XPages library can be deployed as a plugin in the Domino server or in the Notes client. It implements some extension points that contribute to both the runtime and design time (within Domino Designer).
If XPages controls or other JSF artifacts like validators or converters are part of the library then a library object has to be created and registered. This should define the library's name, the faces-config file and the xsp-config file.
For more information, see the following articles.
Creating an XPages Library
Using an XPages Library in Domino Designer
Deploying XPages Libraries