简介
Web 表单已经存在很长时间,但是只能完成屈指可数的几种功能。XForms 提供了很多新的功能,包括改进的交互性和更多的提交选项,不过在深入细节之前,需要了解组成 XForms 表单的基本元素。
入门
首先,需要一个能够显示 XForms 的浏览器。对于本文来说,我们将使用 Firefox 以及 XForms 扩展,可以从 Mozilla.org 下载。(注意,它还带有 “不适合终端用户” 的标志,但是在撰写本文的时候似乎已经很稳定了。)如果喜欢 Microsoft® Internet Explorer,可以从 这里 下载 Formsplayer。加载 Formsplayer 需要对文件的开始稍作修改,并且文件应该使用 .htm 而不是 .xhtml 作为后缀,后者用于 Firefox 表单,但除此之外都能正常使用。看看本文提供的 示例代码,对两者加以比较。
本文假设您熟悉 XML 的一般概念。
创建基本的表单
比方说一个表单用于收集用户所喜欢的作曲家的信息。在 HTML 表单中,只要将页面上的表单字段包含在 form
元素中即可。XForms 表单与此类似,但结构上有点儿区别。清单 1 显示了一个简单的表单。
清单 1. 基本表单
<?xml version="1.0" encoding="ASCII"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xforms="http://www.w3.org/2002/XForms"> <head> <title>Describe a composer</title> <xforms:model id="model_composers"> <xforms:instance xmlns=""> <composers> <composer> <name></name> <genre></genre> <accessibility></accessibility> <difficulty></difficulty> <totalscore></totalscore> <examples> <example></example> <example></example> <example></example> </examples> </composer> </composers> </xforms:instance> <xforms:submission id="submit_model_composers" action="http://XFormstest.org/cgi-bin/showinstance.sh" method="post"/> </xforms:model> </head> <body> ...
表单的第一部分是模型。模型定义了包含在表单中的数据,并规定了当用户提交时如何处理数据。在该例中,创建了一个简单的 XML 文档来保存数据(称为实例),submission 元素规定当提交数据时应该发送到 http://xformstest.org/cgi-bin/showinstance.sh。很快就将看到,这个地址仅仅回显提交的数据。下一步是创建真正的表单元素本身(如清单 2 所示)。
清单 2. 表单元素
... </xforms:instance> <xforms:submission id="submit_model_composers" action="http://XFormstest.org/cgi-bin/showinstance.sh" method="post"/> </xforms:model> </head> <body> <xforms:input ref="/composers/composer/name"> <xforms:label>Name: </xforms:label> </xforms:input> <br /> <xforms:input ref="/composers/composer/genre"> <xforms:label>Genre: </xforms:label> </xforms:input> <br /> <xforms:input ref="/composers/composer/accessibility"> <xforms:label>Accessibility: </xforms:label> </xforms:input> <br /> <xforms:input ref="/composers/composer/difficulty"> <xforms:label>Difficulty: </xforms:label> </xforms:input> <br /> <xforms:input ref="/composers/composer/totalscore"> <xforms:label>Totalscore: </xforms:label> </xforms:input> <br /> <xforms:repeat id="repeat_example_model_composers" nodeset="/composers/composer/examples/example"> <xforms:input ref="."> <xforms:label>Example: </xforms:label> </xforms:input> </xforms:repeat> <xforms:submit submission="submit_model_composers"> <xforms:label>Submit</xforms:label> </xforms:submit> </body> </html>
前面几个字段非常简单。使用 input
元素创建了文本输入字段,并使用 ref
属性指定实例文档中与该 “控件” 对应的节点。换句话说,如果用户在 name
控件中输入一个名字,比如 Mozart,该信息将自动变成 /composers/composer/name
元素的内容。每个控件都有一个 label
,顾名思义,其目的就是在浏览器窗口中提供标签,以便用户知道自己看到的是什么。
清单 2 的底部增加了 repeat
元素。该元素选择某一组节点,在该例中就是 example
元素,并对每个节点执行其内容。要注意,repeat
元素用另一个输入控件作为自己的内容,不过 ref
属性引用了节点集中的当前节点。
如果在浏览器中加载这个表单,将看到和图 1 类似的结果。
图 1. 基本表单
要注意,标签行本身恰恰出现在每个表单字段的前面。一定要在标签中包含必要的空格。
现在我们来看当在表单中输入数据时会发生什么。
基本提交
如前所述,ref
属性将控件链接到实例文档中的节点。填写表单并选择 Submit 按钮之后就可以看到这个动作,如图 2 所示。
图 2. 完成的表单
然后就可以提交表单了。该例中没有对提交的数据做任何特别处理。如果是 HTML 表单,数据将变成名-值对,但这不是 HTML 表单。XForms 表单默认提交真正的 XML 实例作为 POST
请求的内容。指定的目标地址接收这些信息,然后简单地回显,因此可以看到图 3 所示的结果。
图 3. 结果
上面的例子非常简单,但是在进一步探讨之前,我们先做一点整理工作。
把实例放在单独的文件中
在本文附带的系列技巧的很多例子中,都会看到实例实际上不一定是表单的一部分,至少不直接是。也可以将 XML 实例放在单独的文档中,然后在实例元素中引用该文档(如清单 3 所示)。
清单 3. 隔离数据
...
<xforms:model id="model_composers">
<xforms:instance src="composer.xml">
</xforms:instance>
<xforms:submission id="submit_model_composers"
action="http://XFormstest.org/cgi-bin/showinstance.sh" method="post"/>
</xforms:model>
...
通过这样的隔离,表单更容易处理,在需要的时候修改实例数据也更方便了。
新增的控件类型
现在有了基本的印象,下面我们来看看可以添加到表单中的其他一些控件类型。
Secret
在 HTML 中,有一个常用的字段 “password”,它实际上不起保护作用,而是将用户输入的内容隐藏起来,这样别人就无法从背后偷窥。当然,除了密码外还有其他用途,因此 XForms 中将其称为 secret
控件。比方说,可能希望让人根据线索猜出作曲家是谁(如清单 4 所示)。
清单 4. secret 控件
... <body> <xforms:secret ref="/composers/composer/name" model="model_composers"> <xforms:label>Name: </xforms:label> </xforms:secret> <br /> <xforms:input ref="/composers/composer/genre" model="model_composers"> ...
这样再呈现表单时,在姓名字段中输入的内容就看不见了,如图 4 所示。
图 4. 隐藏数据
其他控件的功能就不仅仅是显示的问题了。
Range
在 HTML 中没有对应物的一个控件是 range
。这个控件显示一个 “滚动条”,用户可以来回拖动选择某个值。可以控制 start
、end
的值以及间隔的大小(如清单 5 所示)。
清单 5. range 控件
... <xforms:input ref="/composers/composer/genre"> <xforms:label>Genre: </xforms:label> </xforms:input> <br /> <br /> <xforms:range start="-10" end="10" step="1" ref="/composers/composer/accessibility"> <xforms:label>Accessibility: <br /></xforms:label> </xforms:range> <br /> <br /> <xforms:range start="-5.0" end="5.0" step="0.5" ref="/composers/composer/difficulty"> <xforms:label>Difficulty: <br /></xforms:label> </xforms:range> <br /> <br /> <xforms:input ref="/composers/composer/totalscore" > <xforms:label>Totalscore: </xforms:label> </xforms:input> ...
注意,和以前不同的是,range 不一定是均匀的。选择的数字以及步长的大小完全是任意的。重新打开该页面就会看到图 5 中所示的滚动条。
图 5. range 控件
用户使用 range 选择的数据就是一个数字,就像用户直接输入的数字一样添加到实例文档中。通过提交表单观察实例文档可以看看结果。
select
HTML 还有提供枚举选项的功能。在 HTML 中可以创建单选或多选字段,但是必须事先确定单选还是多选。比方说,可以添加一组复选框、一组单选按钮或者一个下拉菜单。在 XForms 中只要说明希望让用户能够选择,其他工作交给呈现引擎就可以了(如清单 6 所示)。
清单 6. select 控件
...
<xforms:input ref="/composers/composer/name">
<xforms:label>Name: </xforms:label>
</xforms:input>
<br />
<xforms:input ref="/composers/composer/genre">
<xforms:label>Genre: </xforms:label>
</xforms:input>
<xforms:select ref="/composers/composer/genre" appearance="full">
<xforms:label>Genre: </xforms:label>
<xforms:item>
<xforms:label>Baroque</xforms:label>
<xforms:value>B</xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>Classical</xforms:label>
<xforms:value>C</xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>Neo-Classical</xforms:label>
<xforms:value>NC</xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>Modern</xforms:label>
<xforms:value>M</xforms:value>
</xforms:item>
<xforms:item>
<xforms:label>Pop</xforms:label>
<xforms:value>P</xforms:value>
</xforms:item>
</xforms:select>
<xforms:range start="-10" end="10" step="1"
ref="/composers/composer/accessibility">
<xforms:label>Accessibility: <br /></xforms:label>
</xforms:range>
...
该例中添加了一些不同的项供选择,每个项都指定了相应的 label
(用户可见的部分)和 value
(作为数据的一部分实际提交的部分)。重新打开该表单可以看到图 6 所示的结果。
图 6. 选择列表
这里有两点需要注意。首先要注意,Genre 输入框和选择列表引用了实例文档中的同一个节点。因此,如果选择其中的一个框,对应的值就会添加到 genre
元素中并出现在输入框中。
其次,虽然没有指定复选框但显示的是复选框。其中将 appearance
指定为 full
。多数情况下,这样将用复选框显示选择列表。也可将 appearance 指定为 compact
(如清单 7 所示)。
清单 7. 改变选择列表的外观
...
<xforms:input ref="/composers/composer/genre">
<xforms:label>Genre: </xforms:label>
</xforms:input>
<xforms:select ref="/composers/composer/genre" appearance="compact">
<xforms:label>Genre: </xforms:label>
<xforms:item>
<xforms:label>Baroque</xforms:label>
<xforms:value>B</xforms:value>
...
这样将改变选择列表的形式,如图 7 所示。
图 7. 改变选择列表
要注意,控件不再呈现为 “块” 元素,并自动占一行。从功能上来说,相当于 HTML 中的多选列表。
XForms 规范还提供了另一个 appearance 值 “minimal”,不过在 Firefox 中看起来和 compact
相同。
浏览器如何呈现表单完全取决于浏览器本身。这是 XForms 的优点之一,因为它可以让设备根据需要做出选择。比如,蜂窝电话可能以完全不同的方式显示选择列表。这是把数据和表示分开的好处之一。
虽然这样很好,但是如果不希望用户有多种选择该怎么办?比方说,如果需要单选按钮而不是复选框该怎么办?可以使用 select1
控件代替select
(如清单 8 所示)。
清单 8. select1
控件
...
<xforms:input ref="/composers/composer/genre">
<xforms:label>Genre: </xforms:label>
</xforms:input>
<xforms:select1 ref="/composers/composer/genre" appearance="full">
<xforms:label>Genre: </xforms:label>
<xforms:item>
<xforms:label>Baroque</xforms:label>
<xforms:value>B</xforms:value>
</xforms:item>
...
</xforms:select1>
<xforms:range start="-10" end="10" step="1"
ref="/composers/composer/accessibility">
<xforms:label>Accessibility: <br /></xforms:label>
</xforms:range>
...
数据相同,但是在呈现表单的时候就可以看出两者的区别了(如图 8 所示)。
图 8. select1
控件
XForms 规范还包括其他控件,如 file upload
控件,但这些控件按照通常的方法使用。
设置控件的值
表单中另一个常见的功能是设置值,通常在第一次加载表单的时候设置,不过有时候也在使用表单的过程中。我们看看在 XForms 表单中如何实现。
使用初始值
我们已经看到添加到表单控件中的信息如何被添加到实例中。事实上这种交换是双向的。添加到实例中的信息也会添加到表单控件中(如清单 9 所示)。
清单 9. 实例中的信息
... <xforms:model id="model_composers"> <xforms:instance xmlns=""> <composers> <composer> <name>Wolfgang Amadeus Mozart</name> <genre>C</genre> <accessibility>9</accessibility> <difficulty>9</difficulty> <totalscore>18</totalscore> <examples> <example>A little night music</example> <example>Twinkle, twinkle, little star</example> <example>Don Giovanni</example> </examples> </composer> </composers> </xforms:instance> <xforms:submission id="submit_model_composers" action="http://XFormstest.org/cgi-bin/showinstance.sh" method="post"/> </xforms:model> ...
这里看到的信息可能和表单中原来包含的有点儿不同,但是如果打开表单就会在页面上看到(如图 9 所示)。
图 9. 实例中的数据
因此放在实例中的任何内容都会反映到控件上,反之亦然。但是 XForms 还提供了另一种选择来建立这种联系。
绑定数据
到目前为止,我们只看到了如何引用实例中的某个节点。XForms 还允许明确地 “绑定” 到特定节点集。比方说,可以创建一个 ID 来引用某个节点(如清单 10 所示)。
清单 10. 建立绑定
<?xml version="1.0" encoding="ASCII"?> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:xforms="http://www.w3.org/2002/XForms"> <head> <title>Describe a composer</title> <xforms:model id="model_composers"> <xforms:instance xmlns=""> ... </xforms:instance> <xforms:submission id="submit_model_composers" action="http://XFormstest.org/cgi-bin/showinstance.sh" method="post"/> <xforms:bind id="total" nodeset="/composers/composer/totalscore"/> </xforms:model> </head> <body> <xforms:input ref="/composers/composer/name"> <xforms:label>Name: </xforms:label> </xforms:input> ... <xforms:range start="-10.0" end="10.0" step="0.5" ref="/composers/composer/difficulty"> <xforms:label>Difficulty: <br /></xforms:label> </xforms:range> <br /> <br /> <xforms:input bind="total"> <xforms:label>Totalscore: </xforms:label> </xforms:input> <br /> <xforms:repeat ...
要注意,Totalscore
控件并没有引用哪个节点而是通过 ID 值来发现某个节点。表单在模型中定义了绑定本身。这样在打开表单的时候,XForms 就知道到哪里取得 Totalscore
值(如图 10 所示)。
图 10. 使用绑定的值
对于抽象表单来说这种能力很有用;比方说,在很大的表单中可以为每个数据建立绑定,然后只要修改每个绑定的节点集就可以改变实例的结构,而不需要修改整个表单。
使用计算得到的值
不过,绑定最大的价值可能在于能够建立计算值。比方说,可以在创建 Totalscore
的时候自动添加可访问性和难度值(如清单 11 所示)。
清单 11. 创建计算值
...
</xforms:instance>
<xforms:submission id="submit_model_composers"
action="http://XFormstest.org/cgi-bin/showinstance.sh" method="post"/>
<xforms:bind id="total" nodeset="/composers/composer/totalscore"
calculate="/composers/composer/accessibility +
/composers/composer/difficulty"/>
</xforms:model>
...
现在在打开表单的时候,就会发现可访问性或难度值的变化会自动改变 Totalscore
值(如图 11 所示)。
图 11. 添加值
提交表单
我们已经看到,XForms 表单的典型提交方式是将整个实例发送到服务器。但情况并非一定如此。
提交表单的一部分
XForms 提交的一种变化是只提交实例的一部分。比如,可以告诉表单只提交 example 元素(如清单 12 所示)。
清单 12. 仅提交 example
...
</examples>
</composer>
</composers>
</xforms:instance>
<xforms:submission id="submit_model_composers"
ref="/composers/composer/examples"
action="http://localhost/XForms.php"
method="post"/>
<xforms:bind id="total" nodeset="/composers/composer/totalscore"
calculate="/composers/composer/accessibility +
/composers/composer/difficulty" />
</xforms:model>
...
现在提交表单就会发现实例中只有 ref
属性所指定的那一部分被提交了(如图 12 所示)。
图 12. 提交实例的一部分
另一种变化形式是提交实例但不替换整个页面。
替换实例的一部分
和一般 HTML 表单相比,XForms 的一大优点是能够以 XML 的形式发送提交数据和接收返回的数据,然后再将其添加到实例以及页面上已有的控件中。要注意,除非修改浏览器的安全特性,否则响应必须来自和最初下载表单相同的域名。比方说,如果下载页面http://www.nicholaschase.com/composers.xhtml
并希望用返回的提交填充表单实例,那么提交 URL 最好以 http://www.nicholaschase
开始。这就意味着不能本地加载表单。本文示例表单中包含的 PHP 脚本仅仅返回数据。可用其来测试本节中的例子。
让表单替换实例而不是页面只需要在提交中增加一个属性(如清单 13 所示)。
清单 13. 替换实例
... </xforms:instance> <xforms:submission id="submit_model_composers" ref="/composers/composer/examples" replace="instance" action="http://localhost/XForms.php" method="post"/> <xforms:bind id="total" nodeset="/composers/composer/totalscore" calculate="/composers/composer/accessibility + /composers/composer/difficulty" /> </xforms:model> </head> <body> ... <br /> <xforms:repeat id="repeat_example_model_composers" nodeset="/composers/composer/examples/example"> <xforms:input ref="."> <xforms:label>Example: </xforms:label> </xforms:input> </xforms:repeat> <xforms:repeat id="repeat_example_model_composers" nodeset="/examples/example"> <xforms:input ref="."> <xforms:label>Submitted Example: </xforms:label> </xforms:input> </xforms:repeat> <xforms:submit submission="submit_model_composers"> <xforms:label>Submit</xforms:label> </xforms:submit> </body> </html>
要注意新增加的第二个 repeat 元素,只有当文档中存在 /examples/example 节点时才会出现。因此第一次加载文档时不会出现,只有在提交之后才会看到(如图 13 所示)。
图 13. 提交后的实例
结束之前还有一点需要知道。
回到基础:传统的 Web 表单
现在我们看到了 XForms 和 HTML 表单的诸多区别,但是如果希望使用 XForms 表单但向老式的脚本提交,那么怎么办?所幸的是,要求 XForms 按照原来的名-值对样式提交数据很简单(如清单 14 所示)。
清单 14. 提交传统数据
... <xforms:model id="model_composers"> <xforms:instance id="instance_model_composers" src="composer.xml"/> <xforms:submission id="submit_model_composers" action="http://localhost/XForms.php" method="get" encoding="application/x-www-form-urlencoded"/> <xforms:bind id="total" nodeset="/composers/composer/totalscore" calculate="/composers/composer/accessibility + /composers/composer/difficulty" /> </xforms:model> ...
要注意,这里再次使用了 GET
而不是 POST
方法,因为这样可以告诉 XForms 将数据放在 URL 本身而不是请求中。此外,encoding 属性说明要建立名-值对。这样在提交表单的时候,就会看到页面上没有任何信息 —— 增加一条简单的输出语句来避免错误,但是信息出现在 URL 中(如图 14 所示)。
图 14. 提交 GET
请求
如果查看实际的数据,就会发现数据都在那儿,只不过以 “扁平” 的形式存在;没有复制原来的 XML 层次结构的机制(如清单 15 所示)。
清单 15. URL 请求
http://localhost/XForms.php?name=Wolfgang+Amadeus+Mozart;genre=C; accessibility=9;difficulty=9;totalscore=18;example=A+little+night +music;example=Twinkle%2C+twinkle%2C+little+star;example=Don+Giovanni
要注意每个名-值对之间用分号(;)分隔。可以用 separator
属性将分隔符改为 “与” 符号(&)。
结束语
到现在您应该能够创建简单的和比较复杂的 XForms 表单了。这些表单接收和存储的数据都依赖于 XML “实例” 的结构,但是也可以模拟 HTML 表单中很多常见的功能。XForms 表单增强了交互性,数据与表示的分离使其能够用于多种设备。此后您应该能够知道利用这些功能创建更有意义的表单时需要做什么。