353 lines
9.7 KiB
JavaScript
353 lines
9.7 KiB
JavaScript
|
|
||
|
namespace("WM");
|
||
|
|
||
|
|
||
|
WM.Treeview = (function()
|
||
|
{
|
||
|
var Margin = 10;
|
||
|
|
||
|
|
||
|
var tree_template_html = " \
|
||
|
<div class='Treeview'> \
|
||
|
<div class='TreeviewItemChildren' style='width:90%;float:left'></div> \
|
||
|
<div class='TreeviewScrollbarInset'> \
|
||
|
<div class='TreeviewScrollbar'></div> \
|
||
|
</div> \
|
||
|
<div style='clear:both'></div> \
|
||
|
</div>";
|
||
|
|
||
|
|
||
|
var item_template_html = " \
|
||
|
<div class='TreeViewItem basicfont notextsel'> \
|
||
|
<img src='' class='TreeviewItemImage'> \
|
||
|
<div class='TreeviewItemText'></div> \
|
||
|
<div style='clear:both'></div> \
|
||
|
<div class='TreeviewItemChildren'></div> \
|
||
|
<div style='clear:both'></div> \
|
||
|
</div>";
|
||
|
|
||
|
|
||
|
// TODO: Remove parent_node (required for stuff that doesn't use the WM yet)
|
||
|
function Treeview(x, y, width, height, parent_node)
|
||
|
{
|
||
|
// Cache initialisation options
|
||
|
this.ParentNode = parent_node;
|
||
|
this.Position = [ x, y ];
|
||
|
this.Size = [ width, height ];
|
||
|
|
||
|
this.Node = null;
|
||
|
this.ScrollbarNode = null;
|
||
|
this.SelectedItem = null;
|
||
|
this.ContentsNode = null;
|
||
|
|
||
|
// Setup options
|
||
|
this.HighlightOnHover = false;
|
||
|
this.EnableScrollbar = true;
|
||
|
this.HorizontalLayoutDepth = 1;
|
||
|
|
||
|
// Generate an empty tree
|
||
|
this.Clear();
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.SetHighlightOnHover = function(highlight)
|
||
|
{
|
||
|
this.HighlightOnHover = highlight;
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.SetEnableScrollbar = function(enable)
|
||
|
{
|
||
|
this.EnableScrollbar = enable;
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.SetHorizontalLayoutDepth = function(depth)
|
||
|
{
|
||
|
this.HorizontalLayoutDepth = depth;
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.SetNodeSelectedHandler = function(handler)
|
||
|
{
|
||
|
this.NodeSelectedHandler = handler;
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.Clear = function()
|
||
|
{
|
||
|
this.RootItem = new WM.TreeviewItem(this, null, null, null, null);
|
||
|
this.GenerateHTML();
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.Root = function()
|
||
|
{
|
||
|
return this.RootItem;
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.ClearSelection = function()
|
||
|
{
|
||
|
if (this.SelectedItem != null)
|
||
|
{
|
||
|
DOM.Node.RemoveClass(this.SelectedItem.Node, "TreeviewItemSelected");
|
||
|
this.SelectedItem = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.SelectItem = function(item, mouse_pos)
|
||
|
{
|
||
|
// Notify the select handler
|
||
|
if (this.NodeSelectedHandler)
|
||
|
this.NodeSelectedHandler(item.Node, this.SelectedItem, item, mouse_pos);
|
||
|
|
||
|
// Remove highlight from the old selection
|
||
|
this.ClearSelection();
|
||
|
|
||
|
// Swap in new selection and apply highlight
|
||
|
this.SelectedItem = item;
|
||
|
DOM.Node.AddClass(this.SelectedItem.Node, "TreeviewItemSelected");
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.GenerateHTML = function()
|
||
|
{
|
||
|
// Clone the template and locate important nodes
|
||
|
var old_node = this.Node;
|
||
|
this.Node = DOM.Node.CreateHTML(tree_template_html);
|
||
|
this.ChildrenNode = DOM.Node.FindWithClass(this.Node, "TreeviewItemChildren");
|
||
|
this.ScrollbarNode = DOM.Node.FindWithClass(this.Node, "TreeviewScrollbar");
|
||
|
|
||
|
DOM.Node.SetPosition(this.Node, this.Position);
|
||
|
DOM.Node.SetSize(this.Node, this.Size);
|
||
|
|
||
|
// Generate the contents of the treeview
|
||
|
GenerateTree(this, this.ChildrenNode, this.RootItem.Children, 0);
|
||
|
|
||
|
// Cross-browser (?) means of adding a mouse wheel handler
|
||
|
var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
|
||
|
DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this));
|
||
|
|
||
|
DOM.Event.AddHandler(this.Node, "dblclick", Bind(OnMouseDoubleClick, this));
|
||
|
DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this));
|
||
|
DOM.Event.AddHandler(this.Node, "mouseup", OnMouseUp);
|
||
|
|
||
|
// Swap in the newly generated control node if it's already been attached to a parent
|
||
|
if (old_node && old_node.parentNode)
|
||
|
{
|
||
|
old_node.parentNode.removeChild(old_node);
|
||
|
this.ParentNode.appendChild(this.Node);
|
||
|
}
|
||
|
|
||
|
if (this.EnableScrollbar)
|
||
|
{
|
||
|
this.UpdateScrollbar();
|
||
|
DOM.Event.AddHandler(this.ScrollbarNode, "mousedown", Bind(OnMouseDown_Scrollbar, this));
|
||
|
DOM.Event.AddHandler(this.ScrollbarNode, "mouseup", Bind(OnMouseUp_Scrollbar, this));
|
||
|
DOM.Event.AddHandler(this.ScrollbarNode, "mouseout", Bind(OnMouseUp_Scrollbar, this));
|
||
|
DOM.Event.AddHandler(this.ScrollbarNode, "mousemove", Bind(OnMouseMove_Scrollbar, this));
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
DOM.Node.Hide(DOM.Node.FindWithClass(this.Node, "TreeviewScrollbarInset"));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
Treeview.prototype.UpdateScrollbar = function()
|
||
|
{
|
||
|
if (!this.EnableScrollbar)
|
||
|
return;
|
||
|
|
||
|
var scrollbar_scale = Math.min((this.Node.offsetHeight - Margin * 2) / this.ChildrenNode.offsetHeight, 1);
|
||
|
this.ScrollbarNode.style.height = parseInt(scrollbar_scale * 100) + "%";
|
||
|
|
||
|
// Shift the scrollbar container along with the parent window
|
||
|
this.ScrollbarNode.parentNode.style.top = this.Node.scrollTop;
|
||
|
|
||
|
var scroll_fraction = this.Node.scrollTop / (this.Node.scrollHeight - this.Node.offsetHeight);
|
||
|
var max_height = this.Node.offsetHeight - Margin;
|
||
|
var max_scrollbar_offset = max_height - this.ScrollbarNode.offsetHeight;
|
||
|
var scrollbar_offset = scroll_fraction * max_scrollbar_offset;
|
||
|
this.ScrollbarNode.style.top = scrollbar_offset;
|
||
|
}
|
||
|
|
||
|
|
||
|
function GenerateTree(self, parent_node, items, depth)
|
||
|
{
|
||
|
if (items.length == 0)
|
||
|
return null;
|
||
|
|
||
|
for (var i in items)
|
||
|
{
|
||
|
var item = items[i];
|
||
|
|
||
|
// Create the node for this item and locate important nodes
|
||
|
var node = DOM.Node.CreateHTML(item_template_html);
|
||
|
var img = DOM.Node.FindWithClass(node, "TreeviewItemImage");
|
||
|
var text = DOM.Node.FindWithClass(node, "TreeviewItemText");
|
||
|
var children = DOM.Node.FindWithClass(node, "TreeviewItemChildren");
|
||
|
|
||
|
// Attach the item to the node
|
||
|
node.TreeviewItem = item;
|
||
|
item.Node = node;
|
||
|
|
||
|
// Add the class which highlights selection on hover
|
||
|
if (self.HighlightOnHover)
|
||
|
DOM.Node.AddClass(node, "TreeviewItemHover");
|
||
|
|
||
|
// Instruct the children to wrap around
|
||
|
if (depth >= self.HorizontalLayoutDepth)
|
||
|
node.style.cssFloat = "left";
|
||
|
|
||
|
if (item.OpenImage == null || item.CloseImage == null)
|
||
|
{
|
||
|
// If there no images, remove the image node
|
||
|
node.removeChild(img);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// Set the image source to open
|
||
|
img.src = item.OpenImage.src;
|
||
|
img.style.width = item.OpenImage.width;
|
||
|
img.style.height = item.OpenImage.height;
|
||
|
item.ImageNode = img;
|
||
|
}
|
||
|
|
||
|
// Setup the text to display
|
||
|
text.innerHTML = item.Label;
|
||
|
|
||
|
// Add the div to the parent and recurse into children
|
||
|
parent_node.appendChild(node);
|
||
|
GenerateTree(self, children, item.Children, depth + 1);
|
||
|
item.ChildrenNode = children;
|
||
|
}
|
||
|
|
||
|
// Clear the wrap-around
|
||
|
if (depth >= self.HorizontalLayoutDepth)
|
||
|
DOM.Node.AppendClearFloat(parent_node.parentNode);
|
||
|
}
|
||
|
|
||
|
|
||
|
function OnMouseScroll(self, evt)
|
||
|
{
|
||
|
// Get mouse wheel movement
|
||
|
var delta = evt.detail ? evt.detail * -1 : evt.wheelDelta;
|
||
|
delta *= 8;
|
||
|
|
||
|
// Scroll the main window with wheel movement and clamp
|
||
|
self.Node.scrollTop -= delta;
|
||
|
self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2);
|
||
|
|
||
|
self.UpdateScrollbar();
|
||
|
}
|
||
|
|
||
|
|
||
|
function OnMouseDoubleClick(self, evt)
|
||
|
{
|
||
|
DOM.Event.StopDefaultAction(evt);
|
||
|
|
||
|
// Get the tree view item being clicked, if any
|
||
|
var node = DOM.Event.GetNode(evt);
|
||
|
var tvitem = GetTreeviewItemFromNode(self, node);
|
||
|
if (tvitem == null)
|
||
|
return;
|
||
|
|
||
|
if (tvitem.Children.length)
|
||
|
tvitem.Toggle();
|
||
|
}
|
||
|
|
||
|
|
||
|
function OnMouseDown(self, evt)
|
||
|
{
|
||
|
DOM.Event.StopDefaultAction(evt);
|
||
|
|
||
|
// Get the tree view item being clicked, if any
|
||
|
var node = DOM.Event.GetNode(evt);
|
||
|
var tvitem = GetTreeviewItemFromNode(self, node);
|
||
|
if (tvitem == null)
|
||
|
return;
|
||
|
|
||
|
// If clicking on the image, expand any children
|
||
|
if (node.tagName == "IMG" && tvitem.Children.length)
|
||
|
{
|
||
|
tvitem.Toggle();
|
||
|
}
|
||
|
|
||
|
else
|
||
|
{
|
||
|
var mouse_pos = DOM.Event.GetMousePosition(evt);
|
||
|
self.SelectItem(tvitem, mouse_pos);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
function OnMouseUp(evt)
|
||
|
{
|
||
|
// Event handler used merely to stop events bubbling up to containers
|
||
|
DOM.Event.StopPropagation(evt);
|
||
|
}
|
||
|
|
||
|
|
||
|
function OnMouseDown_Scrollbar(self, evt)
|
||
|
{
|
||
|
self.ScrollbarHeld = true;
|
||
|
|
||
|
// Cache the mouse height relative to the scrollbar
|
||
|
self.LastY = evt.clientY;
|
||
|
self.ScrollY = self.Node.scrollTop;
|
||
|
|
||
|
DOM.Node.AddClass(self.ScrollbarNode, "TreeviewScrollbarHeld");
|
||
|
DOM.Event.StopDefaultAction(evt);
|
||
|
}
|
||
|
|
||
|
|
||
|
function OnMouseUp_Scrollbar(self, evt)
|
||
|
{
|
||
|
self.ScrollbarHeld = false;
|
||
|
DOM.Node.RemoveClass(self.ScrollbarNode, "TreeviewScrollbarHeld");
|
||
|
}
|
||
|
|
||
|
|
||
|
function OnMouseMove_Scrollbar(self, evt)
|
||
|
{
|
||
|
if (self.ScrollbarHeld)
|
||
|
{
|
||
|
var delta_y = evt.clientY - self.LastY;
|
||
|
self.LastY = evt.clientY;
|
||
|
|
||
|
var max_height = self.Node.offsetHeight - Margin;
|
||
|
var max_scrollbar_offset = max_height - self.ScrollbarNode.offsetHeight;
|
||
|
var max_contents_scroll = self.Node.scrollHeight - self.Node.offsetHeight;
|
||
|
var scale = max_contents_scroll / max_scrollbar_offset;
|
||
|
|
||
|
// Increment the local float variable and assign, as scrollTop is of type int
|
||
|
self.ScrollY += delta_y * scale;
|
||
|
self.Node.scrollTop = self.ScrollY;
|
||
|
self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2);
|
||
|
|
||
|
self.UpdateScrollbar();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
function GetTreeviewItemFromNode(self, node)
|
||
|
{
|
||
|
// Walk up toward the tree view node looking for this first item
|
||
|
while (node && node != self.Node)
|
||
|
{
|
||
|
if ("TreeviewItem" in node)
|
||
|
return node.TreeviewItem;
|
||
|
|
||
|
node = node.parentNode;
|
||
|
}
|
||
|
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
return Treeview;
|
||
|
})();
|