

// файл общих универсализированных функций, применяемых
// на всех страницах 

// !ALPHA

// ____________________________________________________
//
// общие функции для автоматизирования AJAX-вызовов
//
// функции
// реализуют механизм блокировки управляющих элементов
// на время выполнения запроса, а также отслеживают

// массив удалённых запросов
var request_watches = Array();

// открыт ли общий диалог
var common_dialog_status = false;

// отключение сбора данных из "простых полей" (не элементы управления - span, div, p, td и т.д)
var get_field_data_controls_only = false;

var dont_back_dialogs = false;

// $###^
// инициализация страницы
// добавление рабочих областей
$(document).ready(function()
{

	// добавление к странице системных блоков: для вывода сообщения "подождите"
	// и для вывода системных сообщений
	$(document.body).append(
	'<div class="__system__" id="__system__wait_message"><div class="inner_content">'+
	'подождите...'+
	'</div></div>'+

	'<div class="__system__ json_inner" id="__system__common_message"><div id="__system__common_message__content"></div><div class="inner_content">'+
	'</div></div>'+

	'<div class="__system__ json_inner" id="__system__common_dialog"></div>'+
	
	'<div class="__system__ json_inner" id="__system__cancel_dialog">'+
	'<div id="__system__cancel_dialog__content"></div>'+
	'<div class="tools__yes_button"></div>'+
	'<div class="tools__no_button"></div>'+
	'</div>'
	
	);

	// обеспечение работы спойлеров
	$(".spoiler_head").click(function()
	{
		var parent = $(this).parent(".spoiler");
		if(parent.hasClass("opened")){ parent.removeClass("opened"); }
		else{ $(".spoiler").removeClass("opened"); parent.addClass("opened"); }
	});


	// инициализация элементов
	part_initialize();


});




// *** ОБЩИЙ КОНФИГ
var common_config = {

	// по умолчанию страница блокируется при вызовах
	ajax_page_locking: true,

	// время ожидания запроса при AJAX, после которого
	// запрос помечается утеряным
	ajax_response_timeout: 300000,

	// последнее значение #-параметра адресной строки
	last_diez_value: null,

	// использовать обработчик #-парамеров или нет
	using_diez_handler: false,

	// COMMON DIALOG PROPERTIES - свойство общего всплывающего диалога,
	// вызываемые по умолчанию
	cmdg_properies:{
			position: ["center","center"],
			modal:true,
			buttons:false,
			resizable:false,
			draggable:false,
			width:"auto",
			height:"auto"
	},

	// COMMON AUTO INCREMENT- учёт количества диалогов, создаваемых
	// под нужды отображения подгружаемого контента
	cmdg_auto_increment: 0,

	// изменена страница или нет
	page_changed: false,
	
	// стороние JS-файлы, уже подгруженные системой
	// (чтобы не подгружать один и тот же файл несколько раз)
	loaded_js_extensions: Array(),
	
	// режим вставки подгрузки данных
	//   insert - вставка с заменой
	//   append - вставка в конец
	//   prepend - вставка в начало
	load_part_mode: "insert",
	
	
	// диалог, открытый в данный момент как активный
	// (не относится к сообщениям и "подождите")
	current_active_dialog: null,

	// данные, обязательные лоя отправки
	permanent_sends_data: Array()
};


// поиск значения в массиве
function in_array(needle, stack){
	for(var index in stack){ if(needle==stack[index]) return true; } return false; }


// динамическая подгрузка JS-файла
function js_extension(url)
{
	// проверка, что файл ещё не был добавлен к странице
	if(in_array(url, common_config.loaded_js_extensions)){ return; }

	var js_object = document.createElement('script')
	js_object.setAttribute("type","text/javascript")
	js_object.setAttribute("src", url)
	
	document.getElementsByTagName("head")[0].appendChild(js_object)

	common_config.loaded_js_extensions.push(url);
}

