function initMenus(menu_bag_id)
//Javascript1.3
//GOALS
	//Browsers support:
		//IE 5.0+
		//NS 6.0+ (xmlhttp support begins at 6.2 and 6.0 support ain't looking so hot)
		//Opera 7.11 (and possibly earlier but not much I'd wager)
		//Latest Safari
		//Probably not Mac IE
	//users no longer have to create an object instance of called 'menus'
	//asyncronous menu creation (remove timing issues)
	//easy menu rebuilding
	//support for multiple menu types: UL, OL, DIV, SPAN
	//	TABLES will not be supported because they're ugly and contradict my initial reasoning for creating this
{

////create instances of document.menus and any functions that should be accessible from outside this
	//so that functions can quit gracefully whenever possible
	document.menus = this;
	this.appendMenu = appendMenu;
	this.parameters = parameters;
	this.add = add;
	this.extract = extract;
	this.build = build;
	this.hideMenu = hideMenu;
	
	//quit build if this browser cannot handle this script
	if(browserFails()) return;
	
	function browserFails()
	//returns true if any of the following browser tests fail
	//returns false if none fail
	{
		return (top.junk_browser || !getEl() || !document.getElementsByTagName) ? true : false;
	}
	
////DEFAULT PARAMETERS (read/write access from outside current scope)
	var pars = new Array();
		pars['timeout'] = 300; //time that menus stay visible after mouseout in milliseconds
		
		pars['position'] = 'absolute'; //other possibilities: 'fixed'
		
		pars['menu_button_className'] = 'menu_button'; //the className of sub buttons for sub menus
		
		pars['menu_x'] = 'center';		//other possibilities: an integer, 'left', 'right'
		pars['menu_y'] = 'below';		//other possibilities: an integer, 'above', 'center'
		pars['sub_menu_x'] = 'right';	//other possibilities: an integer, 'left', 'center'
		pars['sub_menu_y'] = 'center';	//other possibilities: an integer, 'above', 'below'
		
		//if no className is provided but combined offsets exceed 0 the background-color will be #000 with an opacity of 50%
		pars['dropshadow_className'] = ''; //the className of menu dropshadows
		pars['dropshadow_x'] = 0; //the relative offsets of a dropshadow
		pars['dropshadow_y'] = 0; //if offset_x + offset_y = 0 a dropshadow will not be applied

////OBJECT VARIABLES (inaccessible from outside current scope)
	var BODY = document.body;
	var menu_bag = document.createElement('DIV');
	var temp_bag = document.createElement('DIV');
	var lookup = new Array(); //final resting place for successfully processed buttons and their menu(s)
	var temp = null; //always reset temp to null when finished with it
	
////GLOBAL VARIABLES (read/write access from outside current scope)
	this.zIndex = 1001;
	this.lookup = lookup;

////GLOBAL EVENT
	document.onmouseup = function()
	//hides all menus
	{
		document.menus.hideMenu();
	}
	
////INITIALIZATION PROCESSES
	//if element with ID menu_bag_id exists point menu_bag at it
	//if it does not exist create it and point menu_bag at it
	menu_bag_id = menu_bag_id ? menu_bag_id : 'menu_bag';
	if(getEl(menu_bag_id)) {
		menu_bag = getEl(menu_bag_id);
	}
	else {
		menu_bag.id = menu_bag_id;
		document.body.appendChild(menu_bag);
	}
	
	//if element with ID 'temp_bag' exists point temp_bag at it
	//if it does not exist create it and point temp_bag at it
	if(getEl('temp_bag')) {
		temp_bag = getEl('temp_bag');
	}
	else {
		temp_bag.id = 'temp_bag';
		document.body.appendChild(temp_bag);
	}
	
////FUNCTIONS
	function appendMenu(menu)
	{
		//quit if this browser cannot handle this script
		if(browserFails() && !menu) return;
		
		menu_bag.appendChild(menu);
	}
	
	
	function parameters(key, value)
	{
		//quit if this browser cannot handle this script
		if(browserFails()) return;
		
		//return a clone of pars
		if(typeof key === 'undefined' && typeof value === 'undefined') return new clone(pars);
		//return the value of key
		if(typeof pars[key] !== 'undefined' && typeof value === 'undefined') return pars[key];
		//update key based on value
		else if(typeof pars[key] !== 'undefined' && typeof value !== 'undefined') {
			switch(key) {
			
			//css position special case
			case 'position':
				if(value === 'static' || value === 'relative') {
					alert('document.menus.parameters( ) reports:\n' + value + ' is not a valid value for parameter ' + key + '. This parameter will not be updated.');
					break;
				}
				pars[key] = navigator.userAgent.indexOf('MSIE') > -1 ? 'absolute' : value;
			break;
			
			//menu offsetting special cases	
			case 'menu_x': case 'sub_menu_x':
				if(typeof value === 'number') pars[key] = value;
				else if(typeof value === 'string')
					switch(value) {
					case 'left': case 'center': case 'right':
						pars[key] = value;
					break;
						
					default:
						if(value.search(/^-?\d+$/) === 0) pars[key] = parseInt(value);
						else alert('document.menus.parameters( ) reports:\n' + value + ' is not a valid value for parameter ' + key + '. This parameter will not be updated.');
					}
			break;
			case 'menu_y': case 'sub_menu_y':
				if(typeof value === 'number') pars[key] = value;
				else if(typeof value === 'string')
					switch(value) {
					case 'above': case 'center': case 'below':
						pars[key] = value;
					break;
						
					default:
						if(value.search(/^-?\d+$/) === 0) pars[key] = parseInt(value);
						else alert('document.menus.parameters( ) reports:\n' + value + ' is not a valid value for parameter ' + key + '. This parameter will not be updated.');
					}
			break;
				
			default:
				if(typeof pars[key] === typeof value) pars[key] = value;
				else alert('document.menus.parameters( ) reports:\n' + typeof value + ' is not a valid type for parameter ' + key + '. It should be of type: ' + typeof pars[key] + '. This parameter will not be updated.');
			
			}
		}
		//make pars a clone of value. users must be careful with this because any object will be accepted
		else if(key === 'parameters' && typeof value === 'object') pars = new clone(value);
		//inform the user that key is invalid
		else alert('document.menus.parameters( ) reports:\n' + key + ' is not a valid parameter. This parameter will not be updated or nothing to return.');
	}
	
	function add(button_id, menu_id)
	//PRE:
	//	The element(button) with ID button_id must exist.
	//	The element(menu) with ID menu_id must exist.
	//	If the menu exists in an external file the button must be an
	//	 'A' tag with an 'HREF' that points to a document that exists.
	//POST:
	//If the user's browser is supported a menu will be built
	//If the user's browser is not supported add() will return
	{
		//quit add if this browser cannot handle this script
		if(browserFails()) return;
		
		//create reference to current button with ID button_id
		var button = getEl(button_id);
		
		//if button does not exist alert and return
		if(!button) {
			alert('document.menus.add( ) reports:\nButton ID ' + button_id + ' is not valid. Menu ' + menu_id + ' will not be built.');
			return;
		}
		
		//define and set values of initial variables of button
		button.parentButton = null;		//parentButton will not change because this is a root button
		button.menu = new Object();					//menu will be the element that appears when button is moused over
		button.pars = parameters();		//creates a clone of the current parameters that will be shared with all children buttons
		button.dropshadow = null;		//dropshadow may be added later depending upon button's parameters
		
		//define and set lookup variables for global access
		button.index = lookup.length; //index is the index of this root button. child buttons share the root's index but recieve there own item
		lookup[button.index] = new Array; //lookup is a 2D array. each root button has it's own array
		button.item = 0; //this will be set at build time
		lookup[button.index][button.item] = button;
		
		//if menu exists in an external file
		if(menu_id.indexOf('href#') == 0) {
			//if button is invalid for use with an external file alert user and exit
			if(!button.href) {
				var A = button.getElementsByTagName('A')[0];
				//if there is a child element with nodeName A, use that for the href
				//if not, alert the user and exit
				if(A.href) button.href = A.href;
				else {
					alert('document.menus.add( ) reports:\nThe button with ID ' + button_id + ' is not valid. It must BE or CONTAIN an "A" tag with an "href" value. Menu ' + menu_id + ' will not be built.');
					return;
				}
			}
			
			//update menu.id for later use
			button.menu.id = menu_id.replace('href#','');
			//request the href of the current button
			request(button.href,'function() { if(xmlhttp.readyState == 4) document.menus.extract(xmlhttp.responseText,' + button.index + ') }');
			return;
		}
		
		//update menu.id for later use
		button.menu.id = menu_id;
		
		//both button and menu exist within this document and are ready to be prepared
		prepare(button);
		
		return;
	}
	
	function extract(input, index)
	//adds menus that are processed by request()
	{
		//quit add if this browser cannot handle this script
		if(browserFails()) return;
		
		var button = lookup[index][0];
		
		//append _menu to differentiate between menus and the original
		input = input.replace(/\sid=(\w+)/gi,' id=$1_menu').replace(/\sid=['"](\w+)['"]/gi,' id="$1_menu"');
		input = input.replace(/\sclass=(\w+)/gi,' class=$1_menu').replace(/\sclass=['"](\w+)['"]/gi,' class="$1_menu"');

		//set src for manipulating relative directory structure
		var src = button.href.split('/');
		
		//set #hash and then ?search with src documents filename if no filename is provided
		//and remove src's hash and search if it exists
		//NOTE: urls that have a search and hash should look like this: somewhere.htm?search=am_i_doing_this_right#YES
		//		As opposed to this: somewhere.htm#NO?search=im_not_doing_this_right
		//The goal is to set these up to be treated exactly how the browser would naturally treat
		//		them if input wasn't ripped from another page
		var replaceThis = new RegExp([' href="(#)'],['gi']);
		input = input.replace(replaceThis,' href="'+src[src.length-1].split('#')[0]+'$1');
		
		replaceThis = new RegExp([' href="(\\?)'],['gi']);
		input = input.replace(replaceThis,' href="'+src[src.length-1].split('?')[0]+'$1');
		
		//make input urls relative to src's relative root
		var dir = new Number();
		for(dir = src.length-2; input.indexOf(' href="../') > -1; dir--) {
			replaceThis = new RegExp([' href="((?!../))'],['gi']);
			input = input.replace(replaceThis,' href="'+src[dir]+'/$1');
			
			replaceThis = new RegExp([' href="'+src[dir]+'/(\\w{1,10}:)'],['gi']);
			input = input.replace(replaceThis,' href="$1')
			
			replaceThis = new RegExp([' href="../'],['gi']);
			input = input.replace(replaceThis,' href="');
		}
		
		//base cur on src's relative root but using this file's url
		var cur = location.href.split('/'+src[dir]+'/')[1].split('/');
		
		//finally, make input urls relative to this file's working directory
		for(dir = 0; dir < cur.length-1; dir++) {
			replaceThis = new RegExp([' href="((?!'+cur[dir]+'/))'],['gi']);

			input = input.replace(replaceThis,' href="../$1');
			
			replaceThis = new RegExp([' href="../(\\w{1,10}:)'],['gi']);
			input = input.replace(replaceThis,' href="$1');
			
			replaceThis = new RegExp([' href="'+cur[dir]+'/'],['gi']);
			input = input.replace(replaceThis,' href="');
		}
		
		//add input to DOM for easy extraction
		temp_bag.innerHTML = input;
		
		//extract list from DOM
		button.menu = getEl(button.menu.id+'_menu');
		menu_bag.appendChild(button.menu);
		
		temp_bag.innerHTML = '';
		
		prepare(button);
	}
	
	var prepare = function(button)
	{
		var menu_id = button.menu.id;
		button.menu = getEl(menu_id)
		if(!button.menu) {
			alert('document.menus.add( ) reports:\nThe menu with ID ' + menu_id + ' is not valid. Menu ' + menu_id + ' will not be built.');
			
			//the current button will not be removed from lookup because that can destroy order
			//but it will be set to null which will throw errors if there is an attempt to build it
			button = null;
			
			return;
		}
		
		//create a bag for placing children of menu
		button.menu.bag = document.createElement('DIV');
		button.menu.parentNode.appendChild(button.menu.bag);
		
		//set initial css
		button.menu.style.visibility = 'hidden';
		button.menu.style.position = button.pars['position'];

		build(button);
	}
	
	function build(button)
	{
		//quit add if this browser cannot handle this script
		if(browserFails()) return;
		
		//itemize and set the button's item if it has not already been set
		if(button.item === null) {
			button.item = lookup[button.index].length;
			lookup[button.index][button.item] = button;
		}
		//rebuild: clear button.menu.bag and lookup[button.index] except for the root item
		else if(button.parentButton === null) {
			button.menu.bag.innerHTML = '';
			while(lookup[button.index].length > 1) lookup[button.index].pop();
		}
		
		//set child menus (any element with the same tagName as the root menu)
		var children = button.menu.getElementsByTagName(button.menu.tagName);
		
		//set initial css for all child menus if this is the root button
		if(button.parentButton === null) {
			for(var i = 0; i < children.length; i++)
			{
				children[i].style.visibility = 'hidden';
				children[i].style.position = button.pars['position'];
			}
		}
		
		//position this button
		position(button);
		
		//set mouse events for button and it's menu
		addEvents(button);
	
		//prepare and build child menus and child buttons
		//a child menu is any child element of a menu with the same nodeName as the menu it's a child of.
		//a child button is the previousSibling of a child menu.
			//logically, the only exemption is that it shouldn't have the same nodeName as menus
		//every child menu must have a child button that points at it
		var new_button;
		while(children.length)
		{
			//we need to find the button used to access the new menu
			//it could have any nodeName EXCEPT for that of button.menu.nodeName
			//ths script assumes that it is the previous sibling of the current child
				//but we must be certain it's an element(nodeType = 1)
			new_button = children[0].previousSibling;
			while(new_button.nodeType > 1) //read until an element is found
				new_button = new_button.previousSibling;
			
			//IE's DOM places us at the child A element of the element we actually want to be at
			//so we step up into the parentNode anytime we end up at an A tag
			if(new_button.nodeName === 'A')
				new_button = new_button.parentNode;
			
			//set the className of the new button
			//so that a developer can use css to indicate visually that it has a sub menu
			new_button.className = new_button.className ? new_button.className : pars['menu_button_className'];
			
			//set variables
			new_button.parentButton = button;
			new_button.index = button.index;
			new_button.item = null;
			new_button.menu = children[0];
			new_button.menu.bag = button.menu.bag;
			new_button.pars = button.pars;
			new_button.dropshadow = null;
			
			//drop the current button in the root menu's bag
			button.menu.bag.appendChild(new_button.menu);
			
			build(new_button);
		}
		
		//if this is the root button and menu_y positioning is 'above',
		//reposition now that the menu is devoid of all children
		if(button.parentNode === null && button.pars['menu_y'] === 'above') position(button);
		
		//cast a shadow
		addShadow(button);
	}
	
	var position = function(button)
	//get and set current menu position based on button and user offset
	{
		var x_switch = button.parentButton === null ? button.pars['menu_x'] : button.pars['sub_menu_x'];
		var y_switch = button.parentButton === null ? button.pars['menu_y'] : button.pars['sub_menu_y'];
		var left = 0;
		var top = 0;
		switch(x_switch) {
			case 'left':
			button.menu.x = findPosX(button) - button.menu.offsetWidth;
			break;
			
			case 'center':
			button.menu.x = findPosX(button);
			break;
			
			case 'right':
			button.menu.x = findPosX(button) + button.offsetWidth;
			break;
			
			default:
			button.menu.x = findPosX(button) + button.offsetWidth + parseInt(x_switch);
		}
		switch(y_switch) {
			case 'above':
			button.menu.y = findPosY(button) - button.menu.offsetHeight;
			break;
			
			case 'center':
			button.menu.y = findPosY(button);
			break;
			
			case 'below':
			button.menu.y = findPosY(button) + button.offsetHeight;
			break;
			
			default:
			button.menu.y = findPosY(button) + parseInt(y_switch);
		}
		
		button.menu.style.left = button.menu.x + 'px';
		button.menu.style.top = button.menu.y + 'px';
	}
	
	var addEvents = function(button)
	{
		button.onmouseover = function() {
			clearTimeout(button.timer);
			button.menu.style.zIndex = ++document.menus.zIndex;
			button.menu.style.visibility = 'visible';
			if(button.menu.dropshadow) {
				button.menu.dropshadow.style.visibility = 'visible';
				button.menu.dropshadow.style.zIndex = document.menus.zIndex - 2;
			}
			var temp = button;
			while(temp.parentButton) {
				clearTimeout(temp.parentButton.timer);
				temp = temp.parentButton;
			}
		}
		
		button.onmouseout = function() {
			clearTimeout(button.timer);
			button.timer = setTimeout('document.menus.hideMenu(' + button.index + ',' + button.item + ')',document.menus.parameters('timeout'));
			var temp = button;
			var wait = 2;
			while(temp.parentButton) {
				if(temp.parentButton == document.menus.mouseSrc) break;
				temp.parentButton.timer = setTimeout('document.menus.hideMenu(' + temp.parentButton.index + ',' + temp.parentButton.item + ')',document.menus.parameters('timeout')*wait++);
				temp = temp.parentButton;
			}
			document.menus.mouseSrc = button;
		}
		
		button.menu.onmouseover = function () {
			clearTimeout(button.timer);
			document.menus.mouseSrc = button;
			
			button.menu.style.zIndex = ++document.menus.zIndex;
			if(button.menu.dropshadow) {
				button.menu.dropshadow.style.visibility = 'visible';
				button.menu.dropshadow.style.zIndex = document.menus.zIndex - 2;
			}
			var temp = button;
			while(temp.parentButton) {
				clearTimeout(temp.parentButton.timer);
				temp = temp.parentButton;
			}
		}
		button.menu.onmouseout = function () {
			clearTimeout(button.timer);
			button.timer = setTimeout('document.menus.hideMenu(' + button.index + ',' + button.item + ')',document.menus.parameters('timeout'));
			var temp = button;
			var wait = 2;
			while(temp.parentButton) {
				temp.parentButton.timer = setTimeout('document.menus.hideMenu(' + temp.parentButton.index + ',' + temp.parentButton.item + ')',document.menus.parameters('timeout')*wait++);
				temp = temp.parentButton;
			}
		}
	}
	
	var addShadow = function(button)
	//if the combined offsets are greater than 0 a dropshadow will be created
	//if the combined offsets are NOT greater than 0 a dropshadow will NOT be created
	{
		if(button.pars['dropshadow_x'] + button.pars['dropshadow_y']) {
			//create shadow
			button.menu.dropshadow = document.createElement('DIV');
			button.menu.bag.appendChild(button.menu.dropshadow);
			
			//apply generic shadow css if no class is provided
			//otherwise set className to the class provided
			if(button.pars['dropshadow_className'] === '') {
				button.menu.dropshadow.style.backgroundColor = '#000';
				button.menu.dropshadow.style.opacity = 0.5;
				if(navigator.userAgent.indexOf('MSIE')) button.menu.dropshadow.style.filter += 'progid:DXImageTransform.Microsoft.Alpha(opacity=50)';
			}
			else button.menu.dropshadow.className = button.pars['dropshadow_className'];
			
			//set default dropshadow css
			button.menu.dropshadow.style.visibility = 'hidden';
			button.menu.dropshadow.style.position = 'absolute';
			
			//set the size of the shadow
			button.menu.dropshadow.style.width = button.menu.offsetWidth+'px';
			button.menu.dropshadow.style.height = button.menu.offsetHeight+'px';
			
			//position the shadow based on it's source and the offset
			button.menu.dropshadow.style.left = button.menu.x + button.pars['dropshadow_x'] + 'px';
			button.menu.dropshadow.style.top = button.menu.y + button.pars['dropshadow_y'] + 'px';
		}
		else button.menu.dropshadow = null;
	}
	
//////EVENT FUNCTION//////////////////////////////////////////////////////////////////////////////
	function hideMenu(passedIndex, passedItem) {
		if(typeof passedIndex == 'number') {
			document.menus.lookup[passedIndex][passedItem].menu.style.visibility = 'hidden';
			if(document.menus.lookup[passedIndex][passedItem].menu.dropshadow)
				document.menus.lookup[passedIndex][passedItem].menu.dropshadow.style.visibility = 'hidden';
		}
		else for(var i = 0; i < document.menus.lookup.length; i++) {
			for(var j = 0; j < document.menus.lookup[i].length; j++) {
				document.menus.lookup[i][j].menu.style.visibility = 'hidden';
				if(document.menus.lookup[i][j].menu.dropshadow)
					document.menus.lookup[i][j].menu.dropshadow.style.visibility = 'hidden';
			}
		}
	}
}
//////XMLHTTP REQUEST FUNCTION////////////////////////////////////////////////////////////////////////////
function request(src,action)
// "src" is the url that request() will access. Both relative and absolute URLs are allowed.
//		If "src" is provided but does not exist this script fails with meaningless JS errors!
//		SO MAKE SURE "src" IS VALID!!!!
// "action" is the function that will run onreadystatechange of the xmlhttp object.
//		Two important properties are xmlhttp.readystate and xmlhttp.responseText
//		EX: if(xmlhttp.readyState == 4) alert(xmlhttp.responseText) will alert you
//		to the content of whatever "src" is.
// request() returns true if onreadystatechange was successfully set to "action".
//		Otherwise it returns false.
// created by jeremy at badlib dot com
// based on JS by Jim Ley of http://jibbering.com/2002/4/httprequest.html
{
	if(!src || !action || !getEl()) return false;
	
	xmlhttp = true;
	
	if(navigator.userAgent.indexOf('Netscape6') > -1) {
		xmlhttp = navigator.userAgent.slice(navigator.userAgent.search(/\d+$/));
		xmlhttp = xmlhttp < 2 ? false : true;
	}
	else if(navigator.userAgent.indexOf('Opera') > -1) {
		xmlhttp = false;
	}

	if(xmlhttp && src) {
		var xmlhttp=false;
		/*@cc_on @*/
		/*@if (@_jscript_version >= 5)
		// JScript gives us Conditional compilation, we can cope with old IE versions.
		// and security blocked creation of the objects.
		try {
			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
		} catch (e) {
			try {
				xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
			} catch (E) {
				xmlhttp = false;
			}
		}
		@end @*/
		if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
			xmlhttp = new XMLHttpRequest();
		}
		xmlhttp.open("GET", src, true);
		eval('xmlhttp.onreadystatechange = '+action);
		xmlhttp.send(null);
		return true;
	}
	return false;
}

//////GLOBAL FUNCTIONS////////////////////////////////////////////////////////////////////////////////
function getEl(passedId)
{
	//if no passedId has no value return true if the getEl() function is supported and false if not
	if(typeof passedId === 'undefined') return document.getElementById  ? true : document.all && navigator.appVersion.indexOf('MSIE 4') < 0 ? true : false;
	
	//if passedId has a value return the element with ID passedId else return the null pointer
	return document.getElementById ? document.getElementById(passedId) : document.all && navigator.appVersion.indexOf('MSIE 4') < 0 ? document.all[passedId] : null;
}
function clone(what) {
    for (i in what) {
        this[i] = what[i];
    }
}
//Position solvers courtesy Peter-Paul Koch of quirksmode.org
function findPosX(el) {
	var curleft = 0;
	if (el.offsetParent) {
		while (el.offsetParent) {
			curleft += el.offsetLeft
			el = el.offsetParent
		}
	}
	else if (el.x) curleft += el.x
	if(navigator.appVersion.indexOf("MSIE 5") > -1 && navigator.platform.indexOf('MacPPC') > -1)
		curleft += document.body.leftMargin
	return curleft;
}
function findPosY(el) {
	var curtop = 0;
	if (el.offsetParent) {
		while (el.offsetParent) {
			curtop += el.offsetTop
			el = el.offsetParent;
		}
	}
	else if (el.y) curtop += el.y
	if(navigator.appVersion.indexOf("MSIE 5") > -1 && navigator.platform.indexOf('MacPPC') > -1)
		curtop += document.body.topMargin
	return curtop
}
