IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Lotus | XML  >

在 Lotus Form Designer 中处理空值的技巧

developerWorks
文档选项

未显示需要 JavaScript 的文档选项


王 小锋 (wangxf@cn.ibm.com), 软件工程师 , IBM 中国软件开发实验室
王 强 (wangq@cn.ibm.com), 高级软件工程师, IBM 中国软件开发实验室

2007 年 12 月 28 日

本文首先简单介绍了 IBM Louts Form 的相应产品以及 XForms 1.0 规范,接着通过对两个非常典型的空值相关问题的描述,重点阐述了如何合理的使用一些技巧来解决 Form 开发人员在日常开发中经常遇到的空值问题,达到预期的开发效果,并给出了一些必要的注意事项。如果你正在用 Louts Form Designer 开发表单并且遇到了空值的问题,不妨看一下这篇文章,也许答案就在里面呢。

简介

Lotus Forms 是 IBM Lotus 产品家族中的一个产品套件,使公司能够使用电子表格来收集用户信息并将这些信息发送到其他系统。这个产品套件主要包括三个产品,分别是 Form Designer、Form Viewer 以及 Form Server。要想了解产品的详细信息,请阅读参考资料部分。

Louts Form Designer 2.7 是 IBM 在 2007 年 3 月发布的,是一款优秀的设计表单的工具,表单设计者能够在图形拖放环境和功能强大的源代码编辑器中创建 XFDL 表单,它完全支持 XForms 1.0 的规范,而采用 XForms 模型可以使表示层和数据层分离,从而设计出的表单有更高的独立性,图 1 展示了 XForms 的地位以及使用它带来的好处。更多关于 XForms 的信息请阅读参考资料部分。


图 1. XForms 的地位及优点
图 1. XForms 的地位及优点




回页首


问题描述

空值一直是令人头疼的问题 , 在很多情况下都需要特殊对待 , 比如在编程领域 , 你需要判断一个对象 (object) 是不是为空 (null), 否则很可能会抛出一大堆异常 , 让人不知所措。在数据库领域也会遇到类似的问题 , 虽然可以允许字段出现空值,但是查找的效率也会因此降低。同样 , 在 Form 开发人员利用 Form Designer 来开发表单的时候,也会碰到很多空值的问题,在本文中我们就来探讨一下一些有效处理空值的技巧。

接下来我们简单描述的两个在设计表单中很具有代表性的空值问题 :

  1. 表单要实现的功能很简单 , 即计算两个字段的和 , 如果其中一个字段为空 , 则只显示另外一个字段的值 ; 如果两个字段都为空 , 则什么也不显示。

    这个问题很常见,看起来似乎也很简单,但是当你试着去做的时候就会发现并不像当初想象的那样,我们将在第三部分详细讨论这个问题。

  2. 用户在一个设计好的表单中输入数据,为了使得数据层和表示层分离,我们采用 XForms 技术,并且这些 XForms 数据实例在提交到 server 端之前要通过 schema 的验证。但是某些字段用户并未填写,也就是说在提交的 XForms 模型中有很多空的结点。

    这样的场景很普遍,对于这个问题我们将在第四部分中详细论述。





回页首


巧用函数

对于第二部分描述的第一个问题,一个直观的想法就是用 xforms:bindcalculate 属性来计算两个字段的和,清单 1 和 清单 2 是这个想法的一个实现。其中清单 1 是数据模型部分,清单 2 是和界面相关的 UI 部分。

清单 1 中包含三个结点,分别是 a、b 和 total,其中 total 是一个通过计算得到的值,在 xforms:bind 中用属性 calculate="../a + ../b" 来表示。由于 total 值不能修改,我们用 readonly="true()" 来进行约束。

清单 2 有三个 XFDL 项与数据模型中的结点相对应,分别是 A1、B1 和 total,XFDL 项通过 ref 或者 bind 同数据模型中对应的结点绑定在一起。


清单 1. XForms 数据模型部分
                
<xformsmodels>
    <xforms:model>
         <xforms:instance xmlns="">
            <root>
                <a></a>
                <b></b>
                <total></total>
            </root>
     </xforms:instance>
     <xforms:bind calculate="../a + ../b" id="result" nodeset="total"></xforms:bind>
     <xforms:bind nodeset="total" readonly="true()"></xforms:bind>
   </xforms:model>
