// $Id: tree.js 4853 2006-10-23 07:30:39Z andrew $
/**
 * The southprojects DHTML Calendar
 *
 * Copyright (c) 2006 by SouthProjects
 * http://www.southprojects.com
 * Santiago, Chile,
 * Version 1.2 Dec 2006.
 * All rights reserved.
 *
 *
 * Tree Widget
 */

/**
 * The southprojects.Tree object constructor.  Pass to it the ID of an UL element (or
 * a reference to the element should you have it already) and an optional
 * configuration object.  This function creates and initializes the tree widget
 * according to data existent in the nested list, and applies the configuration
 * specified.
 *
 * The configuration object may contain the following options (the following
 * shows default values):
 *
 * \code
 * {
 *    tree               : null,     // string or object
 *    hiliteSelectedNode : true,     // boolean
 *    compact            : false,    // boolean
 *    dynamic            : false,    // boolean
 *    initLevel          : false,    // false or number
 *    defaultIcons       : null,      // null or string
 *    expandOnLabel      : null,	// boolean 
 *    labelDblClick      : null,	// boolean 
 *    theme              : null,      // null or string
 *    themepath          : null      // null or string
 * }
 * \endcode
 *
 * - tree -- reference to target UL element. It will be converted to tree.
 *   Required.
 * - hiliteSelectedNode -- if \b false is passed, the tree will not highlight
 *   the currently selected node.
 * - compact -- if \b true is passed the tree will work in a "compact" mode; in
 *   this mode it automatically closes sections not relevant to the current
 *   one.
 * - dynamic -- if \b true is passed the tree will use the "dynamic initialization"
 *   technique which greatly improves generation time.  Some functionality is
 *   not available in this mode until all the tree was generated.  In "dynamic"
 *   mode the tree is initially collapsed and levels are generated "on the fly"
 *   as the end user expands them.  You can't retrieve nodes by ID (which
 *   implies you can't synchronize to certain nodes) until they have been
 *   generated.
 * - initLevel -- when this is a numeric value, it specifies the maximum
 *   "expand level" that the tree will use initially.  Therefore, if for
 *   instance you specify 1 then the tree will be initially expanded one level.
 *   Pass here 0 to have the tree fully collapsed, or leave it \b false to have
 *   the tree fully expanded.
 * - defaultIcons -- you can pass here a string.  If so, all tree items will
 *   get an additional TD element containing that string in the \b class
 *   attribute.  This helps you to include custom default icons without
 *   specifying them as IMG tags in the tree.  See our examples.
 * - jsonLoadCallback - function that will be called when after clicking at
 *   zpLoadJSON node result will be received.
 * - theme - theme name.
 * - themePath - path(absolute or relative) where theme files are located
 * - source - tree source
 * - sourceType - tree source type
 *
 * @param config [Object, optional] -- the configuration options
 *
 * @return
 */
southprojects.Tree = function() {
	var objArgs = {};

	switch(arguments.length){
		case 1:
			objArgs = arguments[0];
			break;
		case 2:
			objArgs = arguments[1];
			objArgs.tree = arguments[0];
			break;
	}
	
	southprojects.Tree.SUPERconstructor.call(this, objArgs);
};

/**
 * Unique static id of the widget class. Gives ability for southprojects#inherit to
 * determine and store path to this file correctly when it is included using
 * southprojects#include. When this file is included using southprojects#include or path
 * to this file is gotten using southprojects#getPath, this value must be specified
 * as script id.
 * @private
 */
southprojects.Tree.id = "southprojects.Tree"

// Inherit SuperClass
southprojects.inherit(southprojects.Tree, southprojects.Widget);

/**
 * Initializes object. Needed to be able to inherit this class.
 *
 * @param config [Object, optional] -- the configuration options
 */
southprojects.Tree.prototype.init = function(config) {
	this.config.tree = null;
	this.config.d_profile = false;
	this.config.hiliteSelectedNode = true;
	this.config.defaultIcons = null;
	this.config.compact = false;
	this.config.dynamic = false;
	this.config.initLevel = false;
	this.config.jsonLoadCallback = southprojects.Tree.defaultProcessJson;
	//expand/collapse the tree when the text or image are clicked
	this.config.expandOnLabel = true;
	this.config.labelDblClick = false;

	//Keep track, using cookies, of the last location the user opened
	this.config.saveState = false;
	this.config.saveId = null;
	// processing Widget functionality
	southprojects.Tree.SUPERclass.init.call(this, config);

	if(this.config.dynamic){
		this.config.initLevel = 0;
	}

	if(
		this.config.dynamic ||
		this.config.saveState &&
		(
			typeof(this.config.saveId) != "string" ||
			(
				typeof(this.config.saveId) == "string" &&
				this.config.saveId.length == 0
			)
		)
	){
		this.config.saveState = false;
	}

	// <PROFILE>
	if (this.config.d_profile) {
		var T1 = new Date().getTime();
	
		profile = {
			items : 0,
			trees : 0,
			icons : 0
		};
	}
	// </PROFILE>

	if (typeof(this.config.tree) == "string"){
		this.config.tree = document.getElementById(this.config.tree);
	}

	if(this.config.tree == null){
		throw "no target UL element given!";
	}

	this.id = this.config.tree.id || southprojects.Utils.generateID("tree");
	this.list = this.config.tree;
	this.items = {};
	this.trees = {};
	this.selectedItem = null;
	this.top_parent = southprojects.Utils.createElement("div");
	this.top_parent.className = "tree tree-top";
	this.createTree(this.config.tree, this.top_parent, 0);
	this.config.tree.parentNode.insertBefore(this.top_parent, this.config.tree);
	this.config.tree.parentNode.removeChild(this.config.tree);
	southprojects.Tree.all[this.id] = this;

	// check if we have an initially selected node and sync. the tree if so
	if (this.selectedItem){
		this.sync(this.selectedItem.__msh_item);
	}

	//if we're keeping track of state, and we have saved state information
	if(this.config.saveState) {
		//restore to previous node
		var txt = southprojects.Utils.getCookie("southprojects.Tree-" + config.saveId)

		if (txt) {
			this.sync(txt);
		}
	}

	this.loadData();

	// <PROFILE>
	if (this.config.d_profile) {
		alert("Generated in " + (new Date().getTime() - T1) + " milliseconds\n" +
		      profile.items + " total tree items\n" +
		      profile.trees + " total (sub)trees\n" +
		      profile.icons + " total icons");
	}
	// </PROFILE>
};

