Validanguage Helper for customizable Javascript form validation
Validanguage is a free, open source javascript library that provides a highly customizable, framework-independent environment for managing client-side form validation. The Validanguage helper enables validation rules to be generated automatically from Cake 1.2 model validation rules.
Overview
By simply including the validanguage.js javascript file on a webpage and issuing a single call to the validanguage helper, you can automatically transform the server-side validation rules in your model into client-side javascript validation to create inline success and error messages that are easily customizable via CSS.
Additional details on validanguage, including demos, the latest version and documentation are available on the project's homepage at http://drlongghost.com/validanguage.php.
Required Files (Appear Inline Below the Article)
- validanguage.js (Validanguage Javascript file)
- validanguage.php (Validanguage CakePHP Helper)
Basic Usage
Step 1 -- Download the required files
Download the validanguage javascript file and CakePHP helper and place them in the appropriate directories (typically, /app/webroot/js/ and /app/helpers/ respectively).Step 2 -- Add the helper to the desired controller(s)
If you wish to use the Validanguage helper throughout your application, you can add it to the helpers array for app_controller.php. Alternately, add the Validanguage helper directly to the desired controller(s):Download code
<?php
var $helpers = array('Validanguage');
?>
Step 3 -- Add the validanguage.js javascript file to your View
You can add the javascript directly to the layout for all your pages by adding a script tag to the head section of the layout:Download code
<script type="text/javascript" src="/path/to/validanguage.js"></script>
Alternately, use the javascript helper in your view file(s) to include the validanguage javascript:
Download code
<?php
$javascript->link('/path/to/validanguage.js', false);
?>
Step 4 -- Call $validanguage->printTags() inside your View
The final step is to call the printTags() method inside your view(s):Download code
<?php
$validanguage->printTags();
?>
This method will convert the validation rules defined for all models associated with the current controller into validanguage Comment tags, similar to the following:Download code
<!-- <validanguage target="ArticleBody" required="true" errorMsg="You must enter some text for the body of your article."/> -->
Validanguage Comment tags are placed inside HTML comments and are themselves written in an HTML-like syntax. When the validanguage javascript is included on a page, it automatically scans through all the comment tags on the page and loads javascript validation rules for any validanguage tags which it finds.
That pretty much sums up the basic usage for this helper. It was designed to entirely automate the process of generating the javascript rules needed to validate your form. However, custom validation functions which you have set up yourself in a model will not be automatically translated into javascript validation rules. For these custom functions, you will need to write a corresponding javascript function to emulate the validation and manually add a validanguage tag to associate the new function with the desired form field(s).
Additional Features Supported by the Validanguage Helper
All of the optional features supported by the validanguage.php helper are exposed to the user via publicly accessible class variables.$validanguage->modelValidationBlacklist -- Populate this array with a list of models for which validation rules should not be loaded.
$validanguage->modelValidationWhitelist -- If $modelValidationWhitelist is left as the default empty array, then validations for all models associated with the current controller (minus any listed in $modelValidationBlacklist) will be loaded. If one or more models are listed in $modelValidationWhitelist, then only those models will be used, and any additional models associated with the controller will be ignored.
$validanguage->skipTheseIds -- Populate this array with a list of IDs for which validation rules should not be loaded.
$validanguage->fieldIds -- Use this array to specify that a non-standard ID is being used for a given field, instead of the Cake default. Provide the cake default as the key and the non-default, custom ID as the value. For example: $fieldIds = array('ArticleBody' => 'blog_post');
$validanguage->transactionType -- Set this variable to either 'create' or 'update' to properly suppress any validation rules from being applied to the page when the model's validation rules are qualified with 'on' => 'create' or 'on' => 'update'
$validanguage->country -- Set this to something other than "US" to suppress US-specific validations.
$validanguage->validateCreditCardArgs -- Determines which arguments will be passed to the validanguage.validateCreditCard function
$validanguage->validateDateArgs -- Determines which arguments will be passed to the validanguage.validateDate function
$validanguage->extraFormSettings -- If you want to set one or more validanguage form settings for all your pages, you can populate this variable with the javascript to set the settings. To make validations run onblur you can use the following: 'validanguage.settings.defaultValidationHandlers=["submit","blur"];'
$validanguage->apiType -- Controls which Validanguage API will be used: 1 = HTML Comment API (Validanguage tags in HTML comments) AND 2 = Javascript Object API (Objects in a script tag). If you would like to use the Object API, you will need to have the PECL json module installed on your Web server's PHP install.
Additional Notes
If you will be using validanguage for client-side validation, you will definitely want to review the documentation on the project's homepage, so that you have a good idea what exactly is going on behind the scenes. Feel free to contact me if you have any additional questions/comments, or leave a comment below.Helper Code
Here is the text of version 1.0.0 of my validanguage helper. See the project's homepage for the latest version.Download code
<?php
/**
* ValidanguageHelper
*
* CakePHP helper to automatically convert model validation rules to either
* the Validanguage Comment API or the Validanguage Object API to permit
* automatic generation of javascript validation.
*
* For details on Validanguage, see http://drlongghost.com/validanguage.php
* For the Validanguage demo, see http://drlongghost.com/vd_tests/vd_demo1.php
*
* Written by DLG (drlongghost@yahoo.com), Oct. 2008.
*
* Released under the MIT License.
*
* @version 1.0.0
*/
class ValidanguageHelper extends AppHelper {
/**
* Holds the model validation info
* @var array
*/
var $modelValidations = array();
/**
* Controls which Validanguage API will be used:
* 1 = HTML Comment API (Validanguage tags in HTML comments)
* 2 = Javascript Object API (JS Objects in a script tag)
* @var integer
*/
var $apiType = 1;
/**
* Set to true to print debugging statements along with the validanguage tags
* @var boolean
*/
var $debug = false;
/**
* Determines which arguments will be passed to the validanguage.validateCreditCard function.
* The first argument *must* be "text"
* @var string
*/
var $validateCreditCardArgs = "text, ['amex','disc','mc','visa'], true";
/**
* Determines which arguments will be passed to the validanguage.validateDate function.
* The first argument *must* be "text"
* @var string
*/
var $validateDateArgs = "text, { dateOrder: 'mdy', allowedDelimeters: './-', twoDigitYearsAllowed: true }";
/**
* If you want to set one or more validanguage form settings for all your pages,
* you can populate this variable with the javascript to set the settings.
* To make validations run onblur you can use the following:
* 'validanguage.settings.defaultValidationHandlers=["submit","blur"];'
* @var string
*/
var $extraFormSettings = '';
/**
* Set this to something other than US to remove US-specific validations.
* @var string
*/
var $country = 'US';
/**
* Populate this array with a list of models for which validation rules
* should not be loaded.
* @var array
*/
var $modelValidationBlacklist = array();
/**
* If $modelValidationWhitelist is left as the default empty array,
* then validations for all models associated with the current controller
* (minus any listed in $modelValidationBlacklist) will be loaded.
* If one or more models are listed in $modelValidationWhitelist,
* then only those models will be used, and any additional models
* associated with the controller will be ignored.
* @var array
*/
var $modelValidationWhitelist = array();
/**
* Populate this array with a list of IDs for which validation rules
* should not be loaded. If using the $fieldIds array below, you will
* need to specify the IDs referenced in $fieldIds.
* @var array
*/
var $skipTheseIds = array();
/**
* Use this array to specify that a non-standard ID is being used for
* a given field, instead of the Cake default. Provide the cake default
* as the key and the non-default, custom ID as the value.
* For example: $fieldIds = array('ArticleBody' => 'blog_post');
* @var array
*/
var $fieldIds = array();
/**
* Set this variable to either 'create' or 'update' to properly
* suppress any validation rules from being applied to the page
* when the model's validation rules are qualified with
* 'on' => 'create' or 'on' => 'update'
* @var string
*/
var $transactionType = '';
/**
* Program variable. Stores the form settings
* @var string
*/
var $validanguageFormSettings = '';
/**
* Program variable. Stores the validanguage API code generated from the cake models
* @var string
*/
var $validanguageText = '';
/**
* getTags
*
* This method parses thru the model and builds validanguage tags for all the
* validation rules. The tags are returned as an array as follows:
* array(
* 0 => validanguageFormSettings,
* 1 => validanguageText,
* )
* @return array
*/
function getTags() {
$this->_getTags();
return array ($this->validanguageFormSettings, $this->validanguageText);
}
/**
* printTags
*
* This method parses thru the model and prints validanguage tags for all the
* validation rules.
*/
function printTags() {
$this->_getTags();
echo $this->validanguageFormSettings;
echo $this->validanguageText;
}
/**
* _getFormSettings
*
* Populates a javascript script tag with all the requested customization settings
*/
function _getFormSettings() {
// Check to make sure json_encode() is available
if ($this->apiType == 2 && !function_exists('json_encode')) {
$this->apiType = 1;
trigger_error("json_encode() PHP extension not installed. Switching to Comment API", E_USER_WARNING);
}
$this->validanguageFormSettings = "<script type=\"text/javascript\">\n";
$this->validanguageFormSettings .= " validanguage.settings.onErrorClassName = 'error-message';\n";
$this->validanguageFormSettings .= " {$this->extraFormSettings}\n";
$this->validanguageFormSettings .= "</script>\n";
}
/**
* This function pulls out a list of all the relevant parts of a rule
* which will be required by the _parseRule() method to properly translate
* the rule from CakePHP to Validanguage.
*
* @param $array1 Object
* @param $array2 Object
* @return array
*/
function _getRelevantSettings( $arr1, $arr2=array(), $arr3=array() ) {
$relevantSettingsList = array(
'on',
'message',
'allowEmpty'
);
$relevantSettings = array();
foreach ($relevantSettingsList as $setting) {
if (is_array($arr1) && isset($arr1[$setting])) $relevantSettings[$setting] = $arr1[$setting];
if (is_array($arr2) && isset($arr2[$setting])) $relevantSettings[$setting] = $arr2[$setting];
if (is_array($arr3) && isset($arr3[$setting])) $relevantSettings[$setting] = $arr3[$setting];
}
return $relevantSettings;
}
/**
* This method parses thru the model and populates validanguage tags for all the
* validation rules.
*/
function _getTags() {
$i = -1;
$validations = array();
$this->_getFormSettings();
if (empty($this->modelValidations)) $this->_loadModelValidations();
foreach ($this->modelValidations as $model=>$fields ) {
foreach ($fields as $field => $rules) {
$id = $model . Inflector::camelize($field);
if (array_key_exists($id, $this->fieldIds)) $id = $this->fieldIds[$id];
if (in_array($id, $this->skipTheseIds)) continue;
if ($this->debug) echo "<br/><br/> -- Checking $id -- <br/>";
$validations[$id] = array( 'validations' => array() );
// There must be an easier way to iterate thru all this...
if (is_array($rules)) {
if (isset($rules[0])) {
// This array must be handled as a single rule
$this->_parseRule( $field, $rules, &$validations[$id] );
} else {
foreach ($rules as $ruleName=>$ruleVal) {
if (is_array($ruleVal)) {
$relevantSettings = $this->_getRelevantSettings($rules, $ruleName, $ruleVal);
if (isset($ruleVal[0])) {
// This array must be handled as a single rule
$this->_parseRule( $ruleName, $ruleVal, &$validations[$id], $relevantSettings );
} else {
foreach ($ruleVal as $ruleName2 => $ruleVal2) {
$this->_parseRule( $ruleName2, $ruleVal2, &$validations[$id], $relevantSettings );
}
}
} else {
$relevantSettings = $this->_getRelevantSettings($ruleName, $rules, $rules[$ruleName]);
$this->_parseRule( $ruleName, $ruleVal, &$validations[$id], $relevantSettings );
}
}
}
} else {
// Single value
$this->_parseRule( $field, $rules, &$validations[$id] );
}
}
}
$apiFunc = ($this->apiType==1) ? '_outputValidanguageTags' : '_outputValidanguageObjects';
$this->{$apiFunc}($validations);
}
/**
* loadModelValidations
*
* Populates the modelValidations array with details on all the models
* assigned to the controller
*/
function _loadModelValidations() {
$models = (empty($this->modelValidationWhitelist)) ? $this->params['models'] : $this->modelValidationWhitelist;
foreach ($models as $m) {
if (in_array($m, $this->modelValidationBlacklist)) continue;
$model = new $m;
if (method_exists($model, 'loadValidation')) $model->loadValidation();
$this->modelValidations[$m] = $model->validate;
}
if ($this->debug == true) pr($this->modelValidations);
}
/**
* Converts the $validations object into the validanguage Object API
* and prints it in a script tag.
* @param $validations Object
*/
function _outputValidanguageObjects($validations) {
if ($this->debug) pr($validations);
$this->validanguageText = "<script type=\"text/javascript\">\n";
foreach( $validations as $id => $rules ) {
$this->validanguageText .= " validanguage.el['{$id}'] = " . json_encode($rules) . ";\n";
}
$this->validanguageText .= "</script>\n";
}
/**
* Converts the $validations object into the validanguage Comment API
* and prints the tags to the page
* @param $validations Object
*/
function _outputValidanguageTags($validations) {
foreach( $validations as $id => &$rules ) {
$addOns = array('minlength','maxlength','required');
foreach ($addOns as $addOn) {
if (isset($rules[$addOn])) {
$tag = "\n<!-- <validanguage target=\"{$id}\" ";
if ($addOn==='required') {
$tag .= "{$addOn}=\"true\" ";
} else {
$tag .= "{$addOn}=\"{$rules[$addOn]}\" ";
}
$tag .= " /> -->";
$this->validanguageText .= $tag;
}
unset($tag);
}
foreach( $rules['validations'] as $validation ) {
$tag = "\n<!-- <validanguage target=\"{$id}\" ";
$tag .= "validations=\"{$validation['name']}\" ";
if (isset($validation['errorMsg'])) {
$tag .= "errorMsg=\"{$validation['errorMsg']}\" ";
} else if (isset($rules['errorMsg'])) {
$tag .= "errorMsg=\"{$rules['errorMsg']}\" ";
}
$tag .= " /> -->";
$empty = "<!-- <validanguage target=\"{$id}\" /> -->\n";
if (isset($tag) && $tag !== $empty) $this->validanguageText .= $tag;
unset($tag);
}
}
}
/**
* _parseRule
*
* This method handles a single CakePHP rule and pushes the corresponding validanguage
* rule onto the $validations array.
*
* @param object $key
* @param object $val
* @param object $validations
* @param object $relevantSettings optional
*/
function _parseRule($key, $val, $validations, $relevantSettings=array() ) {
if (isset($relevantSettings['on']) && $relevantSettings['on'] !== $this->transactionType) return;
if ($this->debug) {
echo " <br/>key = $key and val = $val with ";
print_r($relevantSettings);
}
if (is_array($val) && isset($val[0])) {
// handle arrays
if ($this->debug) pr($val);
// between
if($val[0]=='between') {
$max = ($val[1]>$val[2]) ? $val[1] : $val[2];
$min = ($val[1]<$val[2]) ? $val[1] : $val[2];
if (!empty($relevantSettings['message'])) {
$newFunc = array(
'name' => "validanguage.validateMaxlength(text,{$max}), validanguage.validateMinlength(text,{$min})",
'errorMsg' => $relevantSettings['message'],
);
} else {
$validations['minlength'] = $min;
$validations['maxlength'] = $max;
}
}
// minLength/maxLength
if($val[0]=='minLength' || $val[0]=='maxLength') {
if (!empty($relevantSettings['message'])) {
$func = ($val[0]=='minLength') ? 'Minlength': 'Maxlength';
$newFunc = array(
'name' => "validanguage.validate{$func}(text,{$val[1]})",
'errorMsg' => $relevantSettings['message'],
);
} else {
$func = ($val[0]=='minLength') ? 'minlength': 'maxlength';
$validations[$func] = $val[1];
}
}
if (!empty($newFunc)) {
$validations['validations'][] = $newFunc;
} else {
$func_args = $val;
$val = array_shift($val); // Reset $val to $val[0] and check for the validations below
}
} // close if is_array()
if (($key==='required' && $val===true) || ($key==='allowEmpty' && $val===false) || ($val===VALID_NOT_EMPTY)) {
// required
if (!empty($relevantSettings['message'])) {
$newFunc = array(
'name' => 'validanguage.validateRequired',
);
} else {
$validations['required'] = true;
}
} else if ($key==='min' || $key==='max') {
// minlength/maxlength
if (!empty($relevantSettings['message'])) {
$func = ($key=='min') ? 'Minlength': 'Maxlength';
$newFunc = array(
'name' => "validanguage.validate{$func}(text,{$val})",
);
} else {
$func = ($key=='min') ? 'minlength': 'maxlength';
$validations[$func] = $val;
}
} else if (is_string($val) && (substr($val,0,1)==='/')) {
// regexes
$val = str_replace(array('\\A','\\b','\\b','\\z'),'',$val); // strip out crap that js cant use
$val = substr($val, 1); // strip out the leading and trailing slashes
$val = substr($val, 0, strrpos($val,'/') );
$val = str_replace("\\", "\\\\", $val); // escape the slashes
$val = str_replace("'", "\'", $val); // escape the apostrophes
$newFunc = array(
'name' => "validanguage.validateRegex(text, { expression: '{$val}' })",
);
} else {
// These validations are all handled easily enough
$easilyHandled = array(
'alphaNumeric' => "validateRegex(text, { expression: /[^0-9a-zA-Z]/, errorOnMatch: true })",
'blank' => "validateRegex(text, { expression: /[^\\w]/, errorOnMatch: true })",
'cc' => "validateCreditCard( {$this->validateCreditCardArgs} )",
'date' => "validateDate( {$this->validateDateArgs} )",
'email' => 'validateEmail',
'ip' => 'validateIP',
'max' => 'validateMaxlength(text,{$val[1]})',
'min' => 'validateMinlength(text,{$val[1]})',
'numeric' => 'validateNumeric',
'phone' => 'validateUSPhoneNumber',
'postal' => 'validateUSZipCode',
'ssn' => 'validateUSSSN',
'url' => 'validateURL',
);
if ($this->country !== 'US') {
unset($easilyHandled['phone']);
unset($easilyHandled['postal']);
unset($easilyHandled['ssn']);
}
foreach ($easilyHandled as $provided=>$funcName) {
if ( $val === $provided) {
if ($this->debug) echo "MATCH on $provided<br/>";
$newFunc = array(
'name' => "validanguage.{$funcName}",
);
break;
}
}
}
if (!empty($newFunc)) {
if (!empty($relevantSettings['message'])) $newFunc['errorMsg'] = $relevantSettings['message'];
$validations['validations'][] = $newFunc;
}
}
}
?>
Validanguage version 0.9.6
Here is the text of version 0.9.6 of validanguage.js. See the project's homepage for the latest version.Download code
/**
* The validanguage library was written by DrLongGhost in 2008. See attached MIT_License.js
* and readme.txt for licensing and documentation. Visit http://www.drlongghost.com/ for updates.
*
*
* @namespace Global validanguage object
* @author DrLongGhost
* @version 0.9.6
*/
var validanguage = {
/**
* Valid values are 'none', 'prototype', and 'scriptaculous'.
* @public
* @default 'none'
*/
useLibrary: 'none',
/**
* @private
*/
version: '0.9.6',
/**
* @namespace validanguage.settings object
*/
settings: {
/**
* Should an alert() be shown when a validation fails?
* By default, validanguage.showError() and validanguage.hideError() instead place the
* error msg underneath the failed field.
* @default false
*/
showAlert: false,
/**
* Should the target element of a failed validation receive focus when a validation fails?
* IMPORTANT note regarding showAlert and focusOnError. Do NOT set both of these to true if using onblur validations. Pick either one or the other.
* When you use both, it is possible to create infinite loops in which a validation failure generates an alert, triggering an onblur,
* which triggers another validation failure and subsequent alert.
* If you aren't using onblur validations at all, you can safely use both.
* @default false
*/
focusOnerror: false,
/**
* When a form is submitted, are all form fields validated, or do we stop once one fails?
* @default true
*/
validateAllFieldsOnsubmit: true,
/**
* Override this to set a global success handlers for all validation results
* If you want to use only alert messages via showAlert, set this to {} to turn off inline error msgs
* @default 'validanguage.hideError'
*/
onsuccess: 'validanguage.hideError',
/**
* Override this to set a global error handler for all validation results
* If you want to use only alert messages via showAlert, set this to {} to turn off inline error msgs
* @default 'validanguage.showError'
*/
onerror: 'validanguage.showError',
/**
* Default generic error message
* @default 'You have entered an invalid entry in the form'
*/
errorMsg: 'You have entered an invalid entry in the form',
/**
* Default error message for the validateRequired validation
* @default 'You have skipped a required field'
*/
requiredErrorMsg: 'You have skipped a required field',
/**
* Default error message for the validateMinlength validation
* @default 'The indicated field must be at least {!minlength} characters long'
*/
minlengthErrorMsg: 'The indicated field must be at least {!minlength} characters long',
/**
* Default error message for the validateMaxlength validation
* @default 'The indicated field may not be longer than {!maxlength} characters'
*/
maxlengthErrorMsg: 'The indicated field may not be longer than {!maxlength} characters',
/**
* Default error message for the validateCharacters function
* @default 'You have entered invalid characters'
*/
characterValidationErrorMsg: 'You have entered invalid characters',
/**
* Class name used in showError() to assign to the DIVs
* which are created to show the inline error msgs.
* @default 'vdError'
*/
onErrorClassName: 'vdError',
/**
* Class name used in hideError() to assign to a DIV
* which was created to show an inline error msgs which is then removed.
* @default 'vdNoError'
*/
noErrorClassName: 'vdNoError',
/**
* Class name used in hideError() to assign to a form field which passes validation
* @default 'passedField'
*/
passedFieldClassName: 'passedField',
/**
* Class name used in showError() to assign to a form field which fails validation
* @default 'failedField'
*/
failedFieldClassName: 'failedField',
/**
* Used to make the ID used in hideError() to assign to the SPAN element inside the vdError
* DIV. The errorMsgSpanSuffix is appended to the end of the form field's ID to make the SPAN ID.
* If a SPAN with this ID already exists in the DOM, it will be used. If it doesn't exist, one will
* be created dynamically.
* @default '_errorMsg'
*/
errorMsgSpanSuffix: '_errorMsg',
/**
* To display a combined list of all fields which failed validation in addition to the
* inline error msgs, set showFailedFields to true. The fields will be listed using the
* "field" attribute (or ID if field is not available).
* @default false
*/
showFailedFields: false,
/**
* The text specified in errorListText will be placed at the top of the errorDiv generated
* by the showFailedFields option in showError().
* @default '<strong>Please correct the following fields:</strong>'
*/
errorListText: '<strong>Please correct the following fields:</strong>',
/**
* Specifies the ID to be assigned to the DIV used for the showFailedFields option in showError().
* If a DIV with this ID exists in the DOM, it will be used. If it doesn't exist, one will
* be created dynamically.
* @default 'vdErrorDiv'
*/
errorDivId: 'vdErrorDiv',
/**
* Specifies the ID to be assigned to the UL used for the showFailedFields option in showError().
* @default 'vdErrorList'
*/
errorListId: 'vdErrorList',
/**
* Used to make the ID used for the showFailedFields option in showError().
* The errorListItemSuffix is appended to the end of the form field's ID to make the ID for the LI item.
* @default '_vd_li'
*/
errorListItemSuffix: '_vd_li',
/**
* Determines the ID of the DIV created in the showSubmitMessage() function used to
* replace a form's submit button once the form has been submitted.
* @default 'vdSubmitMessage'
*/
showSubmitMessageId: 'vdSubmitMessage',
/**
* Determines the text used by the showSubmitMessage() function which is used
* replace a form's submit button once the form has been submitted. If desired, you can include HTML
* or IMG tags instead of the default text.
* @default 'Loading'
*/
showSubmitMessageMessage: 'Loading',
/**
* This array is used in the validateRequired function to determine whether a select box
* has been left on the default, "empty" option. Add/Remove from this array as needed.
* @default [' ','0',' ','']
*/
emptyOptionElements: [' ','0',' ',''],
/**
* If a validation is supplied without any event handlers, how should it be treated in loadElAPI()?
* This setting also affects the behavior of the required=true and maxlength/minlength shortcuts.
* @default ['submit']
*/
defaultValidationHandlers: ['submit'],
/**
* Should any validanguage.toggle() transformations which are defined for form fields on the
* page be automatically called when the page has finished loading.
* @default true
*/
callToggleTransformationsOnload: true,
/**
* Should the toggle visibility API in validanguage.toggle() default to "hidden" if a given target
* does not satisfy any provided "visible" conditions? If you set this to false, you will need to
* explicitly provide the desired "hidden" conditions.
*/
toggleVisibilityDefaultsToHidden: true,
/**
* Should the HTML document be scanned for validanguage comment tags?
* Set this to false if you arent using the comment API for better performance.
* @default true
*/
loadCommentAPI: true,
/**
* Determines the delimeter used in the loadCommentAPI() function to split up each
* comment into multiple validanguage tags.
* You probably want to keep this as "\n" to be safe, but if you want to be allowed
* to use carriage returns inside validanguage comment tags, you can set this to
* "/>" if you are careful to always close your validanguage tags
* @default "\n"
*/
commentDelimiter: "\n",
/**
* Color for the textbox to flash when invalid input is entered. The default is light red.
* Set this to empty to turn flashing off.
* @default '#FF6666'
*/
validationErrorColor : '#FF6666',
/**
* Normal color of the textbox. The default is empty. Used in conjunction with validationErrorColor
* to make the textboxes flash.
* @default ''
*/
normalTextboxColor : '',
/**
* Amount of time the text box flashes the validationErrorColor. The default is 100ms
* @default 100
*/
timeDelay : 100,
/**
* Typing delay for the ontyping event. This is the amount of time between keystrokes
* that must elapse before the event fires. The default is just over 1 second.
* @default 1100
*/
typingDelay: 1100,
/**
* Should the validateRequiredAlternatives function be assigned onclick to radio buttons
* and checkboxes named as "requiredAlternatives"? Setting this to true ensures that
* checking/unchecking a radio button or checkbox will correctly call showError/hideError.
* @default true
*/
validateRequiredAlternativesOnclick: true,
/**
* Defines the default behavior of the validateRegex function.
* Is a match against the regex an error or a success?
* @default false
*/
errorOnMatch: false,
/**
* Override this to setup a function to run after all validanguage form fields have
* been intialized inside the populate() function. The default is an empty function.
* @default function() { }
*/
onload: function() { },
//dummy field I put here so the onload above will have a comma after it
foo: ''
},
//PRIVATE PROGRAM VARIABLES
alertCounter: true, //this counter prevents infinite loops from being created between alerts() and onblur handlers
el: {},
forms: {},
formLookup: {}, //hash table to map form element IDs to the ID of the parent form.
requiredAlternatives: [], //hash table used to store requiredAlternatives associations
supportedEvents: ['blur','change','keypress','keyup','keydown','submit','click','typing'],
supportedEventHandlers: ['onblur','onchange','onkeypress','onkeyup','onkeydown','onsubmit','onclick','ontyping'],
typingDelay: [], //hash table to store ontyping timeouts
/**
* Generic cross-browser addEvent() function.
*
* @param {Object} Object to receive the event
* @param {Object} Event type
* @param {Object} Function to be called
*/
addEvent: function(obj, event, func){
if (obj.addEventListener) {
obj.addEventListener(event, func, false);
return true;
} else if (obj.attachEvent){
var newEvent = obj.attachEvent("on"+event, func);
return newEvent;
}
},
/**
* Reassigns the validanguage.addEvent function, if an external library is being used.
*/
addEventInit: function() {
switch ( this.useLibrary ) {
case 'prototype':
case 'scriptaculous':
//reassign the addEvent function to use Event.observe
this.addEvent = function(obj, evtHandler, func){
Event.observe(obj, evtHandler, func);
}
break;
}
},
/**
* This function wraps multiple validanguage.el.elemId.validations event handlers
* and transformations within a single wrapper to call all loaded validations/transformations
* and exit as soon as a validation returns false.
*
* @param {Object} Form element object
* @param {string} eventType, such as "blur" or "keydown"
* @param {integer} validationsCounter, denotes the array index of this item in
* validanguage.el.elemId.validations
*/
addOrCreateValidationWrapper: function( Obj, eventType, validationsCounter ) {
var id = Obj.id;
if (eventType == 'submit') {
if (this.empty(validationsCounter)) return; // exit early for onsubmit transformations
var formId = validanguage.formLookup[id];
if (typeof formId == 'number') {
var form = document.forms[formId];
} else {
var form = document.getElementById(formId);
}
if (typeof validanguage.forms[formId].validations == 'undefined') {
validanguage.forms[formId].validations = [];
this.addEvent(form, eventType, function(e) {
var evt = e || window.evt;
var result = validanguage.validationWrapper(e);
if (result == false) {
evt.returnValue = false; //IE
if (evt.preventDefault) evt.preventDefault(); //Everyone else
return false;
} else {
return true;
}
});
}
//add the element and validationsCounter to the list of onsubmit validations for the parent form
validanguage.forms[formId].validations[validanguage.forms[formId].validations.length] = { element: Obj, validationsCounter: validationsCounter };
} else {
if( typeof validanguage.el[id].handlers == 'undefined' ) validanguage.el[id].handlers = {};
if( typeof validanguage.el[id].handlers[eventType] == 'undefined' ) {
validanguage.el[id].handlers[eventType] = [];
if( eventType == 'typing') {
this.addEvent(Obj, 'keyup', function(e){ validanguage.validationWrapper(e, 'typingTimeout'); });
} else {
this.addEvent(Obj, eventType, function(e){ validanguage.validationWrapper(e); });
}
}
//add validationsCounter to the list of validations for this object/eventType combo
validanguage.el[id].handlers[eventType][validanguage.el[id].handlers[eventType].length] = validationsCounter;
}
},
/**
* This function is used to either load a new validation for a form field, or to
* reactivate a validation previously removed with the removeValidation() method.
*
* NOTE: When adding a new validation, you will need to have previously inserted
* all the relevant details about the validation in the validanguage.el.formField
* object.
*
* @param {String} elemId
* @param {String/Array} eventTypes
* @param {String/Array/Function} validationNames
*/
addValidation: function ( elemId, eventTypes, validationNames ) {
if( typeof validationNames[0]=='undefined' ) validationNames = [ validationNames ];
if( typeof eventTypes=='string' ) eventTypes = [ eventTypes ];
var vals = this.el[elemId].validations;
for (var i = vals.length - 1; i > -1; i--) {
if ( validationNames[0] == '*' || this.inArray(vals.name, validationNames) ) {
for( var j=eventTypes.length-1; j>-1; j--) {
this.addOrCreateValidationWrapper(document.getElementById(elemId), eventTypes[j], i);
}
}
}
},
/**
* Very simple AJAX function
* @param {String} url
* @param {Function} callback
*/
ajax: function( url, callback ) {
validanguage.ajaxObj.open("POST", url, true);
this.ajaxCallback = callback;
this.ajaxObj.onreadystatechange = function() {
if(validanguage.ajaxObj.readyState==4){
validanguage.ajaxCallback(validanguage.ajaxObj.responseText)
}
};
this.ajaxObj.send(null);
},
/**
* Initializes validanguage.ajax as browser-specific
*/
ajaxInit: function() {
if(window.ActiveXObject){
this.ajaxObj = new ActiveXObject("Microsoft.XMLHTTP");
} else if(window.XMLHttpRequest){
this.ajaxObj = new XMLHttpRequest();
}
},
/**
* Combines 2 node lists into 1
* @param {Object} obj1
* @param {Object} obj2
*/
concatCollection: function(obj1,obj2) {
var i;
var arr = new Array();
var len1 = obj1.length;
var len2 = obj2.length;
for (i=0; i<len1; i++) {
arr.push(obj1);
}
for (i=0; i<len2; i++) {
arr.push(obj2);
}
return arr;
},
/**
* Emulates PHP's empty() function. For convenience, you can specify whether
* boolean false is considered empty. Defaults to false is NOT empty.
* Ignores functions.
*
* @param {Object} testVar
* @param {bool} falseIsEmpty
*/
empty: function ( testVar, falseIsEmpty ) {
if( testVar == null || testVar == undefined || testVar == NaN || (testVar =='' && typeof testVar == 'string') ) return true;
if( falseIsEmpty==true && testVar==false) {
return true;
}
if(typeof testVar == 'object') {
for (var i in testVar) {
if( typeof testVar == 'function' ) continue;
if( validanguage.empty(testVar, falseIsEmpty)==false ) {
return false;
}
}
return true;
} else {
return false;
}
},
/**
* This is a preset transformation which is used to reformat text input
* to match a desired pattern
* @param {String} Pattern using x to represent alphanumeric characters.
* For example: "(xxx) xxx-xxxx"
* @param {String} String listing any characters to be removed from the
* form field's value prior to potential reformatting
* For example: "()- "
* @param {String/Regex} Regular expression which, if provided, will be used
* to determine whether or not to proceed with reformatting.
* If not provided, the function will only reformat if the number
* of characters in the form field (after stripThese is applied)
* matches the number of x's in the provided pattern
*/
format: function( pattern, stripThese, regexMatch ) {
var text = this.value;
if(stripThese!=null && typeof stripThese=='string') {
var i = stripThese.length;
for( var i=stripThese.length-1; i>-1; i-- ) {
while (text.indexOf(stripThese.charAt(i)) != -1) {
text = text.replace(stripThese.charAt(i),'','g');
}
}
}
if( regexMatch!=null ) {
var myreg = (typeof regexMatch=='string') ? new RegExp(regexMatch) : regexMatch;
var thisMatch = myreg.exec(text);
if (thisMatch == null) return; //exit early for no match
} else {
//check for required length based on number of x's in the pattern
var countMe = pattern.replace(/[^x]/g,'');
if( text.length != countMe.length ) return;
}
var i = pattern.length;
var k = -1; //counter for text
var newtext = '';
for( var j=0; j<i; j++ ) {
newtext += (pattern.charAt(j)=='x') ? text.charAt(++k) : pattern.charAt(j);
}
this.value = newtext;
},
/**
* This function is one big ass switch case to look up a char code
* for the supplied character
* @param {String} suppliedCharacter
*/
getCharCode: function( suppliedCharacter ){
switch(suppliedCharacter){
case ' ': return '32';
case '!': return '33';
case '"': return '34';
case '#': return '35';
case '$': return '36';
case '%': return '37';
case '&': return '38';
case "'": return '39';
case '(': return '40';
case ')': return '41';
case '*': return '42';
case '+': return '43';
case ',': return '44';
case '-': return '45';
case '.': return '46';
case '/': return '47';
case '0': return '48';
case '1': return '49';
case '2': return '50';
case '3': return '51';
case '4': return '52';
case '5': return '53';
case '6': return '54';
case '7': return '55';
case '8': return '56';
case '9': return '57';
case ':': return '58';
case ';': return '59';
case '<': return '60';
case '=': return '61';
case '>': return '62';
case '?': return '63';
case '@': return '64';
case 'A': return '65';
case 'B': return '66';
case 'C': return '67';
case 'D': return '68';
case 'E': return '69';
case 'F': return '70';
case 'G': return '71';
case 'H': return '72';
case 'I': return '73';
case 'J': return '74';
case 'K': return '75';
case 'L': return '76';
case 'M': return '77';
case 'N': return '78';
case 'O': return '79';
case 'P': return '80';
case 'Q': return '81';
case 'R': return '82';
case 'S': return '83';
case 'T': return '84';
case 'U': return '85';
case 'V': return '86';
case 'W': return '87';
case 'X': return '88';
case 'Y': return '89';
case 'Z': return '90';
case '[': return '91';
case '\\': return '92';
case ']': return '93';
case '^': return '94';
case '_': return '95';
case '`': return '96';
case 'a': return '97';
case 'b': return '98';
case 'c': return '99';
case 'd': return '100';
case 'e': return '101';
case 'f': return '102';
case 'g': return '103';
case 'h': return '104';
case 'i': return '105';
case 'j': return '106';
case 'k': return '107';
case 'l': return '108';
case 'm': return '109';
case 'n': return '110';
case 'o': return '111';
case 'p': return '112';
case 'q': return '113';
case 'r': return '114';
case 's': return '115';
case 't': return '116';
case 'u': return '117';
case 'v': return '118';
case 'w': return '119';
case 'x': return '120';
case 'y': return '121';
case 'z': return '122';
case '{': return '123';
case '|': return '124';
case '}': return '125';
case '~': return '126';
} //close switch
return '';
},
/**
* Fetches all comment nodes in the passed form node and returns them in a node list
* Doesnt work in konqueror, since konqueror strips all comments from the DOM
*
* @param {Containing Node} el
*/
getComments: function(el) {
if (!el) el = document.documentElement;
var comments = new Array();
var length = el.childNodes.length;
for (var c = 0; c < length; c++) {
if (el.childNodes[c].nodeType == 8) {
comments[comments.length] = el.childNodes[c];
} else if (el.childNodes[c].nodeType == 1) {
comments = comments.concat(this.getComments(el.childNodes[c]));
}
}
return comments;
},
/**
* Helper function used by validateDate() and validateTimestamp().
* @param {Object} options object provided by the user to validateDate() or validateTimestamp().
* @param {Object} defaults which should be used. Used to allow validateDate() and validateTimestamp()
* to have different default dateOrder values.
*/
getDateTimeDefaultOptions: function ( options, defaults ) {
if( options==null ) options = {};
// Date options
if( typeof options.dateOrder=='undefined' ) options.dateOrder=defaults.dateOrder;
options.dateOrder = options.dateOrder.toLowerCase();
if( typeof options.allowedDelimiters=='undefined' || typeof options.allowedDelimiters!='string' ) options['allowedDelimiters'] = './-';
if( typeof options.twoDigitYearsAllowed=='undefined' ) options.twoDigitYearsAllowed = false;
if( typeof options.oneDigitDaysAndMonthsAllowed=='undefined' ) options.oneDigitDaysAndMonthsAllowed = true;
if( typeof options.maxYear=='undefined' ) options.maxYear = new Date().getFullYear() + 15;
if( typeof options.minYear=='undefined' ) options.minYear = 1900;
if( typeof options.rejectDatesInTheFuture=='undefined' ) options.rejectDatesInTheFuture = false;
if( typeof options.rejectDatesInThePast=='undefined' ) options.rejectDatesInThePast = false;
// Time options
if( typeof options.timeIsRequired=='undefined' ) options.timeIsRequired = false;
if( typeof options.timeUnits=='undefined' ) options.timeUnits = 'hms';
if( typeof options.microsecondPrecision=='undefined' ) options.microsecondPrecision = 6;
return options;
},
/**
* This function checks for a given setting in increasing specificity
* within the validanguage.forms[formId].settings object, and within the passed
* validanguage.el objects
*
* @param {string} Name of the setting to be retrieved
* @param {string} ID of the form field object being validated
* @param {Object} validanguage.el.objId.validations[index] object
*/
getElSetting: function( setting, id, validationObj ) {
var formSetting = this.getFormSettings(id);
var retVal = formSetting[setting]; //global setting
if( typeof validationObj!='undefined' && typeof validationObj[setting] != 'undefined' ) {
retVal = validationObj[setting];
} else if( typeof this.el[id][setting] != 'undefined' ) {
retVal = this.el[id][setting];
}
return retVal;
},
/**
* This function returns the validanguage.form[formId].setting object for the passed element ID
* @param {string or Node} id of the input field or input node
* @return {Object} settings object
*/
getFormSettings: function(id) {
var formName = ( document.getElementById(id).nodeName.toLowerCase()=='form' ) ?
id : this.formLookup[id];
return this.forms[formName].settings;
},
/**
* This function parses the passed comment to retrieve the indicated setting
*
* @param {String} Name of the setting to retrieve / needle
* @param {String} Full text of the HTML comment / haystack
* @return {String} The value of the requested setting
*/
getSettingFromComment: function( setting, comment ) {
var needle = ' '+setting+'=';
var startPos = comment.indexOf(needle);
if( startPos == -1) return null;
var delimiterPos = (startPos*1) + (needle.length*1);
var delimeter = '\\' + comment.charAt(delimiterPos);
var Regex = needle+delimeter+'(.+?)'+delimeter;
var myreg = new RegExp(Regex);
var thisMatch = myreg.exec(comment, 'gi');
if (thisMatch == null) {
return null; //no match
} else if (thisMatch[1]) {
//Convert booleans. I hope this doesnt screw anyone later....
if(thisMatch[1]=='true') thisMatch[1]=true;
if(thisMatch[1]=='false') thisMatch[1]=false;
return thisMatch[1];
}
},
/**
* This function hides the div containing the validanguage error messages for
* failed validations
*/
hideError: function() {
var settings = validanguage.getFormSettings(this.id);
var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix);
if (errorDisplay != null) {
errorDisplay.innerHTML = '';
var errorDiv = errorDisplay.parentNode;
errorDiv.style.display = 'none';
errorDiv.className = settings.noErrorClassName;
}
if (! this.className.match(validanguage.settings.passedFieldClassName)) this.className += ' '+validanguage.settings.passedFieldClassName;
if (this.className.match(validanguage.settings.failedFieldClassName)) this.className = this.className.replace(validanguage.settings.failedFieldClassName,'');
//Do we need to remove any vd_li items?
if( !settings.showFailedFields ) return;
if( document.getElementById(this.id + settings.errorListItemSuffix) != null ) {
var errorList = document.getElementById(settings.errorListId);
errorList.removeChild( document.getElementById(this.id + settings.errorListItemSuffix) );
if( errorList.getElementsByTagName('LI').length==0 )
document.getElementById(settings.errorDivId).style.display='none';
}
},
/**
* Determines whether the passed item is present in the array or object.
*
* @param {Object} needle
* @param {Object} haystack
*/
inArray: function( needle, haystack ) {
for( var i=haystack.length-1; i>-1; i-- ){
if( haystack===needle ) return true;
}
return false;
},
/**
* This function searches settingsHaystack for all variables defined in the settingsNeedles
* array, and if they are located, they are copied over to the settingsTarget
*
* @param {Object} settingsHaystack -- Object location to be searched for settings
* @param {Array} settingsNeedles -- Array of settings to be checked
* @param {Object} settingsTarget -- Object location where any defined settings should be copied to
* @param {String} constrainType -- Optional type constraint
*/
inheritIfDefined: function ( settingsHaystack, settingsNeedles, settingsTarget, constrainType ) {
if( typeof settingsNeedles.length == 'undefined' ) return false;
for( var i=settingsNeedles.length-1;i>-1;i--) {
if ( typeof settingsHaystack[settingsNeedles]!='undefined' &&
( this.empty(constrainType) || typeof settingsHaystack[settingsNeedles]==constrainType )
) {
settingsTarget[settingsNeedles] = settingsHaystack[settingsNeedles];
}
}
},
/**
* Initialization function for validanguage. Adds the onload hook
* which fires off the populate() method to add all the other event
* handlers
*/
init: function() {
this.addEventInit();
this.ajaxInit();
this.addEvent(window, 'load', function() {
validanguage.populate.call(validanguage);
});
},
/**
* Function to insert 1 Node after another in the DOM. If the referenceNode
* is a label, this function will use the nextSibling instead
*
* @param {Node} nodeToAdd
* @param {Node} referenceNode
*/
insertAfter: function (nodeToAdd, referenceNode ) {
if (referenceNode.nextSibling) {
if (referenceNode.nextSibling.nodeName.toLowerCase() == 'label') {
referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling.nextSibling);
} else {
referenceNode.parentNode.insertBefore(nodeToAdd, referenceNode.nextSibling);
}
} else {
referenceNode.parentNode.appendChild(nodeToAdd);
}
},
/**
* This function parses all comments in the current document, looking for
* the comment-based API and converts any validanguage statements it
* finds into the element/json-based API for further processing.
*
* @param {Array} For konqueror, we pass this function an Array with all
* the comments (retrieved via AJAX)
* For all other browsers, konquerorComments is undefined and
* we retrieve the comments normally via the DOM
*/
loadCommentAPI: function( konquerorComments ) {
var supportedSettings = ['mode','expression','suppress','onsubmit','onblur','onchange',
'onkeypress','onkeyup','onkeydown','onclick', 'ontyping',
'errorMsg','onerror','onsuccess','focusOnError',
'showAlert','required','requiredAlternatives',
'maxlength','minlength','regex','field',
'errorOnMatch','modifiers','transformations','validations'];
var allComments = (this.empty(konquerorComments)) ? this.getComments() : konquerorComments;
var length = allComments.length;
for (var j=0; j<length; j++) {
var singleComment = (this.empty(konquerorComments)) ? allComments[j].nodeValue : allComments[j];
var tagArray = singleComment.split(validanguage.settings.commentDelimiter);
var tagArrayLength = tagArray.length;
for (var a=0; a<tagArrayLength; a++) {
var commentText = tagArray[a];
commentText = commentText.replace(/\n/g,'');
commentText = commentText.replace(/\r/g,'');
var isValidanguageRegEx = /<validanguage/i;
if (isValidanguageRegEx.test(commentText)) {
//get the targets
var targets = this.getSettingFromComment('target', commentText);
var settings = []; //reset settings
if (this.empty(targets, true))
continue;
targets = this.resolveArray(targets, 'string');
for (var k = supportedSettings.length - 1; k > -1; k--) {
var tempSetting = this.getSettingFromComment(supportedSettings[k], commentText);
if (!(tempSetting == null || (typeof tempSetting == 'string' && tempSetting == '') ))
settings[supportedSettings[k]] = tempSetting;
}
//iterate thru our targets and assign the settings
k = targets.length;
for (var l = 0; l < k; l++) {
var id = targets[l];
var obj = document.getElementById(id);
if (typeof this.el[id] == 'undefined' || obj == null)
this.el[id] = {};
/** CHARACTER VALIDATION **/
if (typeof settings.expression != 'undefined') {
this.el[id].characters = {};
this.inheritIfDefined(settings, ['expression','errorMsg','mode','suppress','onerror','onsuccess'], this.el[id].characters);
this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].characters);
}
/** REGEX **/
if (typeof settings.regex != 'undefined') {
this.el[id].regex = { expression: settings.regex };
this.inheritIfDefined(settings, ['errorOnMatch','modifiers'], this.el[id].regex);
this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].regex);
}
/** MISC SETTINGS **/
// Only inherit event handlers that are non-boolean transformations
this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id], 'string');
this.inheritIfDefined(settings, ['minlength','maxlength','requiredAlternatives','required','focusOnError','showAlert',
'onsuccess','onerror','errorMsg'], this.el[id]);
if (typeof settings.minlength != 'undefined') {
this.el[id].minlengthEvents = {};
this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].minlengthEvents);
}
if (typeof settings.maxlength != 'undefined') {
this.el[id].maxlengthEvents = {};
this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].maxlengthEvents);
}
if (typeof settings.required != 'undefined') {
this.el[id].requiredEvents = {};
this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].requiredEvents);
}
/** VALIDATIONS AND TRANSFORMATIONS **/
if (typeof this.el[id].validations == 'undefined') this.el[id].validations = [];
if (typeof this.el[id].transformations == 'undefined') this.el[id].transformations = [];
var functionModifiers = ['focusOnError','showAlert','onsuccess','onerror','errorMsg'];
//Load validations
if( typeof settings.validations != 'undefined' && !this.empty(settings.validations) ) {
this.el[id].validations[this.el[id].validations.length] = {};
this.el[id].validations[this.el[id].validations.length-1].name = settings.validations;
this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].validations[this.el[id].validations.length-1]);
this.inheritIfDefined(settings, functionModifiers, this.el[id].validations[this.el[id].validations.length-1]);
}
//Load transformations
if( typeof settings.transformations != 'undefined' && !this.empty(settings.transformations) ) {
this.el[id].transformations[this.el[id].transformations.length] = {};
this.el[id].transformations[this.el[id].transformations.length-1].name = settings.transformations;
this.inheritIfDefined(settings, this.supportedEventHandlers, this.el[id].transformations[this.el[id].transformations.length-1]);
}
} // foreach (targets)
} // close if(validanguage_comment)
} // close tagArray loop
} // close allComments loop
},
/**
* This function parses the validanguage.el object to load all the
* form-element-specific validation settings which the end user has defined
* via the Object-based API
*/
loadElAPI: function() {
for( var elem in this.el ) { //for each element....
//skip to the next if it's not an element ID
try { if( typeof document.getElementById(elem) == undefined || this.empty(document.getElementById(elem)) ) continue; } catch(e) { continue; }
var Obj = document.getElementById(elem);
var settings = validanguage.getFormSettings(elem);
if (typeof this.el[elem].validations == 'undefined') this.el[elem].validations = [];
if (typeof this.el[elem].field == 'undefined') this.el[elem].field = elem;
/** REQUIRED **/
if (typeof this.el[elem].required != 'undefined' && this.el[elem].required==true) {
this.el[elem].validations[this.el[elem].validations.length] = {};
this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateRequired';
this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = (typeof this.el[elem].errorMsg=='undefined') ? settings.requiredErrorMsg : this.el[elem].errorMsg;
this.inheritIfDefined( this.el[elem], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] );
//If specific requiredEvents are provided, use those instead of the element level event handlers
if( typeof this.el[elem]['requiredEvents']!='undefined') this.inheritIfDefined( this.el[elem]['requiredEvents'], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] );
//We need to call the validateRequiredAlternatives function when a requiredAlternative is clicked
if(settings.validateRequiredAlternativesOnclick==true && typeof this.el[elem].requiredAlternatives != 'undefined' ) {
var onsuccessFuncs = (typeof this.el[elem].onsuccess!='undefined') ? this.el[elem].onsuccess : settings.onsuccess;
var onerrorFuncs = (typeof this.el[elem].onerror!='undefined') ? this.el[elem].onerror : settings.onerror;
var alts = this.resolveArray(this.el[elem].requiredAlternatives,'string');
for( var y=alts.length-1; y>-1; y--) {
this.requiredAlternatives[alts[y]] = {};
if( !((typeof document.getElementById(alts[y]).type != 'undefined') && (document.getElementById(alts[y]).type=='checkbox'||document.getElementById(alts[y]).type=='radio')) ) continue;
this.requiredAlternatives[alts[y]].onsuccess = onsuccessFuncs;
this.requiredAlternatives[alts[y]].onerror = onerrorFuncs;
this.requiredAlternatives[alts[y]].errorMsg = (typeof this.el[elem].errorMsg=='undefined') ? settings.requiredErrorMsg : this.el[elem].errorMsg;
this.requiredAlternatives[alts[y]].parentId = elem;
this.addEvent( document.getElementById(alts[y]), 'click', function(e) { validanguage.validateRequiredAlternatives(e); } );
}
}
}
/** REGEX **/
if (typeof this.el[elem].regex != 'undefined') {
this.el[elem].validations[this.el[elem].validations.length] = {};
this.el[elem].validations[this.el[elem].validations.length - 1].name = 'validanguage.validateRegex';
var errorMsg = (typeof this.el[elem].errorMsg == 'undefined') ? settings.errorMsg : this.el[elem].errorMsg;
if(typeof this.el[elem].regex.errorMsg != 'undefined') errorMsg = this.el[elem].regex.errorMsg
this.el[elem].validations[this.el[elem].validations.length - 1].errorMsg = errorMsg;
this.inheritIfDefined(this.el[elem], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length - 1]);
this.inheritIfDefined(this.el[elem].regex, this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length - 1]);
if(typeof this.el[elem].regex.errorOnMatch=='undefined') this.el[elem].regex.errorOnMatch=settings.errorOnMatch;
}
/** MAXLENGTH **/
if (typeof this.el[elem].maxlength != 'undefined') {
this.el[elem].validations[this.el[elem].validations.length] = {};
this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateMaxlength';
this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.maxlengthErrorMsg.replace('{!maxlength}',this.el[elem].maxlength);
//If specific maxlengthEvents are provided, use those instead of the element level event handlers
if( typeof this.el[elem]['maxlengthEvents']!='undefined') this.inheritIfDefined( this.el[elem]['maxlengthEvents'], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] );
}
/** MINLENGTH **/
if (typeof this.el[elem].minlength != 'undefined') {
this.el[elem].validations[this.el[elem].validations.length] = {};
this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateMinlength';
this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.minlengthErrorMsg.replace('{!minlength}',this.el[elem].minlength);
//If specific minlengthEvents are provided, use those instead of the element level event handlers
if( typeof this.el[elem]['minlengthEvents']!='undefined') this.inheritIfDefined( this.el[elem]['minlengthEvents'], this.supportedEventHandlers, this.el[elem].validations[this.el[elem].validations.length-1] );
}
/** CHARACTERS **/
if (typeof this.el[elem].characters != 'undefined' &&
typeof this.el[elem].characters.mode != 'undefined' &&
typeof this.el[elem].characters.expression != 'undefined'
) {
//supported shortcuts
var expression = this.el[elem].characters.expression;
expression = expression.replace('alphaUpper','ABCDEFGHIJKLMNOPQRSTUVWXYZ');
expression = expression.replace('alphaLower','abcdefghijklmnopqrstuvwxyz');
expression = expression.replace('alpha','abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
expression = expression.replace('numeric','0123456789');
this.el[elem].characters.characterExpression = expression;
var validanguageExpr = ';';
for (var j=expression.length-1;j>-1;j--){
validanguageExpr += expression.charCodeAt(j) + ';';
}
this.el[elem].characters.expression = validanguageExpr;
if(typeof this.el[elem].characters.suppress=='undefined' || this.el[elem].characters.suppress==true) this.addEvent(Obj, "keypress", validanguage.validateKeypress );
//Should we assign handlers? -- onblur, onsubmit, etc
var assignHandlers = false;
for( var z=this.supportedEventHandlers.length-1; z>-1; z--) {
if(typeof this.el[elem].characters[this.supportedEventHandlers[z]] != 'undefined' && this.el[elem].characters[this.supportedEventHandlers[z]]==true) assignHandlers = true;
}
if( assignHandlers ) {
this.el[elem].validations[this.el[elem].validations.length] = {};
this.el[elem].validations[this.el[elem].validations.length-1].name = 'validanguage.validateCharacters';
this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = true;
for( var z=this.supportedEventHandlers.length-1; z>-1; z--) {
if(typeof this.el[elem].characters[this.supportedEventHandlers[z]] != 'undefined' && this.el[elem].characters[this.supportedEventHandlers[z]]==true) this.el[elem].validations[this.el[elem].validations.length-1][this.supportedEventHandlers[z]] = true;
}
//assign onerror
if(typeof this.el[elem].characters.errorMsg != 'undefined') {
this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = this.el[elem].characters.errorMsg;
} else {
this.el[elem].validations[this.el[elem].validations.length-1].errorMsg = settings.characterValidationErrorMsg;
}
}
}
if (typeof this.el[elem].transformations == 'undefined') this.el[elem].transformations = [];
/** TRANSFORMATIONS **/
//First, check for transformations listed by event type
var j = this.supportedEventHandlers.length;
for (var k = 0; k < j; k++) {
var handler = this.supportedEventHandlers[k];
if( typeof this.el[elem][handler] != 'undefined' && typeof this.el[elem][handler] != 'boolean' ) {
//Add the defined transformation to the transformations array
this.el[elem].transformations[this.el[elem].transformations.length] = {};
var n = this.el[elem].transformations.length - 1;
this.el[elem].transformations[n].name = this.el[elem][handler];
//store the event handler
this.el[elem].transformations[n][handler] = true;
}
}
var h = this.el[elem].transformations.length;
for (var i = 0; i < h; i++) {
//for each transformation, load the appropriate function in the element's transformations array
var eventLoaded = false;
var j = this.supportedEvents.length;
for (var k = 0; k < j; k++) {
if(this.supportedEvents[k]=='submit') continue;
if (typeof this.el[elem].transformations['on' + this.supportedEvents[k]] != 'undefined' && this.el[elem].transformations['on' + this.supportedEvents[k]] == true) {
eventLoaded = true;
this.addOrCreateValidationWrapper(Obj, this.supportedEvents[k]);
}
}
//if they didnt supply any events, we default to the defined defaultTransformationsHandlers (usually only "blur")
if (eventLoaded == false) {
if (Obj.nodeName.toLowerCase() == 'form') {
this.addOrCreateValidationWrapper(Obj, 'submit');
} else {
for (var l = settings.defaultTransformationsHandlers.length - 1; l > -1; l--) {
this.addOrCreateValidationWrapper(Obj, settings.defaultTransformationsHandlers[l]);
}
}
}
}
/** VALIDATIONS **/
if (typeof this.el[elem].validations != 'undefined') {
var h = this.el[elem].validations.length;
for (var i = 0; i < h; i++) {
//for each validation, load the appropriate function in the element's validations array
var eventLoaded = false;
var j = this.supportedEvents.length;
for (var k = 0; k < j; k++) {
if (typeof this.el[elem].validations['on' + this.supportedEvents[k]] != 'undefined' && this.el[elem].validations['on' + this.supportedEvents[k]] == true) {
eventLoaded = true;
this.addOrCreateValidationWrapper(Obj, this.supportedEvents[k], i);
}
}
//if they didnt supply any events, we default to the defined defaultValidationHandlers (usually only "submit")
if (eventLoaded == false) {
for( var l=settings.defaultValidationHandlers.length-1; l>-1;l--) {
this.addOrCreateValidationWrapper(Obj, settings.defaultValidationHandlers[l], i);
}
}
}
}
} //close elem loop
},
/**
* This function searches the passed subject and returns an Array of strings
* which are delimited by the characters passed to the function in the
* first 2 arguments. Used to pull comments from the document source
* @param {String} startChar
* @param {String} endChar
* @param {String} subject
* @return {Array}
*/
parseSubstring: function( startChar, endChar, subject ) {
var matches = [];
var parts = subject.split(startChar);
for( var i=0; i<parts.length; i++) {
var endPos = parts.indexOf(endChar);
if( endPos != -1) matches.push( parts.substring(0, endPos) );
}
return matches;
},
/**
* Main function to be called onload to load all the validations
*/
populate: function(){
this.sniffBrowser();
if( this.browser=='ie5' ) return; //There's no way I'm supporting IE5, so it's safest to just not run validanguage at all
//this.ajaxInit();
/**
* Iterate thru all the form elements on the page to populate
* the formLookup hash table and load the default settings
**/
var forms = document.getElementsByTagName('form');
for (var i=0, j=forms.length; i<j; i++) {
var formName = (this.empty(forms.id)) ? i : forms.id;
this.forms[formName] = { settings: this.settings };
var allInputs = forms.getElementsByTagName('input');
var allTextareas = forms.getElementsByTagName('textarea');
var allSelect = forms.getElementsByTagName('select');
var allObjects = this.concatCollection(allInputs, allTextareas);
var allObjects = this.concatCollection(allObjects, allSelect);
var radios = {}; // hash lookup to store radio buttons we've seen
for (var k=allObjects.length-1; k>-1; k--) {
//iterate thru the array and store the id in our formLookup hash table
if (typeof(allObjects[k].id) != 'undefined' && !this.empty(allObjects[k].id)) {
this.formLookup[allObjects[k].id] = formName;
}
}
}
if (this.browser == 'konqueror' && this.settings.loadCommentAPI == true) {
this.ajax(document.location.href, function(docText) {
var comments = validanguage.parseSubstring( '<!--', '-->', docText );
validanguage.loadCommentAPI( comments );
if (typeof validanguage.overloadFormSettings != 'undefined') validanguage.overloadFormSettings();
if (typeof validanguage.el != undefined && !this.empty(validanguage.el)) validanguage.loadElAPI();
});
} else {
//Load comment API
if (this.settings.loadCommentAPI == true) this.loadCommentAPI( );
//Load Form-Specific Settings
if (typeof this.overloadFormSettings != 'undefined') this.overloadFormSettings();
//Load the validanguage.el API
if (typeof this.el != undefined && !this.empty(this.el)) this.loadElAPI();
}
// Call any defined validanguage.toggle() functions to true up the UI
if (this.settings.callToggleTransformationsOnload) {
for (var id in this.el) {
if (typeof this.el[id].transformations != 'undefined') {
for (var i = this.el[id].transformations.length - 1; i > -1; i--) {
if (typeof this.el[id].transformations.name == 'undefined')
continue;
var funcString = this.el[id].transformations.name;
if (typeof funcString == 'string' && funcString.indexOf('validanguage.toggle') > -1) {
var transformations = this.resolveArray(funcString, 'function');
var j = transformations.length;
for (var k = 0; k < j; k++) {
transformations[k].call(document.getElementById(id));
}
}
}
}
}
}
//Garbage collection
this.addEvent(window, 'unload', function() { delete validanguage; });
//Call any onload handler defined by the user
this.settings.onload.call(this);
}, //close populate
/**
* This function is used to deactivate a previously loaded validation.
* Provide the element ID of the field and a list of event types and validation
* names to deactivate. You can use the * character as the eventType and/or
* the validationName arguments to include ALL eventTypes/validationNames.
*
* NOTE: if you are tempted to use removeValidation('id','*','*'), you may be
* better off using validanguage.el.id.disabled=true, as this is much easier to
* undo later.
*
* NOTE: It is up to you to make sure no error msgs are displaying before disabling
* a validation. If one is showing and all validations are disabled, the onsuccess
* handlers used to clear the error msgs will never be called.
*
* @param {String} elemId
* @param {String/Array} eventTypes
* @param {String/Array/Function} validationNames
*/
removeValidation: function ( elemId, eventTypes, validationNames ) {
//prep our arguments
if( eventTypes == '*' ) {
eventTypes = this.supportedEvents;
} else if( typeof eventTypes[0]=='undefined') {
eventTypes = [ eventTypes ];
}
if( typeof validationNames=='string' ) validationNames = [ validationNames ];
for (var j = eventTypes.length - 1; j > -1; j--) {
if (eventTypes[j] == 'submit') {
//Remove form.onsubmit validations
var vals = this.forms[this.formLookup[elemId]].validations;
formValLoop:
for (var i = vals.length - 1; i > -1; i--) {
if( vals==undefined || vals.element.id != elemId ) continue formValLoop;
if ( validationNames[0] == '*' || this.inArray( this.el[elemId].validations[vals.validationsCounter].name, validationNames ) ) {
try { delete vals; } catch(e) {}
}
}
} else {
//Remove field-specific validations
var vals = this.el[elemId].validations;
for (var i = vals.length - 1; i > -1; i--) {
if ( validationNames[0] == '*' || this.inArray(vals.name, validationNames) ) {
try { delete this.el[elemId].handlers[eventTypes[j]]; } catch(e) {}
}
}
}
}
},
/**
* This function accepts as input a function, a string, an array of
* functions, an array of strings, or a comma-delimited list of functon
* names as its argument and returns an Array of functions or strings
* comprising the passed arguments. Example: transforms 'foo, bar'
* to [foo, bar]
*
* @param {Function or String or Array} args
* @param {String} returnType should be either 'string' or 'function'
* @return {Array of Functions}
*/
resolveArray: function (args, returnType, ignoreCommas) {
var returnArray = [];
if( typeof args == 'object' ) {
var i=args.length;
for (var j=0; j<i; j++) {
returnArray[returnArray.length] = this.resolveArray(args[j],returnType)[0];
}
return returnArray;
}
if( typeof args == 'function' ) {
returnArray[0] = args;
return returnArray;
}
if ( typeof args == 'string' ) {
if(returnType=='string') args = args.replace(' ',''); //dont remove spaces when returning a function
if( args.indexOf(',') == -1 || ignoreCommas==true ) {
//function name as a string
if( returnType=='function' ) {
if( args.indexOf('(') != -1 && args.indexOf('function')==-1 ) {
//In order to preserve scope for functions with parameters attached,
//we must transform "func1(text,foo)" into "function(text) { return func1.call(this,text,foo) }"
var splitAt = args.indexOf('(');
var funcName = args.substring(0,splitAt);
var params = args.substring(++splitAt,args.length);
var args = 'function(text) { return '+funcName+'.call(this,'+params+'}';
}
eval("var argsHandle="+args); //easiest way to handle dot notation and framesets
returnArray[returnArray.length] = argsHandle;
} else {
returnArray[returnArray.length] = args;
}
} else {
//comma-delimited list of function names
var tempArray = this.smartCommaSplit(args);
var i=tempArray.length;
if(i==1) {
//The only commas in the string appear within braces or parens
returnArray = this.resolveArray(tempArray[0], returnType, true);
} else {
for (var j=0; j<i; j++) {
returnArray[returnArray.length] = this.resolveArray(tempArray[j],returnType)[0];
}
}
}
return returnArray;
}
return false;
},
/**
* This function shows an alert box and uses a counter to help prevent
* infinite loops which can be created by the interaction between an alert()
* statement and an onblur handler on an element.
*
* @param {string} errorMsg
*/
safeAlert: function (errorMsg) {
if( this.alertCounter == true ) {
this.alertCounter = false;
alert(errorMsg);
this.alertCounter = true;
} else {
return;
}
},
/**
* This function shows the error messages for failed validations by dynamically
* creating a div
* @param {string} Text of the error message to be displayed
*/
showError: function( errorMsg ) {
var settings = validanguage.getFormSettings(this.id);
var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix);
if( errorDisplay == null ) {
var formField = document.getElementById(this.id);
var errorDiv = document.createElement('DIV');
validanguage.insertAfter( errorDiv, formField );
var innerHTML = '<br/><span id="'+ this.id + settings.errorMsgSpanSuffix+'"> </span>';
errorDiv.innerHTML = innerHTML;
errorDiv.className = settings.onErrorClassName;
var errorDisplay = document.getElementById(this.id + settings.errorMsgSpanSuffix);
} else {
var errorDiv = errorDisplay.parentNode;
errorDiv.style.display = 'block';
errorDiv.className = settings.onErrorClassName;
}
if(validanguage.useLibrary=='scriptaculous') new Effect.Highlight(errorDiv, { startcolor: '#A85F5F', endcolor: '#C0A6A6', restorecolor: '#ddd' });
errorDisplay.innerHTML = errorMsg;
if (! this.className.match(validanguage.settings.failedFieldClassName)) this.className += ' '+validanguage.settings.failedFieldClassName;
if (this.className.match(validanguage.settings.passedFieldClassName)) this.className = this.className.replace(validanguage.settings.passedFieldClassName,'');
//if(this.nodeName.toLowerCase()=='input' && this.type.toLowerCase()=='text') this.style.backgroundColor = '#FF6666';
//Do we need to add any vd_li items?
if( !settings.showFailedFields ) return;
if( document.getElementById(settings.errorDivId) == null ) {
var errorDiv = document.createElement('DIV');
errorDiv.id = settings.errorDivId;
document.body.appendChild(errorDiv);
} else {
var errorDiv = document.getElementById(settings.errorDivId);
}
if (document.getElementById(settings.errorListId) == null) {
errorDiv.innerHTML = settings.errorListText + '<br/><ul id="'+settings.errorListId+'"></ul>';
}
var errorDivInner = errorDiv.innerHTML.toLowerCase();
errorDivInner = errorDivInner.replace(/"/g,''); //remove quotes for IE weirdness
var errorList = document.getElementById(settings.errorListId);
var listItem = '<li id="'+this.id+settings.errorListItemSuffix+'">'+validanguage.el[this.id].field+'</li>';
var listItemExists = listItem.toLowerCase();
listItemExists = listItemExists.replace(/"/g,''); //remove quotes for IE weirdness
if(errorDivInner.indexOf(listItemExists)==-1) errorList.innerHTML += listItem;
document.getElementById(settings.errorDivId).style.display='block';
},
/**
* This function is intended for us as an onsubmit transformation. It replaces the form's
* submit button with the text specified in settings.showSubmitMessageMessage.
*
* @param {Object} validationResult
* @param {Object} failedValidations
*/
showSubmitMessage: function( validationResult, failedValidations ) {
if( validationResult==false ) return;
var settings = validanguage.forms[this.id].settings;
//first, we need to find the submit button and hide it
var inputs = this.getElementsByTagName('INPUT');
for( var i=inputs.length-1; i>-1; i-- ) {
if( typeof inputs.type!='undefined' && inputs.type=='submit' ) {
validanguage.forms.submitButton = submitButton = inputs;
break;
}
}
submitButton.style.display='none';
var loadingDiv = document.createElement('DIV');
loadingDiv.id = settings.showSubmitMessageId;
loadingDiv.innerHTML = settings.showSubmitMessageMessage;
validanguage.insertAfter( loadingDiv, inputs );
//set a timeout to unhide the submit button after 60 seconds
//in case the request times out and the user needs to resubmit the form
setTimeout( function( ){ validanguage.forms.submitButton.style.display='inline'; }, 60000);
},
/**
* This function splits a string into an array using commas as the delimiter,
* while being smart enough to ignore commas appearing inside parenthesis and
* braces.
*
* @param {string} Comma-delimited string
* @return {Array}
*/
smartCommaSplit: function ( str ) {
var openParens = 0;
var openBraces = 0;
var lastSplit = 0;
var returnArray = [];
var len = str.length;
for( var i=0; i<len; i++ ) {
switch (str.charAt(i)) {
case '(': openParens++; break;
case ')': openParens--; break;
case '{': openBraces++; break;
case '}': openBraces--; break;
case ',':
if( openParens==0 && openBraces==0 ){
returnArray[returnArray.length] = str.substring(lastSplit,i);
lastSplit = ++i;
}
break;
}
}
returnArray[returnArray.length] = str.substring(lastSplit,i);
return returnArray;
},
/**
* Determines roughly which browser they're using. Defaults to FF for anything
* that isnt IE, Opera, Konqueror or Safari, which assuming the browser is standards-compliant,
* should be good enough for what I'm using it for. Yea, yea, I know....
*/
sniffBrowser: function() {
//yo... dont hate.
var isIE/*@cc_on=1@*/;
if (isIE) {
this.browser = 'ie';
var version = parseFloat(navigator.appVersion.split('MSIE')[1]);
if( version < 6 ) return 'ie5';
} else if(navigator.appName.indexOf('Opera')!=-1) {
this.browser = 'opera';
} else if(navigator.vendor.indexOf('Apple')!=-1) {
this.browser = 'safari';
} else if (navigator.vendor.indexOf('KDE')!=-1) {
this.browser = 'konqueror';
} else {
this.browser = 'ff';
}
},
/**
* Transformation supporting 3 major features: toggling visibility,
* changing the values of form fields and adding options to select
* elements.
* @param {Array} toggleArgs Array of objects
*/
toggle: function( toggleArgs ) {
var j = toggleArgs.length;
var settings = validanguage.getFormSettings(this.id);
var formName = validanguage.formLookup[this.id];
for (var i=0; i < j; i++) {
var obj = toggleArgs;
var targets = validanguage.resolveArray(obj.target, 'string');
var radioExceptionApplies = false;
// If we are running toggle() for a radio button find out if all the other radio buttons in the same group are unchecked,
if (this.nodeName.toLowerCase()=='input' && this.type.toLowerCase() == 'radio') {
var radioButtonChecked = false;
var radios = document.getElementById(formName)[this.name];
for (var k = radios.length - 1; k > -1; k--) if (radios[k].checked) radioButtonChecked = true;
if (!radioButtonChecked) radioExceptionApplies=true;
}
// toggle visibility
if (typeof obj.toggle != 'undefined') {
var visibleMet = (typeof obj.toggle.visible != 'undefined') ? validanguage.toggleCriteriaMet(this, obj.toggle.visible, settings) : false;
var hiddenMet = (typeof obj.toggle.hidden != 'undefined') ? validanguage.toggleCriteriaMet(this, obj.toggle.hidden, settings) : false;
for (var k = targets.length - 1; k > -1; k--) {
if (visibleMet && !radioExceptionApplies) validanguage.toggleDisplay(targets[k], '');
if (hiddenMet || (settings.toggleVisibilityDefaultsToHidden && !visibleMet)) validanguage.toggleDisplay(targets[k], 'none');
}
} // close toggle visibility
// value control
if (typeof obj.values != 'undefined') {
for (var k = targets.length - 1; k > -1; k--) {
if (typeof obj.values.checked != 'undefined' && this.checked==true) document.getElementById(targets[k]).value=obj.values.checked;
else if (typeof obj.values.unchecked != 'undefined' && this.checked==false) document.getElementById(targets[k]).value=obj.values.unchecked;
else if (typeof obj.values[this.value] != 'undefined') document.getElementById(targets[k]).value=obj.values[this.value];
}
} // close toggle value
// dynamic selects
if (typeof obj.dynamicSelect != 'undefined') {
var sel2 = document.getElementById(targets[0]); // dynamicSelect only supports 1 target
for (var sel1Key in obj.dynamicSelect) {
if (typeof obj.dynamicSelect[sel1Key] == 'object' ) {
var sel1Val = obj.dynamicSelect[sel1Key];
if (sel1Key == this.value) {
// remove the existing options
while (sel2.options.length > 0) { sel2.remove(0); }
for (var sel2Key in sel1Val) {
if (sel2Key == '_default') continue;
var opt = document.createElement('option');
opt.value = sel2Key;
opt.text = sel1Val[sel2Key];
sel2.options.add(opt);
}
if (typeof sel1Val['_default'] != 'undefined') sel2.value = sel1Val['_default'];
}
}
}
} // close dynamicSelect
}
},
/**
* Determines whether the criteria used by the toggle function
* has been met
* @param {Object} field
* @param {string} criteria
* @param {Object} settings object
*/
toggleCriteriaMet: function( field, criteria, settings ) {
if (criteria == 'checked') {
return !!(field.checked);
} else if (criteria == 'unchecked') {
return !(field.checked);
} else if (criteria == 'empty') {
return !!(validanguage.inArray(field.value, settings.emptyOptionElements));
} else if (criteria == 'notEmpty') {
return !(validanguage.inArray(field.value, settings.emptyOptionElements));
} else {
return !!(field.value == criteria);
}
},
/**
* Function used to hide or show a node. This function will also automatically disable or enable
* any form fields contained within the passed node.
* @param {Object} nodeId ID of the node to hide/show
* @param {Object} visibility (optional) Pass either 'none' or '' to hide/show. Or leave blank to toggle
*/
toggleDisplay: function( nodeId, visibility ) {
var node = document.getElementById(nodeId);
var nodeName = node.nodeName.toLowerCase();
if (visibility==null||visibility==undefined) visibility = (node.style.display=='none') ? '' : 'none';
disabledBool = (visibility=='none') ? true : false;
// show/hide the passed node
node.style.display = visibility;
// disable/enable any form fields contained within the node
if (nodeName == 'input' || nodeName == 'textarea' || nodeName == 'select') {
node.disabled = disabledBool;
return;
}
var allInputs = node.getElementsByTagName('input');
var allTextareas = node.getElementsByTagName('textarea');
var allSelect = node.getElementsByTagName('select');
var allObjects = this.concatCollection(allInputs, allTextareas);
var allObjects = this.concatCollection(allObjects, allSelect);
for (var i = allObjects.length - 1; i > -1; i--) {
allObjects.disabled = disabledBool;
}
},
/**
* Validates that a field does not contain any of the invalid characters
* listed in the characters validation rules.
*
* @param {string} text
*/
validateCharacters: function( text ) {
var id = this.id;
var mode = validanguage.el[id].characters.mode;
var expression = validanguage.el[id].characters.characterExpression;
switch(mode) {
case 'allow':
outerLoop:
for( var i=text.length-1;i>-1;i--) {
innerLoop:
for (var j=expression.length-1; j>-1; j--) {
if(expression.charAt(j)==text.charAt(i)) continue outerLoop;
}
//if we got here, they entered a disallowed character
return false;
}
break;
case 'deny':
outerLoop:
for( var i=text.length-1;i>-1;i--) {
innerLoop:
for (var j=expression.length-1; j>-1; j--) {
if(expression.charAt(j)==text.charAt(i)) return false;
}
}
break;
}
return true;
},
/**
* Validates that a valid credit card number has been supplied
* @param {string} text
* @param {array} cardTypes
* @param {boolean} testChecksum Pass false to skip the luhn checksum test
*/
validateCreditCard: function (text, cardTypes, testChecksum) {
if (validanguage.empty(cardTypes)) cardTypes = ['amex','disc','mc','visa'];
// Strip any non-digits
var text=text.replace(/\D/g,'');
var cards = {
'amex' : '^3[4|7]\\d{13}$',
'bankcard' : '^56(10\\d\\d|022[1-5])\\d{10}$',
'diners' : '^(?:3(0[0-5]|[68]\\d)\\d{11})|(?:5[1-5]\\d{14})$',
'disc' : '^(?:6011|650\\d)\\d{12}$',
'electron' : '^(?:417500|4917\\d{2}|4913\\d{2})\\d{10}$',
'enroute' : '^2(?:014|149)\\d{11}$',
'jcb' : '^(3\\d{4}|2100|1800)\\d{11}$',
'maestro' : '^(?:5020|6\\d{3})\\d{12}$',
'mc' : '^5[1-5]\\d{14}$',
'solo' : '^(6334[5-9][0-9]|6767[0-9]{2})\\d{10}(\\d{2,3})?$',
'switch' : '^(?:49(03(0[2-9]|3[5-9])|11(0[1-2]|7[4-9]|8[1-2])|36[0-9]{2})\\d{10}(\\d{2,3})?)|(?:564182\\d{10}(\\d{2,3})?)|(6(3(33[0-4][0-9])|759[0-9]{2})\\d{10}(\\d{2,3})?)$',
'visa' : '^4\\d{12}(\\d{3})?$',
'voyager' : '^8699[0-9]{11}$'
};
var validCard = false;
for (var i = cardTypes.length; i--; i > -1) {
validCard = validanguage.validateRegex(text, { expression: cards[cardTypes] });
if (validCard) break;
}
if (!validCard) return false;
if (testChecksum===false) return true;
/** Run the luhn checksum test
* Luhn algorithm number checker - (c) 2005-2008 shaman - www.planzero.org
*/
// Set the string length and parity
var number_length=text.length;
var parity=number_length % 2;
// Loop through each digit and do the maths
var total=0;
for (var i=0; i < number_length; i++) {
var digit=text.charAt(i);
// Multiply alternate digits by two
if (i % 2 == parity) {
digit=digit * 2;
// If the sum is two digits, add them together (in effect)
if (digit > 9) {
digit=digit - 9;
}
}
// Total up the digits
total = total + parseInt(digit);
}
// If the total mod 10 equals 0, the number is valid
if (total % 10 == 0) {
return true;
} else {
return false;
}
},
/**
* Validates that a valid date is supplied and is entered in the correct format.
* The format is specified by the following arguments<br/>
* @param {String} text to be validated<br/>
* @param {Object} Options object containing any of the following options<br/>
* dateOrder: {String} This must be either 'ymd','mdy','dmy','myd','ydm', or 'dym<br/>
* allowedDelimiters: {String}. A string containing the list of delimiters which are allowed.
* Example: './-'<br/>
* twoDigitYearsAllowed: {Boolean}. Is a 2-digit year valid<br/>
* oneDigitDaysAndMonthsAllowed: {Boolean}. Is a 1-digit month or a 1-digit day valid<br/>
* maxYear: {Integer}. Years greater than maxYear will be treated as invalid. Defaults to 15 years from today<br/>
* minYear: {Integer}. Years less than minYear will be treated as invalid. Defaults to 1900<br/>
* rejectDatesInTheFuture: {Boolean}. Are dates in the future valid? rejectDatesInTheFuture defaults to false<br/>
* rejectDatesInThePast: {Boolean}. Are dates in the past valid? rejectDatesInThePast defaults to false<br/>
*/
validateDate: function( text, options ) {
//Set default values
options = validanguage.getDateTimeDefaultOptions(options, {dateOrder: 'mdy'} );
//Loop thru the allowedDelimiters to start building our regex and figure out which one was actually used
var delimiterUsed;
var delimiterRegex = '(';
for( var i=options.allowedDelimiters.length-1;i>-1;i--) {
delimiterRegex += '\\' + options.allowedDelimiters.charAt(i);
if (i>0) delimiterRegex += '|';
if (text.indexOf(options.allowedDelimiters.charAt(i)) > -1) {
delimiterUsed = options.allowedDelimiters.charAt(i);
}
}
delimiterRegex += ')';
if( delimiterUsed==null ) return false; //no delimiter was used
var parts = text.split(delimiterUsed);
if( parts.length!=3 ) return false; //they used more than one delimiter or didnt give us a valid date
//Next we need to build the regex to validate the date comprises only integers and delimiters
var regex = '^';
for(var j=0; j<3; j++) {
switch( options.dateOrder.charAt(j) ) {
case 'y':
var num = (options.twoDigitYearsAllowed) ? '{2,4}' : '{4}';
regex += '\\d' + num;
break;
case 'm':
case 'd':
var num = (options.oneDigitDaysAndMonthsAllowed) ? '{1,2}' : '{2}';
regex += '\\d' + num;
break;
}
if(j<2) regex += delimiterRegex;
}
regex += '$';
//Run the regex
var reg = new RegExp( regex );
var thisMatch = reg.exec(text);
if (thisMatch == null) return false;
//grab our dates
var year = parts[options.dateOrder.indexOf('y')];
var month = parts[options.dateOrder.indexOf('m')];
var day = parts[options.dateOrder.indexOf('d')];
// Verify the year isnt 3-digits long to account for me being lazy in the regex check above
if( year.length==3 ) return false;
//Make sure the year is in bounds
if( (year < options.minYear && year.length==4) || (year > options.maxYear) ) return false;
//Next we check that the date actually exists, to rule out stuff like "12/32/1976"
if( !validanguage.validateDateExists(year,month,day) ) return false;
if( options.rejectDatesInTheFuture || options.rejectDatesInThePast ){
var now = new Date();
var then = new Date();
then.setDate(day);
then.setMonth(--month); // January = 0
then.setFullYear(year);
if( (options.rejectDatesInTheFuture && then > now) || (options.rejectDatesInThePast && then < now) ) return false;
}
return true;
},
/**
* Helper function to verify a date actually exists. Used to reject values
* such as "12/35/2009"
* @param {integer} year, preferably 4-digit
* @param {integer} month
* @param {integer} day
* @return {Boolean}
*/
validateDateExists: function(year, month, day) {
if(year.length==2) {
var prefix = (year > 20 ) ? '19' : '20';
year = prefix+year.toString();
}
if( month.charAt(0)=='0' ) month = month.substr(1,1);
if( day.charAt(0)=='0' ) day = day.substr(1,1);
if( month < 0 || month > 12 ) return false;
switch( month.toString() ) {
case '4':
case '6':
case '9':
case '11':
var maxDay = 30;
break;
case '2':
var maxDay = ((year % 4 == 0) && ( (!(year % 100 == 0)) || (year % 400 == 0)) ) ? 29 : 28;
break;
default:
var maxDay = 31;
}
if( day < 0 || day > maxDay ) return false;
return true;
},
/**
* Validates an email address
*/
validateEmail: function( text ) {
if(! text.match(/^([a-zA-Z0-9]+[a-zA-Z0-9._%-]*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4})$/) )
return false;
else return true;
},
/**
* Calls the validationWrapper function on the passed form ID
* @param {String or Form Node} Id of the form
* @return {Object} Object containing 2 elements:
* Object.result is a boolean to indicate whether any fields in the form failed validation
* Object.failedValidations will contain info detailing any form fields with failed validations
*/
validateForm: function( formId ) {
var form;
if( formId==undefined) form = document.forms[0];
else if (typeof formId=='string') form = document.getElementById(formId);
else form = formId;
return this.validationWrapper( form, 'validateForm' );
},
/**
* Validates that a field contains a valid IPv4 address
*/
validateIP: function( text ) {
var bytes = text.split('.');
if (bytes.length == 4) {
for (var i=bytes.length-1; i> -1; i--) {
if (!(validanguage.validateNumeric(bytes) && bytes >= 0 && bytes <= 255)) {
return false;
}
}
return true;
}
return false;
},
/**
* Function to suppress the keys specified in validanguage.el.characters
* from being entered into a textarea or text box.
*
* This function must be forked to fetch the keyCode
* and differentiate between control and non-control characters.
* For example: in Mozilla both the delete key and . equate to 46.
* We dont fork based on supported functions because IE and Moz event models
* for keyCodes/charCodes are different and confusing, so we're
* better off sniffing the browser.
*
* @param {Event Object} e
*/
validateKeypress: function(e) {
var evt = e || window.evt; var $this = evt.currentTarget || evt.srcElement;
var id = $this.id;
var formName = validanguage.formLookup[id];
var settings = validanguage.getFormSettings(id);
if (validanguage.browser=='ie' || validanguage.browser=='opera' ) {
//branch for IE and opera
//we dont have to worry about noncontrol keys since they dont fire keypress events in IE
charCode = evt.keyCode;
if ( ( (charCode == 16) && (evt.shiftKey) ) || (evt.ctrlKey) ) return true; //prevents firing on ctrl key in opera
} else {
//branch for Mozilla
if ( (evt.charCode == 0) || (evt.ctrlKey) ) return true;
//return true for all control keys and if control is held down
charCode = evt.which; //capture charCode of non-control keys
}
charCode += ';';
searchString = new String(validanguage.el[id].characters.expression);
var mode = validanguage.el[id].characters.mode;
if ( ( (searchString.search(charCode) != -1 ) && (mode == 'allow') ) ||
( (searchString.search(charCode) == -1 ) && (mode == 'deny') ) ){
return true;
} else {
$this.style.backgroundColor = settings.validationErrorColor;
setTimeout("document.getElementById('" + id + "').style.backgroundColor = validanguage.forms['"+formName+"'].settings.normalTextboxColor",validanguage.forms[formName].settings.timeDelay);
evt.returnValue = false; //IE
if(evt.preventDefault) evt.preventDefault(); //Everyone else
return false;
}
},
/**
* Validates that a field is less than maxlength characters long
* @param {string} text
*/
validateMaxlength: function( text, max ) {
var id = this.id;
var maxlength = (validanguage.empty(max)) ? validanguage.el[id].maxlength : max;
if(text.length > maxlength)
return false;
else return true;
},
/**
* Validates that a field is greater than minlength characters long
* @param {string} text
*/
validateMinlength: function( text, min ) {
var id = this.id;
var minlength = (validanguage.empty(min)) ? validanguage.el[id].minlength : min;
if(text.length < minlength)
return false;
else return true;
},
/**
* Validates that a field is numeric
* @param {string} text
*/
validateNumeric: function( text ) {
if(! text.match(/^\d+$/) )
return false;
else return true;
},
/**
* Validates the element against a user-defined regex stored in
* validanguage.el[id].regex
*
* @param {string} text
* @param {object} optional object containing regex settings
*/
validateRegex: function( text, obj ) {
var id = this.id;
var regexObj = (validanguage.empty(obj)) ? validanguage.el[id].regex : obj;
if(typeof regexObj.modifiers=='undefined') regexObj.modifiers='';
if(typeof regexObj.errorOnMatch=='undefined') regexObj.errorOnMatch=false;
var myreg = (typeof regexObj.expression=='string') ? new RegExp(regexObj.expression) : regexObj.expression;
var thisMatch = myreg.exec(text, regexObj.modifiers);
if (thisMatch == null) { //no match
var returnStatus = (regexObj.errorOnMatch==false||regexObj.errorOnMatch=='false') ? false : true;
} else { //match
var returnStatus = (regexObj.errorOnMatch==false||regexObj.errorOnMatch=='false') ? true : false;
}
return returnStatus;
},
/***
* Validates whether or not an element has been filled out,
* selected or checked. This function is a wrapper which
* calls validateRequiredChild()
*
* @param {string} text
*/
validateRequired: function( unused ) {
var id = this.id;
if(typeof validanguage.el[id].requiredAlternatives == 'undefined') {
var alternatives = [ id ];
} else {
var alternatives = validanguage.resolveArray(validanguage.el[id].requiredAlternatives,'string');
alternatives[alternatives.length] = id;
}
for( var i=alternatives.length-1; i>-1; i--) {
id = alternatives;
var elem = document.getElementById(id);
var text = elem.value;
var notEmpty = validanguage.validateRequiredChild.call(elem, text);
if(notEmpty==true) return true; //if this element or one of its alternatives is not empty, it validates
}
return false;
},
/**
* This function calls the validateRequired method on the "master/required"
* form field when the "alternative" form field is clicked and then calls
* the appropriate onerorr/onsuccess function.
*/
validateRequiredAlternatives: function(e) {
var evt = e || window.evt;
var $this = evt.currentTarget || evt.srcElement;
var id = $this.id;
var parentId = validanguage.requiredAlternatives[id].parentId;
var onsuccess = validanguage.requiredAlternatives[id].onsuccess;
var onerror = validanguage.requiredAlternatives[id].onerror;
var parent = document.getElementById(parentId);
if (validanguage.validateRequired.call(parent) == true) {
successHandlers = validanguage.resolveArray(onsuccess, 'function');
for (var m = successHandlers.length - 1; m > -1; m--) {
successHandlers[m].call(parent);
}
} else {
errorHandlers = validanguage.resolveArray(onerror, 'function');
for (var m = errorHandlers.length - 1; m > -1; m--) {
errorHandlers[m].call(parent, validanguage.requiredAlternatives[id].errorMsg);
}
}
},
/***
* Child function called by validateRequired to validates whether or not an element has been filled out,
* selected or checked. validateRequiredChild is required to add support for the requiredAlternatives
* array.
*
* @param {string} text
*/
validateRequiredChild: function( text ) {
var type = ( typeof this.type != 'undefined' ) ? this.type : null;
if( this.nodeName.toLowerCase() == 'textarea' ) type = 'text';
if( this.nodeName.toLowerCase() == 'select' ) type = 'select';
switch( type ) {
case 'checkbox':
if( this.checked == false ){
return false;
}
break;
case 'radio':
var formId = validanguage.formLookup[this.id];
var radios = (typeof formId == 'number') ? document.forms[formId][this.name] : document.getElementById(formId)[this.name];
for( var i=radios.length-1; i>-1;i--) {
if (radios.checked == true) return true;
}
return false;
break;
case 'text':
case 'password':
case 'file':
if(validanguage.empty(text)) {
return false;
}
break;
case 'select':
if (validanguage.empty(text)) {
return false;
}
settings = validanguage.getFormSettings(this.id);
for( var i=settings.emptyOptionElements.length-1; i>-1; i-- ) {
//see if they have selected any of the "empty" option elements
if( text==settings.emptyOptionElements) return false;
}
break;
} //close switch
return true;
},
/**
* Validates that the entered text is a valid timestamp. The options object supports all the options listed
* under the validateDate function as well as the following additional onces<br/>
* @param {String} text to be validated<br/>
* @param {Object} Options object containing any of the following options:<br/>
* timeIsRequired: {Boolean} Is a date which is provided without an accompanying time considered a valid timestamp?
* timeIsRequired defaults to false.<br/>
* timeUnits: {String} A string containing a list of all the time units which are allowed to be entered in the timestamp.
* These may include any of the following: h for hours, m for minutes, s for seconds, u for microseconds,
* and t for timezone. Example: "hms" for hours, minutes and seconds or "hmsut" for all 5 units.
* Defaults to "hms"<br/>
* microsecondPrecision {Integer} Indicates the supported number of decimal places for the microseconds. Defaults to 6.
*
*/
validateTimestamp: function( text, options ) {
// Set default options
options = validanguage.getDateTimeDefaultOptions(options, {dateOrder: 'ymd'} );
// Check the date portion of the timestamp
var pos = text.indexOf(' ');
var date = ( pos == -1 ) ? text : text.substr(0,pos);
if( !validanguage.validateDate(date, options) ) return false;
// Check whether they provided a time
if( pos != -1 ) {
var time = text.substring(++pos);
} else {
if( !options.timeIsRequired ) return true;
if( options.timeIsRequired ) return false;
}
// Build the regex to validate the time
var regex = '^\\d{1,2}:\\d{1,2}';
if( options.timeUnits.indexOf('s')!=-1 ) regex += '(:\\d{1,2}';
if( options.timeUnits.indexOf('u')!=-1 ) regex += '(\\.\\d{1,'+options.microsecondPrecision+'})?';
if( options.timeUnits.indexOf('s')!=-1 ) regex += ')?';
if( options.timeUnits.indexOf('t')!=-1 ) {
regex += '( ?[\\+|\\-]{1,1}(\\d|0\\d|10|11|12|13)(\\:(00|30))?)?';
}
regex += '$';
//Run the regex
var reg = new RegExp( regex );
var thisMatch = reg.exec(time);
if (thisMatch == null) return false;
//Finally we need to make sure that the hours, minutes and seconds which were entered are valid
var timeparts = time.split(':');
if( timeparts[0] > 23) return false;
if( timeparts[1] > 59) return false;
if( timeparts.length > 2){
var seconds = timeparts[2].substr(0,2);
if(seconds > 59) return false;
}
return true;
},
/**
* Validates a URL
*
* @param {string} text
*/
validateURL: function( text ) {
if(! text.match(/^((([hH][tT][tT][pP][sS]?|[fF][tT][pP])\:\/\/)?([\w\.\-]+(\:[\w\.\&%\$\-]+)*@)?((([^\s\(\)\<\>\\\"\.\[\]\,@;:]+)(\.[^\s\(\)\<\>\\\"\.\[\]\,@;:]+)*(\.[a-zA-Z]{2,4}))|((([01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}([01]?\d{1,2}|2[0-4]\d|25[0-5])))(\b\:(6553[0-5]|655[0-2]\d|65[0-4]\d{2}|6[0-4]\d{3}|[1-5]\d{4}|[1-9]\d{0,3}|0)\b)?((\/[^\/][\w\.\,\?\'\\\/\+&%\$#\=~_\-@]*)*[^\.\,\?\"\'\(\)\[\]!;<>{}\s\x7F-\xFF])?)$/) )
return false;
else return true;
},
/**
* DEPRECATED - Please use validateDate() instead as validateUSDate
* will be removed in the future. validateDate() defaults to MM/DD/YYYY
* when called without any additional arguments.
* @param {string} text
*/
validateUSDate: function( text ) {
return validanguage.validateDate(text);
},
/**
* Validates that a US Phone number is entered
*
* @param {string} text
*/
validateUSPhoneNumber: function( text ) {
if(! text.match(/^\D?(\d{3})\D?\D?(\d{3})\D?(\d{4})$/) )
return false;
else return true;
},
/**
* Validates that a US Social Security Number is entered
*
* @param {string} text
*/
validateUSSSN: function( text ) {
if(! text.match(/^\d{3}( |-|.){0,1}\d{2}( |-|.){0,1}\d{4}$/) )
return false;
else return true;
},
/**
* Validates that a US zip code was entered
*
* @param {string} text
*/
validateUSZipCode: function( text ) {
if(! text.match(/^\d{5}( |-|.){0,1}(\d{4})?$/) )
return false;
else return true;
},
/**
* This is a wrapper for all the validation event handlers assigned to both
* form field element and to forms themselves. This function also calls any
* transformations before running the validations.
*
* We use a wrapper for a number of reasons, including exiting validation
* early as soon as a single validation function fails.
*
* @param {Event Object} e
* @param {String} Custom Event
* '
*/
validationWrapper: function(e, customEvent) {
if( customEvent=='typing') {
var $this = document.getElementById(e);
var type = 'typing';
var id = e;
} else if (customEvent=='validateForm') {
var $this = e;
var form = validanguage.whichFormAmI($this);
var type = 'submit';
} else {
var evt = e || window.evt;
var $this = evt.currentTarget || evt.srcElement;
var type = evt.type;
if (type == 'submit') {
var form = validanguage.whichFormAmI($this);
} else {
var id = $this.id;
}
if( customEvent=='typingTimeout') {
if( validanguage.typingDelay[id] ) window.clearTimeout(validanguage.typingDelay[id]);
eval("validanguage.typingDelay[id] = window.setTimeout(\"validanguage.validationWrapper('"+id+"', 'typing')\", validanguage.settings.typingDelay );");
return true;
}
}
// Bail out early if the parent form is marked as disabled
if( typeof validanguage.el[form] != 'undefined' && typeof validanguage.el[form].disabled != 'undefined' && validanguage.el[form].disabled == true ) return true;
var validations = (type=='submit') ? validanguage.forms[form].validations : validanguage.el[id].handlers[type];
var i=validations.length;
var failedValidations = {}; //key value pair of id => validation
if(type=='submit') {
outerLoop:
for(var j=0; j<i; j++) {
if( typeof validations[j]=='undefined') continue outerLoop; //skip deativated validations
var id = validations[j].element.id; //reassign $this and id to the form field in question
var $this = validations[j].element;
if( (typeof failedValidations[id] != 'undefined') ||
(typeof $this.disabled != 'undefined' && $this.disabled==true) ||
(typeof validanguage.el[id].disabled != 'undefined' && validanguage.el[id].disabled==true)
) {
continue outerLoop; //skip disabled fields or fields that already flunked
}
if( typeof validanguage.el[id].failed != 'undefined' && validanguage.el[id].failed==true) {
failedValidations[id] = { failed: true, field: validanguage.el[id].field };
continue outerLoop; //handle fields manually marked as invalid
}
var validationsCounter = validations[j].validationsCounter;
var validation = validanguage.el[id].validations[validationsCounter];
var funcs = validanguage.resolveArray(validation.name, 'function');
innerLoop:
for (var m=funcs.length-1; m>-1; m--) {
if( typeof failedValidations[id] != 'undefined' ) continue innerLoop; //this field already flunked
var result = funcs[m].call($this, $this.value);
if( result == false ) {
failedValidations[id] = validation; //defer onerror handlers till later
failedValidations[id].field = validanguage.el[id].field;
if(! validanguage.forms[form].settings.validateAllFieldsOnsubmit) break outerLoop;
} else {
//run the onsuccess handlers
var onsuccess = validanguage.getElSetting('onsuccess',id,validation);
successHandlers = validanguage.resolveArray(onsuccess, 'function');
for (var n=successHandlers.length-1; n>-1; n--) {
successHandlers[n].call($this);
}
}
}
} //close outerLoop
if( validanguage.empty(failedValidations) ) {
var submitStatus = true;
} else { //call all appropriate onerror handlers
for (var o in failedValidations) {
if( typeof failedValidations[o] == 'function' ) continue;
var id = o;
$this = document.getElementById(o);
validation = failedValidations[o];
var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation);
var errorMsg = validanguage.getElSetting('errorMsg',id,validation);
var onerror = validanguage.getElSetting('onerror',id,validation);
errorHandlers = validanguage.resolveArray(onerror,'function');
for (var m=errorHandlers.length-1; m>-1; m--) {
errorHandlers[m].call($this, errorMsg);
}
var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation);
if( focusOnerror==true ) $this.focus();
var showAlert = validanguage.getElSetting('showAlert',id,validation);
if( showAlert ) validanguage.safeAlert(errorMsg);
}
var submitStatus = false;
}
//Call onsubmit transformations
var transformation = ( typeof validanguage.el[form] != 'undefined' ) ? validanguage.el[form].transformations : [];
for (var n=transformation.length-1; n>-1; n--) {
transformations = validanguage.resolveArray(transformation[n].name,'function');
for (var o=transformations.length-1; o>-1; o--) {
var returnStatus = transformations[o].call(document.getElementById(form), submitStatus, failedValidations);
if(typeof returnStatus == 'boolean') submitStatus = returnStatus;
}
}
if( customEvent=='validateForm' ) {
return { result: submitStatus, failedValidations: failedValidations };
}
return submitStatus;
} else {
//Skip disabled fields
if( (typeof validanguage.el[id].disabled=='boolean' && validanguage.el[id].disabled==true)
|| (typeof $this.disabled!='undefined' && $this.disabled==true) ) {
return;
}
var transformations = validanguage.el[id].transformations;
var p = transformations.length;
for( var q=0; q<p; q++) {
if( typeof transformations[q]['on'+type]=='undefined' || transformations[q]['on'+type]!=true ) continue;
var transformation = validanguage.resolveArray(transformations[q].name, 'function');
for (var m=transformation.length-1; m>-1; m--) {
transformation[m].call($this);
}
}
if( typeof validanguage.el[id].failed=='boolean' && validanguage.el[id].failed==true ) {
//Allow a user to manually flunk a field
result = false;
} else {
//see if the field is valid
var validationCounter;
outerLoop:
for(var j=0; j<i; j++) {
if( typeof validations[j]=='undefined') {
continue outerLoop; //this validation has been removed
} else {
validationCounter = validations[j];
}
//we run thru all the validations until one fails
var validation = validanguage.el[id].validations[validationCounter];
var funcs = validanguage.resolveArray(validation.name,'function');
for (var m=funcs.length-1; m>-1; m--) {
var result = funcs[m].call($this, $this.value);
if( result == false ) break outerLoop;
}
}
if(validationCounter == undefined) return true; //exit early if all validations have been removed
}
//handle the result
if( result == true ) {
var onsuccess = validanguage.getElSetting('onsuccess',id,validation);
successHandlers = validanguage.resolveArray(onsuccess,'function');
for (var m=successHandlers.length-1; m>-1; m--) {
successHandlers[m].call($this);
}
return true;
} else {
var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation);
var errorMsg = validanguage.getElSetting('errorMsg',id,validation);
var onerror = validanguage.getElSetting('onerror',id,validation);
errorHandlers = validanguage.resolveArray(onerror,'function');
for (var m=errorHandlers.length-1; m>-1; m--) {
errorHandlers[m].call($this, errorMsg);
}
var focusOnerror = validanguage.getElSetting('focusOnerror',id,validation);
if( focusOnerror==true ) $this.focus();
var showAlert = validanguage.getElSetting('showAlert',id,validation);
if( showAlert ) validanguage.safeAlert(errorMsg);
return false;
}
}
},
/**
* This function returns the ID of a form, if it exists. Otherwise, it
* returns the form's index position in the document.forms array.
* By using this function, we need not require all forms to have
* distinct IDs.
*
* @param {DOM Node} Form Node
* @return {String} Form ID or Index position
*/
whichFormAmI: function ( obj ) {
if (typeof obj.id != 'undefined' && (!validanguage.empty(obj.id)) ) return obj.id;
var forms = document.forms;
for (var i=0, j=forms.length; i<j; i++) {
if(forms == obj) return i;
}
}
} //close validanguage
validanguage.init();
Latest Comments