/**
# Natrix Form Validator

Автоматическая проверка заполнения форм

\date 06.06.2014
\version 2.1
\author Sergey Gripinskiy <kagami-sorano@mail.ru>, ICQ 224-799-719 */
(
	function($)
	{
		var myNameSpace = 'nxformvalidator';
		var methods = 
		{
			init:function(settings)
			{
				return this.each(function()
				{
					var options = $.extend(
					{
						/*'customField':{//Индивидуальная настройка обработки полей формы
							'<nameattr>':{//Имя поля по атрибуту name 
								'name':'Поле1',//Человеческое название поля
								'msgEmpty':'Вы не заполнили поле №1',//Текст сообщения об ошибке
								'checkCallback':function(curField){},//Своя функция проверки поля, которая должна возвращать true если поле заполнено верно или false в противном случае 
								'checkType':false,//Тип специальной проверки: 'number' - число, 'email' - электропочта. Если задан checkCallback, параметр игнорируется
								'otherName':""//Имя связанного поля "Другое" по атрибуту name, которое при заполненности снимает проверку с этого поля
							},
						},*/
						'visibleFieldsOnly':false,//Признак проверки только видимых на экране полей
						'placeholderAsEmpty':false,//Признак отношения к равенству аттрибутов placeholder и value как к пустому значению поля
						'callback':null,//function(form,message,wrongField) Функция обратного вызова для осуществления своего действия при обнаружении ошибки. form - DOM Object формы, message - строка с ошибкой, wrongField - DOM Object поля с ошибкой
						'useJQUIDialog':false,//Использовать ли jQuery UI Dialog для показа сообщения об ошибке
						//'JQUIDialogOptions':null,//Дополнительные опции для диалогового окна от jQuery UI Dialog http://api.jqueryui.com/dialog/
						'msgEmptyField':'Не заполнено обязательное поле #FIELD_NAME#',//Шаблон сообщения об ошибке в общем случае
						'msgEmptySelect':'Ничего не выбрано для обязательного поля #FIELD_NAME#',//Шаблон сообщения об ошибке для select и radio
						'msgEmptyCheckbox':'Не отмечено обязательное поле #FIELD_NAME#',//Шаблон сообщения об ошибке для checkbox
						'fieldNameBefore':'"',//Текст перед именем поля в сообщении об ошибке
						'fieldNameAfter':'"',//Текст после имени поля в сообщении об ошибке
						'slRequiredFieldList':'input[mandatory],select[mandatory],textarea[mandatory],input[required],select[required],textarea[required],*[mandatory-checkboxes]',//Селектор всех полей, обязательных для заполнения
						'regExpEmailCheck':/^[ ]*[A-Z0-9][.\-_A-Z0-9]*@[A-Z0-9]([_\-A-Z0-9]+|([_\-A-Z0-9]*\.[_\-A-Z0-9]+)+)*[ ]*$/i//Регулярное выражение для проверки E-mail
					},settings);
					methods.saveOpt(this,options);
					var that = this;
					$(this).on('submit.'+myNameSpace, function(e) { return $(this)[myNameSpace]('validateForm', that)});
				});
			},
			/**Проверяет заполненность полей формы и вызывается автоматически при попытке отправить форму, для которой настроен данный плагин
			\return bool true если ошибок нет, false если есть ошибки*/
			validateForm:function(that)
			{
				var options = methods.loadOpt(that);
				var bValidateResult = true;//Не нашлось ошибок
				var jqAllFormFields = $(options.slRequiredFieldList, that);
				if (options.visibleFieldsOnly === true) jqAllFormFields = jqAllFormFields.filter(':visible');
				jqAllFormFields.each(function(index, curField){//Идём по всем обязательным для заполнения полям
					var fieldValidateResult = methods.validateField(that, $(curField));
					if (fieldValidateResult !== true && typeof fieldValidateResult == 'string' && fieldValidateResult.length > 0)
					{//Если нашли ошибку, то запоминаем это и вызываем обработку соответствующих действий
						methods.reportError(that, fieldValidateResult, curField);
						bValidateResult = false;//Запомнить результат
						return false;//Прекратить each
					}
				});
				return bValidateResult;
			},
			/**Проверяет на заполненность конкретное поле
			\param object curField Объект jQuery, являющийся полем
			\return string Текст сообщения об ошибке или true, если поле заполнено верно*/
			validateField:function(that, curField)
			{
				var options = methods.loadOpt(that); var bCheckRes = true;//По умолчанию поле считается заполненным верно
				var fieldTag = curField[0].nodeName.toLowerCase();//Узнать тэг поля
				if (methods.hasCustomFunction(that, curField))
				{//Если поле имеет частную функцию проверки, исполнить её
					var bCheckRes = methods.checkFieldViaCustomFunction(that, curField);
				}
				else if (methods.hasCheckType(that, curField))
				{//Если поле имеет особый тип проверки, исполнить его
					var bCheckRes = methods.checkFieldViaCheckType(that, curField);
				}
				else if (curField.is('[mandatory-checkboxes]'))
				{//Если поле является группой checkbox, проверить её
					var bCheckRes = methods.checkCheckboxGroup(that, curField);
				}
				else if (fieldTag == 'input')
				{//Если поле является input, проверить его
					var bCheckRes = methods.checkInputField(that, curField);
				}
				else if (fieldTag == 'textarea')
				{//Если поле является textarea, проверить его
					var bCheckRes = methods.checkTextareaField(that, curField);
				}
				else if (fieldTag == 'select')
				{//Если поле является select, проверить его
					var bCheckRes = methods.checkSelectField(that, curField);
				}
				if (!bCheckRes)
				{//Если поле не прошло проверку
					if (methods.checkOtherField(that, curField))
					{//Если связанное поле типа "Другое" прошло проверку, то и это поле прошло
						return true;
					}
					return methods.getErrorMessage(that, curField);//Вернуть текст сообщения об ошибке для данного поля
				}
				return true;//В других случаях считается, что поле проверку прошло
			},
			/**Возвращает сообщение об ошибке для данного поля
			\param object curField Объект jQuery, являющийся полем
			\return string */
			getErrorMessage:function(that, curField)
			{
				var options = methods.loadOpt(that);
				if (typeof curField == 'object')
				{
					var curFieldName = methods.findFieldName(that, curField);
					var fullFieldName = options.fieldNameBefore+curFieldName+options.fieldNameAfter;
					var message = options.msgEmptyField.replace('#FIELD_NAME#',fullFieldName);
					if (options.customField != null && options.customField[curField.attr('name')] != null && options.customField[curField.attr('name')].msgEmpty != null)
					{//Частное сообщение об ошибке для данного поля
						message = options.customField[curField.attr('name')].msgEmpty;
					}
					else if (curField[0].nodeName.toLowerCase() == 'select' || (curField[0].nodeName.toLowerCase() == 'input' && curField.attr('type').toLowerCase() == 'radio'))
					{//Сообщение об ошибке для SELECT или type="radio"
						message = options.msgEmptySelect.replace('#FIELD_NAME#',fullFieldName);
					}
					else if (curField[0].nodeName.toLowerCase() == 'input' && curField.attr('type').toLowerCase() == 'checkbox')
					{//Сообщение об ошибке для единичного type="checkbox"
						message = options.msgEmptyCheckbox.replace('#FIELD_NAME#',fullFieldName);
					}
					else if (curField.is('[mandatory-checkboxes]'))
					{//Сообщение об ошибке для группы Checkbox'ов (как и для SELECT)
						curFieldName = curField.attr('mandatory-checkboxes');
						fullFieldName = options.fieldNameBefore+curFieldName+options.fieldNameAfter;
						message = options.msgEmptySelect.replace('#FIELD_NAME#',fullFieldName);
					}
					return message; 
				}
				return false;
			},
			/**Сообщает о найденной ошибке установленным способом, переводит фокус на проблемное поле
			\param string stErrorMessage Сообщение об ошибке
			\param object objField DOM Object поля или группы checkbox, к которому относится сообщение об ошибке
			\return void*/
			reportError:function(that, stErrorMessage, objField)
			{
				var options = methods.loadOpt(that); 
				if (typeof stErrorMessage == 'string' && stErrorMessage.length > 0)
				{//Если пришло непустое сообщение об ошибке
					if (typeof(options.callback) !== 'function')
					{//Не задана callback функция
						methods.setFieldFocus(that, objField);//Установить фокус на незаполненное поле
						methods.showError(that, stErrorMessage);//Показать ошибку
					}
					else
					{//Вызвать callback функцию
						options.callback(that, stErrorMessage, objField);
					}
				}
			},
			/**Устанавливает фокус на поле ввода или на первый checkbox в группе чекбоксов. 
			\param object objField DOM Object поля или группы checkbox
			\return void*/
			setFieldFocus:function(that, objField)
			{
				if (typeof objField == 'object')
				{
					var jqObjField = $(objField);
					if (typeof jqObjField == 'object' && typeof jqObjField.focus == 'function')
					{//Если поле задано и на него можно повесить фокус
						if (jqObjField.is('[mandatory-checkboxes]'))
						{//Если имеем дело с группой чекбоксов, то установить фокус на первый же чекбокс
							jqObjField = jqObjField.find('input[type="checkbox"]:eq(0)');
						}
						jqObjField.focus();
					}
				}
			},
			/**Показывает сообщение об ошибке
			\param string stErrorMessage Сообщение об ошибке
			\return void*/
			showError:function(that, stErrorMessage)
			{
				var options = methods.loadOpt(that);
				if (typeof stErrorMessage == 'string' && stErrorMessage.length > 0)
				{//Если пришло непустое сообщение об ошибке
					if (options.useJQUIDialog)
					{//Использовать jQuery Dialog
						methods.showMsgViaJQUIDialog(that, stErrorMessage);
					}
					else
					{//Использовать обычный Alert
						window.alert(stErrorMessage);
					}
				}
			},
			/**Показывает диалоговое окно jQuery UI Dialog с сообщением
			\param string message Сообщение
			\return void*/
			showMsgViaJQUIDialog:function(that, message)
			{
				var options = methods.loadOpt(that);
				var curDialogOptions = {//Опции по умолчанию
					'buttons':{'OK':function(){$(this).dialog('close');}},
					'closeTextType':'Закрыть',
					'modal':true,
					'title':'Обязательное поле'
				}
				if (options.JQUIDialogOptions != null && typeof(options.JQUIDialogOptions) == 'object' ) $.extend(curDialogOptions,options.JQUIDialogOptions);
				$('<div>'+message+'</div>').dialog(curDialogOptions).dialog();
			},
			/**Возвращает результат проверки поля <input>
			\param object curField Объект jQuery, являющийся полем
			\return bool true, если проверка пройдена или false, если нет*/
			checkInputField:function(that, curField)
			{
				var options = methods.loadOpt(that);
				var type = curField.attr('type').toLowerCase();
				var val = $.trim(curField.val());
				var placeholder = $.trim(curField.attr('placeholder'));
				if (type == "text" && (val == "" || options.placeholderAsEmpty && val == placeholder))
				{
					return false;
				}
				if (type == "radio" && ($('input[name="'+curField.attr('name')+'"]:checked',that).length <= 0 || $.trim($('input[name="'+curField.attr('name')+'"]:checked',that).val()) == ""))
				{//radio: не отмечено ни одно значение или отмеченное значение имеет пустое value
					return false;
				}
				if (type == "checkbox" && !curField.is(':checked'))
				{
					return false;
				}
				return true;
			},
			/**Возвращает результат проверки поля <textarea>
			\param object curField Объект jQuery, являющийся полем
			\return bool true, если проверка пройдена или false, если нет*/
			checkTextareaField:function(that, curField)
			{
				var options = methods.loadOpt(that);
				var val = $.trim(curField.val());
				var placeholder = $.trim(curField.attr('placeholder'));
				if (val == "" || options.placeholderAsEmpty && val == placeholder)
				{
					return false;
				}
				return true;
			},
			/**Возвращает результат проверки поля <select>
			\param object curField Объект jQuery, являющийся полем
			\return bool true, если проверка пройдена или false, если нет*/
			checkSelectField:function(that, curField)
			{
				return(methods.checkTextareaField(that, curField));
			},
			/**Возвращает результат проверки группы checkbox
			\param object curField Объект jQuery, являющийся полем
			\return bool true, если проверка пройдена или false, если нет*/
			checkCheckboxGroup:function(that, curField)
			{
				return (curField.find('input[type="checkbox"]:checked').length > 0);
			},
			/**Проверяет, имеет ли поле свою функцию проверки 
			\param object curField Объект jQuery, являющийся полем
			\return bool*/
			hasCustomFunction:function(that, curField)
			{
				var options = methods.loadOpt(that); 
				return (options.customField != null && 
					options.customField[curField.attr('name')] != null && 
					options.customField[curField.attr('name')].checkCallback != null && 
					typeof(options.customField[curField.attr('name')].checkCallback) == 'function');
			},
			/**Возвращает результат выполнения своей функции проверки для поля
			\param object curField Объект jQuery, являющийся полем
			\return bool true, если проверка пройдена или false, если нет*/
			checkFieldViaCustomFunction:function(that, curField)
			{
				var options = methods.loadOpt(that);
				return (options.customField[curField.attr('name')].checkCallback(curField));
			},
			/**Проверяет, имеет ли поле особый тип проверки
			\param object curField Объект jQuery, являющийся полем
			\return bool*/
			hasCheckType:function(that, curField)
			{
				var options = methods.loadOpt(that);
				return (options.customField != null && 
					options.customField[curField.attr('name')] != null && 
					options.customField[curField.attr('name')].checkType != null && 
					typeof(options.customField[curField.attr('name')].checkType) == 'string');
			},
			/**Возвращает результат проверки поля по особому типу
			\param object curField Объект jQuery, являющийся полем
			\return bool true, если проверка пройдена или false, если нет*/
			checkFieldViaCheckType:function(that, curField)
			{
				var options = methods.loadOpt(that);
				var val = $.trim(curField.val());
				if (val == "")
				{//Пустота не может пройти никакой тип проверки
					return false;
				}
				if (options.customField[curField.attr('name')].checkType == 'number')
				{//Проверка числа
					return !isNaN(val);
				}
				if (options.customField[curField.attr('name')].checkType == 'email')
				{//Проверка Email
					return options.regExpEmailCheck.test(val);
				}
				return true;
			},
			/**Проверяет, имеет ли поле связанное поле типа "Другое"
			\param object curField Объект jQuery, являющийся полем
			\return bool*/
			hasOtherField:function(that, curField)
			{
				var options = methods.loadOpt(that); 
				return (options.customField != null && 
					options.customField[curField.attr('name')] != null && 
					options.customField[curField.attr('name')].otherName != null && 
					typeof(options.customField[curField.attr('name')].otherName) == 'string' && 
					$('*[name="'+options.customField[curField.attr('name')].otherName+'"]',that).length > 0);
			},
			/**Проверяет на заполненность поле типа "другое", если оно задано настройками
			\param object curField Объект jQuery, являющийся полем
			\return bool true, если поле типа "другое" заполнено или false, если оно пустое*/
			checkOtherField:function(that, curField)
			{
				var options = methods.loadOpt(that);
				if ( methods.hasOtherField(that, curField) && methods.validateField(that, $('*[name="'+options.customField[curField.attr('name')].otherName+'"]',that)) === true)
				{
					return true;
				}
				else
				{
					return false;
				}
			},
			/**Узнаёт человеческое название поля
			\param object curField Объект jQuery, являющийся полем
			\return string Человеческое название*/
			findFieldName:function(that, curField)
			{
				var curId = curField.attr('id');
				var curType = curField.attr('type'); if (curType != null) curType = curType.toLowerCase();
				var curName = curField.attr('name');
				var options = methods.loadOpt(that);
				var curFieldName = "";
				if (options.customField != null && options.customField[curName] != null && options.customField[curName].name != null)  
				{//1 Если имя есть в опциях, взять оттуда
					curFieldName = options.customField[curName].name;
				}
				if ((curFieldName == "" || curFieldName == null) && curId != null && curId != "" && curType != 'radio')
				{//2 Если имя есть в label и это не type="radio", отдать его. 
					curFieldName = $('label[for="'+curId+'"]',that).text();
				}
				if (curFieldName == "" || curFieldName == null)
				{//3 Если имя есть в placeholder, то отдать его
					curFieldName = curField.attr('placeholder');
				}
				if (curFieldName == "" || curFieldName == null)
				{//4 Если имя есть в предыдущем тексте или элементе, то отдать его
					var nearHtml = curField.parent().html();
					var REfirstText = /^([^<]+)/;
					var res = REfirstText.exec(nearHtml);
					if (res != null)
					{
						curFieldName = res[1].replace(/[ \t\n\r]+/,'');
					}
				};
				if ((curFieldName == "" || curFieldName == null) && curField[0].nodeName.toLowerCase() == 'select')
				{//5 Если это select и есть option с value="", то взять название оттуда
					curFieldName = curField.find('option[value=""]').text();
				};
				if (curFieldName == "" || curFieldName == null) 
				{//6 Если ничего не вышло, то возвращаем name
					curFieldName = curField.attr('name');
				}
				if (curFieldName == "" && curFieldName == null) curFieldName = 'Безымянное поле';
				return curFieldName;
			},
			saveOpt:function(that,options)
			{
				$(that).data(myNameSpace,options);
			},
			loadOpt:function(that)
			{
				return $(that).data(myNameSpace);
			}
		};
		$.fn[myNameSpace] = function(method) 
		{
			if ( methods[method] ) 
			{
				return methods[method].apply(this, Array.prototype.slice.call(arguments,1));
			} 
			else if ( typeof method === 'object' || !method )
			{
				return methods.init.apply(this, arguments);
			} 
			else 
			{
				$.error( 'Method ' +  method + ' does not exist on jQuery.'+myNameSpace+' plugin' );
			}
		};
	}
)(jQuery)
