构建 Flex 自动验证框架

IBMdW 发布于 2011/04/12 08:20
阅读 805
收藏 1

Flex 验证器简介

用户输入的数据有时候并不满足应用程序的规定,在 Flex 中验证器用来保证 UI 中的数据满足某种规则。比如,你可以用一个数字验证器来保证输入的只能是在某个范围内的数字。在典型的客户端 - 服务器环境中,数据的验证发生在服务器端。Flex 验证器的好处是你可以在客户端就进行一些基本的数据验证,而不用来回的往服务器端发送不合理的数据。

Flex 提供了一些基本的验证器供用户使用。你可以扩展它们来实现更复杂的验证,甚至重新创建自定义规则的验证器。

验证器需要与目标源关联,而目标源包含一个需要验证的属性。目标源可以是一个 UI 组件,也可以是一个数据模型。当验证器与一个目标源的属性关联之后,每次该属性值的改变都会触发验证器的验证。验证结果通过 validinvalid事件派发。valid事件表明验证通过,invalid事件表明验证失败。

虽然 Flex 验证器可以自动地工作,但它没有明确的可绑定的属性来表示验证结果,而仅仅是派发验证结果事件。另外,每个验证器独立的工作,如果有很多个验证器,怎么来 统一管理,Flex 也没有相应的类提供支持。所以本文提出构建一个自动验证框架来统一管理多个验证器,在实践中证明是很有效的数据验证工具。

问题的提出:多个输入数据页面

在 web 开发中,表格非常的普遍。在一个表格中存在着多个输入框,需要验证用户输入。全部验证通过才能进行下一个步骤。用 Flex 来写一个表格非常的简单,只需要调用现成的 Form组件。但是要同时对所有的输入框进行验证,则需要大量的逻辑代码。下面我们来模拟一个简单的添加数据库连接表格,以此说明构建一个验证器框架的重要性。


图 1. 添加数据库连接表格
图 1. 添加数据库连接表格

此表格包含 4 个输入框,2 个按钮。全部输入合法之后,按钮才能被激活。它的 MXML 代码如下。


