/*
    _______________________            ______________________
    XML DOM Alder Component             Browsers support:
              version 1.5.3             -> Internet Explorer
                                        -> Mozilla
    _____________________________       -> Opera
    Features:                           -> Firefox
     -> Server Side Independency        -> Konqueror
     -> Cross Browser Support           -> Safari
     -> Dynamic Loading
     -> XML Source
     -> Easy Customization



                             ______________________________
                             Serghei Egoricev (c) 2006-2008
                             egoricev [at] sourceforget.net
*/

// CSS import
document.writeln('<link rel="stylesheet" href="css/alder.css" type="text/css"/>');

Alder = function() {}
/*
	Use double click for navigate, single click for expand
*/
Alder.useDblClicks = true; // NOT IMPLEMENTED
Alder.saveNodesStateInCookies = true;
/*
	CSS classes to be used.
*/
Alder.expandedClassName = "";
Alder.collapsedClassName = "collapsed";
Alder.selectedClassName = "selected";
Alder.plusMinusClassName = "plusminus";
Alder.cssClass = "alder";

/*
	Images
*/
Alder.collapsedImage = "img/collapsed.gif";
Alder.expandedImage = "img/expanded.gif";
Alder.noChildrenImage = "img/treenochild.gif";

/*
	Xml Attributes
*/
Alder.xmlCaption = "caption";
Alder.xmlUrl = "url";
Alder.xmlTarget = "target";
Alder.xmlRetreiveUrl = "retreiveUrl";
Alder.xmlIcon = "icon";
Alder.xmlExpanded = "expanded";

/*
	Text for loading
*/
Alder.loadingText = "Loading ...";

/*
	Private members
*/
Alder.obj = null;
Alder.instanceCount = 0;
Alder.instancePrefix = "alder";
Alder.cookiePrefix = "alder";
Alder.dwnldQueue = new Array;
Alder.dwnldCheckTimeout = 100;
Alder.expandOnClick = true;

/*
	Interval handler. Ckecks for new nodes loaded.
	Adds loaded nodes to the tree.
*/
Alder.checkLoad = function ()
{
	var i, httpReq;
	for (i = 0; i<Alder.dwnldQueue.length; i++)
		if ((httpReq = Alder.dwnldQueue[i][0]).readyState == 4 /*COMPLETED*/)
		{
			var node = Alder.dwnldQueue[i][1];
			// unqueue loaded item
			Alder.dwnldQueue.splice(i, 1);
			Alder.appendLoadedNode(httpReq, node);
			if (Alder.saveNodesStateInCookies)
				Alder.openAllSaved(Alder.getId(node));
		} // if
	// will call next time, not all nodes were loaded
	if (Alder.dwnldQueue.length != 0)
		window.setTimeout(Alder.checkLoad, Alder.dwnldCheckTimeout);
}

/*
	Adds loaded node to tree.
*/
Alder.appendLoadedNode = function (httpReq, node)
{
	// create DomDocument from loaded text
	var xmlDoc = Alder.loadXml(httpReq.responseText);
	// create tree nodes from xml loaded
	var newNode = Alder.convertXml2NodeList(xmlDoc.documentElement);
	// Add loading error handling here must be added
	Alder.appendNode(node, newNode);
}

/*
	Event handler when node is clicked.
	Navigates node link, and makes node selected.
*/
Alder.NodeClick = function (event)
{
	var node = event.srcElement /*IE*/ || event.target /*DOM*/;
	// <li><a><img> - <img> is capturing the event
	while (node.tagName != "A")
		node = node.parentNode;
	node.blur();
	node = node.parentNode;
	Alder.obj = Alder.getObj(node);
	if (Alder.expandOnClick)
		Alder.expandNode(node);
	Alder.selectNode(node);
}

/*
	Event handler when plus/minus icon is clicked.
	Desides whenever node should be expanded or collapsed.
*/
Alder.ExpandCollapseNode = function (event)
{
	var anchorClicked = event.srcElement /*IE*/ || event.target /*DOM*/;
	// <li><a><img> - <img> is capturing the event
	while (anchorClicked.tagName != "A")
		anchorClicked	= anchorClicked.parentNode;
	anchorClicked.blur();
	var node = anchorClicked.parentNode;
	// node has no children, and cannot be expanded or collapsed
	if (node.isEmpty)
		return;
	Alder.obj = Alder.getObj(node);
	if (Alder.isNodeCollapsed(node))
		Alder.expandNode(node);
	else
		Alder.collapseNode(node);
	// cancelling the event to prevent navigation.
	if (event.preventDefault == undefined)
	{ // IE
		event.cancelBubble = true;
		event.returnValue = false;
	} // if
	else
	{ // DOM
		event.preventDefault();
		event.cancelBubble = true;
	} // else
}

