/* * TreeMenu v1.4 * * Copyright (c) 2006 Mackley F. Pexton. All rights reserved. * * This is free software for individual, educational, and non-profit * use provided that this copyright notice appears on all copies. * Instructions and source code are available at http://www.acmebase.org/tree_menu. * Optimized version is for sale at https://order.acmebase.com. * Commercial web sites are required to have an inexpensive license. * Send correspondence and feedback to: mack_pexton[at]acmebase.org. */ /****************************************************************************** TreeMenu v1.4 -- Make menus out of UL/LI tags that open up when clicked. Tree menus are menus that pop open when a little graphic placed to the left of the menu item is clicked -- very similar to Microsoft Explorer expanding and collapsing folders of files. Usage: make_tree_menu(id); -or- make_tree_menu(id,omit_symbols,no_save_state,singular,no_setup) where id is the id attribute of the beginning <UL> tag. There are four optional arguments. The omit_symbols argument, if set to true, omits adding symbol tags to the beginning of each menu item. The no_save_state argument prevents the open/close state of the menus from being saved in a browser cookie. The singular argument, if true, restricts the open menus to only one per level. The no_setup flag prevents TreeMenu from scanning the document to setup the menu. This flag assumes that the server has constructed a complete menu with appropriate symbols, click event handlers, and class attributes. The optional arguments can be alternately specified by setting the configuration variables before calling make_tree_menu(). Menus are created out of <UL> tags and their enclosed <LI> tags. The <LI> tags can contain other <UL> tags. Symbol objects (e.g. <SPAN> tags) are (optionally) inserted before each <LI> tag. The class name of the symbol tag is set to one of the TreeMenu class variables: SymbolClassOpen, SymbolClassClose, or SymbolClassItem depending upon if the <LI> tag has enclosed <UL> tags and whether the saved open/closed state of the menu's <UL> tag is 1 or 0. Menu open/closed states are preserved in a cookie with a name derived from the menu's first <UL> tag's id. When the user clicks on a symbol tag, the menu is opened or it is closed if it is already open. The symbol's CSS class is changed to the SymbolClassOpen or SymbolClassClose appropriately. To allow more animation, the <LI> tag's class has the class name in the variable ClassOpen appended to it. This doesn't change the class name, it adds a new class name. When the menu is closed, the added class name is removed and the class name in the variable ClassClose added to it. The type of a symbol's tag inserted before each <LI> tag item is determined by the class variable SymbolTag. It can be set to "span" or "div" or it can be empty. The default value is "span". A symbol is NOT inserted at the <LI> tags' beginning if the SymbolTag is set to null or the empty string. Also, a symbol is NOT inserted at the beginning of the <LI> tags if the configuration variable TreeMenu.OmitSymbols is true (default is false). This allows the server to insert the symbol tags and have this JavaScript operate the symbols. You can use regular <A> tags as buttons to open and close menus by defining an onclick handler like: <a href="javascript:;" onclick="TreeMenu.toggle(this)">Submenu</a> If the button is outside of the menu structure, pass TreeMenu.toggle() the id of the menu/submenu you want to toggle instead of "this". <a href="javascript:;" onclick="TreeMenu.toggle('submenu_id')">Toggle Submenu</a> -or- <a href="javascript:;" onclick="TreeMenu.show('submenu_id')">Show Submenu</a> -or- <a href="javascript:;" onclick="TreeMenu.hide('submenu_id')">Hide Submenu</a> The TreeMenu.show(ul) and TreeMenu.hide(ul) shows and hides one branch of the tree menu. The TreeMenu.show_all(ul) and TreeMenu.hide_all(ul) shows and hides every branch of the tree menu. TreeMenu.save_state(ul) saves the current open/close menu states -- usefull after TreeMenu.show_all() or TreeMenu.hide_all(). TreeMenu.reset(ul) resets the menu back to the original default state. It does that by removing the cookie that saves the menu state. The page needs to be refreshed to actually display the menu in its original state. Multiple menus can be made and each menu can have different settings for the configuration variables. Changes from TreeMenu v1.3: - Added use of IMG tags for SymbolTag. This allows the program to change both the forground images and the background image. Added TreeMenu.SymbolSrcOpen, TreeMenu.SymbolSrcClose and TreeMenu.SymbolSrcItem variables. - Added TreeMenu.Singular flag to restrict open menus to just one per menu level, and added fourth optional argument to make_tree_menu() to turn on the flag for an individual menu. - Added TreeMenu.save_state() utility to save menu open/close states. - Added TreeMenu.OmitSymbols to refrain from inserting symbols into the list but yet operate them if found. - Added TreeMenu.SetupMenu flag to keep TreeMenu from scanning the document. This allows the server to build the menu instead of TreeMenu assembling the menu after the document is loaded. This greatly speeds up the rendering of large menus -- especially with IE6 where accessing the document objects is so very slow. ******************************************************************************/ /////// Configuration Variables /////////////////////////// TreeMenu.SymbolTag = 'span'; // symbol inserted at beginning of <LI> tags //TreeMenu.SymbolTag = ''; // uncomment to disable insertion of symbols //TreeMenu.SymbolTag = 'img'; // uncomment to use IMG tags for symbols //TreeMenu.SymbolSrcItem = ''; // url to assign to IMG src attribute of an item //TreeMenu.SymbolSrcClose = ''; // url to assign to IMG src attribute upon close //TreeMenu.SymbolSrcOpen = ''; // url to assign to IMG src attribute upon open TreeMenu.OmitSymbols = false; // don't insert symbol but do adjust them TreeMenu.SymbolClassItem = 'symbol-item'; TreeMenu.SymbolClassClose = 'symbol-close'; TreeMenu.SymbolClassOpen = 'symbol-open'; TreeMenu.ClassItem = 'item'; // class name added to <LI> tag's class TreeMenu.ClassClose = 'close'; // class name added to <LI> tag's class TreeMenu.ClassOpen = 'open'; // class name added to <LI> tag's class TreeMenu.ClassLast = 'last'; // added to last <LI> and symbol tags' classes TreeMenu.CookieSaveStates = true; // flag to use a cookie to save menu state TreeMenu.CookieExpire = 90; // days before cookie saving menu states expires TreeMenu.SetupMenu = true; // scan document objects to initialize menu TreeMenu.Singular = false; // restrict open menus to only one per level /////// End of Configuration Variables /////////////////// function make_tree_menu(id,omit_symbols,no_save_state,singular,no_setup) { var m = new TreeMenu(id); if (omit_symbols) m.OmitSymbols = true; if (no_save_state) m.CookieSaveStates = false; if (singular) m.Singular = true; if (no_setup) m.SetupMenu = false; // Setup menus if we are inserting symbols or restoring menu open/close states. if (m.SetupMenu) m.setup_symbols(); return m; } /* * TreeMenu */ function TreeMenu(ul_id) { // object constructor this.top_ul_id = ul_id; this.top_ul = document.getElementById(ul_id); this.configure(); // Register menu TreeMenu.menus[ul_id] = this; return this; } /* * TreeMenu Class Variables */ TreeMenu.menus = []; // list of defined menus /* * TreeMenu Class Methods */ TreeMenu.toggle = function(e) { e = TreeMenu.get_ref(e); var m = TreeMenu.menus[TreeMenu.get_top_ul(e).id]; var li = TreeMenu.get_li(e); var ul = li.getElementsByTagName("UL")[0]; if (ul.style.display == "block") { m.hide_menu(ul,li,e); } else { if (m.Singular) m.hide_menus_except(li); m.show_menu(ul,li,e); } m.save_menu_states(); } TreeMenu.show = function(ul) { ul = TreeMenu.get_ref(ul); var top_ul = TreeMenu.get_top_ul(ul); if (! top_ul) return; var m = TreeMenu.menus[top_ul.id]; var li = TreeMenu.get_li(ul); m.show_menu(ul,li); } TreeMenu.hide = function(ul) { ul = TreeMenu.get_ref(ul); var top_ul = TreeMenu.get_top_ul(ul); if (! top_ul) return; var m = TreeMenu.menus[top_ul.id]; var li = TreeMenu.get_li(ul); m.hide_menu(ul,li); } TreeMenu.show_all = function(ul) { // Show all menus under ul. ul = TreeMenu.get_ref(ul); var uls = ul.getElementsByTagName("UL"); for (i = 0; i < uls.length; i++) { TreeMenu.show(uls[i]); } } TreeMenu.hide_all = function(ul) { // Hide all menus under ul. ul = TreeMenu.get_ref(ul); var uls = ul.getElementsByTagName("UL"); for (i = 0; i < uls.length; i++) { TreeMenu.hide(uls[i]); } } TreeMenu.save_state = function(ul) { // Reset menu to original settings. ul = TreeMenu.get_ref(ul); var m = TreeMenu.menus[TreeMenu.get_top_ul(ul).id]; m.save_menu_states(); } TreeMenu.reset = function(ul) { // Reset menu to original settings. ul = TreeMenu.get_ref(ul); var m = TreeMenu.menus[TreeMenu.get_top_ul(ul).id]; m.reset_menu_states(); } // Private methods TreeMenu.get_ref = function(id) { if (typeof id == "string") return document.getElementById(id); return id; } TreeMenu.get_top_ul = function(e) { while (e && (e.nodeName != 'UL' || ! e.id || ! TreeMenu.menus[e.id])) e = e.parentNode; return e; } TreeMenu.get_li = function(e) { while (e && e.nodeName != 'LI') e = e.parentNode; return e; } /* * TreeMenu Object Methods */ TreeMenu.prototype.configure = function() { // Assign global class settings (capitalized variables) to object settings. var v,c; for (v in TreeMenu) { c = v.substr(0,1); if (c == c.toUpperCase()) { this[v] = TreeMenu[v]; } } } TreeMenu.prototype.setup_symbols = function() { // Insert open/close symbols at the beginning of the menu items // and open or close menus like they were previously. var states = this.get_menu_states(); var index = 0; var ul, li, symbol, islast = false; var ul_elements, li_elements = this.top_ul.getElementsByTagName("LI"); for(var i=0; i < li_elements.length; i++) { li = li_elements[i]; if (this.ClassLast) islast = this.is_last_item(li); ul_elements = li.getElementsByTagName("UL"); if(ul_elements.length > 0) { // Submenus if (this.SymbolTag && ! this.OmitSymbols) { symbol = document.createElement(this.SymbolTag); if (this.ClassLast && islast) symbol.className = this.ClassLast; symbol.onclick = function() { TreeMenu.toggle(this); }; li.insertBefore(symbol, li.firstChild); } ul = ul_elements[0]; if (states[index] == '1') this.show_menu(ul,li); else this.hide_menu(ul,li); index++; } else { // Menu item if (this.SymbolTag && ! this.OmitSymbols) { symbol = document.createElement(this.SymbolTag); if (this.SymbolClassItem) symbol.className = this.SymbolClassItem; if (this.SymbolSrcItem) symbol.src = this.SymbolSrcItem; if (this.ClassLast && islast) symbol.className += ' ' + this.ClassLast; li.insertBefore(symbol, li.firstChild); } if (this.ClassItem) li.className += ' ' + this.ClassItem; } if (islast) li.className += ' ' + this.ClassLast; } } TreeMenu.prototype.is_last_item = function(e) { // Check if element is the last LI element in the list. e = e.nextSibling; // Get next element (Mozilla puts text nodes at same level here). while (e && e.nodeType != 1) e = e.nextSibling; return e ? false : true; } TreeMenu.prototype.get_menu_states = function() { var cookie = getCookie("tm_" + this.top_ul_id); if (cookie) return cookie.split('x'); return []; } TreeMenu.prototype.save_menu_states = function() { // Save all menu and submenu open/close states in a cookie if (! this.CookieSaveStates) return; var states = []; var ul_elements, li_elements = this.top_ul.getElementsByTagName("LI"); for(var i=0; i < li_elements.length; i++) { ul_elements = li_elements[i].getElementsByTagName("UL"); if (ul_elements.length > 0) { states[states.length] = ul_elements[0].style.display == "block" ? 1 : 0; } } var expire_date = new Date((new Date().getTime()) + this.CookieExpire*24*60*60*1000); setCookie("tm_" + this.top_ul_id, states.join('x'), expire_date, '/'); } TreeMenu.prototype.reset_menu_states = function() { // Reset all menu and submenu open/close states (delete cookie) var expire_date = new Date((new Date().getTime()) - 1000); // set to past time setCookie("tm_" + this.top_ul_id, '', expire_date, '/'); } TreeMenu.prototype.add_remove_class = function(e,add_class,remove_class) { if (e) { if (remove_class) e.className = e.className.replace(remove_class,''); if (add_class && ! e.className.match( (new RegExp("\\b"+add_class+"(\\s.*)?")) ) ) { e.className += ' ' + add_class; } } } TreeMenu.prototype.show_menu = function(ul,li,e) { ul.style.display = 'block'; this.add_remove_class(li,this.ClassOpen,this.ClassClose); if (this.SymbolTag) { var symbol = li.getElementsByTagName(this.SymbolTag)[0]; this.add_remove_class(symbol,this.SymbolClassOpen,this.SymbolClassClose); if (this.SymbolSrcOpen) symbol.src = this.SymbolSrcOpen; } // Following case is for toggle buttons disassociated with menu structure. this.add_remove_class(e,this.SymbolClassOpen,this.SymbolClassClose); } TreeMenu.prototype.hide_menu = function(ul,li,e) { ul.style.display = 'none'; this.add_remove_class(li,this.ClassClose,this.ClassOpen); if (this.SymbolTag) { var symbol = li.getElementsByTagName(this.SymbolTag)[0]; this.add_remove_class(symbol,this.SymbolClassClose,this.SymbolClassOpen); if (this.SymbolSrcClose) symbol.src = this.SymbolSrcClose; } // Following case is for toggle buttons disassociated with menu structure. this.add_remove_class(e,this.SymbolClassClose,this.SymbolClassOpen); } TreeMenu.prototype.hide_menus_except = function(li) { // Hide other menus at same level as li. var n; var re = new RegExp('\\b' + this.ClassOpen + '\\b'); for (var i = 0; i < li.parentNode.childNodes.length; i++) { n = li.parentNode.childNodes[i]; if (n == li || n.nodeType != 1) continue; if (n.className.match(re)) this.hide_menu(n.getElementsByTagName("UL")[0],n); } } /* * Classic Cookie functions */ function setCookie(name, value, expires, path, domain, secure) { document.cookie = name + "=" + escape(value) + (expires ? "; expires=" + expires.toGMTString() : "") + (path ? "; path=" + path : "") + (domain ? "; domain=" + domain : "") + (secure ? "; secure" : ""); } function getCookie(name) { var dc = document.cookie; var prefix = name + "="; var begin = dc.indexOf("; " + prefix); if (begin == -1) { begin = dc.indexOf(prefix); if (begin != 0) return null; } else { begin += 2; } var end = document.cookie.indexOf(";", begin); if (end == -1) end = dc.length; return unescape(dc.substring(begin + prefix.length, end)); }