清单 1. 表格代码
				 
 <?xml version=”1.0” encoding=”utf-8”?> 
 <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”vertical” 
 xmlns:validator=”com.ibm.rdf.validator.*”> 
 <mx:Panel> 
 <mx:Form> 
 <mx:FormHeading label=”Add Connection”/> 
 <mx:FormItem label=”Connection Name”> 
 <mx:TextInput id=”connName”/> 
 </mx:FormItem> 
 <mx:FormItem label=”Database Name”> 
 <mx:TextInput id=”dbName”/> 
 </mx:FormItem> 
 <mx:FormItem label=”Host Name”> 
 <mx:TextInput id=”4ostname”/> 
 </mx:FormItem> 
 <mx:FormItem label=”Port Number”> 
 <mx:TextInput id=”port”/> 
 </mx:FormItem> 
 </mx:Form> 
 <mx:ControlBar horizontalAlign=”right”> 
 <mx:Button id=”okBtn” label=”OK”/> 
 <mx:Button id=”cancelBtn” label=”Cancel”/> 
 </mx:ControlBar> 
 </mx:Panel> 
 </mx:Application> 

  • 没有验证框架代码示例
    1. 为每个输入框关联一个验证器,并用一个布尔变量来保存它的验证结果。

      清单 2. 添加验证器代码
      				 
       Private var connNameValid:Boolean = false; 
       private var dbNameValid:Boolean = false; 
       private var hostValid:Boolean = false; 
       private var portValid:Boolean = false; 
      
       <mx:StringValidator source=”{connName}” property=”text”/> 
       <mx:StringValidator source=”{dbName}” property=”text”/> 
       <validator:RDFHostValidator source=”{4ostname}” property=”text”/> 
       <mx:NumberValidator source=”{port}” property=”text” 
       allowNegative=”false” minValue=”1” maxValue=”65535” domain=”int”/> 
      
    2. 监听每个验证器的 validinvalid事件,根据验证结果改变其对应的布尔变量的值,并根据所有布尔变量的值来设置按钮状态。

      清单 3. 逻辑代码
      				 
       <mx:StringValidator id=”connNameValidator” source=”{connName}” 
       property=”text” valid=”setValid(event)” invalid=”setValid(event)”/> 
       <mx:StringValidator id=”dbNameValidator” source=”{dbName}” 
       property=”text”  valid=”setValid(event)” invalid=”setValid(event)”/> 
       <validator:RDFHostValidator id=”hostValidator” source=”{4ostname}” 
       property=”text”  valid=”setValid(event)” invalid=”setValid(event)”/> 
       <mx:NumberValidator id=”portValidator” source=”{port}” 
       property=”text” allowNegative=”false” minValue=”1” maxValue=”65535” 
       domain=”int” valid=”setValid(event)” invalid=”setValid(event)”/>  
       private function enableButtons():void 
      			 { 
       if (connNameValid && dbNameValid && hostValid && portValid) 
      				 { 
       okBtn.enabled = true;} 
       else 
      				 { 
       okBtn.enabled = false; 
      				 } 
      			 } 
       private function setValid(event:ValidationResultEvent):void 
       { 
       if (event.currentTarget == connNameValidator) 
       { 
       connNameValid = changeValid(event); 
       } 
       else if (event.currentTarget == dbNameValidator) 
       { 
       dbNameValid = changeValid(event); 
       } 
       else if (event.currentTarget == portValidator) 
       { 
       portValid = changeValid(event); 
       } 
       else 
       { 
       hostValid = changeValid(event); 
       } 
       enableButtons(); 
       } 
      
       private function changeValid(event:ValidationResultEvent):Boolean 
       { 
       if (event.type == ValidationResultEvent.VALID) 
       return true; 
       else 
       return false; 
      			 } 
      
    3. 页面创建完成时设置按钮的初始状态。

      清单 4. 页面创建代码
      				 
       <mx:Application xmlns:mx=”http://www.adobe.com/2006/mxml” layout=”vertical” 
       xmlns:validator=”com.ibm.rdf.validator.*” creationComplete=”enableButtons()”> 
      
  • 使用验证框架代码示例

只需将所有验证器置放于验证框架内,再用数据绑定把按钮的 enabled 属性与验证器的验证结果绑定起来,按钮的状态就会根据验证结果自动改变。


清单 5. 使用验证框架代码
				 
 <validator:RDFValidators id=”validators”> 
 <mx:StringValidator source=”{connName}” property=”text”/> 
 <mx:StringValidator source=”{dbName}” property=”text”/> 
 <validator:RDFHostValidator source=”{4ostname}” property=”text”/> 
 <mx:NumberValidator source=”{port}” property=”text” 
 allowNegative=”false” minValue=”1” maxValue=”65535” domain=”int”/> 
 </validator:RDFValidators> 
 <mx:Button label=”OK” enabled=”{validators.valid}”/> 
 <mx:Button label=”Cancel”/> 

从这个简单的示例可以看出验证器框架减少了大量的逻辑代码,从而大大提高了编程效率。特别是多个验证器同时工作的情况下非常实用。

构建自动验证框架

本章中会介绍构建自动框架的具体实现。

定义接口

  1. 自动验证框架最重要的接口就是 valid 属性。它是一个可绑定的布尔值,会随着其包含的所有验证器的验证结果自动变化。注意,它是一个只读属性。因为它的值只会忠实于所包含的验证器,所以不能被人为的更改。

在 Flex 中,只有可读写的属性才能是可绑定的,如何使只读属性可绑定将在下面的设计核心一节中介绍。

  1. 一个名为 validatorArray 的数组来放置验证器。
  2. 适时的事件派发。除了转发每个验证器的 valid 和 invalid 事件之外,它还在自己的 valid 值改变的时候派发 validChanged 事件。用户捕获这个事件之后就可以调用相应的逻辑代码。

继承 EventDispatcher 实现 IMXMLObject 接口

验证框架类没有继承 Flex 的 Validator 类,因为它本身并不是一个验证器,而是管理验证器的框架。它直接继承 EventDispatcher 以实现派发事件的功能。在 Flex 中,所有不包含 UI 的组件都必须继承 IMXMLObject,才可以在 MXML 中使用。所以它实现了 IMXMLObject 接口,该接口只有一个 initialized()方法。


