跳转到主要内容

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

所有提交的信息确保安全。

  • 关闭 [x]

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

所有提交的信息确保安全。

  • 关闭 [x]

XForms 表单预加载数据的几种方法

张凯峰 (zhangkf@cn.ibm.com), 软件工程师, IBM
张凯峰,IBM CSDL 软件工程师,目前在 Lotus Forms team 从事 FVT 和 Automation 相关的工作。

简介: 在本文中,分别介绍了几种基于 XFroms 标准的表单预加载数据的解决方案,从最简单的数据逻辑混在一起的方法,到数据与逻辑分离,最后到声明式预加载数据的方式,每种方式都有着自己的优缺点,能够满足不同的用户需求和数据预加载策略。灵活运用 XForms 标准提供的各项设施,是寻找解决客户问题解决方案的关键所在。

发布日期: 2008 年 11 月 06 日
访问情况 : 1159 次浏览
评论: 


背景

作为以 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 标签即可实现。


从本地 XML 文件预加载数据

第二种预加载数据的方法比第一种方法的优点主要在于 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 标准提供的各项设施,是寻找解决客户问题解决方案的关键所在。


参考资源



下载

名字大小下载方法
xforms_prepop4kHTTP

关于下载方法的信息

注意:

  1. 数据绑定 servlet 代码片断!

关于作者

张凯峰,IBM CSDL 软件工程师,目前在 Lotus Forms team 从事 FVT 和 Automation 相关的工作。

关于报告滥用的帮助

报告滥用

谢谢! 此内容已经标识给管理员注意。


关于报告滥用的帮助

报告滥用

报告滥用提交失败。 请稍后重试。


developerWorks:登录


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 使用条款

 


当您初次登录到 developerWorks 时,将会为您创建一份概要信息。您在 developerWorks 概要信息中选择公开的信息将公开显示给其他人,但您可以随时修改这些信息的显示状态。您的姓名(除非选择隐藏)和昵称将和您在 developerWorks 发布的内容一同显示。

请选择您的昵称:

当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

(长度在 3 至 31 个字符之间)


单击提交则表示您同意developerWorks 的条款和条件。 使用条款.

 


为本文评分

评论

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=350247
ArticleTitle=XForms 表单预加载数据的几种方法
publish-date=11062008
author1-email=zhangkf@cn.ibm.com
author1-email-cc=

标签

Help
使用 搜索 文本框在 My developerWorks 中查找包含该标签的所有内容。

使用 滑动条 调节标签的数量。

热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。

我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。

使用搜索文本框在 My developerWorks 中查找包含该标签的所有内容。热门标签 显示了特定专区最受欢迎的标签(例如 Java technology,Linux,WebSphere)。我的标签 显示了特定专区您标记的标签(例如 Java technology,Linux,WebSphere)。