【IT168 技术文档】
简介
电子表单在当今的软件应用程序中无处不在,标准化的、数据驱动的方法有助于构建表单生成技术。电子表单的标准化格式支持创建容易理解的、可移植的表单,而数据驱动的方法使基于所收集的数据表示来生成电子表单变得更容易。
W3C 针对表单数据的表示和采集提出了 XForms 标准。如 W3C Recommendation 中所述,XForms 的目的是成为“下一代 Web 表单”。和现有的 HTML 表单相比,XForms 有许多突出的优点。正如 Recommendation 中所说的,“通过将传统的 XHTML 表单分成三部分—— XForms 模型、实例数据和用户界面,从而把表示从内容中分离出来,提高重用性和实现强类型,可以减少和服务器通信的次数,提供设备独立性,并减少脚本的使用”。
为了适应电子表单应用飞速发展的市场需求,IBM 推出了 Lotus Forms 电子表单解决方案,通过采用 XForms 等电子表单技术,IBM Lotus Forms 能够很方便的实现后端数据处理和后端应用整合,为所有公共或私有部门组织提供高安全性的电子表单,以充分利用现有的资源和系统来更好地为客户服务,提高运营效率。
XForms 模型表示表单的内容,它能独立于任何用来显示数据值的窗口组件集,来声明数据项和结构。它是一个 XML 数据块,包含一个或多个 XML 实例文档,包括数据实例 (Data Instances)、绑定 (Binds)、提交 (Submissions) 三部分。其中,表单的数据包含在数据实例中;表单的逻辑组件定义其行为,包括绑定和提交等。XForms 为处理这些 XML 数据提供了多种强大的机制,表单操纵这些实例文档,并负责将 XML 提交到后端系统。
Data Instance 是用于在表单中嵌入任意 XML 数据块的一种机制。它定义表单上所有和后端应用进行关联的数据信息。一个 Data Instance 可以用于存储表单上的任何输入数值,为表单上某些用户域进行数据预呈现,或者动态的生成数据可选项列表。
本文作者曾参与了多个与 IBM Lotus Forms 电子表单相关项目的前期需求分析、实例编写和应用搭建。在实施过程中,根据用户需求动态生成数据可选项列表,往往是电子表单应用中最常见的一种设计要求,也是 Data Instance 在使用过程中最广泛的一种应用模式。而更多的应用场景,往往都包含多个相互关联的数据可选项列表,即第二个可选项列表中的可选数值会随着第一个可选列表中选定数值的变化而发生变化,依此类推。这种场景,使用传统的应用开发方法,是非常简单易于实现的,但是使用 XForms 进行设计,如果设计不好,就会大大降低表单应用的灵活性。下文将举一个具体实例,对这一问题进行详细描述,说明如何巧妙的设计 Data Instance 来灵活的进行表单中的数据处理。
图 1 是使用 IBM Lotus Forms Designer 设计的电子表单的一部分用户界面。用户在填写表单过程中,需要填写详细地址信息。其中,当用户在“国家”下拉菜单中选择了国家之后,“省份 / 州”下拉菜单中的可选项将随之发生变化,列出该国家可选的省份 / 州。
以上应用需求,使用 Lotus Forms Designer 进行设计,最简单的方式是使用传统的 XFDL 来实现,将所有国家和省份 / 州的列表数据直接写死赋值给这些下拉菜单字段域。显而易见,这种方法虽然简单,但是没有任何灵活性。当可选国家、省份 / 州的数值列表发生变化时,必须通过 Lotus Forms Designer 修改这个电子表单(.xfdl 文件),修改之后,才能重新生效。因此,采用 XForms,使用 Data Instance,通过 XML 进行数据获取和交换,保证数据层和展现层的分离,是较好的解决方法。
那么,如何根据表单界面的需求,设计灵活合理的 Data Instance 呢?
最被普遍使用的也最容易想到的 Data Instance 结构,如清单 1 所示:
清单 1. 常用的 Data Instance
<xformsmodels> <xforms:model> <xforms:instance id="INSTANCE" xmlns=""> <document> ……… <SelectionLists> <Countries> <option></option> </Countries> <ChinaProvinces> <option></option> </ChinaProvinces> <USAProvinces> <option></option> </USAProvinces> …… </SelectionLists> </document> </xforms:instance> </xforms:model> </xformsmodels> |
其中,<Countries>
代表所有可选国家名称的列表,然后根据国家名称的具体数值,再通过 <…Provinces>
列出该国家下可选的省份 / 州名称。例如:<ChinaProvinces>
列举的是“中国”这个国家可以选择的省份名称,<USAProvinces>
列举的是“美国”这个国家可以选择的州的名称。
相对应的用户界面设计如下:清单 2. 对应的用户界面设计代码(“国家”下拉菜单)
<popup sid="Country"> <xforms:select1 ref="instance('INSTANCE')/PAGE1/Country"> <xforms:label></xforms:label> <xforms:itemset nodeset="../../SelectionLists/Countries/option"> <xforms:label ref="."></xforms:label> <xforms:value ref="."></xforms:value> </xforms:itemset> </xforms:select1> …… </popup> |
清单 3. 对应的用户界面设计代码(“省份 / 州”下拉菜单)
<popup sid="ProvState"> <xforms:select1 ref="instance('INSTANCE')/PAGE1/ProvState"> <xforms:label></xforms:label> <xforms:itemset nodeset="choose(instance('INSTANCE')/PAGE1/Country = 'China', ../../SelectionLists/ChinaProvinces/option, ../../SelectionLists/USAProvinces/option))"> <xforms:label></xforms:label> <xforms:value ref="."></xforms:value> </xforms:itemset> </xforms:select1> …… </popup> |
最终界面显示效果如图 2 所示。从显示效果来看,“省份 / 州”的数据选项确实可以根据“国家”选项的不同而发生相应的变化,满足了用户的需求。
但是,这种被普遍采用的方法存在着很大的缺陷:
- 在 Data Instance 设计中,
<….Provinces>
需要与<Countries>
的<option>
值一一对应。当“国家”选项发生变化时(增加 / 修改等),整个 Data Instance 都需要进行相应的修改。例如:如果<Countries>
的<option>
增加了’United King’,则 Data Instance 中还需要增加<UKProvinces>
及其<option>
,用于表示’United King’国家可选的省份 / 州信息。 - 从上述用户界面代码可以看出,“省份 / 州”的数据选项值通过
nodeset
的choose
函数获取,类似于其他编程语言中的’if – then - else’语句。它根据<Countries>
的<option>
的选项,以获取对应的<…Provinces>
的<option>
值。因此,一旦<Countries>
的<option>
值发生了变化,这部分代码也需要重新修改。例如:如果<Countries>
的<option>
增加了’United King’,则该部分代码需要修改为:
choose(instance('INSTANCE')/PAGE1/Country = 'China', ../../SelectionLists/ChinaProvinces/option, choose(instance('INSTANCE')/PAGE1/Country = 'United States', ../../SelectionLists/USAProvinces/option, ./../SelectionLists/UKProvinces/option)) |
由此可见,这种方法在数据选项内容固定的情况下使用,是非常简单易用的。但是对于数据选项经常发生变化或者无法预先获知的情况,其灵活性则非常差。为此,必须对其进行改造,使其具备更好的通用性和灵活性。
Data Instance 中的数据元素,可以通过使用 XPath 表达式,进行定位引用及相关运算操作。由于篇幅原因,本文不对 XPath 进行具体阐述。在 XPath 丰富的运算符和函数中,有一类可以很好地解决上述方法的缺陷,这就是 <instance>/<element>/<option>[@<attribute>=’VALUE’]。这个表达式的大意是:在 instance 中选取所有的 element,它具备属性 attribute 并且值等于 VALUE。例如://book[@lang=’en’],该表达式的运算结果是选取所有具备属性’lang’且其值等于’en’的’book’元素。
上述表达式中,‘属性’(attribute) 是关键。通过对 <option> 设计不同的 attribute,可以对 instance 中的 element 进行更灵活多样的操作。采用这种方法,对 Data Instance 进行改进,如下:
清单 4. 改进后的 Data Instance
<xformsmodels> <xforms:model> <xforms:instance id="INSTANCE" xmlns=""> <document> ……… <SelectionLists> <Countries> <option></option> </Countries> <StatesLists> <option country=""></option> </StatesLists> </SelectionLists> </document> </xforms:instance> </xforms:model> </xformsmodels> |
与上文所述最普遍使用的 Data Instance 相比,“国家” (<Countries>) 的实例结构没有任何变化,“省份 / 州”的实例结构做了相应改动,由原来为每个“国家”设计一个结构 (<….Provinces>),变成了一个通用的带有属性‘country’的 <StatesLists> 结构。
经过这样的修改,“省份 / 州”这个用户界面域对应的代码片断变化如下:
清单 5. 改进 Data Instance 后的用户界面设计代码(“省份 / 州”下拉菜单)
<popup sid="ProvState"> <xforms:select1 ref="instance('INSTANCE')/PAGE1/ProvState"> <xforms:label></xforms:label> <xforms:itemset nodeset="instance('INSTANCE')/StatesLists/option[@country = instance('INSTANCE')/PAGE1/Country]"> <xforms:label ref="."></xforms:label> <xforms:value ref="."></xforms:value> </xforms:itemset> </xforms:select1> <custom:XformsValue xforms:ref="instance('INSTANCE')/PAGE1/ProvState"> </custom:XformsValue> …….. </popup> |
其中,加粗部分的 XPath 表达式说明,“省份 / 州”域的可选项列表数值将取决于 @country
这个属性值,该值是在选取“国家”域值时赋予的。最终界面显示效果与图 2 相同,完全符合用户的需求。
经过这样的改动,可以很好地解决上文所述清单 1 的 Data Instance 的缺陷,具有非常好的灵活性和通用性:
-
通过增加
country
属性,使得整个 Data Instance 的结构不会在“国家”选项发生变化时(增加 / 修改等),进行相应的修改。唯一有所变动的仅仅是<StatesLists>
‘country
’属性的值。清单 7 加粗部分,说明了当<Countries>
的<option>
由最初的China
,United States
两项,到增加United King
后,<StatesLists>
的<option>
发生的变化。可以看出,这种变化,并没有引起整个 Data Instance 结构的变化。
清单 6. Data Instance 数据值举例
<Countries> <option>China</option> <option>United States</option> </Countries> <StatesLists> <option country="China">BeiJing</option> <option country="China">……</option> <option country=" United States ">California</option> <option country=" United States ">……</option> </StatesLists>
清单 7. Data Instance 数据值举例 (<Countries>
的<option>
增加United King
值后的变化 )
<Countries> <option>China</option> <option>United States</option> <option>United King</option> </Countries> <StatesLists> <option country="China">BeiJing</option> <option country="China">……</option> <option country=" United States ">California</option> <option country=" United States ">……</option> <option country=" United King ">London</option> <option country=" United King ">……</option> </StatesLists>
- 对应的用户界面代码(清单 4)具有非常好的灵活性,也不会因为
<Countries>
的<option>
值发生变化而进行相应修改。
使用 IBM Lotus Forms Designer 设计基于 XForms 的表单,最主要的作用是为了更方便更灵活的实现后端数据处理,比如从后端系统中获取数值为表单上某些用户域进行数据预呈现,动态的生成数据可选项列表,捕获表单上的输入数值并存储到后端应用系统等等。在本文所描述的应用场景中,类似于“国家”、“省份 / 州”这样的可选数值在实际项目应用中,应该是在用户打开表单时,就将后端系统中的存储数据预呈现给用户。使用 IBM Lotus Forms 提供的 Java API,编写应用程序 (servlet),这是最简单也是最直接的实现方式。但是这种方式需要用户客户端始终在线,能实时与服务器进行通信。
本文作者参与的电子表单项目,大多数都有离线操作的要求,这也是 IBM Lotus Forms 产品的一大优势之一。在离线状况下,上述使用 API 编写 servlet,实时与服务器保持通信的方法就不再适用。为此,IBM Lotus Forms 提供了 IFX 机制,通过扩充 Lotus Forms Viewer 客户端的功能,满足更多的用户需求。由于篇幅原因,本文不对 IFX 的原理和实现步骤作详细的描述,仅将使用 IFX 实现上述离线功能的设计思路简述如下:
- 预先将后端系统数据值组装成一个 xml 文件,该文件的结构与 XForms 中 Data Instance 结构匹配。
- 用户在离线状态下打开表单时,触发 IFX 编写的相应函数,读取这个 xml 文件,将其中的数据值与表单中的 Data Instance 进行绑定,从而为相应的用户界面字段域预呈现可选的数据项列表。
- 之后,用户正常操作表单,进行各种工作。
XForms 在表单数据模型及其表示之间增加了一层新的、功能强大的抽象,把表示从内容中分离出来,提高灵活性和重用性。使用 IBM Lotus Forms 设计基于 XForms 的电子表单,通过 Data Instance,可以很方便的实现后端的数据处理。Data Instance 中的数据元素,是通过 XPath 表达式,进行定位引用及相关运算操作的。因此,充分掌握 XPath 的语法语义,设计合理的 Data Instance 结构,是实现数据灵活处理的关键。本文通过一个典型应用场景的运用描述,说明了如何巧用 XPath 来巧妙设计 Data Instance,从而为灵活处理后端数据提供支持。当然,设计灵活合理的电子表单,不仅仅只局限于 Data Instance 的巧妙设计,XForms 还提供了很多强大的机制,比如操作 (actions)、事件 (events) 等,只有充分应用这些机制,才能真正发挥 XForms 的功能,为电子表单应用提供最有效的帮助。