Source for flyout.js

/*
 * Flyout menus for the University of Washington Home Page
 * University of Washington / UW Technology
 * May, 2005
 * Documentation can be found at
 *     http://www.washington.edu/webinfo/case/flyout/
 * $Id: flyout.js,v 2.10 2008/01/28 21:57:21 fmf Exp $
 *
 * You are free to copy and/or use these flyout menus (or
 * derivative works) but please make sure this comment block
 * remains intact and in its entirety at the top of the file.
 */

var d = document;
FlyLyr.on = 0;

if ('undefined' != typeof d.getElementById) {
	FlyLyr.isMac = navigator.platform.indexOf ('Mac') >= 0;
	FlyLyr.on = 1;
	var ua = navigator.userAgent;
	FlyLyr.isOpera = ua.indexOf (' Opera ') >= 0;
	FlyLyr.isKonq = ua.indexOf (' Konqueror') >= 0;
	
	initFlyLyr ();
	initDelay ();
	
	d.write ('<style type="text/css">' +
				'.flyout { visibility: hidden; position: absolute; left: 0; ' +
				'top: 0; margin-right: -15px; margin-bottom: -15px; }' +
				'.flyoutgen table td { padding: 2px; }' +
				'.flyoutgen table table td { padding: 1px; }' +
				'.flyoutgen span { font-size: xx-small; }<\/style>');
}

function useLayer (id) {
	if (! FlyLyr.on)
		return;
	var elem = findObj ('l_' + id);
	if (! elem)
		return;
	tagParent (elem);
	if (FlyLyr.defs.preDetach)
		FlyLyr.defs.preDetach (elem);
	if (elem.parentNode.tagName != 'BODY' && ! elem.flyParent &&
			FlyLyr.defs['position'] != 'CSS')
		d.body.appendChild (elem.parentNode.removeChild (elem));
	new FlyLyr (id);
}

function makeLayer () {
	if (! FlyLyr.on)
		return;
	var a = arguments;
	var img = null;
	var fd = FlyLyr.defs;
	if (typeof a[0] == 'object') {
		img = a[1];
		a = a[0];
	} else {
		var iobj = findObj (a[0]);
		if (iobj && iobj.tagName == 'IMG')
			img = { src: fd.outimg || iobj.src, cls: iobj.className };
		else
			img = { src: fd.outimg || '/home/graphics/mo/noarrow.gif', cls: '' };
	}
	var id = a[0];
	var title = a[1];
	d.write ('<div id="l_' + id + '" class="flyout flyoutgen">' +  
				'<table cellspacing="0" border="1" bgcolor="' +
				fd.background + '" bordercolor="' + fd.border + '">');
	if (title)
		d.write ('<tr><td class="' + fd.titleclass +
					'" align="center" bgcolor="' + fd.titlebackground +  '">');
	makeCell (title, null);
	var divstart = '<tr><td><table cellspacing="0" border="0">' + "\n";
	d.write ('<\/td><\/tr>' + divstart);
	var rowstart = '<tr><td class="' + fd.useclass + '"' +
					(fd.alignright && ' align="right"') + '>';
	for (var j = 2; j < a.length; ++j) {
		var nsp = a[j].length;
		if (! nsp) {
			d.write ('<\/table><\/td><\/tr>' + divstart);
			continue;
		}
		d.write (rowstart);
		var s = a[j].replace (/^ +/, '');
		nsp -= s.length;
		if (nsp) {
			d.write ('<span>');
			while (nsp--)
				d.write ('&nbsp;&nbsp;');
			d.write ('<\/span>');
		}
		var submenu = makeCell (s, img);
		if (submenu) {
			var submenuargs = [submenu];
			for (++j; j < a.length; ++j) {
				if (a[j] == '<' + submenu)
					break;
				submenuargs[submenuargs.length] = a[j];
			}
			makeLayer (submenuargs, img);
		}
		d.write ('<\/td><\/tr>' + "\n");
	}
	d.write ('<\/table><\/td><\/tr><\/table><\/div>' + "\n");
	new FlyLyr (id);
}

