/*
$Id: script.js 1281 2008-09-16 14:34:34Z schepanek $

--------------------------------------------------------------------------
* PART 1: GLOBAL UTILITIY FUNCTIONS AND OBJECTS 
---------------------------------------------------------------------------*/

/*$-- ------  1: GENERAL FUNCTIONS -------
Feel free to use these functions within the cms. they are selectors and make element access easier.
--$*/

/**$--
Empty default function with up to 5 undefined parameters for convienience
--$*/
var FUNCTION_EMPTY = function(arg0,arg1,arg2,arg3,arg4){};

/**$--
This function replaces a given function with an empty or other given default function
--$*/
function defaultFunction(_function, _replacement){
	if(_function){
		return _function;
	}else{
		return _replacement ? _replacement : FUNCTION_EMPTY;
	}
}

/**
* variant of the next method. it is identical to it, but you can specify the document to use
*@param: specifies the document object to use
*/
function makeElementInDocument(_document,_element,_attributes,_children){
	_element = _document.createElement(_element);
	// define the attributes, if any
	if (_attributes && _attributes !== null){
		for (var attribute in _attributes){
			if (_attributes.hasOwnProperty(attribute)){
				var attrib = _attributes[attribute];
				if (attribute == "style"){
					for (var styleattrib in attrib){
						if (attrib.hasOwnProperty(styleattrib)){
							_element.style[styleattrib] = attrib[styleattrib];
						}
					}
				}else{
					_element[attribute] = attrib;
				}
			}
		}
	}
	// define the children, if any.
	if (_children && _children !== null){
		for (var i=0; i < _children.length; i++){
			var child = _children[i];
			if ((typeof child) == "string"){
				// if child is a string, it is first converted into a text node
				child = _document.createTextNode(child);
			}
			_element.appendChild(child);
		}		
	}
	return _element;
}

/*$--
Multi-purpose-function to create dynamically new html elements and append them to the document.
Hierachies of elements are not possible
@param _element the element to create. must be a string name
@param attributes the list of the elements attributes, or null if no attributes are set.
@param _children optional. you can recursively define more children of this element via more makeElement calls.
			you can also define strings, which will be converted into text nodes
--$*/
function makeElement(_element, _attributes, _children){
	return makeElementInDocument(document,_element,_attributes,_children);
}

/*$--
* Helper function used in many of the functions below. Retrieves an element by its id:
* @param element the element to retrieve:
* 1. if element is a string, the element will be retrieved by the function getElementById
* 2. if the element is an html element, the element will be returned directly.
* @return the element identified by the given id, or null if nothing was found.
--$*/
function getElement(element){
	if ((typeof element) == "string"){
		return document.getElementById(element);
	}
	else{
		return element;
	}
}

/**$--
* This function is simple, but efficient, magic.
* It expects a function applyable on a single child which either returns the Child or null. 
* If the child is returned, it will be added to a result list.  (but this need not even to be exciting for you. do whatever you want..)
* <p>
* IMPORTANT NOTE: This behavior can be -partly- replaced with the native method 'foreach' of arrays
* @param _nodes the nodes to filter by the nodeFunction
* @param _nodeFunction the filter function
* @return the list of selected nodes
--$*/
function filterNodes(_nodes,nodeFunction){
	var result = [];
	var j=0;
	for (var i=0; i < _nodes.length; i++){
		var nodeResult = nodeFunction(_nodes[i]);
		if (nodeResult && nodeResult !== null){
			result[j++] = nodeResult;
		}
	}
	return result.slice(0,j);
}

/**$--
* This function will, unlike getElementsByTagName, ONLY return direct child tags with the correct name
* It is a special case of the other selectors in here
* @param _element the element or element id which children should be retrieved.
* @param _tag the tag to apply
* @return the list of selected nodes
--$*/
function getChildTags(_element,_tag){
	_element = getElement(_element);
	_tag = _tag.toLowerCase();
	return filterNodes(_element.childNodes,function(it){
		return (it.nodeName.toLowerCase() == _tag) ? it : null;
	});
}

/**$--
* returns only the html elements of the element, and no text nodes, comments or other stuff. makes manipulation easier.
*@param _element the element which children should be retrieved.
* @return the list of selected nodes
--$*/
function getChildElements(_element){
	_element = getElement(_element);
	return filterNodes(_element.childNodes,function(it){
			return (it.nodeType == 1) ? it : null;
	});
}

/*$--
* Convience function to return the head of the document
--$*/
function getHeadElement(){
	/*$--return getChildTags(getChildTags(document, 'html')[0],'head')[0];--$*/
	return getChildElements(getChildElements(document)[0])[0];
}

