/**
  * Version: $Id$
  *
  * Generic class for hiding parts of html code on button click
  *
  * In order to make this class work, we must use some reserved classes
  * for the following elements:
  *  1) active button, i.e. class of button, when the corresponding section
  *     is visible
  *  2) passive button (class of button, when the corresponding section
  *     is invisible)
  *  3) class of element related to certain section. Individual
  *     sections will then have class names consisting of this name and
  *     suffixes -1, -2 etc. (according to the number of button it is related
  *     to). There may be more such elements with the same class.
  * After creating the html code of sections we must call javascript:
  *   hider = new HTMLHider(ACTIVE_BUTTON_CLASS, PASSIVE_BUTTON_CLASS,
  *             SECTION_CLASS);
  * If we want to use HTMLHider with HTMLReplicator, it is necessary to
  * call HTMLHider::checkHtml() whenever HTML is modified by HTMLReplicator
  */

/**
  * Constructor
  *
  * @param string buttonActiveClass Class of button used for selected section
  * @param string buttonPassiveClass Class of button corresponding to
  *   section which is not currently selected
  * @param string sectionClass Class used for section (to be shown or hidden)
  */
function HTMLHider(butActClass, butPasClass, sectionClass) {
	this.butActClass = butActClass;
	this.butPasClass = butPasClass;
	this.sectionClass = sectionClass;
	this.buttonGroups = [];
	this.sectionGroups = [];
	this.checkHtml();
}

/**
  * Checks HTML code and finds its parts to which methods should be applied
	*
	* @access public
	* @access private
	*/
HTMLHider.prototype.checkHtml = function() {
	var buttons;

	tags = this.getElementsByClass(
		[this.butActClass, this.butPasClass, this.sectionClass + '-[0-9]\+']);

	this.separateGroups(tags);
	this.hideInactiveSections();
	this.addHandlers();
}

/**
  * Returns true if the node has given class
	*
	* @param Node node
	* @param string className This is used in regexp without excaping
	* @return boolean
	*/
HTMLHider.prototype.hasClass = function(node, className) {
	re = eval('/(^| )' + className + '($| )/');
	return node.className && node.className.match(re);
}

/**
  * Gets common parent of given two nodes
	*
	* @access private
	* @param Node node1
	* @param Node node2
	* @return Node
	*/
HTMLHider.prototype.getCommonParent = function(node1, node2) {
	var nodes, parents, i, parent, max;

	nodes = [node1, node2];
	parents = [[], []];
	for (i = 0; i < 2; i++) {
		parent = nodes[i];
		while (parent = parent.parentNode)
			parents[i].unshift(parent);
	}
	max = Math.max(parents[0].length, parents[1].length);
	for (i = 0; i < max - 1; i++)
		if (parents[0][i] != parents[1][i])
			break;
	return parents[0][i-1];
}

/**
  * Returns TRUE if given node has given ancestor
	*
	* access private
	* @param Node node
	* @param Node ancestor
	* @return boolean
	*/
HTMLHider.prototype.hasAncestor = function(node, ancestor) {
	var parent;

	parent = node;
	while (parent = parent.parentNode)
		if (parent == ancestor)
			return true;
	return false;
}

/**
  * Finds all elements having given class
	*
	* @param mixed className string or array
	* @param Node node Node to be searched (optional)
	* @access private
	*/
HTMLHider.prototype.getElementsByClass = function(className) {
	var node, i, retArr;

	if (typeof(className) == 'string')
		className = [className];

	if (arguments.length > 1)
		node = arguments[1];
	else
		node = document;

	retArr = [];
	for (i = 0; i < className.length; i++)
		if (this.hasClass(node, className[i])) {
			retArr.push(node); break;
		}

	if (node.childNodes)
		for (i = 0; i < node.childNodes.length; i++)
			retArr = retArr.concat(this.getElementsByClass(
				className, node.childNodes[i]));
	return retArr;
}

/**
  * Separates groups from tags
	*
	* Groups are saved to this.buttonGroups and this.sectionGroups
	*
	* @access private
	* @param array tags
	* @return void
	*/
