/*
 * n2menu 0.9 - Copyright (c) 2007 Cristian Libardo
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * Usage example:
 *
 *	<script type="text/javascript" src="/Js/jquery-1.1.2.pack.js"></script>
 *	<script type="text/javascript" src="/Js/n2menu.js"></script>
 * 	<script type="text/javascript">
 *		$(document).ready(function(){
 *			n2menu.init("#myMenuRootId");
 *		});
 *	</script>
 *
 *	<!-- the unordered list defines the menu structure -->
 *	<div id='myMenuRootId'>
 *		<ul>
 *			<li>
 *				<a href="#">just a link</>
 *				<ul>
 *					<!-- this is a submenu, when expanded it's moved below the topmost ul -->
 *					<li>...</li>
 *				</ul>
 *			</li>
 *			<li><a href="#">just another link</></li>
 *		</ul>
 *		<!-- expanded submenues are moved here -->
 *	</div>
 */

/// creates a n2menu by traversing ul:s and li:s below the supplied argument
function n2menu(menuRootExpression){
	this.menuRoot = menuRootExpression;
	this.hoverTimeout = 333;
	this.current = null;
	this.initMenu();
}

/// initializes a n2menu by traversing unordered lists below elements defined 
/// by the supplied expression
n2menu.init = function(menuRootExpression){
	return new n2menu(menuRootExpression);
}

/// initialize the menu(s)
n2menu.prototype.initMenu = function(){
	var menu = this;
	$(menu.menuRoot).each(function(){
		var excludes = new Array();
		menu.initBranchRecursive(menu, this, excludes);
		if(menu.current != null)
			menu.expandToCurrentRecursive($(menu.current));
		$(this).addClass("initialized");
	});
}

/// recusively enumerates child elements initializing data and attaching events
n2menu.prototype.initBranchRecursive = function(menu, el, trail){
	el.menu = menu;
	var q = $(el);
	if(q.is("ul")){
		trail.push(el);
		el.level = trail.length;
		el.excludes = trail.slice();
		q.addClass("level" + el.level);
		menu.initExpansion(q);
	}
	q.children().each( function(){
		menu.initBranchRecursive(menu, this, trail);
	});
	if(q.is("ul"))
		trail.pop();
	if(q.is(".current"))
		menu.current = q.get(0);
}

/// creates a relation between li (menu opener) and child ul element (sub menu)
/// and attaches to hover events
n2menu.prototype.initExpansion = function(ulQ){
	var liQ = ulQ.parent();
	if(!liQ.is(this.menuRoot))
	{
		var li = liQ.get(0);
		var ul = ulQ.get(0);
		li.opens = ul;
		ul.opener = li;
		liQ.addClass("expandable").hover(this.onMouseOver, this.onMouseOut);
	}
}

/// executed on mouse over, clears any previous expansion and expand to next 
/// level either right away or after a timeout
n2menu.prototype.onMouseOver = function(){
	var li = this;
	var menu = li.menu;
	var q = $(li);
	q.addClass("hover");
	if(menu.hoverTimeout > 0) {
		setTimeout(function(){
			if(q.is(".hover")){
				menu.removePreviouslyExpanded(q);
				menu.expand(q);
			}
		}, menu.hoverTimeout);
	}
	else {
		menu.removePreviouslyExpanded(q);
		menu.expand(q);
	}
}

/// executed on mouse out, removes the hover class
n2menu.prototype.onMouseOut = function(){
	$(this).removeClass("hover");
}

/// expands a submenu by appending below the previous level
n2menu.prototype.expand = function(q){
	var li = q.get(0);
	if(li.opens){
		$(li.opens).remove().appendTo(li.menu.menuRoot).addClass("subMenu");
		q.addClass("expanded");
	}
}

/// remove previously expanded levels except those leading to the current menu
n2menu.prototype.removePreviouslyExpanded = function(q){
	var li = q.get(0);
	$(li.menu.menuRoot).children(".subMenu").not(li.opens.excludes).each(function(){
		var ul = this;
		$(ul).removeClass("subMenu").remove().appendTo(ul.opener);
		$(ul.opener).removeClass("expanded");
	});
}

/// traverse parent elements recursively and expands menues leading to the 
/// starting element
n2menu.prototype.expandToCurrentRecursive = function(q){
	if(q.is(this.menuRoot))
		return;
	else if (q.is("ul")) {
		this.expandToCurrentRecursive(q.parent());
	} else if (q.is("li")) {
		this.expandToCurrentRecursive(q.parent());
		this.expand(q);
		q.addClass("trail");
	} else
		return;
}