/*$--
* Sets the text of an element which contains plain text, like <b>TEST</b>.
* If the element contains subnodes then nothing happens.
* @param _element The element to set the text to, or the id of the element as a string
* @param _text The text to set.
--$*/	
function setText(_element,_text){
	_element = getElement(_element);
	switch(_element.childNodes.length){
	case 0:
		_element.appendChild(document.createTextNode(_text));
		break;
	case 1:
		if (_element.firstChild.nodeType == 3){
			_element.firstChild.data = _text;
		}
		break;
	}
}

/*$--
* Returns the text of an element which contains just plain text, like <b>TE ST</b>
* If the element is empty, "" is returned.
* If the element contains subnodes, null is returned.
* @param _element The element to get the text from or its id as a string.
--$*/	
function getText(_element){
	_element = getElement(_element);
	switch(_element.childNodes.length){
		case 0:
			return "";
		case 1:
			if (_element.firstChild.nodeType == 3){
				return _element.firstChild.data;
			}
	}
	return null;
}

/*$-- ------  2: EVENT HANDLING ------- --$*/

/**$--
* Adds an eventhandler to the event of an element.
* @param _event is the event, i.e. 'onclick' without the 'on' (leaving: 'click')
* @param _element is the element to add the event to.
* @param _function is a named or anonymous function with zero parameters
*
* example usage: add a click-eventhandler to the element with the id "BUTTON"
*
var btn = document.getElementById('BUTTON');
function clickBody(){
alert("you clicked the body!");
removeEvent(document.body,'click',clickBody);
}
btn.onclick = function(){
addEvent(document.body,'click',clickBody);
};
--$*/
function addEvent(_element,_event,_function){
	if (_element.addEventListener){
		_element.addEventListener(_event,_function,false);
	}
	else{
		try {
			_element.attachEvent('on' + _event,_function);
		} catch (e) {
		}
	}
}


/**$--
* Removes an eventhandler From the event of an element.
* @param _event is the event handler (i.e. 'onclick') without the 'on' (leaving: 'click')
* @param _element is the element to remove the event from
* @param _function is the named function to remove
* example usage: see addEvent
--$*/
function removeEvent(_element,_event,_function){
	if(_element.removeEventListener){
		_element.removeEventListener(_event,_function,false);
	}
	else{
		_element.detachEvent('on' + _event,_function);
	}
}

/**
* @param _event the key-pressed event.
* @param _asChar. Optional boolean variable. Wether the result should be returned as a character.
If false (default), it is returned as an integer.
*  @return the key pressed during a keyboard event
*
*/
function getKeyPressed(_event, _asChar){
	var result = _event ? _event.keyCode : window.event.keyCode;
	if (_asChar){
		result = String.fromCharCode(result);
	}
	return result;
}

/**$--
* This method is intended to be used solely within the eventhandler added by the
* "addEvent"-Method (see above) to an element's event.<p>
*
* Within the eventhandler function, it returns the node which triggered the
* event. This could either be the aforementioned element or one of its children.
* <p>
* @param _reference This parameter must always be 'this', e.g. getEventSender(this)
*
* example usage:
* addEvent(document.body,'click',function(){
*		var clickedElement = getEventSender(this);
*		alert('The element within the body which was clicked is: ' + clickedEvent); 
* });
*
--$*/
function getEventSender(_reference){
	if (_reference && _reference.target){
		return _reference.target;
	}else{
		return window.event.srcElement;
	}		
}

/*$-- ------  3: LAYER MANAGER ------- --$*/

/*$--
* Constructor for the layer manager /
--$*/
function LayerManager(){
	this.layers = [];
	this.putOnTop = function(_element){	
		/*$-- creates a layer object --$*/			
		var layer = {};
		layer.element = _element;
		layer.tempIndex = parseInt(_element.style.zIndex, 10);
		
		/*$-- inserts the layer object --$*/
		var layers = this.layers;	
		var len = layers.length;
		if (len > 0){	
			_element.style.zIndex = parseInt(layers[len-1].element.style.zIndex, 10)+1;
		}
		else{
		/*$-- default zIndex--$*/
			_element.style.zIndex = 100;
		}
		layers[len] = layer;
	};
	
	this.removeFromTop = function(_element){
		/*$-- search backwards for the element --$*/
		var layers = this.layers;
		var i;
		for (i=layers.length-1; i > 0; i--){		
			if (layers[i].element == _element){
				break;
			}
		}
		if (i <= 0){
			/*$--element is not in this list. return and ignore--$*/
			return;
		}
		_element.style.zIndex = layers[i].tempIndex;
		/*$--put all elements one position left and remove this one--$*/
		for (var j=i+1; j < layers.length; j++){
			layers[j-1] = layers[j];
		}						
		layers.pop();
	};
	
}
var layerManager = new LayerManager();	

/**$--
* Returns wether an element is shown or not
--$*/
layerManager.isVisible = function(_element){
	return _element.style.visibility != 'hidden';
};

/**$--
* Shows an element as the top layer.
--$*/
layerManager.show = function(_element){
	if (!this.isVisible(_element)){	
		_element.style.visibility 	= '';
		this.putOnTop(_element);			
	}	
};

