XPages 技术大大提高了我们在 Domino 上开发 Web2.0 应用的能力,越来越多的 XPages 应用被开发出来。我们在开发某些应用时可能会有一种特殊的需求,那就是 XPage 页面上的控件需要动态创建。例如我们做一个调查问卷应用,所有问题的类型、内容等都是由使用者定义的,这就需要我们在运行时根据使用者的定义把调查问卷的 界面动态的创建出来。
XPages 基于 Java Server Face 技术,它天生具备着这种动态创建控件的能力,可以说所有 XPage 的界面都是由程序动态创建出来的。本文由简单到复杂,详述了在 XPage 中动态创建基本控件的方法,以及各种高级的技术,例如动态绑定数据源、动态绑定事件、动态自定义控件创建等。本文也将给出实例代码使我们开发者能方便直观 的掌握这一技术。
XPage 是一种 Domino 服务器上的基于 Java Server Face 的一种 Web 应用开发技术。它提供一种所见即所得的开发方式,使得开发的效率大大提高。同时它内置 dojo 的 javascript 库,具有大量的漂亮而且功能强大的 Web 应用开发的控件,大大简化了我们应用的开发。XPage 的扩展性也非常好,我们可以方便的开发出自定义的控件来增强平台的功能,方便的实现代码的组件化和共享。
我们知道 XPage 所生成的 html 页面都是由服务器解析 XPage 代码动态创建的,其创建的过程正是遵循了 Java Server Face 的标准。我们只要查看一下我们开发的一个 XPage 的页面的源代码会发现,其都是由控件组成,控件间可能相互嵌套,形成一棵控件树。而在运行时,XPage 服务器正是根据这些控件的定义,在内存中创建出了一棵控件树,然后根据控件树中的控件找到对应生成 HTML 代码的类,最后生成 HTML 代码输出。内存中的每一个控件实际上对应一个 Java 的类,该类负责控件的事件响应,数据绑定等,同时该类也对应有一个 html_render 类,负责 HTML 代码的生成等工作。正是因为有这样的特性,使得我们可以方便的动态改变界面控件。
在运行时,我们只要改变内存中的控件树,往树中插入相应的控件类对象,我们就可以动态地生成控件,从而改变输出的 Web 页界面。下面我们就来详细介绍如何动态地创建控件。
首先我们要了解,在 XPage 中的每一个控件,不管是核心基本控件还是自定义控件,都有一个对应的 Java 类。所有这些 Java 类都继承自 javax.faces.component.UIComponentBase。我们要动态的创建控件,实际上只要创建对应类的实例就可以了,非常简单。 例如,我们要创建一个按钮,就可以通过如下清单 1 中的代码来创建:
var newbutton:com.ibm.xsp.component.xp.XspCommandButton = new com.ibm.xsp.component.xp.XspCommandButton(); newbutton.setValue(“dynamic button”); newbutton.setId("buttonID"); |
我们的控件创建出来了后,我们希望它能够出现在指定的位置,这就要求我们把它插入到控件树中合适的位置。这时我们首先要知道要插入节点的父控件的 id,通过这个 id 就可以方便的找到父控件对应的 Java 对象,然后将我们生成的对象添加成它的孩子即可。
例如,我们希望将创建的按钮添加到一个 panel 中,该 panel 的 id 是 panelcontainer。我们可以通过下面的清单 2 中的代码来实现添加:
var panelcontainer = getComponent( 'panelcontainer' ); panelcontainer.getChildren().add( newbutton ); |
我们将以上代码加到一个 XPage 中,来实现一个简单的例子:点击一个按钮,创建一个动态按钮。以下是我们的设计的界面,包含一个按钮和一个 panel。
清单 3 中包含了所有 XPage 代码:
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"> <xp:button value="Click to create new button" id="button4"> <xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="panelcontainer"> <xp:this.action><![CDATA[# {javascript:var newbutton:com.ibm.xsp.component.xp.XspCommandButton = new com.ibm.xsp.component.xp.XspCommandButton(); newbutton.setValue("dynamic button"); newbutton.setId("buttonID"); var panelcontainer = getComponent( 'panelcontainer' ); panelcontainer.getChildren().add( newbutton );}]]></xp:this.action> </xp:eventHandler> </xp:button> <xp:br></xp:br><xp:panel id="panelcontainer"></xp:panel></xp:view> |
在我们创建了控件之后,我们通常需要给控件设置一些属性。设置属性也是非常简单,每个控件对应的类都有一系列的 set 方法,我们可以调用对应的 set 方法来设置属性。上面按钮的例子中我们设置了两个属性 id 和 value。对于其他的属性的设置,我们可以首先查看控件属性面板中属性的名字,设置该属性的方法名通常就是将其首字母大写,然后加 set。
以上我们介绍了动态创建 XPage 控件的基本方法,其中很重要的一点是我们要知道每种控件所对应的 Java 类的名字。下面我们总结了每一种基本控件对应的 Java 类名,以供读者参考:
控件名 | Java 类名 |
---|---|
Button | com.ibm.xsp.component.xp.XspCommandButton |
Edit Box | com.ibm.xsp.component.xp.XspInputText |
Rich Text | com.ibm.xsp.component.xp.XspInputRichText |
Multiline Edit Box | com.ibm.xsp.component.xp.XspInputTextarea |
List Box | com.ibm.xsp.component.xp.XspSelectOneListbox |
Combo Box | com.ibm.xsp.component.xp.XspSelectOneMenu |
Check Box | com.ibm.xsp.component.xp.XspInputCheckbox |
Radio Button | com.ibm.xsp.component.xp.XspInputRadio |
Check box group | com.ibm.xsp.component.xp.XspSelectManyCheckbox |
Radio button group | com.ibm.xsp.component.xp.XspSelectOneRadio |
Link | com.ibm.xsp.component.xp.XspOutputLink |
Line Break | com.ibm.xsp.component.xp.XspLineBreak |
上一节我们介绍了动态创建 XPage 控件的基本方法,在我们实际应用中,我们可能需要更高级的功能,比如我们的编辑框(Edit Box)需要绑定数据,按钮需要绑定事件处理程序。这就要求我们用到一些更高级的技术。下面我们一一介绍这些高级技术。
在我们设计 XPage 的页面时,我们经常会将输入框绑定到表单(Form)的某一个域(Field),这样在保存时,输入框中的数据自动会保存到相应的域中,非常方便。同样,我们在动态创建界面控件时有时也需要去将我们界面的控件与数据绑定。
下面我们以一个编辑框为例,绑定到我们 session 中的一个变量。
var valueBinding = facesContext.getApplication().createValueBinding( "#{sessionScope.testbox}" ); var testbox = getComponent( 'testEditBox' ); testbox.setValueBinding('value',valueBinding); |
在上面的例子中,我们先通过调用 facesContext.getApplication() 的 createValueBinding 方法创建了一个 ValueBinding,它的参数是用花括号括起来的我们要绑定的变量名,前面加一个井号表示是表达式。如果要绑定 Domino Document 的数据源的一个域,方法也是类似的。例如,如果数据源的变量名是 document1,要绑定到 contactName 这个域,那参数应该写成“#{document1.contactName}”。创建好了 ValueBinding,我们只需要直接把它设置到对应的界面控件就可以了。
如果我们要动态创建一个按钮,通常我们也希望用户点击这个按钮时,执行我们指定的操作。这就涉及到动态绑定事件处理函数。
在 XPage 中,一个事件处理的代码对应有一个类:com.ibm.xsp.component.xp.XspEventHandler。和我们动态创建界面控件类 似,我们只要创建一个 XspEventHandler 的对象,并且将其添加成为对应的界面控件的孩子就可以了。
我们知道,在 XPage 中事件处理分为客户端事件处理和服务器端事件处理。我们在绑定这两种不同的事件处理方法时,也需要用不同的方法。
我们首先介绍绑定客户端的事件处理。我们客户端的事件处理函数通常是客户端的 Javascript 代码,其在 XPage 中也对应有一个 Java 类:com.ibm.xsp.actions.client.ExecuteScriptClientAction。下面来看具体的代码:
var newEvent:com.ibm.xsp.component.xp.XspEventHandler = new com.ibm.xsp.component.xp.XspEventHandler(); newEvent.setSubmit(false); newEvent.setEvent("onclick"); var script:com.ibm.xsp.actions.client.ExecuteScriptClientAction = new com.ibm.xsp.actions.client.ExecuteScriptClientAction(); script.setComponent(newEvent); script.setScript("alert(\"OK\")"); newEvent.setScript(script); newbutton.getChildren().add(newEvent); |
在上面的代码中,我们给一个按钮 newbutton 绑定了一端客户端的处理代码。第一行语句,我们创建了一个 XspEventHandler 的对象 newEvent,它用来代表我们的事件处理句柄。第二句调用 setSubmit(false),表示这是一个客户端的事件绑定,不需要提交。第三句是设置响应事件的名称,我们这里设置的是 onclick 事件。一个界面控件通常会有很多的事件,比如 ondbclick,onmousedown 等,每个界面控件能够响应的事件能在它对应的属性面板的事件 Tab 中看到。最后一行代码将创建的事件处理代码与按钮绑定,只需要将 newEvent 添加到按钮的孩子中就可以了。
服务器端的事件绑定和客户端的类似,也需要创建一个 XspEventHandler,也要将创建的事件对象添加到界面控件的孩子中。不同的是,首先它需要将事件的 setSubmit 设成 true,因为需要提交数据,数据提交后页面的刷新模式还区分部分刷新和完全刷新。另外服务器端代码对应的对象也不同。下面我们来看看例子代码。
var newEvent:com.ibm.xsp.component.xp.XspEventHandler = new com.ibm.xsp.component.xp.XspEventHandler(); var methodStr = "#{javascript:var testbox = getComponent('testEditBox');\ntestbox.setValue('222');}"; var methodbinding:com.ibm.xsp.binding.MethodBindingEx = facesContext.getApplication().createMethodBinding(methodStr,null); methodbinding.setComponent(newEvent); newEvent.setAction(methodbinding); newEvent.setSubmit(true); newEvent.setEvent("onclick"); newEvent.setRefreshMode("complete"); newbutton.getChildren().add(newEvent); |
我们如果自己创建了一些自定义控件(Custom Control),也希望能动态的添加到 XPage 页面中去,这个比较复杂。一个自定义控件实际上对应一个 XPage 的页面,我们要在一个 XPage 页面中添加一个自定义控件实际上就是将自定义控件对应的页面包含进来。由于需要包含的自定义控件中包含有子控件,这些子控件也需要添加到我们当前的控件树 当中来。这个添加的过程比较复杂,XPage 的 API 中没有简单直接的方法来实现。我们需要借助 XPage Extension Library 中提供的函数来实现。XPage Extension Library 可以在 http://extlib.openntf.org/中下载到。
var customCtrl = new com.ibm.xsp.component.UIIncludeComposite(); customCtrl.setPageName("/testcustomcontrol.xsp"); var propertyMap = customCtrl.getPropertyMap(); propertyMap.setProperty("property_1", "property_1_value"); customCtrl.setId("custom_control_id"); // 创建一个 XPages Extension Library 中的 ControlBuilder 对象 var objBuilder = new com.ibm.xsp.extlib.builder.ControlBuilder(); // 如果要使用 ControlBuilder,我们需要用到其中的 ControlImpl 类 . // 我们通过下面的方法得到 ControlImpl 类 var classControlImpl = objBuilder.getClass().getDeclaredClasses()[1]; // 创建一个父 panel 的 ControlImpl 对象 var objImplParent = new classControlImpl(panelcontainer); // 创建一个自定义控件的 ControlImpl 对象 var objImplControl = new classControlImpl(customCtrl); // 把自定义控件添加到父 panel 中 objImplParent.addChild(objImplControl); // 重新构建父 panel,从而创建出自定义控件中的所有界面控件 objBuilder.buildControl(facesContext,objImplParent,false); |
上面的例子动态的将一个 XPage 自定义控件 testcustomcontrol 动态添加到一个 XPage 中。其中第一行和第二行代码创建了一个 UIIncludeComosite 的对象用来代表自定义控件。第三行和第四行设置自定义控件的属性,如果自定义控件有多个属性值需要传入,可以多次调用 setProperty 来进行设置。从第六行起,通过调用 XPage Extension Library 中的方法,将自定义控件中的所有子控件挂接到自定义控件的父控件上,从而实现了自定义控件的动态添加。
不仅我们的界面控件可以动态添加,实际上 XPage 中的数据源也是可以动态添加的,这样给了我们更大的灵活性。在 XPage 中动态添加数据源和我们添加界面控件是类似的,只是对应的类不一样。下面来看代码。
var data = new com.ibm.xsp.model.domino.DominoDocumentData(); data.setComponent(view); data.setVar("document1"); data.setFormName("Contact"); view.addData(data); |
上面的代码中,我们给整个 XPage 页面动态添加了一个 Domino Document 的数据源,它的变量名设为了 document1,它对应的 Form 是 Contact。代码中的 view 是一个全局变量,代表当前 XPage 的根界面控件。
上面我们介绍了 XPage 中各种控件的动态创建的方法,下面我们通过一个综合的例子来了解上面各种方法的应用。
首先,我们要在 Notes 数据库中创建一个叫“Contact”的表单(Form),它有两个域(Field):FirstName 和 LastName。另外,创建一个视图(View)用来显示所有的 Contact。
我们创建一个叫 DynamicSample 的 XPage 页面,上面有一个“开始动态添加”,其他的界面控件和数据源都是通过这个按钮的事件响应代码进行添加。整个 XPage 页面的开始界面如下:
点击按钮之后动态创建出的界面如下:
输入 First Name 和 Last Name 后点击 Save 按钮,就可以将输入的内容保存到 Notes 数据库中。整个 XPage 的代码如下:
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"> <xp:button id="button1" value="开始动态添加"> <xp:eventHandler event="onclick" submit="true" refreshMode="complete"> <xp:this.action><![CDATA[#{javascript:var data = new com.ibm.xsp.model.domino.DominoDocumentData(); data.setComponent(view); data.setVar("document1"); data.setFormName("Contact"); view.addData(data); var panelcontainer = getComponent( 'panelcontainer' ); // Add button var newbutton:com.ibm.xsp.component.xp.XspCommandButton = new com.ibm.xsp.component.xp.XspCommandButton(); newbutton.setValue("Save"); newbutton.setId("saveButton"); panelcontainer.getChildren().add( newbutton ); var newEvent:com.ibm.xsp.component.xp.XspEventHandler = new com.ibm.xsp.component.xp.XspEventHandler(); var methodStr = "#{javascript:document1.save();}"; var methodbinding:com.ibm.xsp.binding.MethodBindingEx = facesContext.getApplication().createMethodBinding(methodStr,null); methodbinding.setComponent(newEvent); newEvent.setAction(methodbinding); newEvent.setSubmit(true); newEvent.setEvent("onclick"); newEvent.setRefreshMode("complete"); newbutton.getChildren().add(newEvent); var linebreak1:com.ibm.xsp.component.xp.XspLineBreak = new com.ibm.xsp.component.xp.XspLineBreak(); panelcontainer.getChildren().add( linebreak1 ); var label1 = new com.ibm.xsp.component.xp.XspOutputLabel(); label1.setValue("First Name"); label1.setId("firstname_label1"); panelcontainer.getChildren().add( label1 ); var inputtext1 = new com.ibm.xsp.component.xp.XspInputText(); inputtext1.setId("firstName1"); var valueBinding = facesContext.getApplication().createValueBinding( "#{document1.FirstName}" ); inputtext1.setValueBinding('value',valueBinding); panelcontainer.getChildren().add( inputtext1 ); var linebreak2:com.ibm.xsp.component.xp.XspLineBreak = new com.ibm.xsp.component.xp.XspLineBreak(); panelcontainer.getChildren().add( linebreak2 ); var label2 = new com.ibm.xsp.component.xp.XspOutputLabel(); label2.setValue("Last Name"); label2.setId("lastname_label1"); panelcontainer.getChildren().add( label2 ); var inputtext2 = new com.ibm.xsp.component.xp.XspInputText(); inputtext2.setId("lastName1"); valueBinding = facesContext.getApplication().createValueBinding( "#{document1.LastName}" ); inputtext2.setValueBinding('value',valueBinding); panelcontainer.getChildren().add( inputtext2 ); }]]></xp:this.action> </xp:eventHandler> </xp:button> |
以上我们介绍了在 XPage 中动态创建控件的方法,相信大家对 XPage 技术有了更深的了解。我们可以看到 XPage 技术不仅可以方便的快速开发,其架构也非常灵活,我们可以使用这些技术开发出非常强大的应用。有了这些动态技术,我们甚至可以开发出一个基于 Web 的 XPage 设计工具。在有些应用中希望用户能够在 Web 上直接设计简单的界面,有了这些 XPage 的动态技术,这些都能非常容易实现。
学习
讨论