function makeCell (str, aimg) {
	var eqpos = str.indexOf ('=');
	var args = '';
	if (eqpos <= 0) {
		d.write (str);
		return null;
	}
	var imgstr = '';
	var pos = str.indexOf ('>');
	var submenu = null;
	if (pos > 0) {
		submenu = str.substr (pos + 1);
		args = ' onmouseover="mIn (\'' + submenu +
				'\')" onmouseout="mOut (\'' + submenu + '\')"';
		str = str.substr (0, pos);
		imgstr = ' <img id="' + submenu + '" src="' + aimg.src + '" alt="' +
						(aimg.cls && '" class="' + aimg.cls) + '"/>';
	}
	if ((pos = str.indexOf ('@')) > 0) {
		args += ' target="' + str.substr (pos + 1) + '"';
		str = str.substr (0, pos);
	}
	d.write ('<a href="' + str.substr (eqpos + 1) + '"' + args + '>' +
				str.substr (0, eqpos) + imgstr + '<\/a>');
	return submenu;
}

function positionLayer () {
	if (typeof this.lyr.flyParent != 'object') {
		tagParent (this.lyr);
		if (FlyLyr.isMac && document.all)
			this.positionLayer ();
	}
	var img = this.image;
	this.getObjMetrics (img);
	this.normalizeVars ();
	var xpos = img.width + this.hpad;
	if (this.positionleft)
		xpos = -this.lyr.offsetWidth - this.hpad;
	xpos += img.flyX;
	var ypos = img.flyY + this.vpad;
	if (this.position) {
		var strs = this.position.split (';');
		for (var i = 0; i < strs.length; ++i) {
			var str = strs[i];
			var pos = str.search (/[-|]/);
			if (pos <= 0)
				continue;
			var direct = str.substr (pos, 1);
			var obj = img;
			if (str.substr (0, pos) != 'IMG') {
				if (! (obj = findObj (str.substr (0, pos))))
					continue;
				this.getObjMetrics (obj);
			}
			var posstr = str.substr (pos + 1);
			var cmp = posstr.search (/[<=>]/);
			if (cmp <= 0)
				continue;
			var opos, mpos;
			if (direct == '-') {
				opos = targetPos (posstr.substr (0, cmp), obj.flyX, obj.width);
				mpos = targetPos (posstr.substr (cmp + 1), xpos,
									this.lyr.offsetWidth);
			} else {
				opos = targetPos (posstr.substr (0, cmp ), obj.flyY,
									obj.height);
				mpos = targetPos (posstr.substr (cmp + 1), ypos,
									this.lyr.offsetHeight);
			}
			var rel = posstr.substr (cmp, 1);
			if ((rel == '<' && mpos < opos) || (rel == '>' && mpos > opos) ||
					rel == '=') {
				if (direct == '-')
					xpos += opos - mpos;
				else
					ypos += opos - mpos;
			}
		}
	}
	xpos = posInWindow (xpos, this.lyr.offsetWidth, window.pageXOffset,
							window.innerWidth);
	ypos = posInWindow (ypos, this.lyr.offsetHeight, window.pageYOffset,
							window.innerHeight);
	if (this.lyr.flyParent) {
		this.getObjMetrics (this.lyr.offsetParent);
		xpos -= this.lyr.offsetParent.flyX;
		ypos -= this.lyr.offsetParent.flyY;
	}
	this.moveTo (xpos, ypos);
}

function getObjMetricsIE (obj) {
	var oObj = obj;
	oObj.width = obj.offsetWidth || obj.width;
	oObj.height = obj.offsetHeight || obj.height;
	oObj.flyX = oObj.flyY = 0;
	var seenTable = 0;
	if (FlyLyr.isMac && oObj.offsetParent.tagName == 'BODY') {
		if (getInt (oObj.clientLeft) + getInt (oObj.clientTop)) {
			oObj.flyX = oObj.clientLeft;
			oObj.flyY = oObj.clientTop;
		} else {
			oObj.flyX = oObj.offsetLeft;
			oObj.flyY = oObj.offsetTop;
		}
		return;
	}
	for (; obj; obj = obj.offsetParent) {
		var tag = obj.tagName;
		if (! FlyLyr.noCpos[tag] && (! FlyLyr.isMac || obj != oObj) &&
				! FlyLyr.isOpera) {
			oObj.flyX += getInt (obj.clientLeft);
			oObj.flyY += getInt (obj.clientTop);
		}
		var noOent = FlyLyr.noOpos[tag];
		if (! noOent || (noOent < 0 && obj.currentStyle &&
				obj.currentStyle.display != 'block')) {
			oObj.flyX += getInt (obj.offsetLeft);
			oObj.flyY += getInt (obj.offsetTop);
		}
		if (FlyLyr.isMac && tag == 'TABLE')
			if (seenTable++)
				oObj.flyY += getInt (obj.cellSpacing);
	}
}