/*
	Determines if specified node is selected.
*/
Alder.isNodeSelected = function (node)
{
	return (node.isSelected == true) || (Alder.obj.selectedNode == node);
}

/*
	Determines if specified node is expanded.
*/
Alder.isNodeExpanded = function (node)
{
	return (Alder.expandedClassName == node.className) || (node.expanded == true);
}

/*
	Determines if specified node is collapsed.
*/
Alder.isNodeCollapsed = function (node)
{
	return (Alder.collapsedClassName == node.className) || (node.collapsed == true);
}

/*
	Determines if node currently selected is at same
	level as node specified (has same root).
*/
Alder.isSelectedNodeAtSameLevel = function (node)
{
	if (Alder.obj.selectedNode == null) // no node currently selected
		return false;
	var i, currentNode, children = node.parentNode.childNodes; // all nodes at same level (li->ul->childNodes)
	for (i = 0; i < children.length; i++)
		if ((currentNode = children[i]) != node && Alder.isNodeSelected(currentNode))
			return true;
	return false;
}

/*
	Mark node as selected and unmark prevoiusly selected.
	Node is marked with attribute and <a> is marked with css style
	to avoid mark <li> twise with css style expanded and selected.
*/
Alder.selectNode = function (node)
{
	if (Alder.isNodeSelected(node)) // already marked
		return;
	if (Alder.obj.selectedNode != null)
	{// unmark previously selected node.
		Alder.obj.selectedNode.isSelected = false;
		// remove css style from anchor
		Alder.getNodeAnchor(Alder.obj.selectedNode).className = "";
	} // if
	// collapse selected node if at same level
	if (Alder.isSelectedNodeAtSameLevel(node))
		Alder.collapseNode(Alder.obj.selectedNode);
	// mark node as selected
	Alder.obj.selectedNode = node;
	node.isSelected = true;
	Alder.getNodeAnchor(node).className = Alder.selectedClassName;
}

/*
	Expand collapsed node. Loads children nodes if needed.
*/
Alder.expandNode = function (node, avoidSaving)
{
	if (node.isEmpty)
		return;
	Alder.getNodeImage(node).src = Alder.expandedImage;
	node.className = Alder.expandedClassName;
	node.expanded = true;
	node.collapsed = false;
	if (Alder.areChildrenNotLoaded(node))
		Alder.loadChildren(node);
	if (Alder.saveNodesStateInCookies && !avoidSaving)
		Alder.saveOpenedNode(node);
}

/*
	Collapse expanded node.
*/
Alder.collapseNode = function (node, avoidSaving)
{
	if (node.isEmpty)
		return;
	Alder.getNodeImage(node).src = Alder.collapsedImage;
	node.className = Alder.collapsedClassName;
	node.collapsed = true;
	node.expanded = false;
	if (Alder.saveNodesStateInCookies && !avoidSaving)
		Alder.saveClosedNode(node);
}

/*
	Returns plus/minus <img> for node specified.
*/
Alder.getNodeImage = function (node)
{
	return node.getElementsByTagName("IMG")[0];
}

/*
	Returns retreiveUrl for node specified.
*/
Alder.getNodeRetreiveUrl = function (node)
{
	return node.getElementsByTagName("A")[0].href;
}

/*
	Returns node link <a> element (<li><a><img></a><a>)
*/
Alder.getNodeAnchor = function (node)
{
	return node.getElementsByTagName("A")[1];
}

/*
	Cancel loading children nodes.
*/
Alder.CancelLoad = function (event)
{ 
	var i, node = event.srcElement /*IE*/ || event.target /*DOM*/;
	while (node.tagName != "LI")
		node = node.parentNode;
	// search node in queue
	for (i = 0; i<Alder.dwnldQueue.length; i++)
		if (Alder.dwnldQueue[i][1] == node)
		{
			// remove from queue
			Alder.dwnldQueue.splice(i, 1);
			// collapse node
			Alder.collapseNode(node);
		} // if
}

/*
	Loads text from url specified and returns it as result.
*/
Alder.loadUrl = function (url, async)
{
	// create request object
	var httpReq = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
	// prepare request
	httpReq.open("GET" /* method */, url /* url */, async == true /* async */, null /* login */, null /* password */);
	// send request
	httpReq.send(null);
	return async == true? httpReq : httpReq.responseText;
}

