表单提交
绝大部分应用程序都需要从用户获得输入,其中很大部分便是以表单输入的形式。一个表单,从用户输入系统接收到数据处理,会经历几个普遍的阶段。一个完备的表单提交流程具备以下几个不同阶段的功能:
- 限制:根据字段的数据类型,表单对输入控件所接受的字符做限制。例如,文本类型的字段限制长度,数字类型的字段就不接受输入字母字符,日期时间类型的字段如果采取直接输入也可以限制输入的字符类型和格式。
- 转换:系统按各字段的数据类型,解析用户的原始输入,其转换成指定的数据类型,如上述的数字、日期时间或列表等。
- 校验:系统依照业务逻辑的要求,对各个字段转换好的数据进行校验,如非空,数字和日期时间的取值范围,特殊号码或地址的格式要求等等。如果某个字段的数据没有通过校验,系统就会返回提示信息给用户;如果全部校验通过,则表单输入的数据已经可用,进入下一阶段的处理。
- 应用:到上一阶段为止,一个由表单输入数据的完整过程已经结束,接下来要怎样使用这些数据,因系统而异。可以直接在业务逻辑里分析、计算和存储它们,或者在普及的模型-视图-控制器(MVC)设计里先将它们更新到一个数据模型里再做后续应用。
上面说的表单输入的三个主要阶段在现实世界里又因为系统采用的技术和架构有不同的具体情况。
发生在哪
在广泛应用的客户端服务器架构下,这些阶段是发生在客户端还是服务器端?在传统的桌面客户端和浏览器两类情况下有差异。首先,限制的功能始终位于客户端,原因很简单,这种特性要求即时的响应,把每次按键或鼠标动作都经由网络发到服务器做判断再返回,既在时间上很难满足,又成本过高。转换和校验阶段的工作则有更多选项。如果是桌面客户端,它们可以根据程序员的喜好或系统的架构在客户端和服务器端之间选择一个完成。在web应用程序中,传统上转换和校验都在服务器端完成,为了给用户提供更及时友好的反馈,在网页前端通常也会先采用JavaScript进行这两项工作,但在服务器端的业务逻辑使用或存储这些用户输入前,仍然会再进行一次转换和校验,这样做,既是因为服务器端程序从HTTP获得的原始数据只是字符串,数据被存入数据库之前须被转换成特定的数据类型,也是因为浏览器端的脚本在可靠性和安全性上差于服务器端运行的程序。
现成的功能?
桌面和web应用程序在表单设计所属的用户界面开发领域都有很多现成可用的控件集(如桌面环境下的Windows Forms、Java Swing,web开发有服务器端的ASP.NET、JSF,浏览器端的dojo、ExtJS等),用于接受输入的控件或多或少已具备部分的限制和转换阶段的功能,如文本框可通过属性限制输入的文本的长度,日期时间控件只允许输入合理的年月日和时分秒,可从其直接获得输入的日期时间值。不过仍然有许多场合,开发人员要写代码以完成对输入的限制和转换。比如经常用到的数字类型的字段,在dojo和ExtJS这样的web前端控件集里有专门的控件,但在上面提到的其他几个库里都没有。为了只允许向文本框输入与数字有关的字符(数码、小数点、正负号和退格),就可以在它的KeyPress事件里编码,下面是一个C#里的版本:
this.textBox1.KeyPress += new System.Windows.Forms.KeyPressEventHandler(this.textBox1_KeyPress);
//Prevent entering nonnumerical characters into the textbox.
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
char keyChar = e.KeyChar;
bool isNum = false;
if ((keyChar >= '0' && keyChar <= '9') || keyChar=='+' || keyChar=='-' || keyChar == '.' || keyChar == (char)8)
{
isNum = true;
}
if (!isNum) e.Handled = true;
}
数字、日期时间和列表类型字段的输入,很多时候也需开发人员主动进行类型转换。至于校验,很多用户界面框架也有API供调用。例如ASP.NET和JSF两个框架都具备校验非空、长度、值范围的验证器(validator),都可以创建自定义规则的验证器。
验证
验证是三个阶段中变化最多也最复杂的。我们先来看看理想中的验证是什么样的?我们的讨论针对情况相对棘手的web应用程序。首先如前所述,校验在浏览器端和服务器端都发生,前者给用户即时友好的提示,后者进行“真正的”校验以为业务逻辑服务。做校验的API既可以函数的方式(如validateRequired()、validateLength()、validateRange()),也可以类型的方式呈现(如RequiredValidator、LengthValidator、RangeValidator)。未能通过校验时,提示信息可选择显示在要校验的栏位旁边,或者集中显示在表单的某个位置。对栏位少的表单,可以从第一个栏位获得焦点后,依次在每个栏位失去焦点时校验,甚至在用户输入时对每键入一个字符后的值做即时的校验,比如检查密码是否符合规则和强度;对栏位较多的多列的复杂表单,可以在用户输入完成后点击提交按钮时一次性校验,之后再对未通过的栏位做类似的即时校验。既可在设计时为栏位设置校验,也可在运行时动态进行校验。
最后一点需要特别说明。大部分情况下,表单上哪些栏位需要什么校验,在设计时就可以确定。但是在某些情况下,在程序运行时再设置要校验的栏位可以带来我们想要的灵活性。比如在一个通用的校验系统里,我们将可以把要校验的栏位写在某种形式的可配置的参数里,这样当校验不同的表单时,我们只需要修改参数。在下一篇文章里,笔者将演示XPages环境下对表单上的栏位进行动态的非空校验。