// подгрузка фрагмента в поле
// field_id	- поле, в кторое будет осуществляться подгрузка
// url		- URL подгружаемого блока
// src_id	- поле с отправляемыми серверу данными (может быть форма, а может любое поле, из которого беруться данные во всех полях с ID)
// последние два парметра - функции вызываемые перед и после вызова, соответственно
// UPD: добавлен ещё один параметр - optional_data - данные, передаваемые серверу из вызова
function load_part(field_id, url, src_id, function_before, function_after, optional_data)
{
	// блокирование страницы
	var watch_id = lock_page();

	// отправляемые данные
	var data = (src_id) ? get_field_data(src_id) : new Object;


	// данные, помеченные как
	// обязательные для отправок
	compare_objects(data, common_config.permanent_sends_data);

	// дополнительные данные "всухую"
	if(optional_data) compare_objects(data, optional_data);


	// событие - перед переходом/подгрузкой (может применяться
	// и как "перед закрытием раздела")
	$(document).trigger("before_partload", [field_id, url]);



	// к URL прибавляется GET-параметр "content_only=1", который
	// говорит что нужно только непосредственное содержимое страницы
	var content_only_arg = "content_only=1";

	
	if(url.indexOf("?") > 0)
		 content_only_arg = "&" + content_only_arg
	
	else content_only_arg = "?" + content_only_arg



	$.ajax({

		url: url+content_only_arg, type: "POST", 
		dataType: "html", cache: false, data: data,

		// успешно выполнено		
		success: function(result)
		{
			// пользовательская функция непосредственно перед вставкой
			if(function_before){ function_before.call(); }

			// вывод в всплывающем окне или в область на экране
			if(field_id == "popup_dialog")
			{
				common_config.current_active_dialog
					= load_to_dialog(result, true);
			}
			else
			{
				
				if(common_config.load_part_mode == "append")
					("#"+field_id).append(result)
				
				else if(common_config.load_part_mode == "prepend")
					("#"+field_id).prepend(result)
				
				else $("#"+field_id).html(result);
			}

			// разблокирование  страницы
			unlock_page(watch_id);

			// инициализация элементов
			part_initialize(field_id, url);

			// пользовательская функция после запроса
			if(function_after){ function_after.call(); }

		},

		// ошибка ответа от сервера
		error: function()
		{
			response_error();
		
			// вывод в всплывающем окне или в область на экране
			if(field_id == "popup_dialog")

				load_to_dialog("error", true)
				
			else
			{
				
				if(common_config.load_part_mode == "append")
					("#"+field_id).append("error")
				
				else if(common_config.load_part_mode == "prepend")
					("#"+field_id).prepend("error")
				
				else $("#"+field_id).html("error");
			}	
			
			// инициализация элементов
			part_initialize(field_id, url);
		}

	});
	
	// очистка всиавляемых из вне данных
	common_config.permanent_sends_data = Array();

}

// блокирование страницы перед удалённым запросом
function lock_page()
{
	// блокирование страницы может быть отключено
	if( common_config.ajax_page_locking )
	{
		// отображение диалога "подождите" с блокирующим слоем
		$(document.body).append('<div class="__system__lock_layer"><div class="message" lang="ru"></div></div>');
	}

	// наблюдатель
	return add_request_watch();
}

// разблокирование страницы
function unlock_page(watch_id)
{
	// полная заблокировка (без параметра)
	if(!watch_id)
	{
		// очистка очереди сообщений
		for(var index=0; index<request_watches.length; index++){
			clearTimeout(request_watches[index]);
			request_watches.splice(index,1);
		}

		$(".__system__lock_layer").remove();

		return;
	}

	// удаление конкретного наблюдателя
	if(request_watches.length>0)
	{
		// удаление указанного наблюдателя за запросами и ссылки на него
		clearTimeout(watch_id);
		for(var index=0; index<request_watches.length; index++)
			if(request_watches[index]==watch_id){request_watches.splice(index,1); break; }

	}

	// если не осталось ни одного активного наблюдателя,
	// форма разблокируется
	if(request_watches.length==0) $(".__system__lock_layer").remove();
}


// добавление отслеживания для удалённого запроса:
// по истечении времени, максимально отведённого на запрос,
// он выдаст сообщение о сбое соединения
function add_request_watch()
{
	// регистрация нового запроса в общем массиве, чтобы при получении
	// ответа удалить наблюдателя или очистить список наблюдателей
	// при срабатывании первого их них (и не выводить сообщения по
	// всем отложенным вызовам, закидывая ими пользователя)
	var new_watch_id = setTimeout(request_timeout_message, common_config.ajax_response_timeout);
	request_watches[request_watches.length] = new_watch_id;

	return new_watch_id;
}

