2 JSCookTree v2.01. (c) Copyright 2002 by Heng Yuan
4 Permission is hereby granted, free of charge, to any person obtaining a
5 copy of this software and associated documentation files (the "Software"),
6 to deal in the Software without restriction, including without limitation
7 the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 and/or sell copies of the Software, and to permit persons to whom the
9 Software is furnished to do so, subject to the following conditions:
11 The above copyright notice and this permission notice shall be included
12 in all copies or substantial portions of the Software.
14 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 ITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 DEALINGS IN THE SOFTWARE.
26 // ctTreeInfo stores information about the current tree
28 function ctTreeInfo (nodeProperties, prefix, hideType, expandLevel)
30 // default node properties
31 this.nodeProperties = nodeProperties;
32 // current selected item in this tree
33 this.currentItem = null;
37 // 0: just open the current tree
38 // 1: close other branches in the same tree
39 // 2: close other branches in other trees as well
40 this.hideType = hideType;
41 // the deepest level of the tree is the always expaned
42 this.expandLevel = expandLevel;
43 // beginIndex is the first index of the tree item
45 // endIndex is same as beginIndex + # of items in the tree
49 function ctMenuInfo (id, idSub)
51 // id of the menu item that owns the sub menu
53 // the id of the sub menu
59 var _ctSelected = null;
60 var _ctSelectedId = null;
62 var _ctIDSubMenuCount = 0;
63 var _ctIDSubMenu = 'ctSubTreeID'; // for creating submenu id
65 var _ctCurrentItem = null; // the current menu item being selected;
67 var _ctNoAction = new Object (); // indicate that the item cannot be hovered.
69 var _ctItemList = new Array (); // a simple list of items
70 var _ctTreeList = new Array (); // a list of ctTreeInfo.
71 var _ctMenuList = new Array (); // a list of ctMenuInfo
73 var _ctMenuInitStr = ''; // initiation command that initiate menu items
75 // default node properties
76 var _ctNodeProperties =
80 // except themeLevel, all other attributes can be specified
81 // for each level of depth of the tree.
83 // HTML code to the left of a folder item
84 // first one is for folder closed, second one is for folder opened
85 folderLeft: [['', '']],
86 // HTML code to the right of a folder item
87 // first one is for folder closed, second one is for folder opened
88 folderRight: [['', '']],
89 // HTML code to the left of a regular item
91 // HTML code to the right of a regular item
93 // HTML code for the connector
94 // first one is for w/ having next sibling, second one is for no next sibling
95 folderConnect: [[['',''],['','']]],
96 itemConnect: [['',''],['','']],
97 // HTML code for spacers
98 // first one connects next, second one doesn"t
99 spacer: [[' ', ' ']],
100 // deepest level of theme specified
102 // tells JSCookTree to use <A> ancher tag to open links
103 // if this field is set to false, then JSCookTree would hand it.
106 //////////////////////////////////////////////////////////////////////
108 // Drawing Functions and Utility Functions
110 //////////////////////////////////////////////////////////////////////
113 // produce a new unique submenu id
115 function ctNewSubMenuID ()
117 return _ctIDSubMenu + (++_ctIDSubMenuCount);
121 // return the property string for the menu item
123 function ctActionItem ()
125 return ' onmouseover="ctItemMouseOver (this.parentNode)" onmouseout="ctItemMouseOut (this.parentNode)" onmousedown="ctItemMouseDown (this.parentNode)" onmouseup="ctItemMouseUp (this.parentNode)"';
128 function ctActionTitle ()
130 return ' onmouseup="ctSetSelectedItem(this.parentNode)" onmousedown="ctItemMouseDown (this.parentNode)"';
134 // return the property string for the menu item
136 function ctNoActionItem (item)
142 // used to determine the property string
144 function ctGetPropertyLevel (level, property)
146 return (level >= property.length) ? (property.length - 1) : level;
150 function ctCollapseTree (id)
152 var menu = ctGetObject (id).firstChild;
154 for (i = 0; i < menu.ctItems.length; ++i)
155 ctCloseFolder (menu.ctItems[i]);
159 // expand a tree such that upto level is exposed
161 function ctExpandTree (id, expandLevel)
163 if (expandLevel <= 0)
166 var obj = ctGetObject (id);
170 var thisMenu = obj.firstChild;
174 ctExpandTreeSub (thisMenu, expandLevel)
177 function ctExpandTreeSub (subMenu, expandLevel)
179 if (subMenu.ctLevel >= expandLevel)
183 for (i = 0; i < subMenu.ctItems.length; ++i)
185 item = subMenu.ctItems[i];
189 ctExpandTreeSub (ctGetObject (item.ctIdSub), expandLevel);
195 // expose a particular menu item use its link as the search value
197 function ctExposeItem (treeIndex, link)
199 if (treeIndex < 0 || treeIndex >= _ctTreeList.length)
201 var tree = _ctTreeList[treeIndex];
202 var endIndex = tree.endIndex;
204 for (i = tree.beginIndex; i < endIndex; ++i)
206 if (_ctItemList[i].length > 2 &&
207 _ctItemList[i][2] == link)
209 return ctExposeTreeIndex (treeIndex, i);
215 // expose a particular menu item using its index
217 function ctExposeTreeIndex (treeIndex, index)
219 var item = ctGetObject ('ctItemID' + (_ctTreeList[treeIndex].beginIndex + index)).parentNode;
223 var parentItem = ctGetThisMenu (item).ctParent;
225 ctExposeTreeIndexSub (parentItem);
227 ctSetSelectedItem (item);
231 function ctExposeTreeIndexSub (item)
233 var parentItem = ctGetThisMenu (item).ctParent;
235 ctExposeTreeIndexSub (parentItem);
240 // mark a particular menu item with id using its link
242 function ctMarkItem (treeIndex, link)
244 if (treeIndex < 0 || treeIndex >= _ctTreeList.length)
246 var tree = _ctTreeList[treeIndex];
247 var endIndex = tree.endIndex;
249 for (i = tree.beginIndex; i < endIndex; ++i)
251 if (_ctItemList[i].length > 2 &&
252 _ctItemList[i][2] == link)
254 var item = ctGetObject ('ctItemID' + (_ctTreeList[treeIndex].beginIndex + i)).parentNode;
257 if (item.id == "JSCookTreeItem")
258 item.id = 'JSCookTreeMarked';
265 // mark a particular menu item with id using index
267 function ctMarkTreeIndex (treeIndex, index)
269 var item = ctGetObject ('ctItemID' + (_ctTreeList[treeIndex].beginIndex + index)).parentNode;
272 if (item.id == "JSCookTreeItem")
273 item.id = 'JSCookTreeMarked';
278 // return the current selected node for the current tree
280 // treeItem treeItem is the table row of where the tree item is located
282 function ctGetSelectedItem (treeIndex)
284 if (_ctTreeList[treeIndex].hideType <= 1)
285 return _ctTreeList[treeIndex].currentItem;
287 return _ctCurrentItem;
291 // The function that builds the menu inside the specified element id.
293 function ctDraw (id, tree, nodeProperties, prefix, hideType, expandLevel)
295 var obj = ctGetObject (id);
298 nodeProperties = _ctNodeProperties;
306 //var treeIndex = _ctTreeList.push (new ctTreeInfo (nodeProperties, prefix, hideType, expandLevel)) - 1;
307 _ctTreeList[_ctTreeList.length] = new ctTreeInfo (nodeProperties, prefix, hideType, expandLevel);
308 var treeIndex = _ctTreeList.length - 1;
310 var beginIndex = _ctItemList.length;
313 var str = ctDrawSub (tree, true, null, treeIndex, 0, nodeProperties, prefix, '');
315 eval (_ctMenuInitStr);
316 //alert(_ctMenuInitStr);
319 var endIndex = _ctItemList.length;
321 _ctTreeList[treeIndex].beginIndex = beginIndex;
322 _ctTreeList[treeIndex].endIndex = endIndex;
324 if (_ctSelectedId != null)
326 ctExpandTree (id, endIndex);
331 ctExpandTree (id, expandLevel);
333 //document.write ('<textarea wrap="off" rows="15" cols="80">' + str + '</textarea><br>');
339 // draw the sub menu recursively
341 function ctDrawSub (subMenu, isMain, id, treeIndex, level, nodeProperties, prefix, indent)
344 if (lvl > nodeProperties.themeLevel)
345 lvl = nodeProperties.themeLevel;
347 var str = '<div class="' + prefix + 'TreeLevel' + lvl + '"';
349 str += ' id="' + id + '"';
365 var themeLevel = nodeProperties.themeLevel;
373 var className = ' class="' + prefix + 'Row"';
375 for (; i < subMenu.length; ++i)
381 //index = _ctItemList.push (item) - 1;
382 _ctItemList[_ctItemList.length] = item;
383 index = _ctItemList.length - 1;
385 hasChild = (item.length > 5);
386 idSub = hasChild ? ctNewSubMenuID () : null;
388 str += '<table cellspacing="0" class="' + prefix + 'Table">';
391 // #JSCookTreeFolderClose & #JSCookTreeFolderOpen
392 // are used in style sheet to control the animation of folder open/close
393 // Also, it tells status of the submenu
395 str += '<tr' + className;
397 str += ' id="JSCookTreeFolderClosed">';
399 // str += ' id="JSCookTreeItem">';
401 classStr = prefix + (hasChild ? 'Folder' : 'Item');
404 // markerStr is used to mark Spacer cell such that the item (<tr> tag)
405 // could be tracked in an alternative way
406 // _ctMenuInitStr is used to initate the menu item
408 itemID = 'ctItemID' + index;
409 markerStr = ' id="' + itemID + '"';
410 _ctMenuInitStr += 'ctSetupItem (ctGetObject ("' + itemID + '").parentNode,' + index + ',' + treeIndex + ',' + level + ',' + (idSub ? ('"' + idSub + '"') : 'null') + ');';
412 str += '<td class="' + classStr + 'Spacer"' + markerStr + '>' + indent;
416 if (item[0] == _ctNoAction)
418 str += ctNoActionItem (item, prefix);
419 str += '</tr></table>';
423 actionStr = ctActionItem ();
424 actionStr2 = ctActionTitle ();
426 str += '<td class="' + classStr + 'Left"' + actionStr + ' ' + item[4] + '>';
431 connectSelect = ctHasNextItem (i, subMenu) ? 0 : 1;
432 lvl = ctGetPropertyLevel (level, nodeProperties.folderConnect);
433 str += '<span class="JSCookTreeFolderClosed">' + nodeProperties.folderConnect[lvl][connectSelect][0] + '</span>' +
434 '<span class="JSCookTreeFolderOpen">' + nodeProperties.folderConnect[lvl][connectSelect][1] + '</span>';
438 connectSelect = ctHasNextItem (i, subMenu) ? 0 : 1;
439 lvl = ctGetPropertyLevel (level, nodeProperties.itemConnect);
440 str += nodeProperties.itemConnect[lvl][connectSelect];
443 if (item[0] != null && item[0] != _ctNoAction)
449 lvl = ctGetPropertyLevel (level, nodeProperties.folderLeft);
450 str += '<span class="JSCookTreeFolderClosed">' + nodeProperties.folderLeft[lvl][0] + '</span>' +
451 '<span class="JSCookTreeFolderOpen">' + nodeProperties.folderLeft[lvl][1] + '</span>';
455 lvl = ctGetPropertyLevel (level, nodeProperties.itemLeft);
456 str += nodeProperties.itemLeft[lvl];
460 str += '<td class="' + classStr + 'Text"' + actionStr2 + ' ' + item[4] + '>';
466 str += ' href="' + item[2] + '"';
468 str += ' target="' + item[3] + '"';
471 // if (item[4] != null)
472 // str += ' title="' + item[4] + '"';
474 // str += ' title="' + item[1] + '"';
476 str += '>' + item[1] + '</a></td>';
478 if (item[1] == _ctSelected)
480 _ctSelectedId = itemID;
483 str += '<td class="' + classStr + 'Right"' + actionStr + '>';
487 lvl = ctGetPropertyLevel (level, nodeProperties.folderRight);
488 str += '<span class="JSCookTreeFolderClosed">' + nodeProperties.folderRight[lvl][0] + '</span>' +
489 '<span class="JSCookTreeFolderOpen">' + nodeProperties.folderRight[lvl][1] + '</span>';
493 lvl = ctGetPropertyLevel (level, nodeProperties.itemRight);
494 str += nodeProperties.itemRight[lvl];
497 str += '</tr></table>';
501 childIndent = indent;
502 lvl = ctGetPropertyLevel (level, nodeProperties.spacer);
503 childIndent += nodeProperties.spacer[lvl][connectSelect];
505 str += ctDrawSub (item, false, idSub, treeIndex, level + 1, nodeProperties, prefix, childIndent);
514 //////////////////////////////////////////////////////////////////////
516 // Mouse Event Handling Functions
518 //////////////////////////////////////////////////////////////////////
521 // action should be taken for mouse moving in to the menu item
523 function ctItemMouseOver (item)
525 var treeItem = _ctItemList[item.ctIndex];
526 var isDefaultItem = ctIsDefaultItem (treeItem);
530 var className = ctGetDefaultClassName (item);
532 if (item.className == className)
533 item.className = className + 'Hover';
538 // action should be taken for mouse moving out of the menu item
540 function ctItemMouseOut (item)
542 if (ctIsDefaultItem (_ctItemList[item.ctIndex]))
544 var className = ctGetDefaultClassName (item);
546 if (item.className == (className + 'Hover') ||
547 item.className == (className + 'Active'))
549 var tree = _ctTreeList[item.ctTreeIndex];
550 var currentItem = (tree.hideType <= 1) ? tree.currentItem : _ctCurrentItem;
552 if (item == currentItem)
553 item.className = className + 'Selected';
555 item.className = className;
561 // action should be taken for mouse button down at a menu item
563 function ctItemMouseDown (item)
565 if (ctIsDefaultItem (_ctItemList[item.ctIndex]))
567 var className = ctGetDefaultClassName (item);
569 if (item.className == (className + 'Hover'))
570 item.className = className + 'Active';
575 // action should be taken for mouse button up at a menu item
577 function ctItemMouseUp (item)
581 // toggle the submenu
582 var subMenu = ctGetObject (item.ctIdSub);
583 if (subMenu.style.display == 'block')
585 ctCloseFolder (item);
592 ctSetSelectedItem (item);
595 function ctSelectSelected ()
597 if (_ctSelectedId != null)
599 var item = ctGetObject (_ctSelectedId).parentNode;
601 ctSetSelectedItem (item);
602 ctItemMouseDown (item);
607 // set the item as the selected item
609 function ctSetSelectedItem (item)
611 var tree = _ctTreeList[item.ctTreeIndex];
612 var hideType = tree.hideType;
617 otherItem = tree.currentItem;
619 otherItem = _ctCurrentItem;
621 if (otherItem != item)
625 // set otherItem to normal
628 if (ctIsDefaultItem (_ctItemList[otherItem.ctIndex]))
630 var className = ctGetDefaultClassName (otherItem);
631 if (otherItem.className == (className + 'Selected'))
632 otherItem.className = className;
635 // hide otherItem if required
636 if (hideType > 0 && otherItem)
637 ctHideMenu (otherItem, item);
640 // finally, set this item as selected
642 tree.currentItem = item;
644 _ctCurrentItem = item;
646 if (ctIsDefaultItem (_ctItemList[item.ctIndex]))
648 var className = ctGetDefaultClassName (item);
649 item.className = className + 'Selected';
654 //////////////////////////////////////////////////////////////////////
656 // Mouse Event Support Utility Functions
658 //////////////////////////////////////////////////////////////////////
661 // check if an item is in open form
663 function ctIsFolderOpen (item)
665 if (item.id == 'JSCookTreeFolderOpen')
671 // change an item into the open form
673 function ctOpenFolder (item)
675 if (ctIsFolderOpen (item))
679 var subMenu = ctGetObject (item.ctIdSub);
680 subMenu.style.display = 'block';
682 item.id = 'JSCookTreeFolderOpen';
687 // change an item into the closed form
689 function ctCloseFolder (item)
691 if (!ctIsFolderOpen (item))
694 // hide the downstream menus
697 var subMenu = ctGetObject (item.ctIdSub);
699 for (i = 0; i < subMenu.ctSubMenu.length; ++i)
700 ctCloseFolder (subMenu.ctSubMenu[i].ctParent);
702 var expandLevel = _ctTreeList[item.ctTreeIndex].expandLevel;
703 if (item.ctLevel < expandLevel)
705 subMenu.style.display = 'none';
707 item.id = 'JSCookTreeFolderClosed';
712 // setup an menu item
714 function ctSetupItem (item, index, treeIndex, level, idSub)
718 item.ctIndex = index;
719 item.ctTreeIndex = treeIndex;
720 item.ctLevel = level;
721 item.ctIdSub = idSub;
724 var thisMenu = ctGetThisMenu (item);
725 ctSetupMenu (thisMenu, item, null, null);
729 var subMenu = ctGetObject (idSub);
730 ctSetupMenu (subMenu, null, thisMenu, item);
735 // setup the relationship between a node and its sub menu
737 function ctSetupMenu (thisMenu, thisItem, parentMenu, parentItem)
739 if (!thisMenu.ctSubMenu)
740 thisMenu.ctSubMenu = new Array ();
744 if (!thisMenu.ctParent)
746 // establish the tree w/ back edge
747 thisMenu.ctParent = parentItem;
748 thisMenu.ctLevel = parentItem.ctLevel + 1;
750 //parentMenu.ctSubMenu.push (thisMenu);
751 parentMenu.ctSubMenu[parentMenu.ctSubMenu.length] = thisMenu;
757 if (!thisItem.ctMenu)
759 thisItem.ctMenu = thisMenu;
761 thisMenu.ctLevel = thisItem.ctLevel;
763 if (!thisMenu.ctItems)
764 thisMenu.ctItems = new Array ();
766 //thisMenu.ctItems.push (thisItem);
767 thisMenu.ctItems[thisMenu.ctItems.length] = thisItem;
773 // label the path from the menu root to the item
775 function ctLabelMenu (item)
777 var thisMenu = ctGetThisMenu (item);
778 while (thisMenu && thisMenu.ctLevel != 0)
780 thisMenu.ctCurrentItem = item;
781 thisMenu = ctGetThisMenu (thisMenu.ctParent);
786 // hide an item up to the parent menu of activeItem
788 function ctHideMenu (item, activeItem)
794 (subMenu = ctGetObject (item.ctIdSub)).ctLevel &&
795 (subMenu.ctCurrentItem != activeItem))
797 ctCloseFolder (item);
799 item = ctGetThisMenu (item).ctParent;
804 // returns the menu div where this obj (menu item) is in
806 function ctGetThisMenu (item)
808 var str = _ctTreeList[item.ctTreeIndex].prefix;
809 if (item.ctLevel == 0)
813 var themeLevel = _ctTreeList[item.ctTreeIndex].nodeProperties.themeLevel;
814 var lvl = (item.ctLevel < themeLevel) ? item.ctLevel : themeLevel;
815 str += 'TreeLevel' + lvl;
819 if (item.className == str)
821 item = item.parentNode;
827 // return true if there is next item
829 // used to determine connectors
831 function ctHasNextItem (index, tree)
833 if (index < (tree.length - 2) ||
834 (index == (tree.length - 2) && tree[index + 1]))
840 function ctGetDefaultClassName (item)
842 var tree = _ctTreeList[item.ctTreeIndex];
843 return tree.prefix + 'Row';
847 // return true if this item is handled using default handlers
849 function ctIsDefaultItem (item)
851 if (item[0] == _ctNoAction)
857 // returns the object baring the id
859 function ctGetObject (id)
862 return document.all[id];
863 return document.getElementById (id);
866 function ctSetSelected (item)
872 // debug function, ignore :)
874 function ctGetProperties (obj)
876 var msg = obj + ':\n';
879 msg += i + ' = ' + obj[i] + '; ';
883 /* JSCookTree v2.01 1. change Array.push (obj) call to Array[length] = obj.
884 Suggestion from Dick van der Kaaden <dick@netrex.nl> to
885 make the script compatible with IE 5.0
886 2. added ctGetSelectedItem (treeIndex) function due to demand
888 /* JSCookTree v2.0 1. added controls over tree branches opening/closing
889 2. added the ability to mark a specific tree item
890 3. added an extra description field to make the tree
891 format the same as JSCookMenu
892 4. more control over themes. allow multiple trees
893 w/ different themes co-exist in the same page
896 /* JSCookTree v1.01. made more tolerant to extra commas */
897 /* JSCookTree v1.0. (c) Copyright 2002 by Heng Yuan */