XML-Data driven Component example: File-Tree

The following component takes an XML-string, and draws itself based on the directory structure given in the XML. If you look at the SVG-code (e.g. view source option of SVG Viewer popup), you would find the XML-data (please see the XML below ) is included in it.

You could substitute that XML-string with any XML-string that conforms the XML-schema and reload the web-page, this component presents the new file-tree. The JavaScript in the file processes the XML-string and presents the component. This kind of presentation-blocks are very useful to work with Web Services, since they could be refreshed frequently by getting new XML-string, whenever there is a change at the Web server.

Features:  

  • You could resize it by dragging the red-dot (at lower right corner).
  •  You could open and close folders by clicking on them etc.
  • You could scroll the file-tree (Scroll bars automatically disappear, if some folder are closed. I.e. dynamically determines the space needed for  the subcomponent.)

Note: Please see below for both the XML-data and the SVG/JavaScript code

The Folder Tree component is contributed by Mr. Philippe Elsass at  http://consume.free.fr/svg/samples/tree.svg.

This is a great component, but unfortunately not designed to be used as a subcomponent to build component hierarchy. It used absolute coordinate system, so the scroll bar acts funny, if the component is placed at any location other than X-Y location (0,0). For example, see the example. If we can fix it, it can be used to build a GUI-Class.

Basic Web Application Component Requirements:
  1. The component should use relative coordinate system, so that, it can be used as a subcomponent in a larger component (e.g. to build component hierarchy).
  2. The component variables must be unique, so that, they do not collide with the variables of any other components in the web page. Please see examples:


 var   XML_data      =     '<item caption="Node: Top" id="0">'

+'         <item caption="Node Top.1" id="1">'

+'                     <item caption="Node Top.1.1" id="2">'

+'                                 <item caption="Node Top.1.1.1" id="3">'

+'                                 <item caption="Node Top.1.1.1.1" id="1">'

+'                                             <item caption="Node Top.1.1.1.1.1" id="2">'

+'                                                         <item caption="Node Top.1.1.1.1.1.1" id="3"/>'

+'                                             </item>'

+'                                             <item caption="Node Top.1.1.1.1.2" id="4" />'

+'                                 </item>'

+'                                 <item caption="Node Top.1.1.1.2" id="5" />'

+'                                 <item caption="Node Top.1.1.1.3" id="6">'

+'                                             <item caption="Node Top.1.1.1.3.1" id="7"/>'

+'                                             </item>'

+'                                 </item>'

+'                     </item>'

+'                     <item caption="Node Top.1.2" id="4" />'

+'            </item>'

+'         <item caption="Node Top.2" id="5" />'

+'         <item caption="Node Top.3" id="6">'

+'                     <item caption="Node Top.3.1" id="7"/>'

+'            </item>'

+'         <item caption="Node Top.4" id="1">'

+'                     <item caption="Node Top.4.1" id="2">'

+'                                 <item caption="Node Top.4.1.1" id="3">'

+'                                             <item caption="Node Top.4.1.1.1" id="1">'

+'                                                         <item caption="Node Top.4.1.1.1.1" id="2">'

+'                                                                     <item caption="Node Top.4.1.1.1.1.1" id="3">'

+'                                                                                 <item caption="Node Top.4.1.1.1.1.1.1" id="1">'

+'                                                                                             <item caption="Node Top.4.1.1.1.1.1.1.1" id="2">'

+'                                                                                                         <item caption="Node Top.4.1.1.1.1.1.1.1.1" id="3"/>'

+'                                                                                             </item>'

+'                                                                                             <item caption="I\'m small ;)" id="4" />'

+'                                                                                 </item>'

+'                                                                                 <item caption="Node Top.4.1.1.1.1.1.2" id="5" />'

+'                                                                                 <item caption="Node Top.4.1.1.1.1.1.3" id="6">'

+'                                                                                             <item caption="Node Top.4.1.1.1.1.1.3.1" id="7"/>'

+'                                                                                 </item>'

+'                                                                     </item>'

+'                                                         </item>'

+'                                                         <item caption="Node Top.4.1.1.1.2" id="4" />'

+'                                             </item>'

+'                                             <item caption="Node Top.4.1.1.2" id="5" />'

+'                                             <item caption="Node Top.4.1.1.3" id="6">'

+'                                                         <item caption="Node Top.4.1.1.3.1" id="7"/>'

+'                                             </item>'

+'                                 </item>'

+'                     </item>'

+'                     <item caption="Node Top.4.2" id="4" />'

+'            </item>'

+'         <item caption="Node Top.5" id="5">'

+'                     <item caption="Node Top.5.1" id="1">'