function getObjMetricsDOM (obj) {
	var oObj = obj;
	obj.width = obj.width || obj.offsetWidth;
	obj.height = obj.height || obj.offsetHeight;
	oObj.flyX = oObj.flyY = 0;
	for (; obj; obj = obj.offsetParent) {
		if (obj.tagName == 'TABLE') {
			var bord = parseInt (obj.border);
			if (isNaN (bord)) {
				if (obj.getAttribute ('frame')) {
					++oObj.flyX;
					++oObj.flyY;
				}
			} else if (bord > 0) {
				oObj.flyX += bord;
				oObj.flyY += bord;
			}
		}
		oObj.flyX += obj.offsetLeft;
		oObj.flyY += obj.offsetTop;
	}
}

function getInt (n) {
	n = parseInt (n);
	if (isNaN (n))
		return 0;
	return n;
}

function targetPos (wherestr, start, len) {
	var where = wherestr.substr (0, 1);
	var adj = getInt (wherestr.substr (1));
	if (where == 'l' || where == 't')
		return start + adj;
	if (where == 'r' || where == 'b')
		return start + len + adj;
	return start + len / 2 + adj;
}

function posInWindow (loc, objSize, scroll, winSize) {
	var move = loc + objSize - scroll - winSize;
	if (move > 0)
		loc -= move;
	if (loc < scroll)
		loc = scroll;
	return loc;
}

function tagParent (lyr) {
	for (var p = lyr.offsetParent; p; p = p.offsetParent)
		if (p.className.indexOf ('flyout') >= 0) {
			lyr.flyParent = p;
			return;
		}
	lyr.flyParent = null;
}

function FlyLyr (id) {
	this.lyr = findObj ('l_' + id);
	eval ('this.lyr.onmouseover = function () { mIn ("' + id + '") }');
	eval ('this.lyr.onmouseout = function () { mOut ("' + id + '") }');
	this.id = id;
	FlyLyr.lyrs[id] = this;
	for (var a in FlyLyr.defs)
		this[a] = FlyLyr.defs[a];
}

function flyDefs (defs) {
	if (! FlyLyr.on)
		return;
	if (! defs)
		defs = FlyLyr.defdefs;
	for (var def in defs)
		FlyLyr.defs[def] = defs[def];
}