/**
 * This global variable keeps a "hash table" (that is, a plain JavaScript
 * object) mapping ID-s to references to southprojects.Tree objects.  It's helpful if
 * you want to operate on a tree but you don't want to keep a reference to it.
 * Example:
 *
 * \code
 *   // the following makes a tree for the <ul id="tree-id"> element
 *   var tree = new southprojects.Tree("tree-id");
 *   // ... later
 *   var existing_tree = southprojects.Tree.all("tree-id");
 *   // and now we can use \b existing_tree the same as we can use \b tree
 *   // the following displays \b true
 *   alert(existing_tree == tree);
 * \endcode
 *
 * So in short, this variable remembers values returned by "new
 * southprojects.Tree(...)" in case you didn't.
 */
southprojects.Tree.all = {};

/**
 * \internal Function that creates a (sub)tree.  This function walks the UL
 * element, computes and assigns CSS class names and creates HTML elements for
 * a subtree.  Each time a LI element is encountered, createItem() is called
 * which effectively creates the item.  Beware that createItem() might call
 * back this function in order to create the item's subtree. (so createTree and
 * createItem form an indirect recursion).
 *
 * @param list [HTMLElement] -- reference to the UL element
 * @param parent [HTMLElement] -- reference to the parent element that should hold the (sub)tree
 * @param level [integer] -- the level of this (sub)tree in the main tree.
 *
 * @return id -- the (sub)tree ID; might be automatically generated.
 */
southprojects.Tree.prototype.createTree = function(list, parent, level) {
	// PROFILE
	if (this.config.d_profile){
		++profile.trees;
	}

	var id = list.id || southprojects.Utils.generateID("tree.sub");
	var self = this;

	function _makeIt() {
		self.creating_now = true;
		var last_li = null;
		var next_li = null;
		var i = list.firstChild;
        var items = parent.__msh_items = [];

		self.trees[id] = parent;
		parent.__msh_level = level;
		parent.__msh_treeid = id;

		while (i) {
			if (last_li){
				last_li.className += " tree-lines-c";
			}

			if (i.nodeType != 1){
				i = i.nextSibling;
			} else {
				next_li = southprojects.Utils.getNextSibling(i, 'li');

				if (i.tagName.toLowerCase() == 'li') {
					last_li = self.createItem(i, parent, next_li, level);

					if (last_li) { //false when webmaster creates malformed tree
						items[items.length] = last_li.__msh_item;
					}
				}

				i = next_li;
			}
		}

		i = parent.firstChild;

		if (i && !level) {
			i.className = i.className.replace(/ tree-lines-./g, "");
			i.className += (i === last_li) ? " tree-lines-s" : " tree-lines-t";
		}

		if (last_li && (level || last_li !== i)) {
			last_li.className = last_li.className.replace(/ tree-lines-./g, "");
			last_li.className += " tree-lines-b";
		}

		self.creating_now = false;
	};

	if (this.config.dynamic && level > 0){
		parent.style.display = "none";
		this.trees[id] = _makeIt;
	} else {
		_makeIt();
	}

	return id;
};

/**
 * \internal This function walks through a LI element and creates the HTML
 * elements associated with that tree item.  When it encounters an UL element
 * it calls createTree() in order to create the item's subtree.  This function
 * may also call item_addIcon() in order to add the +/- buttons or icons
 * present in the item definition as IMG tags, or item_addDefaultIcon() if the
 * tree configuration specifies "defaultIcons" and no IMG tag was present.
 *
 * @param li [HTMLElement] -- reference to the LI element
 * @param parent [HTMLElement] -- reference to the parent element where the HTML elements should be created
 * @param next_li [HTMLLiElement] -- reference to the next LI element, if this is not the last one
 * @param level [integer] -- the level of this item in the main tree
 * @param atStart [HTMLElement optional] -- reference to the element DIV with a TABLE object that represents the child following the new child added or inserted
 *
 * @return [HTMLElement] -- a reference to a DIV element holding the HTML elements of the created item
 */
