////////////////////////////////////////////////////////////////////////////////////////////  PROTOTYPE MODIFICATIONS
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); }
Array.prototype.map = function(callbackFunction) { 
	for (var i = 0; i < this.length; i ++) {
		callbackFunction(this[i]);
	}
}


////////////////////////////////////////////////////////////////////////////////////////////  CLASS DEFINITIONS: fsbTree
function fsbTree () {
	this.definitions = [  	[ 1, 0, 0, "<Caption>" ],
							[ 2, 1, 0, "<Caption>" ],
							[ 3, 2, 0, "<Caption>" ],
							[ 4, 0, 0, "<Caption>" ],
							[ 5, 4, 0, "<Caption>" ],
							[ 6, 5, 0, "<Caption>" ], 
							[ 7, 5, 0, "<Caption>" ], 
							[ 8, 5, 0, "<Caption>" ], 
							[ 9, 5, 0, "<Caption>" ], 
							[ 10, 5, 0, "<Caption>" ], 
							[ 11, 5, 0, "<Caption>" ], 
							[ 12, 5, 0, "<Caption>" ],
							[ 13, 5, 0, "<Caption>" ],  
							[ 14, 5, 0, "<Caption>" ], 
							[ 15, 14, 0, "<Caption>" ]  ];

	this.closed_image = "closed.gif";
	this.open_image = "open.gif";
	this.leaf_image = "leaf.gif";
	this.next_available_id = 16;
	this.selected_tree_element = null;
	this.treeLayerId = "tree_layer";
	this.nodeElementPrefix = "node_";
	this.nodeImageElementPrefix = "node_image_";
	this.nodeCaptionElementPrefix = "caption_";
	this.nodeCheckboxElementPrefix = "check_";
	this.nodeRadioElementPrefix = "radio_";
	this._version = "0.29";
	this.collapsible = true;
	
	this._INVALID_NODE_ID = -1;
	this._INVALID_INDEX = -2;
	this._INVALID_NODE_OBJECT = -3;
	
	/////////////////////////////////////////////////////////////////////////////  Data Manipulation Members.
	///////////////////////////// CLASS MEMBERS: fsbTree.getNodeIdByObject;
	this.getNodeIdByObject = function (nodeObject) {
		if (nodeObject) {
			nodeId = parseInt(nodeObject.id.substring(this.nodeElementPrefix.length));
			return nodeId;
		}
		
		return this._INVALID_NODE_OBJECT;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getNodeById;
	this.getNodeByObject = function (nodeObject) {
		if (nodeObject) {
			nodeId = parseInt(nodeObject.id.substring(this.nodeElementPrefix.length));
			return this.getNodeById(nodeId);
		}
		
		return this._INVALID_NODE_OBJECT;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getNodeById;
	this.getNodeById = function (id) {
		for (var i = 0; i < this.definitions.length; i ++)
			if (this.definitions[i][0] == id)
				return this.definitions[i];
		
		return this._INVALID_NODE_ID;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getNodeIndexById;
	this.getNodeIndexById = function (id) {
		for (var i = 0; i < this.definitions.length; i ++)
			if (this.definitions[i][0] == id)
				return i;
		
		return this._INVALID_INDEX;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getNodeElementById;
	this.getNodeElementById = function (id) {
		//alert(this.nodeElementPrefix + id);
		var x = document.getElementById(this.nodeElementPrefix + id);
		return x;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getNodeImageElementById;
	this.getNodeImageElementById = function (id) {
		var x = document.getElementById(this.nodeImageElementPrefix + id);
		return x;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getCaptionElementById;
	this.getCaptionElementById = function (id) {
		var x = document.getElementById(this.nodeCaptionElementPrefix + id);
		return x;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getCheckElementById;
	this.getCheckElementById = function (id) {
		var x = document.getElementById(this.nodeCheckboxElementPrefix + id);
		return x;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getRadioElementById;
	this.getRadioElementById = function (id) {
		var x = document.getElementById(this.nodeRadioElementPrefix + id);
		return x;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.hasChildren;
	this.hasChildren = function (id) {
		for (var i = 0; i < this.definitions.length; i++)
			if (this.definitions[i][1] == id) return true;
		
		return false;
	}

	/////////////////////////////////////////////////////////////////////////////  Node Manipulation Members.
	this.selectionHandler = function (nodeObject) {
		// select the element.
		if (this.selected_tree_element) {
			this.selected_tree_element.style.backgroundColor = "";
		}
		
		this.selected_tree_element = nodeObject;
		
		if (this.selected_tree_element) {
			this.selected_tree_element.style.backgroundColor = "#AAA";
		}
	}
	
	///////////////////////////// CLASS MEMBERS: fsbTree.toggleTreeNode;
	this.toggleTreeNode = function (nodeObject, parentDefIndex, skipSelection) {
		//alert(nodeObject);
		if (nodeObject) {
			// get the node's id value.
			var nodeId = parseInt(nodeObject.id.substring(this.nodeElementPrefix.length));
			var n_parentDefIndex = -1;
	
			var n = this.getNodeById(nodeId);
			n_parentDefIndex = this.getNodeIndexById(nodeId);
			
			if (!this.collapsible) {
				if (!skipSelection) {
					this.selectionHandler(nodeObject);
					return;
				}
			}
			
			if (parentDefIndex == -1) {
				// if this element is selected, change its state.
				if ((this.selected_tree_element == nodeObject) || skipSelection) {
					if (n[2] == 0) n[2] = 1;
					else n[2] = 0; 

					// change the image for this node.
					var imgObject = this.getNodeImageElementById(nodeId);
					local_image_file = imgObject.src.toString().split("/").pop();
					if (imgObject && (local_image_file != this.leaf_image)) {
						imgObject.src = (n[2] == 1)?this.open_image:this.closed_image;
					}
				} else {
					if (!skipSelection) {
						this.selectionHandler(nodeObject);
						return;
					}
				}
			} 

			if (parentDefIndex != -1) {
				// this element conforms to its parent's state.
				var showOrHide = this.definitions[parentDefIndex][2];
				if (showOrHide == 1) {
					nodeObject.style.display = "block";
				} else {
					nodeObject.style.display = "none";
				}
			}
			
			// change the state of all child nodes, and call all child nodes.
			for (var i = 0; i < this.definitions.length; i ++) {
				if (this.definitions[i][1] == nodeId) {
					this.definitions[i][2] = 0; //close all child elements.
					// change the image for all child elements.
					var imgObject = this.getNodeImageElementById(this.definitions[i][0]);
					local_image_file = imgObject.src.toString().split("/").pop();
					if (imgObject && (local_image_file != this.leaf_image)) imgObject.src = this.closed_image;
		
					var child_node = this.getNodeElementById(this.definitions[i][0]);
					if (child_node) this.toggleTreeNode(child_node, n_parentDefIndex, true);
				}
			}
		}
	}
	
	///////////////////////////// CLASS MEMBERS: fsbTree.isVisible;
	this.isVisible = function (id) {
		var n = this.getNodeById(id);
		thisNodeParent = n[1];
	
		// check if this node's parent is visible.
		if (thisNodeParent > 0) {
			var pn = this.getNodeById(thisNodeParent);
			return ((pn[2] > 0) && this.isVisible(thisNodeParent));
		} else {
			// root nodes are always visible.
			return true;
		}
	}	
	
	///////////////////////////// CLASS MEMBERS: fsbTree.selectNode;
	this.selectNode = function (id, forceVisible) {
		if (this.isVisible(id)) {
			var c = this.getNodeElementById(id);
			if (c) {
				this.selectionHandler(c);
				return true;
			}
		} else if (forceVisible) {
			this.expandBranch(id);
			return this.selectNode(id, forceVisible);
		}
		
		return false;
	}	
		
	///////////////////////////// CLASS MEMBERS: fsbTree.expandBranch;
	this.expandBranch = function (id) {
		// locate this node's parent.
		var thisNode = this.getNodeById(id);
		var thisNodeParent = thisNode[1];
		var thisNodeVisible = thisNode[2];
	
		// check if this node has a parent.
		if (thisNodeParent > 0) {
			// expand the parent first.
			this.expandBranch(thisNodeParent);
			
			// check if this node's parent is visible.
			var parentNode = this.getNodeById(thisNodeParent);
			if (parentNode[2] == 0) 
				this.toggleTreeNode(this.getNodeElementById(parentNode[0]), -1, true);
		} else {
			// root nodes are always visible.
			if (thisNodeVisible == 0) {
				this.toggleTreeNode(this.getNodeElementById(id), -1, true);
			}
		}
	}	
	
	///////////////////////////// CLASS MEMBERS: fsbTree.insertSibling;
	this.insertSibling = function (id) {
		var tl = document.getElementById(this.treeLayerId);
		
		this.selectNode(id, true);
		var nodeObject = this.selected_tree_element;
		var nodeId = parseInt(nodeObject.id.substring(this.nodeElementPrefix.length));
		
		if (nodeObject) {
			// clear the current selection.
			this.selectionHandler(null);
			
			// insert the new element.
			x = nodeObject.cloneNode(true);
			var newId = this.next_available_id; 
			tl.insertBefore(x, nodeObject);
			
			// set the new node's id.
			x.id = this.nodeElementPrefix + newId + "";
			this.next_available_id ++;
			
			// select the new element.
			this.selectionHandler(x);
			
			// set DOM properties and adjust childNodes of this element.
			if (x.childNodes) {
				for (var i = 0; i < x.childNodes.length; i ++) {
					if ((thisChildNode = x.childNodes[i]).tagName == "IMG") {
						thisChildNode.id = this.nodeImageElementPrefix + newId;
						thisChildNode.src = this.leaf_image;
					} else if ((thisChildNode = x.childNodes[i]).tagName == "SPAN") {
						thisChildNode.innerHTML = "New Element";
					}
				}
			}
			
			// locate the element to clone and clone it with the new id.
			var n = this.getNodeById(nodeId);
			clonedNode = n.concat();
			clonedNode[0] = newId;
			clonedNode[3] = "New Element";
			this.definitions.push(clonedNode);
		}
	}
	
	///////////////////////////// CLASS MEMBERS: fsbTree.getElementLevel;
	this.getElementLevel = function (id) {
		// locate this node's parent.
		//var thisNode = this.getNodeById(id);
		thisNodeParent = this.getNodeById(id)[1];
	
		if (thisNodeParent > 0)
			return (1 + this.getElementLevel(thisNodeParent));
		else return 1;
	}	
	
	///////////////////////////// CLASS MEMBERS: fsbTree.insertChild;
	this.insertChild = function (id) {
		var tl = document.getElementById(this.treeLayerId);
		
		this.selectNode(id, true);
		var nodeObject = this.selected_tree_element;
		var nodeId = parseInt(nodeObject.id.substring(this.nodeElementPrefix.length));
		
		if (nodeObject) {
			this.selectionHandler(null);
			x = nodeObject.cloneNode(true);
			var newId = this.next_available_id; 
			
			// append in front, and make it a child element.
			if (nodeObject.nextSibling)
				tl.insertBefore(x, nodeObject.nextSibling);
			else 
				tl.appendChild(x);
				
			x.id = this.nodeElementPrefix + newId + "";
			this.next_available_id ++;
	
			this.selectNode(newId, true);
	
			if (nodeObject.childNodes) {
				for (var i = 0; i < nodeObject.childNodes.length; i ++) {
					if ((thisChildNode = nodeObject.childNodes[i]).tagName == "IMG") {
						thisChildNode.src = this.open_image;
					}
				}
			}
			
			if (x.childNodes) {
				for (var i = 0; i < x.childNodes.length; i ++) {
					if ((thisChildNode = x.childNodes[i]).tagName == "IMG") {
						thisChildNode.id = this.nodeImageElementPrefix + newId;
						thisChildNode.src = this.leaf_image;
					} else if ((thisChildNode = x.childNodes[i]).tagName == "SPAN") {
						thisChildNode.innerHTML = "New Child Element";
					}
				}
			}
			
			this.definitions.push([newId, nodeId, 0, "New Element"]);
			this._repositionNode(newId, false);
		} 
	}
	
	///////////////////////////// CLASS MEMBERS: fsbTree.removeNode;
	this.removeNode = function (id) {
		for (var i = 0; i < this.definitions.length; i ++) {
			if (this.definitions[i][0] == id) {
				var indexToRemove = i;
				var parentId = this.definitions[i][1];
				this.expandBranch(id);
				break;
			}
		}
		
		// if this element has children, they must be carried to the 
		// parent of this element.
		for (var i = 0; i < this.definitions.length; i ++) {
			if (this.definitions[i][1] == id) {
				this.moveNodeUp(this.definitions[i][0]);
			}
		}

		// must remove this child from the parent layer.
		var tl = document.getElementById(this.treeLayerId);
		var nodeObject = this.getNodeElementById(id);
		var oldNodeObject = tl.removeChild(nodeObject);
		
		// replace the last element with that of the item to remove, 
		// effectively removing the pesky element.
		this.definitions[indexToRemove] = this.definitions.pop(); 
		this._repositionNode(parentId, false);
		
	}
	
	///////////////////////////// CLASS MEMBERS: fsbTree.moveNodeUp;
	this.moveNodeUp = function (id) {
		// move the node up to the parent of its parent, if applicable.
		var parentId = 0, grandParentId = 0;
		
		var n = this.getNodeById(id);
		parentId = n[1];
		
		if (parentId > 0) {
			var pn = this.getNodeById(parentId);
			grandParentId = pn[1];
		} else grandParentId = 0;
		
		n[1] = grandParentId;
		this._repositionNode(id, true);
	}
	
	///////////////////////////// CLASS MEMBERS: fsbTree._repositionNode;
	this._repositionNode = function (id, cascade) {
		var hasChildren = false;
		
		// perform repositioning for child nodes as well.
		if (cascade)
			for (var i = 0; i < this.definitions.length; i ++)
				if (this.definitions[i][1] == id)
					this._repositionNode(this.definitions[i][0], cascade);
				
		var x = this.getNodeElementById(id);
		var level = this.getElementLevel(id);
		
		if (x)
			if (level > 1) x.style.marginLeft = ((level) * 15) + "px";
			else x.style.marginLeft = "12px";
		
		var newImage = "";

		if (this.hasChildren(id)) {
			var n = this.getNodeById(id);
			if (n[2] == 1) newImage = this.open_image;
			else newImage = this.closed_image;
		} else {
			newImage = this.leaf_image;
		}
		
		var y = this.getNodeImageElementById(id);
		if (y) y.src = newImage;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.setCaption;
	this.setCaption = function (id, newVal) {
		var nc = this.getCaptionElementById(id);
		nc.innerHTML = newVal;
		var n = this.getNodeById(id);
		n[3] = newVal;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getCaption;
	this.getCaption = function (id, newVal) {
		var nc = this.getCaptionElementById(id);
		return nc.innerHTML;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.expandAll;
	this.expandAll = function () {
		for (var i = 0; i < this.definitions.length; i ++) {
			this.definitions[i][2] = 1;
			var n = this.getNodeElementById(this.definitions[i][0]);
			n.style.display = "block";
			this._repositionNode(this.definitions[i][0], false);
		}
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.collapseAll;
	this.collapseAll = function (overideCollapsibleBehavior) {
		if (this.collapsible || overideCollapsibleBehavior)
			for (var i = 0; i < this.definitions.length; i ++) {
				this.definitions[i][2] = 0;
				if (this.definitions[i][1] != 0) {
					var n = this.getNodeElementById(this.definitions[i][0]);
					n.style.display = "none";
				}
				this._repositionNode(this.definitions[i][0], false);
			}
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.getCheckedItems;
	this.getCheckedItems = function () {
		var checked_items = "";
		
		for (var i = 0; i < this.definitions.length; i ++) {
			var chk = this.getCheckElementById(this.definitions[i][0]);
			if (chk) 
				if (chk.checked == 1 | chk.checked == "on")
					checked_items += "," + this.definitions[i][0];
		}
		
		if (checked_items.length > 0)
			checked_items = checked_items.substring(1);
		
		return checked_items;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.setCheckedItems;
	this.setCheckedItems = function (checkedItemsList) {
		if (checkedItemsList.length) {
			items = checkedItemsList.split(",");
			for (var i = 0; i < items.length; i++) {
				var x = this.getCheckElementById(parseInt(items[i]));
				if (x) 
					x.checked = true;
			}
		}
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.pack;
	this.pack = function () {
		var package = "";
		for (var i = 0; i < this.definitions.length; i ++)
			package += "\0" + this.definitions[i].join("|");
		
		return package;
	}

	///////////////////////////// CLASS MEMBERS: fsbTree.selectRadio;
	this.setRadioSelected = function (value) {
		var x = this.getRadioElementById(value);
		if (x) x.checked = true;
	}
} // Class Def Ends.
