«
使用 JavaScript 让 XForms 变得更健壮

时间:2008-5-31    作者:Deri    分类: 分享


    <p>  <a href="http://file.ddvip.com/2007_08/1187080748_ddvip_4348.zip">本文示例源代码或素材下载</a></p>

   <p>  先决条件</p><p>  本文只基于 XForms 和 JavaScript。针对安装在 Mozilla Firefox 2.0 之上的 Mozilla XForms 插件 进行测试。使用的是标准的 XForms 和 JavaScript,因此应该在这两个标准技术的其他实现上运行。没有使用服务器端的技术。</p><p>  典型的表示例</p><p>  我们来看一个典型的 XForms 示例。它展示了如何创建表示 XML 文档中重复节点的表。尤其展示了如何使用 XForms 执行聚集计算以及如何使用 XForms 添加或删除模型数据的节点,同步地自动保存视图。查看清单 1 中的完整源代码。</p><p>  清单 1. 典型的 XForms 表示例<code><?xml version="1.0" encoding="UTF-8"?><br /><xhtml:html xmlns:ev="http://www.w3.org/2001/xml-events"<br />xmlns:xforms="http://www.w3.org/2002/xforms"<br />xmlns:xhtml="http://www.w3.org/1999/xhtml"<br />xmlns:xsd="http://www.w3.org/2001/XMLSchema"><br />  <xhtml:head><br />    <xhtml:title>Demonstration of table with<br />                   column total</xhtml:title><br />    <xf:model id="my-model" xmlns="http://www.w3.org/1999/xhtml"<br />xmlns:xf="http://www.w3.org/2002/xforms"><br />      <xf:instance id="my-data" src="my-data.xml" xmlns=""/><br />      <xf:bind calculate="sum(../Item/Amount)" nodeset="/Data/Total"/><br />      <xf:submission action="my-data.xml" id="update-from-local-file"<br />instance="my-data" method="get" replace="instance"/><br />      <xf:submission action="my-data.xml" id="view-xml-instance"<br />method="get"/><br />      <xf:submission action="my-data.xml" id="save-to-local-file"<br />method="put"/><br />    </xf:model><br />  </xhtml:head><br />  <xhtml:body><br />    <xf:group ref="/Data" xmlns="http://www.w3.org/1999/xhtml"<br />xmlns:xf="http://www.w3.org/2002/xforms"><br />      <xf:label/><br />      <xf:repeat id="repeatItem" nodeset="Item"<br />xmlns="http://www.w3.org/1999/xhtml"><br />        <xf:input class="item-description" id="description-input"<br />ref="Description" xmlns="http://www.w3.org/1999/xhtml"><br />          <xf:label/><br />        </xf:input><br />        <xf:input class="item-amount" ref="Amount"<br />xmlns="http://www.w3.org/1999/xhtml"><br />          <xf:label/><br />        </xf:input><br />      </xf:repeat><br />      <xhtml:div id="sum"><br />      <xf:output ref="/Data/Total" xmlns="http://www.w3.org/1999/xhtml"><br />        <xf:label/><br />      </xf:output><br />      </xhtml:div><br />      <xf:trigger id="insertbutton" xmlns="http://www.w3.org/1999/xhtml"><br />        <xf:label>Add Item</xf:label><br />        <xf:action ev:event="DOMActivate"><br />          <xf:insert at="last()" nodeset="Item[last()]"<br />position="after"/><br />          <xf:setvalue ref="Item[last()]/Description" value="''"/><br />          <xf:setvalue ref="Item[last()]/Amount" value="0"/><br />          <xf:setfocus control="description-input"/><br />        </xf:action><br />      </xf:trigger><br />      <xf:trigger id="delete" xmlns="http://www.w3.org/1999/xhtml"><br />        <xf:label>Delete Item</xf:label><br />        <xf:delete at="index('repeatItem')" ev:event="DOMActivate"<br />nodeset="Item[index('repeatItem')]"/><br />      </xf:trigger><br />      <xf:submit submission="update-from-local-file"<br />xmlns="http://www.w3.org/1999/xhtml"><br />        <xf:label>Reload</xf:label><br />      </xf:submit><br />      <xf:submit submission="save-to-local-file"<br />xmlns="http://www.w3.org/1999/xhtml"><br />        <xf:label>Save</xf:label><br />      </xf:submit><br />      <xf:submit submission="view-xml-instance"<br />xmlns="http://www.w3.org/1999/xhtml"><br />        <xf:label>View XML Instance</xf:label><br />      </xf:submit><br />    </xf:group><br />  </xhtml:body><br /></xhtml:html></code></p>