</xformsmodels>
			


清单 2. 用户界面部分
                
<page sid="PAGE1">
      <global sid="global">
         <label>PAGE1</label>
      </global>
      <field sid="A1">
         <xforms:input ref="a">
            <xforms:label></xforms:label>
         </xforms:input>
         <scrollhoriz>wordwrap</scrollhoriz>
         <layoutflow>block</layoutflow>
         <bgcolor>#C0C0C0</bgcolor>
      </field>
      <field sid="B1">
         <xforms:input ref="b">
            <xforms:label></xforms:label>
         </xforms:input>
         <scrollhoriz>wordwrap</scrollhoriz>
         <layoutflow>block</layoutflow>
         <bgcolor>#C0C0C0</bgcolor>
      </field>
      <field sid="total">
        <xforms:input bind="result">
           <xforms:label></xforms:label>
        </xforms:input>
         <scrollhoriz>wordwrap</scrollhoriz>
         <layoutflow>block</layoutflow>
          <format>
            <datatype>currency</datatype>
         </format>
         <border>off</border>
         <bgcolor>#C0C0C0</bgcolor>
      </field>
      <label sid="LABEL3">
         <xforms:output>
            <xforms:label>total:</xforms:label>
         </xforms:output>
      </label>
   </page>
			

问题好像已经解决了 , 是不是这样的呢 ? 让我们来看一下刚才设计的表单 , 图 2 是在 Lotus Form Viwer 2.7 中的显示界面。


图 2. 输入字段均为空的情况 ( 清单 1- 清单 2)
图 2. 输入字段均为空的情况 ( 清单 1- 清单 2)

这个界面也许不是你想看到的 , 在没有任何输入的情况下 , total 的值为 NaN, 然后我们试着在一个输入框中输入 1, 结果如图 3 所示 , total 的值仍然为 NaN:


图 3. 仅一个输入字段为空的情况 ( 清单 1- 清单 2)
图 3. 仅一个输入字段为空的情况 ( 清单 1- 清单 2)

接下来 , 我们在另一个输入框中输入 2, 图 4 展示了最终的界面 :


图 4. 输入字段均不为空的情况 ( 清单 1- 清单 2)
图 4. 输入字段均不为空的情况 ( 清单 1- 清单 2)

只有图 4 才和我们预期的结果一致 , 但是为什么在前两次 total 的值都显示为 NaN 呢 ? 分析后不难发现 , total 是 a 和 b 之和 , 当其中一个为空或者二者都为空的时候 total 的值也是未知的 , 因为 XPath, XML Schema 等 W3C 的标准都不把空字符串作为数字对待。

这就和我们当初设想的功能有差别了 , 那我们有没有办法来解决这个问题呢 ? 答案是肯定的 , 我们只要在清单 1 上稍作修改就可以了 , 清单 2 保持不变,于是我们得到了清单 3:


清单 3. 修改过的 XForms 数据模型部分
                
<xformsmodels>
    <xforms:model>
         <xforms:instance xmlns="">
            <root>
                <a></a>
                <b></b>
                <total></total>
            </root>
     </xforms:instance>
      <xforms:bind calculate="if(../a[.!=''],../a, '0') + if(../b[.!=''],../b,'0')" 
         id="result" nodeset="total">
      </xforms:bind>
         <xforms:bind nodeset="total" 
         relevant="boolean-from-string(if(../a[.!=''] or ../b[.!=''], 'true' , 'false'))">
         </xforms:bind>
    <xforms:bind nodeset="total" readonly="true()"></xforms:bind>
   </xforms:model>
</xformsmodels>

清单 3 在清单 1 的基础上作了一点改动 , 用黑色加粗的部分就是改动的部分 , 让我们来分析一下这部分的代码 . 首先 , 在 xform:bind 中 calculate 的属性值与清单 1 不同 , 清单 3 引入了 if 函数用于判断输入的两个字段是不是为空 , 如果为空 , 则把这个设置为 0, 否则取这个字段的输入值 , 这样就可以避免空值相加了。其次,清单 3 给 total 这个节点集添加了 relevant 属性 , relevant 中用了 boolean-from-string 函数把字符串转为 boolean 值 , 函数中又嵌套了一个 if 函数 , 表示的含义是当两个字段都为空的时候结点集 total 不相关 , 即不会出现在界面上。