southprojects.Tree.prototype.createItem = function(li, parent, next_li, level, atStart) {
    // PROFILE
	if (this.config.d_profile){
		++profile.items;
	}

	if (!li.firstChild){
		return;
	}

	var afterNode = null;
	if (atStart) { //Optional parameter after the fourth parameter to allow the new created node to be inserted before it, instead of appending to the parent
		afterNode = atStart;
	}

	var id = li.id || southprojects.Utils.generateID("tree.item");

	//Do not append the new div element to the parent, the new node in the div will be inserted before the 'afterNode'
	var item = this.items[id] = ((afterNode == null) ? 
		southprojects.Utils.createElement("div", parent) : 
		southprojects.Utils.createElement("div")); 

	var t = southprojects.Utils.createElement("table", item);
	var tb = southprojects.Utils.createElement("tbody", t);
	var tr = southprojects.Utils.createElement("tr", tb);
	var td = southprojects.Utils.createElement("td", tr);
	var is_list;
	var tmp;
	var i = li.firstChild;
	var has_icon = false;

	t.className = "tree-table";
	t.cellSpacing = 0;
	t.cellPadding = 0;
	td.className = "label";
	item.className = li.className + " tree-item";
	item.__msh_item = id;
	item.__msh_tree = this.id;
	item.__msh_parent = parent.__msh_treeid;

	if (afterNode) { //A child node from the same parent is sent in to let the new child node inserted before it.
		parent.insertBefore(item, afterNode); //New item inserted before the 'afterNode'
	}

	while (i) {
		is_list = i.nodeType == 1 && /^[ou]l$/i.test(i.tagName.toLowerCase());

		if (i.nodeType != 1 || !is_list) {
			if (i.nodeType == 3) {
				// remove whitespace, it seems to cause layout trouble
				tmp = i.data.replace(/^\s+/, '');
				tmp = tmp.replace(/\s+$/, '');
				li.removeChild(i);

				if (tmp) {
					i = southprojects.Utils.createElement("span");
					i.className = "label";
					i.innerHTML = tmp;
					if(this.config.labelDblClick){
						i.ondblclick = southprojects.Tree.onItemToggle;
					} else {
						i.onclick = southprojects.Tree.onItemToggle;
					}
					td.appendChild(i);
				}
			} else if (i.tagName.toLowerCase() == 'img') {
				this.item_addIcon(item, i);
				has_icon = true;
			} else {
				if(this.config.labelDblClick){
					i.ondblclick = southprojects.Tree.onItemToggle;
				} else {
					i.onclick = southprojects.Tree.onItemToggle;
				}

				td.appendChild(i);
			}

			i = li.firstChild;

			continue;
		}

		if (is_list) {
			this.item_addIcon(item, null);

			var np;
			if (afterNode != null) {
				np = southprojects.Utils.createElement("div");
				parent.insertBefore(np, afterNode); //New item inserted before the 'afterNode'
			} else {
				np = southprojects.Utils.createElement("div", item.parentNode);
			}

			np.__msh_item = id;
			np.className = "tree";

			if (next_li){
				np.className += " tree-lined";
			}

			item.__msh_subtree = this.createTree(i, np, level + 1);

			if ((this.config.initLevel !== false && this.config.initLevel <= level) ||
				(this.config.compact && !/(^|\s)expanded(\s|$)/i.test(li.className))
				|| /(^|\s)collapsed(\s|$)/i.test(li.className)
			){
				item.className += " tree-item-collapsed";
				item.__msh_state = false;
				this.toggleItem(id, false, true);
			} else {
				item.className += " tree-item-expanded";
				this.toggleItem(id, true, true);
				item.__msh_state = true;
			}
			
			if (/(^|\s)selected(\s|$)/i.test(li.className)){
				this.selectedItem = item;
			}

			break;
		}
	}

	if (!has_icon){
		this.item_addDefaultIcon(item, this.config.defaultIcons);
	}

	return item;
};

/** 
 * Call this function to create a html element with the optional element type specified. 
 * By default, it is a <LI> element. 
 * @param html [string, optional] -- html of the node; may include <UL>, <LI> elements; user is responsible for the content of the html 
 * @param type [string, optional] -- type of the node to be created  
 */ 
southprojects.Tree.prototype.makeNode = function(html, type) { 
        if (!type) { 
           type = "li"; //Make it a <LI> node if the type is not specified.      
        } 
        var node = southprojects.Utils.createElement(type); 
        if (html) {  
                node.innerHTML = html; //Assign the inner html of the node if it is specified. 
        } 
        return node; 
} 
 
/** 
 * Call to get the parent to append, insert or remove a child either at start or end position or in between two nodes of the (sub-)tree. 
 * 
 * For insert, if the user selects a tree-item that represents the root node of a subtree, then the new child will be inserted outside the subtree,  
 * i.e. exactly before the root node of the subtree. However, if the user selects the node that represents one of the children in the subtree, 
 * then the new child will be inserted in the subtree under the root. 
 * For remove, if the user selects the root node of a subtree, the root node including its children will be removed. If the user selects a node that is not 
 * a root node of a subtree, then only that node will be deleted. 
 * In the implementation, the p will first retrieve the subtree from Tree.trees. But this is just the object that encapsulates the subtree, not the root of the 
 * entire subtree. For insert and remove,  
 * @param id     [String] -- id of the parent the new child will be added to or inserted before/at. 
 * @param mode   [String optional] -- "I" is for inserting a child node. "R" is for removing a child node.  
 */ 
southprojects.Tree.prototype.getParent = function(id, mode) { 
	var parent = null; 
	for(var i in this.trees) { 
		//id sent in may be name of the top tree or one of the tree item's or subtree parent's name  
		if (this.trees[i].__msh_treeid == id || this.trees[i].__msh_item == id){ 
			parent = this.trees[i]; //Get the body of a subtree 
			break; 
		} 
	} 
	//For inserting a new child before the referece child, the reference child should be the tree item,  
	//not the subtree under it (in case it has tree nodes under it). This is because inserting a new child  
	//should be 'before' the subtree (if the tree item has tree nodes under it), not inside or become part of the subtree. 
	if(mode != null && (mode.toUpperCase() == "I" || mode.toUpperCase() == "R")){  //At this point, if p not null, then it must be body of the subtree. So, get the tree item (root node of the subtree) instead.  
		//Otherwise id doesn't refer to a subtree. In this case, get the tree item instead. 
		if (parent != null) { //A subtree (not include its root) has been retrieved. That means the insertion or removal operation got to be performed before or at the root of the subtree. This warrants the retrival of the root of the subtree. 
			if (parent.className != this.top_parent.className) //As long as p is not the top parent, get the previous sibling or node of p. The previous sibling will be the root of the subtree. 
				parent = parent.previousSibling; 
		} else if(id){
			parent = this.items[id]; //If no subtree is retrieved, then it must an item node, then get it from array this.items. 
		}
	} 
	
	if(!parent && id) { //If the node matching the id still cannot be found, then look into each item under a subtree  
		parent = this.items[id]; 
	} 
	
	return parent;   
} 
 