清单 6. 实现 IMXMLObject 接口代码
				 
 /** 
         * @private 
         * 实现 IMXMLObject 
         */ 
        public function initialized(document:Object, id:String):void 
        { 
            this.id = id; 
            this.document = document;           
        } 

验证框架设计核心

Flex 验证器本身是采用观察者模式,当它关联的目标源的输入值改变时,触发验证,它派发验证结果事件。验证框架也沿袭观察者模式,对其管理的验证器数组中的每个 验证器进行监听。根据本次接收到的事件和保存的其它验证结果事件,计算当前综合的验证结果,派发 valid 和 invalid 事件。如果验证结果与上次结果不同,还派发 validChanged 事件。


图 2. 验证框架构造示意图
图 2. 验证框架构造示意图
  • 强制验证的验证器数目 requiredNum

验证器的 required 属性决定此验证器是否强制验证。如果 required 值为真,那么此验证器关联的源目标值不能为空,否则验证失败。反之,源目标值为空的时候也能通过验证。requiredNum 用于保存 required 值为真的验证器的数目。既然验证框架是被动的观察者,它必须知道某些被观察者没有派发任何事件的情况下,表示验证是通过的。因此,验证框架也必须计算 required 为真的验证器的数量,当收到的事件数目小于它时,验证失败;反之,检查是否每个 required 值为真的验证器的结果。

  • 验证器数组 validatorArray

setter/getter存储元访问可读写的 validatorArray属性。这样,在 setter中就可以方便的加入逻辑代码。在这里我们需要对数组中的每个验证器添加 validinvalid事件监听器。同时也需要计算 requiredNum的值以便后续使用。

用标签 [DefaultProperty('validatorArray')],把验证器数组设为验证器框架类的默认值。这样用户就可以直接在 MXML 里直接把验证器放置在框架类的标签中,不需要另写 < validator:validatorArray ></ validator: validatorArray >标签来放验证器。


清单 7. 写 validatorArray 代码
				 
 public function set validatorArray(validators:Array):void 
        { 
            this._validatorArray = validators; 
            var i:int = 0; 
            requiredNum = 0;            
            for each (var v:EventDispatcher in validators) 
            { 
                // 计算 requiredNum 值
                ……
              
                v.addEventListener(ValidationResultEvent.INVALID, resultHandler); 
                v.addEventListener(ValidationResultEvent.VALID, resultHandler);          
            } 
            if (requiredNum == 0) 
            { 
            _required = false; 
            _valid = true; 
            } 
        } 

  • 验证结果数组 results

每次监听到 validinvalid事件就把当次的验证结果放入验证结果数组中。验证结果数组提供了计算验证框架综合结果的依据。

  • 计算已经验证过的验证器数目

requiredValidatedNum保存收到的 required值为真的验证器数目。如果已经验证过的验证器数目 requiredValidatedNum小于强制验证的验证器数目 requiredNum,那么验证结果肯定是失败的。当 requiredValidatedNum等于 requiredNum时,我们需要浏览验证结果数组中的每个结果来计算验证框架的结果。

  • 验证结果 ValidationResultEvent提供的信息

Flex 验证结果 ValidationResultEvent事件的 currentTarget属性包含目标实例,即是哪个源目标触发了验证事件。我们通过比较监听到的事件和验证结果数组 results中保存的事件的触发源,就可以知道当前事件是新事件还是旧事件值的改变。如果是新事件,我们需要把它保存在验证结果数组中;如果是已经保存过的事件,我们需要更新保存的值。

验证结果 ValidationResultEvent事件的 type属性指出该次验证是成功(valid)还是失败(invalid)。我们每次监听到事件,都根据所有保存事件的验证值重新计算验证框架的验证结果。