和刚才一样 , 我们来看一下这次设计的表单 , 图 5 是两个输入字段均为空的情况 , 这次 total 值并没有出现在界面上 , 和我们的预期结果一样。


图 5. 输入字段均为空的情况 ( 清单 3)
图 5. 输入字段均为空的情况 ( 清单 3)

接下来 , 我们给其中一个输入框输入 1, 这时 total 值也为 1, 不再为 NaN, 和我们预期的结果一样 , 如图 6 所示 :


图 6. 仅一个输入字段为空的情况 ( 清单 3)
图 6. 仅一个输入字段为空的情况 ( 清单 3)

最后 , 我们在另一个输入框中输入 2, 这时候的界面和图 4 一样。

到现在为止 , 我们实现了预想的功能 , 总结起来,就是在恰当的时候用合适的函数,比如这里的 if 函数,boolean-from-string 函数。也许你会说 , 用 XFDL 的函数同样可以解决问题 , 比如用清单 4 列出的方法 :


清单 4. 使用 XFDL 函数
                
<field sid="total">
    <scrollhoriz>wordwrap</scrollhoriz>
    <layoutflow>block</layoutflow>
    <value compute="(A1.value == '' and B1.value == '') ?  ('') : (A1.value + B1.value)">
        xx
    </value>
    <visible compute="value=='' ? 'off': 'on'"></visible>
    <custom:set xfdl:compute="set('total',value,'',xforms)"></custom:set>
    <border>off</border>
    <readonly>on</readonly>
    <bgcolor>#C0C0C0</bgcolor>
</field>

清单 4 去掉了 <xforms:bind>, 同时修改了 total 字段 , 与清单 3 不同的是 , total 并没有用 XForms 项 , 而是用了三个 XFDL 函数 , 分别是 value 的计算 , visible 的计算以及一个自定义的函数:将最终的结果写回到 XForms 数据模型中去。

在 Viwer 中清单 4 运行的效果和清单 2 是完全一样的 , 但是我们仍然推荐使用第一种方法 , 直观上讲 , 用第一种方法保持了表单的一致性 , 而且 , 在设计表单时有一个指导性的原则 , 即在有 XForms 模型的地方尽量使用 XForms 操作来解决问题 , 因为这样的表单移植性好。另外 , 使用 XForms 操作也是出于效率上的考虑,如果想了解详细信息,请阅读参考资料部分 John Boyer 在他的 blog 上的文章。





回页首


巧妙通过 Schema 验证

对于第二章节中的第二个问题,我们举个例子来说明。用户提交的数据中可能会包含 <date></date> 这样的空结点,假设 date 在 schema 中定义的数据类型是 xsd:date, 那么这个空结点提交到 server 端之后就会出错 , 因为它通不过 schema 的验证。对于这个问题,看看能不能在本章中找到你想要的答案吧。

添加属性

参照 http://www.w3.org/TR/xmlschema-0/ 中对空结点的描述,我们可以在定义 schema 的时候允许此结点为空,做法就是在定义结点的类型时候加入 nillable="true" 这个属性。

接下来在定义 XForms 数据模型时,给那些在 schema 中允许为空的结点添加 xsd:nil="true" 属性,前面提到的 <date></date> 就变成了 <data xsd:nil="true"></data>

这样 XForms 数据就可以通过 schema 的验证了,但是一个新的问题又产生了 , 即如何动态修改 xsd:nil 的属性值 , 使得当用户在相应的字段填上数据后 , xsd:nil 更新为 false, 否则仍为 true。xsd:nil = "false" 表示该结点不为空,在这种情况下也可以通过 schema 的验证。

清单 5 给出了一个解决方案 , 在这里我们只给出了 XForms 数据模型部分的代码 , 完整的代码可以在清单下载中得到。


清单 5. 动态设置属性值
                
  <xformsmodels>
     <xforms:model>
         <xforms:instance xmlns="">
            <root>
                <data xsi:nil="true"></data>
            </root>
         </xforms:instance>
          <xforms:bind calculate="if(..!='','false','true')" nodeset="data/@xsi:nil">
          </xforms:bind>
      </xforms:model>
   </xformsmodels>