/** 
 * Append a child to the start or end of the given parent. 
 * 
 * @param parent   [HTMLElement] -- reference to a tree node (either as a node at top level or at a subtree level) the new child going to be appended to in the tree. 
 * @param newChild [HTMLElement] -- reference to an HTML element created of type LI, to which futher HTML elements such UL and LI can be included to generate subtrees. 
 * @param atStart  [boolean, optional] -- true if the child going to be added at the start of the parent.  
 */ 
southprojects.Tree.prototype.appendChild = function(parent, newChild, atStart) { 
	atStart = (atStart == true); 
	var isFirst = false;
    // Abort operation when either parent/child is empty or the child node is 
    //   already added to the tree. 
	if (
		parent == null || 
		newChild == null || 
		typeof(parent) == "undefined" ||
		typeof(newChild) == "undefined" ||
		this.items[newChild.id]
	){
		return;
	}     

	var item = null; 

	if(parent.firstChild == null){
		atStart = false;
		isFirst = true;
	}
	
	if (atStart) { //Append new child before first child of the parent 
		item = this.createItem(newChild, parent, parent.firstChild.nextSibling, parent.__msh_level, parent.firstChild); 
	} else { //Append new child after last child of the parent 
		item = this.createItem(newChild, parent, null, parent.__msh_level); 
	} 

	// After adding a child, re-draw tree lines that connect the new child to 
	//   the tree. This is necessary because the tree structure has been 
	//   changed due to addition or insertion of a new child node. 
	if (item) { //Child added has no subtree 
		var this_node = null; 
		var next_node = null; 
		var prev_node = null; 
		var subtree   = false; 
                 
		if (atStart) { //Child appended at start of the tree 
			this_node = parent.childNodes[0]; //Always the first node regardless of whether has a subtree under it 
			
			if (item.__msh_subtree==null) { //New child appended has no subtree 
				next_node = parent.childNodes[1]; //Next node will be the second node - the one after the new child node. 
			} else { //New child appended has a subtree 
				next_node = parent.childNodes[2]; //Next node will be the third node - two nodes after the new child node 
			} 
		} else { //Child appended at end of the tree 
			//Get the appropriate tree node the new node going to attach to 
			if (!item.__msh_subtree) { //Child appended has no subtree 
				this_node = parent.childNodes[parent.childNodes.length-1]; //Last node 

				if(parent.childNodes.length > 1){
					prev_node = parent.childNodes[parent.childNodes.length-2]; 
					subtree = (prev_node.className != null && prev_node.className == "tree"); 
					
					if (subtree) { //prev_node is a not a tree item, it has a subtree 
						prev_node.className += " tree-lined"; //Add vertical tree line to the tree 
						prev_node = parent.childNodes[parent.childNodes.length-3]; //Get its parent instead  
					} 
				}
			} else { 
				this_node = parent.childNodes[parent.childNodes.length-2]; //Second last node 
				
				if(parent.childNodes.length >= 3){
					prev_node = parent.childNodes[parent.childNodes.length-3]; 
					subtree = (prev_node.className != null && prev_node.className == "tree"); 
					
					if (subtree) { //prev_node is a not a tree item, it has a subtree 
						prev_node.className += " tree-lined"; //Add vertical tree line to the tree 
						prev_node = parent.childNodes[parent.childNodes.length-4]; //Get its parent instead 
					} 
				}
			} 
		} 
                 
		//Draw tree lines between the child and the parent 
		if (this_node) { //Make sure the child is in the parent (sub-)tree 
			this_node.className = this_node.className.replace(/ tree-lines-./g, ""); 

			if(isFirst && parent == this.top_parent){
				this_node.className += " tree-lines-s"; 
			}if (atStart) { 
				this_node.className += " tree-lines-t"; 

				if (next_node) { 
					next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 
					next_node.className += " tree-lines-c"; 
				} 
			} else { 
				this_node.className += " tree-lines-b"; 

				if (prev_node) { 
					if(prev_node.className.match(/tree-lines-s/)){
						prev_node.className = prev_node.className.replace(/ ?tree-lines-./g, ""); 
                                          
						prev_node.className += " tree-lines-t"; 
					} else {
						prev_node.className = prev_node.className.replace(/ ?tree-lines-./g, ""); 
                                          
						prev_node.className += " tree-lines-c"; 
					}
				} 
			} 
		} 

		return item;
	} 
};  
 
/** 
 * A new child can be inserted between two nodes/children or before one node /children at the same tree level.  
 * Inserting the new child before the first node of a tree is allowed but not allowed  
 * after the last node of the tree, i.e. end of the tree, except before the last node. 
 * @param newChild [HTMLElement] -- New child node to be inserted into the tree 
 * @param refChild [HTMLElement] -- Reference to the child node which the new child node will be inserted before 
 */ 
