混合式HTML(二)

Hybridizing HTML

混合式HTML(二)

November 8, 2007

 

创建窗体

CSS/JavaScript/AJAX窗体上我有不少负面经历,其中包含使用困难的时间选择器,这些也在旅游网站上出现过。还是让我们创建一个简单窗体,它可以找到你的启程和返程日期、姓名和email地址。

在输入数据提交至服务器之前,让我们先在客户端上加入对输入数据的有效性检查。因为这个功能会避免页面之间来回跳转,各类网站都用AJAXJavaScript实现这个功能,但很难甚至看不到好效果。而Flex提供的内置Valuator对象可为你轻松完成这件事。对于特殊情况你也可以很方便地创建自己的Valuator子类型。我们总体上可以保证所有的输入数据在窗体被提交之前都是正确的,但用JavaScript实现的话就得花费些力气才能保证其可靠性。

这个窗体使用MXML窗体组件进行布局。该组件拥有FormItems,每一个FormItems都包含一个标签和一些其他类型的组件。你需要注意的是标签和组件都被巧妙地组织到一起了而这是你要用HTMLtable编写更多代码才能完成的。

这篇文章中我大概介绍了一下MXML。下面是代码,在后面我会加以解释。所有<Form>标签里面的是窗体标签和组件,之后是有效性检查并提交至服务器。这里大多数代码是由FlexBuilder通过上下文补全(context-completion)自动生成的:

 

<?xml version="1.0" encoding="utf-8"?>
   
   
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   
   
    xmlns:validators="validators.*" applicationComplete="validator()">
   
   
    <mx:Style> global { font-size: 15;    } </mx:Style>
   
   
    <mx:Form>
   
   
        <mx:FormItem label="Departure Date" required="true">
   
   
            <mx:DateField id="departureDate" change="departureChanged()"
   
   
                selectedDate="{new Date()}" />
   
   
        </mx:FormItem>
   
   
        <mx:FormItem label="Return Date" required="true">
   
   
            <mx:DateField id="returnDate" change="validator()"
   
   
                selectedDate="{new Date(new Date().time + validators.ReturnDateValidator.ONEDAY)}" />
   
   
        </mx:FormItem>
   
   
        <mx:FormItem label="Traveler Last Name" required="true">
   
   
            <mx:TextInput id="lastName" change="validator()"/>
   
   
        </mx:FormItem>
   
   
        <mx:FormItem label="Traveler First Name" required="true">
   
   
            <mx:TextInput id="firstName" change="validator()"/>
   
   
        </mx:FormItem>
   
   
        <mx:FormItem label="Traveler's Email" required="true">
   
   
            <mx:TextInput id="email" change="validator()"/>
   
   
        </mx:FormItem>
   
   
        <mx:FormItem>
   
   
            <mx:Button id="submitButton" label="Submit" click="submit()"/>
   
   
        </mx:FormItem>
   
   
    </mx:Form>
   
   
    <mx:Validator id="requireLastName" required="true" source="{lastName}" property="text" />
   
   
    <mx:Validator id="requireFirstName" required="true" source="{firstName}" property="text" />
   
   
    <mx:EmailValidator id="validateEmail" required="true" source="{email}"  property="text" />
   
   
    <validators:ReturnDateValidator id="validateReturnDate"
   
   
        source="{returnDate}" property="selectedDate" departureDate="{departureDate}"/>
   
   
    <mx:Script> <![CDATA[
   
   
    private function departureChanged():void {
   
   
        const ONEDAY:int = validators.ReturnDateValidator.ONEDAY;
   
   
        if(returnDate.selectedDate.time >= departureDate.selectedDate.time + ONEDAY) return;
   
   
        returnDate.selectedDate = new Date(departureDate.selectedDate.getTime() + ONEDAY);
   
   
        validator();
   
   
    }
   
   
    private function validator():void {
   
   
        submitButton.enabled = Validator.validateAll(
   
   
            [requireLastName, requireFirstName, validateEmail, validateReturnDate]).length == 0;
   
   
    }
   
   
    private function submit():void {
   
   
        var args:URLVariables = new URLVariables();
   
   
        args.FirstName = firstName.text;
   
   
        args.LastName = lastName.text;
   
   
        args.Email = email.text;
   
   
        args.DepartureDate = departureDate.selectedDate.toDateString();
   
   
        args.ReturnDate = returnDate.selectedDate.toDateString();
   
   
        var url:URLRequest = new URLRequest("http://www.mindviewinc.com/demos/flex/trip.php");
   
   
        url.method = "POST";
   
   
        url.data = args;
   
   
        navigateToURL(url,"_blank");
   
   
    }
   
   
    ]]> </mx:Script>
   
   
</mx:Application>
   
   

 

全局性地调整风格也很容易。在这里我使用<Style>设置窗体中所有字体大小为15

可以看到,每一个FormItem中的required设置为true。这个标志可能会被误解,它的作用并不是要求用户须填满所有栏。而是在组件旁显示一个红色星号以告诉用户该栏是必须填写的。为了实现必填栏,后面你会看到我们是如何利用有效性检查做到的。