+'                                 <item caption="Node Top.5.1.1" id="2">'

+'                                             <item caption="Node Top.5.1.1.1" id="3"/>'

+'                                 </item>'

+'                                 <item caption="Node Top.5.1.2" id="4" />'

+'                     </item>'

+'                     <item caption="Node Top.5.2" id="5" />'

+'                     <item caption="Node Top.5.3" id="6">'

+'                                 <item caption="Node Top.5.3.1" id="7"/>'

+'                     </item>'

+'            </item>'

+'         <item caption="Node Top.6" id="6">'

+'                     <item caption="Node Top.6.1" id="7"/>'

+'            </item>'

+'</item>'

 

 

The Presentation Block (or JavaScript code for the Directory-tree application-component):

<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/SVG/DTD/svg10.dtd">

<svg id="root" onload="init(evt)" width="320" height="320"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">

<g> 
<script type="text/ecmascript"><![CDATA[
/*****
*
* Tree.js
*
* Copyright 2002, Philippe Elsass
*
* Thanks to Kevin Lindsey for his great Svg scripts.
*
*****/

Tree = new Object;
Tree.VERSION = 0.1;

Tree.ID_PLUS = "tree_plus";
Tree.ID_MINUS = "tree_minus";
Tree.ID_END = "tree_end";
Tree.ID_BUTTON = "tree_button";

Tree.TEXT_STYLE = "font-family:Arial,Helvetica;font-size:8pt;text-rendering:optimizeLegibility;";
Tree.TEXT_SELECT = "fill:white;pointer-events:none;";
Tree.TEXT_NORMAL = "fill:black;pointer-events:none;";
Tree.LINES_STYLE = "fill:none;stroke-width:0.5;stroke:black;stroke-dasharray:1 1;";
Tree.SELBOX_STYLE = "fill:#003399;stroke-width:0.5;stroke:black;stroke-dasharray:1 1;";
Tree.BG_STYLE = "fill:white;stroke:#eeeeee;stroke-width:1;shape-rendering:optimizeSpeed;";
Tree.HIGH_STYLE = "fill:none;stroke:white;stroke-width:1;shape-rendering:optimizeSpeed;";
Tree.LOW_STYLE = "fill:none;stroke:gray;stroke-width:1;shape-rendering:optimizeSpeed;";
Tree.SCROLL_STYLE = "fill:#eeeeee;stroke:none;";
Tree.SCREND_STYLE = "fill:silver;stroke:none;";
Tree.HIDE_STYLE = "stroke-opacity:0;fill-opacity:0;";

Tree.ITEM_HEIGHT = 20;
Tree.CHILD_TAB = 24;
Tree.TEXT_XOFFSET = 14;
Tree.TEXT_YOFFSET = 4;
Tree.BUTTON_SIZE = 12;

Tree.root = null;
Tree.depth = 0;
Tree.callBack = null;
Tree.current = null;


/*****
*
* updateTree (x,y,width,height: optionnal);
*
*****/
function updateTree(reorganize,x,y,width,height) {
if (!Tree.parent) exit;

if (!isNaN(x) && !isNaN(y)) {
Tree.x = x;
Tree.y = y;
}

if (!isNaN(width) && !isNaN(height)) {
Tree.width = width;
Tree.height = height;

Tree.clip.setAttribute("x",x+1);
Tree.clip.setAttribute("y",y+1);
Tree.background.setAttribute("x",x+1);
Tree.background.setAttribute("y",y+1);
Tree.background.setAttribute("width",width-2);
Tree.background.setAttribute("height",height-2);
Tree.background.setAttribute("style",Tree.BG_STYLE);
Tree.lowBorder.setAttribute("d","M"+Tree.x+","+(Tree.y+Tree.height)+"v-"+Tree.height+"h"+Tree.width);
Tree.lowBorder.setAttribute("style",Tree.LOW_STYLE);
Tree.highBorder.setAttribute("d","M"+Tree.x+","+(Tree.y+Tree.height)+"h"+Tree.width+"v-"+Tree.height);
Tree.highBorder.setAttribute("style",Tree.HIGH_STYLE);
}

if (reorganize) {
Tree.linesData = "";
Tree.maxX = 0;
Tree.maxY = 0;
Tree.root.organize(true,Tree.x+12,Tree.y+10);
}

if ((reorganize) || (!isNaN(width) && !isNaN(height))) {
var vBar = true;
var hBar = true;
if ((Tree.maxX+8<Tree.x+Tree.width) && (Tree.maxY+8<Tree.y+Tree.height)) 
{ vBar = false; hBar = false; } else
if ((Tree.maxX+8>Tree.x+Tree.width) && (Tree.maxY+8<Tree.y+Tree.height-16))
{ vBar = false; hBar = true; } else
if ((Tree.maxX+8<Tree.x+Tree.width-16) && (Tree.maxY+8>Tree.y+Tree.height)) 
{ vBar = true; hBar = false; }

if (reorganize || (vBar != Tree.vBar) || (hBar != Tree.hBar) || (!isNaN(width) && !isNaN(height))) {
Tree.vBar = vBar;
Tree.hBar = hBar;
if (Tree.vBar) Tree.xAvail = Tree.width-2-Tree.BUTTON_SIZE; else Tree.xAvail = Tree.width-2;
if (Tree.hBar) Tree.yAvail = Tree.height-2-Tree.BUTTON_SIZE; else Tree.yAvail = Tree.height-2;
Tree.maxScrollX = Tree.maxX-Tree.x-Tree.xAvail+8;
Tree.pageSizeX = Tree.xAvail-24;
Tree.maxScrollY = Tree.maxY-Tree.y-Tree.yAvail+8;
Tree.pageSizeY = Tree.yAvail-Tree.ITEM_HEIGHT-8;

if (Tree.vBar || Tree.hBar)
Tree.scrollBars.setAttribute("style","display:visible;"+Tree.SCROLL_STYLE);

var d="";
if (Tree.hBar) {
Tree.clip.setAttribute("height",Tree.height-2-Tree.BUTTON_SIZE); 
d = "M"+(Tree.x+1)+","+(Tree.y+Tree.height-1-Tree.BUTTON_SIZE)
+ "v"+Tree.BUTTON_SIZE+"h"+Tree.xAvail+"v-"+Tree.BUTTON_SIZE+"z";
}
else Tree.clip.setAttribute("height",Tree.height-2);

if (Tree.vBar) {
Tree.clip.setAttribute("width",Tree.width-2-Tree.BUTTON_SIZE);
d += "M"+(Tree.x+Tree.width-1-Tree.BUTTON_SIZE)+","+(Tree.y+1)
+ "h"+Tree.BUTTON_SIZE+"v"+Tree.yAvail+"h-"+Tree.BUTTON_SIZE+"z";
}
else Tree.clip.setAttribute("width",Tree.width-2);

Tree.scrollBars.setAttribute("d",d);
if (hBar && vBar) {
Tree.scrollEnd.setAttribute("x",Tree.x+Tree.width-1-Tree.BUTTON_SIZE);
Tree.scrollEnd.setAttribute("y",Tree.y+Tree.height-1-Tree.BUTTON_SIZE);
Tree.scrollEnd.setAttribute("style","display:visible;"+Tree.SCREND_STYLE); 
}
else Tree.scrollEnd.setAttribute("style","display:none;");

if (hBar) {
Tree.hSb.setAttribute("transform", 
"translate("+(Tree.x+hPos()+1)+","+(Tree.y+Tree.height-1-Tree.BUTTON_SIZE)+")");
Tree.hSb.setAttribute("style", "display:visible;pointer-events:none;");
}
else {
Tree.hSb.setAttribute("style", "display:none;");
Tree.hScroll = 0;
}

if (vBar) {
Tree.vSb.setAttribute("transform", 
"translate("+(Tree.x+Tree.width-1-Tree.BUTTON_SIZE)+","+(Tree.y+vPos()+1)+")");
Tree.vSb.setAttribute("style", "display:visible;pointer-events:none;");
}
else { 
Tree.vSb.setAttribute("style", "display:none;");
Tree.vScroll = 0;
}

Tree.treeBox.setAttribute("transform",
"translate(-"+Math.floor(Tree.hScroll)+",-"+Math.floor(Tree.vScroll)+")");
}
}
}


/*****
*
* _root_init: reference to first node and callback function - create tree-lines path
*
*****/
function _root_init(_this) {
var SVGDoc = Tree.parent.getOwnerDocument();

Tree.root = _this;

var obj = SVGDoc.createElement("clipPath");
obj.setAttribute("id","treeClip");

Tree.clip = SVGDoc.createElement("rect");
obj.appendChild(Tree.clip);
Tree.parent.appendChild( obj );

Tree.backBox = SVGDoc.createElement("g");

Tree.background = SVGDoc.createElement("rect");
Tree.background.setAttribute("style", "display:none;");
Tree.backBox.appendChild( Tree.background );

Tree.scrollBars = SVGDoc.createElement("path");
Tree.scrollBars.setAttribute("style", "display:none;");
Tree.scrollBars.addEventListener("mousedown", sb_down, false);
Tree.backBox.appendChild( Tree.scrollBars );

Tree.scrollEnd = SVGDoc.createElement("rect");
Tree.scrollEnd.setAttribute("width",Tree.BUTTON_SIZE+1);
Tree.scrollEnd.setAttribute("height",Tree.BUTTON_SIZE+1);
Tree.scrollEnd.setAttribute("style", "display:none;");
Tree.backBox.appendChild( Tree.scrollEnd );

Tree.hSb = SVGDoc.getElementById(Tree.ID_BUTTON).cloneNode(true);
Tree.hSb.setAttribute("style", "display:none;");
Tree.backBox.appendChild( Tree.hSb );
Tree.hScroll = 0;

Tree.vSb = SVGDoc.getElementById(Tree.ID_BUTTON).cloneNode(true);
Tree.vSb.setAttribute("style", "display:none;");
Tree.backBox.appendChild( Tree.vSb );
Tree.vScroll = 0;

Tree.highBorder = SVGDoc.createElement("path");
Tree.highBorder.setAttribute("style", "display:none;");
Tree.backBox.appendChild( Tree.highBorder );

Tree.lowBorder = SVGDoc.createElement("path");
Tree.lowBorder.setAttribute("style", "display:none;");
Tree.backBox.appendChild( Tree.lowBorder );

Tree.treeClip = SVGDoc.createElement("g");
Tree.treeClip.setAttribute("style", "clip-path:url(#treeClip);");

Tree.treeBox = SVGDoc.createElement("g");
Tree.treeBox.setAttribute("style", Tree.TEXT_STYLE);
Tree.treeClip.appendChild( Tree.treeBox );

Tree.lines = SVGDoc.createElement("path");
Tree.lines.setAttribute("style", Tree.LINES_STYLE);
Tree.linesData = "";
Tree.treeBox.appendChild( Tree.lines );


Tree.parent.appendChild( Tree.backBox );
Tree.parent.appendChild( Tree.treeClip );
}


/*****
*
* constructor: create node - create corresponding svg data
*
*****/
function Node(parentIndex, index, caption, collapsed) {

if ((!parentIndex) && (index == 0)) _root_init(this);

this.members = new Object();
this.members.caption = caption;

this.pIndex = parentIndex;
this.index = index;
this.collapsed = collapsed;

this.link = null;
this.more = null;
this.less = null;
this.end = null;
this.text = null;
this.box = null;

this.child = null;
this.next = null;

this.initNode();
}


/*****
*
* initNode: duplicate svg objects and append it to group
*
*****/
Node.prototype.initNode = function() {
var SVGDoc = Tree.parent.getOwnerDocument();

this.link = SVGDoc.createElement("a");
this.link.setAttribute("id", "_node_"+this.pIndex+this.index);
this.link.setAttribute("href", "JavaScript://");
this.link.setAttribute("style", "display:none;");
this.link.addEventListener("mousedown", selectNode, false);

this.more = SVGDoc.getElementById(Tree.ID_PLUS).cloneNode(true);
this.more.setAttribute("style", "display:none;");
this.link.appendChild( this.more );

this.less = SVGDoc.getElementById(Tree.ID_MINUS).cloneNode(true);
this.less.setAttribute("style", "display:none;");
this.link.appendChild( this.less );

this.end = SVGDoc.getElementById(Tree.ID_END).cloneNode(true);
this.end.setAttribute("style", "display:none;");
this.link.appendChild( this.end );

this.box = SVGDoc.createElement("rect");
this.box.setAttribute("style", "display:none;");
this.link.appendChild( this.box );

this.text = SVGDoc.createElement("text");
this.text.setAttribute("x", Tree.TEXT_XOFFSET);
this.text.setAttribute("y", Tree.TEXT_YOFFSET);
this.text.setAttribute("style", "display:none;"+Tree.TEXT_NORMAL);
this.text.appendChild(SVGDoc.createTextNode(this.members.caption));
this.link.appendChild( this.text );

Tree.treeBox.appendChild( this.link );
}


/*****
*
* addNext: recursive find the last brother item and add a new node
*
*****/
Node.prototype.addNext = function(caption, collapsed) {
if (this.next) return this.next.addNext(caption, collapsed);
else {
this.next = new Node(this.pIndex,this.index+1, caption, collapsed);
return this.next;
}
}


/*****
*
* addChild: ask the child to add a brother node
*
*****/
Node.prototype.addChild = function(caption, collapsed) {
if (this.pIndex.length+1 > Tree.depth) Tree.depth = this.pIndex.length+1;

if (this.child) return this.child.addNext(caption, collapsed);
else {
this.child = new Node(this.pIndex+this.index,0, caption, collapsed);
return this.child;
}
}


/*****
*
* organize: recursive show/hide and set position of nodes
*
*****/
Node.prototype.organize = function(visible, x,y) {
this.x = x;
this.y = y;

if (!visible) {
this.link.setAttribute("style", "display:none");

if (this.child) this.child.organize(false,0,0, true);
if (this.next) this.next.organize(false,0,0, false);
return 0;
}

if (this.pIndex)
Tree.linesData += 
"M" + (x-Tree.CHILD_TAB+.5) + "," + (y-Tree.ITEM_HEIGHT+.5) + "v" + (Tree.ITEM_HEIGHT)
+ "M" + (x+.5) + "," + (y+.5) + "h-" + (Tree.CHILD_TAB);

if (!this.child) {
this.more.setAttribute("style", "display:none;");
this.less.setAttribute("style", "display:none;");
this.end.setAttribute("style", "");

else if (this.collapsed) {
this.more.setAttribute("style", "");
this.less.setAttribute("style", "display:none;");
this.end.setAttribute("style", "display:none;");

else {
this.more.setAttribute("style", "display:none;");
this.less.setAttribute("style", "display:visible;");
this.end.setAttribute("style", "display:none;");
}

if (this.selected)
this.text.setAttribute("style", "display:visible;"+Tree.TEXT_SELECT); else
this.text.setAttribute("style", "display:visible;"+Tree.TEXT_NORMAL);

var b = this.text.getBBox();
this.box.setAttribute("x",b.x-3);
this.box.setAttribute("y",b.y-3);
this.box.setAttribute("width",b.width+6);
this.box.setAttribute("height",b.height+4);
if (this.selected) 
this.box.setAttribute("style", "display:visible;"+Tree.SELBOX_STYLE); else
this.box.setAttribute("style", "display:visible;"+Tree.HIDE_STYLE+Tree.SELBOX_STYLE);

var t = x+b.x+b.width;
if (t > Tree.maxX) Tree.maxX = t;
var t = y+b.y+b.height;
if (t > Tree.maxY) Tree.maxY = t;

this.link.setAttribute("transform", "translate("+x+","+y+")");
this.link.setAttribute("style", "display:visible;");

if (this.child) {
var h = this.child.organize( (visible && !this.collapsed), x+Tree.CHILD_TAB,y+Tree.ITEM_HEIGHT);

if (!this.collapsed && this.next)
Tree.linesData += "M" + (x-Tree.CHILD_TAB+.5) + "," + (y+.5) + "v" + h;

else var h = 0;

if (this.next)
return this.next.organize( visible, x,y+Tree.ITEM_HEIGHT+h) + Tree.ITEM_HEIGHT+h; 
else {
if (!this.pIndex) Tree.lines.setAttribute("d",Tree.linesData);
return Tree.ITEM_HEIGHT+h;
}
}


/*****
*
* setSelected: select node, unselect previous node
*
*****/
Node.prototype.setSelected = function(state,update) {
// unselect previous node
if ((Tree.current) && (Tree.current != this)) Tree.current.selected = false;

// select node
this.selected = state;
if (state) 
Tree.current = this; 
else {
Tree.current = null;
//this.box.setAttribute("style", Tree.HIDE_STYLE);
}

// is asked, update
if (update) updateTree(true);
}


/*****
*
* getChild: return child #n of node
*
*****/
Node.prototype.getChild = function(index) {
if (!this.child) return null;

var n = this.child;
for (var i=0; i<index; i++)
if (!n.next) return null;
else n = n.next;

return n;
}


/*****
*
* selectNode: event click on node - collapse if has child - select if click on text
*
*****/
function selectNode(event) {
var obj = event.getTarget().getParentNode().getParentNode();
var id = obj.getAttribute("id").split("_");
var sel = false;

if (id[1]!="node") {
obj = event.getTarget().getParentNode();
id = obj.getAttribute("id").split("_");
sel = true;
}
var path = id[2];
var n = Tree.root;

if (path!="0")
for (var i=1; i<path.length; i++) {
n = n.getChild( parseInt(path.charAt(i)) );
if (!n) break;
}

if (n) {
if (!sel) n.collapsed = !n.collapsed;
var p = Tree.current;
if (sel) n.setSelected(true,false);

updateTree(true);

if ((sel) && (Tree.callBack)) 
if (p) Tree.callBack(event,n.members,p.members); else Tree.callBack(event,n.members,null);
}
}



/***** 
*
* scrollbars events
*
*****/

var hmoving = false;
var hoffs = 0;
var vmoving = false;
var voffs = 0;
var root;

function set_hScroll(h) {
Tree.hScroll = h;
if (Tree.hScroll < 0) Tree.hScroll = 0;
if (Tree.hScroll > Tree.maxScrollX) Tree.hScroll = Tree.maxScrollX;

Tree.hSb.setAttribute("transform", 
"translate("+(Tree.x+hPos())+","+(Tree.y+Tree.height-1-Tree.BUTTON_SIZE)+")");
Tree.treeBox.setAttribute("transform",
"translate(-"+Math.floor(Tree.hScroll)+",-"+Math.floor(Tree.vScroll)+")");
}
function set_vScroll(v) {
Tree.vScroll = v;
if (Tree.vScroll < 0) Tree.vScroll = 0;
if (Tree.vScroll > Tree.maxScrollY) Tree.vScroll = Tree.maxScrollY;

Tree.vSb.setAttribute("transform", 
"translate("+(Tree.x+Tree.width-1-Tree.BUTTON_SIZE)+","+(Tree.y+vPos()+1)+")");
Tree.treeBox.setAttribute("transform",
"translate(-"+Math.floor(Tree.hScroll)+",-"+Math.floor(Tree.vScroll)+")");
}

function hPos() {
// horiz sb button x
if (Tree.maxScrollX <= 0) return 0;
if (Tree.hScroll > Tree.maxScrollX) Tree.hScroll = Tree.maxScrollX;
return (Tree.xAvail-Tree.BUTTON_SIZE)*Tree.hScroll/Tree.maxScrollX;
}
function vPos() {
// vert sb button y
if (Tree.maxScrollY <= 0) return 0;
if (Tree.vScroll > Tree.maxScrollY) Tree.vScroll = Tree.maxScrollY;
return (Tree.yAvail-Tree.BUTTON_SIZE)*Tree.vScroll/Tree.maxScrollY;
}

function sb_down(evt) {
root = evt.getTarget().getOwnerDocument().getDocumentElement();
var x = (evt.clientX-root.currentTranslate.x)/root.currentScale - Tree.x-1;
var y = (evt.clientY-root.currentTranslate.y)/root.currentScale - Tree.y-1;

// horiz sb
if (Tree.hBar && (y >= Tree.yAvail)) {
var p = hPos();
hmoving = (Tree.hBar && (x >= p) && (x <= p+Tree.BUTTON_SIZE));
hoffs = x-p;

if (!hmoving) // page jump
if (hoffs > 0) 
set_hScroll(Tree.hScroll+Tree.pageSizeX); else
set_hScroll(Tree.hScroll-Tree.pageSizeX);
}
// vert sb
else {
var p = vPos();
vmoving = (Tree.vBar && (y >= p) && (y <= p+Tree.BUTTON_SIZE));
voffs = y-p;

if (!vmoving) // page jump
if (voffs > 0) 
set_vScroll(Tree.vScroll+Tree.pageSizeY); else
set_vScroll(Tree.vScroll-Tree.pageSizeY);
}
// prepare to move
if (hmoving || vmoving) {
root.addEventListener("mousemove",sb_move,false);
root.addEventListener("mouseup", sb_up,false);

}

function sb_move(evt) {
if (hmoving) {
var x = (evt.clientX-root.currentTranslate.x)/root.currentScale - Tree.x-1;

set_hScroll(Tree.maxScrollX*(x-hoffs)/(Tree.xAvail-Tree.BUTTON_SIZE));
}
else 
if (vmoving) {
var y = (evt.clientY-root.currentTranslate.y)/root.currentScale - Tree.y-1;

set_vScroll(Tree.maxScrollY*(y-voffs)/(Tree.yAvail-Tree.BUTTON_SIZE));
}
}

function sb_up(evt) {
root.removeEventListener("mousemove",sb_move,false);
root.removeEventListener("mouseup", sb_up,false);

hmoving = false;
vmoving = false;
}



/***** 
*
* super-tiny XML parser
*
*****/

var tokens;
var curr_index;
var collapsedDefault;


// *** parse XML text ***

function parse(parent,text,collapsed) {
Tree.parent = parent;

tokens = tokenize(text);

collapsedDefault = collapsed;
curr_index = 1; // first token always empty
parse_tags(null);
}


// *** find caption attribut in token[curr_index] ***

function get_caption() {
for (var i=1; i<tokens[curr_index].length; i++)
if (tokens[curr_index][i][0] == "caption") 
return tokens[curr_index][i][1].substr(1, tokens[curr_index][i][1].length-2 );
}


// *** add all other attributs in token[curr_index] ***

function add_attributs(node) {
for (var i=1; i<tokens[curr_index].length; i++)
if ((tokens[curr_index][i][0] != "/") && (tokens[curr_index][i][0] != "caption"))
eval("node.members."+tokens[curr_index][i][0]+"="+tokens[curr_index][i][1]+";");
}

// *** recursive parsing ***

function parse_tags(parentNode) {

if ((curr_index < tokens.length) && (tokens[curr_index].length >= 0)) {

// if closing tag, stop
if (tokens[curr_index][0][0].charAt(0) == "/") return;

else {
// if node element
if (tokens[curr_index][0][0] == "item") {

// if no root node
if (!Tree.root) { 
Tree.root = new Node("",0, get_caption(), collapsedDefault);
parentNode = Tree.root;
var n = Tree.root;
}
// create new node
else var n = parentNode.addChild(get_caption(),collapsedDefault);

add_attributs(n);
}

// if element has children
if (tokens[curr_index][tokens[curr_index].length-1] != "/") {
curr_index++;
parse_tags(n);
}

// parse next element
curr_index++;
parse_tags(parentNode);
}
}
}


// *** split XML string ***

function tokenize(text) {
// cleaning xml
res = clean(text).split("<");

// split /tag and /attributs
for (var i=0; i<res.length; i++) {
if (res[i].length > 0) res[i] = res[i].split(" ");

for (var j=0; j<res[i].length; j++) {
if (res[i][j].length > 0) {
res[i][j] = res[i][j].split("=");
if (res[i][j][1]) res[i][j][1] = restaureSpaces(res[i][j][1]);
}
}
}
return res;
}


// *** restaure spaces converted to _

function restaureSpaces(s) {
var res = "";
var c;
for (var i=0; i < s.length; i++) {
c = s.charAt(i);
if (c != "_") res += c; else res += " ";
}
return res;
}

// *** clean XML string ***

function clean(s) {
var i = 0;
var c;
var res = "";
var save = 0;
var nosp;

while (i < s.length) {
// skip blank
nosp = 0;
c = s.charAt(i);
while ((i < s.length) && 
(c==" ") || (c=="\n") || (c=="\t") || (c=="=") || (c==">") || (c=="/")) {
if (c=="=") { res += "="; nosp=1; }
if (c=="/") { 
if (res.charAt(res.length-1)=="<") res += "/"; else res += " /"; 
nosp=1; 
}
if (c==">") { nosp=1; }
if (save) res += "_";
i++; c = s.charAt(i); 
}
if (!nosp && !save) res += " ";

// get word
while ((i < s.length) && 
(c!=" ") && (c!="\n") && (c!="\t") && (c!="=") && (c!=">") && (c!="/")) { 
res += c;
if (c == '\"') save = 1-save;
i++; c = s.charAt(i);
}
}
return res;
}

var SVGDoc;
var SVGRoot;

var root = null;
var sizeBox = null;
var moving = false;
var x = 0;
var y = 0;
var sizeX = 0;
var sizeY = 0;

var xml = '<item caption="Node: Top" id="0">'
+' <item caption="Node Top.1" id="1">'
+' <item caption="Node Top.1.1" id="2">'
+' <item caption="Node Top.1.1.1" id="3">'
+' <item caption="Node Top.1.1.1.1" id="1">'
+' <item caption="Node Top.1.1.1.1.1" id="2">'
+' <item caption="Node Top.1.1.1.1.1.1" id="3"/>'
+' </item>'
+' <item caption="Node Top.1.1.1.1.2" id="4" />'
+' </item>'
+' <item caption="Node Top.1.1.1.2" id="5" />'
+' <item caption="Node Top.1.1.1.3" id="6">'
+' <item caption="Node Top.1.1.1.3.1" id="7"/>'
+' </item>'
+' </item>'
+' </item>'
+' <item caption="Node Top.1.2" id="4" />'
+' </item>'
+' <item caption="Node Top.2" id="5" />'
+' <item caption="Node Top.3" id="6">'
+' <item caption="Node Top.3.1" id="7"/>'
+' </item>'
+' <item caption="Node Top.4" id="1">'
+' <item caption="Node Top.4.1" id="2">'
+' <item caption="Node Top.4.1.1" id="3">'
+' <item caption="Node Top.4.1.1.1" id="1">'
+' <item caption="Node Top.4.1.1.1.1" id="2">'
+' <item caption="Node Top.4.1.1.1.1.1" id="3">'
+' <item caption="Node Top.4.1.1.1.1.1.1" id="1">'
+' <item caption="Node Top.4.1.1.1.1.1.1.1" id="2">'
+' <item caption="Node Top.4.1.1.1.1.1.1.1.1" id="3"/>'
+' </item>'
+' <item caption="I\'m small ;)" id="4" />'
+' </item>'
+' <item caption="Node Top.4.1.1.1.1.1.2" id="5" />'
+' <item caption="Node Top.4.1.1.1.1.1.3" id="6">'
+' <item caption="Node Top.4.1.1.1.1.1.3.1" id="7"/>'
+' </item>'
+' </item>'
+' </item>'
+' <item caption="Node Top.4.1.1.1.2" id="4" />'
+' </item>'
+' <item caption="Node Top.4.1.1.2" id="5" />'
+' <item caption="Node Top.4.1.1.3" id="6">'
+' <item caption="Node Top.4.1.1.3.1" id="7"/>'
+' </item>'
+' </item>'
+' </item>'
+' <item caption="Node Top.4.2" id="4" />'
+' </item>'
+' <item caption="Node Top.5" id="5">'
+' <item caption="Node Top.5.1" id="1">'
+' <item caption="Node Top.5.1.1" id="2">'
+' <item caption="Node Top.5.1.1.1" id="3"/>'
+' </item>'
+' <item caption="Node Top.5.1.2" id="4" />'
+' </item>'
+' <item caption="Node Top.5.2" id="5" />'
+' <item caption="Node Top.5.3" id="6">'
+' <item caption="Node Top.5.3.1" id="7"/>'
+' </item>'
+' </item>'
+' <item caption="Node Top.6" id="6">'
+' <item caption="Node Top.6.1" id="7"/>'
+' </item>'
+'</item>';


function nodeClick(evt,node,prev) {
SVGDoc.getElementById("info").getFirstChild.setData
("CallBack with selected node {caption:"+node.caption+", id:"+node.id+"}");
}

function init(evt,truc) {
SVGDoc = evt.getTarget().getOwnerDocument();
SVGRoot = SVGDoc.getDocumentElement();

parse(SVGDoc.getElementById("tree"),xml,false);
Tree.root.collapsed = false;
Tree.callBack = nodeClick;
updateTree(true,10,10,300,300);

root=SVGDoc.getElementById("root");
root.addEventListener("mousemove", onmove, false);
root.addEventListener("mousedown", ondown, false);
root.addEventListener("mouseup", onup, false);

sizeBox=SVGDoc.getElementById("sizeBox");
sizeX = Tree.x+Tree.width;
sizeY = Tree.y+Tree.height;
sizeBox.setAttribute("x",sizeX);
sizeBox.setAttribute("y",sizeY);
}

function ondown(evt) {
moving = ( (x >= sizeX) && (x < sizeX+4) && (y >= sizeY) && (y < sizeY+5) );
}

function onmove(evt) {
x = (evt.clientX-root.currentTranslate.x)/root.currentScale;
y = (evt.clientY-root.currentTranslate.y)/root.currentScale;

if (moving) {
if (x < Tree.x+50) x = Tree.x+50;
if (y < Tree.y+50) y = Tree.y+50;
sizeX = x-2;
sizeY = y-2;
sizeBox.setAttribute("x",sizeX);
sizeBox.setAttribute("y",sizeY);

updateTree(false,Tree.x,Tree.y,x-Tree.x-2,y-Tree.y-2);
}
}

function onup(evt) {
moving = false;
}

]]></script>

<defs>
<g id="tree_plus">
<image x="-8" y="-6" width="16" height="13" xlink:href="fclose.png" />
</g>
<g id="tree_minus">
<image x="-8" y="-6" width="16" height="13" xlink:href="fopen.png" />
</g>
<g id="tree_end">
<image x="-2" y="-5" width="9" height="10" xlink:href="file.png" />
</g>
<g id="tree_button">
<rect x="0" y="0" width="12" height="12" style="fill:silver;stroke:none;shape-rendering:optimizeSpeed;" />
<path d="M1,12v-11h11" style="fill:none;stroke:white;stroke-width:1;shape-rendering:optimizeSpeed;" />
<path d="M1,11h10v-10" style="fill:none;stroke:gray;stroke-width:1;shape-rendering:optimizeSpeed;" />
<path d="M0,12h12v-12" style="fill:none;stroke:black;stroke-width:1;shape-rendering:optimizeSpeed;" />
</g>
</defs>

<rect x="0" y="0" width="320" height="320" style="stroke:none;fill:darkblue;" mousemove="onmove(evt);" />



<rect id="sizeBox" x="-10" y="-10" width="4" height="5" style="stroke:none;fill:red;" />

<g style="font-family:Arial;font-size:10pt;fill:black;text-rendering:optimizeLegibility;pointer-events:none;">

<text id="info" x="30" y="75" style="fill:#ff3300;"> </text>
</g>
<g style="font-family:Arial;font-size:10pt;fill:blue;text-rendering:optimizeLegibility;">

</g>

<g id="tree"></g>
</g>
</svg>