// вывод оповещения о сбое соединения;
// функция должна очищать список наблюдателей,
// чтобы при большом количестве запросов, сбой
// соединения не обрушил на пользователя
// шквал сообщений
function request_timeout_message()
{
	// полное разблокирование страницы
	unlock_page();

	// вывод сообщения о медлнном соединении
	show_sysem_message("slow_connection");
}


// диалог для LOAD_PART (и некоорых других системных функций)
// отображает внутри диалога содержимое (аргумент html)
// "use_diez_handler" определяет, будет ли диалог изменять #-адрес при закрытии
function load_to_dialog(html, use_diez_handler)
{
	// свойства диалога
	var dialog_properties = new Object;

	// компоновка с параметрами по умолчанию (из конфига)
	compare_objects(dialog_properties, common_config.cmdg_properies);



	// ID блока для диалога
	var new_block_id = '__system__common_dialog_'+common_config.cmdg_auto_increment;

	// добавление нового блока, в котором будет отображемое в диалоге содержимое
	$(document.body).append('<div class="__system__ json_inner" id="'+new_block_id+'"></div>');

	// заполнение
	$("#"+new_block_id).html(html);
	


	// *** дополнительные свойства объекта

	// если используется #-обработчик, закрытие диаолга
	// должно приводить к смене #-параметра (вернуть в исходное состояние)
	if(use_diez_handler) if(common_config.using_diez_handler)
	dialog_properties.beforeClose = function()
	{
		
		// если это активный диалог
		if(	common_config.current_active_dialog
			== new_block_id
		)
		
			// откат обратно
			if(!dont_back_dialogs) history.back();


		// иначе в следующий раз не откроется
		common_config.last_diez_value = false;

		// удаление созданного блока
		$("#"+new_block_id).remove();
	};


	// счётчик вызова диалогов
	common_config.cmdg_auto_increment++;


	// отображение диалога с подгружаемым содержимым
	$("#"+new_block_id).dialog(dialog_properties);
	$("#"+new_block_id).dialog("show");

	// ID созданного диалога чтобы можно было его отследить и закрыть
	return new_block_id;
}


// инициализация страницы и подгружаемых элементов
// (срабатывает при загрузке и при подгрузке новых элементов)
function part_initialize(field_id, url)
{
	// *** встроенные предобработки

	// элементы из формы, которые были автоматически определены
	// как содержащие дату
	$(".auto_date").each(function()
	{
		
		$(this).datepicker
		({
			dateFormat: 'yy-mm-dd',
			firstDay: 1 
		});
	
	} );


	// *** пользовательские предобработки
	// *** СОБЫТИЕ "partload", которое генерится каждый раз при подгрузке
	// *** его можно переопределять

	// пользовательские привязки
	// им передаются параметры - ID поля и URL, чтобы в любой момент
	// можно было отследить источники запроса
	$(document).trigger("partload", [field_id, url]);
}


// функция оповещения об ошибке ответа от сервера/сбоя соединения
function response_error()
{
	// разблокирование  страницы
	unlock_page();

	// вывод сообщения об ошибке
	show_sysem_message("service_unvialable");	
}

// вывод системной ошибки
function show_sysem_message(message_class)
{
	$.jGrowl('<div class="__system_message__'+message_class+'"></div>');
}

// обновление капчи
function captcha_reset(){ $(".captcha_image").attr("src", "/files/kcaptcha/?r="+(Math.random()*100)); }


// удаление файла из списка загруженных
// нужна для обслуживание поля, в котором хранятся
// имена загруженных файлов, разделёные запятой
function delete_file_from_list(field, file)
{
	var field_id = "file_"+field+"__uploaded_files";

	$('#'+field_id).each(function(){
		this.innerHTML = this.innerHTML.replace(file+',',""); });
}

