/**
 * Этот файл содержит фунции-обёртки для работы с DOM
 */

// объявление глобальных переменных
var is_ie = ( /msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent) );

//{{{ class All
//{{{ All::All
/**
 * All class constructor
 * this class is to emulate cross-browser all (like in IE) for the exact element
 * @param Element elem the target element
 * @param string id optional id searching
 */
var all_j = 0;
function All(elem,id)
{
	all_j=0;
	this.__x_construct = AllConstruct;
	this.__x_construct(elem,id);
	this.length=all_j;
}
//---------------------------------------------------------------------------}}}
//{{{ All::AllConstruct
/**
 * recursive All collector
 * @param Element elem current processed element
 * @param string id optional id searching
 */
function AllConstruct(elem,id)
{
	var cur;
	if (elem.hasChildNodes())
	{
		for ( var i = 0; i < elem.childNodes.length; i++)
		{
			cur = elem.childNodes.item(i);
			if ( null == cur.nodeValue )
			{
				if ( cur.id && ( !id || cur.id==id ) ) {
					this[id ? all_j : cur.id] = cur;
					all_j++;
				}
				this.__x_construct(cur,id);
			}
		}
	}
}
//---------------------------------------------------------------------------}}}

/*============================================================================*
 *  END OF class All                                                          *
 *=========================================================================}}}*/

//{{{ Javascript Wrappers

//{{{ emptyTrim
/**
 * Is the string really empty?
 * @param string str the testing string
 * @return bool
 */
function emptyTrim(str)
{
	return /^[\s]*$/.test(str);
}
//===========================================================================}}}
//{{{ arrayIndexOf
/**
 * Finds out index of the given value in the array object
 * @param Array ar the array
 * @param mixed val target value
 * @return int
 */
function arrayIndexOf(ar, val)
{
	if ( ar.indexOf ) {
		return ar.indexOf( val );
	}
	else
	{
		var i;
		for ( i in ar )
		{
			if ( ar[i] == val ) {
				return i;
			}
		}
		return -1;
	}
}
//===========================================================================}}}

/*============================================================================*
 *  END OF Javascript Wrappers                                                *
 *=========================================================================}}}*/

//{{{ DOM Wrappers

//{{{ $
/**
 * Shortcut to document.getElementById
 * @param string id the id
 */
function $(element) {
  if (arguments.length > 1) 
  {
		for (var i = 0, elements = [], length = arguments.length; i < length; i++)
			elements.push($(arguments[i]));
		return elements;
	}
	if (typeof element == 'string')
   	element = document.getElementById(element);
  
	return element;
}
//===========================================================================}}}
//{{{ $FX
/**
 * Shortcut to document.form[element]
 * @param string formName the form name
 * @param string elemName the element name
 */
function $FX( formName, elemName )
{
	var form, i;
	if ( typeof formName == 'string' ) {
		form = eval( 'document.'+formName+';' );
	}
	else {
		form = document.forms[parseInt(formName)];
	}
	return form[elemName];
}
//===========================================================================}}}
//{{{ $FV
/**
 * Shortcut to document.form[element].value
 * @param string formName the form name
 * @param string elemName the element name
 */
function $FV( formName, elemName )
{
	return $FX(formName, elemName).value;
}
//===========================================================================}}}

//{{{ findParent
/**
 * find the closest parent element with given tag name
 * @param Element el source element
 * @param string tag target tag name
 * @param bool includeSelf true to include the source element
 * @return Element
 */
function findParent(el,tag,includeSelf)
{
	if (el.parentNode || includeSelf)
	{
		if ( !includeSelf ) { el = el.parentNode; }
		while (el.nodeName != tag && el.parentNode){el = el.parentNode;}
		if (el.nodeName == tag) {
			return el;
		}
	}
	return null;
}
//===========================================================================}}}
//{{{ findChild
/**
 * find the first child of the element with the given tag name
 * @param Element el source element
 * @param string tag target tag name
 * @return Element
 */