southprojects.Tree.prototype.insertBefore = function(newChild, refChild) { 
	//Abort operation when either newChild/refChild is empty or the child node is already added to the tree..   
	if(
		newChild == null || 
		refChild == null || 
		typeof(newChild) == "undefined" ||
		typeof(refChild) == "undefined" || 
		this.items[newChild.id]
	){
		return; 
	} 

	var parent = refChild.parentNode; 
	var item = this.createItem(newChild, parent, parent.firstChild.nextSibling, parent.__msh_level, refChild); 
	var nodeBefore = false, nodeAfter = false; 
	var next_node  = null; 
 
	if(item.previousSibling){
		nodeBefore = true; 
	}

	if(item.nextSibling){
		nodeAfter = true;
	}
 
	item.className = item.className.replace(/ tree-lines-./g, ""); 

	if (nodeBefore && nodeAfter){
		item.className += " tree-lines-c";
	} else if (nodeBefore){
		item.className += " tree-lines-b"; 
	} else if (nodeAfter) {
		//Insert new child at the start of the tree. 
		item.className += " tree-lines-t"; 

		//Since the node now after the new child was the first node before, the tree line from it is not connected to the new child. 
		//So it needs to redraw the tree line for the second node (formerly the first node in the tree). 
		if (item.className.indexOf("tree-item-more tree-item")>-1) { //New child has a subtree under it. 
			next_node = item.nextSibling.nextSibling; //Get the next node at the 'same' level as the new child, skip the new child's subtree. 
		} else { 
			next_node = item.nextSibling; //Get the next node at the 'same' level. 
		} 

		next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 

		//To find out whether next node has next node after it. 
		if (next_node.className.indexOf("tree-item-more tree-item")>-1) { //Next node has a subtree 
			if (next_node.nextSibling.nextSibling != null){
				next_node.className += " tree-lines-c";  
			} else {
				next_node.className += " tree-lines-b"; 
			}
		} else { //Next node is an tree item. 
			if (next_node.nextSibling != null){
				next_node.className += " tree-lines-c";  
			} else {
				next_node.className += " tree-lines-b"; 
			}
		} 
	} 
}; 
 
/** 
 * An old child node in the tree can be removed at any level. 
 * If the old child node happens to be the root of a subtree,  
 * then the entire subtree including child node(s) under the root node will be removed. 
 * If the old child node is just an item node without any child node under it as children,  
 * then the only node removed is the old child node. 
 * Once an item node or a subtree is removed, the node/subtree before and/or after the old  
 * node will be joined together, sometimes with tree lines redrawn. 
 * The only limitations are that the root node of the top tree is not permitted for removal; 
 * the first child node found will be removed if there are more than one tree node with the same id. 
 * @param oldChild [HTMLElement] -- Old child node to be removed from the tree where it can  
 * be an item node without children or a subtree including the old child node as the root. 
 */ 
southprojects.Tree.prototype.removeChild = function(oldChild) { 
 	if (
		oldChild == null || 
		typeof(oldChild) == "undefined"
	){
		//No child to remove 
		return;
	} else if (oldChild.className == this.top_parent.className) {
		//Top root node is not allowed 
		alert("Removing root node not allowed.") 
		return;  
	} 
         
	//Get the child node's parent node 
	var p = oldChild.parentNode; 
         
	//Remove node(s) - check if the old child represents a root node of a subtree. If it does, remove the child nodes in the subtree as well as the root node. 
	//If it does not represents a root node of a subtree, just remove it from the parent it attached to. 
	//Clean up - remove the subtree's id from this.trees or the item node's id from this.items. 
	if (oldChild.__msh_item && oldChild.__msh_tree && oldChild.__msh_parent) { //Make sure all common attributes of root node (of a subtree) or item node are there. 
		var prev_node = oldChild.previousSibling; 
		var next_node = oldChild.nextSibling; 
		var hasPrevNode = false; 
		var hasNextNode = false; 
            
		//Find out if there is a node before the old node at the same tree level. 
		if (prev_node) { 
			//If the node before the old child is the entire subtree at next level, get the root node of that subtree. 
			//The root node will be at the same level as the old child. Otherwise, the node before the old child is an item node. 

			if (prev_node.__msh_treeid) { //The node before the old child is a tree consists of all tree nodes in a subtree. 
			    //Get the root node of that subtree. It should have the same level as the old child. 
				prev_node = prev_node.previousSibling; 
			} 

			hasPrevNode = true; 
		} else { //No child before the old child at the same level. 
			//Check if the old child has a parent node such as root of a subtree and the parent node is not the top of the tree 
			if (oldChild.parentNode && oldChild.parentNode.className != this.top_parent.className) {                 
			    //This is needed in order to 'not' change any tree line for the child node(s) under a subtree.  
				hasPrevNode = true; 
			} 
		} 
      
		//Find out if there is a node after the old node at the same tree level. 
		if (next_node) { 
			if (oldChild.__msh_subtree) { //Old child is a subtree. So old node is the root of a subtree. Get the node after the subtree then. 
			    //Get the node after the old node at the same level. 
				next_node = next_node.nextSibling; 
				
				if (next_node) {
					//There is a next node at same level as the old node. 
					hasNextNode = true; 
				}
		} else {
			//Old child is an item node. So old node is an item node, not a subtree. Old node and next node are at the same level. 
			hasNextNode = true; //There is a next node at same level as the old node. 
		} 
	} //else no next node 
       
	if (oldChild.__msh_subtree) { //Root node of a subtree; has child node(s) under it. 
	    //Try to get the whole subtree of the old child node 
		var subtreeNode = oldChild.nextSibling; 
          
		if (subtreeNode && oldChild.__msh_subtree == subtreeNode.__msh_treeid) { //Subtree node exists 
			for (var i = 0; i < subtreeNode.childNodes.length; i++) { //Loop through each children of the subtree  
				if (subtreeNode.childNodes[i]){
					//and delete the corresponding object in this.items 
					delete this.items[subtreeNode.childNodes[i].__msh_item];
				}
            } 

			//Remove the corresponding item in this.items for the root of the subtree 
			delete this.items[subtreeNode.__msh_item]; 

			//Remove the whole subtree 
			p.removeChild(subtreeNode); 
		} 
		
		//Remove the corresponding subtree in this.trees 
		delete(this.trees[oldChild.__msh_subtree]); 

		//Remove the child node from its parent
		p.removeChild(oldChild); 
	} else {
		//An item node, not a subtree 
		delete this.items[oldChild.__msh_item]; //Remove the corresponding item in this.items for the item node 
		p.removeChild(oldChild); //Remove the child node from its parent 
	} 

	//Re-draw tree lines nodes/subtrees between removed node if necessary 
	//If there is a previous node and next node between the old node, there is no need to redraw thr tree lines 
	//as the previous and next node will become joined together after the old node is removed. 
	//If 'before' removal of the old node, there is a next node after the old node but no previous node before  
	//the old node, then it needs to redraw the tree line of the next node. The next node thus become the first node of the tree. 
	if (!hasPrevNode && hasNextNode) { 
		if (next_node) { 
			next_node.className = next_node.className.replace(/ tree-lines-./g, ""); 
			next_node.className += " tree-lines-t"; 
		} 
	} else if (hasPrevNode && !hasNextNode) { 
		if (prev_node) { 
			if (prev_node.__msh_subtree) { 
				prev_node.nextSibling.className = prev_node.nextSibling.className.replace(/ tree-lined/g, ""); 
			} //else not a subtree  

			prev_node.className = prev_node.className.replace(/ tree-lines-./g, ""); 
			prev_node.className += " tree-lines-b"; 
		} 
	}
} 
}; 