HTMLHider.prototype.separateGroups = function(tags) {
	var button, section, i, parent, hTags, bGroup, sGroup;

	while (tags.length) {
		button = null; section = null;
		for (i = 0; i < tags.length; i++)
			if (this.hasClass(tags[i], this.butActClass)) {
				button = tags[i];
				break;
			}
		for (i = 0; i < tags.length; i++)
			if (this.hasClass(tags[i], this.sectionClass + '-[0-9]\+')) {
				section = tags[i];
				break;
			}
		if (!button)
			alert('Incorrect HTML! Cannot find active button.');
		if (!section)
			alert('Incorrect HTML! Cannot find any section.');
		parent = this.getCommonParent(button, section);

		hTags = [];
		bGroup = []; sGroup = [];
		for (i = 0; i < tags.length; i++) {
			if (this.hasAncestor(tags[i], parent)) {
				if (this.hasClass(tags[i], this.sectionClass + '-[0-9]\+'))
					sGroup.push(tags[i]);
				else
					bGroup.push(tags[i]);
			}
			else
				hTags.push(tags[i]);
		}
		tags = hTags;
		this.buttonGroups.push(bGroup);
		this.sectionGroups.push(sGroup);
	}
}

/**
  * Alters class of given element
	*
	* className should start with + (in which case class is added) or -
	*   (class is removed)
	*
	* @access private
	* @param Node node
	* @param string className
	* @return void
	*/
HTMLHider.prototype.alterClass = function(node, className) {
	var classes, newClasses, hClass, i;

	classes = node.className.split(/\s+/);
	newClasses = [];
	hClass = className.substr(1);
	for (i = 0; i < classes.length; i++)
		if (classes[i] != hClass)
			newClasses.push(classes[i]);
	if (className.charAt(0) == '+')
		newClasses.push(hClass);
	node.className = newClasses.join(' ');
}

/**
  * Hides section of html code, which are not active and reveals the active ones
	*
	* @access public
	* @return void
	*/
HTMLHider.prototype.hideInactiveSections = function() {
	var i, j, k;

	for (i = 0; i < this.buttonGroups.length; i++) {
		for (j = 0; j < this.buttonGroups[i].length; j++)
			if (this.hasClass(this.buttonGroups[i][j], this.butActClass))
				break;
		for (k = 0; k < this.sectionGroups[i].length; k++)
			if (this.hasClass(this.sectionGroups[i][k],
					this.sectionClass + '-' + (j+1)))
				this.alterClass(this.sectionGroups[i][k], '-invisible');
			else
				this.alterClass(this.sectionGroups[i][k], '+invisible');
	}
}

/**
  * Add handlers to buttons
	*
	* @access public
	*/
HTMLHider.prototype.addHandlers = function() {
	var i, j;

	for (i = 0; i < this.buttonGroups.length; i++)
		for (j = 0; j < this.buttonGroups[i].length; j++) {
			if (this.hasClass(this.buttonGroups[i][j], this.butPasClass)) {
				this.buttonGroups[i][j].onmouseover = this.onMouseOverHandler;
				this.buttonGroups[i][j].onclick = this.onClickHandler;
			}
			else {
				this.buttonGroups[i][j].onmouseover = '';
				this.buttonGroups[i][j].onclick = '';
			}
		}
	html_hider_global_object_reference = this;
}

/**
  * Handler for OnMouseOverEvent
	*
	* @param Event e
	* @return void
	*/
HTMLHider.prototype.onMouseOverHandler = function(e) {
	var target;

	if (!e) var e = window.event;
	target = e.srcElement ? e.srcElement : e.target;
	target.style.cursor = 'pointer';
}

/**
  * Handler for OnClickEvent
	*
	* @param Event e
	* @return void
	*/
HTMLHider.prototype.onClickHandler = function(e) {
	var target, i, j, hider;

	hider = html_hider_global_object_reference;
	if (!e) var e = window.event;
	target = e.srcElement ? e.srcElement : e.target;
	for (i = 0; i < hider.buttonGroups.length; i++) {
		for (j = 0; j < hider.buttonGroups[i].length; j++)
			if (hider.buttonGroups[i][j] == target)
				break;
		if (j < hider.buttonGroups[i].length) {
			for (k = 0; k < hider.buttonGroups[i].length; k++)
				if (hider.hasClass(hider.buttonGroups[i][k], hider.butActClass)) {
					hider.alterClass(hider.buttonGroups[i][k], '-' + hider.butActClass);
					hider.alterClass(hider.buttonGroups[i][k], '+' + hider.butPasClass);
					break;
				}
			hider.alterClass(hider.buttonGroups[i][j], '+' + hider.butActClass);
			hider.alterClass(hider.buttonGroups[i][j], '-' + hider.butPasClass);
		}
		hider.addHandlers();
		hider.hideInactiveSections();
	}
}