清单 5 主要用了 date 结点集的 calculate 属性来动态设置 data 的 xsi:nil 的值 , 这个计算表示的意思是如果 date 结点不为空,则设置 xsi:nil 的值为 false,否则仍为 true。通过这个动态计算就可以使得 date 总能通过 schema 的验证。例如,当用户在界面上输入 12/12/2000 并点击保存时 , 察看源码 , 我们发现,data 部分已经被修改为 <data xsi:nil="false">2000-12-12</data>,这和我们的预期结果一致。

过滤空结点

对于问题 2,如果应用的需求是对于那些空结点,根本不需要提交到 server 端,因为根本不需要处理那些空结点,这样做的一个好处是减小了提交的 XForms 数据模型的大小,可以减少网络传输的代价。举一个例子来说,如果用户根本没有填 date 的值,那么我们在 server 端也根本不用处理 date 这个结点,只要我们在 schema 中设置此结点最小出现次数为 0,同样也可以通过 schema 的验证。剩下的问题就是解决如何过滤掉那些为空的结点,接下来我们详细讨论这个问题。

要想在提交的时候过滤掉那些为空的结点,必须保证这个结点是不相关的,即 relevant = 'false'。我们的想法就是找出那些为空的结点,然后设置他们的 relevant 为 false,这个动作只能在提交的时候做。幸运的是,Louts Form Designer 已经给我们提供了 submit 事件,清单 6 给出了一个可行的解决方案。


清单 6. 过滤空结点
                
  <xformsmodels>
<xforms:model>
         <xforms:instance xmlns="">
            <root>
                <data></data>
            </root>
         </xforms:instance>
         <xforms:bind nodeset="date" relevant="boolean-from-string( if(.='false', 'false',
 'true'))"></xforms:bind>
         <xforms:submission action="http://xformstest.org/cgi-bin/showinstance.sh" id="S"
                    method="post">
               <xforms:action if="date=''" ev:event="xforms-submit">
                   <xforms:setvalue ref="date" value="'false'"></xforms:setvalue>
               </xforms:action>
   </xforms:model>
  </xformsmodels>

我们注意到在清单 6 中,<xforms:submission> 有一个动作 <xforms:action>,触发这个动作的事件是 xforms-submit,即用户提交的时候。if="date=''" 表示如果这个动作满足 date 结点为空,则触发 xforms:setvalue 事件,即设定 date 结点集的 value 值为 false。

清单 6 有一个小技巧,在 <xforms:bind> 中,设置了 date 结点集相关的条件是 date 结点值为 false,而这个 false 值的设置却是在 xforms:submit 事件中完成的,先用 if 判断 date 值是否为空,如果为空,则设置其值为 false,这时候 date 结点自然就成了不相关的了,因此提交的时候并没有这个结点出现。

可能一开始你并不明白上面代码的含义,没关系,通过下面的实践后你就清楚了。

  1. 如果用户在界面上什么也不填,则提交之后得到的数据实例如清单 7 所示:

    清单 7
                            
    <?xml version="1.0"?>
    <root 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.1" 
        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">
    </root>
    

    清单 7 没有任何实例数据结点,说明 date 这个空结点已经被过滤掉了。

  2. 如果用户在界面上输入 12/12/2000,则提交之后得到的数据实例如清单 8 所示:

    清单 8
                            
    <?xml version="1.0"?>
    <root 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.1" 
        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">
        <date>2005-12-12</date>
    </root>
    





回页首


总结

在本文中我们主要讨论了在 Form Designer 中开发表单时最常见的和空值相关的两个问题 , 并给出了相应的解决方案。一般性的原则是当有 XForms 数据模型出现的地方 , 尽量用 XForms 的操作来实现,因为这样的表单移植性更好。如果提交的时候遇到空值的问题 , 可以第四章中提到的两种方法来解决,该采用那种方法,还要根据实际的需求和场景来决定。



参考资料

学习

获得产品和技术

讨论


作者简介

王小锋,IBM 软件工程师,工作在 IBM 中国软件开发实验室– 中国新技术中心(China Emerging Technology Institute),从事 Incubator 的工作,对 J2EE、Ontology、语义网等相关技术有浓厚兴趣,对数据库技术有较深入的研究。


王强,IBM 高级软件工程师,工作在 IBM 中国软件开发实验室 - 中国新技术中心(China Emerging Technology Institute),,从事 Incubator 及 SOA 相关项目的工作,对 J2EE 架构、SOA 架构、MDA/MDD 以及项目管理技术有较深入的研究。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款