function findChild(el,tag)
{
	if (el.firstChild)
	{
		el = el.firstChild;
		while (el.nodeName != tag && el.nextSibling){el = el.nextSibling;}
		if (el.nodeName == tag) {
			return el;
		}
	}
	return null;
}
//===========================================================================}}}
//{{{ findLastChild
/**
 * find the first child of the element with the given tag name from an end
 * @param Element el source element
 * @param string tag target tag name
 * @return Element
 */
function findLastChild(el,tag)
{
	if (el.lastChild)
	{
		el = el.lastChild;
		while (el.nodeName != tag && el.previousSibling){el = el.previousSibling;}
		if (el.nodeName == tag) {
			return el;
		}
	}
	return null;
}
//===========================================================================}}}
//{{{ nextSibling
/**
 * find the closest sibling to the right with the given tag name
 * @param Element el source element
 * @param string tag target tag name
 * @return Element
 */
function nextSibling(el,tag)
{
	if ( el.nextSibling )
	{
		el = el.nextSibling;
		while (el.nodeName != tag && el.nextSibling){el = el.nextSibling;}
		if (el.nodeName == tag) {
			return el;
		}
	}
	return null;
}
//===========================================================================}}}
//{{{ previousSibling
/**
 * find the closest sibling to the left with the given tag name
 * @param Element el source element
 * @param string tag target tag name
 * @return Element
 */
function previousSibling(el,tag)
{
	if ( el.previousSibling )
	{
		el = el.previousSibling;
		while (el.nodeName != tag && el.previousSibling){el = el.previousSibling;}
		if (el.nodeName == tag) {
			return el;
		}
	}
	return null;
}
//===========================================================================}}}
//{{{ hasAttribute
/**
 * Wrapper for ie
 * @param Element el source element
 * @param string attrName target attributeName
 * @return bool
 */
function hasAttribute(el,attrName)
{
	if ( el.hasAttribute ) {
		return el.hasAttribute(attrName);
	}
	else if ( el.getAttribute ) {
		return el.getAttribute(attrName) != null;
	}
	else {
		return false;
	}
}
//===========================================================================}}}

/*============================================================================*
 *  END OF DOM Wrappers                                                       *
 *=========================================================================}}}*/

//{{{ getAbsPos
/**
 * возвращает массив абсолютных координат элемента.
 * @param Element el элемент, координаты которого хотим узнать
 * @param bool notDocRel если да, то возвращает координаты по схеме position:absolute,
 *                       иначе - жёстко относительно верхнего левого угла документа
 * @return object
 */
function getAbsPos(el, notDocRel)
{
	if ( notDocRel == undefined )
		notDocRel = false;
	
	var SL = 0, ST = 0;
	var is_div = /^div$/i.test(el.tagName);
	if (is_div && el.scrollLeft)
		SL = el.scrollLeft;
	if (is_div && el.scrollTop)
		ST = el.scrollTop;
	var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
	if (el.offsetParent) {
		if ( notDocRel && getElementStyle(el.offsetParent, 'position') == 'relative' ) {
			return r;
		}
		var tmp = getAbsPos(el.offsetParent, notDocRel);
		r.x += tmp.x;
		r.y += tmp.y;
	}
	return r;
}
//===========================================================================}}}

//{{{ CSS Manipulation Functions

//{{{ getElementStyle
/**
 * возвращает текущее значение стиля элемента
 * @param Element elem элемент, значения стиля которого хотим выяснить
 * @param string styleProp название стиля
 * @return string
 */
function getElementStyle(elem, styleProp)
{
    if (elem.currentStyle) {
        return elem.currentStyle[styleProp];
    } else if (window.getComputedStyle) {
        var compStyle = window.getComputedStyle(elem, "");
        return compStyle.getPropertyValue(styleProp);
    }
    return "";
}
//===========================================================================}}}
//{{{ getStyleSheetById
/**
 * returns stylesheet object by containing element's id
 * @param string id target id
 * @return CSSStyleSheet
 */
