// LiveValidation 1.3 (prototype.js version)

// Copyright (c) 2007-2008 Alec Hill (www.livevalidation.com)

// LiveValidation is licensed under the terms of the MIT License



var LiveValidation = Class.create();



/*********************************************** LiveValidation class ***********************************/



/*** static ***/



Object.extend(LiveValidation, {

  

  VERSION: '1.3 prototype',

  

  /*** element types constants ***/

  TEXTAREA:  1,

  TEXT:         2,

  PASSWORD: 3,

  CHECKBOX:  4,

  SELECT:      5,

  FILE:          6,



  /**

   *	pass an array of LiveValidation objects and it will validate all of them

   *	

   *	@var validations {Array} - an array of LiveValidation objects

   *	@return {Bool} - true if all passed validation, false if any fail						

   */

  massValidate: function(validations){

    var returnValue = true;

    for(var i = 0, len = validations.length; i < len; ++i ){

      var valid = validations[i].validate();

      if(returnValue) returnValue = valid;

    }

    return returnValue;

  }



});



/*** prototype ***/



LiveValidation.prototype = {

    

  validClass: 'LV_valid',

  invalidClass: 'LV_invalid',

  messageClass: 'LV_validation_message',

  validFieldClass: 'LV_valid_field',

  invalidFieldClass: 'LV_invalid_field',

    

  /**

   *	constructor for LiveValidation - validates a form field in real-time based on validations you assign to it

   *	

   *	@var element {mixed} - either a dom element reference or the string id of the element to validate

   *	@var optionsObj {Object} - general options, see below for details

   *

   *	optionsObj properties:

   *							validMessage {String} 	- the message to show when the field passes validation

   *													  (DEFAULT: "Thankyou!")

   *							onValid {Function} 		- function to execute when field passes validation

   *													  (DEFAULT: function(){ this.insertMessage(this.createMessageSpan()s); this.addFieldClass(); } )	

   *							onInvalid {Function} 	- function to execute when field fails validation

   *													  (DEFAULT: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); })

   *							insertAfterWhatNode {mixed} 	- reference or id of node to have the message inserted after 

   *													  (DEFAULT: the field that is being validated

   *              onlyOnBlur {Boolean} - whether you want it to validate as you type or only on blur

   *                            (DEFAULT: false)

   *              wait {Integer} - the time you want it to pause from the last keystroke before it validates (ms)

   *                            (DEFAULT: 0)

   *              onlyOnSubmit {Boolean} - whether should be validated only when the form it belongs to is submitted

   *                            (DEFAULT: false)

   */

  initialize: function(element, optionsObj){

    // set up special properties (ones that need some extra processing or can be overidden from optionsObj)

    if(!element) throw new Error("LiveValidation::initialize - No element reference or element id has been provided!");

    this.element = $(element);

    if(!this.element) throw new Error("LiveValidation::initialize - No element with reference or id of '" + element + "' exists!");

    // properties that could not be initialised above

    this.elementType = this.getElementType();

    this.validations = [];

    this.form = this.element.form;

    // overwrite the options defaults with passed in ones

    this.options = Object.extend({

      validMessage: 'Thankyou!',

      onValid: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); },

      onInvalid: function(){ this.insertMessage(this.createMessageSpan()); this.addFieldClass(); },

      insertAfterWhatNode: this.element,

      onlyOnBlur: false,

      wait: 0,

      onlyOnSubmit: false

    }, optionsObj || {});

	var node = this.options.insertAfterWhatNode || this.element;

    this.options.insertAfterWhatNode = $(node);

    Object.extend(this, this.options); // copy the options to the actual object

    // add to form if it has been provided

    if(this.form){

      this.formObj = LiveValidationForm.getInstance(this.form);

      this.formObj.addField(this);

    }

    // events

	// event callbacks are cached so they can be stopped being observed

	this.boundFocus = this.doOnFocus.bindAsEventListener(this);

    Event.observe(this.element, 'focus', this.boundFocus);

    if(!this.onlyOnSubmit){

      switch(this.elementType){

        case LiveValidation.CHECKBOX:

		  this.boundClick = this.validate.bindAsEventListener(this);

          Event.observe(this.element, 'click', this.boundClick);

          // let it run into the next to add a change event too

        case LiveValidation.SELECT:

        case LiveValidation.FILE:

		  this.boundChange = this.validate.bindAsEventListener(this);

          Event.observe(this.element, 'change', this.boundChange);

          break;

        default:

          if(!this.onlyOnBlur){

		  	this.boundKeyup = this.deferValidation.bindAsEventListener(this);

		  	Event.observe(this.element, 'keyup', this.boundKeyup);

		  }

          this.boundBlur = this.validate.bindAsEventListener(this);

		  Event.observe(this.element, 'blur', this.boundBlur);

      }

    }

  },

  

  /**

   *	destroys the instance's events and removes it from any LiveValidationForms

   */

  destroy: function(){

  	if(this.formObj){

		// remove the field from the LiveValidationForm

		this.formObj.removeField(this);

		// destroy the LiveValidationForm if no LiveValidation fields left in it

		this.formObj.destroy();

	}

    // remove events

    Event.stopObserving(this.element, 'focus', this.boundFocus);

    if(!this.onlyOnSubmit){

      switch(this.elementType){

        case LiveValidation.CHECKBOX:

          Event.stopObserving(this.element, 'click', this.boundClick);

          // let it run into the next to add a change event too

        case LiveValidation.SELECT:

        case LiveValidation.FILE:

          Event.stopObserving(this.element, 'change', this.boundChange);

          break;

        default:

          if(!this.onlyOnBlur) Event.stopObserving(this.element, 'keyup', this.boundKeyup);

          Event.stopObserving(this.element, 'blur', this.boundBlur);

      }

    }

    this.validations = [];

	this.removeMessageAndFieldClass();

  },

  

  /**

   *	adds a validation to perform to a LiveValidation object

   *

   *	@var validationFunction {Function} - validation function to be used (ie Validate.Presence )

   *	@var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary

   * @return {Object} - the LiveValidation object itself so that calls can be chained

   */

  add: function(validationFunction, validationParamsObj){

    this.validations.push( { type: validationFunction, params: validationParamsObj || {} } );

    return this;

  },

  

  /**

     *	removes a validation from a LiveValidation object - must have exactly the same arguments as used to add it 

     *

     *	@var validationFunction {Function} - validation function to be used (ie Validate.Presence )

     *	@var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary

     * @return {Object} - the LiveValidation object itself so that calls can be chained

     */

    remove: function(validationFunction, validationParamsObj){

	  this.validations = this.validations.reject(function(v){

	  	return (v.type == validationFunction && v.params == validationParamsObj);

	  });

	  return this;

    },

    

  /**

   * makes the validation wait the alotted time from the last keystroke 

   */

  deferValidation: function(e){

    if(this.wait >= 300) this.removeMessageAndFieldClass();

    if(this.timeout) clearTimeout(this.timeout);

    this.timeout = setTimeout(this.validate.bind(this), this.wait);

  },

    

  /**

   * sets the focused flag to false when field loses focus 

   */

  doOnBlur: function(){

    this.focused = false;

    this.validate();

  },

    

  /**

   * sets the focused flag to true when field gains focus and removes old message and field class 

   */

  doOnFocus: function(){

    this.focused = true;

    this.removeMessageAndFieldClass();

  },

		

  /**

   *	gets the type of element, to check whether it is compatible

   *

   *	@var validationFunction {Function} - validation function to be used (ie Validate.Presence )

   *	@var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary

   */

  getElementType: function(){

    switch(true){

      case (this.element.nodeName.toUpperCase() == 'TEXTAREA'):

        return LiveValidation.TEXTAREA;

      case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'TEXT'):

        return LiveValidation.TEXT;

      case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'PASSWORD'):

        return LiveValidation.PASSWORD;

      case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'CHECKBOX'):

        return LiveValidation.CHECKBOX;

      case (this.element.nodeName.toUpperCase() == 'INPUT' && this.element.type.toUpperCase() == 'FILE'):

        return LiveValidation.FILE;

      case (this.element.nodeName.toUpperCase() == 'SELECT'):

        return LiveValidation.SELECT;

      case (this.element.nodeName.toUpperCase() == 'INPUT'):

        throw new Error('LiveValidation::getElementType - Cannot use LiveValidation on an ' + this.element.type + ' input!');

      default:

        throw new Error('LiveValidation::getElementType - Element must be an input, select, or textarea!');

    }

  },

    

  /**

   *	loops through all the validations added to the LiveValidation object and checks them one by one

   *

   *	@var validationFunction {Function} - validation function to be used (ie Validate.Presence )

   *	@var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary

   * @return {Boolean} - whether the all the validations passed or if one failed

   */

  doValidations: function(){

    this.validationFailed = false;

    for(var i = 0, len = this.validations.length; i < len; ++i){

      var validation = this.validations[i];

      switch(validation.type){

        case Validate.Presence:

        case Validate.Confirmation:

        case Validate.Acceptance:

          this.displayMessageWhenEmpty = true;

          this.validationFailed = !this.validateElement(validation.type, validation.params); 

          break;

        default:

          this.validationFailed = !this.validateElement(validation.type, validation.params);

          break;

      }

      if(this.validationFailed) return false;	

    }

    this.message = this.validMessage;

    return true;

  },

    

  /**

   *	performs validation on the element and handles any error (validation or otherwise) it throws up

   *

   *	@var validationFunction {Function} - validation function to be used (ie Validate.Presence )

   *	@var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary

   * @return {Boolean} - whether the validation has passed or failed

   */

  validateElement: function(validationFunction, validationParamsObj){

    var value = (this.elementType == LiveValidation.SELECT) ? this.element.options[this.element.selectedIndex].value : this.element.value;     

    if(validationFunction == Validate.Acceptance){

      if(this.elementType != LiveValidation.CHECKBOX) throw new Error('LiveValidation::validateElement - Element to validate acceptance must be a checkbox!');

      value = this.element.checked;

    }

    var isValid = true;

    try{    

      validationFunction(value, validationParamsObj);

    } catch(error) {

      if(error instanceof Validate.Error){

        if( value !== '' || (value === '' && this.displayMessageWhenEmpty) ){

          this.validationFailed = true;

          this.message = error.message;

          isValid = false;

        }

      }else{

        throw error;

      }

    }finally{

      return isValid;

    }

  },

    

  /**

   *	makes it do the all the validations and fires off the onValid or onInvalid callbacks

   *

   * @return {Boolean} - whether the all the validations passed or if one failed

   */

  validate: function(){

  	if(!this.element.disabled){

		var isValid = this.doValidations();

		if(isValid){

			this.onValid();

			return true;

		}else {

			this.onInvalid();

			return false;

		}

	}else{

    return true;

  }

  },

  

  /**

   *  enables the field

   *

   *  @return {LiveValidation} - the LiveValidation object for chaining

   */

  enable: function(){

  	this.element.disabled = false;

	return this;

  },

  

  /**

   *  disables the field and removes any message and styles associated with the field

   *

   *  @return {LiveValidation} - the LiveValidation object for chaining

   */

  disable: function(){

  	this.element.disabled = true;

	this.removeMessageAndFieldClass();

	return this;

  },

    

  /** Message insertion methods ****************************

   * 

   * These are only used in the onValid and onInvalid callback functions and so if you overide the default callbacks,

   * you must either impliment your own functions to do whatever you want, or call some of these from them if you 

   * want to keep some of the functionality

   */

   

  /**

   *	makes a span containg the passed or failed message

   *

   * @return {HTMLSpanObject} - a span element with the message in it

   */

  createMessageSpan: function(){

    var span = document.createElement('span');

    var textNode = document.createTextNode(this.message);

    span.appendChild(textNode);

    return span;

  },

    

  /**

   *	inserts the element containing the message in place of the element that already exists (if it does)

   *

   * @var elementToIsert {HTMLElementObject} - an element node to insert

   */

  insertMessage: function(elementToInsert){

    this.removeMessage();

    var className = this.validationFailed ? this.invalidClass : this.validClass;

    if( (this.displayMessageWhenEmpty && (this.elementType == LiveValidation.CHECKBOX || this.element.value == '')) || this.element.value != '' ){

      $(elementToInsert).addClassName( this.messageClass + (' ' + className) );

      if( nxtSibling = this.insertAfterWhatNode.nextSibling){

        this.insertAfterWhatNode.parentNode.insertBefore(elementToInsert, nxtSibling);

      }else{

        this.insertAfterWhatNode.parentNode.appendChild(elementToInsert);

      }

    }

  },

    

  /**

   *	changes the class of the field based on whether it is valid or not

   */

  addFieldClass: function(){ 

    this.removeFieldClass();

    if(!this.validationFailed){

      if(this.displayMessageWhenEmpty || this.element.value != ''){

        if(!this.element.hasClassName(this.validFieldClass)) this.element.addClassName(this.validFieldClass);

      }

    }else{

      if(!this.element.hasClassName(this.invalidFieldClass)) this.element.addClassName(this.invalidFieldClass);

    }

  },

    

  /**

   *	removes the message element if it exists

   */

  removeMessage: function(){

    if( nxtEl = this.insertAfterWhatNode.next('.' + this.messageClass) ) nxtEl.remove();

  },

    

  /**

   *	removes the class that has been applied to the field to indicte if valid or not

   */

  removeFieldClass: function(){

    this.element.removeClassName(this.invalidFieldClass);

    this.element.removeClassName(this.validFieldClass);

  },

    

  /**

   *	removes the message and the field class

   */

  removeMessageAndFieldClass: function(){

    this.removeMessage();

    this.removeFieldClass();

  }

   

} // end of LiveValidation.prototype object



