window._ || (window._ = {
	isFunction: function(func) {
		return typeof func == 'function';
	},
	isObject: function(obj) {
		return obj === Object(obj);
	},
	isUndefined: function(value) {
		return value === void 0;
	}
});

/**
 * Form validator.
 */
var FormValidator = (function() {

	///// private functions

	function bindEventsHandler(formValidator) {
		formValidator.formWalk(function(input) {
			$(input).on(this.options.events.join(' '), function(ev) {
				formValidator.event = ev;
				formValidator.validateField(ev);
			});
		});

		if (typeof formValidator.options.bindAdditionalEvents === 'function') {
			formValidator.options.bindAdditionalEvents.call(formValidator);
		}
	}

	function rejectDeferreds(deferreds) {
		if (!_.isArray(deferreds)) {
			return;
		}
		$.each(deferreds, function(index, deferred) {
			if (deferred && _.isFunction(deferred['reject'])) {
				deferred.doNotCallValidatorCallback = true; // suppress any callbacks @todo: replace this hack by proper usage of deferred
				deferred.reject();
			}
		});
	}

	function keepDeferredsForField(formValidator, fieldName, newDeferredsList)
	{
		rejectDeferreds(formValidator.fieldDeferredMap[fieldName]);
		formValidator.fieldDeferredMap[fieldName] = newDeferredsList || [];
	}

	function bindSubmitHandler(formValidator) {
		var fv = formValidator;
		var isRunning = false; // parallel submitting guard

		fv.form.on('submit', function() {
			fv.form.trigger('beforeSubmit');

			if(!fv.options.submitHandler && fv.isValid) {
				// if it's true then we called it from this function having set flag to true
				// garin: ???
				return true
			}

			if(isRunning) { // prevent parallel submitting
				return false;
			}
			isRunning = true;

			fv.runValidation()
				.done(function() {
					fv.isValid = fv.isFormValid();
					var extraValidationResult = fv.runAdditionalValidation();
					isRunning = false;

					if(fv.isValid && extraValidationResult) {
						_.isFunction(fv.options.submitHandler)
							? fv.options.submitHandler.call(fv, fv.form)
							: fv.form.get(0).submit();
					}
				})
			;

			return false;
		});
	}

	function flushErrors(formValidator) {
		formValidator.formWalk(function(input) {
			formValidator.errors[input.name] = null;
		});
	}


	///// constructor
	function FormValidator(selector) {
		this.form = $(selector);
		this.fieldDeferredMap = {};

        if (typeof this.form.submit !== 'function') {
            throw new Error('There should not be an input with name "submit".');
        }

		this.options = {
			rules: {},
			messages: {},
			errorPlacement: function(fieldName, message) {},
			successPlacement: function(fieldName) {},
			events: ['focusout'],
			onload: false,
			changeSubmitButtonClass: true
			// rejectHandler: null,
			// additionalValidationHandler: null
		};
		this.errors = {};
		this.previousValidationErrors = {};
		this.isValid = false;
		this.defaultMessage = __('Поле заполнено неверно');
		this.cache = {}; // object for caching remote validation results
		this.requests = {};

        Pod.declare('validator.' + selector, this);
	}

	FormValidator.prototype.getInputField = function(fieldName) {
		return this.form.find('input[name="' + fieldName + '"]').eq(0);
	};

	/** setup validation's rules and procedure */
	FormValidator.prototype.validate = function(options) {
		this.setOptions(options);
        this.fetchReferences();
		flushErrors(this);
		this.runValidation(!this.options.onload);
		bindEventsHandler(this);
		bindSubmitHandler(this);
	};

	FormValidator.prototype.request = function(url, value) {
		var def = $.Deferred(),
			self = this;

		if(this.requests[url]) {
			clearTimeout(this.requests[url]);
			this.requests[url] = null;
		}

		var cacheKey = url + '|' + value;

		if(typeof this.cache[cacheKey] !== 'undefined') {
			// console.log('resolve request from cache', value, this.cache[cacheKey], cacheKey);
			def.resolveWith(this, [this.cache[cacheKey]]);
			return def;
		}

		this.requests[url] = setTimeout(function() {
			$.ajax({
				url: url,
				type: 'POST',
				dataType: "json",
				data: { data: value },
				success: function(response) {
					// console.log('keep response', value, response, cacheKey);
					self.cache[cacheKey] = response;
					def.resolveWith(self, [response]);
				},
				error: function() {
					def.rejectWith(self);
				}
			});
		}, 500);
		return def;
	};

	FormValidator.prototype.setOptions = function(options) {
		this.options = $.extend(this.options, options);
	};

    /**
     * Fetch all the references to other inputs in order to validate required input when another input has been changed
     */
    FormValidator.prototype.fetchReferences = function () {
        var references = {};
        _.each(this.options.rules, function (rule, name) {
            if (_.isArray(rule.triggerOn)) {
                _.each(rule.triggerOn, function (inputName) {
                    references[inputName] || (references[inputName] = []);
                    references[inputName].push(name);
                });
            }
        });

        this.references = references;
    };

    FormValidator.prototype.triggerReferences = function (inputName, withoutRender) {
        if (this.references[inputName]) {
            var deferreds = [];
            _.each(this.references[inputName], function (name) {
                var input = this.getInput(name).get(0),
                    result = this.validateField(input, withoutRender);

                if (_.isArray(result)) {
                    deferreds = deferreds.concat(result);
                }
            }, this);

            return deferreds;
        }
        // return [];
    };

	FormValidator.prototype.runValidation = function(withoutRender) {
		var promises = [];
		this.isValid = false;
        this.isFullValidation = true;
		this.formWalk(function(input) {
			var result = this.validateField(input, withoutRender);

			if($.isArray(result)) {
				promises = promises.concat(result);
			}
		});

        this.setPromises(promises);

		return $.when.apply(null, promises)
            .always(function () {
                this.isFullValidation = false;
            }.bind(this)); // return combined promise for all deferred objects we've collected
	};

	FormValidator.prototype.runAdditionalValidation = function () {
		return !_.isFunction(this.options.additionalValidationHandler)
			|| this.options.additionalValidationHandler.call(this, this.form);
	};

    FormValidator.prototype.setPromises = function (promises) {
        this.promises = promises;

        _.each(promises, function (promise, i) {
            promise.always(function () {
                promises[i] = null;
            });
        }, this);
    };

	FormValidator.prototype.formWalk = function(callable) {
		var $this = this;
        if (!_.isFunction(callable)) {
            return;
        }
		this.form.find('input[type="text"],input[type="password"],input[type="checkbox"],input[type="hidden"],textarea,select').each(function(i, input) {
			if($this.isFieldToBeValidated(input.name)) {
				callable.call($this, input);
			}
		});
	};

    FormValidator.prototype.isFieldToBeValidated = function (inputName) {
        return !_.isUndefined(this.options.rules[inputName]) || _.isArray(this.references[inputName]);
    };

    // add memoization for performance
    FormValidator.prototype.getInput = _.memoize(function (inputName) {
        return this.form.find('[name="' + inputName + '"]');
    });

	FormValidator.prototype.validateField = function(field, withoutRender) {
		if(field.target != undefined) {
			field = field.target;
			withoutRender = false;
		}

        // if it's full validation (all the fields together) - don't trigger refs
		var fieldName = field.name;
        var triggeredInputValidations = this.isFullValidation ? null : this.triggerReferences(fieldName, withoutRender);
        var deferreds = _.isArray(triggeredInputValidations) ? triggeredInputValidations : [];
        var finalCallback = this.getFinalValidationCallback(withoutRender);
		var formValidator = this;

        if (_.isObject(this.options.rules[fieldName])) {
	        var fieldValidationRules = this.options.rules[fieldName];
	        if (fieldValidationRules.hasOwnProperty('before') && _.isFunction(fieldValidationRules['before'])) {
		        fieldValidationRules['before'].call(this); // me must call this function before run other validations
	        }

			// do not validate field if its value is empty and not required
			if(this.options.rules[fieldName].required == false && !field.value) {
				$.each(fieldValidationRules, function(ruleName, rule) {
					formValidator.successCallback(fieldName, withoutRender);
				});
				return finalCallback();
			}

			var fieldValid = true;
            // loop over all rules defined for this field
	        $.each(fieldValidationRules, function(ruleName, rule) {
                if(!validators.exist(ruleName)) {
                    return;
                }

				if(!fieldValid) {
					formValidator.renderError(fieldName, formValidator.errors[fieldName]);
					return;
				}

                var hasDependence = _.isFunction(rule['depends']);
		        var checkDepends = rule['depends'];
                // if 'depends' returns something casted to false, there is no need to run base (=depended) validation
                var resolvedByDependence = hasDependence && !checkDepends.call(formValidator, field);

                if (resolvedByDependence) {
                    // in this case we don't have to validate this rule for this field, i.e. if validate mobile we see that email is checked
                    // console.log('resolved by dependence', fieldName);
                    formValidator.successCallback(fieldName, withoutRender);
                    return;
                }

		        var params = rule['params'] || rule;
		        var value = _.isFunction(rule.value) ? rule.value.call(formValidator, field) : field.value;
		        var result = validators.applyValidator(formValidator, ruleName, value, params, field, function (result) {
                    if (!result) {
						fieldValid = false;
					}
			         // console.log('applyValidator callbacked with ', result, ruleName, value);
			        formValidator.resultCallback(result, ruleName, fieldName, withoutRender);
                });

                if (result === null) {
                    return; // the result is undefined - need to check the next rule
                }

                // otherwise result is deferred object
		        if (result.state() == 'rejected') { // if we have already rejected this promise we should break the chain
			        rejectDeferreds(deferreds);
                    deferreds = [];
                    return false; // break the cycle
                }
		        deferreds.push(result); // push another one promise in the list
            });
        } else {
            this.errors[fieldName] = null;
        }

		keepDeferredsForField(this, field.name, deferreds);

		// if we have any async calls then wait for all them to be done
		if(deferreds.length) {
			$.when.apply(null, deferreds) // wait when all of deferred have been resolved
                .always(finalCallback); // always call final callback
			return deferreds;
		}

		// otherwise calling final callback immediately (sync)
		finalCallback();
	};

	FormValidator.prototype.getFinalValidationCallback = function(withoutRender) {
		return $.proxy(function() {
			if(!withoutRender) {
				this.tryChangeSubmitButtonClass();
			}
		}, this);
	};

	FormValidator.prototype.successCallback = function(name, withoutRender) {
		this.errors[name] = null;
		if(!withoutRender) {
			this.renderSuccess(name);
		}
	};

	FormValidator.prototype.errorCallback = function(ruleName, name, withoutRender) {
		var messages = this.options.messages[name];
        var errorMessage = !_.isUndefined(messages) && !_.isUndefined(messages[ruleName]) ? messages[ruleName] : this.defaultMessage;

		this.errors[name] = _.isFunction(errorMessage) ? errorMessage() : errorMessage;
		if(!withoutRender) {
			this.renderError(name, this.errors[name]);
		}
	};

	FormValidator.prototype.resultCallback = function(result, ruleName, name, withoutRender) {
		if(!result) {
			this.errorCallback(ruleName, name, withoutRender)
		} else {
			this.successCallback(name, withoutRender)
		}
	};

	FormValidator.prototype.renderError = function(fieldName, errorText) {
		if(!_.isFunction(this.options.errorPlacement)) {
			return;
		}
		this.options.errorPlacement.call(this, fieldName, this.errors[fieldName] || errorText);
	};

	FormValidator.prototype.renderSuccess = function(fieldName) {
		if(!_.isFunction(this.options.successPlacement)) {
			return;
		}
		this.options.successPlacement.call(this, fieldName);
	};

	FormValidator.prototype.tryChangeSubmitButtonClass = function() {
		if(this.options.changeSubmitButtonClass) {
			var submitButton = this.form.find('input[type="submit"]');

			if(this.isFormValid()) {
				submitButton.removeClass('disabled').prop('disabled', false);
			} else {
				submitButton.addClass('disabled').prop('disabled', true);
			}
		}
	};

	FormValidator.prototype.isFormValid = function() {
		var result = true,
			errors = $.extend({}, this.previousValidationErrors, this.errors);
		$.each(errors, function(fieldName, error) {
			if(error) {
				result = false;
				return false;
			}
		});
		this.previousValidationErrors = errors;
		this.errors = {};
		return result;
	};

	var validators = {
		exist: function(name) {
			return _.isFunction(this.list[name]);
		},
		applyValidator: function(context, ruleName, value, params, field, callback) {
			if(!this.exist(ruleName)) {
				return null; // invalid validator
			}

			var that = this;
			var deferred = $.Deferred();
			deferred.ruleName = ruleName;
			deferred.always(function(result) {
				if (!deferred.hasOwnProperty('doNotCallValidatorCallback'))
					callback.call(that, result);
			});
			var ruleArguments = [value, params, field, deferred];

			var result = this.list[ruleName].apply(context, ruleArguments);
			if(!this.isAsync(ruleName)) {
				result ? deferred.resolve(result) : deferred.reject(result);
            } // otherwise async procedure connected to deferred himself

			// var promise = deferred.promise();
			// promise.ruleName = ruleName;
			return deferred; // return the deferred himself, not a promise, to get a chance to owner to reject procedure
		},
		isAsync: function(name) {
			return name == 'remote';
		},
		getParam: function (param) {
			if (typeof param === 'function') {
				return param.call();
			}
			return param;
		},
		list: {
			required: function(value, expected) {
				if(typeof expected == 'function') {
					expected = expected.call(this)
				}
				return expected ? $.trim(value).length > 0 : true;
			},
			checked: function(value, param, elem) {
				return $(elem).is(':checked');
			},
			minlength: function(value, param) {
				return $.trim(value).length >= param;
			},
			maxlength: function(value, param) {
				return $.trim(value).length <= param;
			},
			email: function(value) {
                value = $.trim(value);
				return !value.length || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i.test(value);
			},
			url: function(value) {
                value = $.trim(value);
				return /^(https?|s?ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value);
			},
			date: function(value) {
                value = $.trim(value);
				return !/Invalid|NaN/.test(new Date(value).toString());
			},
			dateISO: function(value) {
                value = $.trim(value);
				return /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}$/.test(value);
			},
			number: function(value) { // NOTE: returns true for empty value
                value = $.trim(value);
				return /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test(value);
			},
			digits: function(value) {
                value = $.trim(value);
				return /^\d+$/.test(value);
			},
			oneDigit: function(value) {
                value = $.trim(value);
				return /\d{1,}/.test(value);
			},
			// based on http://en.wikipedia.org/wiki/Luhn
			creditcard: function(value) {
                value = $.trim(value);
				if ( /[^0-9 \-]+/.test(value) ) {
					return false;
				}
				var nCheck = 0,
					nDigit = 0,
					bEven = false;

				value = value.replace(/\D/g, "");

				for (var n = value.length - 1; n >= 0; n--) {
					var cDigit = value.charAt(n);
					nDigit = parseInt(cDigit, 10);
					if ( bEven ) {
						if ( (nDigit *= 2) > 9 ) {
							nDigit -= 9;
						}
					}
					nCheck += nDigit;
					bEven = !bEven;
				}

				return (nCheck % 10) === 0;
			},
			min: function(value, param) {
				value = $.trim(value);
				return value >= validators.getParam(param);
			},
			max: function(value, param) {
				value = $.trim(value);
				return value <= validators.getParam(param);
			},
			emptyOrNotBelow: function (value, param) {
				value = $.trim(value);
				if (value == '') {
					return true;
				}
				return value >= validators.getParam(param);
			},
			emptyOrNotAbove: function (value, param) {
				value = $.trim(value);
				if (value == '') {
					return true;
				}
				return value <= validators.getParam(param);
			},
			equalTo: function(value, param) {
                value = $.trim(value);
				var target = $(param);
				return value === target.val();
			},
			remote: function(value, param, elem, deferred) {
                value = $.trim(value);
				this.request(param, value)
					.done(function(response) {
						if(!response) {
							// console.log('resolved remote rule checking with empty response', value, elem);
							deferred.resolve(true);
							return;
						}
						var result = response['status'] === true || response['status'] === "true" || response['status'] === "ok";
						// console.log('resolved remote rule checking with result ', result, value, elem);
						deferred.resolve(result);
					})
                    .fail(function () {
						// console.log('REJECTED remote rule checking', value, elem);
                        deferred.reject();
                    });
			},
			regexp: function(value, param) {
                value = $.trim(value);
				return param.test(value);
			},
			nospecialchars: function(value) {
                value = $.trim(value);
				return !value.length || !/[^@_\.\-1234567890qwertyuiopasdfghjklzxcvbnm ЁЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮёйцукенгшщзхъфывапролджэячсмитьбюAĀBCČDEĒFGĢHIĪJKĶLĻMNŅOPRSŠTUŪVZŽaābcčdeēfgģhiījkķlļmnņoprsštuūvzž]/i.test(value);
			},
			nospecialcharsCn: function(value) {
				value = $.trim(value);
				return !value.length || !/[^@_\.\-1234567890qwertyuiopasdfghjklzxcvbnm \u4E00-\u9FFF]/i.test(value);
			},
			nospecialcharsNocyrillic: function(value) {
                value = $.trim(value);
				return !/[^@_\.\-1234567890qwertyuiopasdfghjklzxcvbnm]/i.test(value);
			},
			custom: function(value, param, elem) {
                value = $.trim(value);
				return typeof param == 'function' && param(value, elem);
			},
			nocyrillic: function(value) {
                value = $.trim(value);
				return !/[а-яёА-ЯЁ]/.test(value);
			},
			nonumbers : function(value) {
                value = $.trim(value);
				return !/\d{1,}/.test(value);
			},
			nochars :  function(value) {
                value = $.trim(value);
				return !value.length || /^[\d\s]+$/.test(value);
			},
			nospaces : function(value) {
				return (value.indexOf(' ') === -1)
			},

			mobile: function(value) {
                value = $.trim(value);
				return !value.length || /^\(\d{3}\)\-\d{3}-\d{2}-\d{2}$/i.test(value);
			},
			capslock: function(value) {
				if(this.event) {
					var charCode = String.fromCharCode(this.event.which);

					if (charCode.toUpperCase() === charCode && charCode.toLowerCase() !== charCode && !this.event.shiftKey || this.event.shiftKey && charCode === charCode.toLowerCase()) {
						return false;
					} else {
						return value;
					}
				} else {
					return true;
				}
			},
			nolatin: function(value) {
				value = $.trim(value);
				return !value.length || /[^a-zA-Z]/i.test(value);
			}
		}
	};

	return FormValidator;
})();