function getStyleSheetById(id)
{
	for (i = 0; i < document.styleSheets.length; i++)
	{
		node = is_ie ? document.styleSheets[i].owningElement : document.styleSheets[i].ownerNode;
		if (node.id == id) {
			return document.styleSheets[i];
		}
	}
	return null;
}
//===========================================================================}}}
//{{{ getStyleSheetRulesById
/**
 * returns stylesheet rules by containing element's id
 * @param string id target id
 * @return CSSStyleSheetRules
 */
function getStyleSheetRulesById(id)
{
	var sheet = getStyleSheetById(id);
	if (sheet) {
		return is_ie ? sheet.rules : sheet.cssRules;
	}
	else {
		return null;
	}
}
//===========================================================================}}}
//{{{ getCssRule
/**
 * returns stylesheet rule by containing element's id and selector
 * @param string id target id
 * @param string sel target selector
 * @return CSSStyleSheetRule
 */
function getCssRule(id, sel)
{
	var rules = getStyleSheetRulesById(id);
	if (rules)
	{
		sel = sel.toLowerCase();
		for (i = 0; i < rules.length; i++) {
			if ( rules[i].selectorText.toLowerCase() == sel ) {
				return rules[i];
			}
		}
	}
	return null;
}
//===========================================================================}}}
//{{{ addClass
/**
 * Adds given class to the element's class list
 * @param Element el target element
 * @param string class the class
 * @return bool
 */
function addClass(el, className)
{
	var classes = el.className.split(' ');
	if ( arrayIndexOf(classes, className) == -1 )
	{
		classes[classes.length] = className;
		el.className = classes.join(' ');
	}
	return true;
}
//===========================================================================}}}
//{{{ removeClass
/**
 * Removes given class from the element's class list
 * @param Element el target element
 * @param string class the class
 * @return bool
 */
function removeClass(el, className)
{
	var classes = el.className.split(' ');
	var i;
	if ( (i = arrayIndexOf(classes, className)) > -1 )
	{
		classes.splice( i, 1 );
		el.className = classes.join(' ');
		return true;
	}
	else {
		return false;
	}
}
//===========================================================================}}}
//{{{ hasClassName
/**
 * Finds out if the element is of the given class
 * @param Element el target element
 * @param string class the class
 * @return bool
 */
function hasClassName(el, className)
{
	return arrayIndexOf(el.className.split(' '), className) != -1;
}
//===========================================================================}}}

/*============================================================================*
 *  END OF CSS Manipulation Functions                                         *
 *=========================================================================}}}*/

//{{{ Event Handlers Manipulation Functions

var _handlerDelim = ';%||%;';
var _handlerIdDelim = ':|%%|:';
var _handlerInitialId = 'initial';
//{{{ addEventHandler
/**
 * Adds event handler for the element
 * @param Element el target element
 * @param string eventName target event name
 * @param function handler the handler
 * @param string handlerId optional handler id (for further remove)
 */
function addEventHandler( el, eventName, handler, handlerId )
{
	var storeAttr = eventName+'Handlers';
	var strHandlers;
	var i, tmp, isNew;
	handlerId = handlerId ? handlerId : '';
	if ( hasAttribute(el, storeAttr) )
	{
		if ( el.getAttribute(storeAttr) )
		{
			strHandlers = el.getAttribute(storeAttr).split(_handlerDelim);
			if ( handlerId != '' )
			{
				for ( i in strHandlers )
				{
					tmp = strHandlers[i].split(_handlerIdDelim);
					if ( tmp[0] == handlerId )
					{
						strHandlers.splice( i, 1 );
						break;
					}
				}
			}
		}
		else {
			strHandlers = new Array();
		}
	}
	else
	{
		strHandlers = new Array();
		var cur = eval( 'el.'+eventName+';' );
		if ( undefined != cur ) {
			strHandlers[0] = _handlerInitialId + _handlerIdDelim + cur;
		}
	}
	strHandlers[strHandlers.length] = handlerId + _handlerIdDelim + handler;
	el.setAttribute( storeAttr, strHandlers.join(_handlerDelim) );
	eval( 'el.'+eventName+'=function(e){handleEvent("'+eventName+'",this,e);};' );
}
//===========================================================================}}}
//{{{ handleEvent
/**
 * Handler for managed event
 * @param string eventName target event name
 * @param Element el source element
 * @param Event ev the event object
 */