// вызов операции с уведомлением
// выводится диалоговое окно с вариантами ответа: "Да" и "Нет"
// если выбрано "Нет", не происходит ничего, если "Да",
// вызывается колбэк
function cancel_next_operation(text, callback)
{
	// установка отображаемого текста
	$("#__system__cancel_dialog__content").html(text);

	// действия по нажатию кнопок
	$("#__system__cancel_dialog .tools__yes_button, #__system__cancel_dialog .tools__no_button").unbind("click");
	$("#__system__cancel_dialog .tools__yes_button, #__system__cancel_dialog .tools__no_button").
	click(function(){ $("#__system__cancel_dialog").dialog("close"); });

	$("#__system__cancel_dialog .tools__yes_button").click(function(){ callback.call(); });

	// отображние диалога
	$("#__system__cancel_dialog").dialog({
		position: ["center","center"],
		modal:true, buttons:false,
		resizable:false, draggable:false,
		width:290,
		height:190
	});
	$("#__system__cancel_dialog").dialog("show");
}


// включение обработчика #-параметра адресной строки
// агрумент - поле, в которое подгружается
function diez_handler_on(){ setInterval(diez_handler, 100); common_config.using_diez_handler = true; }

// обработчик #-параметра
function diez_handler()
{
	// если есть выполняющиеся задания, обработчик откладывается
	if(request_watches.length>0) return;

	// проверка изменений
	if(common_config.last_diez_value == location.hash){ return; }


	// рэг на выдерёгивание идентефикатора и вызываемой функции
	var diez_pattern = new RegExp('^#([a-zA-Z0-9_]{1,})(\/{1}([a-zA-Z0-9_\.]+\/{1})+)$');

	// проверка корректности 
	if(!diez_pattern.test(location.hash)){ return; }

	// выдернутые значения
	var diez_values	= diez_pattern.exec(location.hash);
	var field_id	= diez_values[1];
	var uri			= diez_values[2];


	// закрытие активных диалогов
	if(common_config.current_active_dialog)
	{
		// уничтожение диалога
		$(	"#"+
			common_config.current_active_dialog
		)
			.dialog("destroy")
			.remove();

		// указатель активного диалога
		common_config.current_active_dialog
			= null;
	
	}

	// подгрузка
	load_part(field_id, uri);

	// отслеживание истории изменений
	common_config.last_diez_value = location.hash;
}



// если имя элемента - массив
function is_array_input(object, acc, value)
{	
	// сверка по регекспу
	var array_pattern = new RegExp('^([A-Za-z0-9_]+)\[{1}[A-Za-z0-9_]+\]{1}$');
	if(!array_pattern.test(object.name)){ return false; }

	// выдернутые значения
	var array_values	= array_pattern.exec(object.name);
	var array_name		= array_values[1];

	// если это впервый раз запись в массив элемента
	// он инициализируется
	if(!acc[array_name]){ acc[array_name] = Array(); }

	// добавление к общему массиву элементов
	if(value) acc[array_name].push(object.value);


	return true;
}

