Treeview.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352
  1. namespace("WM");
  2. WM.Treeview = (function()
  3. {
  4. var Margin = 10;
  5. var tree_template_html = " \
  6. <div class='Treeview'> \
  7. <div class='TreeviewItemChildren' style='width:90%;float:left'></div> \
  8. <div class='TreeviewScrollbarInset'> \
  9. <div class='TreeviewScrollbar'></div> \
  10. </div> \
  11. <div style='clear:both'></div> \
  12. </div>";
  13. var item_template_html = " \
  14. <div class='TreeViewItem basicfont notextsel'> \
  15. <img src='' class='TreeviewItemImage'> \
  16. <div class='TreeviewItemText'></div> \
  17. <div style='clear:both'></div> \
  18. <div class='TreeviewItemChildren'></div> \
  19. <div style='clear:both'></div> \
  20. </div>";
  21. // TODO: Remove parent_node (required for stuff that doesn't use the WM yet)
  22. function Treeview(x, y, width, height, parent_node)
  23. {
  24. // Cache initialisation options
  25. this.ParentNode = parent_node;
  26. this.Position = [ x, y ];
  27. this.Size = [ width, height ];
  28. this.Node = null;
  29. this.ScrollbarNode = null;
  30. this.SelectedItem = null;
  31. this.ContentsNode = null;
  32. // Setup options
  33. this.HighlightOnHover = false;
  34. this.EnableScrollbar = true;
  35. this.HorizontalLayoutDepth = 1;
  36. // Generate an empty tree
  37. this.Clear();
  38. }
  39. Treeview.prototype.SetHighlightOnHover = function(highlight)
  40. {
  41. this.HighlightOnHover = highlight;
  42. }
  43. Treeview.prototype.SetEnableScrollbar = function(enable)
  44. {
  45. this.EnableScrollbar = enable;
  46. }
  47. Treeview.prototype.SetHorizontalLayoutDepth = function(depth)
  48. {
  49. this.HorizontalLayoutDepth = depth;
  50. }
  51. Treeview.prototype.SetNodeSelectedHandler = function(handler)
  52. {
  53. this.NodeSelectedHandler = handler;
  54. }
  55. Treeview.prototype.Clear = function()
  56. {
  57. this.RootItem = new WM.TreeviewItem(this, null, null, null, null);
  58. this.GenerateHTML();
  59. }
  60. Treeview.prototype.Root = function()
  61. {
  62. return this.RootItem;
  63. }
  64. Treeview.prototype.ClearSelection = function()
  65. {
  66. if (this.SelectedItem != null)
  67. {
  68. DOM.Node.RemoveClass(this.SelectedItem.Node, "TreeviewItemSelected");
  69. this.SelectedItem = null;
  70. }
  71. }
  72. Treeview.prototype.SelectItem = function(item, mouse_pos)
  73. {
  74. // Notify the select handler
  75. if (this.NodeSelectedHandler)
  76. this.NodeSelectedHandler(item.Node, this.SelectedItem, item, mouse_pos);
  77. // Remove highlight from the old selection
  78. this.ClearSelection();
  79. // Swap in new selection and apply highlight
  80. this.SelectedItem = item;
  81. DOM.Node.AddClass(this.SelectedItem.Node, "TreeviewItemSelected");
  82. }
  83. Treeview.prototype.GenerateHTML = function()
  84. {
  85. // Clone the template and locate important nodes
  86. var old_node = this.Node;
  87. this.Node = DOM.Node.CreateHTML(tree_template_html);
  88. this.ChildrenNode = DOM.Node.FindWithClass(this.Node, "TreeviewItemChildren");
  89. this.ScrollbarNode = DOM.Node.FindWithClass(this.Node, "TreeviewScrollbar");
  90. DOM.Node.SetPosition(this.Node, this.Position);
  91. DOM.Node.SetSize(this.Node, this.Size);
  92. // Generate the contents of the treeview
  93. GenerateTree(this, this.ChildrenNode, this.RootItem.Children, 0);
  94. // Cross-browser (?) means of adding a mouse wheel handler
  95. var mouse_wheel_event = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel";
  96. DOM.Event.AddHandler(this.Node, mouse_wheel_event, Bind(OnMouseScroll, this));
  97. DOM.Event.AddHandler(this.Node, "dblclick", Bind(OnMouseDoubleClick, this));
  98. DOM.Event.AddHandler(this.Node, "mousedown", Bind(OnMouseDown, this));
  99. DOM.Event.AddHandler(this.Node, "mouseup", OnMouseUp);
  100. // Swap in the newly generated control node if it's already been attached to a parent
  101. if (old_node && old_node.parentNode)
  102. {
  103. old_node.parentNode.removeChild(old_node);
  104. this.ParentNode.appendChild(this.Node);
  105. }
  106. if (this.EnableScrollbar)
  107. {
  108. this.UpdateScrollbar();
  109. DOM.Event.AddHandler(this.ScrollbarNode, "mousedown", Bind(OnMouseDown_Scrollbar, this));
  110. DOM.Event.AddHandler(this.ScrollbarNode, "mouseup", Bind(OnMouseUp_Scrollbar, this));
  111. DOM.Event.AddHandler(this.ScrollbarNode, "mouseout", Bind(OnMouseUp_Scrollbar, this));
  112. DOM.Event.AddHandler(this.ScrollbarNode, "mousemove", Bind(OnMouseMove_Scrollbar, this));
  113. }
  114. else
  115. {
  116. DOM.Node.Hide(DOM.Node.FindWithClass(this.Node, "TreeviewScrollbarInset"));
  117. }
  118. }
  119. Treeview.prototype.UpdateScrollbar = function()
  120. {
  121. if (!this.EnableScrollbar)
  122. return;
  123. var scrollbar_scale = Math.min((this.Node.offsetHeight - Margin * 2) / this.ChildrenNode.offsetHeight, 1);
  124. this.ScrollbarNode.style.height = parseInt(scrollbar_scale * 100) + "%";
  125. // Shift the scrollbar container along with the parent window
  126. this.ScrollbarNode.parentNode.style.top = this.Node.scrollTop;
  127. var scroll_fraction = this.Node.scrollTop / (this.Node.scrollHeight - this.Node.offsetHeight);
  128. var max_height = this.Node.offsetHeight - Margin;
  129. var max_scrollbar_offset = max_height - this.ScrollbarNode.offsetHeight;
  130. var scrollbar_offset = scroll_fraction * max_scrollbar_offset;
  131. this.ScrollbarNode.style.top = scrollbar_offset;
  132. }
  133. function GenerateTree(self, parent_node, items, depth)
  134. {
  135. if (items.length == 0)
  136. return null;
  137. for (var i in items)
  138. {
  139. var item = items[i];
  140. // Create the node for this item and locate important nodes
  141. var node = DOM.Node.CreateHTML(item_template_html);
  142. var img = DOM.Node.FindWithClass(node, "TreeviewItemImage");
  143. var text = DOM.Node.FindWithClass(node, "TreeviewItemText");
  144. var children = DOM.Node.FindWithClass(node, "TreeviewItemChildren");
  145. // Attach the item to the node
  146. node.TreeviewItem = item;
  147. item.Node = node;
  148. // Add the class which highlights selection on hover
  149. if (self.HighlightOnHover)
  150. DOM.Node.AddClass(node, "TreeviewItemHover");
  151. // Instruct the children to wrap around
  152. if (depth >= self.HorizontalLayoutDepth)
  153. node.style.cssFloat = "left";
  154. if (item.OpenImage == null || item.CloseImage == null)
  155. {
  156. // If there no images, remove the image node
  157. node.removeChild(img);
  158. }
  159. else
  160. {
  161. // Set the image source to open
  162. img.src = item.OpenImage.src;
  163. img.style.width = item.OpenImage.width;
  164. img.style.height = item.OpenImage.height;
  165. item.ImageNode = img;
  166. }
  167. // Setup the text to display
  168. text.innerHTML = item.Label;
  169. // Add the div to the parent and recurse into children
  170. parent_node.appendChild(node);
  171. GenerateTree(self, children, item.Children, depth + 1);
  172. item.ChildrenNode = children;
  173. }
  174. // Clear the wrap-around
  175. if (depth >= self.HorizontalLayoutDepth)
  176. DOM.Node.AppendClearFloat(parent_node.parentNode);
  177. }
  178. function OnMouseScroll(self, evt)
  179. {
  180. // Get mouse wheel movement
  181. var delta = evt.detail ? evt.detail * -1 : evt.wheelDelta;
  182. delta *= 8;
  183. // Scroll the main window with wheel movement and clamp
  184. self.Node.scrollTop -= delta;
  185. self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2);
  186. self.UpdateScrollbar();
  187. }
  188. function OnMouseDoubleClick(self, evt)
  189. {
  190. DOM.Event.StopDefaultAction(evt);
  191. // Get the tree view item being clicked, if any
  192. var node = DOM.Event.GetNode(evt);
  193. var tvitem = GetTreeviewItemFromNode(self, node);
  194. if (tvitem == null)
  195. return;
  196. if (tvitem.Children.length)
  197. tvitem.Toggle();
  198. }
  199. function OnMouseDown(self, evt)
  200. {
  201. DOM.Event.StopDefaultAction(evt);
  202. // Get the tree view item being clicked, if any
  203. var node = DOM.Event.GetNode(evt);
  204. var tvitem = GetTreeviewItemFromNode(self, node);
  205. if (tvitem == null)
  206. return;
  207. // If clicking on the image, expand any children
  208. if (node.tagName == "IMG" && tvitem.Children.length)
  209. {
  210. tvitem.Toggle();
  211. }
  212. else
  213. {
  214. var mouse_pos = DOM.Event.GetMousePosition(evt);
  215. self.SelectItem(tvitem, mouse_pos);
  216. }
  217. }
  218. function OnMouseUp(evt)
  219. {
  220. // Event handler used merely to stop events bubbling up to containers
  221. DOM.Event.StopPropagation(evt);
  222. }
  223. function OnMouseDown_Scrollbar(self, evt)
  224. {
  225. self.ScrollbarHeld = true;
  226. // Cache the mouse height relative to the scrollbar
  227. self.LastY = evt.clientY;
  228. self.ScrollY = self.Node.scrollTop;
  229. DOM.Node.AddClass(self.ScrollbarNode, "TreeviewScrollbarHeld");
  230. DOM.Event.StopDefaultAction(evt);
  231. }
  232. function OnMouseUp_Scrollbar(self, evt)
  233. {
  234. self.ScrollbarHeld = false;
  235. DOM.Node.RemoveClass(self.ScrollbarNode, "TreeviewScrollbarHeld");
  236. }
  237. function OnMouseMove_Scrollbar(self, evt)
  238. {
  239. if (self.ScrollbarHeld)
  240. {
  241. var delta_y = evt.clientY - self.LastY;
  242. self.LastY = evt.clientY;
  243. var max_height = self.Node.offsetHeight - Margin;
  244. var max_scrollbar_offset = max_height - self.ScrollbarNode.offsetHeight;
  245. var max_contents_scroll = self.Node.scrollHeight - self.Node.offsetHeight;
  246. var scale = max_contents_scroll / max_scrollbar_offset;
  247. // Increment the local float variable and assign, as scrollTop is of type int
  248. self.ScrollY += delta_y * scale;
  249. self.Node.scrollTop = self.ScrollY;
  250. self.Node.scrollTop = Math.min(self.Node.scrollTop, (self.ChildrenNode.offsetHeight - self.Node.offsetHeight) + Margin * 2);
  251. self.UpdateScrollbar();
  252. }
  253. }
  254. function GetTreeviewItemFromNode(self, node)
  255. {
  256. // Walk up toward the tree view node looking for this first item
  257. while (node && node != self.Node)
  258. {
  259. if ("TreeviewItem" in node)
  260. return node.TreeviewItem;
  261. node = node.parentNode;
  262. }
  263. return null;
  264. }
  265. return Treeview;
  266. })();