/*
 * TreeMenu v1.31
 *
 * Copyright (c) 2005 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.31 -- 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)

where id is the id attribute of the beginning <UL> tag. There are two
optional arguments. The omit_symbols argument, if set to true, omits
adding symbol tags to the beginning of each menu item. The no_save
argument prevents the open/close state of the menus from being saved
in a browser cookie. Both options 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.
If you don't use a SymbolTag you must provide your own buttons to open and
close the menus.

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. The TreeMenu.reset(ul)
resets the menu back to the original default state 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.

******************************************************************************/


/////// Configuration Variables ///////////////////////////

TreeMenu.SymbolTag = 'span';			// symbol inserted at beginning of <LI> tags
//TreeMenu.SymbolTag = '';			// uncomment to disable insertion of symbols

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 = false;		// flag to use a cookie to save menu state
TreeMenu.CookieExpire = 90;			// days before cookie saving menu states expires

/////// End of Configuration Variables ///////////////////

function make_tree_menu(id,omit_symbols,no_save) {
	var m = new TreeMenu(id);
	if (omit_symbols) m.SymbolTag = '';
	if (no_save) m.CookieSaveStates = false;
	m.setup();
	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				 m.show_menu(ul,li,e);

	m.save_menu_states();
}

TreeMenu.show = function(ul) {
	ul = TreeMenu.get_ref(ul);
	var m = TreeMenu.menus[TreeMenu.get_top_ul(ul).id];
	var li = TreeMenu.get_li(ul);
	m.show_menu(ul,li);
}

TreeMenu.hide = function(ul) {
	ul = TreeMenu.get_ref(ul);
	var m = TreeMenu.menus[TreeMenu.get_top_ul(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.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 = 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) {
				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) {
				symbol = document.createElement(this.SymbolTag);
				if (this.SymbolClassItem)
					symbol.className = this.SymbolClassItem;
				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.tagName) 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);
	}

	// 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);
	}

	// Following case is for toggle buttons disassociated with menu structure.
	this.add_remove_class(e,this.SymbolClassClose,this.SymbolClassOpen);
}

/*
 * 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));
}