/**
 * \internal This function adds a TD element having a certain class attribute
 * which helps having a tree containing icons without defining IMG tags for
 * each item.  The class name will be "tgb icon className" (where "className"
 * is the specified parameter).  Further, in order to customize the icons, one
 * should add some CSS lines like this:
 *
 * \code
 *  div.tree-item td.customIcon {
 *    background: url("themes/img/fs/document2.png") no-repeat 0 50%;
 *  }
 *  div.tree-item-expanded td.customIcon {
 *    background: url("themes/img/fs/folder-open.png") no-repeat 0 50%;
 *  }
 *  div.tree-item-collapsed td.customIcon {
 *    background: url("themes/img/fs/folder.png") no-repeat 0 50%;
 *  }
 * \endcode
 *
 * As you can see, it's very easy to customize the default icons for a normal
 * tree item (that has no subtrees) or for expanded or collapsed items.  For
 * the above example to work, one has to pass { defaultIcons: "customIcon" } in
 * the tree configuration object.
 *
 * This function does nothing if the \b className parameter has a false logical
 * value (i.e. is null).
 *
 * @param item [HTMLElement] -- reference to the DIV element holding the item
 * @param className -- a string containing the additional class name
 */
southprojects.Tree.prototype.item_addDefaultIcon = function(item, className) {
	if (!className){
		return;
	}

	var last_td = item.firstChild.firstChild.firstChild.lastChild, td;
	var td = southprojects.Utils.createElement("td");
	
	td.className = "tgb icon " + className;
	td.onclick = southprojects.Tree.onItemToggle;
	last_td.parentNode.insertBefore(td, last_td);
};

/** 
 * \internal This function does different things, depending on whether the \b
 * img parameter is passed or not.  If the \b img is passed, then this function
 * adds it as an icon for the given item.  If not passed, this function creates
 * a "+/-" button for the given item.
 * 
 * @param item [HTMLElement] -- reference to the DIV holding the item elements
 * @param img [HTMLImgElement, optional] -- reference to an IMG element; normally one found in the <LI>
 */
southprojects.Tree.prototype.item_addIcon = function(item, img) {
	// PROFILE
	if (this.config.d_profile){
		++profile.icons;
	}
	
	var last_td = item.firstChild.firstChild.firstChild, td;
	last_td = img ? last_td.lastChild : last_td.firstChild;
	
	if (!img || !item.__msh_icon) {
		td = southprojects.Utils.createElement("td");
		td.className = "tgb " + (img ? "icon" : "minus");
		last_td.parentNode.insertBefore(td, last_td);
		td.onclick = southprojects.Tree.onItemToggle;
	} else {
		td = item.__msh_icon;
		img.style.display = "none";
	}
	
	if (!img) {
		td.innerHTML = "&nbsp;";
		item.className += " tree-item-more";
		item.__msh_expand = td;
	} else {
		td.appendChild(img);
		item.__msh_icon = td;

		if(td.childNodes.length == 1){
			item.__msh_icon_expanded = img;
		} else {
			item.__msh_icon_collapsed = img;
		}
	}
};

/** 
 * This function gets called from a global event handler when some item was
 * clicked.  It selects the item and toggles it if it has a subtree (expands or
 * collapses it).
 * 
 * @param item_id [string] -- the item ID
 */
southprojects.Tree.prototype.itemClicked = function(item_id, expand) {
	this.selectedItem = this.toggleItem(item_id, null, expand);
	southprojects.Utils.writeCookie("southprojects.Tree-" + this.config.saveId, this.selectedItem.__msh_item, null, '/', 7);

	if (this.config.hiliteSelectedNode && this.selectedItem){
		southprojects.Utils.addClass(this.selectedItem, "tree-item-selected");
	}

	this.onItemSelect(item_id);
};

/** 
 * This function toggles an item if the \b state parameter is not specified.
 * If \b state is \b true then it expands the item, and if \b state is \b false
 * then it collapses the item.
 * 
 * @param item_id [string] -- the item ID
 * @param state [boolean, optional] -- the desired item state
 * @param expand [boolean, optional] -- should expand/collapse if it is a branch
 * 
 * @return a reference to the item element if found, null otherwise
 */
