// ccSlideMenu.js
//
// JavaScript Support for Sliding Menu
// The menu will be vertically repositioned to stay on-screen
// Horizontal position does not change.
// Chip Chapin 2003-02-14
// Originally written for use in the StrataLight UI Overview document
//
// 2005-05-14: New version uses relative positioning, intended for use within a column.
// Also supports panels that are taller than the screen height.

// [OUTDATED] Here's a typical style for a right-column menu:
/*
#menu {
	position: absolute;
	right: 10px;
	top: 75;
	width: 200;
	height: 200;
	z-index: 2;
	visibility: visible;
}
*/

// Tinker with these numbers if you want
var smallMargin = 0;     // Stop moving the menu when it's this close to target
var distFraction = 3;    // What fraction of distance to move at a time.
var frameTime = 100;     // msec between position updates.
var triggerMargin = 64;  // Must be out of position by this many pixels to move
var shadowOffset = 4;    // Expose this much of the shadow element.

var menu1;               // Global menu element
var menuTopDistance = 0; // Desired distance of menu top from top of screen. (Gets set later)
var inMotion = false;    // Whether menu is moving or not
var moveTimer = null;

var showStatus = false; // Set true to show debug info in window.status

/**
 * Given a style value string with expected units,
 * return the numeric value.
 */
function styleValue(sval, sunit) {
	var result;
	if (sval == null || sval == "") 
		result = 0;
	else {
		var s = sval.substring(0, sval.indexOf(sunit));
		result = parseInt(s);
	}
	return result;
}

/**
 * moveMenu: Possibly move the panel.
 */
function moveMenu(panel, triggerMargin) {
	if (moveTimer != null)
		window.clearTimeout(moveTimer);
	/*
	 * For ABSOLUTE positioned elements we can move the panel by setting offsetTop.
	 * For RELATIVE positioned elements we move the panel by setting marginTop.
	 */
	//var mTop = panel.offsetTop;    // current panel top position
	var mTop = getPositionableOffsetTop(panel);    // current panel top position
	var sTop = document.body.scrollTop;
	var targetTop = Math.max(sTop + smallMargin, menuTopDistance); // desired panel top position
	var mHeight = panel.offsetHeight;
	var bodyHeight = document.body.clientHeight;
	var dist = 0;

	if (showStatus) 
		window.status = "moveMenu: scrollTop=" + sTop 
			+ ", mtd=" + menuTopDistance 
			+ ", mTop=" + mTop 
			+ ", targetTop=" + targetTop 
			+ ", mHeight=" + mHeight;

	if (mHeight <= bodyHeight) {
		// panel is shorter than client window.
		// Keep it positioned at the top.
		dist = targetTop - mTop;  // distance to move
		if (showStatus) window.status += ", Mode S, Dist is " + dist;
	}
	else {
		// panel is taller than client window: 
		// 3. If top of panel is below top of viewable area then position it at the top.
		//    This happens when scrolling back up the document.
		// 1. Don't move at all if bottom of panel is below viewable area.
		// 2. If bottom of panel is above bottom of viewable area then adjust so 
		//    the panel bottom is at the screen bottom.
		var mBottom = mTop + mHeight; // panel bottom position
		var sBottom = sTop + bodyHeight; // screen bottom position
		var panelFooterDistance = menuTopDistance + panel.parentNode.offsetHeight; // Footer distance can change
		var targetBottom = Math.min(sBottom, panelFooterDistance);
		if (mTop > targetTop) {
			dist = targetTop - mTop;  // distance to move
			if (showStatus) window.status += ", Mode T3, mBottom=" + mBottom + ", sBottom=" + sBottom + ", targetBottom=" + targetBottom + ", mfd=" + panelFooterDistance + ", Dist is " + dist;
		}
		else if (mBottom >= sBottom) {
			if (showStatus) window.status += ", Mode T1, mBottom=" + mBottom + ", sBottom=" + sBottom + ", targetBottom=" + targetBottom + ", mfd=" + panelFooterDistance + ", Dist is " + dist;
		}
		else {
			dist = targetBottom - mBottom;
			if (showStatus) window.status += ", Mode T2, mBottom=" + mBottom + ", sBottom=" + sBottom + ", targetBottom=" + targetBottom + ", mfd=" + panelFooterDistance + ", Dist is " + dist;
		}
	}

	// Introduce some hysteresis to the panel motion
	var workingMargin = inMotion ? smallMargin : triggerMargin;
		
	if (Math.abs(dist) <= workingMargin) {
		inMotion = false;
	}
	else {
		moveIt(panel, dist);
	}
} // moveMenu

/**
 * moveIt: Move panel some fraction of the distance
 */
function moveIt(panel, dist) {
	inMotion = true;
	
	var moveAmt = Math.abs(dist) <= 2 ? dist : Math.round(dist/distFraction);
	//var moveToHeight = mTop + moveAmt;
	//menu.style.top = moveToHeight + "px";
	var mMargin = styleValue(panel.style.marginTop, "px"); // current margin
	mMargin += moveAmt;
	panel.style.marginTop = (mMargin > 0 ? mMargin : 0) + "px";
	shadowResize(panel);
	if (showStatus) window.status += " [moving " + moveAmt + "]";
	// HACK: note use of global "menu1".
	moveTimer = window.setTimeout("moveMenu(menu1, " + triggerMargin + ")", frameTime);
}

