text: /**
   * jQuery yiiactiveform plugin file.
   *
   * @author Qiang Xue <qiang.xue@gmail.com>
   * @link http://www.yiiframework.com/
   * @copyright Copyright &copy; 2008-2010 Yii Software LLC
   * @license http://www.yiiframework.com/license/
   * @version $Id: jquery.yiiactiveform.js 3538 2012-01-14 18:24:30Z mdomba $
   * @since 1.1.1
   */
  
  (function ($) {
  	/*
  	 * returns the value of the CActiveForm input field
  	 * performs additional checks to get proper values for checkbox / radiobutton / checkBoxList / radioButtonList
  	 * @param o object the jQuery object of the input element
  	 */
  	var getAFValue = function (o) {
  		var type,
  			c = [];
  		if (!o.length) {
  			return undefined;
  		}
  		if (o[0].tagName.toLowerCase() === 'span') {
  			o.find(':checked').each(function () {
  				c.push(this.value);
  			});
  			return c.join(',');
  		}
  		type = o.attr('type');
  		if (type === 'checkbox' || type === 'radio') {
  			return o.filter(':checked').val();
  		} else {
  			return o.val();
  		}
  	};
  
  	/**
  	 * yiiactiveform set function.
  	 * @param options map settings for the active form plugin. Please see {@link CActiveForm::options} for availablel options.
  	 */
  	$.fn.yiiactiveform = function (options) {
  		return this.each(function () {
  			var settings = $.extend({}, $.fn.yiiactiveform.defaults, options || {}),
  				$form = $(this);
  
  			if (settings.validationUrl === undefined) {
  				settings.validationUrl = $form.attr('action');
  			}
  			$.each(settings.attributes, function (i) {
  				this.value = getAFValue($form.find('#' + this.inputID));
  				settings.attributes[i] = $.extend({}, {
  					validationDelay: settings.validationDelay,
  					validateOnChange: settings.validateOnChange,
  					validateOnType: settings.validateOnType,
  					hideErrorMessage: settings.hideErrorMessage,
  					inputContainer: settings.inputContainer,
  					errorCssClass: settings.errorCssClass,
  					successCssClass: settings.successCssClass,
  					beforeValidateAttribute: settings.beforeValidateAttribute,
  					afterValidateAttribute: settings.afterValidateAttribute,
  					validatingCssClass: settings.validatingCssClass
  				}, this);
  			});
  			$form.data('settings', settings);
  
  			settings.submitting = false;  // whether it is waiting for ajax submission result
  			var validate = function (attribute, forceValidate) {
  				if (forceValidate) {
  					attribute.status = 2;
  				}
  				$.each(settings.attributes, function () {
  					if (this.value !== getAFValue($form.find('#' + this.inputID))) {
  						this.status = 2;
  						forceValidate = true;
  					}
  				});
  				if (!forceValidate) {
  					return;
  				}
  
  				if (settings.timer !== undefined) {
  					clearTimeout(settings.timer);
  				}
  				settings.timer = setTimeout(function () {
  					if (settings.submitting || $form.is(':hidden')) {
  						return;
  					}
  					if (attribute.beforeValidateAttribute === undefined || attribute.beforeValidateAttribute($form, attribute)) {
  						$.each(settings.attributes, function () {
  							if (this.status === 2) {
  								this.status = 3;
  								$.fn.yiiactiveform.getInputContainer(this, $form).addClass(this.validatingCssClass);
  							}
  						});
  						$.fn.yiiactiveform.validate($form, function (data) {
  							var hasError = false;
  							$.each(settings.attributes, function () {
  								if (this.status === 2 || this.status === 3) {
  									hasError = $.fn.yiiactiveform.updateInput(this, data, $form) || hasError;
  								}
  							});
  							if (attribute.afterValidateAttribute !== undefined) {
  								attribute.afterValidateAttribute($form, attribute, data, hasError);
  							}
  						});
  					}
  				}, attribute.validationDelay);
  			};
  
  			$.each(settings.attributes, function (i, attribute) {
  				if (this.validateOnChange) {
  					$form.find('#' + this.inputID).change(function () {
  						validate(attribute, false);
  					}).blur(function () {
  						if (attribute.status !== 2 && attribute.status !== 3) {
  							validate(attribute, !attribute.status);
  						}
  					});
  				}
  				if (this.validateOnType) {
  					$form.find('#' + this.inputID).keyup(function () {
  						if (attribute.value !== getAFValue($(this))) {
  							validate(attribute, false);
  						}
  					});
  				}
  			});
  
  			if (settings.validateOnSubmit) {
  				$form.find(':submit').live('mouseup keyup', function () {
  					$form.data('submitObject', $(this));
  				});
  				var validated = false;
  				$form.submit(function () {
  					if (validated) {
  						return true;
  					}
  					if (settings.timer !== undefined) {
  						clearTimeout(settings.timer);
  					}
  					settings.submitting = true;
  					if (settings.beforeValidate === undefined || settings.beforeValidate($form)) {
  						$.fn.yiiactiveform.validate($form, function (data) {
  							var hasError = false;
  							$.each(settings.attributes, function () {
  								hasError = $.fn.yiiactiveform.updateInput(this, data, $form) || hasError;
  							});
  							$.fn.yiiactiveform.updateSummary($form, data);
  							if (settings.afterValidate === undefined || settings.afterValidate($form, data, hasError)) {
  								if (!hasError) {
  									validated = true;
  									var $button = $form.data('submitObject') || $form.find(':submit:first');
  									// TODO: if the submission is caused by "change" event, it will not work
  									if ($button.length) {
  										$button.click();
  									} else {  // no submit button in the form
  										$form.submit();
  									}
  									return;
  								}
  							}
  							settings.submitting = false;
  						});
  					} else {
  						settings.submitting = false;
  					}
  					return false;
  				});
  			}
  
  			/*
  			 * In case of reseting the form we need to reset error messages
  			 * NOTE1: $form.reset - does not exist
  			 * NOTE2: $form.live('reset', ...) does not work
  			 */
  			$form.bind('reset', function () {
  				/*
  				 * because we bind directly to a form reset event, not to a reset button (that could or could not exist),
  				 * when this function is executed form elements values have not been reset yet,
  				 * because of that we use the setTimeout
  				 */
  				setTimeout(function () {
  					$.each(settings.attributes, function () {
  						this.status = 0;
  						var $error = $form.find('#' + this.errorID),
  							$container = $.fn.yiiactiveform.getInputContainer(this, $form);
  
  						$container.removeClass(
  							this.validatingCssClass + ' ' +
  							this.errorCssClass + ' ' +
  							this.successCssClass
  						);
  
  						$error.html('').hide();
  
  						/*
  						 * without the setTimeout() we would get here the current entered value before the reset instead of the reseted value
  						 */
  						this.value = getAFValue($form.find('#' + this.inputID));
  					});
  					/*
  					 * If the form is submited (non ajax) with errors, labels and input gets the class 'error'
  					 */
  					$form.find('label, input').each(function () {
  						$(this).removeClass('error');
  					});
  					$('#' + settings.summaryID).hide().find('ul').html('');
  					//.. set to initial focus on reset
  					if (settings.focus !== undefined && !window.location.hash) {
  						$form.find(settings.focus).focus();
  					}
  				}, 1);
  			});
  
  			/*
  			 * set to initial focus
  			 */
  			if (settings.focus !== undefined && !window.location.hash) {
  				$form.find(settings.focus).focus();
  			}
  		});
  	};
  
  	/**
  	 * Returns the container element of the specified attribute.
  	 * @param attribute object the configuration for a particular attribute.
  	 * @param form the form jQuery object
  	 * @return jquery the jquery representation of the container
  	 */
  	$.fn.yiiactiveform.getInputContainer = function (attribute, form) {
  		if (attribute.inputContainer === undefined) {
  			return form.find('#' + attribute.inputID).closest('div');
  		} else {
  			return form.find(attribute.inputContainer).filter(':has("#' + attribute.inputID + '")');
  		}
  	};
  
  	/**
  	 * updates the error message and the input container for a particular attribute.
  	 * @param attribute object the configuration for a particular attribute.
  	 * @param messages array the json data obtained from the ajax validation request
  	 * @param form the form jQuery object
  	 * @return boolean whether there is a validation error for the specified attribute
  	 */
  	$.fn.yiiactiveform.updateInput = function (attribute, messages, form) {
  		attribute.status = 1;
  		var $error, $container,
  			hasError = false,
  			$el = form.find('#' + attribute.inputID);
  		if ($el.length) {
  			hasError = messages !== null && $.isArray(messages[attribute.id]) && messages[attribute.id].length > 0;
  			$error = form.find('#' + attribute.errorID);
  			$container = $.fn.yiiactiveform.getInputContainer(attribute, form);
  
  			$container.removeClass(
  				attribute.validatingCssClass + ' ' + 
  				attribute.errorCssClass + ' ' + 
  				attribute.successCssClass
  			);
  
  			if (hasError) {
  				$error.html(messages[attribute.id][0]);
  				$container.addClass(attribute.errorCssClass);
  			} else if (attribute.enableAjaxValidation || attribute.clientValidation) {
  				$container.addClass(attribute.successCssClass);
  			}
  			if (!attribute.hideErrorMessage) {
  				$error.toggle(hasError);
  			}
  
  			attribute.value = getAFValue($el);
  		}
  		return hasError;
  	};
  
  	/**
  	 * updates the error summary, if any.
  	 * @param form jquery the jquery representation of the form
  	 * @param messages array the json data obtained from the ajax validation request
  	 */
  	$.fn.yiiactiveform.updateSummary = function (form, messages) {
  		var settings = $(form).data('settings'),
  			content = '';
  		if (settings.summaryID === undefined) {
  			return;
  		}
  		if (messages) {
  			$.each(settings.attributes, function () {
  				if ($.isArray(messages[this.id])) {
  					$.each(messages[this.id], function (j, message) {
  						content = content + '<li>' + message + '</li>';
  					});
  				}
  			});
  		}
  		$('#' + settings.summaryID).toggle(content !== '').find('ul').html(content);
  	};
  
  	/**
  	 * Performs the ajax validation request.
  	 * This method is invoked internally to trigger the ajax validation.
  	 * @param form jquery the jquery representation of the form
  	 * @param successCallback function the function to be invoked if the ajax request succeeds
  	 * @param errorCallback function the function to be invoked if the ajax request fails
  	 */
  	$.fn.yiiactiveform.validate = function (form, successCallback, errorCallback) {
  		var $form = $(form),
  			settings = $form.data('settings'),
  			needAjaxValidation = false,
  			messages = {};
  		$.each(settings.attributes, function () {
  			var value,
  				msg = [];
  			if (this.clientValidation !== undefined && (settings.submitting || this.status === 2 || this.status === 3)) {
  				value = getAFValue($form.find('#' + this.inputID));
  				this.clientValidation(value, msg, this);
  				if (msg.length) {
  					messages[this.id] = msg;
  				}
  			}
  			if (this.enableAjaxValidation && !msg.length && (settings.submitting || this.status === 2 || this.status === 3)) {
  				needAjaxValidation = true;
  			}
  		});
  
  		if (!needAjaxValidation || settings.submitting && !$.isEmptyObject(messages)) {
  			if (settings.submitting) {
  				// delay callback so that the form can be submitted without problem
  				setTimeout(function () {
  					successCallback(messages);
  				}, 200);
  			} else {
  				successCallback(messages);
  			}
  			return;
  		}
  
  		var $button = $form.data('submitObject'),
  			extData = '&' + settings.ajaxVar + '=' + $form.attr('id');
  		if ($button && $button.length) {
  			extData += '&' + $button.attr('name') + '=' + $button.attr('value');
  		}
  
  		$.ajax({
  			url : settings.validationUrl,
  			type : $form.attr('method'),
  			data : $form.serialize() + extData,
  			dataType : 'json',
  			success : function (data) {
  				if (data !== null && typeof data === 'object') {
  					$.each(settings.attributes, function () {
  						if (!this.enableAjaxValidation) {
  							delete data[this.id];
  						}
  					});
  					successCallback($.extend({}, messages, data));
  				} else {
  					successCallback(messages);
  				}
  			},
  			error : function () {
  				if (errorCallback !== undefined) {
  					errorCallback();
  				}
  			}
  		});
  	};
  
  	/**
  	 * Returns the configuration for the specified form.
  	 * The configuration contains all needed information to perform ajax-based validation.
  	 * @param form jquery the jquery representation of the form
  	 * @return object the configuration for the specified form.
  	 */
  	$.fn.yiiactiveform.getSettings = function (form) {
  		return $(form).data('settings');
  	};
  
  	$.fn.yiiactiveform.defaults = {
  		ajaxVar: 'ajax',
  		validationUrl: undefined,
  		validationDelay: 200,
  		validateOnSubmit : false,
  		validateOnChange : true,
  		validateOnType : false,
  		hideErrorMessage : false,
  		inputContainer : undefined,
  		errorCssClass : 'error',
  		successCssClass : 'success',
  		validatingCssClass : 'validating',
  		summaryID : undefined,
  		timer: undefined,
  		beforeValidateAttribute: undefined, // function (form, attribute) : boolean
  		afterValidateAttribute: undefined,  // function (form, attribute, data, hasError)
  		beforeValidate: undefined, // function (form) : boolean
  		afterValidate: undefined,  // function (form, data, hasError) : boolean
  		/**
  		 * list of attributes to be validated. Each array element is of the following structure:
  		 * {
  		 *     id : 'ModelClass_attribute', // the unique attribute ID
  		 *     model : 'ModelClass', // the model class name
  		 *     name : 'name', // attribute name
  		 *     inputID : 'input-tag-id',
  		 *     errorID : 'error-tag-id',
  		 *     value : undefined,
  		 *     status : 0,  // 0: empty, not entered before,  1: validated, 2: pending validation, 3: validating
  		 *     focus : undefined,  // jquery selector that indicates which element to receive input focus initially
  		 *     validationDelay: 200,
  		 *     validateOnChange : true,
  		 *     validateOnType : false,
  		 *     hideErrorMessage : false,
  		 *     inputContainer : undefined,
  		 *     errorCssClass : 'error',
  		 *     successCssClass : 'success',
  		 *     validatingCssClass : 'validating',
  		 *     enableAjaxValidation : true,
  		 *     enableClientValidation : true,
  		 *     clientValidation : undefined, // function (value, messages, attribute) : client-side validation
  		 *     beforeValidateAttribute: undefined, // function (form, attribute) : boolean
  		 *     afterValidateAttribute: undefined,  // function (form, attribute, data, hasError)
  		 * }
  		 */
  		attributes : []
  	};
  })(jQuery);