//
//  Soda for jQuery
//

//this.Form.plug("AsyncSubmit", {onsend: [...]});

var soda = {
	
	/**
	* Включение режима отладки на локальном компьютере
	*/
	debug_mode: (window.location.href.toString().search(/^http[s]?:\/\/\w+(?:\.local)?(?:\/|$)/) != -1),
	
	/**
	* Счётчик уникальных идентификаторов
	* Используется в uid
	*/
	unique_id: 0,
	
	
	/************************************************************************
	* Вернуть уникальный id (в рамках текущего окна)
	************************************************************************/
	
	id: function()
	{
		return "SodaID"+(++this.unique_id);
	},
	

	/************************************************************************
	* Расширить объект
	************************************************************************/
	
	extend: function(destination, source)
	{
		for (var property in source)
		{
			destination[property] = source[property];
		}
		return destination;
	},
	
	
	/************************************************************************
	* Вернуть значение ключа объекта
	************************************************************************/
	
	value: function(object, key, default_value)
	{
		if (Object.prototype.toString.call(key) === "[object Array]")
		{
			var obj = object;
			for (var i=0; i<key.length; i++)
			{
				if (typeof obj === "object" && obj !== null && (key[i] in obj))
				{
					obj = obj[key[i]];
				}
				else
				{
					return default_value;
				}
			}
			
			return obj;
		}
		
		return (typeof object === "object" && object !== null && (key in object)) ? object[key] : default_value;
	},
	
	
	/************************************************************************
	* Вернуть массив ключей хэша
	************************************************************************/
	
	keys: function(object)
	{
		var keys = [];
		for(var key in object)
		{
			//if (!object.hasOwnProperty(key)){ continue; }
			keys.push(key);
		}
		return keys;
	},
	
	
	/************************************************************************
	* Вернуть новый массив со значениями текущего объекта
	************************************************************************/
	
	values: function(object)
	{
		if (object.length !== undefined)
		{
			return Array.prototype.slice.call(object);
		}
		else
		{
			var array = [];
			for(var i in object){ array.push(object[i]); }
			
			return array;
		}
	},
	
		
	/************************************************************************
	* Возвращает текущее время в миллисекундах
	************************************************************************/
	
	now: function()
	{
		return +new Date;
	},
	
	
	/************************************************************************
	* Подгрузить изображение в кеш браузера
	* \1 (array|string) Адрес одного или нескольких изображений
	************************************************************************/
	
	preload: function(images, callback)
	{
		if (!images){ return; }
		
		if (typeof images === "string")
		{
			images = [images];
		}
		else if (Object.prototype.toString.call(images) !== "[object Array]")
		{
			 throw new "Unexpected type of images params"; 
		}
		
		for(var i=0; i < images.length; i++)
		{
			var image = new Image();
			
			// Обработчик должен указываться до установки src, иначе IE7 
			// не вызовет его после загрузки изображения.
			if (callback){ image.onload = callback; }
			
			image.src = images[i];
		}
	},
	
	
	/************************************************************************
	* Регистрация Callback-функции
	*
	* \1 (Function) - Функция вызываемая в контексте объекта
	* \2 (Object)   - Объект в контексте которого будет вызываться указанный метод
	* \3 [Array]    - Массив параметров метода с которыми он будет вызываться
	*
	* Использование:
	* soda.bind(Function, object[, params])
	*
	************************************************************************/
	
	bind: function(method, object, params)
	{
		return function(){ return method.apply(object, (params === undefined) ? arguments : params); };
	},

	
	/************************************************************************
	* Вернуть функцию выполняющую текущую функцию с указанной задержкой
	************************************************************************/
	
	latency: function(method, msec)
	{
		var latency_timeout_id;
		
		return function()
		{
			if (latency_timeout_id){ clearTimeout(latency_timeout_id); }
			latency_timeout_id = window.setTimeout(method, msec, Array().slice.call(arguments));
			
			return method;
		};
	},
	
	
	/************************************************************************
	* Методизировать функцию
	* Подставить вместо первого параметра объект в контексте которого вызывается функция
	************************************************************************/
	// Нужно ли такое :/
	
	methodize: function(method)
	{
		if (this.__methodized_function){ return this.__methodized_function; }
		
		return this.__methodized_function = function()
		{
			return method.apply(null, [this].concat(Array.prototype.slice.call(arguments)));
		};
	},
	
	unval: function(array, value)
	{
		for(var i=0; i<array.length; i++)
		{
			if (array[i] === value)
			{
				array.splice(i, 1);
				i--;
			}
		}
		
		return array;
	},
	
	rand: function(min, max)
	{
		if (min === undefined){ min = 0; }
		if (max === undefined){ max = 2147483647; }
		
		return Math.floor(Math.random() * (max - min + 1)) + min;
	}
}