// собирает данные в поле с указанным ID
function get_field_data(field_id)
{
	// объект, в который собираются все данные страницы
	var result_data = new Object;

	// проход по всем идентифицированным элементам запрашиваемого поля

	// обычные элементы ввода (обычные текстовые поля, скрытые и т.д.), текстаера и списки
	$("#"+field_id+" input:text, #"+field_id+" input:password, #"+field_id+" input:hidden, #"+field_id+" textarea, #"+field_id+" select")
		.each(	function(obj)
				{
					// элементы с классом "dont_send" не отправляются
					if($(this).hasClass('dont_send')) return;

					// приоритетней записывть по NAME
					if(this.name){
						
						if(!is_array_input(this, result_data, true))
							result_data[this.name] = this.value;
					}

					// в противном случае, запись по ID
					else if(this.id) result_data[this.id] = this.value;
					
					// исключения для тегов
					if(this.name=="tags") result_data[this.name] = $("#tags").val();
				});


	// чекбоксы
	$("#"+field_id+" input:checkbox")
		.each(	function(obj)
				{
					// элементы с классом "dont_send" не отправляются
					if($(this).hasClass('dont_send')) return;

					// своя логика
					var cur_value = this.checked ? this.value : false;
	
					// приоритетней записывть по NAME
					if(this.name){
						
						if(!is_array_input(this, result_data, cur_value))
							result_data[this.name] = cur_value;
					}

					// в противном случае, запись по ID
					else if(this.id) result_data[this.id] = cur_value;
				});

	// радио вставляются только по имени, у них обработка несколько более хитрая
	$("#"+field_id+" input:radio[name]")
		.each(	function(obj)
				{
					// элементы с классом "dont_send" не отправляются
					if($(this).hasClass('dont_send')){ return; }


					var cur_value = this.value;

					// если элемент выделен, он записывается
					if( (this.checked=="checked") || (this.checked) )
						cur_value = this.value;

					// а если нет - то только в первый раз (на случай,
					// если выделенный так и не встретится)
					else if(result_data[this.name]) return;

					// записывается только по NAME
					if(this.name){
						
						if(!is_array_input(this, result_data, cur_value))
							result_data[this.name] = cur_value;
					}
		
		
				});


	// иногда требуется отключить сбор данных с простых полей
	if(get_field_data_controls_only) return result_data;


	// *** продолжение сбора по простым полям

	// подстановка простых полей, у которых берётся innerHTML
	$	(
			"#"+field_id+" div[id], "+ "#"+field_id+" p[id], "+ "#"+field_id+" li[id], "+ "#"+field_id+" td[id],"+
			"#"+field_id+" dt[id], "+ "#"+field_id+" dd[id], "+  "#"+field_id+" span[id], "+ "#"+field_id+" a[id]"
		)

	.each( function(obj)
	{
		// элементы с классом "dont_send" не отправляются
		if(!$(this).hasClass('dont_send') && !/^(cke_)|(chzn-).*$/.test(this.id))

			result_data[this.id] = this.innerHTML;
	});


	return result_data;

}


// получить элемент по ссылке, возвращаемой от сервера
function _ref_to_element(ref, index, default_selector)
{
	// элемент, к которому будет выводиться сообщение
	var dest_element = default_selector ? $(default_selector) : false;

	//может быть указан индексный параметр (в случае с элементами массивами)
	if(index)
	{
		// проверка существования указанного индекса
		if( $('*[name="'+ref+'"]').length >= parseInt(index+1) )
				
		// ссылка на конкретный элемент
		dest_element = $($('*[name="'+ref+'"]')[index]);
	}
	else
	{
		// сначала пытается обратиться по ID
		if( $("#"+ref).length ) dest_element = $("#"+ref);

		// потом по имени
		else if( $('*[name="'+ref+'"]').length ) dest_element = $($('*[name="'+ref+'"]')[0]);
	}
	
	return dest_element;
}


// обработка сообщения, полученного от функции,
// использующей JSON для обмена с клиентским интерфейсом
// используется функцие load_json
function json_message_handler(result)
{
	if(!result){ return; }

	// $###^ ГОВНОКОД!!! УБРАТЬ В ТРИГГЕР!!!
	// если результат выполнения функции положительный,
	// вероятно, это было успешное сохранение, значит
	// страница сохранена
	if(result._STATUS){ common_config.page_changed=false; } 

	// вывод сообщения
	if(result._MESSAGE)
	{

		// удаление всех предыдущих
		$('.message_success, .message_error').remove();

		// объект выводимого сообщением
		var status_class = (result._STATUS) ? "message_success" : "message_error";
		var message_tag	 = '<div onclick="$(this).fadeOut(900)" class="json_message '+status_class+'">'+result._MESSAGE+'</div>';


		// если указан пункт назначения
		if(result._DEST_ID)
		{
			// элемент, к которому будет выводиться сообщение
			var dest_element =	_ref_to_element
								(
									result._DEST_ID,
									(result._DEST_ID_INDEX?result._DEST_ID_INDEX:0),
									null
								);

			
			if(dest_element)
			{
			
				// вставка во внутрь
				if(dest_element.hasClass("json_inner")) dest_element.html(message_tag)
				
				// вставка слева
				else
				if(dest_element.hasClass("json_left")) dest_element.before(message_tag)
				
				// вставка справа
				else
				if(dest_element.hasClass("json_right")) dest_element.after(message_tag);
				
				// фокус на элемент
				dest_element.focus();
			}
		}
				
		// ненавязчивый гроул
		$.jGrowl('<div class="growl_'+status_class+'">'+result._MESSAGE+'</div>');
	}

	// команды управления страницей
	if(result._REFRESH){ common_config.last_diez_value = null; }
	if(result._RESET){ lock_page(); location.reload(); return; }
	if(result._BACK){ history.back(); }
	if(result._LOCATION){ location.href = result._LOCATION; }


	// замена содержимого
	if(result._REPLACE)
	{
		// массив ссылок на элементы, которые надо заменить
		var elements_to_replace = Array();
		
		for(element_index in result._REPLACE)
		{
			element_data = result._REPLACE[element_index];
	
			
			// элемент, к которому будет выводиться сообщение
			var dest_element =	_ref_to_element
								(
									element_data._DEST_ID,
									(element_data._DEST_ID_INDEX?element_data._DEST_ID_INDEX:0),
									null
								);
			
			// вносится в список найденных элементов
			if(dest_element)
				elements_to_replace.push({element:dest_element, _HTML:element_data._HTML});
		}
		
		// замены элементов
		for(element_index in elements_to_replace)
		{
			elements_to_replace[element_index]["element"]
				.replaceWith(	'<div class="replaced_item">'+
								elements_to_replace[element_index]._HTML+
								'</div>'
				);
		}
	}

}