清单 8. 利用验证结果事件代码
				 
            if (results == null || results.length != validatorArray.length) 
            { 
                var alreadyIn:Boolean = false; 
                for (var i:int = 0; i < results.length; i++) 
                {                                   
                    // 比较发出事件的目标源
                    if (results[i].currentTarget == event.currentTarget) 
                    {                                       
                        results[i] = event; 
                        alreadyIn = true; 
                    }                   
                } 
                if (!alreadyIn) 
                { 
                    results.push(event); 
                }                               
            } 

  • 实现 valid属性可绑定

属性可绑定表示该属性可以作为数据绑定的源。数据绑定就是将数据从源位置拷贝到目标位置,从而实现目标位置的数据保持与源位置的数据一致。bindable对于 valid属性非常重要。它必须是 bindable才能绑定到 UI 组件上,让用户在任何时候看到的都是最新的值。

valid属性只有 getter,是只读属性。一般来说,属性的 getter绑定到 setter上可以实现可绑定(bindable)。在没有 setter的情况下,我们把 valid属性绑定到 validChanged事件。每当验证框架的 valid值改变时都会触发 validChanged 事件。也即是每当写 valid值时,都会有 validChanged事件派发出来。所以我们把 valid属性绑定到 validChanged事件上就可以实现 valid 属性的 bindable了,可以用标签来实现 [Bindable("validChanged")]


清单 9. 实现可绑定的 valid 属性代码
				 
 [Bindable("validChanged")] 
        /** 
         * 验证框架的验证结果 
         */ 
        public function get valid():Boolean 
        { 
            return this._valid; 
        } 

  • 计算 valid

验证框架的价值就在于它把其包含的验证器进行统一的管理,提供一个整体的结果,而这个结果用 valid值来表示。每次监听到验证器的验证事件时,都需要重新计算 valid值,其步骤如下:

  •  
    1. 根据当前收到的验证结果与 results中的所有结果进行比较,如果是新结果就直接放进 results数组,如果是新数据就更新它。
    2. 计算已经验证过的验证器数目 requiredValidatedNum和强制验证的验证器数目 requiredNum
    3. 如果 requiredValidatedNum小于 requiredNum,说明还有不允许为空的数据源没有数据,验证结果是失败的。
    4. 如果 requiredValidatedNum等于 requiredNum,遍历 results数组并把当前收到的验证结果更新到 results数组中,对所有验证结果进行逻辑运算计算出总体结果。
    5. 如果刚计算出的总体结果不同于当前 valid值,更新 valid值,并派发 validChanged事件。
    6. 如果 valid值为真,派发 valid事件。
    7. 如果 valid值为假,派发 invalid事件。

清单 10. 计算 valid 值代码
				 
 protected function resultHandler (event:ValidationResultEvent):void 
        { 
            // 还没有验证过验证框架内所有的验证器,需要对验证结果进行整理
            if (results == null || results.length != validatorArray.length) 
            { 
                var alreadyIn:Boolean = false; 
                var r:ValidationResultEvent; 
                for (var i:int = 0; i < results.length; i++) 
                {                                   
                    // 派发事件的目标源
                    if (results[i].currentTarget == event.currentTarget) 
                    {                                       
                        results[i] = event; 
                        alreadyIn = true; 
                    }                   
                } 
                if (!alreadyIn) 
                { 
                    results.push(event); 
                }                               
            } 
            
            // 计算 requiredValidatedNum 值和 requiredNum 值
……
 // 强制验证的验证器都验证过之后可以计算总体结果
            if (requiredValidatedNum == requiredNum) 
            { 
                var wholeValid:Boolean = true; 
                for (var j:int = 0; j < results.length; j++) 
                {   
                    ……
                    var isValid:Boolean = results[j].type == ValidationResultEvent.VALID; 
                    wholeValid = wholeValid && isValid; 
                } 
                if (valid != wholeValid) 
                {                                       
                    this._valid = wholeValid; 
                    dispatchEvent(new Event(“validChanged”));
                } 
                if (valid) 
                { 
                    dispatchEvent(new ValidationResultEvent(“valid”)); 
                }               
                if (!valid) 
                { 
                    dispatchEvent(new ValidationResultEvent(“invalid”)); 
                }   
            } 
        } 

进阶设计

前章中阐述了自动验证框架的基本设计,为了适应更复杂的用户需求,我们可以在此基础上使它更加完善。

