375 lines
12 KiB
JavaScript
375 lines
12 KiB
JavaScript
|
/* This file is a part of CanDyDOC fileset.
|
||
|
File is written by Victor Nakoryakov and placed into the public domain.
|
||
|
|
||
|
This file is javascript with classes that represents native style tree control. */
|
||
|
|
||
|
var pmNone = 0;
|
||
|
var pmPlus = 1;
|
||
|
var pmMinus = 2;
|
||
|
|
||
|
var hlNone = 0;
|
||
|
var hlGrey = 1;
|
||
|
var hlSelected = 2;
|
||
|
|
||
|
function TreeView(hrefMode)
|
||
|
{
|
||
|
this.domEntry = document.createElement("div");
|
||
|
this.children = new Array();
|
||
|
this.selection = null;
|
||
|
this.hrefMode = hrefMode;
|
||
|
|
||
|
this.createBranch = function(text, iconSrc)
|
||
|
{
|
||
|
var root = new TreeNode(text, iconSrc, this.hrefMode);
|
||
|
root.owner = this;
|
||
|
this.children[ this.children.length ] = root;
|
||
|
this.domEntry.appendChild( root.domEntry );
|
||
|
return root;
|
||
|
}
|
||
|
|
||
|
this.branch = function(text)
|
||
|
{
|
||
|
var ret = null;
|
||
|
for (var i = 0; i < this.children.length; ++i)
|
||
|
if (this.children[i].textElement.data == text)
|
||
|
{
|
||
|
ret = this.children[i];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
this.domEntry.style.fontSize = "10px";
|
||
|
this.domEntry.style.cursor = "default";
|
||
|
this.domEntry.style.whiteSpace = "nowrap";
|
||
|
}
|
||
|
|
||
|
var idCounter = 0;
|
||
|
function TreeNode(text, iconSrc, hrefMode)
|
||
|
{
|
||
|
this.id = idCounter++;
|
||
|
this.parentNode = null;
|
||
|
this.children = new Array();
|
||
|
this.domEntry = document.createElement("div");
|
||
|
this.icon = document.createElement("img");
|
||
|
this.textElement = document.createTextNode(text);
|
||
|
this.textSpan = document.createElement("span");
|
||
|
this.lineDiv = document.createElement("div");
|
||
|
this.hierarchyImgs = new Array();
|
||
|
this.onclick = null;
|
||
|
|
||
|
function createIcon()
|
||
|
{
|
||
|
var img = document.createElement("img");
|
||
|
img.style.verticalAlign = "middle";
|
||
|
img.style.position = "relative";
|
||
|
img.style.top = "-1px";
|
||
|
img.width = 16;
|
||
|
img.height = 16;
|
||
|
return img;
|
||
|
}
|
||
|
|
||
|
function createHierarchyImage()
|
||
|
{
|
||
|
var img = createIcon();
|
||
|
img.pointsTop = false;
|
||
|
img.pointsBottom = false;
|
||
|
img.pointsRight = false;
|
||
|
img.pmState = pmNone;
|
||
|
return img;
|
||
|
}
|
||
|
|
||
|
function genHierarchyImageSrc(hierarchyImg)
|
||
|
{
|
||
|
var name = "";
|
||
|
if (hierarchyImg.pointsTop)
|
||
|
name += "t";
|
||
|
|
||
|
if (hierarchyImg.pointsBottom)
|
||
|
name += "b";
|
||
|
|
||
|
if (hierarchyImg.pointsRight)
|
||
|
name += "r";
|
||
|
|
||
|
if (hierarchyImg.pmState == pmPlus)
|
||
|
name += "p";
|
||
|
else if (hierarchyImg.pmState == pmMinus)
|
||
|
name += "m";
|
||
|
|
||
|
if (name == "")
|
||
|
name = "shim";
|
||
|
|
||
|
return "candydoc/img/tree/" + name + ".gif";
|
||
|
}
|
||
|
|
||
|
function setSrc(icon, src)
|
||
|
{
|
||
|
icon.src = src;
|
||
|
// After src change width and height are reseted in IE.
|
||
|
// Bug workaround:
|
||
|
icon.width = 16;
|
||
|
icon.height = 16;
|
||
|
}
|
||
|
|
||
|
this.createChild = function(text, iconSrc)
|
||
|
{
|
||
|
var child = new TreeNode(text, iconSrc, this.owner.hrefMode);
|
||
|
this.children[ this.children.length ] = child;
|
||
|
this.domEntry.appendChild( child.domEntry );
|
||
|
child.parentNode = this;
|
||
|
child.owner = this.owner;
|
||
|
|
||
|
// insert hierarchy images according to deepness level
|
||
|
// of created child.
|
||
|
|
||
|
if (this.children.length > 1)
|
||
|
{
|
||
|
// there were already added child before. So copy `level-1`
|
||
|
// hierarchy images from it.
|
||
|
|
||
|
var prevAddedChild = this.children[ this.children.length - 2 ];
|
||
|
|
||
|
for (var i = 0; i < prevAddedChild.hierarchyImgs.length - 1; ++i)
|
||
|
{
|
||
|
var prevAddedChildImg = prevAddedChild.hierarchyImgs[i];
|
||
|
var img = createHierarchyImage();
|
||
|
setSrc(img, prevAddedChildImg.src);
|
||
|
img.pointsTop = prevAddedChildImg.pointsTop;
|
||
|
img.pointsBottom = prevAddedChildImg.pointsBottom;
|
||
|
img.pointsRight = prevAddedChildImg.pointsRight;
|
||
|
img.pmState = prevAddedChildImg.pmState;
|
||
|
|
||
|
child.hierarchyImgs[ child.hierarchyImgs.length ] = img;
|
||
|
child.lineDiv.insertBefore(img, child.icon);
|
||
|
}
|
||
|
|
||
|
// change last hierarchy image of prevAddedChild from |_ to |-
|
||
|
var lastHierarchyImg = prevAddedChild.hierarchyImgs[ prevAddedChild.hierarchyImgs.length - 1 ];
|
||
|
lastHierarchyImg.pointsBottom = true;
|
||
|
setSrc(lastHierarchyImg, genHierarchyImageSrc(lastHierarchyImg));
|
||
|
|
||
|
// change hierarchy images of prevAddedChild's children on it's last
|
||
|
// level to |
|
||
|
prevAddedChild.addHierarchyTBLine(prevAddedChild.hierarchyImgs.length - 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// this is a first child. So copy `level-2`
|
||
|
// hierarchy images from parent, i.e. this.
|
||
|
|
||
|
for (var i = 0; i < this.hierarchyImgs.length - 1; ++i)
|
||
|
{
|
||
|
var parentImg = this.hierarchyImgs[i];
|
||
|
var img = createHierarchyImage();
|
||
|
setSrc(img, parentImg.src);
|
||
|
img.pointsTop = parentImg.pointsTop;
|
||
|
img.pointsBottom = parentImg.pointsBottom;
|
||
|
img.pointsRight = parentImg.pointsRight;
|
||
|
img.pmState = parentImg.pmState;
|
||
|
|
||
|
child.hierarchyImgs[ child.hierarchyImgs.length ] = img;
|
||
|
child.lineDiv.insertBefore(img, child.icon);
|
||
|
}
|
||
|
|
||
|
if (this.hierarchyImgs.length > 0) // we are not root
|
||
|
{
|
||
|
// change last hierarchy image of parent (i.e. this): add minus to it
|
||
|
var lastHierarchyImg = this.hierarchyImgs[ this.hierarchyImgs.length - 1];
|
||
|
lastHierarchyImg.pmState = pmMinus;
|
||
|
setSrc(lastHierarchyImg, genHierarchyImageSrc(lastHierarchyImg));
|
||
|
lastHierarchyImg.owner = this;
|
||
|
lastHierarchyImg.onclick = new Function("e", "this.owner.processPMClick(e);");
|
||
|
|
||
|
// make decision on image on `level-1`. It depends on parent's (ie this)
|
||
|
// image on same level.
|
||
|
var parentL1HierarchyImg = lastHierarchyImg;
|
||
|
var l1HierarchyImg = createHierarchyImage();
|
||
|
if (parentL1HierarchyImg.pointsBottom)
|
||
|
{
|
||
|
l1HierarchyImg.pointsTop = true;
|
||
|
l1HierarchyImg.pointsBottom = true;
|
||
|
}
|
||
|
setSrc(l1HierarchyImg, genHierarchyImageSrc(l1HierarchyImg));
|
||
|
child.hierarchyImgs[ child.hierarchyImgs.length ] = l1HierarchyImg;
|
||
|
child.lineDiv.insertBefore(l1HierarchyImg, child.icon);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// in any case on last level our child will have icon |_
|
||
|
var img = createHierarchyImage();
|
||
|
img.pointsTop = true;
|
||
|
img.pointsRight = true;
|
||
|
setSrc(img, genHierarchyImageSrc(img));
|
||
|
|
||
|
child.hierarchyImgs[ child.hierarchyImgs.length ] = img;
|
||
|
child.lineDiv.insertBefore(img, child.icon);
|
||
|
|
||
|
return child;
|
||
|
}
|
||
|
|
||
|
this.lastChild = function()
|
||
|
{
|
||
|
return this.children[ this.children.length - 1 ];
|
||
|
}
|
||
|
|
||
|
this.child = function(text)
|
||
|
{
|
||
|
var ret = null;
|
||
|
for (var i = 0; i < this.children.length; ++i)
|
||
|
if (this.children[i].textElement.data == text)
|
||
|
{
|
||
|
ret = this.children[i];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
this.addHierarchyTBLine = function(level)
|
||
|
{
|
||
|
for (var i = 0; i < this.children.length; ++i)
|
||
|
{
|
||
|
var img = this.children[i].hierarchyImgs[level];
|
||
|
img.pointsTop = true;
|
||
|
img.pointsBottom = true;
|
||
|
setSrc(img, genHierarchyImageSrc(img));
|
||
|
this.children[i].addHierarchyTBLine(level);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.expand = function()
|
||
|
{
|
||
|
var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ];
|
||
|
|
||
|
if (img.pmState == pmPlus)
|
||
|
{
|
||
|
img.pmState = pmMinus;
|
||
|
setSrc(img, genHierarchyImageSrc(img));
|
||
|
|
||
|
for (var i = 0; i < this.children.length; ++i)
|
||
|
this.children[i].domEntry.style.display = "";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.collapse = function()
|
||
|
{
|
||
|
var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ];
|
||
|
|
||
|
if (img.pmState == pmMinus)
|
||
|
{
|
||
|
img.pmState = pmPlus;
|
||
|
setSrc(img, genHierarchyImageSrc(img));
|
||
|
|
||
|
for (var i = 0; i < this.children.length; ++i)
|
||
|
this.children[i].domEntry.style.display = "none";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.toggle = function()
|
||
|
{
|
||
|
var img = this.hierarchyImgs[ this.hierarchyImgs.length - 1 ];
|
||
|
if (img.pmState == pmMinus)
|
||
|
this.collapse();
|
||
|
else
|
||
|
this.expand();
|
||
|
}
|
||
|
|
||
|
this.select = function()
|
||
|
{
|
||
|
if (this.owner.selection != this)
|
||
|
{
|
||
|
if (this.owner.selection)
|
||
|
this.owner.selection.setHighlight(hlNone);
|
||
|
|
||
|
this.owner.selection = this;
|
||
|
this.setHighlight(hlSelected);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.setHighlight = function(mode)
|
||
|
{
|
||
|
if (mode == hlNone)
|
||
|
{
|
||
|
this.textSpan.style.backgroundColor = "";
|
||
|
this.textSpan.style.color = "";
|
||
|
this.textSpan.style.border = "";
|
||
|
}
|
||
|
else if (mode == hlGrey)
|
||
|
{
|
||
|
this.textSpan.style.backgroundColor = "#aaaaaa";
|
||
|
this.textSpan.style.color = "";
|
||
|
this.textSpan.style.border = "";
|
||
|
}
|
||
|
else if (mode == hlSelected)
|
||
|
{
|
||
|
this.textSpan.style.backgroundColor = "3399cc";
|
||
|
this.textSpan.style.color = "white";
|
||
|
this.textSpan.style.border = "dotted 1px red";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this.setOnclick = function(proc)
|
||
|
{
|
||
|
this.onclick = proc;
|
||
|
}
|
||
|
|
||
|
this.setRef = function(url)
|
||
|
{
|
||
|
if (this.anchor)
|
||
|
this.anchor.href = url;
|
||
|
}
|
||
|
|
||
|
this.processPMClick = function(e)
|
||
|
{
|
||
|
this.toggle();
|
||
|
|
||
|
// prevent this line selection, stop bubbling
|
||
|
if (e)
|
||
|
e.stopPropagation(); // Mozilla way
|
||
|
if (window.event)
|
||
|
window.event.cancelBubble = true; // IE way
|
||
|
}
|
||
|
|
||
|
this.processOnclick = function()
|
||
|
{
|
||
|
this.select();
|
||
|
if (this.onclick instanceof Function)
|
||
|
this.onclick();
|
||
|
}
|
||
|
|
||
|
///////////////////////////////////////////////////////////////////////////
|
||
|
if (iconSrc)
|
||
|
this.icon.src = iconSrc;
|
||
|
else
|
||
|
{
|
||
|
this.icon.width = 0;
|
||
|
this.icon.height = 0;
|
||
|
}
|
||
|
|
||
|
this.icon.style.verticalAlign = "middle";
|
||
|
this.icon.style.position = "relative";
|
||
|
this.icon.style.top = "-1px";
|
||
|
this.icon.style.paddingRight = "2px";
|
||
|
|
||
|
if (!hrefMode)
|
||
|
{
|
||
|
this.textSpan.appendChild( this.textElement );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
this.anchor = document.createElement("a");
|
||
|
this.anchor.appendChild( this.textElement );
|
||
|
this.textSpan.appendChild( this.anchor );
|
||
|
}
|
||
|
|
||
|
this.lineDiv.appendChild( this.icon );
|
||
|
this.lineDiv.appendChild( this.textSpan );
|
||
|
this.domEntry.appendChild( this.lineDiv );
|
||
|
|
||
|
this.lineDiv.owner = this;
|
||
|
|
||
|
if (!hrefMode)
|
||
|
this.lineDiv.onclick = new Function("this.owner.processOnclick();");
|
||
|
}
|