作为以 XForms 作为数据描述和交换方式的表单,在以丰富的页面和逻辑展示给用户的同时,最大的价值在收集用户的数据并提交给处理系统,并获得准确的反馈给用户。这里无法避免的一个问题就是,在向用户呈现这样一个具备丰富表现力的表单的过程中,如何提供给用户必备的可选项,来提高表单与用户之间的交互性。本文要讨论的就是基于 XFroms 的表单在处理预加载数据方面,可以参照的几种解决方案。并按照由浅入深的程度逐一介绍。
预加载数据到表单,能想起来的最简单的办法当然是将待加载的数据直接写到表单中了,这是最直观也最明白的做法。Lotus Forms 中的 XFDL 是 XFroms 的宿主语言,这个语言规范提供了很多 UI 方面的元素来增强表单与用户之间的交互能力,其中就包括 Cell 这种类型的 item,来为可以合成组(group)的选项提供语义上的支持。Cell 的定义可以简单写成如下这样:
清单 1. 数据和逻辑混在一起
<popup sid="POPUP1"> <itemlocation> <x>70</x> <y>231</y> </itemlocation> <label>XFDL Country</label> <size> <width>20</width> <height>1</height> </size> <group>country_group</group> </popup> <cell sid="CELL1"> <value>China</value> <group>country_group</group> </cell> <cell sid="CELL2"> <value>United States</value> <group>country_group</group> </cell> |
这样,只要是 UI 元素(List、Popup 或者 Checkgroup)的 group 申明为 country_group,在表单被打开呈现时,该 UI 元素自然就会绑定同一 group 的所有 cell 的数据。这样的方式操作起来一目了然,但弊端也是无法忽略的。如果可选项的数据是限制在可以(人工输入)接受的限度下,无疑我们就不需要再继续本文下面的讨论了。一旦可选项数据产生了数量级上的变化,那么每次修改表单源码,并重复拷贝粘贴的工作量无疑是场噩梦。一个再正常不过的例子就是,在搜集用户账号信息的表单中,Country 这一项是绝不会只有 China 和 United States 两个选项供选择的。
XForms 作为专门为表单而设计的 WEB 标准,提供了一系列增强 UI 元素与数据实例之间进行绑定的标签定义,比如 xform:item 和 xforms:itemset。使用 xforms:itemset 同样可以将 UI 中描述可选项的元素和数据实例中的数据绑定起来,达到动态绑定的效果。这样数据就与 UI 脱离开来,两部分各司其职,表单各部分结构清晰,达到 MVC 结构在表单层面的实现。这也是 XForms 所谓新表单标准的一部分意义之所在。在分离了可选项数据与 UI 元素之后的表单内容如下:
清单 2. 存在于数据实例中的可选项数据:
<xforms:instance id="choices" xmlns=""> <data> <choice>China</choice> <choice>United States</choice> <choice>This is loaded from data instance</choice> </data> </xforms:instance> |
绑定到数据实例中可选项数据的 UI 元素,这里是一个 Popup:
清单 3. 绑定到数据实例的 Popup
<popup sid="POPUP2">
<itemlocation>
<x>70</x>
<y>279</y>
</itemlocation>
<size>
<width>20</width>
<height>1</height>
</size>
<xforms:select1 ref="account/country">
<xforms:label>XForms Country</xforms:label>
<xforms:itemset nodeset="instance('choices')/choice">
<xforms:label ref="."></xforms:label>
<xforms:value ref="."></xforms:value>
</xforms:itemset>
</xforms:select1>
</popup>
|
这样,即使是不同的 UI 元素需要绑定到同一套可选项数据,只需要在 UI 元素内部定义同样的 xforms:itemset 标签即可实现。
第二种预加载数据的方法比第一种方法的优点主要在于 MVC 中 Model(数据)和 View(视图)在同一表单内实现分离,数据除了可以绑定到视图上,还可以用作提交等其他的用途,这样的表单结构更清晰,也更容易维护和扩展。但两种方式具有同样的缺点,那就是待预加载的数据仍然需要放在表单中,每一次数据的变化(CRUD)都会需要重新编辑该表单的内容。这在数据如果来自于第三方来源的情况下显得束手无策。从第三种方法开始,将讨论几种动态加载数据的方法,使得表单在不需要变动的前提下,实现预加载数据的动态变化。
XForms 标准提供对于本地 XML 数据实例的加载,这样我们可以事先将预加载数据保存为本地的 XML 文件,在 XForms 表单数据实例中声明对该 XML 的动态加载。
清单 4. 本地的包含待加载数据的 countries.xml 文件:
<data xmlns="" xmlns:custom="http://www.ibm.com/xmlns/prod/XFDL/Custom" xmlns:designer="http://www.ibm.com/xmlns/prod/workplace/forms/designer/2.6" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xfdl="http://www.ibm.com/xmlns/prod/XFDL/7.6" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <choice>China</choice> <choice>United States</choice> <choice>Japan</choice> <choice>England</choice> <choice>This is loaded from local xml file.</choice> </data> |
在 XForms 表单中声明对该 XML 数据的加载:
清单 5. 声明加载本地 XML
<xforms:submission action="countries.xml" id="LoadXMLInstance"
includenamespaceprefixes="" method="get" ref="instance('choices')"
replace="instance"/>
<xforms:send ev:event="xforms-ready" submission="LoadXMLInstance"/>
|
并在表单初始化成功后,等待用户交互时,动态加载该外部 XML 数据:
这样就实现了一个新的目标,当 XForms 表单预加载的数据来自于本地的 XML 时,可以通过 XFroms 标准提供的提交逻辑来动态加载,而不需要将数据放在表单中,将数据和逻辑之间的分离推进到了更为高级的层次。这样,当用户表单需要预加载的数据发生变化时,不再需要编辑修改表单内容本身,而只需要修改外部 XML 数据文件,这在预加载数据会频繁变化以及数据量很大的情况下非常便于维护。
如果 XForms 表单要预加载的数据不是存在本地文件系统中的 XML,或者是存在第三方的系统,比如存储在 DB2 数据表中的某一列,甚至是通过 webservice 访问才能访问得到,那么我们就需要一个全新的方案来解决这个问题。不过幸运的是,XForms 标准对这样的技术需求也提供了支持,我们仍然可以通过 xforms:submission 来向任何 url 包括第三方异构系统所开放的 webservice 接口来获取所需的数据。
下面的例子中编写了一个 Java Servlet 运行在 web server 上,用来接受 XForms 表单发出的加载数据请求,并构造所需的数据返回给 XForms 表单。这里采用的依旧是读取服务器端文件系统上的一个 XML 文件内容作为表单预加载的数据。用户同样可以根据自己需求,修改该 servlet 的代码,以求从其他数据源或者数据库中读取需要的数据。
以下是 Servlet 中 doPost 方法调用的 handlerRequest 方法,用来预加载数据:
清单 6. 处理预加载数据的 handlerRequest 方法
private void handleRequest(HttpServletRequest request, HttpServletResponse response){
try {
ServletInputStream si = request.getInputStream();
byte[] data = new byte[4096];
int l = 0;
while ((l = si.read(data)) != -1) {
System.out.println("-----FormServlet-------Read in ----------'");
System.out.println(new String(data, 0, l).trim());
}
si.close();
FileInputStream fis = new FileInputStream("C:/countries.xml");
ServletOutputStream so=response.getOutputStream();
byte[] buffer=new byte[2048];
int len = 0;
while ((len = fis.read(buffer)) != -1) {
System.out.println("-----FormServlet-------Content read----------'");
System.out.println(new String(buffer, 0, len).trim());
so.write(buffer);
}
fis.close();
so.flush();
so.close();
} catch (IOException e) {
e.printStackTrace();
}
}
|
而在 XForms 表单的数据实例中仅做如下声明即可:
清单 7. 在数据实例中声明对 servlet 的请求
<xforms:submission action="http://localhost:8080/examples/servlet/xforms.FormServlet"
id="SubmissionToServlet" includenamespaceprefixes="" instance="choices_from_servlet"
method="post"
ref="instance('choices_from_servlet')" replace="instance"></xforms:submission>
|
需求总是在变化的。如果一个表单中有多处数据需要预加载,而且加载的数据源来自于不同的后端数据库,甚或是不同类型的数据库,难道要针对每个不同预加载元素分别编写一个可以请求后端数据的 servlet 吗?有什么更简单的办法。如果有一样声明式的数据预加载方式,可以应用在同一个表单内不同的 UI 元素上,而后端负责请求数据的 servlet 只有一个,是不是会简单很多?也更容易维护。
比如在表单中有这样的声明:
清单 8. 在表单中声明的数据库信息
<xforms:instance id="dbinfo" xmlns="">
<root>
<jdbc_driver>org.hsqldb.jdbcDriver</jdbc_driver>
<jdbc_url>jdbc:hsqldb:hsql://localhost/xdb</jdbc_url>
<jdbc_user>sa</jdbc_user>
<jdbc_pwd></jdbc_pwd>
<jdbc_table>formdata</jdbc_table>
<jdbc_column>choice</jdbc_column>
<template>
<data>
<choice></choice>
</data>
</template>
</root>
</xforms:instance>
<xforms:submission action="http://localhost:8080/examples/servlet/xforms.FormDBServlet"
id="SubmissionToDBServlet" includenamespaceprefixes="" instance="choices_from_db"
method="post" ref="instance('dbinfo')" replace="instance"></xforms:submission>
|
而 servlet 的的 handlerRequest 方法如下:
清单 9. servlet 的 handlerRequest 方法定义
private void handleRequest(HttpServletRequest request, HttpServletResponse response){
try {
//read stream and db info xml from request
ServletInputStream si = request.getInputStream();
ByteArrayOutputStream buf = new ByteArrayOutputStream();
byte[] data = new byte[4096];
int length = 0;
while ((length = si.read(data)) != -1) {
buf.write(data, 0, length);
}
si.close();
String dbInfo = new String(buf.toByteArray());
buf.close();
//parse to get db params
dbInfo = dbInfo.trim();
Document doc = DocumentHelper.parseText(dbInfo);
Element rootElement = doc.getRootElement();
String jdbc_driver = rootElement.elementTextTrim("jdbc_driver");
String jdbc_url = rootElement.elementTextTrim("jdbc_url");
String jdbc_user = rootElement.elementTextTrim("jdbc_user");
String jdbc_pwd = rootElement.elementTextTrim("jdbc_pwd");
String jdbc_table = rootElement.elementTextTrim("jdbc_table");
String jdbc_column = rootElement.elementTextTrim("jdbc_column");
//<template>
Element templateE = rootElement.element("template");
List list = templateE.elements();
if(list.size()>1)
System.out.println("template has more than 1 root elements, so only the 1st is used.");
//<data>
Element templateRoot = (Element) list.get(0);
list = templateRoot.elements();
if(list.size()>1)
System.out.println("root element has more than 1 element, so only the 1st is used.");
//<choice>
Element et = (Element) list.get(0);
String etName = et.getName();
//clear <data>
for (Iterator iter = list.iterator(); iter.hasNext();) {
et = (Element) iter.next();
templateRoot.remove(et);
}
//access db
Class.forName(jdbc_driver);
Connection c = DriverManager.getConnection(jdbc_url, jdbc_user, jdbc_pwd);
PreparedStatement p=c.prepareStatement("select "+ jdbc_column +" from "+ jdbc_table);
ResultSet rs = p.executeQuery();
while(rs.next()){
Element newEt = DocumentHelper.createElement(etName);
newEt.setText(rs.getString(jdbc_column));
templateRoot.add(newEt);
}
rs.close();
p.close();
c.close();
ServletOutputStream so=response.getOutputStream();
so.write(templateRoot.asXML().getBytes());
so.flush();
so.close();
} catch (Exception e) {
e.printStackTrace();
}
}
|
这里采用了 dom4j 作为 XML 解析工具包对接收到的数据库信息进行分析,并采用 HsqlDB 开源 Java 数据库作为预加载数据的存储示例。
它代表着将由一个固定不变的 servlet 从后端指定类型数据库(这里是 hsqldb)中,获取指定表列中的数据加载到表单中来。并且通过 <template> 声明加载后的数据以怎样的格式加入到 data instance 中来。这样就可以实现以不变应万变,在需要从不同的数据源中加载数据时,每次只需要简单的添加这样一段 data instance 片段,并将 UI 元素与该 data instance 绑定起来,就可以达到完全动态的数据加载了。有关 servlet 的编写请参考附件。
在本文中,分别介绍了几种基于 XFroms 标准的表单预加载数据的解决方案,从最简单的数据逻辑混在一起的方法,到数据与逻辑分离,最后到声明式预加载数据的方式,每种方式都有着自己的优缺点,能够满足不同的用户需求和数据预加载策略。灵活运用 XForms 标准提供的各项设施,是寻找解决客户问题解决方案的关键所在。
- 通过 developerWorks 的XForms 专题,进一步了解和学习 Xforms。
- 通过W3C XForms 主页进一步了解 XForms。
- 阅读 developerWorks 文章“通过 XML 数据交换实现 XForm DataInstance 的动态更新”,掌握 XML 数据交换的基本方法。
- 阅读 developerWorks 文章“技巧:使用 XForms 发送和接收 Web 服务消息”,学习 XForms 发送和接收 Web 服务消息的基本知识。
- 阅读 developerWorks 文章“巧用 Data Instance 实现 XForms 中的数据处理”,学习 XForms 中的数据处理过程。
| 名字 | 大小 | 下载方法 |
|---|---|---|
| xforms_prepop | 4k | HTTP |
注意:
- 数据绑定 servlet 代码片断!