(function () {

    /**
     * Creates a function that returns negation of the result of calling given function
     * @param func
     * @returns {Function}
     */
    function not(func) {
        return function () {
            return !func.apply(this, arguments);
        };
    }

    var codes = {
        '': 7,
        'ru': 7,
        'by': 37,
        'ua': 38,
        'cn': 86
    };

    // common validation rules
    FormValidator.common = {
        isEmail: function () {
            var names = ['reqtype', 'regtype'],
                input;

            for (var i = 0, name; name = names[i]; i++) {
                input = this.form.find('input[name="' + name + '"]:checked');

                if (input.length) {
                    return input.val() === 'email';
                }
            }

            return false;
        },
        fullMobile: function (validator, countryCodeFieldName, allowOwn) {
            var depends = not(FormValidator.common.isEmail.bind(validator));

            typeof countryCodeFieldName === 'undefined' && (countryCodeFieldName = 'country_code');
            typeof allowOwn === 'undefined' && (allowOwn = false);
            return {
                triggerOn: [countryCodeFieldName],
                custom: {
                    params: function(value) { return value.replace(/[^\d]/g, '').length == 10 },
                    depends: depends
                },
                remote: {
                    value: function (input) {
                        var code = this.getInput(countryCodeFieldName).val();
                        if (codes[code]) {
                            code = codes[code];
                        }
                        return '' + code + input.value;
                    },
                    params: '/actions/ajax/check/mobile?allowown=' + +allowOwn,
                    depends: depends
                }
            };
        },
        errorPlacement: function (fieldName, message) {
            $('#' + fieldName + '_error', this.form).removeClass('hide').find('span').html(message);
            $('#' + fieldName + '_input', this.form).addClass('error');
        },
        successPlacement: function (fieldName) {
            $('#' + fieldName + '_error', this.form).addClass('hide').find('span').html('');
            $('#' + fieldName + '_input', this.form).removeClass('error');
        }
    };
}());