/*************************************** LiveValidationForm class ****************************************/



var LiveValidationForm = Class.create();



/*** static ***/



Object.extend(LiveValidationForm, {



	/**

	 * namespace to hold instances

	 */

	instances: {},

	

	/**

	   *	gets the instance of the LiveValidationForm if it has already been made or creates it if it doesnt exist

	   *	

	   *	@var element {HTMLFormElement} - a dom element reference to a form

	   */

	getInstance: function(element){

	  var rand = Math.random() * Math.random();

	  if(!element.id) element.id = 'formId_' + rand.toString().replace(/\./, '') + new Date().valueOf();

	  if(!LiveValidationForm.instances[element.id]) LiveValidationForm.instances[element.id] = new LiveValidationForm(element);

	  return LiveValidationForm.instances[element.id];

	}



});



/*** prototype ***/



LiveValidationForm.prototype = {

  

  /**

   *	constructor for LiveValidationForm - handles validation of LiveValidation fields belonging to this form on its submittal

   *	

   *	@var element {HTMLFormElement} - a dom element reference to the form to turn into a LiveValidationForm

   */

  initialize: function(element){

    this.element = $(element);

    this.fields = [];

    // need to capture onsubmit in this way rather than Event.observe because Rails helpers add events inline

	// and must ensure that the validation is run before any previous submit events 

	//(hence not using Event.observe, as inline events appear to be captured before prototype events)

	this.oldOnSubmit = this.element.onsubmit || function(){};

	this.element.onsubmit = function(e){

	  var ret = (LiveValidation.massValidate(this.fields)) ? this.oldOnSubmit.call(this.element, e) !== false : false;

	  if (!ret) Event.stop(e)

    }.bindAsEventListener(this);

  },

  

  /**

   *	adds a LiveValidation field to the forms fields array

   *	

   *	@var lvObj {LiveValidation} - a LiveValidation object

   */

  addField: function(lvObj){

    this.fields.push(lvObj);

  },

  

  /**

   *	removes a LiveValidation field from the forms fields array

   *	

   *	@var victim {LiveValidation} - a LiveValidation object

   */

  removeField: function(victim){

	this.fields = this.fields.without(victim);

  },

  

  /**

   *	destroy this instance and its events

   *

   * @var force {Boolean} - whether to force the detruction even if there are fields still associated

   */

  destroy: function(force){

  	// only destroy if has no fields and not being forced

  	if (this.fields.length != 0 && !force) return false;

	// remove events

	this.element.onsubmit = this.oldOnSubmit;

	// remove from the instances namespace

	LiveValidationForm.instances[this.element.id] = null;

	return true;

  }

   

}// end of LiveValidationForm prototype



