//////////////////////////////////////////////////////////////////////////////// // // Licensed to the Apache Software Foundation (ASF) under one or more // contributor license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright ownership. // The ASF licenses this file to You under the Apache License, Version 2.0 // (the "License"); you may not use this file except in compliance with // the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// package mx.validators { import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IEventDispatcher; import mx.binding.BindingManager; import mx.core.IMXMLObject; import mx.events.FlexEvent; import mx.events.ValidationResultEvent; import mx.managers.ISystemManager; import mx.managers.SystemManager; import mx.resources.IResourceManager; import mx.resources.ResourceManager; import mx.validators.IValidator; //-------------------------------------- // Events //-------------------------------------- /** * Dispatched when validation succeeds. * * @eventType mx.events.ValidationResultEvent.VALID * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="valid", type="mx.events.ValidationResultEvent")] /** * Dispatched when validation fails. * * @eventType mx.events.ValidationResultEvent.INVALID * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ [Event(name="invalid", type="mx.events.ValidationResultEvent")] //-------------------------------------- // Other metadata //-------------------------------------- [ResourceBundle("core")] [ResourceBundle("validators")] /** * The Validator class is the base class for all Flex validators. * This class implements the ability for a validator to make a field * required, which means that the user must enter a value in the field * or the validation fails. * * @mxml * *

The Validator class defines the following tag attributes, * which all of its subclasses inherit:

* *
 *  <mx:Validator 
 *    enabled="true|false" 
 *    listener="Value of the source property" 
 *    property="No default" 
 *    required="true|false" 
 *    requiredFieldError="This field is required." 
 *    source="No default" 
 *    trigger="Value of the source property" 
 *    triggerEvent="valueCommit" 
 *  />
 *  
