blob: 6faab51af5d95f56b53c270bf7825ddf5f9f614b [file] [log] [blame]
////////////////////////////////////////////////////////////////////////////////
//
// 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 mx.managers.ISystemManager;
import mx.managers.SystemManager;
[ResourceBundle("validators")]
[Alternative(replacement="spark.validators.NumberValidator", since="4.5")]
/**
* The NumberValidator class ensures that a String represents a valid number.
* It can ensure that the input falls within a given range
* (specified by <code>minValue</code> and <code>maxValue</code>),
* is an integer (specified by <code>domain</code>),
* is non-negative (specified by <code>allowNegative</code>),
* and does not exceed the specified <code>precision</code>.
* The validator correctly validates formatted numbers (e.g., "12,345.67")
* and you can customize the <code>thousandsSeparator</code> and
* <code>decimalSeparator</code> properties for internationalization.
*
* @mxml
*
* <p>The <code>&lt;mx:NumberValidator&gt;</code> tag
* inherits all of the tag attributes of its superclass,
* and adds the following tag attributes:</p>
*
* <pre>
* &lt;mx:NumberValidator
* allowNegative="true|false"
* decimalPointCountError="The decimal separator can only occur once."
* decimalSeparator="."
* domain="real|int"
* exceedsMaxError="The number entered is too large."
* integerError="The number must be an integer."
* invalidCharError="The input contains invalid characters."
* invalidFormatCharsError="One of the formatting parameters is invalid."
* lowerThanMinError="The amount entered is too small."
* maxValue="NaN"
* minValue="NaN"
* negativeError="The amount may not be negative."
* precision="-1"
* precisionError="The amount entered has too many digits beyond the decimal point."
* separationError="The thousands separator must be followed by three digits."
* thousandsSeparator=","
* /&gt;
* </pre>
*
* @includeExample examples/NumberValidatorExample.mxml
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public class NumberValidator extends Validator
{
include "../core/Version.as";
/**
* Convenience method for calling a validator
* from within a custom validation function.
* Each of the standard Flex validators has a similar convenience method.
*
* @param validator The NumberValidator instance.
*
* @param value A field to validate.
*
* @param baseField Text representation of the subfield
* specified in the <code>value</code> parameter.
* For example, if the <code>value</code> parameter specifies value.number,
* the <code>baseField</code> value is "number".
*
* @return An Array of ValidationResult objects, with one ValidationResult
* object for each field examined by the validator.
*
* @see mx.validators.ValidationResult
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public static function validateNumber(validator:NumberValidator,
value:Object,
baseField:String):Array
{
var results:Array = [];
// Resource-backed properties of the validator.
var allowNegative:Boolean = validator.allowNegative;
var decimalSeparator:String = validator.decimalSeparator;
var domain:String = validator.domain;
var maxValue:Number = Number(validator.maxValue);
var minValue:Number = Number(validator.minValue);
var precision:int = int(validator.precision);
var thousandsSeparator:String = validator.thousandsSeparator;
var input:String = String(value);
var len:int = input.length;
var isNegative:Boolean = false;
var i:int;
var c:String;
var isNumber:Boolean = (value is Number);
// Make sure the formatting character parameters are unique,
// are not digits or the negative sign,
// and that the separators are one character.
var invalidFormChars:String = DECIMAL_DIGITS + "-";
if (decimalSeparator == thousandsSeparator ||
invalidFormChars.indexOf(decimalSeparator) != -1 ||
invalidFormChars.indexOf(thousandsSeparator) != -1 ||
decimalSeparator.length != 1 ||
thousandsSeparator.length != 1)
{
results.push(new ValidationResult(
true, baseField, "invalidFormatChar",
validator.invalidFormatCharsError));
return results;
}
// Check for invalid characters in input.
var validChars:String = DECIMAL_DIGITS + "-" +
decimalSeparator + thousandsSeparator;
for (i = 0; i < len; i++)
{
c = input.charAt(i);
if (validChars.indexOf(c) == -1)
{
results.push(new ValidationResult(
true, baseField, "invalidChar",
validator.invalidCharError));
return results;
}
}
// Check if the input is negative.
if (input.charAt(0) == "-")
{
if (len == 1) // we have only '-' char
{
results.push(new ValidationResult(
true, baseField, "invalidChar",
validator.invalidCharError));
return results;
}
else if (len == 2 && input.charAt(1) == '.') // handle "-."
{
results.push(new ValidationResult(
true, baseField, "invalidChar",
validator.invalidCharError));
return results;
}
// Check if negative input is allowed.
if (!allowNegative)
{
results.push(new ValidationResult(
true, baseField, "negative",
validator.negativeError));
return results;
}
// Strip off the minus sign, update some variables.
input = input.substring(1);
len--;
isNegative = true;
}
// Make sure there's only one decimal point.
if (input.indexOf(decimalSeparator) !=
input.lastIndexOf(decimalSeparator))
{
results.push(new ValidationResult(
true, baseField, "decimalPointCount",
validator.decimalPointCountError));
return results;
}
// Make sure every character after the decimal is a digit,
// and that there aren't too many digits after the decimal point:
// if domain is int there should be none,
// otherwise there should be no more than specified by precision.
var decimalSeparatorIndex:Number = input.indexOf(decimalSeparator);
if (decimalSeparatorIndex != -1)
{
var numDigitsAfterDecimal:Number = 0;
if (i == 1 && i == len) // we only have a '.'
{
results.push(new ValidationResult(
true, baseField, "invalidChar",
validator.invalidCharError));
return results;
}
for (i = decimalSeparatorIndex + 1; i < len; i++)
{
// This character must be a digit.
if (DECIMAL_DIGITS.indexOf(input.charAt(i)) == -1)
{
results.push(new ValidationResult(
true, baseField, "invalidChar",
validator.invalidCharError));
return results;
}
++numDigitsAfterDecimal;
// There may not be any non-zero digits after the decimal
// if domain is int.
if (domain == NumberValidatorDomainType.INT && input.charAt(i) != "0")
{
results.push(new ValidationResult(
true, baseField,"integer",
validator.integerError));
return results;
}
// Make sure precision is not exceeded.
if (precision != -1 &&
numDigitsAfterDecimal > precision)
{
results.push(new ValidationResult(
true, baseField, "precision",
validator.precisionError));
return results;
}
}
}
// Make sure the input begins with a digit or a decimal point.
if (DECIMAL_DIGITS.indexOf(input.charAt(0)) == -1 &&
input.charAt(0) != decimalSeparator)
{
results.push(new ValidationResult(
true, baseField, "invalidChar",
validator.invalidCharError));
return results;
}
// Make sure that every character before the decimal point
// is a digit or is a thousands separator.
// If it's a thousands separator,
// make sure it's followed by three consecutive digits.
var end:int = decimalSeparatorIndex == -1 ?
len :
decimalSeparatorIndex;
if (!isNumber)
{
for (i = 1; i < end; i++)
{
c = input.charAt(i);
if (c == thousandsSeparator)
{
if (c == thousandsSeparator)
{
if ((end - i != 4 &&
input.charAt(i + 4) != thousandsSeparator) ||
DECIMAL_DIGITS.indexOf(input.charAt(i + 1)) == -1 ||
DECIMAL_DIGITS.indexOf(input.charAt(i + 2)) == -1 ||
DECIMAL_DIGITS.indexOf(input.charAt(i + 3)) == -1)
{
results.push(new ValidationResult(
true, baseField, "separation",
validator.separationError));
return results;
}
}
}
else if (DECIMAL_DIGITS.indexOf(c) == -1)
{
results.push(new ValidationResult(
true, baseField,"invalidChar",
validator.invalidCharError));
return results;
}
}
}
// Make sure the input is within the specified range.
if (!isNaN(minValue) || !isNaN(maxValue))
{
// First strip off the thousands separators.
for (i = 0; i < end; i++)
{
if (input.charAt(i) == thousandsSeparator)
{
var left:String = input.substring(0, i);
var right:String = input.substring(i + 1);
input = left + right;
}
}
// Translate the value back into standard english
// If the decimalSeperator is not '.' we need to change it to '.'
// so that the number casting will work properly
if (validator.decimalSeparator != '.')
{
var dIndex:int = input.indexOf( validator.decimalSeparator );
if (dIndex != -1)
{
var dLeft:String = input.substring(0, dIndex);
var dRight:String = input.substring(dIndex + 1);
input = dLeft + '.' + dRight;
}
}
// Check bounds
var x:Number = Number(input);
if (isNegative)
x = -x;
if (!isNaN(minValue) && x < minValue)
{
results.push(new ValidationResult(
true, baseField, "lowerThanMin",
validator.lowerThanMinError));
return results;
}
if (!isNaN(maxValue) && x > maxValue)
{
results.push(new ValidationResult(
true, baseField, "exceedsMax",
validator.exceedsMaxError));
return results;
}
}
return results;
}
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function NumberValidator()
{
super();
}
//--------------------------------------------------------------------------
//
// Properties
//
//--------------------------------------------------------------------------
//----------------------------------
// allowNegative
//----------------------------------
/**
* @private
* Storage for the allowNegative property.
*/
private var _allowNegative:Object;
/**
* @private
*/
private var allowNegativeOverride:Object;
[Inspectable(category="General", defaultValue="null")]
/**
* Specifies whether negative numbers are permitted.
* Valid values are <code>true</code> or <code>false</code>.
*
* @default true
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get allowNegative():Object
{
return _allowNegative;
}
/**
* @private
*/
public function set allowNegative(value:Object):void
{
allowNegativeOverride = value;
_allowNegative = value != null ?
Boolean(value) :
resourceManager.getBoolean(
"validators", "allowNegative");
}
//----------------------------------
// decimalSeparator
//----------------------------------
/**
* @private
* Storage for the decimalSeparator property.
*/
private var _decimalSeparator:String;
/**
* @private
*/
private var decimalSeparatorOverride:String;
[Inspectable(category="General", defaultValue="null")]
/**
* The character used to separate the whole
* from the fractional part of the number.
* Cannot be a digit and must be distinct from the
* <code>thousandsSeparator</code>.
*
* @default "."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get decimalSeparator():String
{
return _decimalSeparator;
}
/**
* @private
*/
public function set decimalSeparator(value:String):void
{
decimalSeparatorOverride = value;
_decimalSeparator = value != null ?
value :
resourceManager.getString(
"validators", "decimalSeparator");
}
//----------------------------------
// domain
//----------------------------------
/**
* @private
* Storage for the domain property.
*/
private var _domain:String;
/**
* @private
*/
private var domainOverride:String;
[Inspectable(category="General", enumeration="int,real", defaultValue="null")]
/**
* Type of number to be validated.
* Permitted values are <code>"real"</code> and <code>"int"</code>.
*
* <p>In ActionScript, you can use the following constants to set this property:
* <code>NumberValidatorDomainType.REAL</code> or
* <code>NumberValidatorDomainType.INT</code>.</p>
*
* @default "real"
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get domain():String
{
return _domain;
}
/**
* @private
*/
public function set domain(value:String):void
{
domainOverride = value;
_domain = value != null ?
value :
resourceManager.getString(
"validators", "numberValidatorDomain");
}
//----------------------------------
// maxValue
//----------------------------------
/**
* @private
* Storage for the maxValue property.
*/
private var _maxValue:Object;
/**
* @private
*/
private var maxValueOverride:Object;
[Inspectable(category="General", defaultValue="null")]
/**
* Maximum value for a valid number. A value of NaN means there is no maximum.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get maxValue():Object
{
return _maxValue;
}
/**
* @private
*/
public function set maxValue(value:Object):void
{
maxValueOverride = value;
_maxValue = value != null ?
Number(value) :
resourceManager.getNumber(
"validators", "maxValue");
}
//----------------------------------
// minValue
//----------------------------------
/**
* @private
* Storage for the minValue property.
*/
private var _minValue:Object;
/**
* @private
*/
private var minValueOverride:Object;
[Inspectable(category="General", defaultValue="null")]
/**
* Minimum value for a valid number. A value of NaN means there is no minimum.
*
* @default NaN
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get minValue():Object
{
return _minValue;
}
/**
* @private
*/
public function set minValue(value:Object):void
{
minValueOverride = value;
_minValue = value != null ?
Number(value) :
resourceManager.getNumber(
"validators", "minValue");
}
//----------------------------------
// precision
//----------------------------------
/**
* @private
* Storage for the precision property.
*/
private var _precision:Object;
/**
* @private
*/
private var precisionOverride:Object;
[Inspectable(category="General", defaultValue="null")]
/**
* The maximum number of digits allowed to follow the decimal point.
* Can be any nonnegative integer.
* Note: Setting to <code>0</code> has the same effect
* as setting <code>domain</code> to <code>"int"</code>.
* A value of -1 means it is ignored.
*
* @default -1
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get precision():Object
{
return _precision;
}
/**
* @private
*/
public function set precision(value:Object):void
{
precisionOverride = value;
_precision = value != null ?
int(value) :
resourceManager.getInt(
"validators", "numberValidatorPrecision");
}
//----------------------------------
// thousandsSeparator
//----------------------------------
/**
* @private
* Storage for the thousandsSeparator property.
*/
private var _thousandsSeparator:String;
/**
* @private
*/
private var thousandsSeparatorOverride:String;
[Inspectable(category="General", defaultValue="null")]
/**
* The character used to separate thousands
* in the whole part of the number.
* Cannot be a digit and must be distinct from the
* <code>decimalSeparator</code>.
*
* @default ","
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get thousandsSeparator():String
{
return _thousandsSeparator;
}
/**
* @private
*/
public function set thousandsSeparator(value:String):void
{
thousandsSeparatorOverride = value;
_thousandsSeparator = value != null ?
value :
resourceManager.getString(
"validators", "thousandsSeparator");
}
//--------------------------------------------------------------------------
//
// Properties: Errors
//
//--------------------------------------------------------------------------
//----------------------------------
// decimalPointCountError
//----------------------------------
/**
* @private
* Storage for the decimalPointCountError property.
*/
private var _decimalPointCountError:String;
/**
* @private
*/
private var decimalPointCountErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the decimal separator character occurs more than once.
*
* @default "The decimal separator can occur only once."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get decimalPointCountError():String
{
return _decimalPointCountError;
}
/**
* @private
*/
public function set decimalPointCountError(value:String):void
{
decimalPointCountErrorOverride = value;
_decimalPointCountError = value != null ?
value :
resourceManager.getString(
"validators", "decimalPointCountError");
}
//----------------------------------
// exceedsMaxError
//----------------------------------
/**
* @private
* Storage for the exceedsMaxError property.
*/
private var _exceedsMaxError:String;
/**
* @private
*/
private var exceedsMaxErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the value exceeds the <code>maxValue</code> property.
*
* @default "The number entered is too large."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get exceedsMaxError():String
{
return _exceedsMaxError;
}
/**
* @private
*/
public function set exceedsMaxError(value:String):void
{
exceedsMaxErrorOverride = value;
_exceedsMaxError = value != null ?
value :
resourceManager.getString(
"validators", "exceedsMaxErrorNV");
}
//----------------------------------
// integerError
//----------------------------------
/**
* @private
* Storage for the integerError property.
*/
private var _integerError:String;
/**
* @private
*/
private var integerErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the number must be an integer, as defined
* by the <code>domain</code> property.
*
* @default "The number must be an integer."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get integerError():String
{
return _integerError;
}
/**
* @private
*/
public function set integerError(value:String):void
{
integerErrorOverride = value;
_integerError = value != null ?
value :
resourceManager.getString(
"validators", "integerError");
}
//----------------------------------
// invalidCharError
//----------------------------------
/**
* @private
* Storage for the invalidCharError property.
*/
private var _invalidCharError:String;
/**
* @private
*/
private var invalidCharErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the value contains invalid characters.
*
* @default The input contains invalid characters."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get invalidCharError():String
{
return _invalidCharError;
}
/**
* @private
*/
public function set invalidCharError(value:String):void
{
invalidCharErrorOverride = value;
_invalidCharError = value != null ?
value :
resourceManager.getString(
"validators", "invalidCharError");
}
//----------------------------------
// invalidFormatCharsError
//----------------------------------
/**
* @private
* Storage for the invalidFormatCharsError property.
*/
private var _invalidFormatCharsError:String;
/**
* @private
*/
private var invalidFormatCharsErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the value contains invalid format characters, which means that
* it contains a digit or minus sign (-) as a separator character,
* or it contains two or more consecutive separator characters.
*
* @default "One of the formatting parameters is invalid."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get invalidFormatCharsError():String
{
return _invalidFormatCharsError;
}
/**
* @private
*/
public function set invalidFormatCharsError(value:String):void
{
invalidFormatCharsErrorOverride = value;
_invalidFormatCharsError = value != null ?
value :
resourceManager.getString(
"validators", "invalidFormatCharsError");
}
//----------------------------------
// lowerThanMinError
//----------------------------------
/**
* @private
* Storage for the lowerThanMinError property.
*/
private var _lowerThanMinError:String;
/**
* @private
*/
private var lowerThanMinErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the value is less than <code>minValue</code>.
*
* @default "The amount entered is too small."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get lowerThanMinError():String
{
return _lowerThanMinError;
}
/**
* @private
*/
public function set lowerThanMinError(value:String):void
{
lowerThanMinErrorOverride = value;
_lowerThanMinError = value != null ?
value :
resourceManager.getString(
"validators", "lowerThanMinError");
}
//----------------------------------
// negativeError
//----------------------------------
/**
* @private
* Storage for the negativeError property.
*/
private var _negativeError:String;
/**
* @private
*/
private var negativeErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the value is negative and the
* <code>allowNegative</code> property is <code>false</code>.
*
* @default "The amount may not be negative."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get negativeError():String
{
return _negativeError;
}
/**
* @private
*/
public function set negativeError(value:String):void
{
negativeErrorOverride = value;
_negativeError = value != null ?
value :
resourceManager.getString(
"validators", "negativeError");
}
//----------------------------------
// precisionError
//----------------------------------
/**
* @private
* Storage for the precisionError property.
*/
private var _precisionError:String;
/**
* @private
*/
private var precisionErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the value has a precision that exceeds the value defined
* by the precision property.
*
* @default "The amount entered has too many digits beyond the decimal point."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get precisionError():String
{
return _precisionError;
}
/**
* @private
*/
public function set precisionError(value:String):void
{
precisionErrorOverride = value;
_precisionError = value != null ?
value :
resourceManager.getString(
"validators", "precisionError");
}
//----------------------------------
// separationError
//----------------------------------
/**
* @private
* Storage for the separationError property.
*/
private var _separationError:String;
/**
* @private
*/
private var separationErrorOverride:String;
[Inspectable(category="Errors", defaultValue="null")]
/**
* Error message when the thousands separator is in the wrong location.
*
* @default "The thousands separator must be followed by three digits."
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
public function get separationError():String
{
return _separationError;
}
/**
* @private
*/
public function set separationError(value:String):void
{
separationErrorOverride = value;
_separationError = value != null ?
value :
resourceManager.getString(
"validators", "separationError");
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* @private
*/
override protected function resourcesChanged():void
{
super.resourcesChanged();
allowNegative = allowNegativeOverride;
decimalSeparator = decimalSeparatorOverride;
domain = domainOverride;
maxValue = maxValueOverride;
minValue = minValueOverride;
precision = precisionOverride;
thousandsSeparator = thousandsSeparatorOverride;
decimalPointCountError = decimalPointCountErrorOverride;
exceedsMaxError = exceedsMaxErrorOverride;
integerError = integerErrorOverride;
invalidCharError = invalidCharErrorOverride;
invalidFormatCharsError = invalidFormatCharsErrorOverride;
lowerThanMinError = lowerThanMinErrorOverride;
negativeError = negativeErrorOverride;
precisionError = precisionErrorOverride;
separationError = separationErrorOverride;
}
/**
* Override of the base class <code>doValidation()</code> method
* to validate a number.
*
* <p>You do not call this method directly;
* Flex calls it as part of performing a validation.
* If you create a custom Validator class, you must implement this method. </p>
*
* @param value Object to validate.
*
* @return An Array of ValidationResult objects, with one ValidationResult
* object for each field examined by the validator.
*
* @langversion 3.0
* @playerversion Flash 9
* @playerversion AIR 1.1
* @productversion Flex 3
*/
override protected function doValidation(value:Object):Array
{
var results:Array = super.doValidation(value);
// Return if there are errors
// or if the required property is set to <code>false</code> and length is 0.
var val:String = value ? String(value) : "";
if (results.length > 0 || ((val.length == 0) && !required))
return results;
else
return NumberValidator.validateNumber(this, value, null);
}
}
}