/*************************************** Validate class ****************************************/

/**

 * This class contains all the methods needed for doing the actual validation itself

 *

 * All methods are static so that they can be used outside the context of a form field

 * as they could be useful for validating stuff anywhere you want really

 *

 * All of them will return true if the validation is successful, but will raise a ValidationError if

 * they fail, so that this can be caught and the message explaining the error can be accessed ( as just 

 * returning false would leave you a bit in the dark as to why it failed )

 *

 * Can use validation methods alone and wrap in a try..catch statement yourself if you want to access the failure

 * message and handle the error, or use the Validate::now method if you just want true or false

 */



var Validate = {



  /**

   *	validates that the field has been filled in

   *

   *	@var value {mixed} - value to be checked

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							failureMessage {String} - the message to show when the field fails validation 

   *													  (DEFAULT: "Can't be empty!")

   */

  Presence: function(value, paramsObj){

    var params = Object.extend({

      failureMessage: "Can't be empty!"

    }, paramsObj || {});

    if(value === '' || value === null || value === undefined) Validate.fail(params.failureMessage);

    return true;

  },

    

  /**

   *	validates that the value is numeric, does not fall within a given range of numbers

   *	

   *	@var value {mixed} - value to be checked

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							notANumberMessage {String} - the message to show when the validation fails when value is not a number

   *													  	  (DEFAULT: "Must be a number!")

   *							notAnIntegerMessage {String} - the message to show when the validation fails when value is not an integer

   *													  	  (DEFAULT: "Must be a number!")

   *							wrongNumberMessage {String} - the message to show when the validation fails when is param is used

   *													  	  (DEFAULT: "Must be {is}!")

   *							tooLowMessage {String} 		- the message to show when the validation fails when minimum param is used

   *													  	  (DEFAULT: "Must not be less than {minimum}!")

   *							tooHighMessage {String} 	- the message to show when the validation fails when maximum param is used

   *													  	  (DEFAULT: "Must not be more than {maximum}!")

   *							is {Int} 					- the value must be equal to this numeric value

   *							minimum {Int} 				- the minimum numeric allowed

   *							maximum {Int} 				- the maximum numeric allowed

   *                          onlyInteger {Boolean} - if true will only allow integers to be valid

   *                                                             (DEFAULT: false)

   *

   *  NB. can be checked if it is within a range by specifying both a minimum and a maximum

   *  NB. will evaluate numbers represented in scientific form (ie 2e10) correctly as numbers				

   */

  Numericality: function(value, paramsObj){

    var suppliedValue = value;

    var value = Number(value);

    var paramsObj = paramsObj || {};

    var params = { 

      notANumberMessage:  paramsObj.notANumberMessage || "Must be a number!",

      notAnIntegerMessage: paramsObj.notAnIntegerMessage || "Must be an integer!",

      wrongNumberMessage: paramsObj.wrongNumberMessage || "Must be " + paramsObj.is + "!",

      tooLowMessage:         paramsObj.tooLowMessage || "Must not be less than " + paramsObj.minimum + "!",

      tooHighMessage:        paramsObj.tooHighMessage || "Must not be more than " + paramsObj.maximum + "!", 

      is:                            ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null,

      minimum:                   ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null,

      maximum:                  ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null,

      onlyInteger:               paramsObj.onlyInteger || false

    };

    if (!isFinite(value))  Validate.fail(params.notANumberMessage);

    if (params.onlyInteger && ( ( /\.0+$|\.$/.test(String(suppliedValue)) )  || ( value != parseInt(value) ) ) ) Validate.fail(params.notAnIntegerMessage);

    switch(true){

      case (params.is !== null):

        if( value != Number(params.is) ) Validate.fail(params.wrongNumberMessage);

        break;

      case (params.minimum !== null && params.maximum !== null):

        Validate.Numericality(value, {tooLowMessage: params.tooLowMessage, minimum: params.minimum});

        Validate.Numericality(value, {tooHighMessage: params.tooHighMessage, maximum: params.maximum});

        break;

      case (params.minimum !== null):

        if( value < Number(params.minimum) ) Validate.fail(params.tooLowMessage);

        break;

      case (params.maximum !== null):

        if( value > Number(params.maximum) ) Validate.fail(params.tooHighMessage);

        break;

    }

    return true;

  },

    

  /**

   *	validates against a RegExp pattern

   *	

   *	@var value {mixed} - value to be checked

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							failureMessage {String} - the message to show when the field fails validation

   *													  (DEFAULT: "Not valid!")

   *							pattern {RegExp} 		- the regular expression pattern

   *													  (DEFAULT: /./)

   *             negate {Boolean} - if set to true, will validate true if the pattern is not matched

   *                           (DEFAULT: false)

   *

   *  NB. will return true for an empty string, to allow for non-required, empty fields to validate.

   *		If you do not want this to be the case then you must either add a LiveValidation.PRESENCE validation

   *		or build it into the regular expression pattern

   */

  Format: function(value, paramsObj){

    var value = String(value);

    var params = Object.extend({ 

      failureMessage: "Not valid!",

      pattern:           /./ ,

      negate:            false

    }, paramsObj || {});

    if(!params.negate && !params.pattern.test(value)) Validate.fail(params.failureMessage); // normal

    if(params.negate && params.pattern.test(value)) Validate.fail(params.failureMessage); // negated

    return true;

  },

    

  /**

   *	validates that the field contains a valid email address

   *	

   *	@var value {mixed} - value to be checked

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							failureMessage {String} - the message to show when the field fails validation

   *													  (DEFAULT: "Must be a number!" or "Must be an integer!")

   */

  Email: function(value, paramsObj){

    var params = Object.extend({ 

      failureMessage: "Must be a valid email address!"

    }, paramsObj || {});

    Validate.Format(value, { failureMessage: params.failureMessage, pattern: /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i } );

    return true;

  },

    

  /**

   *	validates the length of the value

   *	

   *	@var value {mixed} - value to be checked

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							 wrongLengthMessage {String} - the message to show when the fails when is param is used

   *													  	  (DEFAULT: "Must be {is} characters long!")

   *							tooShortMessage {String} 	- the message to show when the fails when minimum param is used

   *													  	  (DEFAULT: "Must not be less than {minimum} characters long!")

   *							tooLongMessage {String} 	- the message to show when the fails when maximum param is used

   *													  	  (DEFAULT: "Must not be more than {maximum} characters long!")

   *							is {Int} 					- the length must be this long 

   *							minimum {Int} 				- the minimum length allowed

   *							maximum {Int} 				- the maximum length allowed

   *

   *  NB. can be checked if it is within a range by specifying both a minimum and a maximum				

   */

  Length: function(value, paramsObj){

    var value = String(value);

    var paramsObj = paramsObj || {};

    var params = { 

      wrongLengthMessage: paramsObj.wrongLengthMessage || "Must be " + paramsObj.is + " characters long!",

      tooShortMessage:      paramsObj.tooShortMessage || "Must not be less than " + paramsObj.minimum + " characters long!",

      tooLongMessage:       paramsObj.tooLongMessage || "Must not be more than " + paramsObj.maximum + " characters long!",

      is:                           ((paramsObj.is) || (paramsObj.is == 0)) ? paramsObj.is : null,

      minimum:                  ((paramsObj.minimum) || (paramsObj.minimum == 0)) ? paramsObj.minimum : null,

      maximum:                 ((paramsObj.maximum) || (paramsObj.maximum == 0)) ? paramsObj.maximum : null

    }

    switch(true){

      case (params.is !== null):

        if( value.length != Number(params.is) ) Validate.fail(params.wrongLengthMessage);

        break;

      case (params.minimum !== null && params.maximum !== null):

        Validate.Length(value, {tooShortMessage: params.tooShortMessage, minimum: params.minimum});

        Validate.Length(value, {tooLongMessage: params.tooLongMessage, maximum: params.maximum});

        break;

      case (params.minimum !== null):

        if( value.length < Number(params.minimum) ) Validate.fail(params.tooShortMessage);

        break;

      case (params.maximum !== null):

        if( value.length > Number(params.maximum) ) Validate.fail(params.tooLongMessage);

        break;

      default:

        throw new Error("Validate::Length - Length(s) to validate against must be provided!");

    }

    return true;

  },

    

  /**

   *	validates that the value falls within a given set of values

   *	

   *	@var value {mixed} - value to be checked

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							failureMessage {String} - the message to show when the field fails validation

   *													  (DEFAULT: "Must be included in the list!")

   *							within {Array} 			- an array of values that the value should fall in 

   *													  (DEFAULT: [])	

   *							allowNull {Bool} 		- if true, and a null value is passed in, validates as true

   *													  (DEFAULT: false)

   *             partialMatch {Bool} 	- if true, will not only validate against the whole value to check but also if it is a substring of the value 

   *													  (DEFAULT: false)

   *             caseSensitive {Bool} - if false will compare strings case insensitively

   *                          (DEFAULT: true)

   *             negate {Bool} - if true, will validate that the value is not within the given set of values

   *													  (DEFAULT: false)			

   */

  Inclusion: function(value, paramsObj){

    var params = Object.extend({

    failureMessage: "Must be included in the list!",

      within:           [],

      allowNull:        false,

      partialMatch:   false,

      caseSensitive: true,

      negate:          false

    }, paramsObj || {});

    if(params.allowNull && value == null) return true;

    if(!params.allowNull && value == null) Validate.fail(params.failureMessage);

    //if case insensitive, make all strings in the array lowercase, and the value too

    if(!params.caseSensitive){ 

      var lowerWithin = [];

      params.within.each( function(item){

        if(typeof item == 'string') item = item.toLowerCase();

        lowerWithin.push(item);

      });

      params.within = lowerWithin;

      if(typeof value == 'string') value = value.toLowerCase();

    }

    var found = (params.within.indexOf(value) == -1) ? false : true;

    if(params.partialMatch){

      found = false;

      params.within.each( function(arrayVal){

        if(value.indexOf(arrayVal) != -1 ) found = true;

      }); 

    }

    if( (!params.negate && !found) || (params.negate && found) ) Validate.fail(params.failureMessage);

    return true;

  },

    

  /**

   *	validates that the value does not fall within a given set of values (shortcut for using Validate.Inclusion with exclusion: true)

   *	

   *	@var value {mixed} - value to be checked

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							failureMessage {String} - the message to show when the field fails validation

   *													  (DEFAULT: "Must not be included in the list!")

   *							within {Array} 			- an array of values that the value should not fall in 

   *													  (DEFAULT: [])

   *							allowNull {Bool} 		- if true, and a null value is passed in, validates as true

   *													  (DEFAULT: false)

   *             partialMatch {Bool} 	- if true, will not only validate against the whole value to check but also if it is a substring of the value 

   *													  (DEFAULT: false)

   *             caseSensitive {Bool} - if false will compare strings case insensitively

   *                          (DEFAULT: true)					

   */

  Exclusion: function(value, paramsObj){

    var params = Object.extend({

      failureMessage: "Must not be included in the list!",

      within:             [],

      allowNull:          false,

      partialMatch:     false,

      caseSensitive:   true

    }, paramsObj || {});

    params.negate = true;// set outside of params so cannot be overridden

    Validate.Inclusion(value, params);

    return true;

  },

    

  /**

   *	validates that the value matches that in another field

   *	

   *	@var value {mixed} - value to be checked

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							failureMessage {String} - the message to show when the field fails validation

   *													  (DEFAULT: "Does not match!")

   *							match {String} 			- id of the field that this one should match						

   */

  Confirmation: function(value, paramsObj){

    if(!paramsObj.match) throw new Error("Validate::Confirmation - Error validating confirmation: Id of element to match must be provided!");

    var params = Object.extend({

      failureMessage: "Does not match!",

      match:            null

    }, paramsObj || {});

    params.match = $(paramsObj.match);

    if(!params.match) throw new Error("Validate::Confirmation - There is no reference with name of, or element with id of '" + params.match + "'!");

    if(value != params.match.value) Validate.fail(params.failureMessage);

    return true;

  },

    

  /**

   *	validates that the value is true (for use primarily in detemining if a checkbox has been checked)

   *	

   *	@var value {mixed} - value to be checked if true or not (usually a boolean from the checked value of a checkbox)

   *	@var paramsObj {Object} - parameters for this particular validation, see below for details

   *

   *	paramsObj properties:

   *							failureMessage {String} - the message to show when the field fails validation 

   *													  (DEFAULT: "Must be accepted!")

   */

  Acceptance: function(value, paramsObj){

    var params = Object.extend({

      failureMessage: "Must be accepted!"

    }, paramsObj || {});

    if(!value) Validate.fail(params.failureMessage);

    return true;

  },

  

   /**

     *	validates against a custom function that returns true or false (or throws a Validate.Error) when passed the value

     *	

     *	@var value {mixed} - value to be checked

     *	@var paramsObj {Object} - parameters for this particular validation, see below for details

     *

     *	paramsObj properties:

     *							failureMessage {String} - the message to show when the field fails validation

     *													  (DEFAULT: "Not valid!")

     *							against {Function} 			- a function that will take the value and object of arguments and return true or false 

     *													  (DEFAULT: function(){ return true; })

     *							args {Object} 		- an object of named arguments that will be passed to the custom function so are accessible through this object within it 

     *													  (DEFAULT: {})

     */

  Custom: function(value, paramsObj){

    var params = Object.extend({

	  against: function(){ return true; },

	  args: {},

      failureMessage: "Not valid!"

    }, paramsObj || {});

    if(!params.against(value, params.args)) Validate.fail(params.failureMessage);

    return true;

  },

    

  /**

   *	validates whatever it is you pass in, and handles the validation error for you so it gives a nice true or false reply

   *

   *	@var validationFunction {Function} - validation function to be used (ie Validate.Presence )

   *	@var value {mixed} - value to be checked 

   *	@var validationParamsObj {Object} - parameters for doing the validation, if wanted or necessary

   */

  now: function(validationFunction, value, validationParamsObj){

    if(!validationFunction) throw new Error("Validate::now - Validation function must be provided!");

    var isValid = true;

    try{    

      validationFunction(value, validationParamsObj || {});

    } catch(error) {

      if(error instanceof Validate.Error){

        isValid =  false;

      }else{

        throw error;

      }

    }finally{ 

      return isValid 

    }

  },

  

    

  Error: function(errorMessage){

    this.message = errorMessage;

    this.name = 'ValidationError';

  },

    

  fail: function(errorMessage){

    throw new Validate.Error(errorMessage);

  }



} // end of Validate object