function handleEvent(eventName,el,ev)
{
	var storeAttr = eventName+'Handlers';
	var strHandlers;
	var i;
	var tmp;
	if ( el.getAttribute(storeAttr) )
	{
		strHandlers = el.getAttribute(storeAttr).split(_handlerDelim);
		for ( i = 0; i < strHandlers.length; i++ )
		{
			tmp = strHandlers[i].split(_handlerIdDelim);
			eval('tmp='+tmp[1]+';');
			tmp(el, ev ? ev : event);
		}
	}
}
//===========================================================================}}}
//{{{ removeEventHandler
/**
 * Removes event handler with the given id for the element
 * @param Element el target element
 * @param string eventName target event name
 * @param string handlerId handler id to remove
 * @return bool
 */
function removeEventHandler( el, eventName, handlerId )
{
	var storeAttr = eventName+'Handlers';
	var strHandlers;
	var i;
	var tmp;
	if ( handlerId && el.getAttribute(storeAttr) )
	{
		strHandlers = el.getAttribute(storeAttr).split(_handlerDelim);
		for ( i = 0; i < strHandlers.length; i++ )
		{
			tmp = strHandlers[i].split(_handlerIdDelim);
			if ( tmp[0] == handlerId )
			{
				strHandlers.splice( i, 1 );
				el.setAttribute( storeAttr, strHandlers.join(_handlerDelim) );
				return true;
			}
		}
	}
	return false;
}
//===========================================================================}}}

/*============================================================================*
 *  Event Handlers Manipulation Functions                                     *
 *=========================================================================}}}*/

//{{{ outOf
/**
 * Finds out if one element is not in the another
 * @param Element el the target element
 * @param Element cont the testing container
 * @return bool
 */
function outOf( el, cont )
{
	while ( el.parentNode && el != cont ) {
		el = el.parentNode;
	}
	return el != cont;
}
//===========================================================================}}}

//{{{ Smart Layer Feature Functions

var _layerInfo = new Array();
//{{{ layerShow
/**
 * Opens the smart layer
 * @param Element el the layer element
 * @param Element callerEl the caller element (it's for smart mouse over/out)
 * @param Function callback the function to call on the layer close
 * @param int timeout optional timeout to close layer on mouseout
 */
function layerShow(el, callerEl, callback, timeout)
{
// добавляем обработчики событий
	addEventHandler( el, 'onmouseover',	function(el)	{ layerOver(el); },	'layer' );
	addEventHandler( el, 'onmouseout',	function(el,ev){ layerOut(ev); },	'layer' );
	addEventHandler( callerEl, 'onmouseover',	function(el)	{ layerCallerOver(el); },	'layer' );
	addEventHandler( callerEl, 'onmouseout',	function(el,ev){ layerCallerOut(el,ev); },'layer' );
// находим свободный id
	var id = _layerInfo.length;
	for ( i = 0; i < _layerInfo.length; i++ )
	{
		if ( null == _layerInfo[i] )
		{
			id = i;
			break;
		}
	}
// сохраняем информацию о слое
	el.setAttribute( 'layerId', i );
	timeout = timeout ? timeout : 0;
	callback = callback ? callback : null;
	_layerInfo[i] = { 'elem' : el, 'callerElem': callerEl, 'callback' : callback, 'over' : 2, 'timeout' : timeout };
// показываем слой
	el.style.display = 'block';
}
//===========================================================================}}}
//{{{ layerClose
/**
 * Closes the smart layer
 * @param Element|int layerId the layer id or any element inside the layer
 */