// компановка двух объектов/массивов
function compare_objects(dest, src){ for(index in src) dest[index] = src[index]; }


// подгрузка json-конструкции
// url		- URL подгружаемой страницы
// src_id	- поле с отправляемыми серверу данными (может быть форма, а может любое поле, из которого беруться данные во всех полях с ID)
// последние два парметра - функции вызываемые перед и после вызова, соответственно
// UPD: добавлен ещё один параметр - optional_data - данные, передаваемые серверу из вызова
function load_json(url, src_id, function_before, function_after, optional_data)
{
	// блокирование страницы
	var watch_id = lock_page();

	// отправляемые данные
	var data = (src_id) ? get_field_data(src_id) : new Object;


	// данные, помеченные как
	// обязательные для отправок
	compare_objects(data, common_config.permanent_sends_data);

	// дополнительные данные "всухую"
	if(optional_data) compare_objects(data, optional_data);


	// сюда записываются возвращемые запросами объекты,
	// и функция возвращает это значение. нужно для простого
	// обращения к результату запроса из вне
	var func_result = null;


	// пользовательская функция перед запросом
	if(function_before){ function_before.call(); }


	// добавление модификатора к URL (пометка,
	// что используется JSON-запрос)
	url = url + (url.match(/\?/) ? "&" : "?") + "use_json=1";


	$.ajax({

		url: url, type: "POST",	dataType: "json", cache: false, data: data,
		
		success: function(result)
		{ 
			// разблокирование  страницы
			unlock_page(watch_id);

			// обработчик обратной связи (ожидание подтверждения)
			if(result){
			
			if(result._CONFIRMATION)
			{
				// обязательные аргументы - текст сообщение и отлеженая функция,
				// которая будет вызвана вновь в случае подтверждения
				if(!result._MESSAGE || !result._RETURN_TO){ show_sysem_message("data_error"); }
				
				else
				{
					// вывод на экран окошка с подтверждением и функция, вызываемая
					// в случае положительного ответа
					cancel_next_operation(result._MESSAGE, function(){
						$.ajax({url:result._RETURN_TO, type: "POST",	dataType: "json", cache: false, data:{_CONFIRMATION:true},
							   	success:function(deposited_result)
								{
									if(!deposited_result){deposited_result=new Object}
									if(deposited_result._DATA){ deposited_result = deposited_result._DATA; }
									
									// обработка сообщений
									json_message_handler(deposited_result);
									
									// функция, указанная пользователем должна вызываться после запрашиваемого
									// действия, а не после запроса на разрешение
									if(function_after){ function_after.call(deposited_result); }	
								} 
						});
					});
				}

			}
			else
			{
				// обработка сообщений
				json_message_handler(result);
			
				// пользовательская функция после запроса
				if(function_after){ function_after.call(result); }
			}
			
			func_result = result;
		} },

		// ошибка ответа от сервера
		error: function(){ response_error(); }

	});

	
	// $### временный "постоянный"
	common_config.permanent_sends_data = Array();


	return func_result;
	
}