function initFlyLyr () {
	FlyLyr.prototype.doHide = function () {
		this.stopHide ();
		this.realHide ();
		if (this.hideImage)
			this.hideImage (this.image, this.lyr);
		else if (this.outimg && this.image.tagName == 'IMG')
			this.image.src = this.outimg;
		FlyLyr.showing[this.id] = null;
	};
	FlyLyr.prototype.doShow = function () {
		this.stopShow ();
		if (! this.image && ! (this.image = findObj (this.id)))
			return;
		if (this.position != 'CSS')
			this.positionLayer ();
		for (var l in FlyLyr.hideQueue)
			if (FlyLyr.hideQueue[l])
				FlyLyr.hideQueue[l].doHide ();
		if (! this.outimg && this.image.tagName == 'IMG')
			this.outimg = this.image.src;
		if (this.showImage)
			this.showImage (this.image, this.lyr);
		else if (this.overimg && this.image.tagName == 'IMG')
			this.image.src = this.overimg;
		this.realShow ();
		FlyLyr.showing[this.id] = this;
	};
	FlyLyr.prototype.queueHide = function () {
		if (! FlyLyr.hideQueue[this.id]) {
			this.queuedHide = new Delay (this.timeout, this, 'doHide');
			FlyLyr.hideQueue[this.id] = this;
		}
	};
	FlyLyr.prototype.queueShow = function () {
		if (! FlyLyr.showQueue[this.id]) {
			this.queuedShow = new Delay (this.pause, this, 'doShow');
			FlyLyr.showQueue[this.id] = this;
		}
	};
	FlyLyr.prototype.stopHide = function () {
		if (this.queuedHide) {
			this.queuedHide.stop ();
			FlyLyr.hideQueue[this.id] = null;
		}
	};
	FlyLyr.prototype.stopShow = function () {
		if (this.queuedShow) {
			this.queuedShow.stop ();
			FlyLyr.showQueue[this.id] = null;
		}
	};

	FlyLyr.lyrs = new Object ();
	FlyLyr.showing = new Object ();
	FlyLyr.hideQueue = new Object ();
	FlyLyr.showQueue = new Object ();
	FlyLyr.defs = new Object ();
	FlyLyr.defdefs = {
		background: '#ffffff',
		titlebackground: '#333399',
		border: '#333399',
		useclass: 'navlink',
		titleclass: 'barlink',
		overimg: '/home/graphics/mo/arrow.gif',
		outimg: null,
		pause: 250,
		timeout: 1000,
		positionleft: 0,
		alignright: 0,
		hpad: 2,
		vpad: -2,
		position: '',
		preDetach: null,
		showImage: null,
		hideImage: null
	}
	flyDefs ();
	FlyLyr.prototype.positionLayer = positionLayer;

	FlyLyr.prototype.realHide = function () {
		this.lyr.style.visibility = 'hidden';
	};
	FlyLyr.prototype.realShow = function () {
		this.lyr.style.visibility = 'visible';
	};
	FlyLyr.prototype.normalizeVars = function () {};
	FlyLyr.prototype.moveTo = function (x, y) {
		this.lyr.style.left = x + 'px';
		this.lyr.style.top = y + 'px';
	};
	FlyLyr.prototype.getObjMetrics = getObjMetricsDOM;
	if (d.all && ! FlyLyr.isKonq) {
		FlyLyr.prototype.getObjMetrics = getObjMetricsIE;
		if (! FlyLyr.isOpera)
			FlyLyr.prototype.normalizeVars = function () {
				var de = d.documentElement && d.documentElement.clientWidth ?
								d.documentElement : d.body;
				window.innerWidth = de.clientWidth;
				window.innerHeight = de.clientHeight;
				window.pageXOffset = d.body.scrollLeft;
				window.pageYOffset = d.body.scrollTop;
			};
		FlyLyr.noCpos = {
			'BODY': 1,
			'TABLE': 1
		};
		FlyLyr.noOpos = {};
		if (! FlyLyr.isOpera)
			FlyLyr.noOpos['A'] = -1;
		if (FlyLyr.isMac && ! FlyLyr.isOpera) {
			FlyLyr.noOpos = FlyLyr.noCpos;
			FlyLyr.noCpos = {
				'DIV': 1,
				'TD': 1,
				'TH': 1
			};
		}
	}
}

function Delay (delay, obj, fn) {
	this.obj = obj;
	var uid = ++Delay.nuid;
	this.timeoutid = setTimeout ('Delay.dispatch (' + uid + ')', delay);
	this.uid = uid;
	this.func = fn;
	Delay.disparr[uid] = this;
}

function initDelay () {
	Delay.prototype.stop = function () {
		clearTimeout (this.timeoutid);
		Delay.disparr[this.uid] = null;
	};
	Delay.dispatch = function (uid) {
		var item = Delay.disparr[uid];
		if (! item)
			return;
		item.stop ();
		eval ('item.obj.' + item.func + ' ()');
	};
	Delay.nuid = 0;
	Delay.disparr = new Object;
}

function findObj (n) {
	return d.getElementById (n) || d[n] || (d.all && d.all[n]);
}

function mIn (id) {
	if (! FlyLyr.on)
		return;
	var lyr = FlyLyr.lyrs[id];
	if (! lyr) {
		useLayer (id);
		lyr = FlyLyr.lyrs[id];
		if (! lyr)
			return;
	}
	if (FlyLyr.showing[id])
		lyr.stopHide ();
	else
		lyr.queueShow ();
}

function mOut (id) {
	if (! FlyLyr.on)
		return;
	if (! id) {
		for (var l in FlyLyr.showing)
			if (FlyLyr.showing[l])
				FlyLyr.showing[l].queueHide ();
		for (l in FlyLyr.showQueue)
			if (FlyLyr.showQueue[l])
				FlyLyr.showQueue[l].stopShow ();
	} else if (FlyLyr.showing[id])
		FlyLyr.showing[id].queueHide ();
	else if (FlyLyr.showQueue[id])
		FlyLyr.showQueue[id].stopShow ();
}