function layerClose(layerId)
{
// если параметр-объект, то выясняем идентификатор
	if ( typeof layerId == 'object' )
	{
		layerId = layerFindParent(layerId);
		if ( layerId ) {
			layerId = layerId.getAttribute('layerId');
		}
		else {
			return;
		}
	}
	var el = _layerInfo[layerId].elem;
	var callerEl = _layerInfo[layerId].callerElem;
	var callback = _layerInfo[layerId].callback;
//	удаляем обработчики событий
	removeEventHandler( el,			'onmouseover',	'layer' );
	removeEventHandler( el,			'onmouseout',	'layer' );
	removeEventHandler( callerEl,	'onmouseover',	'layer' );
	removeEventHandler( callerEl,	'onmouseout',	'layer' );
//	очищаем таймаут, если есть
	if ( _layerInfo[layerId].timeoutId ) {
		clearTimeout( _layerInfo[layerId].timeoutId );
	}
// очищаем вспомогаетльную инфу
	_layerInfo[layerId] = null;
// скрываем слой...
	el.style.display = 'none';
// ...и вызываем функцию-обработчик закрывания
	if ( typeof callback == 'function' ) {
		callback(el);
	}
	el.setAttribute('layerId', -1);
}
//===========================================================================}}}
//{{{ layerIsShown
/**
 * Finds out if the smart layer is shown now
 * @param Element el the layer element
 * @return bool
 */
function layerIsShown(el)
{
	if (	hasAttribute( el, 'layerId' )
		&&	_layerInfo[el.getAttribute('layerId')]
		&&	el.style.display == 'block' )
	{
		return true;
	}
	else {
		return false;
	}
}
//===========================================================================}}}
//{{{ layerOver
/**
 * Smart layer onMouseOver handler
 * @param Element el the source layer
 */
function layerOver(el)
{
	if ( hasAttribute( el, 'layerId' ) )
	{
		var id = el.getAttribute('layerId');
		_layerInfo[id].over = 1;
		if ( _layerInfo[id].timeoutId )
		{
			clearTimeout( _layerInfo[id].timeoutId );
			_layerInfo[id].timeoutId = 0;
		}	
	}
}
//===========================================================================}}}
//{{{ layerOut
/**
 * Smart layer onMouseOut handler
 * @param Event e the source event
 */
function layerOut(e)
{
	var target = e.target ? e.target : e.srcElement;
	var relatedTarget = e.toElement ? e.toElement : (e.relatedTarget ? e.relatedTarget : null);
	var toLayer = layerFindParent(relatedTarget);
	var fromLayer = layerFindParent(target);
	if ( toLayer != fromLayer && _layerInfo[fromLayer.getAttribute('layerId')])
	{
		var id = fromLayer.getAttribute('layerId');
		_layerInfo[id].over = 0;
		if ( _layerInfo[id].timeout ) {
			_layerInfo[id].timeoutId = setTimeout( 'layerClose('+id+')', _layerInfo[id].timeout );
		}
	}
}
//===========================================================================}}}
//{{{ layerCallerOver
/**
 * Smart layer caller element onMouseOver handler
 * @param Element el the source caller element
 */
function layerCallerOver(el)
{
	var id = layerIdByCaller( el );
	if ( id >= 0 && _layerInfo[id].timeoutId )
	{
		clearTimeout( _layerInfo[id].timeoutId );
		_layerInfo[id].timeoutId = 0;
	}	
}
//===========================================================================}}}
//{{{ layerCallerOut
/**
 * Smart layer caller element onMouseOut handler
 * @param Element el the source caller element
 * @param Event e the source event
 */