southprojects.Tree.prototype.toggleItem = function(item_id, state, expand) {
	if (item_id) {
		var stateDefined = false;

		if(this.config.saveState) {
			stateDefined = true;
		}

		if (this.config.hiliteSelectedNode && this.selectedItem){
			southprojects.Utils.removeClass(this.selectedItem, "tree-item-selected");
		}
		var item = this.items[item_id];

		if (typeof(state) == "undefined" || state == null) {
			state = !item.__msh_state;
			stateDefined = false;
		}

		// Expand is true when the '+' or '-' are clicked.
		// If expandOnLabel is true we will expand even when the label is clicked
		// We will also expand when the state is explictally passed
		if (
			(expand || this.config.expandOnLabel || stateDefined)
		){
			var subtree = this._getTree(item.__msh_subtree, this.creating_now);

			if (subtree) {
				subtree.style.display = state ? "block" : "none";
				southprojects.Utils.removeClass(item, "tree-item-expanded");
				southprojects.Utils.removeClass(item, "tree-item-collapsed");
				southprojects.Utils.addClass(item, state ?
					"tree-item-expanded" : "tree-item-collapsed");
			}

			var img = item.__msh_expand;

			if (img){
				img.className = "tgb " + (state ? "minus" : "plus");
			}

			item.__msh_state = state;
			img = item.__msh_icon;

			if(img){
				if(item.__msh_icon_expanded){
					item.__msh_icon_expanded.style.display = 'none';
				}

				if(item.__msh_icon_collapsed){
					item.__msh_icon_collapsed.style.display = 'none';
				}

				if(item[state ? "__msh_icon_expanded" : "__msh_icon_collapsed"]){
					item[state ? "__msh_icon_expanded" : "__msh_icon_collapsed"].style.display = 'block';
				}
			}

			if (this.config.compact && state) {
				var a = this._getTree(item.__msh_parent).__msh_items;
				for (var i = a.length; --i >= 0;)
					if (a[i] != item_id)
						this.toggleItem(a[i], false);
			}

			if(/zpLoad(JSON|HTML)=([^ $]*)/.test(item.className) && state){
				var dataType = RegExp.$1;
				var url = RegExp.$2;

				item.className = item.className.replace(/zpLoad(JSON|HTML)=([^ $]*)/g, "");
				
				var loadingLabel = this.appendChild(this.getParent(item.__msh_item), this.makeNode("...loading..."), true);

				var errorFunc = function (objError){
					southprojects.Log({description: objError.errorDescription});

					item.className += "zpLoad" + dataType + "=" + url;

					self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
				}

				var self = this;

				if(dataType == "JSON"){
					southprojects.Transport.fetchJsonObj({
						url: url,
						method: "GET",
						onLoad: function(objResponse){
							var items = self.config.jsonLoadCallback(objResponse, item);

							for(var ii = 0; ii < items.length; ii++){
								self.appendChild(self.getParent(item.__msh_item), items[ii]);
							}

							self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
						},
						onError : errorFunc
					});
				} else if(dataType == "HTML" || dataType == "XML"){
					southprojects.Transport.fetchXmlDoc({
						url: url,
						method: "GET",
						onLoad: function(objResponse){
							if(objResponse == null || objResponse.documentElement == null){
								return null;
							}

							var items = southprojects.Tree.defaultProcessXml(objResponse, item);

							for(var ii = 0; ii < items.length; ii++){
								self.appendChild(self.getParent(item.__msh_item), items[ii]);
							}
							
							self.removeChild(self.getParent(loadingLabel.__msh_item, "R"));
						},
						onError : errorFunc
					});
				}
			}
		}

		return item;
	}

	return null;
};

/**
 * Call this function to collapse all items in the tree.
 */
southprojects.Tree.prototype.collapseAll = function() {
	for (var i in this.trees){
		this.toggleItem(this._getTree(i).__msh_item, false, true);
	}
};

/**
 * Expands tree up to given level.
 * @param level [int] - level to expand. zero-based.
 */
southprojects.Tree.prototype.collapseToLevel = function(level) {
	for (var i in this.trees) {
		if(this._getTree(i).__msh_level > level){
			this.toggleItem(this._getTree(i).__msh_item, false, true);
		}
	}
};

/**
 * Call this function to expand all items in the tree.
 */
southprojects.Tree.prototype.expandAll = function() {
	for (var i in this.trees) {
		this.toggleItem(this._getTree(i).__msh_item, true, true);
	}
};

/**
 * Expands tree up to given level.
 * @param level [int] - level to expand. zero-based.
 */
southprojects.Tree.prototype.expandToLevel = function(level) {
	for (var i in this.trees) {
		if(this._getTree(i).__msh_level <= level){
			this.toggleItem(this._getTree(i).__msh_item, true, true);
		}
	}
};

/**
 * Call this function to toggle all items in the tree.
 */
southprojects.Tree.prototype.toggleAll = function() {
	for (var i in this.trees) {
		this.toggleItem(this._getTree(i).__msh_item);
	}
};

/** 
 * Call this function to synchronize the tree to a given item.  This means that
 * all items will be collapsed, except that item and the full path to it.
 * 
 * @param item_id [string] -- the ID of the item to sync to.
 */
