Simple Tree in Javascript
<html>
<head>
<title>:: Tree Sample ::</title>
<style type="text/css">
body
{
padding: 0;
margin: 0;
}
.tree ul, .tree li
{
list-style-type: none;
margin: 0 0 0 2px;
padding: 0;
display: block;
}
.tree ul ul
{
margin-left: 16px;
}
.tree a.selected
{
background-color: lavender;
}
.tree .collapsed ul, .tree .collapsed span
{
display: none;
}
.tree span
{
display: block;
margin: 0 0 0 16px;
padding: 0;
color: Gray;
cursor: default;
font-size: smaller;
}
.tree img
{
border: none;
text-align: left;
vertical-align: middle;
margin-right: 2px;
}
.tree img.plusminus
{
width: 9px;
height: 9px;
}
.tree a, .tree a:link, .tree a:visited, .tree a:active
{
font-size: 10pt;
color: navy;
font-family: Verdana;
text-decoration: none;
text-align: left;
margin: 0 2px 0 2px;
}
.tree a:hover
{
text-decoration: underline;
color: Blue;
}
</style>
<script type="text/javascript">
/*
_______________________ ______________________
XML DOM Tree Component Browsers support:
Version 1.5 -> Internet Explorer
-> Mozilla
_____________________________ -> Opera
Features: -> Firefox
-> Server Side Independency -> Konqueror
-> Cross Browser Support
-> Dynamic Loading
-> XML Source
-> Easy Customization
______________________________
Serghei Egoricev (c) 2006
egoricev [at] gmail.com
*/
// CSS import
Tree = function() {}
/*
Use double click for navigate, single click for expand
*/
Tree.useDblClicks = true; // NOT IMPLEMENTED
Tree.saveNodesStateInCookies = true;
/*
CSS classes
*/
Tree.expandedClassName = "";
Tree.collapsedClassName = "collapsed";
Tree.selectedClassName = "selected";
Tree.plusMinusClassName = "plusminus";
Tree.treeClass = "tree";
/*
Images
*/
Tree.collapsedImage = "treeimg/collapsed.gif";
Tree.expandedImage = "treeimg/expanded.gif";
Tree.noChildrenImage = "treeimg/treenochild.gif";
/*
Xml Attributes
*/
Tree.xmlCaption = "caption";
Tree.xmlUrl = "url";
Tree.xmlTarget = "target";
Tree.xmlRetreiveUrl = "retreiveUrl";
Tree.xmlIcon = "icon";
Tree.xmlExpanded = "expanded";
/*
Text for loading
*/
Tree.loadingText = "Loading ...";
/*
Private members
*/
Tree.obj = null;
Tree.instanceCount = 0;
Tree.instancePrefix = "alder";
Tree.cookiePrefix = "alder";
Tree.dwnldQueue = new Array;
Tree.dwnldCheckTimeout = 100;
/*
Interval handler. Ckecks for new nodes loaded.
Adds loaded nodes to the tree.
*/
Tree.checkLoad = function ()
{
var i, httpReq;
for (i = 0; i<Tree.dwnldQueue.length; i++)
if ((httpReq = Tree.dwnldQueue[i][0]).readyState == 4 /*COMPLETED*/)
{
var node = Tree.dwnldQueue[i][1];
// unqueue loaded item
Tree.dwnldQueue.splice(i, 1);
Tree.appendLoadedNode(httpReq, node);
if (Tree.saveNodesStateInCookies)
Tree.openAllSaved(Tree.getId(node));
} // if
// will call next time, not all nodes were loaded
if (Tree.dwnldQueue.length != 0)
window.setTimeout(Tree.checkLoad, Tree.dwnldCheckTimeout);
}
/*
Adds loaded node to tree.
*/
Tree.appendLoadedNode = function (httpReq, node)
{
// create DomDocument from loaded text
var xmlDoc = Tree.loadXml(httpReq.responseText);
// create tree nodes from xml loaded
var newNode = Tree.convertXml2NodeList(xmlDoc.documentElement);
// Add loading error handling here must be added
Tree.appendNode(node, newNode);
}
/*
Event handler when node is clicked.
Navigates node link, and makes node selected.
*/
Tree.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;
Tree.obj = Tree.getObj(node);
Tree.expandNode(node);
Tree.selectNode(node);
}
/*
Event handler when plus/minus icon is clicked.
Desides whenever node should be expanded or collapsed.
*/
Tree.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.empty)
return;
Tree.obj = Tree.getObj(node);
if (Tree.isNodeCollapsed(node))
Tree.expandNode(node);
else
Tree.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.
*/
Tree.isNodeSelected = function (node)
{
return (node.isSelected == true) || (Tree.obj.selectedNode == node);
}
/*
Determines if specified node is expanded.
*/
Tree.isNodeExpanded = function (node)
{
return (Tree.expandedClassName == node.className) || (node.expanded == true);
}
/*
Determines if specified node is collapsed.
*/
Tree.isNodeCollapsed = function (node)
{
return (Tree.collapsedClassName == node.className) || (node.collapsed == true);
}
/*
Determines if node currently selected is at same
level as node specified (has same root).
*/
Tree.isSelectedNodeAtSameLevel = function (node)
{
if (Tree.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 && Tree.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.
*/
Tree.selectNode = function (node)
{
if (Tree.isNodeSelected(node)) // already marked
return;
if (Tree.obj.selectedNode != null)
{// unmark previously selected node.
Tree.obj.selectedNode.isSelected = false;
// remove css style from anchor
Tree.getNodeAnchor(Tree.obj.selectedNode).className = "";
} // if
// collapse selected node if at same level
if (Tree.isSelectedNodeAtSameLevel(node))
Tree.collapseNode(Tree.obj.selectedNode);
// mark node as selected
Tree.obj.selectedNode = node;
node.isSelected = true;
Tree.getNodeAnchor(node).className = Tree.selectedClassName;
}
/*
Expand collapsed node. Loads children nodes if needed.
*/
Tree.expandNode = function (node, avoidSaving)
{
if (node.empty)
return;
Tree.getNodeImage(node).src = Tree.expandedImage;
node.className = Tree.expandedClassName;
node.expanded = true;
node.collapsed = false;
if (Tree.areChildrenNotLoaded(node))
Tree.loadChildren(node);
if (Tree.saveNodesStateInCookies && !avoidSaving)
Tree.saveOpenedNode(node);
}
/*
Collapse expanded node.
*/
Tree.collapseNode = function (node, avoidSaving)
{
if (node.empty)
return;
Tree.getNodeImage(node).src = Tree.collapsedImage;
node.className = Tree.collapsedClassName;
node.collapsed = true;
node.expanded = false;
if (Tree.saveNodesStateInCookies && !avoidSaving)
Tree.saveClosedNode(node);
}
/*
Returns plus/minus <img> for node specified.
*/
Tree.getNodeImage = function (node)
{
return node.getElementsByTagName("IMG")[0];
}
/*
Returns retreiveUrl for node specified.
*/
Tree.getNodeRetreiveUrl = function (node)
{
return node.getElementsByTagName("A")[0].href;
}
/*
Returns node link <a> element (<li><a><img></a><a>)
*/
Tree.getNodeAnchor = function (node)
{
return node.getElementsByTagName("A")[1];
}
/*
Cancel loading children nodes.
*/
Tree.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<Tree.dwnldQueue.length; i++)
if (Tree.dwnldQueue[i][1] == node)
{
// remove from queue
Tree.dwnldQueue.splice(i, 1);
// collapse node
Tree.collapseNode(node);
} // if
}
/*
Loads text from url specified and returns it as result.
*/
Tree.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.
*/
Tree.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.
*/
Tree.areChildrenNotLoaded = function (node)
{
return Tree.getNodeSpan(node) != null;
}
/*
Finds loading span for node.
*/
Tree.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.
*/
Tree.loadChildren = function (node)
{
// get url with children
var url = Tree.getNodeRetreiveUrl(node);
// retreive xml text from url
var httpReq = Tree.loadUrl(url, true);
// enqueue node loading
if (Tree.dwnldQueue.push(new Array (httpReq, node)) == 1)
window.setTimeout(Tree.checkLoad, Tree.dwnldCheckTimeout);
}
/*
Creates HTML nodes list from XML nodes.
*/
Tree.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(Tree.convertXml2Node(node)).nodeIndex = index++;
return ul;
}
/*
Adds event handler
*/
Tree.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.
*/
Tree.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.
*/
Tree.appendNode = function (node, newNode)
{
node.replaceChild(newNode, Tree.getNodeSpan(node));
}
/*
Creates tree object. Loads it content from url specified.
*/
Tree.prototype.Create = function (url, obj)
{
var div = document.createElement("DIV");
div.id = Tree.instancePrefix + Tree.instanceCount++;
div.className = Tree.treeClass;
var xml = Tree.loadUrl(url, false);
var xmlDoc = Tree.loadXml(xml);
var newNode = Tree.convertXml2NodeList(xmlDoc.documentElement);
div.appendChild(newNode);
if (obj != undefined)
{
if (obj.appendChild) // is node
obj.appendChild(div);
else if (document.getElementById(obj)) // is node id
document.getElementById(obj).appendChild(div);
} // if
else
document.body.appendChild(div);
if (Tree.saveNodesStateInCookies)
Tree.openAllSaved(div.id);
}
/*
Creates HTML tree node (<li>) from xml element.
*/
Tree.convertXml2Node = function (xmlElement)
{
var li = document.createElement("LI");
var a1 = document.createElement("A");
var a2 = document.createElement("A");
var i1 = document.createElement("IMG");
var i2 = document.createElement("IMG");
var hasChildNodes = Tree.hasXmlNodeChildren(xmlElement);
var retreiveUrl = xmlElement.getAttribute(Tree.xmlRetreiveUrl);
// plus/minus icon
i1.className = Tree.plusMinusClassName;
a1.appendChild(i1);
Tree.addEvent(a1, Tree.ExpandCollapseNode);
// plus/minus link
a1.href = retreiveUrl != null && retreiveUrl.length != 0 ? retreiveUrl : "about:blank";
li.appendChild(a1);
// node icon
i2.src = xmlElement.getAttribute(Tree.xmlIcon);
a2.appendChild(i2);
// node link
a2.href = xmlElement.getAttribute(Tree.xmlUrl);
a2.target = xmlElement.getAttribute(Tree.xmlTarget);
a2.title = xmlElement.getAttribute(Tree.xmlCaption);
a2.appendChild(document.createTextNode(xmlElement.getAttribute(Tree.xmlCaption)));
Tree.addEvent(a2, Tree.NodeClick);
li.appendChild(a2);
// loading span
if (!hasChildNodes && retreiveUrl != null && retreiveUrl.length != 0)
{
var span = document.createElement("SPAN");
span.innerHTML = Tree.loadingText;
Tree.addEvent(span, Tree.CancelLoad);
li.appendChild(span);
} // if
// add children
if (hasChildNodes)
li.appendChild(Tree.convertXml2NodeList(xmlElement));
if (hasChildNodes || retreiveUrl != null && retreiveUrl.length != 0)
{
if (xmlElement.getAttribute(Tree.xmlExpanded))
Tree.expandNode(li, true);
else
Tree.collapseNode(li, true);
} // if
else
{
i1.src = Tree.noChildrenImage; // no children
li.empty = true;
} // else
return li;
}
/*
Retreives current tree object.
*/
Tree.getObj = function (node)
{
var obj = node;
while (obj != null && obj.tagName != "DIV")
obj = obj.parentNode;
return obj;
}
Tree.getId = function (node)
{
var obj = Tree.getObj(node);
if (obj)
return obj.id;
return "";
}
/*
Retreives unique id for tree node.
*/
Tree.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.
*/
Tree.saveOpenedNode = function (node)
{
var treeId = Tree.getId(node);
var state = Tree.getAllNodesSavedState(treeId);
var nodeState = Tree.getNodeId(node) + ",";
if (state.indexOf(nodeState) == -1)
{
state += nodeState;
Tree.setAllNodesSavedState(treeId, state);
} // if
}
/*
Saves node as closed for reload.
*/
Tree.saveClosedNode = function (node)
{
var treeId = Tree.getId(node);
var state = Tree.getAllNodesSavedState(treeId);
state = state.replace(new RegExp(Tree.getNodeId(node) + ",", "g"), "");
Tree.setAllNodesSavedState(treeId, state);
}
Tree.getAllNodesSavedState = function (treeId)
{
var state = Tree.getCookie(Tree.cookiePrefix + "_" + treeId);
return state == null ? "" : state;
}
Tree.setAllNodesSavedState = function (treeId, state)
{
Tree.setCookie(Tree.cookiePrefix + "_" + treeId, state);
}
/*
Enques list of all opened nodes
*/
Tree.openAllSaved = function(treeId)
{
var nodes = Tree.getAllNodesSavedState(treeId).split(",");
var i;
for (i=0; i<nodes.length; i++)
{
var node = Tree.getNodeById(treeId, nodes[i]);
if (node && Tree.isNodeCollapsed(node))
Tree.expandNode(node);
} // for
}
Tree.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;
}
Tree.setCookie = function(sName, sValue)
{
document.cookie = sName + "=" + escape(sValue) + ";";
}
Tree.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;
}
</script>
</head>
<body>
<div id="tree"></div>
<hr/>
<script type="text/javascript">
new Tree().Create("tree.xml", "tree");
new Tree().Create("tree.xml");
</script>
</body>
</html>
SimpleTree.zip( 7 k)Related examples in the same category