XPages Extensibility API 是 Notes/Domino 8.5.2 的一个新功能。它是一套 Java 的 API。通过使用这一套 API,开发人员可以访问 XPages 运行时的信息,并且可以对 XPages 的组件库进行扩展,创建自定义的组件。自定义的组件可以被打包成扩展组件库 (Extension Library),集中部署到 Notes 客户端或者 Domino 服务器上。Extensibility API 是 XPages 开发的一个进阶知识,在开始使用 Extensibility API 之前,希望您对 XPages 的基本开发已经有一定的了解。
我们知道,XPages 是基于 JSF 框架的。对 JSF 有一定了解的开发人员,可以很容易的从 XPages 的开发中看到很多 JSF 的概念,如 Converter, Validator, Managed bean 等等。JSF 的相关概念在 XPages 中基本都是适用的, 开发人员也可以在 XPages 的开发中,使用 JSF 的 API。另一方面,JSF 是一个开放的框架,它允许开发人员开发自定义的扩展。这里说的扩展不仅局限于 UI 控件,也包括非 UI 控件,如 Converter 等。 在 Notes/Domino 8.5.2 之前, 这部分扩展 API 并没有公开给第三方的开发人员使用。为了满足 XPages 开发中的高级需求,8.5.2 发布了 Extensibility API,同时 Domino Designer 对 Extensibility API 的开发也提供了良好的支持。IBM 还基于 Extensibility API, 创建了一个 XPages Extension Library。Extension Library 提供了很多有用的 UI 控件,如对话框,菜单,工具条,还提供了 REST 服务相关的功能。我们可以从 OpenNTF 站点上下载 Extension Library,它的源代码也可以通过 OpenNTF 获得。在使用 Extensibility API 进行开发的时候,Extension Library 的源代码是一个非常好的参考。目前,我们可以自定义的组件包括:
- UI 控件
- Converter
- Validator
- 数据源
- Language binding
- Simple Actions
大家可能会问,XPages 已经提供了 Custom Control 的方式定义自定义组件,为什么还要提供 Extensibility API ?这两种方式又有什么区别?就我的理解,它们的区别在于:
- Custom Control 主要是通过组合已有控件的方式进行扩展,大多数情况下,它主要是用来定义 UI 控件。而 Extensibility API 不仅仅可以扩展 UI 控件,还可以扩展非 UI 控件。此外,Extensibility API 不局限于对已有的控件进行组合,开发人员可以定义全新的控件。
- Extensibility API 提供了丰富的 API 让开发人员可以访问 XPages 运行时的信息,在 Extensibility API 发布之前,Custom Control 中能访问的信息相对较少。。
- Custom Control 的门槛较低,比较容易上手和使用。而 Extensibility API 较为复杂,需要对 XPages 有更深入的了解。如果在使用 Custom Control 就可以满足使用需求的情况下,没有必要一定要使用 Extensibility API。
- 在组件重用方面,Custom Control 的代码是包括在 nsf 中的,重用的时候需要在 nsf 中进行拷贝;使用 Extensibility API 创建的组件可以被打包成 library 的方式,方便的进行部署和共享。
下面我们就来看使用 Extensibility API 创建自定义组件的步骤。
我们首先在 Designer 中创建一个新的应用程序。在这里我们将示例程序命名为 ExtSample, 选择 Blank 模板。
图 1. 新建应用程序
前面说过,Extensibility API 是一套 Java 的 API,在开发的时候主要使用的是 Java。新版的 Domino Designer 是构架在 Eclipse 平台之上的,因此,开发人员在开发的时候可以很方便的使用 Eclipse 强大的 Java 编辑功能。我们可以通过菜单栏打开熟悉的 Java 开发视图,选择 Window -> Open perspective -> Java。
图 2. Java 界面
接下来我们要创建一个 Java 源文件目录,用于存放我们的 Java 代码。从图 2 中可以看到,新建的应用中已经包括了一个名为 Local 的 Java 源文件目录,注意不要在这个目录下创建我们自定义的 Java 代码。这个 Local 目录并不是保存在 nsf 文件中的。当我们将开发好的 nsf 程序拷贝到别的机器上的时候,这个目录下的 Java 代码会丢失。我们需要创建一个另外的源文件目录。通常我们可以使用 WebContent/WEB-INF/src。
下面我们可以开始创建自定义的组件。 所有的 UI 控件都必须从 javax.faces.component.UIComponent 类继承,为了实现的方便,我们可以继承自 UIComponentBase,它为一些方法提供了默认的实现。 UIComponent 描述了一个 UI 控件的状态以及行为,它可以自己负责 UI 的绘制工作,但通常都会将 UI 绘制代理给一个 Renderer 对象来进行。这和 JSF 早期的一个设计目标有关。JSF 希望同样的一个 UIComponent,在搭配不同的 Renderer 的情况下,可以展示出不同的 UI。这样,在使用不同的设备(浏览器,手机)来访问同一个页面的时候,JSF 可以根据设备的不同,选择合适的 renderer,从而绘制出相应的页面。这一步我们暂时不需要加入额外的代码,实现如下:
清单 1. 新建控件类
public class SampleComponent extends UIComponentBase {
@Override
public String getFamily() {
return null;
}
}
|
为了能够让 Designer 识别出我们新定义的组件,并且可以在 XPages 中使用,我们需要进行注册。注册的方式是通过 xsp-config 文件。xsp-config 文件只在开发过程中被 Designer 识别,在 XPages 运行时是不起作用的。这个文件的主要作用是向 Designer 提供控件的各种信息。一个 xsp-config 文件中可以注册多个组件。我们在 WebContent/WEB-INF 目录下创建一个 sample.xsp-config 文件,内容如下:
清单 2. 注册控件信息
<faces-config>
<faces-config-extension>
<namespace-uri>http://www.ibm.com/xsp/sample</namespace-uri>
<default-prefix>sa</default-prefix>
</faces-config-extension>
<component>
<description>This is a sample control.</description>
<display-name>Sample Control</display-name>
<component-type>com.ibm.sample.SampleComponent
</component-type>
<component-class>com.ibm.sample.SampleComponent
</component-class>
<component-extension>
<component-family>com.ibm.sample.SampleComponent
</component-family>
<renderer-type>com.ibm.sample.SampleComponent
</renderer-type>
<tag-name>sample</tag-name>
</component-extension>
</component>
</faces-config>
|
在这个文件中,最重要的几个信息是控件的 namespace、class 以及 tag。Domino Designer 会根据这三个属性,匹配 XPages 页面的标签和 Java 类。
我们可以新建一个 XPages 页面来测试一下我们的自定义组件是否已经被 Designer 所识别。打开新建的 XPages 页面后,在控件面板中点击 Other,在弹出的对话框中选择 Other Controls。如果之前的步骤正确的话,我们就可以看到新创建的组件,如图 3 所示。我们在 xsp-config 文件中定义的属性会被 Designer 显示在 Control details 中。如果在控件面板中看不到自定义的控件,则说明 xsp-config 文件中存在错误。我们可以查看 Designer 的 log,来了解和分析错误信息。可以通过选择菜单栏的选择 Help -> Support -> View Trace 打开 log 显示。
图 3. 在控件面板中选择自定义的组件
接下来我们就开始创建 Renderer。Renderer 需要在 faces-config.xml 文件中定义,定义内容如下:
清单 3. 定义 renderer
<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
<render-kit>
<renderer>
<component-family>com.ibm.sample.SampleComponent</component-family>
<renderer-type>com.ibm.sample.SampleComponent</renderer-type>
<renderer-class>com.ibm.sample.SampleRenderer</renderer-class>
</renderer>
</render-kit>
</faces-config>
|
XPages 根据 UIComponent 的 component-family 和 renderer-type 来确定对应的 Renderer。因此我们就需要修改前面定义的 SampleComponent 类,覆盖 getFamily 和 getRendererType 方法,返回对应的值。细心的读者可能会问,xsp-config 文件中已经声明了 component-family 和 renderer-type,为什么我们在 Java 代码中还要重复这一信息?这是因为 xsp-config 文件只在开发时起作用,而 Renderer 的匹配则是在运行时决定的。为了在运行时也能获得相应的信息,我们就需要通过这两个方法来实现。
清单 4. 修改控件类,返回 renderer 所需信息
@Override
public String getFamily() {
return "com.ibm.sample.SampleComponent";
}
@Override
public String getRendererType() {
return "com.ibm.sample.SampleComponent";
}
|
Renderer 类需要继承 javax.faces.render.Renderer。这里我们覆盖 encodeEnd 方法,在这个方法里面生成 HTML 的输出,代码如下:
清单 5. SampleRenderer 的实现
public class SampleRenderer extends Renderer {
@Override
public void encodeEnd(FacesContext context, UIComponent component)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
writer.startElement("div", component);
Map attr = component.getAttributes();
writer.writeText("Hello World. ", null);
writer.endElement("div");
}
}
|
通常我们都会需要给控件添加一些自定义的属性。在页面开发人员使用这些控件的时候,可以通过自定义属性,对控件做一定程度的配置。例如 XPages 自带的大多数控件都提供了 style 和 styleClass 这两个属性,用于指定 css。接下来我们就将给自定义控件添加这两个属性。
首先我们要修改 SampleComponent 类,加入相应的成员变量,以及 getter/setter 方法,修改如下:
清单 6. 定义控件属性以及 getter/setter 的实现
private String style;
private String styleClass;
public String getStyle() {
if(style != null)
return style;
ValueBinding vb = getValueBinding("style");
if(vb != null)
return (String)vb.getValue(getFacesContext());
else
return null;
}
public void setStyle(String style) {
this.style = style;
}
public String getStyleClass() {
if(styleClass != null)
return styleClass;
ValueBinding vb = getValueBinding("styleClass");
if(vb != null)
return (String)vb.getValue(getFacesContext());
else
return null;
}
public void setStyleClass(String styleClass) {
this.styleClass = styleClass;
}
|
值得注意的是 get 方法,在 XPages 的开发中,我们可以通过服务器端的 Javascript(SSJS) 或者值绑定的方式动态的给属性赋值,如果这个属性允许用户通过 SSJS 或值绑定的方式赋值的话,我们就需要在相应的 get 方法中通过 JSF 的 API 对其进行解析。
此外,我们还要实现 saveState 和 restoreState 方法,XPages 在保存和恢复页面的时候通过这两个方法进行状态的保存和恢复,它们的实现必须保证对应关系,这样保存的状态才能够正确的被恢复。saveState 和 restoreState 可以按照以下的方式来实现:
清单 7. saveState 和 restoreState 的参考实现
@Override
public void restoreState(FacesContext context, Object state) {
Object[] values = (Object[])state;
super.restoreState(context, values[0]);
style = (String)values[1];
styleClass = (String)values[2];
}
@Override
public Object saveState(FacesContext context) {
Object[] values = new Object[3];
values[0] = super.saveState(context);
values[1] = style;
values[2] = styleClass;
return values;
}
|
为了使得 Designer 可以正确的识别新定义的属性,我们需要修改 xsp-config 文件。在 xsp-config 文件中,我们可以定义属性在 Designer 中显示的时候所属的分类,以及编辑这个属性时候所用的编辑器。Designer 内建了一些不同种类的编辑器可以方便我们编辑属性值,这里我们选用最简单的字符串编辑器。xsp-config 文件新增的内容如下:
清单 8. 向 Designer 注册自定义属性
<property>
<description>CSS Styles</description>
<display-name>style</display-name>
<property-name>style</property-name>
<property-class>java.lang.String</property-class>
<property-extension>
<Designer-extension>
<category>styling</category>
<editor>com.ibm.std.String</editor>
</Designer-extension>
</property-extension>
</property>
<property>
<description>CSS Style Class</description>
<display-name>styleClass</display-name>
<property-name>styleClass</property-name>
<property-class>java.lang.String</property-class>
<property-extension>
<Designer-extension>
<category>styling</category>
<editor>com.ibm.std.String</editor>
</Designer-extension>
</property-extension>
</property>
|
修改 xsp-config 文件之后,我们可以在 XPages 页面中测试一下结果。如果 Java 文件和 xsp-config 文件中的声明都正确的话,我们就可以在 Designer 的属性面板中看到新定义的属性,如图 4 所示。
图 4. 自定义属性
最后我们需要修改 Renderer 来使用新定义的属性。
清单 9. 在 renderer 中读取属性值并生成输出
String style = (String)attr.get("style");
String styleClass = (String)attr.get("styleClass");
if(style != null && style.trim().length() != 0)
writer.writeAttribute("style", style, null);
if(styleClass != null && styleClass.trim().length() != 0)
writer.writeAttribute("class", styleClass, null);
|
我们可以创建一个 XPages 页面来测试一下最后的结果。页面的内容如下:
清单 10. 测试页面
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core" xmlns:sa="http://www.ibm.com/xsp/sample"> <sa:sample id="sample1" style="color:blue"> </sa:sample> </xp:view> |
生成的显示如图 5 所示。
图 5. XPages 显示
至此,我们已经完成了使用 Extensibility API 创建自定义 UI 控件的所有步骤。
定义好的自定义控件需要能够方便的部署到页面开发人员的 Designer 中,以及 Domino 生产服务器上。XPages 支持将自定义的控件打包成组件库来进行部署。一个组件库实际上就是一个 Eclipse 插件,包括了自定义控件的 Java 代码和相关的 xml 文件。组件库的工程结构如图 6 所示。
图 6. Library 的工程结构
从图 6 中可以看到,组件库的很多代码在前面的步骤中都已经完成了,我们所需要做的只是将代码从 nsf 文件中复制到独立的插件工程里。有一点要特别注意的小区别是,我们将 faces-config.xml 文件重命名为了 sample-faces-config.xml 文件。这是因为每个应用中只能存在一个 faces-config.xml 文件,而 XPages 应用的 nsf 中已经包括一个 faces-config.xml 文件了,所以这里就不能再使用这个文件名。
唯一需要实现的是 SampleLibrary 类。它用于描述这个组件库中包括了哪些组件以及相关的信息。首先我们需要向 XPages 注册一下自定义的组件库,XPages 提供了一个扩展点来进行注册,定义的方式如下
清单 11. 定义 library 扩展
<extension
point="com.ibm.commons.Extension">
<service
class="com.ibm.sample.library.SampleLibrary"
type="com.ibm.xsp.Library">
</service>
</extension>
|
SampleLibrary 需要继承自 AbstractXspLibrary 类,实现如下:
清单 12. SampleLibrary 的实现
public class SampleLibrary extends AbstractXspLibrary {
public SampleLibrary() {
}
@Override
public String getLibraryId() {
return "com.ibm.sample.library";
}
@Override
public String[] getFacesConfigFiles() {
return new String[]{"META-INF/sample-faces-config.xml"};
}
@Override
public String getPluginId() {
return "com.ibm.sample.library";
}
@Override
public String[] getXspConfigFiles() {
return new String[]{"META-INF/sample.xsp-config"};
}
}
|
作为一个 Eclipse 插件,扩展组件库在 Notes/Domino 中的部署方式是和一般的插件部署是相同的。这里就不再赘述。大家可以参考 OpenNTF 上 XPages Extension Library 文档的部署部分。
安装完成以后我们可以新建一个 XPages 应用检测一下是否可以使用新安装的控件了。要注意的是,这里控件不再默认出现在 Other 分类下,而是出现在 Extended 分类下面。
图 7. 部署的控件在控件面板中的位置
希望大家在看了这篇文章以后,对如何使用 Extensibility API 创建自定义控件有更好的了解。除了创建 UI 控件以外,我们还可以创建非 UI 控件。即使我们没有创建自定义控件的需求,里面的很多 API 在我们写服务器端逻辑的时候,也是很有帮助的。
| 描述 | 名字 | 大小 | 下载方法 |
|---|---|---|---|
| 样例代码 | com.ibm.sample.library_1.0.0.jar | 8KB | HTTP |
| 样例代码 | ExtSample.nsf | 384KB | HTTP |
学习
- 访问 Extension Library项目,了解更多关于 XPages 扩展的内容。
- 访问 XPagesInfo,获取 XPages 技术最新的信息。
- 访问站点 Lotus Notes and Domino wiki,了解 XPages 技术相关内容。
- 访问 developerWorks Lotus 专区。
- 随时关注 developerWorks 技术活动和网络广播。
讨论
- 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。
- 加入 IBM 软件下载与技术交流群组,参与在线交流。