repeat 元素允许您在 XForms 文档的 XML 数据实例中将 XForms 用户界面(UI)元素映射到一个同构集合。“同构集合” 是在文档中相同级别上数据类型相同的一系列节点。例如,在下面的清单 1 中,bread 元素集就是该 XML 实例中的一个同构节点集合。
清单 1. XML 节点的同构集合
<xforms:instance id="in-stock">
<in-stock>
<groceries>
<perishable>
<bread brand="Wonder" quantity="1"/>
<bread brand="Merita" quantity="2"/>
<bread brand="Arnold" quantity="4"/>
</perishable>
</groceries>
</in-stock>
</xforms:instance> |
要将一个 repeat 元素与这个同构集相关联,只需在 repeat 元素的 nodeset 属性中指定 XPath 来引用它即可,如清单 2 所示。
清单 2. 将一个 repeat 绑定到同构集合
<xforms:repeat id="bread-repeat" nodeset="instance('in-stock')/perishable/bread">
... |
另请注意,在 清单 2 中,repeat 被赋予 id 值,因此我们随后可从其他 XForms 结构中引用它。
现在,您可以把 UI 元素放置在 repeat 元素内,当您察看表单时,UI 元素将是 “展开的”,这样将为同构集中的每个成员显示一个控件。清单 3 显示了一个 XForms input UI 元素,它绑定到 清单 1 所示同构集内的一个属性。请注意,input 元素内的数据实例引用与为周围 repeat 定义的节点集有关。
清单 3. repeat 中的 UI 元素
<xforms:repeat id="bread-repeat"
nodeset="instance('in-stock')/groceries/perishable/bread">
<xforms:input ref="@brand">
<xforms:label>Brand of bread: </xforms:label>
</xforms:input>
</xforms:repeat> |
清单 4 指出了将真正呈现的 “展开的” 版本。(开发此表单时您不会看到这段源代码;这里只是为了演示呈现程序对于 repeat 结构的解释。)
清单 4. 清单 3 中 repeat 的展开的版本
<xforms:input ref="instance('in-stock')/groceries/perishable/bread[1]/@brand">
<xforms:label>Brand of bread: </xforms:label>
</xforms:input>
<xforms:input ref="instance('in-stock')/groceries/perishable/bread[2]/@brand">
<xforms:label>Brand of bread: </xforms:label>
</xforms:input>
<xforms:input ref="instance('in-stock')/groceries/perishable/bread[3]/@brand">
<xforms:label>Brand of bread: </xforms:label>
</xforms:input> |
图 1 显示了此结构在带有 XForms Extension 的 Mozilla Firefox 中呈现时的效果(关于如何获取 Firefox XForms Extension 的信息,请参阅 参考资料 )。
图 1. repeat 的呈现效果
以表格格式显示同构集中的数据往往很有意义。在 XForms Extension for Firefox 中,格式化 repeat 使之以表格形式呈现,常常需要使用级联样式表(CSS)。
清单 5 中的源代码显示了文档体中的 markup,CSS 定义将应用于此。请注意,表格和列的头都是用 XHTML markup 声明的。而表格 “体” 则完全由 XForms repeat 组成。
清单 5. 用来将 repeat 表示为表格的 Markup
<xhtml:table>
<xhtml:tr>
<xhtml:thead>
<xhtml:th>Brand Name</xhtml:th>
<xhtml:th>Quantity</xhtml:th>
</xhtml:thead>
</xhtml:tr>
<xforms:repeat id="bread-repeat"
nodeset="instance('in-stock')/groceries/perishable/bread">
<xforms:output ref="@brand">
<xforms:label/>
</xforms:output>
<xforms:input ref="@quantity">
<xforms:label/>
</xforms:input>
</xforms:repeat>
</xhtml:table>
|
清单 6 给出了可用于 repeat 的样式定义。CSS 声明置于 XHTML style 标记内(您也可以使用 style 标记上的 src 属性引用存储在外部文件中的 CSS)。
清单 6. 可将 repeat 制成表格样式的 CSS
<xhtml:style type="text/css">
@namespace xforms url("http://www.w3.org/2002/xforms");
@namespace xhtml url("http://www.w3.org/1999/xhtml");
xhtml|th
{
width: 240px;
}
xforms|repeat .xf-repeat-item
{
display: table-row;
width: 480px;
}
xforms|repeat xforms|output, xforms|repeat xforms|input
{
display: table-cell;
border: thin;
border-style: solid;
width: 240px;
text-align: center;
background-color: lightgray;
}
</xhtml:style> |
清单 6 是这样开始样式化处理的:首先声明 repeat 中的每一条目(即每一行)都必须作为表格中的一行来显示。接着,下一个声明要求将 repeat 内的每一个 XForms 输出输入控件都作为一个表格单元格处理,它们都具有实线边框和浅灰色的背景。
图 2 显示了 repeat 在带有 XForms Extension 的 Mozilla Firefox 中呈现时的效果。
图 2. 以表格形式呈现的 repeat
还有其他样式可以用来在 Firefox 中获得类似的效果。此外,其他的 XForms 呈现程序可能还允许同时使用不同的方法。其中的一种方法是利用在 XForm 1.0 规范中定义的一些可选属性,尤其是 repeat-nodeset。这些属性可以置于一个非 XForms 元素上来定义一个 repeat 结构,此结构可应用于该元素之子。清单 7 显示了一个在 XHTML table 元素上使用 repeat-nodeset 的例子。
清单 7. 使用 repeat-nodeset 属性
<xhtml:table>
<xhtml:tr>
<xhtml:thead>
<xhtml:th>Brand Name</xhtml:th>
<xhtml:th>Quantity</xhtml:th>
</xhtml:thead>
</xhtml:tr>
</xhtml:table>
<xhtml:table xforms:repeat-nodeset="instance('in-stock')/groceries/perishable/bread">
<xhtml:tr>
<xhtml:td>
<xforms:output ref="@brand">
<xforms:label/>
</xforms:output>
</xhtml:td>
<xhtml:td>
<xforms:input ref="@quantity">
<xforms:label/>
</xforms:input>
</xhtml:td>
</xhtml:tr>
</xhtml:table>
|
在非 XForms 元素上使用 repeat 属性是一个很不错的方法。包括 XForms Extension for Firefox 在内的一些 XForms 实现都支持这种方法。
到目前为止,我们一直都把 repeat 结构看作是存在于 XML 数据实例内并加以呈现的。实际上,XForms 允许用户用那些数据进行更多操作。比如,将新行添加到表单中或者删除现有行,这些都是很常见的。清单 8 显示了用于允许用户在 repeat 集中触发行的删除和插入的 markup。
清单 8. 启用行的插入和删除
<xforms:trigger>
<xforms:label>Insert Row</xforms:label>
<xforms:insert ev:event="DOMActivate"
at="index('bread-repeat')" position="after"
nodeset="instance('in-stock')/groceries/perishable/bread"/>
</xforms:trigger>
<xforms:trigger>
<xforms:label>Delete Row</xforms:label>
<xforms:delete ev:event="DOMActivate"
at="index('bread-repeat')"
nodeset="instance('in-stock')/groceries/perishable/bread"/>
</xforms:trigger>
|
清单 8 引入了 repeat “索引” 的概念。请注意 insert 和 delete 动作的 at 属性内 index() 函数的使用。at 属性的值表示基于 1 的索引在插入或删除发生的位置进入 repeat 数据集。通过在此属性值内使用 index() 函数,可以指定插入或删除将在 repeat 中的当前选中的行发生。在删除操作的情况下,这表示当前选中的行将被删除。对于插入操作来说,这表示新行将在选中的索引所对应的行之前或之后直接插入。新行是否在当前选中的行之前或之后插入取决于 insert 元素的 position 属性中的值。
当触发插入操作时,repeat 的最后一行被复制,副本会被插入。一般来讲,这意味着此行的元素和属性的值也随之被插入。也许这不并是我们想要的结果,因为您可能想在插入之后进行 setvalue 操作来重置值,如 清单 9 所示。index() 函数会被再次使用来指定 repeat 的当前选中行作为发生 setvalue 操作的行。这符合预期目的 —— 指向刚插入的行 —— 因为插入操作会把 repeat 的索引重置为刚插入行的索引。
清单 9. 重新设置插入的值
<xforms:trigger>
<xforms:label>Insert Row</xforms:label>
<xforms:action ev:event="DOMActivate">
<xforms:insert at="index('bread-repeat')"
position="after"
nodeset="instance('in-stock')/groceries/perishable/bread"/>
<xforms:setvalue
ref="instance('in-stock')/
groceries/perishable/bread[index('bread-repeat')]/@quantity"
value="0"/>
</xforms:action>
</xforms:trigger>
|
用来在 repeat 中插入和删除行的触发器实现起来并不难。事实上,自动的 XForms 生成工具,特别是 IBM® XML Forms Generator(参见 参考资料)将在每一次 repeat 时都会自动生成用来进行添加和删除操作的触发器。
在上一节中,我们曾提到插入操作会复制 repeat 中的最后一行。如果 repeat 没有行,就会非常糟糕,之所以没有行可能是因为用户为所有行都触发了删除操作。在前面的例子中,如果所有行都被删除,将导致插入操作不再发挥任何作用。
因此,您可能会希望防止最后一行被删除。清单 10 给出了一种方法,那就是将“删除”触发器绑定到数据模型中的节点上,然后在节点集中的节点数达到 1 时使该节点变为不相关。
清单 10. 防止最后一行被删除
<xforms:instance id="trigger-controller">
<trigger-sets>
<trigger-set>
<insert-trigger/>
<delete-trigger/>
</trigger-set>
</trigger-sets>
</xforms:instance>
<xforms:bind nodeset="instance('trigger-controller')/trigger-set/delete-trigger"
relevant="count(instance('in-stock')/groceries/perishable/bread) > 1"/>
...
<xforms:trigger ref="instance('trigger-controller')/trigger-set/insert-trigger">
<xforms:label>Insert Row</xforms:label>
<xforms:insert ev:event="DOMActivate" at="index('bread-repeat')"
position="after"
nodeset="instance('in-stock')/groceries/perishable/bread"/>
</xforms:trigger>
<xforms:trigger ref="instance('trigger-controller')/trigger-set/delete-trigger">
<xforms:label>Delete Row</xforms:label>
<xforms:delete ev:event="DOMActivate" at="index('bread-repeat')"
nodeset="instance('in-stock')/groceries/perishable/bread"/>
</xforms:trigger>
|
当由触发器引用的节点变成不相关时,触发器就变为 “不可用”。在 Firefox 中,默认行为是让按钮从页面上消失,如 图 3 所示,该图显示了倒数第二行被删除之前和之后的表单。
图 3. 防止最后一行被删除
您也许会希望用其他 “相关” 属性的方法进行试验;比如,使 repeat 的最后一行总是不相关的。这种方法将导致最后一行永远不能被选中,因此,当您依靠 repeat 索引来决定删除区域时,最后一行就永远不能被删除。
清单 11 显示了您可以怎样将 “原型” 项添加到您的数据实例中,然后添加绑定使最后这项永远不相关。要确保最后一行永不显示,可以创建一个新的 CSS 项,敲击 “.disabled” CSS 提示。对于这种方法来说,repeat 元素及其内容无需改变。
清单 11. 有二级嵌套的数据
<xforms:model>
<xforms:instance id="in-stock">
<in-stock>
<groceries>
<perishable>
<bread brand="Wonder" quantity="1" />
<bread brand="Merita" quantity="2" />
<bread brand="Arnold" quantity="4" />
<bread brand="Not sure yet" quantity="0" />
</perishable>
</groceries>
</in-stock>
</xforms:instance>
<xforms:bind nodeset="instance('in-stock')/groceries/perishable/bread
[count(instance('in-stock')/groceries/perishable/bread)]/
@brand"
relevant="false()"/>
<xforms:bind nodeset="instance('in-stock')/groceries/perishable/bread
[count(instance('in-stock')/groceries/perishable/bread)]/
@quantity"
relevant="false()"/>
</xforms:model>
<xhtml:style type="text/css">
@namespace xforms url("http://www.w3.org/2002/xforms");
xforms|*:disabled { display: none; }
</xhtml:style>
|
就目前来讲,有必要使用这种技术来防止插入原型的删除。然而,帮助已有处可寻。XForms 1.1 规范将致力于解决这个问题及插入和删除操作存在的其他不足,这样一来,您就不再需要这里所介绍的这些方法了。
当数据以不同级别通过嵌套方式包含同构数据集时,XForms 将尽显其功能的强大。例如,清单 12 显示了一个数据实例,在此数据实例中顶级的同构元素集由三个 item 元素组成。每个 item 元素又包含其自身的 info 元素集。
清单 12. 二级嵌套的数据
<xforms:instance id="in-stock">
<in-stock>
<groceries>
<perishable>
<item type="bread">
<info brand="Wonder" quantity="1"/>
<info brand="Merita" quantity="2"/>
<info brand="Arnold" quantity="4"/>
</item>
<item type="peanut butter"">
<info brand="Skippy" quantity="2"/>
<info brand="Jif" quantity="1"/>
</item>
<item type="jelly">
<info brand="Smuckers" quantity="1"/>
<info brand="Welch's" quantity="3"/>
</item>
</perishable>
</groceries>
</in-stock>
</xforms:instance>
|
通过将相应的 repeat 嵌套,XForms 使在类似这样的嵌套集上迭代得以轻松实现。清单 13 显示了具体的做法。
清单 13. 定义嵌套 repeat
<xforms:repeat id="bread-repeat" nodeset="instance('in-stock')/groceries/perishable/item">
<xforms:output ref="@type">
<xforms:label>Item type: </xforms:label>
</xforms:output>
<xforms:repeat id="bread-repeat" nodeset="info">
<xforms:output ref="@brand">
<xforms:label>Brand: </xforms:label>
</xforms:output>
<xforms:input ref="@quantity">
<xforms:label>Quantity: </xforms:label>
</xforms:input>
</xforms:repeat>
<xhtml:hr/>
</xforms:repeat>
|
清单 13 在最外部 repeat 中定义一个输出,并为此显示每个条目类型。然后,一个内部 repeat 在每个 item 内的 info 元素上迭代。注意,内部 repeat 的 nodeset 属性中的 XPath 被定义为与它包含的 repeat 的节点集有关。图 4 显示了 Firefox 中的这些嵌套 repeat。
图 4. 呈现的嵌套 repeat
XForms 再一次显示了它的强大功能,它对可套入的 repeat 的深度没有限制。惟一的限制是呈现程序的性能和要求为终端用户维护一个干净、易导航的界面。
在对 插入和删除操作 的讨论中,我们略微谈到了 repeat 的索引 这个概念 —— 即当前所选择的“行”的基于一的索引。
选择可以由用户做,也可以用以下这两种方法的一种以编程的方式强制进行。
第一种方法:通过为被称作 startindex 的 repeat 的属性指定一个整数值,在表单第一次被呈现时,指定 repeat 的索引。不同于其他用于处理索引的 XForms 工具,在这里不能指定 XPath 表达式;依照 XForms 1.0 规范,只能指定正整数值。
第二种方法:也可以用 setindex 操作设置 repeat 索引,这个 setindex 操作可以是出于对用户激活触发器的响应,也可以是其他操作的一部分(比如,在插入或删除后将索引重新设置到一个期望值)。清单 14 给出了一个 markup,该 markup 将索引最初设置为 2 ,然后允许用户为索引输入值,并通过激活标为 “Select” 的触发器将该值设置为索引值。
清单 14. 使用 setindex
<xforms:instance id="target-index">
<target-index>
<value>1</value>
</target-index>
</xforms:instance>
...
<xhtml:table>
<xhtml:tr>
<xhtml:thead>
<xhtml:th>Brand Name</xhtml:th>
<xhtml:th>Quantity</xhtml:th>
</xhtml:thead>
</xhtml:tr>
<xforms:repeat id="bread-repeat"
nodeset="instance('in-stock')/groceries/perishable/bread"
startindex="2">
<xforms:output ref="@brand">
<xforms:label/>
</xforms:output>
<xforms:input ref="@quantity">
<xforms:label/>
</xforms:input>
</xforms:repeat>
</xhtml:table>
<xforms:input ref="instance('target-index')/value">
<xforms:label>Target Index: </xforms:label>
</xforms:input>
<xforms:trigger>
<xforms:label>Select</xforms:label>
<xforms:setindex ev:event="DOMActivate"
repeat="bread-repeat"
index="instance('target-index')/value"/>
</xforms:trigger>
|
在 清单 14 中,添加了一个数据实例用来保存 repeat 索引的期望值。输入控件被绑定到此数据,所以不论用户何时输入一个新值,这个值都会被放置到 value 元素中。最后,触发器的 setvalue 操作引用其 index 属性中的相同 value 元素,index 属性可以 使用 XPath 表达式。图 5 显示了 Firefox 中的表单效果。
图 5. 允许用户输入 repeat 索引
图 5 显示了选中的行与其他行的外观不同。如果没有这样的可视化提示,用户很有可能会弄不清楚哪一行是当前选中的行。可以用 CSS 为用户指出当前所选中的行。在 Firefox 中,如 清单 15 中所示的 CSS 会为选中的索引制定样式。
清单 15. 为 repeat 索引制定样式
xforms|repeat .xf-repeat-index > xforms|*
{
color: white;
background-color: black;
border: none;
}
|
XForms repeat 元素上的 number 属性可以告知呈现程序一次显示的 repeat 条目(或行)的数量。对于 XForms 处理器来说,对这个属性的支持是可选的,所以很多呈现程序都不支持这个属性。然而,一些呈现程序,比如用于 Microsoft® Internet Explorer 的 FormPlayer 插件(参见 参考资料),却的确支持 number 属性。
使用 number 属性可以允许一个更干净的表示,尤其是大型数据集上的 repeat 的表示。它也可以改善性能,因为当数据更改时,并非 repeat 中的每一条目都需要刷新其关联控件。
我们可以这样说,使用来自 清单 1 中的数据实例,一次只需要显示一行数据。我们只要将值 1 添加到 清单 2 中定义的 repeat 中。结果会如 清单 16 中所示。
清单 16. 将 number 属性应用到 repeat 中
<xforms:repeat id="bread-repeat"
nodeset="instance('in-stock')/groceries/perishable/bread"
number="1">
... |
现在,由于一次只显示一个 repeat 行,因而节约了大量空间,这很棒,但是,除了最初显示的行的数据,此表单用户可能还想要操作其余行的数据。可以通过允许用户设置当前 repeat 索引,为用户提供访问其他 repeat 条目的机会。清单 17 显示了如何利用 setindex 操作实现该功能。
清单 17. 允许对 repeat 的导航
<xforms:trigger>
<xforms:label>< < Previous</xforms:label>
<xforms:setindex ev:event="DOMActivate" repeat="bread-repeat"
index="index('bread-repeat') - 1"/>
</xforms:trigger>
<xforms:trigger>
<xforms:label>Next > ></xforms:label>
<xforms:setindex ev:event="DOMActivate" repeat="bread-repeat"
index="index('bread-repeat') + 1"/>
</xforms:trigger>
|
清单 17 引入了 “Previous” 和 “Next ” 按钮,当分别按下这两个按钮时,repeat 的索引会被分别增减。这使表单的用户可以前后滚动查看 repeat 中的数据。
图 6 是使用了 FormsPlayer 插件的可滚动 repeat 在 Internet Explorer 中显示的效果。
图 6. 可滚动的 repeat
XForms repeat 是 XForms 中甚为强大的结构之一。如果您还没有体验过它的强大功能,就请赶紧获取一个呈现程序 —— 比如可免费下载的 XForms Extension for Mozilla Firefox(参见 参考资料)—— 立即亲自尝试一下这些技术吧。
学习
- 您可以参阅本文在 developerWorks 全球网站上的 英文原文。
- 在 W3C XForms 上深入研究 XForms,这里有到官方 XForms 规范以及 XForms 的不同呈现选项的链接。
- 在 W3C 站点 上查找更多关于 XHTML、Cascading Style Sheets(CSS)、XML、XML Events、XPath 和其他相关标准的信息。
获得产品和技术
-
Mozilla XForms:使用此插件在 Mozilla Firefox 中呈现符合标准的表单。
-
获得 MozzIE,一种允许您在 Internet Explorer 中呈现 XForms 的开放源码控件。
-
体验 FormsPlayer,一种 Internet Explorer 的插件,可以用来呈现 XForms。
-
XML Forms Generator:使用 alphaWorks 基于 Eclipse 的工具,通过点击鼠标创建符合标准的功能性表单。
-
Visual XForms Designer:查看该主页,上有到安装指导、先决条件和论坛的链接。
-
Compound XML Document Toolkit:探究其他开放标准的 XML markup,包括 Scalable Vector Graphics(SVG)、MathML、VoiceXML 和 Synchronized Multimedia Integration Language(SMIL)。
讨论
- 在 讨论论坛 上获得关于 Visual XForms Designer 问题的答案。