southprojects.Tree.prototype.sync = function(item_id) {
	var item = this.items[item_id];
	
	if (item) {
		this.collapseAll();
		this.selectedItem = item;
		var a = [];
	
		while (item.__msh_parent) {
			a[a.length] = item;
			var pt = this._getTree(item.__msh_parent);
		
			if (pt.__msh_item){
				item = this.items[pt.__msh_item];
			} else {
				break;
			}
		}

		for (var i = a.length; --i >= 0;){
			this.toggleItem(a[i].__msh_item, true);
		}

		southprojects.Utils.addClass(this.selectedItem, "tree-item-selected");
	}
};

/** 
 * Destroys the tree. Removes all elements. Does not destroy the southprojects.Tree
 * object itself (actually there's no proper way in JavaScript to do that).
 */
southprojects.Tree.prototype.destroy = function() {
	var p = this.top_parent;
	p.parentNode.removeChild(p);
};

/** 
 * \internal This function is used when "dynamic initialization" is on.  It
 * retrieves a reference to a subtree if already created, or creates it if it
 * wasn't yet and \b dont_call is \b false (returns null in that case).
 * 
 * @param tree_id [string] the ID of the subtree
 * @param dont_call [boolean] pass true here if you don't want the subtree to be created
 * 
 * @return reference to the tree if it was found or created, null otherwise.
 */
southprojects.Tree.prototype._getTree = function(tree_id, dont_call) {
	var tree = this.trees[tree_id];

	if (typeof(tree) == "function") {
		if (dont_call){
			tree = null;
		} else {
			tree();
			tree = this.trees[tree_id];
			tree.__msh_state = false;
		}
	}

	return tree;
};

/**
* \internal Default function for processing received HTML/XML object
* @param objResponse [object] JSON object with response
*/
southprojects.Tree.defaultProcessXml = function(objResponse){
    if(objResponse == null || objResponse.documentElement == null){
    	return null;
    }

	function xml2dom(node){
		if(node.nodeType == 3){
			return document.createTextNode(node.nodeValue);
		}

		if(node.nodeType != 1){
			return null;
		}
		
		var el = southprojects.Utils.createElement(node.nodeName);

		for (var ii = 0; ii < node.attributes.length; ii++){
			var attr = node.attributes[ii];
		
			if(attr.name.toLowerCase() == "class"){
				el.className = node.getAttribute(attr.name);
			} else {      
				el.setAttribute(attr.name, node.getAttribute(attr.name));
			}
		}

		if(node.hasChildNodes()){
			for(var ii = 0; ii < node.childNodes.length; ii++){
				var childNode = xml2dom(node.childNodes[ii]);
			
				if(childNode != null){
					el.appendChild(childNode);
				}
			}
		}

		return el;
	}

	var cNodes = objResponse.documentElement.childNodes;

	var arr = [];

	for(var jj = 0; jj < cNodes.length; jj++){
		arr.push(xml2dom(cNodes[jj]));
	}

	return arr;
}

/**
* \internal Function to load tree content on tree load
* @param objResponse [objResponse] Server response
*/
southprojects.Tree.prototype.loadDataHtml = southprojects.Tree.prototype.loadDataHtml = function(objResponse){
    if(objResponse == null || objResponse.documentElement == null){
    	return null;
    }

	var items = southprojects.Tree.defaultProcessXml(objResponse, this.top_parent);
	
	if(items == null || items.length == 0){
		return null;
	}

	var ul = document.createElement("UL");
	
	for(var ii = 0; ii < items.length; ii++){
		ul.appendChild(items[ii]);
	}

	this.createTree(ul, this.top_parent, 0);
}

/**
* \internal Default function for processing received JSON object
* @param objResponse [object] JSON object with response
*/
southprojects.Tree.defaultProcessJson = function(objResponse){
	if(objResponse == null){
		return null;
	}

	function json2html(json){
		if(typeof(json) == 'string'){
			json = [json];
		}

		var res = southprojects.Utils.createElement("LI");

		res.innerHTML = json[0];

		if(json[1]){
			var ul = southprojects.Utils.createElement("UL");
			
			for(var ii = 0; ii < json[1].length; ii++){
				ul.appendChild(json2html(json[1][ii]));
			}

			res.appendChild(ul);
		}

		return res;
	}

	var arr = [];

	for(var ii = 0; ii < objResponse.length; ii++){
		arr.push(json2html(objResponse[ii]));
	}

	return arr;
}

/**
* \internal Function to load tree content on tree load
* @param objResponse [objResponse] Server response
*/
southprojects.Tree.prototype.loadDataJson = function(objResponse){
	if(objResponse == null){
		return null;
	}

	var items = this.config.jsonLoadCallback(objResponse, this.top_parent);

	var ul = document.createElement("UL");

	for(var ii = 0; ii < items.length; ii++){
		ul.appendChild(items[ii]);
	}

	for(var ii = 0; ii < items.length; ii++){
		this.appendChild(this.top_parent, items[ii]);
	}
}

// CUSTOMIZABLE EVENT HANDLERS; default action is "do nothing"

/** 
 * Third party code can override this member in order to add an event handler
 * that gets called each time a tree item is selected.  It receives a single
 * string parameter containing the item ID.
 */
southprojects.Tree.prototype.onItemSelect = function() {};

// GLOBAL EVENT HANDLERS (to workaround the stupid Microsoft memory leak)

/** 
 * \internal This is a global event handler that gets called when a tree item
 * is clicked.  Don't override! ;-)
 */
southprojects.Tree.onItemToggle = function() {
	var item = this;
	var body = document.body;
	var expand = false;

	if(/tgb (minus|plus)/.test(this.className)) {
		expand = true;
	}
	
	while (item && item !== body && !/tree-item/.test(item.className)){
		item = item.parentNode;
	}

	southprojects.Tree.all[item.__msh_tree].itemClicked(item.__msh_item, expand);
};

southprojects.Utils.addEvent(window, 'load', southprojects.Utils.checkActivation);