/************************************************************************
* Расширение функций объекта Array
************************************************************************/

soda.array = {	
	
	
	/************************************************************************
	* Перемешать значения массива
	************************************************************************/
	
	shuffle: function(array)
	{
		return array.sort(function(){ return Math.random()-0.5; });
	},
	
	
	/************************************************************************
	* Очистить текущий массив от повторяющихся значений
	************************************************************************/
	
	unique: function(array, is_strict_comparsion)
	{
		is_strict_comparsion = !!is_strict_comparsion;
		
		var a = 0;
		for(var i=0, i_max=array.length-1; i <= i_max; i++)
		{
			for(var j=i+1; j <= i_max; j++)
			{
				if ((!is_strict_comparsion && array[i] == array[j]) || array[i] === array[j])
				{
					j = ++i;
				}
			}
			
			array[a++] = array[i];
		}
		
		array.length = a;
		return array;
	},
	
	
	/************************************************************************
	* Вернуть значения не содержащиеся в "array2"
	************************************************************************/
	
	diff: function(array, array2)
	{
		var result = [], matched;
		for(var i=0; i < array.length; i++)
		{	
			matched = false;
			for(var i2=0; i2<array2.length; i++)
			{
				if (array2[i2] === array[i])
				{
					matched = true;
					break;
				}
			}
			if (!matched)
			{
				result.push(array[i]);
			}
		}
		
		return result;
	},
	
	
	/************************************************************************
	* Вернуть значения которые присутствуют в "array2"
	************************************************************************/
	
	intersect: function(array, array2)
	{
		var result = [], matched;
		for(var i=0; i < array.length; i++)
		{	
			matched = false;
			for(var i2=0; i2<array2.length; i++)
			{
				if (array2[i2] === array[i])
				{
					matched = true;
					break;
				}
			}
			if (matched)
			{
				result.push(array[i]);
			}
		}
		
		return result;
	}
};



/************************************************************************
* Расширение функций объекта String
************************************************************************/