逻辑计算

一般来说,我们需要验证框架包括的所有验证器都验证通过时,总体 valid结果才为真。但在某些情况下,只要某一个验证器通过验证,就可以认为整个验证通过了。从逻辑上看,一般我们考虑的是与(AND)运算,但或(OR)运算也有其实用价值。

为了支持用户选用与或运算,我们需要提供一个变量 logic来保存逻辑运算的方式。另外还提供两个静态常量 ANDOR来表示与或运算。logic缺省值为 AND

前章阐述的实现对于与或运算都适用。只是在计算 valid值时不需要第 2,3 步,第 4 步选用不同的逻辑运算方式。除此之外,注意到 Flex 的验证器会自动对验证失败的目标源组件添加 errorString。导致目标源组件呈现红色的验证失败提示框。所以我们需要额外地清除这些 errorString,以免误导用户。


清单11. 清除errorString代码
protected function cleanErrorMessages():void
        {
             for each (var rr:ValidationResultEvent in results)
            {
                if (rr.type == ValidationResultEvent.INVALID)
                {
                     if (rr.currentTarget is Validator)
                     {
                        //将rr的results属性置空
                        //清空目标源的控件的errorString属性
                          
                     }
                     else if (rr.currentTarget is RDFValidators)
                     {
                        (rr.currentTarget as RDFValidators).cleanErrorMessages();
                     }                              
                 }
            } 
        }			 
			 


图 3. 出现红色的验证失败提示框的表格
图 3. 出现红色的验证失败提示框的表格

嵌套框架

前章我们把验证框架设计成可以管理验证器的容器,我们通过监听验证器的 validinvalid事件来触发容器的管理。从广义上来说,只要能够派发 validinvalid事件的组件都可以被管理。那么我们的验证框架也可以被管理,这样我们就能实现嵌套框架,组合出更多的逻辑运算。

在计算验证框架的 valid值时,我们用到了每个验证器的 required属性。required属性表明该验证器的目标源数据是否可以为空,它对于验证框架来说也是有意义的。所以我们可以赋予验证框架一个只读的 required属性,它根据自身管理的验证器来决定自己的 required属性值。只要有一个验证器是强制验证的,管理它的验证框架 required值就是真。否则相反。


图 4. 嵌套框架示意图
图 4. 嵌套框架示意图

下面通过一个实例来展示:


清单 12. 嵌套框架的示例代码
				 
 <mx:Panel> 
 <mx:Form> 
 <mx:FormHeading label="Input your information"/> 
 <mx:Label text="Input your either your name or your ID"/> 
 <mx:HBox> 
 <mx:FormItem label="Name"> 
 <mx:TextInput id="myname"/> 
 </mx:FormItem> 
 <mx:FormItem label="ID"> 
 <mx:TextInput id="myid"/> 
 </mx:FormItem> 
 </mx:HBox> 
 <mx:FormItem label="Email" required="true"> 
 <mx:TextInput id="myemail"/> 
 </mx:FormItem> 
 </mx:Form> 
 <mx:ControlBar horizontalAlign="right"> 
 <mx:Button label="OK" enabled="{validators2.valid}"/> 
 <mx:Button label="Cancel"/> 
 </mx:ControlBar> 
 </mx:Panel> 
 <validator:RDFValidators id="validators2"> 
 <validator:RDFValidators logic="or"> 
 <mx:StringValidator id="nameValidator" source="{myname}" 
 property="text" triggerEvent="change"/> 
 <mx:StringValidator id="idValidator" source="{myid}" 
 property="text" triggerEvent="change"/> 
 </validator:RDFValidators> 
 <mx:EmailValidator id="emailValidator" source="{myemail}" 
 property="text" triggerEvent="change"/> 
 </validator:RDFValidators> 


图 5. 示例代码显示界面
图 5. 示例代码显示界面

结束语

本文阐述了构建 Flex 自动验证框架以实现对多个验证器的统一管理 , 并通过示例演示了自动验证框架在代码中的使用。希望通过自动验证框架的使用,可以帮助您更高效地开发 Flex 应用。

加载中
返回顶部
顶部