/*
	Creates XmlDom document from xml text string.
*/
Alder.loadXml = function (xmlString)
{
	var xmlDoc;
  if (window.DOMParser) /*Mozilla*/
    xmlDoc = new DOMParser().parseFromString(xmlString, "text/xml");
  else
  {
  	if (document.implementation && document.implementation.createDocument)
	  	xmlDoc = document.implementation.createDocument("","", null); /*Konqueror*/
		else
	    xmlDoc = new ActiveXObject("Microsoft.XmlDom"); /*IE*/
	  
  	xmlDoc.async = false;
  	xmlDoc.loadXML(xmlString);
  } // else
  return xmlDoc;
}

/*
	Determines if children are loaded for node specified.
*/
Alder.areChildrenNotLoaded = function (node)
{
	return Alder.getNodeSpan(node) != null;
}

/*
	Finds loading span for node.
*/
Alder.getNodeSpan = function (node)
{
	var span = node.getElementsByTagName("SPAN");
	return (span.length > 0 && (span = span[0]).parentNode == node) ? span : null;
}

/*
	Enqueue load of children nodes for node specified.
*/
Alder.loadChildren = function (node)
{
	// get url with children
	var url = Alder.getNodeRetreiveUrl(node);
	// retreive xml text from url
	var httpReq = Alder.loadUrl(url, true);
	// enqueue node loading
	if (Alder.dwnldQueue.push(new Array (httpReq, node)) == 1)
		window.setTimeout(Alder.checkLoad, Alder.dwnldCheckTimeout);
}

/*
	Creates HTML nodes list from XML nodes.
*/
Alder.convertXml2NodeList = function (xmlElement)
{
	var ul = document.createElement("UL");
	var i, node, children = xmlElement.childNodes;
	var index = 0;
	for (i = 0; i<children.length; i++)
		if ((node = children[i]).nodeType == 1 /* ELEMENT_NODE */)
			ul.appendChild(Alder.convertXml2Node(node)).nodeIndex = index++;
	return ul;
}

/*
	Adds event handler
*/
Alder.addEvent = function (obj, fn, ev)
{
	if (ev == undefined) ev = "click"; // defaulting event to onclick
	if (obj.addEventListener)
		obj.addEventListener(ev, fn, false);
	else
		if (obj.attachEvent)
			obj.attachEvent("on"+ev, fn);
		else
			obj.onclick = fn;
}

/*
	Determines if xml node has child nodes inside.
*/
Alder.hasXmlNodeChildren = function (xmlElement)
{
	var i, children = xmlElement.childNodes;
	for (i = 0; i<children.length; i++)
		if ((node = children[i]).nodeType == 1 /* ELEMENT_NODE */)
			return true;
	return false;
}

/*
	Appends newly created node to node specified.
	Simply replace loading <span> at new node.
*/
Alder.appendNode = function (node, newNode)
{
	node.replaceChild(newNode, Alder.getNodeSpan(node));
}

/*
	Creates tree object. Loads it content from url specified.
	Appends tree to node, specified as obj argument by node id or node itself.
*/
Alder.prototype.Create = function (url, obj)
{
	var div = document.createElement("DIV");
	div.id = Alder.instancePrefix + Alder.instanceCount++;
	div.className = Alder.cssClass;
	var xml = Alder.loadUrl(url, false);
	var xmlDoc = Alder.loadXml(xml);
	var newNode = Alder.convertXml2NodeList(xmlDoc.documentElement);
	div.appendChild(newNode);
	if (obj != undefined)
	{
		if (obj.appendChild) // is node
			obj.appendChild(div);
		else // is node id
		{
			var hostNode = document.getElementById(obj);
			if (hostNode) 
				hostNode.appendChild(div);
		} // else
	} // if
	else
		document.body.appendChild(div);
	if (Alder.saveNodesStateInCookies)
		Alder.openAllSaved(div.id);
}