soda.text = {
	
	/************************************************************************
	* Заэкранировать специальные символы
	************************************************************************/
	
	addslashes: function(str)
	{
		return str.replace(/(["'\\])/g, "\\$1");
	},
	
	
	/************************************************************************
	* Вернуть текущую строку повторенную указанное число раз
	************************************************************************/
	
	repeat: function(str, times)
	{
		var value = "";
		
		for(var i=0; i<times; i++)
		{
			value += str;
		}
		
		return value;
	},
	
	
	/************************************************************************
	* Вернуть вариант склонения числа
	************************************************************************/
	
	numeral: function(str, a, b, c)
	{
		var count = Math.floor(str).toString();
		
		if (count.search(/1\d$/) == -1)
		{
			var lastdigit = count.substr(-1);
			
			if (lastdigit == 1)
			{
				return a
			}
			else if (lastdigit >= 2 && lastdigit <= 4)
			{
				return b;
			}
		}
		
		return c;
	},
	
	
	/************************************************************************
	* Преобразовать HTML в текст
	************************************************************************/
	
	htmlToText: function(str)
	{
		return str.replace(/</g, "&lt;").replace(/>/g, "&gt;");
	},
	
	
	/************************************************************************
	* Преобразовать текст с разделителяями в camelCase
	************************************************************************/
	
	separatorToCamelCase: function(str, separator)
	{
		if (separator === undefined){ separator = " "; }
		return str.replace(new RegExp(separator+"\\D", "g"), function(match){ return match.charAt(1).toUpperCase(); });
	},
	
	
	/************************************************************************
	* Преобразовать camelCase в текст с разделителяями
	************************************************************************/
	
	camelCaseToSeparator: function(str, separator)
	{
		if (separator === undefined){ separator = " "; }
		return str.replace(/([A-Z])/g, separator+"$1" ).toLowerCase();
	},
	
	
	/************************************************************************
	* Преобразовать HTML-символы в unicode-символы
	* Код HTML-символа должен быть указан в десятичной или шестнадцатеричной форме
	************************************************************************/
	
	htmlcharsToUnicode: function(str)
	{
		// Диапазоны значений не проверяютя, чем быстрее отработает функция, тем лучше.
		return str.replace(/&#(x?)([a-fA-F0-9]{1,5});/g, function($0, $1, $2)
			{
				return ($1) ? String.fromCharCode(parseInt($2, 16)) : String.fromCharCode($2);
			}
		);
	},
	
	
	/***/
	
	stripHtml: function(str)
	{
		return str.replace(/<("[^"]*"|'[^']*'|[^'">])*>/g, "");
	}
}



/************************************************************************
* Создать DOM элемент с указаными свойствами
*
* \1 (String) Имя HTML элемента
* \2 (Object) Оъбект с параметрами и их значениями. Функции в свойствах on[event],
*     подписываются на указаное событие
* \3 (String|Array) Массив дочерних элементов. Если указана строка, она воспринимается
*     как дочерний текстовый элемент
*
* Заметка:
* Так как IE не может нормально отработать при установке свойств создаваемого через
* DOM элемента, пришлось вынести то, что он поддерживает в отдельный блок. 
*
************************************************************************/

function __create(name, properties)
{
	if (!properties){ return new jQuery.fn.init(document.createElement(name)); }
	
	var attributes = {};
	var handlers = {};
	var style_object = null;
	
	for(var p in properties)
	{
		var type = typeof properties[p];
		
		// Если свойство начинается с "on" (например, onclick) и значением является функция,
		// предположить, что это подписчик события.
		if (p.indexOf("on") === 0 && type === "function")
		{
			handlers[p.substr(2)] = properties[p];
		}
		else if (p === "style" && type === "object")
		{
			style_object = properties[p];
		}
		else //if (type === "string")
		{
			attributes[p] = properties[p];
		}
	}
	
	// Так как IE не может нормально отработать при установке свойств создаваемого 
	// через DOM элемента, пришлось вынести то что он поддерживает в отдельный блок.
	var dom_element;
	if (document.all)
	{
		var text_attrs = [name];
		for(var a in attributes)
		{
			text_attrs.push(a+'="'+attributes[a]+'"');
		}
		text_attrs = text_attrs.join(" ");
		
		try
		{
			dom_element = document.createElement("<"+text_attrs+">");
		}
		catch(e)
		{
			var div = document.createElement("div");
			div.innerHTML = "<"+text_attrs+">";
			var dom_element = div.firstChild;
		}
	}
	else
	{
		var dom_element = document.createElement(name);
		for(var a in attributes)
		{
			dom_element.setAttribute(a, attributes[a]);
		}
	}
	
	// Оптимизация для исключения лишнего вызова функцмии "$"
	var element = new jQuery.fn.init(dom_element);
	
	for(var h in handlers)
	{
		element.bind(h, handlers[h]);
	}
	
	if (style_object !== null)
	{
		element.css(style_object);
	}
	
	return element;
}


/************************************************************************
* Создать DOM DocumentFragment из HTML-кода
************************************************************************/

__create.html = function(html)
{
	var div = document.createElement("div");
	div.innerHTML = html;
	
	var fragment = document.createDocumentFragment();
	while(div.hasChildNodes())
	{
		fragment.appendChild(div.firstChild);
	}
	
	return fragment;
}


/************************************************************************
* Создать DOM Element из HTML-кода
************************************************************************/

__create.dom = function(name, attributes)
{
	if (!attributes){ return document.createElement(name); }
	
	// Так как IE не может нормально отработать при установке свойств создаваемого 
	// через DOM элемента, пришлось вынести то что он поддерживает в отдельный блок.
	var element;
	if (document.all)
	{
		var attrs = [name];
		for(var a in attributes)
		{
			attrs.push(a+'="'+attributes[a]+'"');
		}
		attrs = attrs.join(" ");
		
		try
		{
			element = document.createElement("<"+attrs+">");
		}
		catch(e)
		{
			var div = document.createElement("div");
			div.innerHTML = "<"+attrs+">";
			var element = div.firstChild;
		}
	}
	else
	{
		var element = document.createElement(name);
		for(var a in attributes)
		{
			element.setAttribute(a, attributes[a]);
		}
	}
	
	return element;
}




soda.extend(Function.prototype, 
{
	
	/************************************************************************
	* Регистрация Callback-функции
	*
	* \1 (Object)   - Объект в контексте которого будет вызываться указанный метод
	* \2 [Array]    - Массив параметров метода с которыми он будет вызываться
	*
	* Использование:
	* Function.bind(object[, params])
	*
	************************************************************************/
	
	bind: function(object, params)
	{
		return soda.bind(this, object, params);
	}
});



/************************************************************************
* Создать на основе класса новый класс и переопределить методы
* Реализация простейшего наследования классов
*
* Заметка:
* Второй и третий параметры можно поменять местами, также можно вторым параметром 
* указать функцию, а неуказанный третий параметр возьмётся из её прототипа
*
* Специальные свойства:
* (Function) this.__class        - Текущий класс объекта
* (Function) this.__parentClass  - Родительский класс
* (Object)   this.__parent       - Прототип родительского класса (this.__parentClass.prototype)
* (Function) object.soda.bind       - Метод создания локализованой версии метода класса
*                                  Пример:
*                                  object.soda.bind("method") эквивалентно soda.bind(object.method, object)
*
************************************************************************/
// Идея для статических данных: 
//Editor.$static.method(1, 2, 3);
//this.$.static_method;

soda.Class = function(parent_class, extended_prototype)
{
	// Если указан только один параметр, предположить, что это прототип
	if (!extended_prototype)
	{
		extended_prototype = parent_class;
		parent_class = null;
	}
	
	// Если родительский класс не указан, сгенерировать стандартный
	if (!parent_class)
	{
		parent_class = function(){ this.__construct.apply(this, arguments); }
		parent_class.prototype = { __construct: function(){} };
	}
	
	// Проверка пришедших данных
	if (!(typeof parent_class === "function")){ throw "Value of 'parent_class' is not a function"; }
	if (!(typeof extended_prototype === "object" && extended_prototype !== null)){ throw "Value of 'extended_prototype' is not an object. Type \""+(typeof extended_prototype)+"\" given"; }
	
	// Инициализация свойств родительского класса при создании экземпляра
	//var new_class = function(){ this.__construct.apply(this, arguments); }
	//new_class.prototype = { __construct: function(){} };
	
	// Инициализация свойств родительского класса при создании экземпляра
	//var new_class = function(){ if (arguments[0] !== window.soda.Class.OMIT_CONSTRUCTOR){ this.__construct.apply(this, arguments); }};
	
	var new_class = function()
	{
		// Отмена вызова конструктора. Используется для инициализации прототипа
		if (arguments[0] === window.soda.Class.OMIT_CONSTRUCTOR){ return; }
		
		// Копирование объектов указанных при создании класса
		if (this.__hasObjectProperties)
		{
			for(var i in this)
			{
				if (this[i] && typeof this[i] === "object" && !(i in window.soda.Class.special_properties))
				{
					this[i] = $.extend((Object.prototype.toString.call(this[i]) === "[object Array]") ? [] : {}, this[i]);
				}
			}
		}
		
		// Определение объявленных событий
		/*if (this.__events)
		{
			var events = {};
			for(var i=0; i<this.__events.length; i++)
			{
				events[this.__events[i]] = [];
			}
			this.__events = events;
		}*/
		
		// Запуск конструктора
		this.__construct.apply(this, arguments);
	};
	
	// Инициализация прототипа
	new_class.prototype = new parent_class(window.soda.Class.OMIT_CONSTRUCTOR);
	
	// Расширение родительского класса и инициализация специальных свойств
	soda.extend(new_class.prototype, window.soda.Class.special_properties);
	soda.extend(new_class.prototype, extended_prototype);
	soda.extend(new_class.prototype, {__class: new_class, __parentClass: parent_class, __parent: parent_class.prototype});
	
	// Прототип нового класса не может содержать объекты, так как их изменение 
	// будет оказывать влияние на все экземпляры класса, что не правильно.
	var proto = new_class.prototype;
	for (var property in proto)
	{
		if ((typeof proto[property] === "object" && proto[property] !== null) && !(property in window.soda.Class.special_properties))
		{
			//throw "Unexpected type of property '"+property+"'. Object prototype can contain only functions and primitive types.";
			proto.__hasObjectProperties = true;
			break;
		}
	}
	
	return new_class;
};

//
// (!) Нечего менять функцию "soda.Class", надо убрать это в отдельный объект
//

// Константа должна быть именно объектом, чтобы при строгом сравнении сравнивались ссылки
soda.Class.OMIT_CONSTRUCTOR = {};


/**
* Специальные свойства класса
*/

soda.Class.special_properties = {
		
	/**
	* Ссылка на класс объекта
	*/
	__class: null,
	
	/**
	* Ссылка на родительский класс, нужна для проверки наследования
	*/
	__parentClass: null,
	
	/**
	* Ссылка на прототип родительского класса
	* Вызов функций в контексте текущего объекта:
	* this.__parent.__construct.call(this);
	*/
	__parent: null,
	
	/**
	* Список событий
	*/
	__events: null,
	
	/**
	* Маркер, содержит ли текущий класс свойства с сылками на объекты
	*/
	__hasObjectProperties: false,
	
	
	/************************************************************************
	* Замкнуть указанный метод в контексте текущего объекта
	*
	* \1 (function) - Функция вызываемая в контексте объекта
	* \1 (string)   - Имя метода текущего класса который будет выполнятся только в его контексте 
	*                 его объекта.
	* \2 [Array]    - Массив параметров метода с которыми он будет вызываться, если не указан будут
	*                 использованны параметры переданные функции.
	*
	* Пример:
	* object.soda.bind(function(){ return this.method(); })
	* object.soda.bind("method")
	*
	************************************************************************/
	
	__bind: function(method, params)
	{
		return soda.bind((typeof method === "string") ? this[method] : method, this, params);
	},
	
	
	/************************************************************************
	* Подписаться на событие объекта / Вызвать событие
	************************************************************************/
	
	__on: function(name, handler)
	{
		if (!this.__events){ this.__events = new soda.Observer(this); }
		
		this.__events.on(name, handler);
		return this;
	},
	
	
	/************************************************************************
	* Удалить обработчик события
	************************************************************************/
	
	__rem: function(name, handler)
	{
		if (!this.__events){ throw "Undefined event '"+name+"'"; }
		
		this.__events.unsubscribe(name, handler);
		return this;
	}
}


soda.Observer = soda.Class(
{
	/**
	* Список событий
	*/
	events: null,
	context: null,
	
	
	/************************************************************************
	* Конструктор
	************************************************************************/
	
	__construct: function(context)
	{
		this.context = context || window;
	},
	
	
	/************************************************************************
	* Подписаться на событие объекта / Вызвать событие
	************************************************************************/
	
	on: function(name, handler)
	{
		if (!this.events){ this.events = []; }
		var events = (!this.events[name]) ? this.events[name] = [] : this.events[name];
		
		if (handler)
		{
			events.push(handler);
		}
		else
		{
			for(var i=0; i<events.length; i++)
			{
				events[i].call(this.context);
			}
		}
		
		return this;
	},
	
	
	/************************************************************************
	* Удалить обработчик события
	************************************************************************/
	
	unsubscribe: function(name, handler)
	{
		if (!this.events){ this.events = []; }
		var events =  (!this.events[name]) ? this.events[name] = [] : this.events[name];
		
		if (handler)
		{
			for(var i=0; i<events.length; i++)
			{
				if (events[i] === handler)
				{
					events.splice(i, 1);
					i--;
				}
			}
		}
		else
		{
			events.length = 0;
		}
		
		return this; 
	}
});


/**
* Напечатать текст
*/

function pp(data)
{
	if (window.log){ window.log(data); }
	
	// Если DOM не загружен, пришем в документ напрямую
	if (!jQuery.isReady)
	{
		document.writeln("<pre>"+data+"</pre>");
	}
	else
	{
		var pre = document.createElement('pre');
		pre.innerHTML = data;
		document.body.appendChild(pre);
	}
}

/**
* Вывести текст на консоль
* Сокращённый вариант функции записи в консоль
*/
if (!window.log)
{
	if(window.console && console.log){ window.log = function(data){ return console.log(data); }; }
	else if (window.opera && opera.postError){ window.log = opera.postError; }
}


// ПЛАГИНЫ ДЛЯ JQUERY

(function($)
{

/**
* Вернуть уникальный идентификатор первого найденого элемента
* Если идентификатор не установлен, функция генерирует присваивает его.
*/
$.fn.getId = function()
{
	var id = this[0].getAttribute("id");
	if (!id)
	{
		this[0].setAttribute("id", id = soda.id());
	}
	
	return id;
};


/**
* Вернуть суммарную ширину бордюров элемента
*/
$.fn.borderWidth = function()
{
	return parseInt(this.css("borderLeftWidth"), 10) + parseInt(this.css("borderRightWidth"), 10);
};


/**
* Найти на форме поля содержащие значения по-умолчанию
* и отобразить значения указанные по-умолчанию, когда поля не заполнены.
*/
$.fn.initDefaultValues = function(options)
{
	var value_property = (options && options.value_property) || "data-default";
	var onblur_class   = (options && options.onblur_class)   || "is_default_value";
	
	var focus_handler = function()
	{
		var t = $(this).removeClass(onblur_class);
		
		if (this.value === this.getAttribute(value_property))
		{
			t.val("");
		}
	};
	
	var blur_handler = function()
	{
		if (this.value === "" || this.value === this.getAttribute(value_property))
		{
			$(this).addClass(onblur_class).val(this.getAttribute(value_property));
		}
	};
	
	var elements = $(this).find("input["+value_property+"], textarea["+value_property+"]")
		.focus(focus_handler)
		.blur(blur_handler)
		.each(blur_handler);
	
	//this.submit(function(el, fh){ return function(){ el.each(fh); } }(elements, focus_handler));
}



/**
* Показать внутри элемента индикатор процесса загрузки
*
* @param mode (undefined|bool|string|jQuery|Element) Режим работы индикаора загрузки
* (undefined|true) Отображение графического индикатора загрузки
* (false) Остановка индикатора загрузи запущенного в любом режиме
* (String) Текстовый индикатор загрузки (графический индикатор не отображается)
* (jQuery|Element) Объект jQuery или элемент индикатора загрузки добавляемый в контейнер AjaxLoader
* 
* @return (jQuery)
*/
$.fn.loading = function(mode)
{
	return this.each(function()
	{
		if (mode === undefined){ mode = true; }
		
		var t = $(this).empty();
		
		if (mode === false || mode === 0){ return t; }
		
		var loader = __create("div", {"class": "AjaxLoader"});
		
		if (mode === true)
		{		
			loader.append(__create("div", {"class": "-image"}));
		}
		else if (typeof mode === "string")
		{		
			loader.append(__create("div", {"class": "-text"}).html(mode));
		}
		else
		{
			loader.append(mode);
		}
		
		t.append(loader);
	});
};


$.fn.LengthLimit = function(limit, callback)
{
	var limit_handler = function()
	{
		if (this.value.length >= limit){ this.value = this.value.substr(0, limit); }
		if (callback){ callback.apply(this, arguments); }
	}
	
	return this.each(function()
	{
		var f = function(){ window.setTimeout(soda.bind(limit_handler, this)); }
		$(this).keydown(f).change(f).focus(f).blur(f);
	});
};


	
/*var changeHash = function()
{
	var hash = window.location.hash.substr(1);
	if (hash === $.location.hashValue){ return false; }
	
	$.location.hashValue = hash;
	
	//log("Hash changed. "+(prev)+" -> "+($.location.hashValue));
	
	$(window).trigger("hashchange");
	return true;
};

$.extend({
	location: {
		hash: function(hash)
		{
			if (hash === undefined){ return window.location.hash; }
			
			if (hash !== $.location.hashValue)
			{
				window.location.hash = hash;
				changeHash(hash);
			}
			
			return hash;
		},
		
		hashValue: null
	}
});

setInterval(function(){ changeHash(window.location.hash); }, 100);
if (window.location.hash){ changeHash(window.location.hash); }
*/

})(jQuery);


(function($)
{

/**
* Queued Ajax requests
* A new Ajax request won't be started until the previous queued 
* request has finished.
* 
* Запросы посылаются последовательно, следующий запрос не отправится пока 
* не выполнен обработчик предыдущего запроса.
*
* Usage: $.ajaxq("queue", {[jquery_ajax_options]}) - start queue
*        $.ajaxq("queue")                          - stop queue
*/

var queues_storage  = {};
var request = null;

$.ajaxq = function(label, settings)
{
	if (typeof label === "object")
	{
		settings = label;
		label = null;
	}
	
	if (!label){ label = "default_label"; }
	var queue = queues_storage[label] || (queues_storage[label] = []);
	
	if (settings)
	{
		settings = $.extend({}, jQuery.ajaxSettings, settings);
		
		var original_complete = settings.complete;
		
		settings.complete = function()
		{
			if (original_complete){ original_complete.apply(this, arguments); }
			
			queue.shift();
			
			request = (queue.length) ? $.ajax(queue[0]) : null;
		};
		
		queue.push(settings);
		
		// Если это первый запрос в очереди - выполнить его
		if (queue.length === 1){ request = $.ajax(settings); }
		
		return request;
	}
	else
	{
		if (request)
		{
			request.abort();
			request = null;
		}
		
		queue.length = 0;
	}
}


/**
* Synced Ajax requests
* The Ajax request will happen as soon as you call this method, but
* the callbacks (success/error/complete) won't fire until all previous
* synced requests have been completed.
*
* Если по-русски, запросы посылаются паралельно, а обработчики событий выполняются
* последовательно, в порядке в котором были посланы запросы.
*
*/

var synced = {};
var synced_data = {};

$.ajaxSync = function(settings)
{
	settings = $.extend({}, jQuery.ajaxSettings, settings);
	
	if (!synced){ synced = []; }
	
	var index = synced.length;
	
	synced[index] = {
		error:    settings.error,
		success:  settings.success,
		complete: settings.complete,
		done:     false
	};
	
	synced_data[index] = {
		error:    [],
		success:  [],
		complete: []
	};
	
	settings.error    = function(){ synced_data[index].error = arguments; };
	settings.success  = function(){ synced_data[index].success = arguments; };
	settings.complete = function()
	{
		synced_data[index].complete = arguments;
		synced[index].done = true;
		
		if (!index || !synced[index-1])
		{
			for (var i = index; i < synced.length && synced[i].done; i++)
			{
				if (synced[i].error){    synced[i].error.apply(jQuery,    synced_data[i].error); }
				if (synced[i].success){  synced[i].success.apply(jQuery,  synced_data[i].success); }
				if (synced[i].complete){ synced[i].complete.apply(jQuery, synced_data[i].complete); }
				
				synced[i] = null;
				synced_data[i] = null;
			}
		}
	}
	
	return $.ajax(settings);
}

})(jQuery);


/**
* MouseWheel
* Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net)
* Version: 3.0.2
* 
* Requires: 1.2.2+
*/

(function($)
{
var types = ['DOMMouseScroll', 'mousewheel'];

$.event.special.mousewheel = {
	setup: function()
	{
		if (this.addEventListener)
		{
			for (var i=types.length; i;)
			{
				this.addEventListener(types[--i], handler, false);
			}
		}
		else
		{
			this.onmousewheel = handler;
		}
	},
	
	teardown: function()
	{
		if (this.removeEventListener)
		{
			for (var i=types.length; i;)
			{
				this.removeEventListener(types[--i], handler, false);
			}
		}
		else
		{
			this.onmousewheel = null;
		}
	}
};

$.fn.extend(
{
	mousewheel: function(fn)
	{
		return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
	},
	
	unmousewheel: function(fn)
	{
		return this.unbind("mousewheel", fn);
	}
});


function handler(event)
{
	var args = [].slice.call(arguments, 1), delta = 0, returnValue = true;
	
	event = $.event.fix(event || window.event);
	event.type = "mousewheel";
	
	if (event.wheelDelta){ delta = event.wheelDelta/120; }
	if (event.detail){ delta = -event.detail/3; }
	
	// Add events and delta to the front of the arguments
	args.unshift(event, delta);

	return $.event.handle.apply(this, args);
}

})(jQuery);