DateField是一个内置Flex组件,用于在选择日期时弹出日历项。它和常见的AJAX组件很相近,但前者可以在所有平台和配置下运行。在departureDate中,使用一个绑定表达式(大括号)将selectedDate设置为当前日期。returnDate被初始化为当前日期加1

FormItem剩余部分就是TextInput和最后的一个提交按钮。只有当输入数据正确时按钮才会有效,点击按钮数据就会提交至服务器。

注意,窗体中每一个输入组件设置的change属性是一个方法,该方法执行有效性检查。

 

窗体数据的有效性检查

<Form>之后就见到了四个Validator组件:其中的三个来自标准Flex库,还有一个是我自己写的。前两个保证了lastNamefirstName TextInputs不为空,第三个要求email TextInput内容必须遵循email地址格式(这是一个成熟的标准Flex组件,你没必要自己重新造一个)。而自定义的ReturnDateValidator确保了返程日期大于启程日期。

每一个Validator都要知道在哪能找到要检查的数据,通过设置它的source(待检查栏,使用绑定表达式)和property栏(该组件内部所测试的属性)来完成。ReturnDateValidator也得要知道待比较的departureDate

检查整个窗体有效性最简便的方法是调用static validateAll()。你可以在<Script>里面的validator()方法中找到它。validator()包含一个Validator对象的数组并测试其中每一个元素,然后返回一个results数组。由于任意results都表示一种有效性检查失败,所以如果数组length不为0,那么窗体有效性检查即为失败(提交按钮也会无效)。

下面这个是一个更为复杂一点的窗体。我们不只想检查输入有效性,还想限制一下日期范围,使得旅行返程至少是在启程的一天之后。如果用户选择了启程日期,返程日期应自动初始化为启程的第二天,这样看起来才更合理一些。这也是departureDate DateField调用departureChanged()的原因(而不会像上面的validator()那样做)。在departureChanged()中返程日期被强制设置为启程的一天之后之后它同样也调用了validator()

然而,如果用户选择返程日期,我们就无法假定启程日期。能做的只是告诉用户为什么该栏是无效的。这也是ReturnDateValidator的入口。

 

一个自定义的Validator

自定义Validator是另一种自定义组件,我在这里曾介绍过。当子类化时,必须覆盖的方法只有doValidation()。在有效性检查期间,ValidatorMXML声明中的sourceproperty作为参数传递给该方法。

 

package validators {
   
   
    import mx.controls.DateField;
   
   
    import mx.validators.ValidationResult;
   
   
    import mx.validators.Validator;
   
   

   
   
    
     
   
   
    public class ReturnDateValidator extends Validator {
   
   
        private var departure:DateField;
   
   
        public static const ONEDAY:int = 1000 * 60 * 60 * 24; // In milliseconds
   
   
        public function set departureDate(depart:DateField):void {
   
   
            departure = depart;
   
   
        }
   
   
        protected override function doValidation(value:Object):Array {
   
   
            var results:Array = super.doValidation(value);
   
   
            if(results.length > 0)
   
   
                return results;
   
   
            if(value.time < departure.selectedDate.time + ONEDAY)
   
   
                 results.push(new ValidationResult(true, null, "returnBeforeDeparture",
   
   
                     "Return date must happen after departure date"));
   
   
            return results;
   
   
        }
   
   
    }
   
   
}
   
   

 

Validator和一般的Validator不太一样,因为它使用了两个UI组件,而大多数只用了一个。为了连接至第二个组件(另一个DateField),我引入了名为departureDate的属性,并通过set方法将其保存在departure中。

我在设计Validator的时候遇到了点麻烦。我首先覆盖了validator()方法,运行没问题,validateAll()也是。但是文档中提到说必须覆盖doValidation()而不应该覆盖validator()。可当我这么做的时候却出现一个运行时错误,因为创建对象Validator必须设定sourceproperty,然后value参数通过合并sourceproperty由框架传递给doValidation()。我原以为只需用到source,但为了满足框架两个确实都要用。这是定义你自己的Validator的必要条件。这样设计的原因对我来说虽不是显而易见的,但它们确实能保证其运行起来。

ReturnDateValidator中,作为参数传递的value来自于与propertyselectedDate中的)合并的sourceDateField中的)。所以我们用value.time保存returnDate1970年之后的秒数,用departure.selectedDate.time保存departureDate的秒数。这样的设计可能起初又把你搞懵了,但应该还算吃得消。

doValidation()中,通常会首先调用基类的doValidation()方法,它返回一个数组。如果数组为空,成功完成基类的有效性检查。在这种情况下,基类调用可能就是多余的了,因为它仅保证对应栏不空,况且我认为在DateField上总是会有一些数据的,因而也总能调用成功。总之,这些代码给出了在自定义Validator下一个普通窗体的创建过程。

如果返程日期不是被设置为至少在启程日期的一天之后,就会出现问题。因此我们向results数组增加了一个ValidationResult。它可以在检查失败的时候给出提示,但更重要的是它可以返回一个消息来告诉用户问题出在哪里。当用户移动鼠标经过输入框时就会显示该消息(如是无效就会出现一个红框)。

这就是Validator的基本思想。当再遇到特殊窗体约束时,创建一个自定义窗体就不是难事了。(未完待续)

 

(原文链接网址:http://www.artima.com/weblogs/viewpost.jsp?thread=213902

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值