Sometimes, while using validators in Flex, i needed to have more than one validator connected to a single component, e.g. a TextInput. So, at the beginnig, I tried the naive approach, just create multiple validators and assign to all of them the same source and the same property to validate. After a few tests, I quickly realized that in Flex, a single component is meant to have only one validator associated to it. This is a problem in some situations because I have different validatiors that already do what I need and I just want my component to be considered valid only when all the validators are valid. So I investigated more in depth taking a look at the Flex SDK source code and I realized that it’s by design that I simply cannot do it easily. What usually happens is that it’s the last triggered validator that decides if a component is valid or not. We know that everything is based on events dispatching in Flex and that we cannot know the order in which events reach a particular component, so we also don’t know what will be the last validator to be triggered for a component. This is especially evident when a form is quite complex and contains a lot of fields that have to be validated.
What can we do to solve this problem? Here I’m going to explain what I did about a couple of years ago, when I faced this problem. There are rumors that there should be something available in the next version of the Flex SDK (version 4) that solves the problem, but meanwhile, we can still solve it using the manager class I developed. I’ve been using it up until now and it always worked as expected without problems. Basically, the class takes care of all the validation events coming from different validators and makes sure that the right validity state is preserved for the component that has been validated. It doesn’t matter when the events are dispatched, it’s not important, the final result is:
- a component is considered VALID if all the validators say that it’s valid;
- a component is considered INVALID if at least one of the validators says that it’s not valid.
Let’s take a look at the source code now. The ComponentValidationManager is the class that makes it possible to have multiple validators on a single component. We’ll call the component that has to be validated as the managed component and the validators attached to it the managed validators.
package com.devahead.utils
{
import mx.core.UIComponent;
import mx.validators.Validator;
import mx.events.ValidationResultEvent;
import mx.collections.ArrayCollection;
import mx.events.FlexEvent;
public class ComponentValidationManager
{
protected var _isManagerValid: Boolean = true;
protected var _managedComponent: UIComponent;
protected var _managedValidators: Array;
protected var _isValidationManagementEnabled: Boolean = false;
// Array of the validators in INVALID state
protected var _invalidValidators: ArrayCollection = new ArrayCollection();
protected var _invalidValidationMessages: ArrayCollection = new ArrayCollection();
/**
* Constructor.
*
* @param managedComponent
* UIComponent managed by the ComponentValidationManager.
* If must not be NULL.
* @param managedValidators
* Array of the validators that have to be managed. They must
* all be in VALID state at this time.
*/
public function ComponentValidationManager(managedComponent: UIComponent,
managedValidators: Array)
{
// NOTE: I expect that all the managedValidators are VALID at this time
_managedComponent = managedComponent;
_managedValidators = managedValidators;
if (_managedComponent != null && _managedValidators != null &&
_managedValidators.length > 0)
{
for (var i: int = 0; i < _managedValidators.length; i++)
{
if (_managedValidators[i] == null || !(_managedValidators[i] is Validator))
{
// NULL values or components that are not subclasses of
// mx.validators.Validator are not allowed.
_isManagerValid = false;
break;
}
}
if (_isManagerValid)
{
enableValidationManagement();
}
}
}
/**
* Tells if the ComponentValidationManager is valid.
*
* @return "True" if the manager is valid (i.e. constructors arguments
* where correct).
*/
public function get isManagerValid(): Boolean
{
return _isManagerValid;
}
/**
* Managed component.
*/
public function get managedComponent(): UIComponent
{
return _managedComponent;
}
/**
* Managed validators.
*/
public function get managedValidators(): Array
{
return _managedValidators;
}
/**
* "True" if the validation management is enabled.
*/
public function get isValidationManagementEnabled(): Boolean
{
return _isValidationManagementEnabled;
}
/**
* Enables validation management.
*/
public function enableValidationManagement(): void
{
if (!_isValidationManagementEnabled)
{
// Add the listeners for "VALID" and "INVALID" events generated by
// the managed validators.
for each (var v: Validator in _managedValidators)
{
v.addEventListener(ValidationResultEvent.VALID, onValidatorValidationEvent);
v.addEventListener(ValidationResultEvent.INVALID, onValidatorValidationEvent);
}
_managedComponent.addEventListener(FlexEvent.VALID, onComponentValidationEvent);
_managedComponent.addEventListener(FlexEvent.INVALID, onComponentValidationEvent);
_isValidationManagementEnabled = true;
}
}
/**
* Disables validation management.
*/
public function disableValidationManagement(): void
{
if (_isValidationManagementEnabled)
{
_managedComponent.removeEventListener(FlexEvent.INVALID, onComponentValidationEvent);
_managedComponent.removeEventListener(FlexEvent.VALID, onComponentValidationEvent);
// Remove the listeners for "VALID" and "INVALID" events generated by
// the managed validators.
for each (var v: Validator in _managedValidators)
{
v.removeEventListener(ValidationResultEvent.VALID, onValidatorValidationEvent);
v.removeEventListener(ValidationResultEvent.INVALID, onValidatorValidationEvent);
}
_invalidValidators.removeAll();
_invalidValidationMessages.removeAll();
_isValidationManagementEnabled = false;
}
}
/**
* The managed component has been judged VALID by a validator, so we must
* update its state.
*
* @param validatorInstance
* Validator that judged the managed component as VALID.
*/
protected function updateManagedComponentValidState(validatorInstance: Validator): void
{
var itemIndex: int = _invalidValidators.getItemIndex(validatorInstance);
if (itemIndex != -1)
{
// Remove the validator from the INVALID list
_invalidValidators.removeItemAt(itemIndex);
// Remove also the corresponding validation message
_invalidValidationMessages.removeItemAt(itemIndex);
if (_invalidValidators.length == 0)
{
// Restore the normal state of the managed component (now it's valid)
_managedComponent.errorString = null;
if (_managedComponent.focusManager != null &&
_managedComponent.focusManager.getFocus() == _managedComponent)
{
// The managed component has the focus so we must also
// draw the border.
_managedComponent.drawFocus(true);
}
}
else
{
// The managed component is still INVALID and due to the fact that
// the error message previously generated by the validator was
// removed, we must update the message that should now be displayed
// for the managed component.
updateManagedComponentInvalidState();
}
}
}
/**
* The managed component has been judged INVALID by a validator, so we must
* update its state.
*
* @param validatorInstance
* Validator that judged the managed component as INVALID.
*/
protected function updateManagedComponentInvalidState(
validatorInstance: Validator = null, validationMessage: String = ""): void
{
var isValidationMessageChanged: Boolean = false;
if (validatorInstance != null)
{
if (!_invalidValidators.contains(validatorInstance))
{
// Add the validator the the INVALID list
_invalidValidators.addItem(validatorInstance);
// Add the corresponding validation message
_invalidValidationMessages.addItem(validationMessage);
isValidationMessageChanged = true;
}
else
{
// The validation message could have changed, so we update it
// if necessary.
var oldValidationMessage: String = String(_invalidValidationMessages.getItemAt(
_invalidValidators.getItemIndex(validatorInstance)));
if (oldValidationMessage != validationMessage)
{
_invalidValidationMessages.setItemAt(validationMessage,
_invalidValidators.getItemIndex(validatorInstance));
isValidationMessageChanged = true;
}
}
}
else
{
if (_invalidValidators.length > 0)
{
// We force the update of the INVALID state for the managed component
isValidationMessageChanged = true;
}
}
if (isValidationMessageChanged)
{
// We graphically display the error on the managed component
// We prepare the error message taking into account all those
// returned by the managed validators that are in the INVALID state.
var errorMessage: String = "";
for each (var msg: String in _invalidValidationMessages)
{
if (errorMessage != "")
{
errorMessage += "\n";
}
errorMessage += msg;
}
_managedComponent.errorString = errorMessage;
if (_managedComponent.focusManager != null &&
_managedComponent.focusManager.getFocus() == _managedComponent)
{
// The managed component has the focus so we must also draw the border
_managedComponent.drawFocus(true);
}
}
}
/**
* Handler for the validation events coming from the managed component.
*
* @param event
* Validation event.
*/
protected function onComponentValidationEvent(event: FlexEvent): void
{
if (event.target == _managedComponent)
{
// We check that the INVALID state is kept correctly for the managed
// component if it is in fact INVALID (this is useful to avoid the
// interference of the function "validationResultHandler" in
// mx.core.UIComponent with the management made internally by
// ComponentValidationManager).
updateManagedComponentInvalidState();
}
}
/**
* Handler for the validation events coming from the managed validators.
*
* @param event
* Validation event.
*/
protected function onValidatorValidationEvent(event: ValidationResultEvent): void
{
if (event.type == ValidationResultEvent.VALID)
{
updateManagedComponentValidState(Validator(event.target));
}
else if (event.type == ValidationResultEvent.INVALID)
{
updateManagedComponentInvalidState(Validator(event.target), event.message);
}
}
}
}
So, what does this class do? You create an instance of the class specifying which component you want to manage and all the validators that are connected to it, then the class attaches its handlers to catch the validation events and keeps the component updated setting its validation error message accordingly. The validation error message is created as the sequence of all the messages returned by the invalid validators. To better understand how to use ComponentValidationManager we can take a look at the following example.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
pageTitle="Multiple validators on a single component - devahead BLOG"
layout="vertical" horizontalAlign="left"
applicationComplete="{onApplicationComplete(event)}">
<mx:Script>
<![CDATA[
import com.devahead.utils.ComponentValidationManager;
import mx.events.FlexEvent;
protected const TOO_SHORT_ERROR: String = "String must not be shorter than {0} characters";
protected const TOO_LONG_ERROR: String = "String must not be longer than {0} characters";
protected var compValManager: ComponentValidationManager;
protected function onApplicationComplete(event: FlexEvent): void
{
compValManager = new ComponentValidationManager(textInput2, [strVal2a, strVal2b]);
}
]]>
</mx:Script>
<mx:StringValidator id="strVal1a" minLength="1" maxLength="4"
source="{textInput1}" property="text"
tooShortError="{TOO_SHORT_ERROR}" tooLongError="{TOO_LONG_ERROR}"/>
<mx:StringValidator id="strVal1b" minLength="4" maxLength="6"
source="{textInput1}" property="text"
tooShortError="{TOO_SHORT_ERROR}" tooLongError="{TOO_LONG_ERROR}"/>
<mx:StringValidator id="strVal2a" minLength="1" maxLength="4"
source="{textInput2}" property="text"
tooShortError="{TOO_SHORT_ERROR}" tooLongError="{TOO_LONG_ERROR}"/>
<mx:StringValidator id="strVal2b" minLength="4" maxLength="6"
source="{textInput2}" property="text"
tooShortError="{TOO_SHORT_ERROR}" tooLongError="{TOO_LONG_ERROR}"/>
<mx:Label text="Validator A: string length >= 1 and <= 4"/>
<mx:Label text="Validator B: string length >= 4 and <= 6"/>
<mx:Label text="Only 4 characters strings should be recognized as valid" fontWeight="bold"/>
<mx:HBox>
<mx:Label text="Write something:"/>
<mx:TextInput id="textInput1"/>
<mx:Button label="Validate (A -> B)"
click="{strVal1a.validate(); strVal1b.validate()}"/>
<mx:Button label="Validate (B -> A)"
click="{strVal1b.validate(); strVal1a.validate()}"/>
<mx:Label text="(without ComponentValidationManager)" fontWeight="bold"/>
</mx:HBox>
<mx:HBox>
<mx:Label text="Write something:"/>
<mx:TextInput id="textInput2"/>
<mx:Button label="Validate (A -> B)"
click="{strVal2a.validate(); strVal2b.validate()}"/>
<mx:Button label="Validate (B -> A)"
click="{strVal2b.validate(); strVal2a.validate()}"/>
<mx:Label text="(with ComponentValidationManager)" fontWeight="bold"/>
</mx:HBox>
</mx:Application>
In the example, there are a couple of TextInput components and each of them has a couple of validators attached, but only textInpu2 is managed by ComponentValidationManager. According to the validators, only strings made by exactly four characters should be considered valid. If you type something in textInput1 and you try to click the two buttons next to it, you’ll see different behaviors:
- if only Validator A considers the string valid and you click Validate (A -> B), textInput1 will be displayed as INVALID because the last validation has been executed by Validator B, while if you click Validate (B -> A) the text field will be considered valid because the last validation has been executed by Validator A;
- if only Validator B considers the string valid and you click Validate (B -> A), textInput1 will be displayed as INVALID because the last validation has been executed by Validator A, while if you click Validate (A -> B) the text field will be considered valid because the last validation has been executed by Validator B;
- if both the validators consider the string valid, then there are no problems and textInput1 is displayed as VALID;
- if both the validators consider the string invalid, then the validation error message displayed as a hint while moving over the text field with the mouse pointer will be the message returned by the last validator that executed the validation.
If you try to repeat the same tests with textInput2, you’ll see that the validity of the component is always kept in the correct state and also the validation error hint reflects the real state of the field. This happens thanks to ComponentValidationManager, so you don’t care when the validation occurs in different validators, you just connect them to your text field and everything is fine.
What we saw can be applied to every kind of UIComponent and every kind of validators. I hope this will be useful to somebody, for me it was. If you develop your own procedure to automatically create ComponentValidationManager instances for all the validators in a form, everything could become transparent to the developer. We’ll see in other posts something that can be useful to execute such a task.
Tags: Flex, validators

