/************************************************ * ypXmlTree.js * 2/10/2001 * www.youngpup.net //  //4/02/2001 //modified initDefaultImgs() to use imgPath //variable. previously, setting the img-path //parameter did nothing at all. // //10/08/2002 //modified opera check to allow isDOM=true for //Opera 7+, as it has DOM support. ************************************************/ypXmlTree.isIE   = document.all ? 1 : 0ypXmlTree.isMAC  = navigator.platform == "MacPPC"ypXmlTree.isDOM  = document.getElementById ? 1 : 0if (navigator.userAgent.toLowerCase().indexOf("opera") != -1) {	if (parseFloat(navigator.userAgent.match(new RegExp("opera ([^ ]+)", "i"))[1]) < 7) {                // opera <  7.0 cannot dynamically update the layout                ypXmlTree.isIE = 0                ypXmlTree.isDOM = 0        }}if (ypXmlTree.isIE || ypXmlTree.isDOM) {	document.writeln('<style type="text/css">')	document.writeln('.ypXmlTree {display:none;}')	document.writeln('.ypXmlTree .ypxt-n {width:100%;}')	document.writeln('.ypXmlTree div { margin-bottom:0px; }')	document.writeln('.ypxt-clip { overflow:hidden; display:none; }')	document.writeln('</style>')}//===============================================// ypXmlTree( sContainerId, iActiveNode )////	creates a collapsible tree object from the//	HTML contained in sContainerId.//===============================================function ypXmlTree(sContainerId) {	if (ypXmlTree.isIE || ypXmlTree.isDOM)	{		// internal state-tracking propeties		this.containerId		= sContainerId		this.openFolders		= []		this.activeNode			= 0		this.animating			= 0		this.aniInfo			= {}		// allows this object to refer to itself from		// window.setTimeout calls.		this.globalRef = "ypXmlTree_" + this.containerId		eval(this.globalRef+"=this")		this.load()	}}//===============================================// load( )////	the main initialization method for the tree.//	cannot run until the entire tree is available//	in the DOM.//===============================================ypXmlTree.prototype.load = function(){	this.root  = ypGetElementById(this.containerId)	this.divs  = ypGetDescendantsByTagName(this.root, "DIV")	this.rows  = ypGetDescendantsByTagName(this.root, "TR")	this.links = ypGetDescendantsByTagName(this.root, "A")	this.root.onmousedown = this.mouseEvent	this.root.ondblclick = this.mouseEvent	this.root.onclick = this.mouseEvent	this.root.yp = this	this.selectionStyle		= def(this.root.getAttribute("selection-style"), 1)	this.animationStyle		= def(this.root.getAttribute("animation-style"), "box")	this.animationFrames	= def(this.root.getAttribute("animation-frames"), 5)	this.animationStep		= def(this.root.getAttribute("animation-step"), 35)	this.persistState		= def(this.root.getAttribute("persist-state"), 1) == '1'		this.setTargetWindow()	this.getState()	this.initDefaultImgs()	this.initRowImgs()	this.initNodes()	this.root.style.display = "block"		function def(opt, def) {		return (opt == "" || opt == null ? def : opt)	}}// this initializes the default images for toggles and icons.ypXmlTree.prototype.initDefaultImgs = function() {	var r = this.root	var imgPath		= r.getAttribute("img-path")	this.toggleC	= ypImage(imgPath + r.getAttribute("toggle-closed"))	this.toggleO	= ypImage(imgPath + r.getAttribute("toggle-open"))	this.folderC	= ypImage(imgPath + r.getAttribute("default-folder-icon"))	this.folderO	= ypImage(imgPath + r.getAttribute("default-folder-open-icon"))	this.folderA	= ypImage(imgPath + r.getAttribute("default-folder-active-icon"))	this.fileC		= ypImage(imgPath + r.getAttribute("default-file-icon"))	this.fileA		= ypImage(imgPath + r.getAttribute("default-file-active-icon"))}// this loads up any custom images, and sets the toggle to it's initial positionypXmlTree.prototype.initRowImgs = function(){	var aImgs, img, cls, custom	aImgs = ypGetDescendantsByTagName(this.root, "IMG")		for (var i = 0; (img = aImgs[i]); i++) {		cls		= ypGetParentNode(img).className		custom	= img.getAttribute("custom-icon")		if (cls == "ypxt-i" && custom != null && custom != "") {			img.c = ypImage(img.src)			img.o = ypImage(img.getAttribute("osrc"))			img.a = ypImage(img.getAttribute("asrc"))		}		else if (cls == "ypxt-t") {			img.src = this.toggleC.src		}	}}// this initializes the state of any folders, and the active file in the treeypXmlTree.prototype.initNodes = function(){	var row, membs, id	for (var i = 0; (row = this.rows[i]); i++) {		id = row.getAttribute("fId")		if (this.openFolders[id] || row == this.activeNode) {			membs = this.getNodeMembers(row)			if (this.openFolders[id]) this.toggle(id, row, membs.marker, membs.icon, membs.clip, true, "pop")			if (row == this.activeNode) this.activate(id, row, membs.marker, membs.icon, membs.name, membs.clip, "pop")		}	}}// this is the master function that figures out the state of the tree to display// 1) get the state saved from the cookie (if persisting state is allowed)// 2) if the user specified an active node, get it// 3) if the user did not specify an active node attempt to match the current URL with one or more of the nodes//    in the tre.// 4) if the best match found in step 3 is not a tie, use it for the active node// 5) if the best match from step 3 was a tie, use the active node saved in the cookie (if persisting state is allowed)ypXmlTree.prototype.getState = function() {	this.retreiveSavedState()	var aNode, urlMatches	aNode = this.getRow(this.root.getAttribute("active-node"))	if (!aNode || aNode == null) {		urlMatches = this.getNodeMatchesByURL()		if (urlMatches.length == 1) aNode = ypGetParentNode(ypGetParentNode(urlMatches[0]))	}	if (aNode) this.activeNode = aNode}// this function attempts to find one or more nodes in the tree that match the current window's URL.// it returns the node(s) that match best based on the following scale://	each folder from the root down that matches gets 1 point.//	if all folders match, and the files match, add 1 point//	if all folders match, and the query matches, add 1 point//	if all folders match, and the hash matches, add 1 point//// ex:	pageURL = "/components/ypXmlTree/demo.asp"//		nodeURL = "/components"							(1 point)//		nodeURL = "/components/ypRing/"					(1 point)//		nodeURL = "/components/ypXmlTree/"				(2 points)//		nodeURL = "/components/ypXmlTree/overview.asp"  (2 points)//		nodeURL = "/components/ypXmlTree/demo.asp"		(3 points)//// note that it would be more accurate if a nodeURL such as the first one was deemed a closer// match than the second one. But sofar, it hasn't been necessary to do develop that.ypXmlTree.prototype.getNodeMatchesByURL = function() {	var pageURL = new ypURL(this.targetWindow.location)	var curMatches = [], matchDegree = -1, link, foldersMatch	function update(newMatchDegree, newObj) {		if (newMatchDegree == matchDegree) curMatches[curMatches.length] = newObj		if (newMatchDegree > matchDegree) {			matchDegree = newMatchDegree			curMatches = [newObj]		}	}	for (var i = 0; i < this.links.length; i++) {		var link = this.links[i]		var curURL = new ypURL(link)		foldersMatch = true		curDegree = 0		for (var j = 0; j < curURL.folders.length; j++) {			if (curURL.folders[j] == pageURL.folders[j])				curDegree++			else				foldersMatch = false		}		if (foldersMatch) {			if (curURL.file == pageURL.file) curDegree++			if (curURL.query == pageURL.query) curDegree++			if (curURL.hash == pageURL.hash) curDegree++		}		update(curDegree, link)	}		return curMatches}//===============================================// saveState( )////	saves the current state of the tree (which//	folders are open, which node is active) to//	the browser's in-memory cookie, so that on//	the next page view we can display the tree//	the same way it is now.//===============================================ypXmlTree.prototype.saveState = function() {	if (this.persistState) {		var a1, a2, id		id = this.activeNode ? this.activeNode.getAttribute("fId") : ""		a1 = this.openFolders		a2 = []		for (i = 0; i < a1.length; i++) if (a1[i] == true) a2[a2.length] = i		document.cookie = "ypxt-" + this.root.id + "=" + a2.join("&") + "|" + id + ";path=" + "/"	}}//===============================================// retreiveSavedState( )////	parses the last saved state out of the//	browser cookie and puts it into .openFolders //	and .activeNode//===============================================ypXmlTree.prototype.retreiveSavedState = function() {	var c, s, e, a1, a2, a3, n	if (this.persistState) {		c = document.cookie		n = "ypxt-"+this.root.id+"="		s = c.indexOf(n)		if (s != -1) {			s += n.length			e  = c.indexOf(";",s)			if (e == -1) e = c.length			c  = c.substring(s, e)			a1 = c.split("|")			a2 = a1[0].split("&")			for (i in a2) this.openFolders[a2[i]] = 1			this.activeNode = this.getRow(a1[1])		}	}}//===============================================// mouseEvent( )////	handles mouseclick or mousedown events that//	occur within the tree and calls .toggle()//	or .activate() if the event meets certain//	conditions//===============================================ypXmlTree.prototype.mouseEvent = function(e) {	// get the source element of the event.	// if it's a node of type text (3), 	// use the immediate parent instead (which will be <a> or <span>).	var srcEl, srcTD, ypType, row, oMembs, y, id, bOpen	if (!e) e = window.event	srcEl	= e.originalTarget ? e.originalTarget : e.srcElement	if (srcEl.nodeType == 3) srcEl = srcEl.parentNode	srcTD	= ypGetParentNode(srcEl)	y		= this.yp	if (srcTD && srcTD.tagName == "TD" && srcTD.className.indexOf("ypxt") == 0) {		row		= ypGetParentNode(srcTD)		id		= row.getAttribute("fId")		bOpen	= y.openFolders[id] == 1		ypType	= srcTD.className.split("-")[1]		oMembs	= y.getNodeMembers(row)		if (e.type == "mousedown") {			if (row.className == "ypxt-r-fo") {				if (ypType == "t" || ((ypType == "i" || ypType == "n") && y.selectionStyle == 3)) {					y.toggle(id, row, oMembs.marker, oMembs.icon, oMembs.clip, !bOpen)					y.openFolders[id] = !bOpen				}			}			if (ypType == "i" || ypType == "n" || (ypType == "t" && y.selectionStyle == 3)) {				y.activate(id, row, oMembs.marker, oMembs.icon, oMembs.name, oMembs.clip)			}			y.saveState()			return false		}		if (e.type == "dblclick" && ((ypType == "i" || ypType == "n") && y.selectionStyle == 1)) {			y.toggle(id, row, oMembs.marker, oMembs.icon, oMembs.clip, !bOpen)			y.openFolders[id] = !bOpen		}		if (e.type == "click" && row.className.indexOf("ypxt-") == 0 && ypType == "i" && y.selectionStyle != 3 && oMembs.name.href) {			y.targetWindow.location = oMembs.name.href		}	}}//===============================================// toggle( )////	visually opens or closes a folder. ////	by default, this examines the current state//	of the folder and then sets it to the//	the opposite, using .animationStyle.////	however, that can be overridden by passing//	the last two parameters.//===============================================ypXmlTree.prototype.toggle = function(id, r, m, i, c, bOpen, style){	if (r.className == "ypxt-r-fo") {		var custom = i ? i.getAttribute("custom-icon") : 0		if (custom == null) custom = ""		if (typeof style == "undefined") style = this.animationStyle		// slide animation style doesn't work for IE4. Always use "pop".		if (!ypXmlTree.isDOM) style = "pop"		if (m) m.src = bOpen && this.toggleO ? this.toggleO.src : this.toggleC.src		if (i && r != this.activeNode) {			if (bOpen) {				if (custom && i.o) i.src = i.o.src				else if (!custom && this.folderO) i.src = this.folderO.src			} else {				if (custom && i.c) i.src = i.c.src				else if (!custom && this.folderC) i.src = this.folderC.src			}		}		this[style](c, bOpen)	}}//===============================================// activate( )////	visually 'activates' a node. This means that//	the name's class is switched to ypxt-active-//	node and the icon is switched to active (if//	there is one)//===============================================ypXmlTree.prototype.activate = function(id, r, m, i, n, c, style) {	var bFolder	= r.className == "ypxt-r-fo"	var bFile	= r.className == "ypxt-r-fi"	var sNew, old	if (!(this.selectionStyle == 3 && bFolder)) {		if (this.activeNode) {			old = this.getNodeMembers(this.activeNode)			ypSwap(old.icon)			old.name.className = ""		}		n.className = "ypxt-active"		sNew = i.a ? i.a.src : bFolder && this.folderA ? this.folderA.src : bFile && this.fileA ? this.fileA.src : 0		if (sNew) ypSwap(i, sNew)		if (bFolder && this.selectionStyle == 2 && !this.openFolders[id]) {			this.toggle(id, r, m, i, c, true, style ? style : this.animationStyle)			this.openFolders[id] = true		}		this.activeNode = r	}}// figures out the destination window object for links of this tree.ypXmlTree.prototype.setTargetWindow = function() {	var win = window	var t = this.root.getAttribute("target-frame")	if (t && t != null) {		t = t.toLowerCase()		if (t == "_top") win = top		else if (t == "_parent") win = win.parent		else if (t == "_blank") win = win.open("", "", "")		else if (win.frames[t]) win = win.frames[t]	}	this.targetWindow = win}//===============================================// pop( )////	opens or closes a folder's children div with//	no animation.//===============================================ypXmlTree.prototype.pop = function(div, bOpen) {	div.style.display = bOpen ? "block" : "none"	if (ypXmlTree.isMAC && ypXmlTree.isIE) {		// wow. what a hack. ie mac is a piece, and sometimes doesn't realize it needs to re-layout this part of the page.		// i do this to force it that operation. I haven't been able to visually see the change yet, so I think it's ok.		window.resizeBy(0,1)		window.resizeBy(0,-1)	}}//===============================================// slide( )////	opens or closes a folder's children div with//	a sliding animation.//===============================================ypXmlTree.prototype.slide = function(div, bOpen){	var i = this.aniInfo	if (!this.animating) {		this.animating = true		i.clip		= div		i.content	= div.firstChild ? div.firstChild : div.children[0]		i.bOpen		= bOpen		i.frameNum	= 1		if (bOpen) {			i.clip.style.height = "1px"			i.clip.style.display = "block"		}		this.slideFrame()	}}//===============================================// slideFrame( )////	called repeatedly after a call to .slide()//	to paint each frame of the slide animation//===============================================ypXmlTree.prototype.slideFrame = function(){	var i = this.aniInfo	if (i.frameNum <= this.animationFrames) {		window.setTimeout(this.globalRef + ".slideFrame()", this.animationStep)		var percentComplete = i.frameNum / this.animationFrames		var newH = Math.round((i.content.offsetHeight) * percentComplete)		if (!i.bOpen) newH = i.content.offsetHeight - newH		i.clip.style.height = Math.max(newH,1)		i.frameNum++	} else {		this.slideEnd()	}}//===============================================// slideEnd( )////	cleans up and finishes after the last call//	to slideFrame()//===============================================ypXmlTree.prototype.slideEnd = function() {	var i = this.aniInfo	i.clip.style.height = i.bOpen ? i.content.offsetHeight : 1	if (!i.bOpen) i.clip.style.display = "none"	else i.clip.style.height = "auto"	i = {}	this.animating = false}// given a reference to a row in the tree, find and return it's// associated marker, icon, name, and clip objects as properties// of one object.ypXmlTree.prototype.getNodeMembers = function(row) {	var id = row.getAttribute("fId")	var obj = {marker:0,icon:0,name:0,clip:0}		var cells = row.cells, c, d	for (var i = 0; (c = cells[i]); i++) {		if (c.className == "ypxt-b" || c.className == "ypxt-t") obj.marker = ypFirstChild(c)		else if (c.className == "ypxt-i") obj.icon = ypFirstChild(c)		else if (c.className == "ypxt-n") obj.name = ypFirstChild(c)	}	for (var i = 0; (d = this.divs[i]); i++) {		if (d.getAttribute("fId") == id) {			obj.clip = d			break		}	}	return obj}// find a row of the tree given the value of it's fId property.ypXmlTree.prototype.getRow = function(id) {	var row	if (id && id != "" && id != null) {		for (var i = 0; (row = this.rows[i]); i++)			if (row.getAttribute("fId") == id) return row	}	return 0}//===============================================// miscellaneous utitity functions//===============================================// cross platform equiv. to document.getElementByIdfunction ypGetElementById(s) {	var o = (document.getElementById ? document.getElementById(s) : document.all[s])	return o == null ? false : o}// cross-platform equiv. to <htmlelement>.parentNodefunction ypGetParentNode(objChild) {	return (objChild.parentNode ? objChild.parentNode : objChild.parentElement)}function ypFirstChild(a) {	return a.firstChild ? a.firstChild : a.children[0]}// gets the a list of all the descendant nodes with the specified tag namefunction ypGetDescendantsByTagName(a, s) {	return (a.getElementsByTagName ? a.getElementsByTagName(s) : a.all.tags(s))}// convenience function for creating image objects quickly.function ypImage(s) {	if (s && s != null && s != "") {		var oImg  = new Image()		oImg.loadError = false		oImg.onerror = function() { this.loadError = true }		oImg.src = s		return oImg	}	return 0}// this is just a simple function that makes image swapping more conveninent.//// if s is passed, sets the .src property of img to s and saves the old source in the "swap" property of the img object// otherwise, if s is not passed, sets the src property of img to the source of the object in "swap"function ypSwap(img, s) {	if (img) {		if (s) { img.swap = ypImage(img.src); img.src = s; }		else if (img.swap) img.src = img.swap.src	}}// represents a link or window.location object a little bit cleanerfunction ypURL(linkObj) {	if (linkObj.href.indexOf("http:") == 0) {		var s, a		s = linkObj.pathname		if (s.indexOf("/") == 0) s = s.substring(1, s.length)		a = s.split("/")		for (var i = 0; i < a.length; i++) a[i] = a[i].toLowerCase()		this.file = a[a.length-1].toLowerCase()		a.length--		this.folders = a		this.query = linkObj.search.toLowerCase()		this.hash = linkObj.hash.toLowerCase()	} else {		this.folders = []		this.file = linkObj.href.toLowerCase()		this.query = ""		this.hash = ""	}}