/**$--
*Hides an element and resets its layer (zIndex)
--$*/
layerManager.hide = function(_element){
	if (this.isVisible(_element)){					
		this.removeFromTop(_element);
		_element.style.visibility = 'hidden';
	}
};

/*$-- ---------------------------------------------------------------------------
* PART THREE: RENDER TEMPLATE SPECIFIC FUNCTIONS 
--------------------------------------------------------------------------- --$*/

/**
* This method initializes a dropdown menu and implements its behavoir.
*
* @params: An object consisting of the following parameters
* +dropdownElement 		->HTML-Element, the entire pulldownMenu
* +headTextElement 		->HTML-Element, the Head-Text Area
* +headButtonElement	->HTML-ELement, the Head Button Area
* +bodyElement     		->HTML-Element, the body Area
* +onOpenFunction		-> EventHandler, defines what happens if the pulldown is opened
* +onCloseFunction		-> EventHandler, defines what happens if the pulldown is closed 
* @return a dropdown component with the following public methods:
* --- access
* -dropdown.getHead();
* -dropdown.getHead().getText();
* -dropdown.getHead().getButton();
* -dropdown.getBody();
* --- methods
* -dropdown.open();
* -dropdown.close();
* -dropdown.isOpen();
*/
function dropdown_init(params){
	
	var self = params.dropdownElement;
	//-----PRIVATE SECTION
	// VARIABLES
	
	// Status variables
	var bodyHandlerClicked = false;
	var onClickBodyHandler = null;
	
	// Element Variables
	var head 				= params.headElement;
	var headButton	= params.headButtonElement;
	var headText		= params.headTextElement;
	var body				= params.bodyElement;
	
	// FUNCTIONS
	var doClickBodyHandler = function(e){
		/*$-- abort the first call of this function --$*/
		if(bodyHandlerClicked === false){
			bodyHandlerClicked = true;
			return;
		}
		var child = getEventSender(e);				
		while (child !== null && child != body && child != headButton){
			child = child.parentNode;
		}
		/*$-- abort if the clicked elem=ent is within the pulldown's body or its head button --$*/
		if (child === null){
			removeEvent(document.body,'click',onClickBodyHandler);
			onClickBodyHandler = null;
			bodyHandlerClicked = false;
			self.close();
		}
	};
	
	//-- PUBLIC INTERFACE 
	// VARIABLES
	self.onOpen = defaultFunction(params.onOpenFunction);	
	self.onClose = defaultFunction(params.onCloseFunction);
	
	// METHODS
	self.open = function(){
		if (!self.isOpen()){			
			layerManager.show(body);
			// add the event
			onClickBodyHandler = doClickBodyHandler;						
			addEvent(document.body,'click',onClickBodyHandler);
			self.onOpen();
			return true;
		}
		return false;
	};
	
	self.close = function(){
		if (self.isOpen()){
			self.onClose();
			// remove a click handler, if it exists
			if (onClickBodyHandler !== null){
				removeEvent(document.body,'click',onClickBodyHandler);
			}
			layerManager.hide(body);
			return true;
		}
		return false;
	};	
	
	self.isOpen = function(){	
		return layerManager.isVisible(body);
	};
	
	// KEYBOARD HANDLER - MUST BE REFERENCED BY ITS IMPLEMENTING EVENT HANDLERS
	self.keyHandler = function(event){
		// the result is set by the key handlers. if it is true, the key event handling wont stop after these elements
		var result = true;
		switch(getKeyPressed(event)){
		// SPACE	
		case 32:
			// Fall-through
		// ENTER
		case 13:
			if (!self.isOpen()){
				self.open();
			}
			else{
				self.close();
			}
			result = false;
			break;
		// TAB - You cannot tab while the menu is opened.
		case 9:
			result = !self.isOpen();
			break;
		}
		return result;
	};
	
	// ELEMENT ACCESSORS
	self.getHead = function(){
		return head;
	};
	
	head.getText = function(){
		return headText;
	};
	
	head.getButton = function(){
		return headButton;
	};
	
	self.getBody = function(){
		return body;
	};

	//INITIALIZATION	
	layerManager.hide(body);
}

/**$--
* This function handles the dropdown menu's opening and closing
* when the mouse is clicked.
* @param dropdown the dropdown menu or its id
--$*/
function dropdown_buttonHandler(dropdown){
	dropdown = getElement(dropdown);
	if (!dropdown.isOpen()){
		dropdown.open();
	}else{
		dropdown.close();
	}
}

/**$--
* This function handles the opening and closing of the dropdown on a key press
* Supported Keys: space, enter
* @param dropdown The dropdown menu or its id
* @param event The event object (usually: event...)
--$*/
function dropdown_keyHandler(dropdown, event){
	dropdown = getElement(dropdown);
	return dropdown.keyHandler(event);	
}