December 10th, 2009 at 15:18
This is great, thanks for sharing!
December 10th, 2009 at 15:21
Just out of curiosity, what would happen if you had 3 validators on a text field and 2 out of the three failed?
December 12th, 2009 at 01:44
You’re welcome Chris. If you had 3 validators on a text field and two of them fail, the text field is invalid because a component is considered invalid if at least one of the validators says that it’s not valid.
December 15th, 2009 at 15:37
I was actually referring to the error message presented, I had already asked before I read above how the error messages are truncated then added to the managedComponent. I was wondering if you have considered perhaps extending Validator so that it could be completely declared in MXML? This approach could possibly allow staying within the existing flex validation architecture, rather than setting the errorString and focus manually. Thoughts?
December 17th, 2009 at 01:52
My main goal was to be able to use transparently all the existing Flex standard validators (like StringValidator, NumberValidator and so on) possibly together with my own validators. To achieve this, I couldn’t extend the base Validator class (the standard validators still extend the base one in the SDK). Anyway, I think it would be difficult to obtain the same result extending only the base Validator class because the real problem is in the function validationResultHandler inside the UIComponent base class. That’s where all the validation events dispatched by the validators are handled (in fact, the Validator instance adds the validationResultHandler function on its source instance as a listener for the validation events). The validationResultHandler function is the one that sets the errorString property of the validator’s source so with the ComponentValidationManager I basically override the behavior of validationResultHandler setting my own errorString after it already set its own. To use the ComponentValidationManager directly inside MXML, I think it would be possible to modify it trying to manage the managedComponent and the managedValidators properties through setter functions and the commitProperties function to coordinate everything, instead of passing them only in the constructor of the class. I didn’t investigate into this solution just because having multiple validators on a single component is not so common and I thought it was fine to just use this approach creating instances of the ComponentValidationManager in the script part, but it would be interesting to extend the versatility of the manager.
December 19th, 2009 at 15:08
We have a need for declaration in MXML because we use validators within our datagrid columns using the Farata Systems Clear Toolkit giving us cell level on screen validations. After posting here last time I thought I would go give my idea a try. I was successful, unfortunately my company won’t let me share source code created on the job but the implementation was so deceptively simple I figure I can describe it. My class has a public array so you just declare that array within the MXML declaration. Then you put n number of validators inside there, the trick is to not give them source or property information, only give that to the main validator. Then within the overridden doValidations method you loop through the validators giving them the main validator’s value (the value from source) and capturing their validation result event failures and corresponding error messages. If you have gone through all of them and your capture list is empty then you have no errors, if any errors are found create a validation result with the combined error messages (minus duplicates if desired) to be returned and the desired effect is achieved. Using this method we can add n number of validators to a component and display valid or n number of errors in one.
December 21st, 2009 at 23:50
I think I understand what you did. You’ve got a main validator connected to the component that has to be validated through its source and property values. The only purpose of the main validator is to manage a set of different… let’s call them child validators, that you pass to the main one through an array. When the main validator is required to validate the component, it loops through the array calling for each child validator its validate method and passing to it, as an argument, the value read from the source component through the specified property. You avoid setting source and property values for the child validators because you pass the value to validate directly to the validate method and you avoid having the child validators to interfere with the setting of the errorString of the component to validate. So the main validator can collect all the results from the validate method of the child validators and return a merged version as the only validation event for the component, thus allowing Flex to set the errorString as the union of all the validations. This is a really interesting approach. Thanks a lot Chris for taking the time to write your idea! Looking at something from a different point of view is always a good thing. Thanks.
December 31st, 2009 at 13:41
You are very welcome, I wouldn’t have even thought of it if I hadn’t come across your site so the thanks really belongs to you. The basic idea was all yours and I am grateful for your willingness to share. That is one of the things I love the most about the Flex community, the openness of the developers all building an improving each-other’s creations.