<p> </p>

   <p>  您可能注意到了,在源代码中模型中的数据来自外部 XML 文件。该文件如清单 2 所示。</p><p>  清单 2. 示例的 XML 数据<code><?xml version="1.0" encoding="UTF-8"?><br /><Data><br />  <Item><br />   <Description>Furniture</Description><br />   <Amount>1000</Amount><br />  </Item><br />  <Item><br />   <Description>Dock</Description><br />   <Amount>2000</Amount><br />  </Item><br />  <Item><br />   <Description>Boat</Description><br />   <Amount>3000</Amount><br />  </Item><br />  <Item><br />   <Description>Lawn equipment</Description><br />   <Amount>4000</Amount><br />  </Item><br />  <Item><br />   <Description>Hot tub</Description><br />   <Amount>5000</Amount><br />  </Item><br />  <Total>15000</Total><br /></Data></code></p><p>  运行示例</p><p>  简单地在 Web 浏览器中打开示例即可运行它。您应该看到类似图 1 所示的内容。</p><p>  图 1. 典型的 XForms 示例</p><p>  <img src="/content/uploadfile/200805/2008053117262749.jpg" onclick="get_larger(this)" alt="使用 JavaScript 让 XForms 变得更健壮" /></p><p>  尝试此示例,注意,您可以使用显示的 Add Item 和 Delete Item 按钮添加和删除行。例如,如果您单击一次 Delete Item 按钮,您应看到类似图 2 的内容。</p>
 <p> </p>

   <p>  图 2. 删除了一个项目</p><p>  <img src="/content/uploadfile/200805/2008053117263086.jpg" onclick="get_larger(this)" alt="使用 JavaScript 让 XForms 变得更健壮" /></p><p>  注意,不仅是删除了顶部的项目,而且项目总数也重新进行了计算。这很好地说明了 XForms 的强大功能。再单击 Delete Item 按钮四次,您应看到类似图 3 的内容。</p><p>  图 3. 删除了所有项目</p><p>  <img src="/content/uploadfile/200805/2008053117263503.jpg" onclick="get_larger(this)" alt="使用 JavaScript 让 XForms 变得更健壮" /></p><p>  这不怎么好看,但是可以单击 Add Item 按钮开始重新输入数据,对吧?但结果是在这种情况下单击 Add Item 按钮不起任何作用。</p><p>  问题出在哪儿?</p><p>  为什么 Add Item 在这种情况下不起作用?如果您查看 源代码,Add Item 按钮导致向模型中进行插入,使用 XForms 插入命令添加一条记录。而那条记录刚好是一个 “Item” 节点,因此 Add Item 操作接着为 Item 的 Description 和 Amount 节点设置一些默认值。Add Item 通过实际上克隆结构中的最后一个节点以定义插入节点的类型。nodeset="Item[last()]" 正是实现此操作。这正是 “bug” 的来源。如果您消除了所有项目,那么就没有节点可供克隆因而也就无法执行插入。因此 Add Item 按钮会失效。当然,现在的问题是,如何修正这个问题?</p><p>  修正</p><p>  正如软件工程中的大多数问题一样,有很多方法可以解决此处描述的问题。这里要阐述的解决方案是使用 JavaScript。您的策略是更改删除的方式。在 清单 1 中您会发现 Delete Item 按钮使用了 XForms 删除命令。您将使用对一个 JavaScript 函数的调用替代对此命令的调用。该函数需要与 XForm 模型交互。您将只修改对 Delete Item 按钮的调用,因此无需更改 Add Item 按钮。我们来查看一下 JavaScript 解决方案。</p>
 <p> </p>

   <p>  JavaScript</p><p>  正如前面提到的,方法是只更改 Delete Item 按钮,而不是 Add Item 按钮。因此要想使 Add Item 按钮起作用,决不能删除模型中的所有数据。因此您将跟踪周围的项目个数,当您删除至最后一个时,不要继续删除。只是使用默认内容替代该项目的内容,好像您执行了 Add Item 按钮一样。我们来查看一下清单 3 中的 JavaScript 代码。</p><p>  清单 3. JavaScript deleteItem() 函数<code><xhtml:script type="text/javascript"><br />         //<![CDATA[<br />           function deleteItem(){<br />              var model = document.getElementById("my-model");<br />              var instance = model.getInstanceDocument("my-data");<br />              var dataElement = instance.getElementsByTagName("Data")[0];<br />              var itemElements = dataElement.getElementsByTagName("Item");<br />              var cnt = itemElements.length;<br />              if (cnt > 1){<br />                dataElement.removeChild(itemElements[cnt-1]);<br />              } else {<br />                // last element so just set its data to default vals<br />                var descripElement =<br />itemElements[0].getElementsByTagName("Description")[0];<br />                descripElement.childNodes[0].nodeValue = "";<br />                var amtElement =<br />itemElements[0].getElementsByTagName("Amount")[0];<br />                amtElement.childNodes[0].nodeValue = "0";<br />              }<br />              model.rebuild();<br />              model.recalculate();<br />              model.refresh();<br />           }<br />         //]]><br />      </xhtml:script></code></p>
 <p> </p>

   <p>  下面介绍其工作原理。您需要从 JavaScript 中访问 XForms 模型。所幸的是,使用 JavaScript 的 DOM API 即可轻松地实现访问。XForms 模型是页面 DOM 的一部分,因此您只需使用 document.getElementById() 方法,正如您访问 HTML div 或 HTML 输入字段那样。</p><p>  当您使用 document.getElementById() 访问 XForms 模型时,如 清单 3 所示,您得到的是 nsIXFormsModelElement。此对象中含有几个非常有用的方法,包括上面使用的 getInstanceDocument() 方法。这样您就可以访问 清单 1 中定义的 XForms 实例。这是一个 DOM 对象,表示 清单 2 中的 XML 文档。因此在 JavaScript 代码中,您只需让 DOM 获取 Item 元素即可。您确定模型中有多少个 Item 并将其存储在 cnt 变量中。很明显,此处要处理两个用例。第一个是您拥有不止一个 Item,第二个是您只有一个 Item。在第一个用例中,您需要删除项目。</p><p>  使用 JavaScript 删除项目</p><p>  那么如何使用 JavaScript 而不是 XForms 删除控件删除一条 Item 记录呢?解决方案极其简单。先前调用的 getInstanceDocument() 方法给您提供一个真正的 DOM 对象。因此您可以对此对象执行任何对其他 DOM 对象执行的操作。它支持全部的 DOM API 功能。因此您简单地对 DOM 元素使用 removeChild() 方法。删除 index (cnt - 1) 处的 Item,因为您的元素数组(调用 getElementsByTagName("Item") 获得)是 0-indexed。这里没有什么神奇的 XForms API,只有简单的 DOM 编程。</p><p>  删除最后一个项目</p><p>  您已经重新生成了使用 XForms 删除命令能够正常完成的逻辑。这样做的目的是为了更优雅地处理删除最后一个项目时的情况。现在查看一下这个临界情况,比如 cnt == 1。</p>
 <p> </p>

   <p>  正如前面提到的,此处的关键在于您并不希望实际删除最后一个项目。如果删除,则 Add Item 动作中的 XForms 插入命令将不再有效。当然,您也可以重新编码,但是尽量避免这样做。</p><p>  因此不是删除最后一个项目,而是使用一个空白 Item 替代该项目的内容。这个 Item 的类型与您单击 Add Item 按钮时得到的 Item 类型相同。例如,它的 Description 为一个空字符串,而它的 Amount 为 0。为此,您再次使用 DOM API。您只访问最后一个 Item 元素,然后访问该元素的 Description 和 Amount 元素。我们将其文本节点的值分别设为空字符串和 0。</p><p>  让视图和模型同步</p><p>  此时,您已经处理了 Delete Item 的两个用例。还有一点工作要做。通常情况下,当您使用 XForms 命令修改 XForms 模型时,所有的重新计算和视图刷新都是自动完成的。使用 JavaScript 访问这些内容时情况却并非如此。您需要手动完成这些工作。</p><p>  所幸的是,在 deleteItem() 函数开始部分,您引用的 nsIXFormsModelElement 对象含有几个有用的方法可以帮助解决问题。首先使用的是它的 rebuild() 方法。此方法让它重建模型中数据的内部表示。它实际上让模型对象与 DOM 同步。这完全不会影响视图,只是对模型有影响。</p><p>  接下来您需要使用模型的 recalculate() 方法。原因在于您要使用模型中的 XForms bind-calculate 命令跟踪表中 amount 的总数。当您调用 recalculate() 方法时会刷新该计算。通用,这只是让模型与 DOM 同步。完全不影响视图。</p><p>  既然模型和数据已经同步了,您可以重新绘制视图。为此,您对模型调用 refresh() 方法。此方法导致绑定到模型的所有控件被刷新。对于删除一行而言,这将使该行消失,对于删除最后一行,这将导致最后一行的数据更改为指定的空白数据。在这两种情况下,这将使显示的项目总数基于当前数据得到更新。</p>
 <p> </p>

   <p>  触发 JavaScript</p><p>  既然已经编写了智能的 JavaScript 函数处理删除,只需修改 XForm,使它在调用到 Delete Item 按钮时调用此 JavaScript 就可大功告成了。为此,只需修改 Delete Item 的 XForms 声明,如清单 4 所示。</p><p>  清单 4. 新的 XForm delete item 控件<code><xf:trigger id="delete" xmlns="http://www.w3.org/1999/xhtml"><br />        <xf:label>Delete Item</xf:label><br />        <xf:load ev:event="DOMActivate"<br />resource="javascript:deleteItem()"/><br />      </xf:trigger></code></p><p>  注意,您只是将 XForms delete 命令替换为引用 deleteItem() JavaScript 函数的 load 命令。这是最后一个需要修改的地方。现在我们来运行一下修改后的示例。</p><p>  运行修改后的示例</p><p>  简单地将示例加载到浏览器中。显示的内容与 图 1 中原来显示的一样。但是,当您删除至最后一行并单击 Delete Item 后,您应看到图 4 所示的结果。</p><p>  图 4. 删除最后一行</p><p>  <img src="/content/uploadfile/200805/2008053117263775.jpg" onclick="get_larger(this)" alt="使用 JavaScript 让 XForms 变得更健壮" /></p><p>  现在最后一行没有消失。而是变为默认值。如果单击 Add Item 按钮,您将看见类似图 5 的内容。</p><p>  图 5. 删除最后一个 Item 后添加 Item</p><p>  <img src="/content/uploadfile/200805/2008053117264161.jpg" onclick="get_larger(this)" alt="使用 JavaScript 让 XForms 变得更健壮" /></p><p>  Add Item 按钮仍然有效,没有什么变化。</p><p>  您已经了解了如何使用 JavaScript 创建更智能的 delete item 动作。更重要的是,您了解了如何使用 JavaScript 访问和修改 XForms 模型数据,重新计算 XForms 绑定的计算,以及刷新 XForms 视图。您可以想像几种其他方法,使 Add Item 和 Delete Item 动作提供越来越复杂的功能。希望您也能够发现如何使用这些技术改进您自己的基于 XForms 的应用程序。</p>