/**
 * startMenuMove
 */
function startMenuMove() {
	// alert("startMenuMove");
	if (showStatus) window.status = "startMenuMove: scrollTop="+document.body.scrollTop;
	moveMenu(menu1, triggerMargin); // HACK: note use of global "menu1".
}

/**
 * shadowResize
 * Set shadow size
 * Called for init and also on menu resize.
 */
function shadowResize(menu) {
	if (menu == null) {
		alert("shadowResize: called with null menu");
		return;
	}
	var shadowId = menu.getAttribute("shadowId");
	if (shadowId == null) {
		//alert("shadowResize: No shadowId attribute.");
		return;
	}
	var shadow = document.getElementById(shadowId);
	if (shadow == null) {
		alert("shadowResize: no shadow element '" + shadowId + "'");
		return;
	}
	shadow.style.left = (menu.offsetLeft + 4) + "px";
	shadow.style.top = (menu.offsetTop + 4) + "px";
	shadow.style.width = menu.offsetWidth + "px";
	shadow.style.height = menu.offsetHeight + "px";
}

/**
 * addShadowMenu
 * Add a drop shadow to our menu
 */
function addShadowMenu(menu) {
	var shadow = document.createElement("DIV");
	
	shadow.style.position = "absolute";
	shadow.style.zIndex = 100;
	menu.style.zIndex = 101;
	shadow.style.backgroundColor = "rgb(80,80,80)";
	shadow.id = menu.id + "_shadow";
	document.body.appendChild(shadow);
	menu.setAttribute("shadowId", shadow.id);
	shadowResize(menu);
} // addShadowMenu

/**
 * menuOnResize
 * Window resize handler for the sliding panel
 * This doesn't handle motion for the panel itself, it just makes
 * the shadow track the menu panel if it is moved left or right
 * by the application page.
 */
function menuOnResize() {
	// alert("menuOnResize");
	var panel = menu1; // HACK: note use of global "menu1". 
	shadowResize(panel);
	return true;
}

/**
 * setMoveMenu
 * Setup function, called from <BODY onLoad="setMoveMenu('menuID')">
 * @param menuId -- String, id of menu element
 * @param addShadow -- boolean, if true then create a matching shadow element.
 */
function setMoveMenu(menuId, addShadow) {
	// TODO: Browser Check: I've only tested this with IE6, Firefox 1.0.3, Moz 1.8
	if (!document.getElementById) {
		window.status = "Warning: Sliding panel '" + menuId + "' disabled due to browser limitation";
		return;
	}
	
	if (menuId == null) {
		alert("setMoveMenu called with null menuId");
		return;
	}
	
	var panel = document.getElementById(menuId);
	if (panel == null) {
		alert("setMoveMenu: No element '" + menuId + "' found.");
		return;
	}
	menuTopDistance = panel.offsetTop; // Get initial position
	triggerMargin = Math.min(menuTopDistance, triggerMargin);
	if (addShadow)
		addShadowMenu(panel);
	//moveTimer = setTimeout("moveMenu(menu1, " + triggerMargin + ", " + topDistance + ")", frameTime);

	// Set global panel element
	menu1 = panel;
	
	if (window.addEventListener) {
		// DOM 2 event model
		//alert("DOM2");
		window.addEventListener("resize", menuOnResize, false);
		window.addEventListener("scroll", startMenuMove, false);
		window.addEventListener("mousewheel", startMenuMove, false); // Placeholder: Not supported.
	}
	else {
		//alert("attachEvent");
		// window.onresize = menuOnResize;
		attachEvent('onresize', menuOnResize);
		attachEvent('onscroll', startMenuMove);
		attachEvent('onmousewheel', startMenuMove);
	}
} // setMoveMenu

/**
 * Return the current left offset of the element relative to it's
 * enclosing positionable element.  I.e. this is the value that you
 * could assign to style.left for it to stay where it is (if the 
 * element is positionable).
 */
function getPositionableOffsetLeft(el)
{
	if (el == null)	return 0;
	
	var x = el.offsetLeft;
	//logger("getPositionableOffsetLeft: id=" + el.id + ", pos=" + el.currentStyle.position);
	
	var parent = el.offsetParent;
	if (parent != null) {
		var pos = (typeof parent.currentStyle == "object" ? parent.currentStyle.position : parent.style.position);
		if (pos != "absolute") 
			x += getPositionableOffsetLeft(parent);
	}
	//logger("getPositionableOffsetLeft returns " + x);
	return x;
}

/**
 * Return the current top offset of the element relative to it's
 * enclosing positionable element.  I.e. this is the value that you
 * could assign to style.top for it to stay where it is.
 */
function getPositionableOffsetTop(el)
{
	if (el == null)	return 0;

	var y = el.offsetTop;

	var parent = el.offsetParent;
	var parent = el.offsetParent;
	if (parent != null) {
		var pos = (typeof parent.currentStyle == "object" ? parent.currentStyle.position : parent.style.position);
		if (pos != "absolute") 
			y += getPositionableOffsetTop(parent);
	}
	return y;
}