* * @see mx.events.ValidationResultEvent * @see mx.validators.ValidationResult * @see mx.validators.RegExpValidationResult * * @includeExample examples/SimpleValidatorExample.mxml * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public class Validator extends EventDispatcher implements IMXMLObject,IValidator { include "../core/Version.as"; //-------------------------------------------------------------------------- // // Class constants // //-------------------------------------------------------------------------- /** * A string containing the upper- and lower-case letters * of the Roman alphabet ("A" through "Z" and "a" through "z"). * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected static const ROMAN_LETTERS:String = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; /** * A String containing the decimal digits 0 through 9. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected static const DECIMAL_DIGITS:String = "0123456789"; //-------------------------------------------------------------------------- // // Class methods // //-------------------------------------------------------------------------- /** * Invokes all the validators in the validators Array. * Returns an Array containing one ValidationResultEvent object * for each validator that failed. * Returns an empty Array if all validators succeed. * * @param validators An Array containing the Validator objects to execute. * * @return Array of ValidationResultEvent objects, where the Array * contains one ValidationResultEvent object for each validator * that failed. * The Array is empty if all validators succeed. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public static function validateAll(validators:Array):Array { var result:Array = []; var n:int = validators.length; for (var i:int = 0; i < n; i++) { var v:IValidator = validators[i] as IValidator; if (v && v.enabled) { var resultEvent:ValidationResultEvent = v.validate(); if (resultEvent.type != ValidationResultEvent.VALID) result.push(resultEvent); } } return result; } /** * @private */ private static function findObjectFromString(doc:Object, value:String):Object { var obj:Object = doc; var parts:Array = value.split("."); var n:int = parts.length; for (var i:int = 0; i < n; i++) { try { obj = obj[parts[i]]; // There's no guarantee that the objects have // already been created when this function fires; // for example, in the deferred instantiation case, // this function fires before the object for validation // has been created. if (obj == null) { //return true; } } catch(error:Error) { if ((error is TypeError) && (error.message.indexOf("null has no properties") != -1)) { var resourceManager:IResourceManager = ResourceManager.getInstance(); var message:String = resourceManager.getString( "validators", "fieldNotFound", [ value ]); throw new Error(message); } else { throw error; } } } return obj; } /** * @private */ private static function trimString(str:String):String { var startIndex:int = 0; while (str.indexOf(' ', startIndex) == startIndex) { ++startIndex; } var endIndex:int = str.length - 1; while (str.lastIndexOf(' ', endIndex) == endIndex) { --endIndex; } return endIndex >= startIndex ? str.slice(startIndex, endIndex + 1) : ""; } //-------------------------------------------------------------------------- // // Constructor // //-------------------------------------------------------------------------- /** * Constructor. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function Validator() { super(); // Register as a weak listener for "change" events from ResourceManager. // If Validators registered as a strong listener, // they wouldn't get garbage collected. resourceManager.addEventListener( Event.CHANGE, resourceManager_changeHandler, false, 0, true); resourcesChanged(); } //-------------------------------------------------------------------------- // // Variables // //-------------------------------------------------------------------------- /** * @private */ private var document:Object; //-------------------------------------------------------------------------- // // Properties // //-------------------------------------------------------------------------- //---------------------------------- // actualTrigger //---------------------------------- /** * Contains the trigger object, if any, * or the source object. Used to determine the listener object * for the triggerEvent. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function get actualTrigger():IEventDispatcher { if (_trigger) return _trigger; else if (_source) return _source as IEventDispatcher; return null; } //---------------------------------- // actualListeners //---------------------------------- /** * Contains an Array of listener objects, if any, * or the source object. Used to determine which object * to notify about the validation result. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function get actualListeners():Array { var result:Array = []; if (_listener) result.push(_listener); else if (_source) result.push(_source); return result; } //---------------------------------- // enabled //---------------------------------- /** * @private * Storage for the enabled property. */ private var _enabled:Boolean = true; [Inspectable(category="General", defaultValue="true")] /** * Setting this value to false will stop the validator * from performing validation. * When a validator is disabled, it dispatch no events, * and the validate() method returns null. * * @default true * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get enabled():Boolean { return _enabled; } /** * @private */ public function set enabled(value:Boolean):void { _enabled = value; } //---------------------------------- // listener //---------------------------------- /** * @private * Storage for the listener property. */ private var _listener:Object; [Inspectable(category="General")] /** * Specifies the validation listener. * *

If you do not specify a listener, * Flex uses the value of the source property. * After Flex determines the source component, * it changes the border color of the component, * displays an error message for a failure, * or hides any existing error message for a successful validation.

* * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ /* This behavior has been removed. *

If Flex does not find an appropriate listener, * validation errors propagate to the Application object, causing Flex * to display an Alert box containing the validation error message.

* *

Specifying this causes the validation error * to propagate to the Application object, * and displays an Alert box containing the validation error message.

* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get listener():Object { return _listener; } /** * @private */ public function set listener(value:Object):void { removeListenerHandler(); _listener = value; addListenerHandler(); } //---------------------------------- // property //---------------------------------- /** * @private * Storage for the property property. */ private var _property:String; [Inspectable(category="General")] /** * A String specifying the name of the property * of the source object that contains * the value to validate. * The property is optional, but if you specify source, * you should set a value for this property as well. * * @default null * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get property():String { return _property; } /** * @private */ public function set property(value:String):void { _property = value; } //---------------------------------- // required //---------------------------------- [Inspectable(category="General", defaultValue="true")] /** * If true, specifies that a missing or empty * value causes a validation error. * * @default true * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public var required:Boolean = true; //---------------------------------- // resourceManager //---------------------------------- /** * @private * Storage for the resourceManager property. */ private var _resourceManager:IResourceManager = ResourceManager.getInstance(); /** * @private * This metadata suppresses a trace() in PropertyWatcher: * "warning: unable to bind to property 'resourceManager' ..." */ [Bindable("unused")] /** * @copy mx.core.UIComponent#resourceManager * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function get resourceManager():IResourceManager { return _resourceManager; } //---------------------------------- // source //---------------------------------- /** * @private * Storage for the source property. */ private var _source:Object; [Inspectable(category="General")] /** * Specifies the object containing the property to validate. * Set this to an instance of a component or a data model. * You use data binding syntax in MXML to specify the value. * This property supports dot-delimited Strings * for specifying nested properties. * * If you specify a value to the source property, * then you should specify a value to the property * property as well. * The source property is optional. * * @default null * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get source():Object { return _source; } /** * @private */ public function set source(value:Object):void { if (_source == value) return; if (value is String) { var message:String = resourceManager.getString( "validators", "SAttribute", [ value ]); throw new Error(message); } // Remove the listener from the old source. removeTriggerHandler(); removeListenerHandler(); _source = value; // Listen for the trigger event on the new source. addTriggerHandler(); addListenerHandler(); } //---------------------------------- // subFields //---------------------------------- /** * An Array of Strings containing the names for the properties contained * in the value Object passed to the validate() method. * For example, CreditCardValidator sets this property to * [ "cardNumber", "cardType" ]. * This value means that the value Object * passed to the validate() method * should contain a cardNumber and a cardType property. * *

Subclasses of the Validator class that * validate multiple data fields (like CreditCardValidator and DateValidator) * should assign this property in their constructor.

* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected var subFields:Array = []; //---------------------------------- // trigger //---------------------------------- /** * @private * Storage for the trigger property. */ private var _trigger:IEventDispatcher; [Inspectable(category="General")] /** * Specifies the component generating the event that triggers the validator. * If omitted, by default Flex uses the value of the source property. * When the trigger dispatches a triggerEvent, * validation executes. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get trigger():IEventDispatcher { return _trigger; } /** * @private */ public function set trigger(value:IEventDispatcher):void { removeTriggerHandler(); _trigger = value; addTriggerHandler(); } //---------------------------------- // triggerEvent //---------------------------------- /** * @private * Storage for the triggerEvent property. */ private var _triggerEvent:String = FlexEvent.VALUE_COMMIT; [Inspectable(category="General")] /** * Specifies the event that triggers the validation. * If omitted, Flex uses the valueCommit event. * Flex dispatches the valueCommit event * when a user completes data entry into a control. * Usually this is when the user removes focus from the component, * or when a property value is changed programmatically. * If you want a validator to ignore all events, * set triggerEvent to the empty string (""). * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get triggerEvent():String { return _triggerEvent; } /** * @private */ public function set triggerEvent(value:String):void { if (_triggerEvent == value) return; removeTriggerHandler(); _triggerEvent = value; addTriggerHandler(); } //-------------------------------------------------------------------------- // // Properties: Errors // //-------------------------------------------------------------------------- //---------------------------------- // requiredFieldError //---------------------------------- /** * @private * Storage for the requiredFieldError property. */ private var _requiredFieldError:String; /** * @private */ private var requiredFieldErrorOverride:String; [Inspectable(category="Errors", defaultValue="null")] /** * Error message when a value is missing and the * required property is true. * * @default "This field is required." * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function get requiredFieldError():String { return _requiredFieldError; } /** * @private */ public function set requiredFieldError(value:String):void { requiredFieldErrorOverride = value; _requiredFieldError = value != null ? value : resourceManager.getString( "validators", "requiredFieldError"); } //-------------------------------------------------------------------------- // // Methods: IMXMLObject // //-------------------------------------------------------------------------- /** * Called automatically by the MXML compiler when the Validator * is created using an MXML tag. * * @param document The MXML document containing this Validator. * * @param id Ignored. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function initialized(document:Object, id:String):void { this.document = document; } //-------------------------------------------------------------------------- // // Methods // //-------------------------------------------------------------------------- /** * This method is called when a Validator is constructed, * and again whenever the ResourceManager dispatches * a "change" Event to indicate * that the localized resources have changed in some way. * *

This event will be dispatched when you set the ResourceManager's * localeChain property, when a resource module * has finished loading, and when you call the ResourceManager's * update() method.

* *

Subclasses should override this method and, after calling * super.resourcesChanged(), do whatever is appropriate * in response to having new resource values.

* * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function resourcesChanged():void { requiredFieldError = requiredFieldErrorOverride; } /** * @private */ private function addTriggerHandler():void { if (actualTrigger) actualTrigger.addEventListener(_triggerEvent, triggerHandler); } /** * @private */ private function removeTriggerHandler():void { if (actualTrigger) actualTrigger.removeEventListener(_triggerEvent, triggerHandler); } /** * Sets up all of the listeners for the * valid and invalid * events dispatched from the validator. Subclasses of the Validator class * should first call the removeListenerHandler() method, * and then the addListenerHandler() method if * the value of one of their listeners or sources changes. * The CreditCardValidator and DateValidator classes use this function internally. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function addListenerHandler():void { var actualListener:Object; var listeners:Array = actualListeners; var n:int = listeners.length; for (var i:int = 0; i < n; i++) { actualListener = listeners[i]; if (actualListener is IValidatorListener) { addEventListener(ValidationResultEvent.VALID, IValidatorListener(actualListener).validationResultHandler); addEventListener(ValidationResultEvent.INVALID, IValidatorListener(actualListener).validationResultHandler); } } } /** * Disconnects all of the listeners for the * valid and invalid * events dispatched from the validator. Subclasses should first call the * removeListenerHandler() method and then the * addListenerHandler method if * the value of one of their listeners or sources changes. * The CreditCardValidator and DateValidator classes use this function internally. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function removeListenerHandler():void { var actualListener:Object; var listeners:Array = actualListeners; var n:int = listeners.length; for (var i:int = 0; i < n; i++) { actualListener = listeners[i]; if (actualListener is IValidatorListener) { removeEventListener(ValidationResultEvent.VALID, IValidatorListener(actualListener).validationResultHandler); removeEventListener(ValidationResultEvent.INVALID, IValidatorListener(actualListener).validationResultHandler); } } } /** * Returns true if value is not null. * * @param value The value to test. * * @return true if value is not null. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function isRealValue(value:Object):Boolean { return (value != null); } /** * Performs validation and optionally notifies * the listeners of the result. * * @param value Optional value to validate. * If null, then the validator uses the source and * property properties to determine the value. * If you specify this argument, you should also set the * listener property to specify the target component * for any validation error messages. * * @param suppressEvents If false, then after validation, * the validator will notify the listener of the result. * * @return A ValidationResultEvent object * containing the results of the validation. * For a successful validation, the * ValidationResultEvent.results Array property is empty. * For a validation failure, the * ValidationResultEvent.results Array property contains * one ValidationResult object for each field checked by the validator, * both for fields that failed the validation and for fields that passed. * Examine the ValidationResult.isError * property to determine if the field passed or failed the validation. * * @see mx.events.ValidationResultEvent * @see mx.validators.ValidationResult * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ public function validate( value:Object = null, suppressEvents:Boolean = false):ValidationResultEvent { if (value == null) value = getValueFromSource(); if (isRealValue(value) || required) { // Validate if the target is required or our value is non-null. return processValidation(value, suppressEvents); } else { // We assume if value is null and required is false that // validation was successful. var resultEvent:ValidationResultEvent = new ValidationResultEvent(ValidationResultEvent.VALID); if (!suppressEvents && _enabled) { dispatchEvent(resultEvent); } return resultEvent; } } /** * Returns the Object to validate. Subclasses, such as the * CreditCardValidator and DateValidator classes, * override this method because they need * to access the values from multiple subfields. * * @return The Object to validate. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function getValueFromSource():Object { var message:String; if (_source && _property) { return _source[_property]; } else if (!_source && _property) { message = resourceManager.getString( "validators", "SAttributeMissing"); throw new Error(message); } else if (_source && !_property) { message = resourceManager.getString( "validators", "PAttributeMissing"); throw new Error(message); } return null; } /** * @private * Main internally used function to handle validation process. */ private function processValidation( value:Object, suppressEvents:Boolean):ValidationResultEvent { var resultEvent:ValidationResultEvent; if (_enabled) { var errorResults:Array = doValidation(value); resultEvent = handleResults(errorResults); } else { suppressEvents = true; // Don't send any events } if (!suppressEvents) { dispatchEvent(resultEvent); } return resultEvent; } /** * Executes the validation logic of this validator, * including validating that a missing or empty value * causes a validation error as defined by * the value of the required property. * *

If you create a subclass of a validator class, * you must override this method.

* * @param value Value to validate. * * @return For an invalid result, an Array of ValidationResult objects, * with one ValidationResult object for each field examined * by the validator that failed validation. * * @see mx.validators.ValidationResult * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function doValidation(value:Object):Array { var results:Array = []; var result:ValidationResult = validateRequired(value); if (result) results.push(result); return results; } /** * @private * Determines if an object is valid based on its * required property. * This is a convenience method for calling a validator from within a * custom validation function. */ private function validateRequired(value:Object):ValidationResult { if (required) { var val:String = (value != null) ? String(value) : ""; val = trimString(val); // If the string is empty and required is set to true // then throw a requiredFieldError. if (val.length == 0) { return new ValidationResult(true, "", "requiredField", requiredFieldError); } } return null; } /** * Returns a ValidationResultEvent from the Array of error results. * Internally, this function takes the results from the * doValidation() method and puts it into a ValidationResultEvent object. * Subclasses, such as the RegExpValidator class, * should override this function if they output a subclass * of ValidationResultEvent objects, such as the RegExpValidationResult objects, and * needs to populate the object with additional information. You never * call this function directly, and you should rarely override it. * * @param errorResults Array of ValidationResult objects. * * @return The ValidationResultEvent returned by the validate() method. * * @langversion 3.0 * @playerversion Flash 9 * @playerversion AIR 1.1 * @productversion Flex 3 */ protected function handleResults(errorResults:Array):ValidationResultEvent { var resultEvent:ValidationResultEvent; if (errorResults.length > 0) { resultEvent = new ValidationResultEvent(ValidationResultEvent.INVALID); resultEvent.results = errorResults; if (subFields.length > 0) { var errorFields:Object = {}; var subField:String; // Now we need to send valid results // for every subfield that didn't fail. var n:int; var i:int; n = errorResults.length; for (i = 0; i < n; i++) { subField = errorResults[i].subField; if (subField) { errorFields[subField] = true; } } n = subFields.length; for (i = 0; i < n; i++) { if (!errorFields[subFields[i]]) { errorResults.push(new ValidationResult(false,subFields[i])); } } } } else { resultEvent = new ValidationResultEvent(ValidationResultEvent.VALID); } return resultEvent; } //-------------------------------------------------------------------------- // // Event handlers // //-------------------------------------------------------------------------- /** * @private */ private function triggerHandler(event:Event):void { validate(); } /** * @private */ private function resourceManager_changeHandler(event:Event):void { resourcesChanged(); } } }