function layerCallerOut(el, e)
{
	var target = e.target ? e.target : e.srcElement;
	var relatedTarget = e.toElement ? e.toElement : (e.relatedTarget ? e.relatedTarget : null);
	if ( outOf( relatedTarget, el ) )
	{
		var id = layerIdByCaller( el );
		if ( id >= 0 ) {
			_layerInfo[id].timeoutId = setTimeout( 'layerClose('+id+')', _layerInfo[id].timeout );
		}
	}
}
//===========================================================================}}}
//{{{ layerIdByCaller
/**
 * Returns smart-layer id by the given caller element
 * @param Element el the caller element
 * @return int
 */
function layerIdByCaller( el )
{
	for ( var i = 0; i < _layerInfo.length; i++ )
	{
		if ( _layerInfo[i] != null && _layerInfo[i].callerElem == el ) {
			return i;
		}
	}
	return -1;
}
//===========================================================================}}}
//{{{ layerFindParent
/**
 * Find the containing layer of the element
 * @param Element el the target element
 * @return Element|null
 */
function layerFindParent(el)
{
	while ( el.parentNode && !hasAttribute(el, 'layerId') ) {
		el = el.parentNode;
	}
	return hasAttribute(el, 'layerId') ? el : null;
}
//===========================================================================}}}
document.onclick = function(e) { layerClickProcess(e ? e : event); };
//{{{ layerClickProcess
/**
 * Document onClick handler to close smart layers
 * @param Event e the event
 */
function layerClickProcess(e)
{
	var target = e.target ? e.target : e.srcElement;
	for ( var i = 0; i < _layerInfo.length; i++ )
	{
		if ( _layerInfo[i] != null )
		{
			switch (_layerInfo[i].over)
			{
				case 0:
					layerClose(i);
					break;
				case 2:
					var trg = target;
					while ( trg.parentNode && trg != _layerInfo[i].callerElem ) trg = trg.parentNode;
					if ( trg == _layerInfo[i].callerElem ) {
						_layerInfo[i].over = 0;
					}
					else {
						layerClose(i);
					}
					break;
			}
		}
	}
}
//===========================================================================}}}
//{{{ getWindowWidth
/**
 * Returns window width
 */
function getWindowWidth()
{
	var myWidth = 0;
 	if( typeof( window.innerWidth ) == 'number' ) 	{
    	//Non-IE
   	myWidth = window.innerWidth;
  	} else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    	//IE 6+ in 'standards compliant mode'
    	myWidth = document.documentElement.clientWidth;
	} else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
   	//IE 4 compatible
    	myWidth = document.body.clientWidth;
  	}
  	return myWidth;
}
//===========================================================================}}}
//{{{ getWindowHeight
/**
 * Returns window height
 */
function getWindowHeight()
{
	var myHeight = 0;
	if( typeof( window.innerWidth ) == 'number' ) {
	  //Non-IE
	  myHeight = window.innerHeight;
	} else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
	  //IE 6+ in 'standards compliant mode'
	  myHeight = document.documentElement.clientHeight;
	} else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
	  //IE 4 compatible
	  myHeight = document.body.clientHeight;
	}
	return myHeight;
}
//===========================================================================}}}
//{{{ encodeObject( obj )
/**
 * encodes object 
 */
function encodeObject( obj )
{
	for ( i in obj ) 	{
		if ( 'object' == typeof obj[i] )	{
			obj[i] = encodeObject( obj[i] )
		}
		else 	{
			obj[i] = encodeURIComponent( obj[i] );
		}
	}
	return obj;
}
//===========================================================================}}
/*============================================================================*
 *  END OF Smart Layer Feature Functions                                      *
 *=========================================================================}}}*/

/*============================================================================*
 * vim: set expandtab tabstop=3 shiftwidth=3 foldmethod=marker:               *
 *   END OF FILE                                                              *
 *============================================================================*/