/*
	Creates HTML tree node (<li>) from xml element.
*/
Alder.convertXml2Node = function (xmlElement)
{
	var li = document.createElement("LI");
	var a2 = document.createElement("A");
	var i1 = document.createElement("IMG");
	var i2 = document.createElement("IMG");
	var hasChildNodes = Alder.hasXmlNodeChildren(xmlElement);
	var retreiveUrl = xmlElement.getAttribute(Alder.xmlRetreiveUrl);
	i1.className = Alder.plusMinusClassName;

	var a1 = document.createElement("A");
	// plus/minus icon
	a1.appendChild(i1);
	Alder.addEvent(a1, Alder.ExpandCollapseNode);
	
	// plus/minus link
	a1.href = retreiveUrl;
	if (retreiveUrl == null || retreiveUrl.length == 0)
		a1.onclick = function () { return false; };
	li.appendChild(a1);
	
	// node icon
	i2.src = xmlElement.getAttribute(Alder.xmlIcon);
	a2.appendChild(i2);
	
	// node link
	a2.href = xmlElement.getAttribute(Alder.xmlUrl);
	a2.target = xmlElement.getAttribute(Alder.xmlTarget);
	a2.title = xmlElement.getAttribute(Alder.xmlCaption);
	a2.appendChild(document.createTextNode(xmlElement.getAttribute(Alder.xmlCaption)));
	Alder.addEvent(a2, Alder.NodeClick);

	li.appendChild(a2);
	
	// loading span
	if (!hasChildNodes && retreiveUrl != null && retreiveUrl.length != 0)
	{
		var span = document.createElement("SPAN");
		span.innerHTML = Alder.loadingText;
		Alder.addEvent(span, Alder.CancelLoad);
		li.appendChild(span);
	} // if
	
	// add children
	if (hasChildNodes)
		li.appendChild(Alder.convertXml2NodeList(xmlElement));
	if (hasChildNodes || retreiveUrl != null && retreiveUrl.length != 0)
	{
		if (xmlElement.getAttribute(Alder.xmlExpanded))
			Alder.expandNode(li, true);
		else
			Alder.collapseNode(li, true);
	} // if
	else
	{
		i1.src = Alder.noChildrenImage; // no children
		li.isEmpty = true;
	} // else

	return li;
}

/*
	Retreives current tree object.
*/
Alder.getObj = function (node)
{
	var obj = node;
	while (obj != null && obj.tagName != "DIV")
		obj = obj.parentNode;
	return obj;
}

/*
	Retreives id for the current tree instance.
*/
Alder.getId = function (node)
{
	var obj = Alder.getObj(node);
	if (obj)
		return obj.id;
	return "";
}

/*
	Retreives unique id for tree node.
*/
Alder.getNodeId = function (node)
{
	var id = "";
	var obj = node;
	while (obj != null && obj.tagName != "DIV")
	{
		if (obj.tagName == "LI" && obj.nodeIndex != null)
			id = "_" + obj.nodeIndex + id;
		obj = obj.parentNode;
	} // while
//	if (obj != null && obj.tagName == "DIV")
//		id = obj.id + "_" + id;
	return id;
}

/*
	Saves node as opened for reload.
*/
Alder.saveOpenedNode = function (node)
{
	var treeId = Alder.getId(node);
	var state = Alder.getAllNodesSavedState(treeId);
	var nodeState = Alder.getNodeId(node) + ",";
	if (state.indexOf(nodeState) == -1)
	{
		state += nodeState;
		Alder.setAllNodesSavedState(treeId, state);
	} // if
}

/*
	Saves node as closed for reload.
*/
Alder.saveClosedNode = function (node)
{
	var treeId = Alder.getId(node);
	var state = Alder.getAllNodesSavedState(treeId);
	state = state.replace(new RegExp(Alder.getNodeId(node) + ",", "g"), "");
	Alder.setAllNodesSavedState(treeId, state);
}

/*
	Retreives nodes state from cookies (helper function).
*/
Alder.getAllNodesSavedState = function (treeId)
{
	var state = Alder.getCookie(Alder.cookiePrefix + "_" + treeId);
	return state == null ? "" : state;
}

/*
	Saves nodes state in cookies (helper function).
*/
Alder.setAllNodesSavedState = function (treeId, state)
{
	Alder.setCookie(Alder.cookiePrefix + "_" + treeId, state);
}

/*
	Enques list of all opened nodes
*/
Alder.openAllSaved = function(treeId)
{
	var nodes = Alder.getAllNodesSavedState(treeId).split(",");
	var i;
	for (i=0; i<nodes.length; i++)
	{
		var node = Alder.getNodeById(treeId, nodes[i]);
		if (node && Alder.isNodeCollapsed(node))
			Alder.expandNode(node);
	} // for
}

/*
	Scans tree for specific node by it's id.
*/
Alder.getNodeById = function(treeId, nodeId)
{
	var node = document.getElementById(treeId);
	if (!node)
		return null;
	var path = nodeId.split("_");
	var i;
	for (i=1; i<path.length; i++)
	{
		if (node != null)
		{
			node = node.firstChild;
			while (node != null && node.tagName != "UL")
				node = node.nextSibling;
		} // if
		if (node != null)
			node = node.childNodes[path[i]];
		else
			break;
	} // for
	return node;
}

/*
	Writes value in cookie (helper function).
*/
Alder.setCookie = function(sName, sValue)
{
  document.cookie = sName + "=" + escape(sValue) + ";";
}

/*
	Reads value from cookie (helper function).
*/
Alder.getCookie = function(sName)
{
  var a = document.cookie.split("; ");
  for (var i=0; i < a.length; i++)
  {
    var aa = a[i].split("=");
    if (sName == aa[0]) 
      return unescape(aa[1]);
  } // for
  return null;
}