//
// PTree - A dynamically loaded TOC tree
//
// Author:
// Piers Haken (piersh@friskit.com)
//
// (C) 2003 Piers Haken
//
// TODO:
// work out how to cancel scrolling keyboard events on Mozilla
// better support for multiple trees in a single body
function PTree ()
{
this.strActionBase = "";
this.strSrcBase = "";
this.strTargetDefault = "";
this.strImagesBase = "images/";
this.strImageExt = ".png";
this.eltSelected = null;
this.nImageWidth = 18;
this.nImageHeight = 18;
this.CreateItemFromXML = function (oNode, fLast, eltParent)
{
var strText = oNode.getAttribute ("text");
var strAction = oNode.getAttribute ("action");
var strSrc = oNode.getAttribute ("src");
var strTarget = oNode.getAttribute ("target");
return this.CreateItem (eltParent, strText, strAction, strSrc, fLast, strTarget, fLast, eltParent);
}
this.CreateItem = function (eltParent, strText, strAction, strSrc, fLast, strTarget)
{
var _this = this;
var eltDiv = document.createElement ("DIV");
if (eltParent == null)
eltDiv.tree_fRoot = true;
if (fLast)
eltDiv.tree_fLast = true;
if (strAction)
eltDiv.tree_action = strAction;
if (strSrc != null)
eltDiv.tree_src = strSrc;
var eltSpan = document.createElement ("SPAN");
eltSpan.className = "tree-label";
if (eltParent)
{
eltDiv.className = "tree-node-collapsed";
// this node's tree icon
var eltIcon = new Image ();
eltIcon.width = this.nImageWidth;
eltIcon.height = this.nImageHeight;
if (strSrc)
eltIcon.onclick = function () { _this.LoadNode (this); }
eltIcon.src = this.GetIconSrc (eltDiv, true);
eltSpan.appendChild (eltIcon);
// parent's tree icons
var eltIconLast = eltIcon;
var eltParentDiv = eltParent;
while (!this.IsRootDiv (eltParentDiv))
{
var eltIcon = new Image ();
eltIcon.width = this.nImageWidth;
eltIcon.height = this.nImageHeight;
if (this.IsLastDiv (eltParentDiv))
eltIcon.src = this.strImagesBase + "blank" + this.strImageExt;
else
eltIcon.src = this.strImagesBase + "I" + this.strImageExt;
eltSpan.insertBefore (eltIcon, eltIconLast);
eltIconLast = eltIcon;
eltParentDiv = this.GetParentDiv (eltParentDiv);
}
}
else
{
eltDiv.className = "tree-node";
//document.body.onkeydown = function () { return _this.onKeyDown (); }
}
// description
var eltText = document.createTextNode (strText);
var eltDescription;
if (strAction)
{
eltDescription = document.createElement ("a");
eltDescription.href = this.strActionBase + strAction;
eltDescription.title = strText;
if (strTarget)
eltDescription.target = strTarget;
else if (this.strTargetDefault)
eltDescription.target = this.strTargetDefault;
eltDescription.appendChild (eltText);
eltDescription.onclick = function () { _this.SelectNode (eltDiv); }
eltDescription.onmouseover = function () { this.blur (); }
eltDescription.onmouseup = function () { this.blur (); }
}
else
{
eltDescription = document.createElement ("span");
eltDescription.className = "tree-label";
eltDescription.innerHTML = strText;
}
eltSpan.appendChild (eltDescription);
eltDiv.appendChild (eltSpan);
// append this node to its parent
if (eltParent)
eltParent.appendChild (eltDiv);
else
this.SelectNode (eltDiv);
return eltDiv;
}
this.SelectNode = function (eltDiv)
{
if (this.eltSelected != eltDiv)
{
if (eltDiv)
{
var eltLabel = this.GetSpan (eltDiv);
eltLabel.className = "tree-label-selected";
}
if (this.eltSelected)
{
var eltLabel = this.GetSpan (this.eltSelected);
eltLabel.className = "tree-label";
}
this.eltSelected = eltDiv;
}
}
this.LoadNode = function (eltIcon)
{
var eltDiv = this.GetDivFromIcon (eltIcon);
eltIcon.onclick = null;
var eltLoading = this.CreateItem (eltDiv, "<img src=\"../images/searching.gif\"/>Loading...", null, null, true);
eltLoading.className = '';
var xmlHttp = XmlHttp.create();
xmlHttp.open ("GET", this.strSrcBase + eltDiv.tree_src, true); // async
var _this = this;
xmlHttp.onreadystatechange = function () { _this.onReadyStateChange (xmlHttp, eltIcon, eltLoading); }
setTimeout (function () { xmlHttp.send (null); }, 10);
}
this.onReadyStateChange = function (xmlHttp, eltIcon, eltLoading)
{
if (xmlHttp.readyState != 4)
return;
// XML loaded
var eltDiv = this.GetDivFromIcon (eltIcon);
try
{
var doc = xmlHttp.responseXML;
var root = doc.documentElement;
var nodes = root.childNodes;
var cNodes = nodes.length;
for (var iNode = 0; iNode < cNodes; iNode ++)
this.CreateItemFromXML (nodes [iNode], iNode == cNodes-1, eltDiv);
eltDiv.removeChild (eltLoading);
if (this.eltSelected == eltLoading)
this.SelectNode (this.GetFirstChild (eltDiv));
eltIcon.src = this.GetIconSrc (eltDiv, false);
}
catch (e)
{
this.SetText (eltLoading, "Failed to load topic");
}
eltDiv.className = "tree-node";
var _this = this;
eltIcon.onclick = function () { _this.onClickMinus (this); }
}
this.onClickPlus = function (eltIcon)
{
var eltDiv = this.GetDivFromIcon (eltIcon);
eltDiv.className = "tree-node";
eltIcon.src = this.GetIconSrc (eltDiv, false);
var _this = this;
eltIcon.onclick = function () { _this.onClickMinus (this); }
}
this.onClickMinus = function (eltIcon)
{
var eltDiv = this.GetDivFromIcon (eltIcon);
eltDiv.className = "tree-node-collapsed";
eltIcon.src = this.GetIconSrc (eltDiv, true);
var _this = this;
eltIcon.onclick = function () { _this.onClickPlus (this); }
}
this.onKeyDown = function (event)
{
var eltSelect = this.eltSelected;
var fLast = this.IsLastDiv (eltSelect);
var fRoot = this.IsRootDiv (eltSelect);
switch (event.keyCode)
{
case 13: // return
var eltLink = eltSelect.firstChild.lastChild;
if (eltSelect.tree_action)
window.open (eltLink.href, eltLink.target);
this.SelectNode (eltSelect);
return false; // don't EnsureVisible
case 38: // up
if (!fRoot)
{
if (this.IsFirstChild (eltSelect))
eltSelect = this.GetParentDiv (eltSelect);
else
{
eltSelect = eltSelect.previousSibling;
while (this.IsExpanded (eltSelect))
eltSelect = eltSelect.lastChild;
}
}
break;
case 40: // down
if (this.IsExpanded (eltSelect))
eltSelect = this.GetFirstChild (eltSelect);
else if (!fLast)
eltSelect = eltSelect.nextSibling;
else
{
while (!this.IsRootDiv (eltSelect) && this.IsLastDiv (eltSelect))
eltSelect = this.GetParentDiv (eltSelect);
if (this.IsRootDiv (eltSelect))
return false;
eltSelect = eltSelect.nextSibling;
}
break;
case 37: // left
if (!fRoot)
{
if (this.IsExpanded (eltSelect))
this.onClickMinus (this.GetIconFromDiv (eltSelect));
else
eltSelect = this.GetParentDiv (eltSelect);
}
break;
case 39: // right
if (this.HasChildren (eltSelect))
{
var eltChild = this.GetFirstChild (eltSelect);
if (this.IsExpanded (eltSelect))
eltSelect = eltChild;
else if (eltChild != null)
this.onClickPlus (this.GetIconFromDiv (eltSelect));
else
this.LoadNode (this.GetIconFromDiv (eltSelect));
}
break;
default:
return true;
}
this.SelectNode (eltSelect);
this.EnsureVisible (this.GetLabel (eltSelect));
return false;
}
this.SetText = function (eltDiv, strText)
{
var eltText = eltDiv.lastChild;
eltText.nodeValue = strText;
}
this.GetIconSrc = function (eltDiv, fPlus)
{
var strIconSrc = this.IsLastDiv (eltDiv) ? "L" : "T";
if (eltDiv.tree_src != null)
strIconSrc += fPlus ? "plus" : "minus";
return this.strImagesBase + strIconSrc + this.strImageExt;
}
this.GetDivFromIcon = function (eltIcon)
{
return eltIcon.parentNode.parentNode;
}
this.GetIconFromDiv = function (eltDiv)
{
return eltDiv.firstChild.lastChild.previousSibling;
}
this.GetFirstChild = function (eltDiv)
{
return eltDiv.firstChild.nextSibling;
}
this.GetSpan = function (eltDiv)
{
return eltDiv.firstChild;
}
this.GetLabel = function (eltDiv)
{
return eltDiv.firstChild.lastChild;
}
this.GetParentDiv = function (eltDiv)
{
if (this.IsRootDiv (eltDiv))
return null;
return eltDiv.parentNode;
}
this.HasChildren = function (eltDiv)
{
return eltDiv.tree_src || this.IsRootDiv (eltDiv);
}
this.IsLastDiv = function (eltDiv)
{
return eltDiv.tree_fLast;
}
this.IsRootDiv = function (eltDiv)
{
return Boolean (eltDiv.tree_fRoot);
}
this.IsExpanded = function (eltDiv)
{
return eltDiv.className != "tree-node-collapsed";
}
this.IsFirstChild = function (eltDiv)
{
var fFirst =
eltDiv.previousSibling &&
eltDiv.previousSibling.tagName != "DIV";
return fFirst;
}
this.EnsureVisible = function (elt)
{
var x = 0;
var y = 0;
var parent = elt;
while (parent != null)
{
x += parent.offsetLeft;
y += parent.offsetTop;
parent = parent.offsetParent;
}
var yView = window.frameElement.scrollTop + document.body.scrollTop;
var dyView = document.body.clientHeight;
var dy = 0;
if (y + elt.offsetHeight > yView + dyView)
dy = (y + elt.offsetHeight) - (yView + dyView);
if (y < yView + dy)
dy = y - yView;
var xView = window.frameElement.scrollLeft + document.body.scrollLeft;
var dxView = document.body.clientWidth;
var dx = 0;
if (x + elt.offsetWidth > xView + dxView)
dx = (x + elt.offsetWidth) - (xView + dxView);
if (x < xView + dx)
dx = x - xView;
if (dx != 0 || dy != 0)
window.scrollBy (dx, dy);
}
}
syntax highlighted by Code2HTML, v. 0.9.1