function detectIEVersion()
{
	var search = "msie";
	var userAgent = navigator.userAgent.toLowerCase();
	var iePosition = userAgent.indexOf(search);
	if (iePosition == -1)
	{
		return 0;
	}
	
	var ie_version = parseFloat(userAgent.substring(iePosition+search.length));
	
	return ie_version;
}

function isIE()
{
	var search = "msie";
	var userAgent = navigator.userAgent.toLowerCase();
	return (userAgent.indexOf("opera") == -1 && userAgent.indexOf("msie") != -1);
}

var is_ie = isIE();

var is_opera = navigator.userAgent.toLowerCase().indexOf("opera") != -1;
var is_mozilla = navigator.userAgent.toLowerCase().indexOf("gecko") != -1;

var ie_version = detectIEVersion();

// Adds a new function to be called during the body's onLoad event.
function addLoadFunction(func)
{
	addEvent(window, "onload", func);
}

// Adds a new function to be called during the window's onResize event.
function addResizeFunction(func)
{
	addEvent(window, "onresize", func);
}

var functionNameRE = /function (\S+)/;
function getClassName(obj)
{
	var desc = obj.constructor.toString();
	var t = functionNameRE.exec(desc);
	if (t)
	{
		return t[1];
	}
	else
	{
		return "unknown";
	}
}
	
var activeElements = new Array();

function addActiveElement(obj)
{
	activeElements.push(obj);
}

function clearArray(a)
{
	for (var n = 0; n < a.length; a++)
	{
		if (a[n].release)
		{
			a[n].release();
		}
		
		a[n] = null;
	}
}

function clearObject(obj)
{
	for (var key in obj)
	{
		if (obj[key].release)
		{
			obj[key].release();
		}
		debugLog("clearing " + key);
		obj[key] = null;
	}
}

function clearActiveElements()
{
	for (var n = 0; n < activeElements.length; n++)
	{
		var o = activeElements[n];
		if (isArray(o))
		{
			clearArray(o);
		}
		else if (o.release)
		{
			o.release();
		}
		else if (isObject(o))
		{
			clearObject(o);
		}
	}
}

addEvent(window, "onunload", clearActiveElements);

function attachKeyToButton(keys, button_name)
{
	function f(e) 
	{ 
		if (contains(keys, getKeyPressed(e)))
		{
			document.forms[0][button_name].click();
		}
	}

	addEvent(document, "onkeypress", f);
}

// holds any extra information needed to be logged
var errorDetails = new Object();

function addErrorDetail(key, data)
{
	errorDetails["custom-" + key] = data;
}

function logError(msg, url, linenumber) 
{
	var errorPage = "javascript_error.do";
	// verify that 1) we are in production mode, and 2) we are not already on the error-handling page	
	if (isProduction && window.location.pathname.indexOf(errorPage) == -1)
	{
		var parameters = { "true-window" : true, "width" : 500, "height" : 250 };
		
		if (createDialogBox(null, "error_logging_window", parameters))
		{
			var w = lastDialogBoxWindow;
			var d = w.document;
			
			// first write a wrapper iframe for IE's poor modal dialog box support
			// <base target=""> doesn't work for IE5 either.
			d.open();
			d.write("<html><head></head><body style='padding: 0; margin: 0;'>");
			d.write("<iframe name='submit' width='100%' height='100%' frameborder='no' scrolling='no'></iframe>");			
			d.write("</body></html>");
			d.close();

			// now write the form to the iframe
			var f = w.frames["submit"];
			d = f.document;
			d.open();
			d.write("<html><head></head><body>");
			errorDetails["msg"] = msg;
			errorDetails["source"] = url;
			errorDetails["linenumber"] = linenumber;
			errorDetails["cookies"] = document.cookie;
			errorDetails["page"] = window.location.href;
			
			d.write("<form method='POST' action='" + sitePrefix + "/" + errorPage + "'>");
			// write out each value
			for (key in errorDetails)
			{
				d.write("<input type='hidden' name='" + key + "' value=" + String(errorDetails[key]).quote() + ">");
			}
			
			d.write("</form></body></html>");
			d.close();
			
			// now submit the form with the parameters
			d.forms[0].submit();
		}
	}
	else
	{
		// simple error for debugging sites
		alert("Error: " + msg + "\nat " + linenumber + " in " + url);
	}
	
	// now clear any details
	errorDetails = new Object();

	return true;
}

var errorHandlers = [ window.onerror, logError ];

function handleErrors(msg, url, linenumber)
{
	for (var i = errorHandlers.length - 1; i >= 0; --i)
	{
		if (errorHandlers[i])
		{
			var ret = errorHandlers[i](msg, url, linenumber);
			if (ret) break;
		}
	}
	
	return true;
}

window.onerror = handleErrors;

var resizableItems = new Array();

// Adds a new function to be called during the window's onResize event,
// or any time resizeAllItems is called
function addResizableItem(func)
{
	resizableItems.push(func);
}

function resizeAllItems()
{
	for (var n = 0; n < resizableItems.length; n++)
	{
		resizableItems[n]();
	}
}

addEvent(window, "onresize", resizeAllItems);

function checkCookies(disabledPage, enabledPage)
{
	if (disabledPage)
	{
		setPermanentCookieForPath("test_cookies", "true", fullCookiePath);
		function testCookies()
		{
			var cookie = findCookie("test_cookies");
			
			removeCookie("test_cookies");
			if (cookie != "true")
			{
				window.location.href = disabledPage;
			} 
			else if (enabledPage)
			{
				window.location.href = enabledPage;
			}			
		}
		// give the browser some time to set the cookie
		window.setTimeout(testCookies, 10);
	}
}

if (typeof(cookiesNotEnabledPage) != "undefined")
{
	checkCookies(cookiesNotEnabledPage)
}

function isUndefined(a) 
{
    return typeof a == 'undefined';
} 

function isFunction(a) 
{
    return typeof a == 'function';
}

function isObject(a) 
{
    return (a && typeof a == 'object') || isFunction(a);
}

function isNumber(a) 
{
	return typeof(a) == 'number';
}

function isString(a) 
{
	return typeof(a) == 'string';
}

function isArray(a) 
{
    return isObject(a) && a.constructor == Array;
}


Function.prototype.method = function (name, func) 
{
    this.prototype[name] = func;
    return this;
};

String.method('trim', function () 
{
    return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1");
}); 


String.method('replicate', function (count) 
{
	var result = [];
	for (var n = 0; n < count; n++) result[result.length] = this;
	return result.join("");
}); 

function contains(array, a) 
{
    for (var i = 0; i < array.length; i++)
    {
    	if (array[i] == a)
    	{
    		return true;
    	}
    }
    return false;
};

// adds 'apply' for IE
if (!isFunction(Function.apply)) 
{
    Function.method('apply', function (o, a) 
    {
        var r, x = '____apply';
        if (!isObject(o)) 
        {
            o = {};
        }
        o[x] = this;
        switch ((a && a.length) || 0) 
        {
        case 0:
            r = o[x]();
            break;
        case 1:
            r = o[x](a[0]);
            break;
        case 2:
            r = o[x](a[0], a[1]);
            break;
        case 3:
            r = o[x](a[0], a[1], a[2]);
            break;
        case 4:
            r = o[x](a[0], a[1], a[2], a[3]);
            break;
        case 5:
            r = o[x](a[0], a[1], a[2], a[3], a[4]);
            break;
        case 6:
            r = o[x](a[0], a[1], a[2], a[3], a[4], a[5]);
            break;
        default:
            alert('Too many arguments to apply.');
        }
        // IE 5 doesn't seem to support this
        //delete o[x];
        return r;
    });
} 

// adds 'call' for IE
if (!isFunction(Function.call)) 
{
    Function.method('call', function (o) 
    {
        var r, x = '____call';
        if (!isObject(o)) 
        {
            o = {};
        }
        o[x] = this;
        a = arguments;

        switch ((a && a.length) || 0) 
        {
        case 0:
            r = o[x]();
            break;
        case 1:
            r = o[x](a[0]);
            break;
        case 2:
            r = o[x](a[0], a[1]);
            break;
        case 3:
            r = o[x](a[0], a[1], a[2]);
            break;
        case 4:
            r = o[x](a[0], a[1], a[2], a[3]);
            break;
        case 5:
            r = o[x](a[0], a[1], a[2], a[3], a[4]);
            break;
        case 6:
            r = o[x](a[0], a[1], a[2], a[3], a[4], a[5]);
            break;
        default:
            alert('Too many arguments to apply.');
        }
        o[x] = null;
        // IE 5 doesn't seem to support this
//        delete o[x];
        
        return r;
    });
} 

// adds 'splice' for IE
if (!isFunction(Array.prototype.splice)) {
    Array.method('splice', function (s, d) {
        var max = Math.max,
            min = Math.min,
            a = [], // The return value array
            e,  // element
            i = max(arguments.length - 2, 0),   // insert count
            k = 0,
            l = this.length,
            n,  // new length
            v,  // delta
            x;  // shift count

        s = s || 0;
        if (s < 0) {
            s += l;
        }
        s = max(min(s, l), 0);  // start point
        d = max(min(isNumber(d) ? d : l, l - s), 0);    // delete count
        v = i - d;
        n = l + v;
        while (k < d) {
            e = this[s + k];
            if (!isUndefined(e)) {
                a[k] = e;
            }
            k += 1;
        }
        x = l - s - d;
        if (v < 0) {
            k = s + i;
            while (x) {
                this[k] = this[k - v];
                k += 1;
                x -= 1;
            }
            this.length = n;
        } else if (v > 0) {
            k = 1;
            while (x) {
                this[n - k] = this[l - k];
                k += 1;
                x -= 1;
            }
        }
        for (k = 0; k < i; ++k) {
            this[s + k] = arguments[k + 2];
        }
        return a;
    });
}

// adds 'push' for IE
if (!isFunction(Array.prototype.push)) {
    Array.method('push', function () {
        var A_p = 0;
		for (A_p = 0; A_p < arguments.length; A_p++) {
			this[this.length] = arguments[A_p];
		}
		return this.length;
    });
}

// adds 'pop' for IE
if (!isFunction(Array.prototype.pop)) {
    Array.method('pop', function () {
        return this.splice(this.length - 1, 1)[0];
    });
}

// adds 'quote'
String.method('quote', function () {
    return '"' + this.replace(/(["\\])/g, '\\$1') + '"';
});

// adds 'quote'
String.method('htmlQuote', function () {
    return '"' + this.replace(/["]/g, '&quot;') + '"';
});

// appends the new event handler to the element for the given event ("onclick", "onsubmit", etc).
// The original handler is called first, if there is one, then the new one.
// This function can be called as many times as needed, and every event added
// will get called in the order they were added.
function addEvent(element, event, newHandler)
{
	var oldHandler = element[event];
	// we do NOT use an inner function for memory reasons.  See below.
	element[event] = makeCascadedHandler(oldHandler, newHandler, false);
}

// Adds an event handler that is executed only if the previous function
// returned true (or if there was no previous function). This
// is useful for event handlers whose return value is significant
// (like onClick).
function addConditionalEvent(element, event, newHandler)
{
	var oldHandler = element[event];
	// we do NOT use an inner function for memory reasons.  See below.
	element[event] = makeCascadedHandler(oldHandler, newHandler, true);
}

/* This function returns a function so that it is a closure around
the two handlers, but NOT the element it is being added to.  Otherwise,
we get a circular reference where the element references the function,
which in turn (because of the closure) references the element.
This causes a memory leak in IE, because it cannot handle circular 
references between javascript things and DOM elements.
Having this separate function breaks the closure on the element.
*/
function makeCascadedHandler(oldHandler, newHandler, checkReturn)
{
	return function(e)
	{
		var args = new Array();
		args[0] = e;
		
		// the default is to run the new function
		var executeNew = true;
		
		// first call the original handler, if there was one...
		if (oldHandler)
		{
			// run the old function, but if it returns non-true
			// and we are checking the return, then don't execute the new function
			if (!oldHandler.apply(this, args) && checkReturn)
			{
				executeNew = false;
			}
		}
		
		// then call our new handler
		if (executeNew)
		{
			return newHandler.apply(this, args);
		}
	}
}

function setElementStyle(id, styleName, value)
{
	var el = document.getElementById(id);
	if (el)
	{
		el.style[styleName] = value;
	}
}
	
// returns true if the given style appears in element's class.
// Does not match subwords: testing for 'hover' will not match 'hover-state'
function hasStyle(element, style)
{
	var regexp = new RegExp("\\b" + style + "\\b");
	return regexp.test(element.className);
}

function addStyle(element, style)
{
	if (hasStyle(element, style) == false)
	{
		element.className = (element.className + " " + style);
	}
}

function removeStyle(element, style)
{
	setClass(element, element.className.replace(new RegExp("\\b" + style + "\\b"), ""));
}

// adds the class element if enabled is true, removes it if false
function setConditionalStyle(element, style, enabled)
{
	if (enabled)
	{
		addStyle(element, style);
	}
	else
	{
		removeStyle(element, style);
	}
}

// removes the oldClassname if it exists, and adds newClassname
function switchStyle(element, oldClassname, newClassname)
{
	removeStyle(element, oldClassname);
	addStyle(element, newClassname);
}

/* Sets the element to have the given class name,
but only if it is different than what is there (which can be more efficient).
*/
function setClass(element, newClass)
{
	// as an optimization, don't change the class (which forces an update)
	// unless it's really changed
	if (element.className != newClass)
	{
		element.className = (newClass);
	}
}



/* A class that aids in add/removing CSS class components (loosely referred to as 'styles' here) 
to an element's class.  This both simplifies the tedium of adding a class once only,
by checking to make sure it's not there, and makes it more efficient.  It is more efficient
in two ways: first, the regular expressions are constructed when the class is constructed,
and if you repeatedly add/remove the same class style, you can construct this class once.
Second, you can build up a composite class with the merge function, and only set the element's
className once (which triggers less work on the part of some browsers).

Note: there is an oddity in the regular expression test() function.  If it is a global regular expression,
it is intended to be executed in a loop like:

while re.test()) 
{
}

If it is called repeatedly, it returns true for each match in the string, then returns false.  After that,
it is reset.  This means that we must use a non-global regular expression for testing, so that it always returns the same
result, and a global one for matching, so it replaces all instances.
*/
function ClassModifier(style)
{
	this.style = style;
	// matches (and captures) any valid style
	this.anyStyleRE = /\b(\S+)\b/g;
	
	// matches the separate word 'foo' (NOT a global search--see note above)
	this.originalStyleTestRE = new RegExp("\\b" + style + "\\b");

	// used to replace all instances of the separate word 'foo'
	this.originalStyleReplaceRE = new RegExp("\\b" + style + "\\b", "g");

	// matches the separate word 'foo-anystyle' (global for replaces)
	this.modifiedStyleReplaceRE = new RegExp("\\b" + style + "-(\\S+)\\b", "g");
}

var spacesRE = /\s{2,}/g;
var dashesRE = /\s+-+/g;
var trailingDashesRE = /\S+-+\s+/g;

/* Adds this class component to the element's class. */
ClassModifier.prototype.add = function (element)
{
	if (this.originalStyleTestRE.test(element.className) == false)
	{
		setClass(element, element.className + " " + this.style);
	}
}

/* Returns true if the style already exists. */
ClassModifier.prototype.has = function (element)
{
	return this.originalStyleTestRE.test(element.className);
}

/* Removes this class component from the element's class. */
ClassModifier.prototype.remove = function (element)
{
	setClass(element, element.className.replace(this.originalStyleReplaceRE, ""));
}

/* Returns the resultant class name from merging this component
into the given class name. */
ClassModifier.prototype.merge = function (className)
{
	if (this.originalStyleTestRE.test(className) == false)
	{
		return className + " " + this.style;
	}
	else
	{
		return className;
	}
}

/* Returns the resultant class name from separating this class
component from the given class name (the opposite of merge). */
ClassModifier.prototype.separate = function (className)
{
	return className.replace(this.originalStyleReplaceRE, "");
}

/* Adds a new class to an existing element both as itself and
as a modifier on every other class on the element. So, for example, using 'foo'
on an element with a classes 'bar a b' would produce 'bar a b foo-a foo-b foo'.
This is to help out IE, which doesn't properly support rules like .foo.a.
Thus, your style sheets can use .foo-a to achieve the same effect.
*/
ClassModifier.prototype.addRedundantly = function (element)
{
	// first make sure it's not there already
	if (this.originalStyleTestRE.test(element.className) == false)
	{
		// turn all styles 'anystyle' into 'foo-anystyle'
		var c = element.className.replace(this.anyStyleRE, this.style + "-$1");
		// add 'foo' to the end
		c = c + " " + this.style;
		// now add the above to the existing class to get 'anystyle foo-anystyle foo'
		element.className = (element.className + " " + c.trim());
	}
}

/* Undoes the effect of addRedundantly. */
ClassModifier.prototype.removeRedundantly = function (element)
{
	var c = element.className.replace(this.modifiedStyleReplaceRE, "");
	c = c.replace(this.originalStyleReplaceRE, "");
	c = c.replace(dashesRE, " ");
	c = c.replace(trailingDashesRE, " ");
	c = c.replace(spacesRE, " ");
	element.className = (c.trim());
}


function getFirstChildByTag(element, tag)
{
	tag = tag.toUpperCase();
	var children = element.getElementsByTagName(tag);
	if (children.length > 0)
	{
		return children[0];
	}
	else
	{
		return null;
	}
}

function getFirstParentByTag(element, tag)
{
	tag = tag.toUpperCase();
	var node = element.parentNode;
	while (node.nodeName != tag && node != node.parentNode) node = node.parentNode;
	
	if (node.nodeName == tag)
	{
		return node;
	}
	else
	{
		return null;
	}
}

function getFirstParentByClass(element, className)
{
	var node = element.parentNode;
	while (node.className != className && node != node.parentNode) node = node.parentNode;
	
	if (node.className == className)
	{
		return node;
	}
	else
	{
		return null;
	}
}

function createSubmitButton(text, name, onClick, target)
{
	createButtonInternal(text, name, onClick, target);
}

function createPushButton(text, onClick, target)
{
	createButtonInternal(text, null, onClick, target);
}

function createLinkButton(text, dest, enabled, target)
{
	var onClick = "open_link(\"" + dest + "\")";
	if (target)
	{
		onClick = "open_link(\"" + dest + "\", \"" + target + "\")";
	}
	
	createButtonInternal(text, null, onClick, enabled);
}

function createPopupButton(text, dest, enabled, target)
{
	var onClick = "openNewWindowFromButton(\"" + dest + "\", \"" + target + "\")";
	createButtonInternal(text, null, onClick, enabled);
}

// creates buttons with <input>.  looks ugly on old IE versions, but works.
function createButtonInternal(text, name, onClick, enabled, target)
{
	var html = "<input ";
	if (name)
	{
		html += " name='" + name + "' type='submit'";
	}
	else
	{
		html += " type='button'";
	}
	
	html += " value='" + text + "'";
		
	if (onClick)
	{
		html += " onClick='" + onClick + "'";
	}
	
	if (target)
	{
		html += " target='" + target + "'";
	}
	
	if (enabled == false)
	{
		html += " disabled";
	}
	
	html += ">";
	
	document.write(html);	
}

var setButtonStyle = new ClassModifier("set");
var pushedButtonStyle = new ClassModifier("pushed");
var highlightedButtonStyle = new ClassModifier("highlighted");

var activeToggleButton = null;

function lowerToggleButton()
{
	activeToggleButton = this; 
	pushedButtonStyle.addRedundantly(this);
	
	return false;
}

function initializeToggleButton(l, down)
{
	addEvent(l, "onclick", clickToggleButton);
	l.onmousedown = lowerToggleButton;
	l.onmouseup = raiseToggleButton;
	l.onmouseover = hoverToggleButton;
	l.onmouseout = unhoverToggleButton;
	l.ondragstart = returnFalse;
	
	if (down)
	{
		setToggleButton(l);
	}
}

function raiseToggleButton()
{
	activeToggleButton = null; 
	pushedButtonStyle.removeRedundantly(this);
	return false;
}

function setToggleButton(b)
{
	setButtonStyle.addRedundantly(b);
	b.BUTTON_SET = true;
}

function resetToggleButton(b)
{
	setButtonStyle.removeRedundantly(b);
	b.BUTTON_SET = false;
}

function clickToggleButton()
{
	activeToggleButton = null; 
	if (hasStyle(this, "toggle-button"))
	{
		if (this.BUTTON_SET)
		{
			resetToggleButton(this);
		}
		else
		{
			setToggleButton(this);
		}
	}	
	
	return false;
}

function hoverToggleButton()
{
	if (activeToggleButton == this)
	{
		pushedButtonStyle.addRedundantly(this);
	}
	else if (!activeToggleButton)
	{
		highlightedButtonStyle.addRedundantly(this);
	}
	
	return false;
}

function unhoverToggleButton()
{
	if (activeToggleButton == this)
	{
		pushedButtonStyle.removeRedundantly(this);
	}
	else if (!activeToggleButton)
	{
		highlightedButtonStyle.removeRedundantly(this);
	}
	
	return false;
}

function revertToggleButton()
{
	if (activeToggleButton)
	{
		pushedButtonStyle.removeRedundantly(activeToggleButton);
		highlightedButtonStyle.removeRedundantly(activeToggleButton);
	}
	
	activeToggleButton = null;
}

addEvent(document, "onmouseup", revertToggleButton);
addEvent(window, "onblur", revertToggleButton);
addEvent(window, "onunload", revertToggleButton);

// this is an IE-specific event that lets us use high-resolution
// images no matter how the page is printed.  Other browsers have
// no equivalent, so they only do this for "print this page" links
// that call printPage().

addEvent(window, "onbeforeprint", loadPrintImages);

function printPage()
{
	// first make sure the print versions are loaded
	loadPrintImages();

	window.print();
}


// always returns false.  useful for event handlers, to unconditionally block the response.
function returnFalse()
{
	return false;
}

// always returns false.  useful for event handlers, to unconditionally permit the response.
function returnTrue()
{
	return true;
}

// pops up an alert and returns false.  useful for event handlers to find out if it is firing.
function notify(e)
{
	if (!e) var e = window.event;
	alert("event: " + e.type);
	return false;
}

function getTarget(e)
{
	if (e.target) return e.target;
	else if (e.srcElement) return e.srcElement;
}

function getKeyPressed(e)
{
	if (!e) var e = window.event;
	if (e.keyCode) return e.keyCode;
	else if (e.which) return e.which;
}

function disableEnter(e)
{
	// return false if the user pressed Enter
	return getKeyPressed(e) != 13;
}

// stops the propagation of the event. useful if you know you have handled the event by another means
// (since events like mouseup/click and mousemove/mouseout overlap somewhat).
// This can either be an event handler on its own or called at the top of your event handler.
// (You can pass in your e, even if null (IE), because this function gets it from window.event.)
function stopPropagation(e)
{
	if (!e) var e = window.event;
	e.cancelBubble = true;
	if (e.stopPropagation) e.stopPropagation();

	return false;
}

// like stopPropagation, but returns true so this can be used on elements
// that do handle the event (stopPropagation stops the event and cancels).
function stopPropagationAndContinue(e)
{
	stopPropagation(e);
	return true;
}

function followLink()
{
	return followGivenLink(this.href, this.target);
}

function followGivenLink(url, target)
{
	if (target)
	{
		if (window.frames[target])
		{
			// a frame with this name exists, so we use that
			window.frames[target].location = url;
		}
		else
		{
			// no frame exists, so we create a new window
			createWindow(target, 'scrollbars, resizable, menubar=yes', url);
		}
	}
	else
	{
		window.location.href = url;
	}
	
	return false;
}

function encodeStateData(data, forceFlat)
{
	if (forceFlat)
	{
		// do not escape the data if we never encode hierarchies
		return data;
	}
	else if (isObject(data))
	{
		// encode the object hierarchically and use a ~ to remember it
		return "~" + escape(serializeState(data));
	}
	else
	{
		// escape the data in case it has a ~ in it
		return escape(data);
	}
}

// use this function to serialize a hierachical object representing some state
function serializeState(state, delim, forceFlat)
{
	if (!delim) delim = ";";
	var values = new Array();
	
	if (isArray(state))
	{
		// just serialize the values
		for (var i = 0, len = state.length; i < len; ++i)
		{
			if (typeof(state[i]) !== "function")
			{
				var encoded = escape(encodeStateData(state[i], forceFlat));
				values.push(encoded);
				values.push(delim);
			}
		}
	}
	else
	{
		// serialize name-value pairs from the map
		for (var i in state)
		{
			if (typeof(state[i]) !== "function")
			{
				values.push(i);
				values.push("=");
				var encoded = escape(encodeStateData(state[i], forceFlat));
				values.push(encoded);
				values.push(delim);
			}
		}
	}
	
	// remove the trailing delimiter
	if (values.length > 0)
	{
		values.pop();
	}
	
 	return values.join("");
}

function serializeToURL(state)
{
	return serializeState(state, "&", true);
}

function deserializeFromURL(state)
{
	return deserializeState(state, "&", true);
}

function decodeStateData(state, forceFlat)
{
	if (forceFlat)
	{
		// do not unescape the data if there is no hierarchy
		return state;
	}
	else if (state.charAt(0) == "~")
	{
		// extract the state from the hierarchical object (indicated by ~)
		return deserializeState(unescape(state.substr(1)));
	}
	else
	{
		// unescape the data since it was escaped to hide any real ~
		return unescape(state);
	}
}

// use this function to deserialize from a hierachical object representing some state
function deserializeState(serializedState, delim, forceFlat)
{
	if (!delim) delim = ";";
	
	if (serializedState.length == 0)
	{
		// an array with no values serializes to "", but the split()
		// function will return an array with one entry
		return [];
	}
	else
	{
		var parameters = serializedState.split(delim);
		if (serializedState.indexOf("=") != -1)
		{
			// make a map from the name-value pairs
			var result = new Object();
			for (var i = 0, len = parameters.length; i < len; ++i)
			{
				var pair = parameters[i].split("=");
				result[pair[0]] = decodeStateData(unescape(pair[1]), forceFlat);
			}
			
			return result;
		}
		else
		{
			// make an array of values
			var result = new Array();
			for (var i = 0, len = parameters.length; i < len; ++i)
			{
				result[result.length] = decodeStateData(unescape(parameters[i]), forceFlat);
			}
			
			return result;
		}
	}
}

// use this function to deserialize into a simple map of values
function deserializeParameters(serializedParameters, delim)
{
	if (!delim) delim = ";";
	
	if (serializedParameters.length == 0)
	{
		// an array with no values serializes to "", but the split()
		// function will return an array with one entry
		return {};
	}
	else
	{
		var parameters = serializedParameters.split(delim);
		// make a map from the name-value pairs
		var result = new Object();
		for (var i = 0, len = parameters.length; i < len; ++i)
		{
			var pair = parameters[i].split("=");
			result[pair[0]] = unescape(pair[1]);
		}
		
		return result;
	}
}

// use this function to serialize a simple map of values
function serializeParameters(parameters, delim)
{
	if (!delim) delim = ";";
	var values = new Array();
	
	// serialize name-value pairs from the map
	for (var i in parameters)
	{
		values.push(i);
		values.push("=");
		values.push(escape(parameters[i]));
		values.push(delim);
	}
	
	// remove the trailing delimiter
	if (values.length > 0)
	{
		values.pop();
	}
	
	return values.join("");
}


function setDialogBoxTitle()
{
	// this doesn't work in IE--can't seem to set the title on dialog boxes
	top.document.title = self.document.title; 
}


// identifies the latest event that is scheduled to go.  Delayed event handlers 
// increment this value and store that so that when the handler is called,
// they can check to make sure that no other event has arrived in the meantime.
// This lets us "skip" all the actions that have been queued and just do the latest one.
var _delayedEventIdentifier = 0;
var _delayedCutoffTime = new Date().getTime();
//var _delayedEventsDisabled = false;
var _forcedEventIdentifier = 0;

function cancelAllDelayedEvents()
{
	++_delayedEventIdentifier;
}

function _recordNextIdentifier()
{
	// record the identifier that will be scheduled next
	_forcedEventIdentifier = _delayedEventIdentifier + 1;
}

/* Schedules the event to run after the given delay, but only if no 
events have been scheduled in the meantime.  This ensures that we don't 
execute handlers if they are "out of date".  Not all handlers become out of date,
and those handlers should not be scheduled with this function.  One common source
of these types of handlers is mouse events, which can generate a lot of events, but usually
only the last is important.  So this function drops events that are not the latest.

forceRegularEvents: give this argument to ensure that handlers are executed back-to-back
regardless of how fast events are coming in.  If true, we execute the most recent event 
after the previous handler finished executing, even if it is no longer the latest event
by the time we get around to running it.

This is useful for applications like scrollbars, where you want to update to the new state
while the user is dragging, and if we waited for the last event before doing anything,
we could possibly wait forever if mouse events came in faster than we could handle them: in the 
amount of time it took us to skip over old events, more events could have come in.
*/
function scheduleDelayedEvent(handler, delayTime, forceRegularEvents)
{
	// this is a new event, so increment the count and remember what it is
	var identifier = ++_delayedEventIdentifier;

	// now schedule this for the future
	window.setTimeout(execute, delayTime);

	function execute()
	{
		var run = false;
				
		if (identifier === _delayedEventIdentifier)
		{
			// this is the last event, so it is up-to-date and we run it
			run = true;
		}
		else if (_delayedEventIdentifier === _forcedEventIdentifier)
		{
			// this is the first event after the last handler, so it gets to go
			// even if it's not the latest
			run = true;
			_forcedEventIdentifier = 0;
		}
			
		if (run)
		{
			handler();
			
			if (forceRegularEvents)
			{
				// run this function after all the currently pending events,
				// so that we can get the identifier of the event after that
				window.setTimeout(_recordNextIdentifier, 1);
			}
		}
	}
}


var errors = new Array();	
var messages = new Array();
// make a load function that adds these to the end, so that they run after everything else
addLoadFunction(function() { window.setTimeout(showErrorsAndMessages, 1) });

function showErrorsAndMessages()
{
	showErrors(); 
	showMessages();
}

function addError(error)
{
	errors[errors.length] = error;
}
function addMessage(message)
{
	messages[messages.length] = message;
}

function showErrors()
{
	for (var i = 0; i < errors.length; i++)
	{
		alert(errors[i]);
	}
}
function showMessages()
{
	for (var i = 0; i < messages.length; i++)
	{
		alert(messages[i]);
	}
}

function make_copy_button(url, button_text)
{
	if (is_ie)
	{
		document.write("<form action='#'>");
		// style cannot be display: none or visibility: hidden, or IE won't select/copy it.
		// so we move it off the screen.
		document.write("<textarea name='hiddenCopy' cols='1' style='position: absolute; left: -1000px; '>" + url + "</textarea>");
		document.write("<input type='button' value='" + button_text + "' onClick='copyToClipboard(this)'>");
		document.write("</form>");
	}
}

function copyElementToClipboard(id, message)
{
	var range = document.body.createControlRange();
	var el = document.getElementById(id);
	range.add(el);
	range.execCommand("Copy");
	if (message)
	{
		alert(message);
	}
}

function copyToClipboard(el)
{
	el.form.hiddenCopy.select();
	document.execCommand('Copy');
}

// Given the name of the iframe, it resizes it to the given height.
// Note: this assumes that the iframe has the same name and id, since
// getting the frame by name doesn't give you an element with the 'style' attribute,
// and I don't know how to get the id from the frame object.
function resizeIframeHeight(name, height)
{
	var f = document.getElementById(name);
	if (f)
	{
		f.style.height = (height) + "px";
	}
}

// resizes the iframe object in the parent to the height required by the frame's
// contents.  Call this function from within the iframe scope.
function setParentHeight()
{
	if (is_ie)
	{
		var height = getBodyHeight();
	}
	else
	{
		// this seems to work better than getBodyHeight() in Mozilla, which gets bigger but not smaller.
		var height = GetElementHeight(document.body);
	}
	
	parent.resizeIframeHeight(self.name, height);
}


function createHiddenInput(name, value)
{
	var el = document.createElement("input");
	el.type = "hidden";
	el.name = name;
	el.value = value;
	return el;
}

function executeForAllElements(tagName, f)
{
	var children = document.getElementsByTagName(tagName);
	for (var i = 0; i < children.length; i++)
	{
		var child = children[i];
		if (child.nodeType == 1)
		{
			f(child);
		}
	}
}

// activates the given link if the user answers "OK" to the confirmation dialog.
// returns true for OK, false for Cancel.
function confirm_link(question)
{
	if (confirm(question))
	{
		// let the link proceed
		return true;
	}
	else
	{
		return false;
	}
}

// goes to the given url if the user answers "OK" to the confirmation dialog.
// returns true for OK, false for Cancel.
function confirm_link_activation(question, url)
{
	if (confirm(question))
	{
		window.location = url;
		return true;
	}
	else
	{
		return false;
	}
}

function open_popup(url, name) 
{ 
	name = name || '';
	var w = createWindow(name, 'width=500, height=400, scrollbars, resizable', url); 

	// returns false since this javascript is following the link,
	// and we want to make sure the browser doesn't then follow the href
	return false;
}


// opens a new window (with no browser features except scrollbars)
// with the given name and brings it to the foreground.
// This keeps us from using the 'target' attribute on the link,
// because non-JS browsers will open up a new window, and we can't control that new window
// (like close it, or bring it to the foreground if it's already opened).
// So instead we make non-JS browsers open it in the same window, but JS browsers
// open it in a new window.
// 'element' can be either a form or a link, since both have the 'target' property.
// The additional url is optional, and is only needed to use one URL for
// non-JS browsers, and another for JS browsers.
function open_new_window(name, element, different_url) 
{ 
	var w = createWindow( name, 'scrollbars, resizable, menubar=yes'); 
	
	if (w)
	{
		element.target = name;
	
		if (different_url) {
			if (element.href) {
				element.href=different_url;
			}  else if (element.action) {
				element.action=different_url;
			}
		}
	}		

	return true;
}

// opens a new window (with no browser features except scrollbars)
// with the given name and brings it to the foreground.
// This function differs from open_new_window by 1) not taking
// a different URL, since this function is intended for JS sites only,
// and 2) not needing a separate element, since it is the 'this' object.
// 
// Set this function as the onClick or onSubmit event of a link or submit button.
function openNewWindow(name) 
{ 
	var w = createWindow(name, 'scrollbars, resizable, menubar=yes'); 

	this.target = name;

	return true;
}

// Like openNewWindow, but this is appropriate for elements
// that are not links (with an href) nor submit buttons (with an action).
// Thus they take the url in, and explicitly activate it.
function openNewWindowFromButton(url, target) 
{ 
	var w = createWindow(target, 'scrollbars, resizable, menubar=yes', url); 
}

function createWindow(target, parameters, url)
{
	// first assume that parameters is a string
	var features = parameters;
	
	// if it is an object but not a string, then we assume it is an
	// associative array (a map)
	if (isObject(parameters) && !isString(parameters))
	{
		// start with a new object since not all parameters are relevant for window.open()
		var featureMap = new Object();
		
		if (featureMap["chromeless"])
		{
			featureMap["menubar"] = "no";
			featureMap["location"] = "no";
			featureMap["directories"] = "no";
			featureMap["toolbar"] = "no";
			featureMap["status"] = "no";
		}
		
		// now copy in all the parameters, overwriting what we set above
		for (var property in parameters)
		{
			featureMap[property] = parameters[property];
		}

		if (featureMap["maximized"])
		{
			featureMap["width"] = (screen.availWidth ? screen.availWidth : screen.width) - 50;
			featureMap["height"] = (screen.availHeight ? screen.availHeight : screen.height) - 50;
			featureMap["left"] = 0;
			featureMap["top"] = 0;
		}

		if (featureMap["centered"])
		{
			var width = parseInt(featureMap["width"]);
			var scW = screen.availWidth ? screen.availWidth : screen.width;
			var left = Math.max(0, Math.round((scW-width)/2));
			featureMap["left"] = left;
			
			var height = parseInt(featureMap["height"]);
			var scH = screen.availHeight ? screen.availHeight : screen.height;
			var newTop = Math.max(0, Math.round((scH-height)/2));
			featureMap["top"] = newTop;
		}
		
		// remove all parameters that do not apply to window.open()
		delete featureMap["modal"];
		delete featureMap["centered"];
		delete featureMap["maximized"];
		delete featureMap["chromeless"];

		// now make the feature string from the map
		features = serializeState(featureMap, ",");
	}
	
	var w = top.window.open('', target, features); 
	if (!w)
	{
		alert("Could not open window.  Make sure popups are not being blocked.");
		return null;
	}
	else
	{
		w.focus();
		if (url)
		{
			w.location = url;
		}
		
		if (parameters["modal"])
		{
			makeModal(w);
		}
		return w;
	}	
}

// stores the most recently opened dialog box's window.
// For some type of dialog boxes (modal under IE, for instance),
// there is no window object to get, so this will always be null.
var lastDialogBoxWindow = null;

// returns true if successful, false if not.  For some systems,
// this function does not return until the dialog box is closed,
// which is why we cannot return a window handle.
// url: the new url 
// target: the name of the window (required)
// parameters: an object with the following members (optional if noted):
// 		modal: if present, it is a modal dialog box; if not, modeless
// 		width: if present, the window has the given width and is centered horizontally
// 		height: if present, the window has the given height and is centered vertically
function createDialogBox(url, target, parameters)
{
	lastDialogBoxWindow = null;

	// supply default sizes so the initial box is reasonable
	if (!parameters["width"])
	{
		parameters["width"] = 500;
	}
	
	if (!parameters["height"])
	{
		parameters["height"] = 300;
	}
	
	if (typeof(parameters["left_offset"]) != "undefined")
	{
		parameters["left"] = getWindowLeft() + parameters["left_offset"];
	}

	if (typeof(parameters["top_offset"]) != "undefined")
	{
		parameters["top"] = getWindowTop() + parameters["top_offset"];
	}
	
	// automatically center if a width/height is given without a left/top
	if (parameters["width"] && !parameters["left"])
	{
		var width = parseInt(parameters["width"]);
		var scW = screen.availWidth ? screen.availWidth : screen.width;
		var left = Math.max(0, Math.round((scW-width)/2));
		parameters["left"] = left;
	}
	
	if (parameters["height"] && !parameters["top"])
	{
		var height = parseInt(parameters["height"]);
		var scH = screen.availHeight ? screen.availHeight : screen.height;
		var newTop = Math.max(0, Math.round((scH-height)/2));
		parameters["top"] = newTop;
	}

	var featureMap = new Object();
	
	// set some properties that are common to showModalDialog/showModelessDialog/window.open
	featureMap["resizable"] = "yes";
	featureMap["status"] = "no";
		
	if (window.showModalDialog)
	{
		featureMap["help"] = "no";
		featureMap["scroll"] = "yes";
		if (parameters["width"])
		{
			featureMap["dialogWidth"] = parameters["width"] + "px";
			featureMap["dialogLeft"] = parameters["left"] + "px";
			featureMap["center"] = "no";
		}
		if (parameters["height"])
		{
			featureMap["dialogHeight"] = parameters["height"] + "px";
			featureMap["dialogTop"] = parameters["top"] + "px";
			featureMap["center"] = "no";
		}

		// now make the feature string from the map
		var features = serializeState(featureMap, ";");

		if (parameters["modal"])
		{
			window.showModalDialog(url, top, features);
			
			// since it is modal, we don't execute the next line until the 
			// dialog box closes.  Currently we assume it was successful,
			// since I don't know how to tell otherwise.
			return true;
		}
		else
		{
			var w = window.showModelessDialog(url, top, features);
			if (!w)
			{
				// popup blockers could prevent this from working
				alert("Could not open window.  Make sure popups are not being blocked.");
				return false;
			}
			else
			{
				lastDialogBoxWindow = w;
				return true;
			}
		}
	}
	else
	{
		// make it as much like a dialog box as possible.  Some of these are
		// Netscape/Mozilla features, but we are using IE above, so we use them.
		// (Other browsers that do not implement them will have less-appealing
		// dialog boxes.)
		featureMap["alwaysRaised"] = "yes";
		featureMap["dependent"] = "yes";
		featureMap["location"] = "no";
		featureMap["menubar"] = "no";
		featureMap["status"] = "no";
		featureMap["toolbar"] = "no";
		featureMap["directories"] = "no";
		featureMap["scrollbars"] = "yes";
		
		featureMap["outerWidth"] = parameters["width"];
		featureMap["outerHeight"] = parameters["height"];
		featureMap["screenX"] = parameters["left"];
		featureMap["screenY"] = parameters["top"];

		// now make the feature string from the map
		features = serializeState(featureMap, ",");
		
		var w = window.open(url, target, features); 
		lastDialogBoxWindow = w;
		
		if (!w)
		{
			// popup blockers could prevent this from working
			alert("Could not open window.  Make sure popups are not being blocked.");
			return false;
		}
		else
		{
			// in case the window is being reused, we need to bring it back
			// to the top so the user sees it
			w.focus();
			
			if (parameters["modal"])
			{
				makeModal(w);
			}
			return true;
		}	
	}	
}

function closeDialogBox(f, message, changedState)
{
	var close = true;
	if (!f)
	{
		// take the first form
		f = document.forms[0];
	}
	
	if (message)
	{
		close = confirmDialogBoxAction(f, message, changedState);
	}
	
	if (close)
	{
		top.window.close();
	}
}



// returns true if either the user confirms the given message
// and the dialog box's changed state is as given (see checkDialogBoxState).
function confirmDialogBoxAction(f, message, changedState)
{
	if (checkDialogBoxState(f, changedState))
	{
		// ask the user, since the dialog box is in the expected state
		return window.confirm(message);
	}
	else
	{
		return true;
	}
}

// returns true if the dialog box is in the expected state.
// if changedState is true or false, the dialog must be changed or not changed.
// if changedState is neither, then either state is considered an expected state.
function checkDialogBoxState(f, changedState)
{
	if (changedState === true)
	{
		return formChanged(f);
	}
	else if (changedState === false)
	{
		return !formChanged(f);
	}
	else
	{
		return true;
	}
}

// returns true if any element in the form has changed from its initial state.
// If an element has the class "js-never-changed", it is automatically treated as unchanged.
// Use it to exclude controls which are not part of the "true state" of the page.
function formChanged(f)
{
	for (var i = 0, len = f.elements.length; i < len; i++)
	{
		var el = f.elements[i];
		// skip elements that are considered never changed
		if (!hasStyle(el, "js-never-changed"))
		{
			if (el.type == "select-one" || el.type == "select-multiple")
			{
				if (selectChanged(el))
				{
					return true;
				}
			}
			else if (el.type == "checkbox" || el.type == "radio")
			{
				if (el.checked != el.defaultChecked)
				{
					return true;
				}
			}
			else if (el.type == "text" || el.type == "textarea" || el.type == "password")
			{
				if (el.value != el.defaultValue)
				{
					return true;
				}
			}
			else if (el.type == "hidden")
			{
				// IE & Mozilla sets defaultValues, but not all browsers do, 
				// so we make sure defaultValue is a string
				if (typeof(el.defaultValue) == "string" && el.value != el.defaultValue)
				{
					return true;
				}
			}
		}
	}
	
	return false;
}

// returns true if the selection state of any option in the select has changed
function selectChanged(s)
{
	for (var i = 0, len = s.options.length; i < len; i++)
	{
		var o = s.options[i];
		if (o.selected != o.defaultSelected)
		{
			return true;
		}
	}
	
	return false;
}

function changeLabel(button, newLabel)
{
	button.value = newLabel;
}

function disableButtons(form)
{
	for (var i = 0, len = form.elements.length; i < len; i++)
	{
		var el = form.elements[i];
		if (el.type == "button" || el.type == "submit" || el.type == "reset")
		{
			el.disabled = true;
		}
	}	
}

function updateOpener(feedbackRequested)
{
	if (top.opener)
	{
		// assume we are writing to the top frame of the opener
		var dest = top.opener;
		if (top.opener.document.body.className == "dialog-box-wrapper")
		{
			// but if the opener is a dialog box, we write into the iframe inside it
			dest = top.opener.document["dialog_wrapper"];
		}
	
		var l = dest.location;
		// have to reconstruct the whole path for IE, for some reason
		var url = l.protocol + "//" + l.host + l.pathname;

		// assume there are no parameters, but if there are, make a map of them
		var query = {};
		if (l.search)
		{
			query = deserializeParameters(l.search.substr(1), "&");			
		}			
			
		if (feedbackRequested)
		{
			// set the feedback parameter appropriately
			query["_fb"] = "1";
		}
		
		// IE 5.0 won't reload the page unless we give it a different URL,
		// so we increment some parameter each time
		if (is_ie && ie_version < 5.5)
		{
			var reloadIE = query["_rie"] ? parseInt(query["_rie"]) : 1;
			query["_rie"] = reloadIE + 1;
		}
		
		// reconstruct the URL
		url = url + "?" + serializeParameters(query, "&");
		
		// now refresh it
		if (is_ie && ie_version < 5.5)
		{
			// IE5 also doesn't seem to handle replace()
			dest.location.href = url;
		}
		else
		{
			// we use replace here so that the Back button goes
			// back to what the user expects--the previous page
			dest.location.replace(url);
		}		
	}
}


function open_link(url, targetName)
{
	if (targetName)
	{
		if (targetName == 'top')
		{
			// Opera doesn't recognize window.frames['top'], so we special-case it
			top.location = url;
		}
		else if (window.frames[targetName])
		{
			// hopefully, opera will recognize normal 'named frames' via this code
			window.frames[targetName].location = url;
		}
	}
	else
	{
		window.location = url;
	}
}	
	
/* Like open_link, but requires that the url be encoded then have the percent
characters replaced with |.  This is done because some browsers decode the href parameter (IE, Netscape) 
and some don't, and IE5 has a bug in which it over-decodes the href parameter.  Thus we use a meaningless
symbol.
*/ 
function open_encoded_href(url, targetName)
{
	open_link(url.replace(/\|/g, "%"), targetName);
}

function openPopup(url, name, config) 
{ 
	name = name || '';
	var w = window.open(url, name, config); 

	w.focus();

	// returns false since this javascript is following the link,
	// and we want to make sure the browser doesn't then follow the href
	return false;
}


var currentModalWindow = null;

// makes the given window modal
function makeModal(w)
{
	// set it in the top frame 
	top.currentModalWindow = w;
}

// makes the current window modal
function makeCurrentWindowModal()
{
	if (opener)
	{
		opener.makeModal(window);
	}
}


// checks to see if there is a modal window open, and if
// so, restores focus to it.  Returns true if there is an active modal window.
function checkModalWindow()
{
	var active = false;
	var modalWindow = top.currentModalWindow;
	if (modalWindow)
	{
		if (!modalWindow.closed && modalWindow.location)
		{
			if (self != modalWindow)
			{
				// there is a modal window open, but this is not it, 
				// so return the focus to it
				try
				{
					modalWindow.focus();
					active = true;
				}
				catch (e)
				{
					// IE5 doesn't always set window.closed properly, so 
					// we can't detect that the window is closed.  Instead, we catch the error and clear it manually.  
					// (We can't set window.onunload on the popup to clear it, since
					// that fires when the browser loads another page, not when the
					// window is closed.)
					top.currentModalWindow = null;
				}
			}
		}
		else
		{
			// this window has been closed, so the reference is no longer valid
			top.currentModalWindow = null;
		}
	}		
	
	return active;
}
	
addEvent(window, "onfocus", checkModalWindow);

function findCookie(name)
{	
	// cookies are terminated by semicolons (some browsers add spaces)
	var cookies = document.cookie.split(/\s*;\s*/);
	for (var i = 0; i < cookies.length; i++)
	{
		// each cookie is name=value
		var result = cookies[i].split("=");
		if (result[0] == name)
		{
			return unescape(result[1]);
		}
	}
}

/* Sets a cookie that expires when the browser is closed. */
function setSessionCookie(name, value)
{
	document.cookie = name + "=" + escape(value);
}

function removeCookie(name)
{
	// set a day two days in the past
	var expired = new Date(new Date().getTime() - 2 * 86400 * 1000);	
	document.cookie = name + "=null; expires=" + expired.toGMTString();
}

function setPermanentCookie(name, value, days)
{
	// default to expire in 30 days
	if (!days)
	{
		days = 30;
	}
	
	var expired = new Date(new Date().getTime() + days * 86400 * 1000);	
	document.cookie = name + "=" + escape(value) + ";expires=" + expired.toGMTString();
}

function setPermanentCookieForPath(name, value, path)
{
	var days = 30;
	
	var expired = new Date(new Date().getTime() + days * 86400 * 1000);	
	document.cookie = name + "=" + escape(value) + ";path=" + path + ";expires=" + expired.toGMTString();
}

function cloneObject(obj) 
{
	var objectClone = new Object();
	for (var property in obj)
	{
		if (isArray(obj[property]))
		{
			objectClone[property] = cloneArray(obj[property]);
		}
		else if (typeof(obj[property]) == "object")
		{	
			objectClone[property] = cloneObject(obj[property]);
		}
		else
		{
			objectClone[property] = obj[property];
		}
	}
	return objectClone;
}

function cloneArray(array) 
{
	var arrayClone = new Array();
	for (var n = 0; n < array.length; n++)
	{
		arrayClone[n] = array[n];
	}
	return arrayClone;
}


function getSourceElement(e)
{
	if (!e) var e = window.event;
	if (typeof e.target != 'undefined')
	{
		return e.target; 
	}
	else if (typeof e.srcElement != 'undefined') 
	{
		return e.srcElement; 
	}
}


function onInputFocus(e) 
{
	var source = getSourceElement(e);

	if (source && !source.readOnly)
	{
		addStyle(source, 'text-focused');
	}
} 

function onInputBlur(e) 
{
	var source = getSourceElement(e);
	if (source && !source.readOnly)
	{
		removeStyle(source, 'text-focused');
	}
} 

function getFirstAvailableFormControl(f)
{
	var controls = f.elements;
	for (var n = 0; n < controls.length; n++)
	{
		if (controls[n].type != "hidden" && !controls[n].disabled && !controls[n].readOnly)
		{
			return controls[n];
		}
	}
}

function getSubmitButtons(f)
{
	var controls = f.elements;
	var buttons = new Array();
	for (var n = 0; n < controls.length; n++)
	{
		if (controls[n].type == "submit")
		{
			buttons.push(controls[n]);
		}
	}
	
	return buttons;
}

var initialFocusSet = false;

function setInitialFocus(el, select)
{
	if (!initialFocusSet)
	{
		function focus()
		{
			try
			{
				var firstElement = getFirstAvailableFormControl(el.form);
				if (firstElement == el && is_ie)
				{
					// IE auto-focuses the first available element and thus already
					// ran whatever onFocus handler was in place when the element
					// was created.  Thus to get it to run we have to blur it first
					// (running onfocus() doesn't seem to do it). Unfortunately there is no
					// way to automatically know if an element is focused, so
					// we assume the first element is (this is not strictly true).
					el.blur();
				}

				el.focus();
			}
			catch (e)
			{
				// IE5 sometimes gives spurious errors about being unable to focus the text box.
				// It's rare and hard to reproduce, so at least we keep it from stopping the rest of execution.
			}
			
			if (select)
			{
				el.select();
			}
			
			el = null;
			initialFocusSet = true;
		}		
			
		window.setTimeout(focus, 1);
	}	
}


var linkedOptions = new Object();
addActiveElement(linkedOptions);

var linked_to_re = /linked-to-(\S+)/;


function scanDocument()
{
	// use the root
	scanElement(document);
}

/*
Goes through the given element and children and looks for elements with certain classes:

select.autosubmit: sets the onChange event to submit the form.

select.grouped: for IE <6, converts OPTGROUP elements to OPTIONs that are disabled,
and adds an onChange event to make sure that the pseudo-OPTGROUP line is not selected.
For all other browsers, does nothing.

input.linked-option: looks for inputs and label elements that have "linked-to-XXXX",
where XXXX is the id of the given master input.  Those other inputs and labels are then "linked" to 
the state of the master input: if the master is unchecked, the linked inputs are disabled, and
both the inputs and labels are given the class of "disabled"; if checked, this the linked inputs are enabled
and the "disabled" class removed.   If a linked input has the 
class "linked-clear", then its value is set to the empty string when it is disabled and restored
when enabled. If the master input has the class "linked-invert", then the above behavior is switched: linked 
inputs and labels are disabled when the master input is CHECKED.

a.one-click: modifies the link so it can only be clicked once.  This keeps
users from clicking a link twice and sending two requests to the server.
Links with 'one-click' get an onClick event handler that sets a new onClick handler when
the link is called.  This new handler always returns false, disabling the link.
If there is already an onClick handler, it is called first and the link is disabled
only if it returns true (indicating the link will be followed).  This is intended primarily
for confirmation dialogs or other "cancelable" clicks--you don't want to disable the link
until it actually will be followed.

input.js-immediate-onchange: indicates that the onChange event handler for this element
should be fired as soon as the user makes a change, not just after the input loses focus.
Note that the browser will still fire onChange when the input loses focus if it has been changed.

ul.checklist: makes a checklist out of the items in the list.  Assumes that the name of
the checked items is the same as the id on the ul element (in case there is nothing in the
list and therefore no inputs to get the name from).
*/
function scanElement(root)
{
	if (root.getElementsByTagName)
	{
		var inputs = root.getElementsByTagName("input");
		for (var i = 0; i < inputs.length; i++)
		{
			var input = inputs[i];
			if (input.className.indexOf("linked-option") != -1)
			{
				linkedOptions[input.id] = new LinkedOption(input);
			}
			else 
			{
				addControlIfLinked(input, linkedOptions);
			}

			if (input.type == "text" || input.type == "password")
			{
				initializeInputFocus(input);
		  	}	
		  	
		  	if (hasStyle(input, "js-immediate-onchange"))
		  	{
			  	addImmediateOnChange(input);
		  	}	
		  	
		  	// do this last, so the onfocus stuff above runs
		  	if (hasStyle(input, "initial-focus"))
		  	{
		  		setInitialFocus(input, true);
		  	}		
		}		

		var selects = document.getElementsByTagName("select");
		for (var i = 0; i < selects.length; i++)
		{
			var select = selects[i];
			if (hasStyle(select, "grouped"))
			{
				if (is_ie && ie_version < 6)
				{
					// first make OPTION elements for each OPTGROUP tag
					emulateOptgroup(select);
					
					// mark this so we know we have to handle it specially
					select.HAS_OPTGROUP = true;
					
					// unless changed below, we use this as the event so we 
					// can disallow bad selections
					select.onchange = eventCheckSelect;
				}
			}
			
			if (hasStyle(select, "autosubmit"))
			{
				// note that this event will automatically fix bad selections if needed,
				// so it does not interfere with the work we did above
				select.onchange = eventSubmitSelect;
			}
			
			if (hasStyle(select, "separate-selection"))
			{
				new SeparateSelection(select);
			}
			
			addControlIfLinked(select, linkedOptions);
		}

		var labels = document.getElementsByTagName("label");
		for (var i = 0; i < labels.length; i++)
		{
			var label = labels[i];
			addLabelIfLinked(label, linkedOptions);
		}		
		
		var fieldsets = document.getElementsByTagName("fieldset");
		for (var i = 0; i < fieldsets.length; i++)
		{
			var fieldset = fieldsets[i];
			var linked_to = linked_to_re.exec(fieldset.className);

			if (linked_to)
			{
				var linkName = linked_to[1];
				if (linkedOptions[linkName])
				{
					linkedOptions[linkName].addLabel(fieldset);
				}
				
				linkedOptions[linkName].addAllControls(fieldset.getElementsByTagName("input"));
				linkedOptions[linkName].addAllLabels(fieldset.getElementsByTagName("label"));
			}				
		}		

		var links = document.getElementsByTagName("a");
		for (var i = 0; i < links.length; i++)
		{
			var l = links[i];
			if (l.className.indexOf("one-click") != -1)
			{
				if (l.onclick)
				{
					l.CHAINED_ONCLICK = l.onclick;
				}
				l.onclick = oneClickonClick;
			}
			else if (l.className.indexOf("toggle-button") != -1)
			{
				initializeToggleButton(l);
			}
			else if (l.className.indexOf("icon-button") != -1)
			{
				initializeToggleButton(l);
			}
		}	
		
		var restrict_length_re = /js-restrict-length-(\S+)/;
		var textareas = document.getElementsByTagName("textarea");
		for (var i = 0; i < textareas.length; i++)
		{
			var textarea = textareas[i];
			addEvent(textarea, 'onfocus', onInputFocus); 
	  		addEvent(textarea, 'onblur', onInputBlur); 
	  				  	
			if (textarea.className.indexOf("linked-option") != -1)
			{
				linkedOptions[textarea.id] = new LinkedOption(textarea);
			}
			else 
			{
				addControlIfLinked(textarea, linkedOptions);
			}
			
		  	if (hasStyle(textarea, "js-immediate-onchange"))
		  	{
			  	addImmediateOnChange(textarea);
		  	}	
		  	
	  		var restricted = restrict_length_re.exec(textarea.className);
			if (restricted)
			{
				new RestrictedLength(textarea, restricted[1]);
			}
			
			// run this last
		  	if (!textarea.readOnly && hasStyle(textarea, "initial-focus"))
		  	{
				setInitialFocus(textarea, false);
		  	}		
	  	}	
	  	
	  	var body = document.body;
	  	
	  	if (hasStyle(body, "js-resize-parent-height"))
	  	{
	  		// for some reason, IE5 doesn't seem to like it when we
	  		// call setParentHeight immediately
	  		window.setTimeout(setParentHeight, 1);
	  	}
	  	
	  	var lists = document.getElementsByTagName("ul");
		for (var i = 0; i < lists.length; i++)
		{
			var list = lists[i];
			if (hasStyle(list, "checklist"))
			{
				new Checklist(list);
			}
		}				
	}	

	// now set the initial state properly
	for (var o in linkedOptions)
	{
		linkedOptions[o].updateState();
	}
}

function initializeInputFocus(input)
{
	addStyle(input, "text");
	addEvent(input, 'onfocus', onInputFocus); 
	addEvent(input, 'onblur', onInputBlur); 
}

function addImmediateOnChange(element)
{
	if (!element.IMMEDIATE_ONCHANGE)
	{
		addEvent(element, "onfocus", startImmediateOnChange);
		addEvent(element, "onblur", stopImmediateOnChange);
		element.IMMEDIATE_ONCHANGE = true;
	}
}

	

/*
Maintains a "status" display, which has two aspects: a set of blocks which
can flagged with the "active" class, and a set of variable elements that
can have assigned values.
*/
function StatusDisplay(matchName)
{
	this.activeFlag = new ClassModifier("active");
	
	this.blocks = new Array();
	this.vars = new Array();

	var re = new RegExp(matchName);
	
	var fields = document.getElementsByTagName("fieldset");
	for (var n = 0; n < fields.length; n++)
	{
		if (re.test(fields[n].className))
		{
			var blocks = fields[n].getElementsByTagName("p");
			for (var i = 0; i < blocks.length; i++)
			{
				this.blocks.push(blocks[i]);
			}

			var vars = fields[n].getElementsByTagName("var");
			for (var i = 0; i < vars.length; i++)
			{
				var v = vars[i];
				if (v.firstChild == null)
				{
					v.appendChild(document.createTextNode(""));
				}
				this.vars.push(v);
			}
		}
	}	
	
	addActiveElement(this);
}

StatusDisplay.prototype.updateActive = function(flag)
{
	for (var n = 0; n < this.blocks.length; n++)
	{
		if (hasStyle(this.blocks[n], flag))
		{
			this.activeFlag.add(this.blocks[n]);
		}
		else
		{
			this.activeFlag.remove(this.blocks[n]);
		}
	}			
}		

StatusDisplay.prototype.setVariable = function(name, value)
{
	for (var n = 0; n < this.vars.length; n++)
	{
		if (hasStyle(this.vars[n], name))
		{
			// if we change this via innerHTML, IE5 adds extra space
			this.vars[n].firstChild.nodeValue = value;
		}
	}	
}		

StatusDisplay.prototype.release = function()
{
	clearArray(this.blocks);
	clearArray(this.vars);
}


/*
Any "p" element labelled under a fieldset marked with "status-for-TEXTAREA_ID"
with one of these classes will get the "active"
class under the following condition:

restricted-length-under: there is room for more than one more character
restricted-length-one-under: there is room for just one more character
restricted-length-at: there is no more room
restricted-length-one-over: it is just one character over the limit
restricted-length-over: it is more than one character over the limit

In addition, the following variables are supported:
remaining: the number of characters remaining or over the limit
maximum: the maximum number of characters allowed

If the textarea is labelled "js-required-valid" then all submit buttons
will be disabled if the length requirement is not met.
*/
function RestrictedLength(textarea, maxLength)
{
	this.maxLength = maxLength;
	this.id = textarea.id;

	this.display = new StatusDisplay("status-for-" + textarea.id);

	this.updateRestrictedStatus();
	var me = this;
	addEvent(textarea, "onchange", function () { me.updateRestrictedStatus(); });
	
  	addImmediateOnChange(textarea);

	if (hasStyle(textarea, "js-required-valid"))
	{
		this.submitButtons = getSubmitButtons(textarea.form);
		// preserve the original state of these buttons
		for (var n = 0; n < this.submitButtons.length; n++)
		{
			this.submitButtons[n].ORIGINAL_STATE = this.submitButtons[n].disabled;
		}		
	}
	
	// clear the closure on any HTML elements
	textarea = null;
	
	addActiveElement(this);
}

RestrictedLength.prototype.release = function()
{
	if (this.submitButtons)
	{
		clearArray(this.submitButtons);
	}
}

RestrictedLength.prototype.updateRestrictedStatus = function()
{
	var el = document.getElementById(this.id);
	if (el)
	{
		var currentLength = el.value.length;
		var active;
		var error = false;
		var difference = currentLength - this.maxLength;
		if (difference == 0)
		{
			var active = "restricted-length-at";
		}
		else if (difference == 1)
		{
			active = "restricted-length-one-over";
			error = true;
		}
		else if (difference == -1)
		{
			active = "restricted-length-one-under";
		}
		else if (currentLength < this.maxLength)
		{
			active = "restricted-length-under";
		}
		else 
		{
			active = "restricted-length-over";
			error = true;
		}
		
		this.display.updateActive(active);
		this.display.setVariable("remaining", Math.abs(difference));
		this.display.setVariable("maximum", this.maxLength);
		
		if (this.submitButtons)
		{
			for (var n = 0; n < this.submitButtons.length; n++)
			{
				this.submitButtons[n].disabled = error ? true : this.submitButtons[n].ORIGINAL_STATE;
			}		
		}	
	}
}


function addControlIfLinked(element, linkedOptions)
{
	var linked_to = linked_to_re.exec(element.className);
	if (linked_to)
	{
		if (linkedOptions[linked_to[1]])
		{
			linkedOptions[linked_to[1]].addControl(element);
		}
	}
}

function addLabelIfLinked(element, linkedOptions)
{
	var linked_to = linked_to_re.exec(element.className);
	if (linked_to)
	{
		if (linkedOptions[linked_to[1]])
		{
			linkedOptions[linked_to[1]].addLabel(element);
		}
	}
}

function oneClickonClick()
{
	var followLink = true;
	if (this.CHAINED_ONCLICK)
	{
		followLink = this.CHAINED_ONCLICK();
	}
	
	if (followLink)
	{
		// now disable this link
		this.onclick = returnFalse;
	}
	
	return followLink;
}

// now run this function 
addLoadFunction(scanDocument);

var OPTION_INDENT = "   ";

/* Parses the select list and converts OPTGROUP to OPTION elements. 
Luckily, although IE5 and 5.5 don't support OPTGROUP, they leave it in the document 
so we can find it.  Unluckily, we can't get the true structure back.  We get the OPTGROUP
element at the start of each group, but no ending indicator, so lists like this:
GROUP1
	OPTION1
	OPTION2
OPTION3 (not in a group)
GROUP2
	OPTION1
	
will be interpreted as having two groups, with three and one element in each.
As a special case, initial options that are not grouped will not be, because
we don't assume we are in a group until we find the first OPTGROUP element.

Other browsers (including IE6, Mozilla, and Opera) get this right, so we do
not have to do any of this for them.
*/
function emulateOptgroup(select)
{
	var newOptions = new Array();
	var current = 0;
	
	children = select.childNodes;
	for (var n = 0; n < children.length; n++)
	{
		var child = children[n];
		if (child.nodeType == 1)
		{
			if (child.nodeName == "OPTGROUP")
			{
				// make an option from the label attribute
				var option = new Option(child.getAttribute('label').toUpperCase());
				// set it to disabled so we can disable clicks on it (manually, since IE ignores this)
				option.disabled = true;
				// add the class so we can style it to set it apart 
				// (although IE only supports color and background-color)
				option.className = (option.className + " optgroup");
				newOptions[newOptions.length] = option;					
			}
			else if (child.nodeName == "OPTION")
			{
				if (child.selected)
				{
					// this is the selected item, so remember its new position
					current = newOptions.length;
				}
				
				// add some spaces to indent
				child.text = OPTION_INDENT + child.text;
				newOptions[newOptions.length] = child;
				
				// IE doesn't set disabled, so we set it here
				child.disabled = false;
			}
		}
	}
	
	// now add all the options we made above to the select
	select.options.length = 0;
	for (var n = 0; n < newOptions.length; n++)
	{
		select.options[select.options.length] = newOptions[n];
	}
	// set the current selection for the index into the new longer list
	select.selectedIndex = current;
	// and remember this first selection for checkSelect
	select.OLD_SELECTION = current;
}


function eventSubmitSelect()
{
	if (this.HAS_OPTGROUP && checkSelect(this) == false)
	{
		// skip the submission if the user picked an invalid choice
		return;
	}
	
	// since this is being called directly, we emulate the onSubmit event manually
	var cont = true;
	if (this.form.onsubmit)
	{
		// call the onSubmit event and get the result
		cont = this.form.onsubmit();
	}
	
	if (cont)
	{
		this.form.submit();	
	}
}

function eventCheckSelect()
{
	// convert 'this' into a simple argument
	checkSelect(this);
}


function checkSelect(select)
{
	if (select.multiple)
	{
		return checkMultipleSelect(select);
	}
	else
	{
		return checkSingleSelect(select);
	}
}

function checkSingleSelect(select)
{
	var option = select.options[select.selectedIndex];
	if (option.disabled)
	{
		// can't select this, so return to the last good section
		select.selectedIndex = select.OLD_SELECTION;
		return false;
	}
	else if (option.selected)
	{
		// this is a good selection, so we remember it
		select.OLD_SELECTION = select.selectedIndex;
		return true;
	}
}

function checkMultipleSelect(select)
{
	var hasSelection = false;
	
	for (var n = 0; n < select.options.length; n++)
	{
		var option = select.options[n];
		if (option.disabled)
		{
			// can't select this
			option.selected = false;
		}
		else if (option.selected)
		{
			// this is a good selection, so we remember it
			hasSelection = true;
		}
	}		
	
	return hasSelection;
}

function LinkedOption(source)
{
	this.source = source;
	
	this.invert = source.className.indexOf("linked-invert") != -1;
	this.setDisabled();
	
	this.controls = new Array();
	// keeps the old control values so we can hide them.
	// if an entry is null, that means that control does not have its input hidden.
	this.controlValues = new Array();
	this.labels = new Array();
	source.LINKED_OPTION = this;
	
	if (source.type == "radio" && source.form)
	{
		// set the onclick for each radio button in the same group
		var elements = source.form.elements;
		for (var i = 0; i < elements.length; i++)
		{
			if (elements[i].name == source.name)
			{
				// this has the same name, so it's in the same group
				addEvent(elements[i], "onclick", linkedOptionClick);
			}
		}
	}
	else
	{
		// run our code when the user toggles the input
		addEvent(source, "onclick", linkedOptionClick);
	}
}

LinkedOption.prototype.release = function()
{
	debugLog("released");
	this.source = null;
	clearObject(this.controls);
}


LinkedOption.prototype.addControl = function(control)
{
	this.controls[this.controls.length] = control;
	if (control.className.indexOf("linked-clear") != -1)
	{
		// we will clear this input, so remember its value
		this.controlValues[this.controlValues.length] = control.value;
	}
	else
	{
		// this control will not get cleared
		this.controlValues[this.controlValues.length] = null;
	}
	
	if (hasStyle(control, "linked-select-on-edit"))
	{	
	  	addImmediateOnChange(control);
	  	addEvent(control, "onchange", activateLinkedOption);
	}

	control.LINKED_OPTION = this;
}

function activateLinkedOption()
{
	this.LINKED_OPTION.source.click();
	this.LINKED_OPTION.updateState();
}

LinkedOption.prototype.addAllControls = function(controls)
{
	for (var i = 0; i < controls.length; i++)
	{
		this.addControl(controls[i]);
	}
}
			
LinkedOption.prototype.addLabel = function(label)
{
	this.labels[this.labels.length] = label;
}

LinkedOption.prototype.addAllLabels = function(labels)
{
	for (var i = 0; i < labels.length; i++)
	{
		this.addLabel(labels[i]);
	}
}

LinkedOption.prototype.updateState = function()
{
	this.setDisabled();
	for (var i = 0; i < this.controls.length; i++)
	{
		var control = this.controls[i];
		var disabled = this.disabled;
		if (hasStyle(control, "linked-invert"))
		{
			disabled = !disabled;
		}
		
		if (!hasStyle(control, "linked-always-enabled"))
		{	
			control.disabled = disabled;
		}
	
		this.setClass(control);
		if (this.controlValues[i] != null)
		{
			if (disabled)
			{
				// hide the value if this option is not checked
				this.controlValues[i] = control.value;
				control.value = "";
			}
			else
			{
				// restore the value
				control.value = this.controlValues[i];
			}
		}
	}
	
	for (var i = 0; i < this.labels.length; i++)
	{
		this.setClass(this.labels[i]);
	}	
}

// determines whether the elements are enabled or disabled 
LinkedOption.prototype.setDisabled = function()
{
	// normally disabled if the option is not checked...
	this.disabled = (this.source.checked == false);
	if (this.invert)
	{
		// though you can switch this behavior
		this.disabled = !this.disabled;
	}
}	


var disabledRE = new ClassModifier("disabled");
LinkedOption.prototype.setClass = function(element)
{
	// first remove disabled, if it is there...
	disabledRE.removeRedundantly(element);
	
	var disabled = this.disabled;
	if (hasStyle(element, "linked-invert"))
	{
		disabled = !disabled;
	}
	
	// then add it only if needed
	if (disabled)
	{
		disabledRE.addRedundantly(element);
	}
}


function linkedOptionClick()
{
	if (this.type == "radio" && this.form)
	{
		// update each radio button in the same group
		// set the onclick for each radio button in the same group
		var elements = this.form.elements;
		for (var i = 0; i < elements.length; i++)
		{
			if (elements[i].name == this.name && elements[i].LINKED_OPTION)
			{
				// this has the same name, so it's in the same group
				elements[i].LINKED_OPTION.updateState();
			}
		}
	}
	else if (this.LINKED_OPTION)
	{
		// just update this checkbox
		this.LINKED_OPTION.updateState();
	}
}

var immediateOnChangeIntervalId = null;
var onChangeElement = null;

function startImmediateOnChange()
{
	stopImmediateOnChange();
	onChangeElement = this;
	// make sure the previous value is set appropriately.
	// We use this instead of defaultValue, because that would interfere
	// with our ability to detect if the user changed the form.
	onChangeElement.previousValue = onChangeElement.value;
	
	// once every 1/100 of a second. Since the code to check if there 
	// was a change is so fast, we can use such a high frequency without causing a problem.
	immediateOnChangeIntervalId = window.setInterval(onChangeUpdate, 10);
}

function onChangeUpdate()
{
	if (onChangeElement && onChangeElement.value != onChangeElement.previousValue)
	{
		onChangeElement.onchange();
	}
	
	// we check this again, since if the onChange function pops up an alert,
	// IE runs stopImmediateOnChange (for onBlur) before it returns to this function,
	// and onChangeElement then becomes null. This prevents the resultant error message.
	if (onChangeElement)
	{
		onChangeElement.previousValue = onChangeElement.value;
	}
}

function stopImmediateOnChange()
{
	if (immediateOnChangeIntervalId)
	{
		window.clearInterval(immediateOnChangeIntervalId);
		immediateOnChangeIntervalId = null;
	}
	// we make sure we clear this so we don't cause leaks in IE
	onChangeElement = null;
}

var times = [];
var timeMessages = [];
var timeNames = [];


var timeIndent = ">";

function startTimer(name, message)
{
	times[times.length] = new Date().getTime();
	timeNames[timeNames.length] = name;
	timeMessages[timeMessages.length] = message ? message : name;
	debugLog(timeIndent.replicate(times.length) + timeMessages[timeMessages.length-1]);
}

function stopTimer(name, showAlert)
{
	var indent = timeIndent.replicate(times.length);
	var thisMessage = timeMessages.pop();
	var thisName = timeNames.pop();
	var time = times.pop();
	if (thisName !== name)
	{
		alert("*** expected " + name + ", got " + thisName);
	}
	else
	{
		if (showAlert)
		{
			alert(thisMessage + ": " + (new Date().getTime() - time));
		}
		else
		{
			debugLog(indent + thisMessage + ": " + (new Date().getTime() - time));
		}
	}
}

function debugPrint(message)
{
	if (debuggingEnabled)
	{
		var el = top.document.getElementById("debugwindow");
		if (el)
		{
			el.value = message;
		}
	}
}

var debuggingEnabled = true;
var queuedDebugMessages = "";
function debugLog(message)
{
	if (debuggingEnabled)
	{
		var el = document.getElementById("debugwindow");
		if (el)
		{
			if (queuedDebugMessages.length > 0)
			{
				el.value += queuedDebugMessages;
				queuedDebugMessages = "";			
			}			
			el.value += message + "\n";
		}
		else
		{
			queuedDebugMessages += message + "\n";
		}
	}
}

function clearDebugLog()
{
	var el = top.document.getElementById("debugwindow");
	if (el)
	{
		el.value = "";
	}
}

function Poller(f, interval)
{
	this.f = f;
	this.interval = interval;
}

Poller.prototype.schedule = function()
{
	this.id = window.setInterval(this.f, this.interval);
}

Poller.prototype.cancel = function()
{
	if (this.id)
	{
		window.clearInterval(this.id);
		this.id = null;
	}
}

/* An object to help manage "popup dialogs", which are movable iframes
that simulate dialog boxes. */
function PopupDialog(id, baseURL)
{
	this.id = id;
	this.frameElement = document.getElementById(id);
	this.scope = frames[this.frameElement.name];
	this.baseURL = baseURL;
	this.visible = false;
	
	this.pollHandlers = new Array();
	this.addLoadEvent();

	addActiveElement(this);
}

PopupDialog.prototype.release = function()
{
	this.scope = null;
	this.document = null;
	this.frameElement = null;
	// we clear these in case the functions inadvertently make a closure
	this.callback = null;
	clearArray(this.pollHandlers);
}

PopupDialog.prototype.setCallback = function(callback)
{
	this.callback = callback;
}

PopupDialog.prototype.addPoll = function(handler, interval)
{
	var me = this;
	function poll()
	{
		if (me.scope.frameLoaded)
		{
			poller.cancel();
		}
		else
		{
			handler.call(window);
		}
	}
	
	var poller = new Poller(poll, interval);
	this.pollHandlers.push(poller);
}

PopupDialog.prototype.addLoadEvent = function()
{
	var me = this;
	function callback()
	{
		if (me.scope.frameLoaded)
		{
			// first cancel this since it is a one-time event
			poller.cancel();

			// now initialize the new document
			me.initialize();

			// call the callback function
			if (me.callback) me.callback.call(window);
		}
	}
	
	var poller = new Poller(callback, 10);
	this.pollHandlers.push(poller);
}

PopupDialog.prototype.initialize = function()
{
	// grab the new document 
	this.document = this.scope.document;
	
	var header = this.document.getElementById("popup-dialog-title");
	
	var me = this;
	
	var mouseDownIframeX, mouseDownIframeY;
	var mouseDownPageX, mouseDownPageY;
	
	// set this to "forward" the mouseup event to the main document.
	// this is needed for scripts in the main document
	// whose dragging is ended by a mouseup event.
	me.document.onmouseup = onMouseUp;
	
	function scrollMoveFromFrame(e)
	{		
		var deltaX = me.scope.getMouseX(e) - mouseDownIframeX;
		var deltaY = me.scope.getMouseY(e) - mouseDownIframeY;
		scrollMove(deltaX, deltaY);
	};
	
	function scrollMoveFromTop(e)
	{
		var deltaX = parent.getMouseX(e) - mouseDownPageX;
		var deltaY = parent.getMouseY(e) - mouseDownPageY;
		scrollMove(deltaX, deltaY);
	};
	
	function scrollMove(deltaX, deltaY)
	{
		me.frameElement.style.left = (deltaX + GetAbsoluteLeft(me.frameElement)) + "px";
		me.frameElement.style.top  = (deltaY + GetAbsoluteTop(me.frameElement)) + "px";

		// since we moved the frame which is the reference point,
		// we have effectively moved the "down" point relative to the page
		mouseDownPageX += deltaX;
		mouseDownPageY += deltaY;
	};

	function mouseGrab(e)
	{
		me.document.onmousemove = scrollMoveFromFrame;
		document.onmousemove = scrollMoveFromTop;

		// note: this clobbers the existing handlers, but the addEvent mechanism currently offers no way of restoring them
		addEvent(me.document, "onmouseup", mouseRelease);	
		document.onmouseup = mouseRelease;	

		mouseDownIframeX = me.scope.getMouseX(e);
		mouseDownIframeY = me.scope.getMouseY(e);

		mouseDownPageX = GetAbsoluteLeft(me.frameElement) + mouseDownIframeX;
		mouseDownPageY = GetAbsoluteTop(me.frameElement) + mouseDownIframeY;

		// disable selecting for IE while dragging
		if (is_ie)
		{
			me.document.onselectstart = returnFalse;
			document.onselectstart = returnFalse;
		}
	};
	
	// sends a mouseup event to the main document to 
	// release anything that was active while the mouse was down
	function onMouseUp(e)
	{
		if (document.onmouseup)
		{
			document.onmouseup(e);
		}
	}
	
	function mouseRelease()
	{
		// first remove/restore the mouseup and mousemove handlers we added
		me.document.onmouseup = onMouseUp;
		document.onmouseup = null;
		
		document.onmousemove = null;
		me.document.onmousemove = null;
	
		if (is_ie)
		{
			me.document.onselectstart = null;
			document.onselectstart = null;
		}
	}
	
	header.onmousedown = mouseGrab;
	// clear this so the closure doesn't hold on to this reference
	header = null;
}

PopupDialog.prototype.load = function()
{
	if (this.scope.location.href == "about:blank")
	{
		// start loading the page
		this.scope.location.href = this.baseURL;
		
		// then schedule all the polling functions
		for (var n = 0; n < this.pollHandlers.length; n++)
		{
			this.pollHandlers[n].schedule();
		}
	}	
	else
	{
		this.document = this.scope.document;
		if (this.callback)
		{
			this.callback.call(window);
		}
	}	
}

PopupDialog.prototype.show = function()
{
	var el = document.getElementById(this.id);
	el.style.display = "block";
	this.visible = true;
}

PopupDialog.prototype.hide = function()
{
	var el = document.getElementById(this.id);
	el.style.display = "none";
	this.visible = false;
}


/*

Any "p" element labelled under a fieldset marked with "status-for-LIST_ID"
with one of these classes will get the "active" class under the following condition:

none-selected: no items are selected
one-selected: one item is selected
some-selected: more than one, but less than all items, are selected
all-selected: all items are selected

In addition, the following variables are supported:
count: the number of items selected (useful for the some-selected case)

*/
function Checklist(list)
{
	if (is_ie)
	{
		// turns off selection of text in IE (Mozilla uses a proprietary CSS statement)
		list.onselectstart = returnFalse;
	}
	
	this.list = list;
	
	this.form = null;
	var children = list.childNodes;

	this.display = new StatusDisplay("status-for-" + list.id);

	var me = this;	
	
	// these handlers are on list items
	function checkItem(e)
	{
		me.check(this.parentNode);
		stopPropagation(e);
	}
	function selectItem(e)
	{
		me.select(this.parentNode);
		stopPropagation(e);
	}	
	function selectAndCheckItem(e)
	{
		var input = getFirstChildByTag(this.parentNode, "input");
		input.checked = !input.checked;
		me.select(this.parentNode);
		stopPropagation(e);
	}
	
	// this handler is on the list itself
	function checkSelectedItem()
	{
		var input = getFirstChildByTag(me.getSelectedItem(), "input");
		input.checked = true;
		me.updateChecklist();
	}

	// these handlers are on associated buttons
	function uncheckSelectedItem()
	{
		var input = getFirstChildByTag(me.getSelectedItem(), "input");
		input.checked = false;
		me.updateChecklist();	
	}

	function moveSelectedItemUp()
	{
		var selectedItem = me.getSelectedItem();
		if (selectedItem != me.list.firstChild)
		{
			me.insertItemBefore(selectedItem, selectedItem.previousSibling);
		}
	}

	function moveSelectedItemDown()
	{
		var selectedItem = me.getSelectedItem();
		if (selectedItem != me.list.lastChild)
		{
			if (selectedItem.nextSibling == me.list.lastChild)
			{
				// since this will be the last item, we can't insert before any node
				me.insertItemBefore(selectedItem, null);
			}
			else
			{
				// we insert before the node two down, so this appears after the
				// node just below, and therefore moves down by one
				me.insertItemBefore(selectedItem, selectedItem.nextSibling.nextSibling);
			}
		}
	}
	
	function updateList()
	{
		me.updateChecklist();	
	}

	function clearSelectedItem()
	{
		var selectedItem = me.getSelectedItem();
		if (selectedItem) 
		{
			removeStyle(selectedItem, me.activeStyle);
			me.updateChecklist();	
		}
	}

	if (hasStyle(list, "commutable"))
	{
		this.clickWillCheck = false;
		this.commutable = true;
		this.activeStyle = "selected";
	}
	else
	{
		this.clickWillCheck = true;
		this.commutable = false;
		this.activeStyle = "checked";
	}
	
	for (var i = 0, len = children.length; i < len; i++)
	{		
		var listItem = children[i];
		if (listItem.nodeType == 1)
		{
			var input = getFirstChildByTag(listItem, "input");
			
			if (!this.form) 
			{
				this.form = input.form;
			}

			if (this.clickWillCheck)
			{
				setConditionalStyle(listItem, this.activeStyle, input.checked);
				
				addEvent(input, "onclick", checkItem);

				var label = getFirstChildByTag(listItem, "label");
				if (label)
				{
					label.onclick = checkItem;					
				}	
				label = null;
			}
			else
			{
				addEvent(input, "onclick", updateList);

				addEvent(input, "onclick", stopPropagationAndContinue);
				
				var label = getFirstChildByTag(children[i], "label");
				if (label)
				{
					label.onclick = selectItem;
					label.ondblclick = selectAndCheckItem;
				}
				label = null;
			}
			
			input = null;
		}
		listItem = null;
	}
	
	if (this.commutable)
	{
		for (var i = 0, len = this.form.elements.length; i < len; i++)
		{
			var control = this.form.elements[i];
			if (control.type == "button")
			{
				if (hasStyle(control, "checklist-check"))
				{
					this.checkButton = control;
					addEvent(control, "onclick", checkSelectedItem);
				}
				else if (hasStyle(control, "checklist-uncheck"))
				{
					this.uncheckButton = control;
					addEvent(control, "onclick", uncheckSelectedItem);
				}
				else if (hasStyle(control, "checklist-up"))
				{
					this.upButton = control;
					// we use mouseup due to the lack of consistency on
					// what two clicks in rapid succession mean.
					control.onmouseup = moveSelectedItemUp;
				}
				else if (hasStyle(control, "checklist-down"))
				{
					this.downButton = control;
					control.onmouseup = moveSelectedItemDown;
				}
			}
			else if (hasStyle(control, "checklist-result"))
			{
				this.resultInput = control;
			}
		}
		
		addEvent(this.list, "onclick", clearSelectedItem);
	}

	this.updateChecklist();
	
	// break the closure on these elements
	list = null;
	children = null;
	
	addActiveElement(this);
}

Checklist.prototype.release = function()
{
	this.list = null;
	this.form = null;
	this.checkButton = null;
	this.uncheckButton = null;
	this.upButton = null;
	this.downButton = null;
	this.resultInput = null;
}


Checklist.prototype.check = function(listItem)
{
	var input = getFirstChildByTag(listItem, "input");
	var label = getFirstChildByTag(listItem, "label");

	if (!hasStyle(label, "disabled"))
	{
		setConditionalStyle(listItem, this.activeStyle, input.checked);
	}
	
	this.updateChecklist();	
}


Checklist.prototype.select = function(listItem)
{
	var label = getFirstChildByTag(listItem, "label");

	if (!hasStyle(label, "disabled") && !hasStyle(listItem, this.activeStyle))
	{
		var children = this.list.childNodes;
		for (var i = 0, len = children.length; i < len; i++)
		{		
			var li = children[i];
			if (li.nodeType == 1)
			{
				removeStyle(li, this.activeStyle);
			}
		}
		addStyle(listItem, this.activeStyle);
	}
	this.updateChecklist();	
}

Checklist.prototype.getSelectedItem = function()
{
	var children = this.list.childNodes;
	for (var i = 0, len = children.length; i < len; i++)
	{		
		var listItem = children[i];
		if (listItem.nodeType == 1 && hasStyle(listItem, this.activeStyle))
		{
			return listItem;
		}
	}
	
	return null;
}

Checklist.prototype.insertItemBefore = function(selectedItem, newNext)
{
	// we have to save and restore the checked state, because
	// IE automatically checks it when we move an element	
	var input = getFirstChildByTag(selectedItem, "input");
	var checked = input.checked;
	
	if (newNext)
	{
		selectedItem.parentNode.insertBefore(selectedItem, newNext);
	}
	else
	{
		selectedItem.parentNode.appendChild(selectedItem);
	}
	
	input.checked = checked;
	
	// we delay this because disabling buttons during a mouseup event
	// causes IE to get confused about focus
	var me = this;
	window.setTimeout(function() { me.updateChecklist(); }, 1);
}	
	


Checklist.prototype.updateChecklist = function()
{
	var count = 0;
	
	var result = new Array();
	// go over the list in the current order, in case it has been shuffled
	var children = this.list.childNodes;
	for (var i = 0, len = children.length; i < len; i++)
	{		
		var listItem = children[i];
		if (listItem.nodeType == 1)
		{
			var input = getFirstChildByTag(listItem, "input");
			result.push(escape(input.value) + "=" + (input.checked ? "1" : "0"));
			if (input.checked)
			{
				count++;
			}
		}
	}
	
	if (this.resultInput) this.resultInput.value = result.join(",");
	
	var selectedInput = null;
	var selectedItem = this.getSelectedItem();
	if (selectedItem)
	{
		selectedInput = getFirstChildByTag(selectedItem, "input");
	}
	
	if (this.checkButton)
	{
		this.checkButton.disabled = (selectedInput == null || selectedInput.checked);
	}
	if (this.uncheckButton)
	{
		this.uncheckButton.disabled = (selectedInput == null || !selectedInput.checked);
	}
	if (this.upButton)
	{
		this.upButton.disabled = (selectedInput == null || selectedItem == this.list.firstChild);
	}
	if (this.downButton)
	{
		this.downButton.disabled = (selectedInput == null || selectedItem == this.list.lastChild);
	}
	
	if (count == 0)
	{
		this.display.updateActive("none-selected");
	}
	else if (count == len)
	{
		this.display.updateActive("all-selected");
	}
	else if (count == 1)
	{
		this.display.updateActive("one-selected");
	}
	else 
	{
		this.display.updateActive("some-selected");
	}
		
	this.display.setVariable("count", count);
}




/*
Constructs a SeparateSelection object, which maintains two lists that the 
user can move items between.
*/
function SeparateSelection(available)
{
	this.available = available;
	this.available.SEPARATE_SELECTION = this;
	this.available.ondblclick = moveSelectedToChosen;

	var id = available.id;

	this.chosen  = document.getElementById(id + "-chosen");
	this.chosen.SEPARATE_SELECTION = this;
	this.chosen.ondblclick = removeSelectedFromChosen;

	var chosen_max_re = /max-items=(\d+)/;
	var chosen_params = chosen_max_re.exec(this.chosen.className);
	if (chosen_params)
	{
		this.max_chosen = chosen_params[1];
	}
	
	var chosen_min_re = /min-items=(\d+)/;
	chosen_params = chosen_min_re.exec(this.chosen.className);
	if (chosen_params)
	{
		this.min_chosen = chosen_params[1];
	}
	
	this.add  = document.getElementById(id + "-add");
	this.add.SEPARATE_SELECTION = this;
	this.add.onclick = moveSelectedToChosen;

	this.remove = document.getElementById(id + "-remove");
	this.remove.SEPARATE_SELECTION = this;
	this.remove.onclick = removeSelectedFromChosen;	

	this.up = document.getElementById(id + "-up");
	if (this.up)
	{
		this.up.SEPARATE_SELECTION = this;
		// we use mouseup and keypress since IE interprets quick clicks as 
		// double clicks.  If I set the onDblClick event IE needs it to do
		// two actions and Mozilla needs it to do one action.  So instead
		// I use mouseup for the mouse and keypress for keyboard use.
		// This way, no matter how fast you click N times, you get N events
		// in all browsers I tested (IE, Mozilla, Opera).
		this.up.onmouseup = this.up.onkeypress = moveUp;
	}
	
	this.down = document.getElementById(id + "-down");
	if (this.down)
	{
		this.down.SEPARATE_SELECTION = this;
	
		// see above
		this.down.onmouseup = this.down.onkeypress = moveDown;	
	}
	
	this.list = document.getElementById(id + "-list");
	this.updateStatus();
	// store the initial value
	this.list.defaultValue = this.list.value;
}

function getSeparateSelection(id)
{
	return document.getElementById(id).SEPARATE_SELECTION;
}

function moveSelectedToChosen()
{
	this.SEPARATE_SELECTION.addSelected();
}
function removeSelectedFromChosen()
{
	this.SEPARATE_SELECTION.removeSelected();
}

function moveUp()
{
	this.SEPARATE_SELECTION.moveUp(this.SEPARATE_SELECTION.chosen);
}

function moveDown()
{
	this.SEPARATE_SELECTION.moveDown(this.SEPARATE_SELECTION.chosen);
}
	
function submitSeparateSelect(select)
{
	for (var n = 0; n < select.options.length; n++)
	{
		select.options[n].selected = true;
	}
	
	return false;
}

SeparateSelection.prototype.addSelected = function ()
{
	this.moveSelected(this.available, this.chosen, true, true, null, this.max_chosen);
	this.updateStatus();
}

SeparateSelection.prototype.removeSelected = function ()
{
	this.moveSelected(this.chosen, this.available, true, true, this.min_chosen, null);
	this.updateStatus();
}

SeparateSelection.prototype.updateStatus = function()
{
	this.add.disabled = (this.chosen.length == this.max_chosen);
	this.remove.disabled = (this.chosen.length == this.min_chosen || this.chosen_length == 0);
	if (this.up)
	{
		this.up.disabled = (this.chosen.length < 2);
	}
	if (this.down)
	{
		this.down.disabled = (this.chosen.length < 2);
	}
	
	var list = "";
	for (var n = 0; n < this.chosen.length; n++)
	{
		list += LTrim(getValueFromOption(this.chosen.options[n]));
		if (n < this.chosen.length-1)
		{
			list += ",";
		}
	}
	this.list.value = list;
}

SeparateSelection.prototype.moveSelected = function (source, dest, add, remove, min_source_length, max_dest_length)
{
	/* copy over selected items */
	for (var n = 0; n < source.options.length; )
	{
		var option = source.options[n];
		// this is enabled if disabled is defined and false, or if it is not defined at all
		var enabled = (typeof(option.disabled) != "undefined" && !option.disabled) || typeof(option.disabled) == "undefined";
		if (option.selected && enabled)
		{	
			if (remove == false && findOptionInSelectByValue(option, dest) != -1)
			{
				// skip this one if it already exists
				n++;
				continue;
			}
			
			// see if we are given a max and have already reached it
			if (max_dest_length && dest.options.length >= max_dest_length)
			{
				alert("You can only select " + max_dest_length + " items.");
				// can't add anymore 
				break;
			}

			// see if we are given a min and have already reached it
			if (min_source_length && source.options.length <= min_source_length)
			{
				alert("You must have at least " + min_source_length + " item(s).");
				// can't add anymore 
				break;
			}

			if (add)
			{
				var newOption = new Option(LTrim(option.text), option.value, false, false);
	
				// add to the end
				dest.options[dest.options.length] = newOption;
			}
						
			if (remove)
			{
				// Removing it puts the next one at the current position, so we do NOT increment n 
				source.options[n] = null;
			}
			else
			{
				// since we are not removing this option, we move to the next one
				n++;
			}
		}
		else
		{
			// since we are leaving this unselected option in place,
			// we move to the next one
			n++;
		}
	}	
}

SeparateSelection.prototype.insertInto = function(select, newOption)
{
	var valuePosition = this.findValue(newOption);
	var insertPosition = this.positions[valuePosition];
	
	for (var n = select.options.length; n > insertPosition; n--)
	{
		// copy the option one spot down
		select.options[n] = cloneOption(select.options[n-1]);
	}
	
	// now copy the new option in
	select.options[insertPosition] = newOption;
}


SeparateSelection.prototype.moveUp = function(select)
{	
	for (var n = 0; n < select.options.length-1; n++)
	{
		if (select.options[n].selected == false && select.options[n+1].selected)
		{
			var temp = cloneOption(select.options[n]);
			select.options[n] = cloneOption(select.options[n+1]);
			select.options[n+1] = temp;
		}
	}
	this.updateStatus();
}

SeparateSelection.prototype.moveDown = function(select)
{
	for (var n = select.options.length-1; n > 0; n--)
	{
		if (select.options[n-1].selected && select.options[n].selected == false)
		{
			var temp = cloneOption(select.options[n-1]);
			select.options[n-1] = cloneOption(select.options[n]);
			select.options[n] = temp;
		}
	}
	this.updateStatus();
}

function cloneOption(option)
{
	return new Option(option.text, option.value, option.defaultSelected, option.selected);
}

function getValueFromOption(option)
{
	if (option.value == "")
	{
		return option.text;
	}
	else
	{
		return option.value;
	}
}

function findOptionInSelectByValue(option, select)
{
	var searchValue = getValueFromOption(option);
	for (var i = 0; i < select.options.length; i++)
	{
		if (searchValue == getValueFromOption(select.options[i]))
		{
			// found this item
			return i;
		}
	}
	
	return -1;
}

var hiddenWidgets= {};

function restoreLayoutState()
{
	var serialized = findCookie("layout");
	if (serialized)
	{
		var state = deserializeState(serialized);
		for (var widget in state.hiddenWidgets)
		{
			hiddenWidgets[widget] = (state.hiddenWidgets[widget] == "true");
		}
	}
}
	
restoreLayoutState();
	
function saveLayoutState()
{
	var state = {};
	state["hiddenWidgets"] = hiddenWidgets;
	
	var serialized = serializeState(state);
	setPermanentCookie("layout", serialized);
}

function alterPane(el)
{	
	var paneElement = getFirstParentByClass(el, "pane");
	
	var header = getFirstChildByTag(paneElement, "h3");
	var inner = getFirstChildByTag(paneElement, "div");
	var button = getFirstChildByTag(paneElement, "input");
	
	if (inner.style.display == "none")
	{
		inner.style.display = "block";
		button.src = button.src.replace("_down", "_up");
		hiddenWidgets[paneElement.id] = false;
	}
	else
	{
		if (is_ie && ie_version > 5.5)
		{
			// IE6 is buggy
			header.style.width = (GetElementWidth(header) - computeHorizontalSpacing(header)) + "px";
		}
		else
		{
			header.style.width = GetElementWidth(header) + "px";
		}
				
		inner.style.display = "none";
		button.src = button.src.replace("_up", "_down");
		hiddenWidgets[paneElement.id] = true;
	}
	saveLayoutState();
	resizeAllItems();
}


function hideSidebar()
{
	hiddenWidgets["sidepanel"] = true;
	hideElement("sidepanel-open");
	showElement("sidepanel-closed");
	saveLayoutState();
	resizeAllItems();
}

function showSidebar()
{
	hiddenWidgets["sidepanel"] = false;
	hideElement("sidepanel-closed");
	showElement("sidepanel-open");
	saveLayoutState();
	resizeAllItems();
}

function hideElement(name)
{
	var el = document.getElementById(name);
	if (el)
	{
		el.style.display = "none";
	}
}

function showElement(name)
{
	var el = document.getElementById(name);
	if (el)
	{
		el.style.display = "block";
	}
}

function addHiddenStyle(name)
{
	var el = document.getElementById(name);
	if (el)
	{
		addStyle(el, "hidden");
	}
}
	
function removeHiddenStyle(name)
{
	var el = document.getElementById(name);
	if (el)
	{
		removeStyle(el, "hidden");
	}
}


var imagesRetried = {};

function imageLoadError(img)
{
	if (self != top)
	{
		// we can only reload if we are in a frame, since
		// otherwise the record of what we reloaded (imagesRetried)
		// will be reset when we load the new page.
		if (!top.imagesRetried[img.src])
		{
			// only reload the frame containing the image one time, to prevent infinite loops
			self.location.reload();
			top.imagesRetried[img.src] = true;
		}
	}
}


// an XBM image that is one pixel by one pixel.  Useful for img elements
// whose contents you don't want to load when the page loads.
var tiny_image = "#define x_width 1\n#define x_height 1\nstatic char x_bits = {0x00};\n";

var printImagesLoaded = false;

var depth = 0;

function loadPrintImagesRecursive(doc)
{
	// look at all image elements
	var images = doc.getElementsByTagName("img");
	for (var i = 0; i < images.length; i++)
	{
		var img = images[i];
		if (img.className.indexOf("js-print-image") != -1)
		{
			// only do this if it has the name attribute
			if (img.name)
			{
				// first upgrade this image to be visible on printouts (but not the screen)
				img.className = (img.className.replace("all-hidden", "screen-hidden"));
	
				// now set the high-resolution version as the source
				img.src = img.name;
			}
		}
		else if (img.className.indexOf("js-screen-image") != -1)
		{
			// hide this image on printouts since we are now using the 
			// print version above
			img.className = (img.className + " print-hidden");
		}
	}
	
	// recurse into all iframes
	var iframes = doc.getElementsByTagName("iframe");
	for (var i = 0; i < iframes.length; i++)
	{
		var f = frames[iframes[i].name];
		if (depth++ < 5)
		{
			loadPrintImagesRecursive(f.document);
		}
	}		
}

/* Loads images marked as "js-print-image" with the source coming from the name
attribute, and sets their class to show only on printouts.  Also changes
the styles of images marked as "js-screen-image" to show only on the screen.
*/
function loadPrintImages(doc)
{
	if (printImagesLoaded == false)
	{
		// start off with the top-level document
		loadPrintImagesRecursive(document);
	}

	printImagesLoaded = true;
}



function initializeTabHeader(headerId)
{
	if (document.getElementById)
	{
		var ul = document.getElementById(headerId);
		ul.TAB_HEADER = new Object();

		ul.TAB_HEADER.tabNames = new Array();
		ul.TAB_HEADER.hideOthers = hasStyle(ul, "hidden-tabs");
		
		var children = ul.childNodes;
		for (var n = 0; n < children.length; n++)
		{
			var li = children[n];
			if (li.nodeType == 1 && li.nodeName == "LI")
			{
				initializeTabLinks(ul, li);
			}
		}		
	}
}

function initializeTabLinks(ul, li)
{	
	if (li.className.indexOf("current-tab") != -1)
	{
		ul.TAB_HEADER.activeName = li.id;
	}
	
	if (ul.TAB_HEADER.tabNames)
	{
		ul.TAB_HEADER.tabNames[ul.TAB_HEADER.tabNames.length] = li.id;
	}
		
	var children = li.childNodes;
	for (var n = 0; n < children.length; n++)
	{
		var l = children[n];
		if (l.nodeType == 1)
		{
			if (l.nodeName == "A")
			{
				addConditionalEvent(l, "onclick", tabClick);
				l.ondragstart = returnFalse;
			}
		}
	}
}

// returns the body element for the current tab
function getCurrentTabBody(id)
{
	var ul = document.getElementById(id);
	var children = ul.childNodes;
	for (var i = 0; i < children.length; i++)
	{
		var li = children[i];
		if (li.nodeType == 1 && li.nodeName == "LI")
		{
			if (hasStyle(li, "current-tab"))
			{
				return document.getElementById(li.id + "_body");
			}
		}
	}
}

// returns the name of the current tab (the id is defined as "NAME_tab")
function getCurrentTabItemName(id)
{
	var ul = document.getElementById(id);
	var children = ul.childNodes;
	for (var i = 0; i < children.length; i++)
	{
		var li = children[i];
		if (li.nodeType == 1 && li.nodeName == "LI")
		{
			if (hasStyle(li, "current-tab"))
			{
				return li.id.substr(0, li.id.length-4);
			}
		}
	}
}

function activateTabElement(li)
{
	if (li.className.indexOf("current-tab") == -1)
	{
		var ul = li.parentNode;
	
		// add the current style to the current tab
		li.className = (li.className + " current-tab");
		
		// and remove it from the old current tab
		if (ul.TAB_HEADER.activeName)
		{
			var old = document.getElementById(ul.TAB_HEADER.activeName);
			old.className = old.className.replace(/\s*current-tab/, "");
		}
		ul.TAB_HEADER.activeName = li.id;

		if (ul.TAB_HEADER.hideOthers)
		{
			var tabNames = ul.TAB_HEADER.tabNames;
			for (var i = 0; i < tabNames.length; i++)
			{
				var el = document.getElementById(tabNames[i] + "_body");
				if (el)
				{
					el.style.display = "none";
				}
			}
			
			var currentBody = document.getElementById(li.id + "_body");	
			if (currentBody)
			{
				// show the body
				currentBody.style.display = "block";

				// change the focus to the first input, if there is one
				var setFocus = false;
				var inputs = currentBody.getElementsByTagName("input");
				for (var i = 0; i < inputs.length; i++)
				{
					var input = inputs[i];
					if (input.type == "text" && getEffectiveStyle(input, "display") != "none" && !setFocus)
					{
						input.focus();
						input.select();
						setFocus = true;
					}					
				}
			}
			
			return false;
		}		
		else
		{
			return true;
		}
	}
	else
	{
		return false;
	}
}

function tabClick()
{
	return activateTabElement(this.parentNode);
}

function activateTab(name)
{
	var li = document.getElementById(name + "_tab");
	if (li)
	{
		return activateTabElement(li);
	}
}


/** Finds all the iframes with the given class name and refreshes them.
*/
function refreshIframes(w, className)
{
	var frames = w.document.getElementsByTagName("iframe");
	for (var i = 0; i < frames.length; i++)
	{
		var iframe = frames[i];
		if (iframe.className.indexOf(className) != -1)
		{
			// have to update it by location.href, not src
			var f = w.frames[iframe.name];
			// IE5 has odd problems with f.location = f.location.href, so we use this instead
			f.location.reload();
		}
	}	
}

/*
   PURPOSE: Remove leading blanks from our string.
*/
function LTrim(str)
{
   var whitespace = new String(" \t\n\r");

   var s = new String(str);

   if (whitespace.indexOf(s.charAt(0)) != -1) {
      // We have a string with leading blank(s)...

      var j=0, i = s.length;

      // Iterate from the far left of string until we
      // don't have any more whitespace...
      while (j < i && whitespace.indexOf(s.charAt(j)) != -1)
         j++;

      // Get the substring from the first non-whitespace
      // character to the end of the string...
      s = s.substring(j, i);
   }
   return s;
}

// Returns the Y offset of the browser's window.
function getScrollY() 
{
  var offset = 0;
  if (typeof( window.pageYOffset ) == 'number') 
  {
    //Netscape compliant
    offset = window.pageYOffset;
  } 
  else 
  {
    if (document.body && document.body.scrollTop) 
    {
      //DOM compliant
      offset = document.body.scrollTop;
    } 
    else 
    {
      if (document.documentElement && document.documentElement.scrollTop) 
      {
      	//IE6 standards compliant mode
        offset = document.documentElement.scrollTop;
      }
    }
  }
  
  return offset;
}

function getOffsetFromBottom()
{
	return getScrollY()-getBodyHeight();
}

// opens the given URL so that certain elements in the browser do not move 
// relative to the window.
// Adds "pos" to the URL.
// If top is true, the url is relative to the top of the browser; if
// false, from the bottom.  
function openAtSameOffset(el, top) 
{
	var pos = top ? getScrollY() : getOffsetFromBottom();
	
	if (el.href)
	{
		el.href += "&pos=" + pos;
	}
	else if (el.action)
	{
		el.elements["pos"].value = pos;
		el.submit()
	}
	
	return true;
}

function bookmarkThisLink(l, title)
{
	if (window.external)
	{
		window.external.AddFavorite(l.href, title);
		return false;
	}
	else if (window.sidebar)
	{
		window.sidebar.addPanel(title, l.href, "");
		return false;
	}
	else if (window.opera && window.print ) 
	{
		l.title = title;
		l.rel = "sidebar";
		return true;
	}

	return false;
}

function resizeToFit() 
{
	window.setTimeout(function() {
	var el = document.getElementById('popup-page');
	var w = el.offsetWidth + 100;
	var h = el.offsetHeight + 100; 

	var scW = screen.availWidth ? screen.availWidth : screen.width;
	var scH = screen.availHeight ? screen.availHeight : screen.height;
	
	var newLeft = Math.max(0, Math.round((scW-w)/2));
	var newTop = Math.max(0, Math.round((scH-h)/2));

	if (newLeft + w > scW)
	{
		w = scW;
		newLeft = 0;
	}
	
	if (newTop + h > scH)
	{
		h = scH;
		newTop = 0;
	}
	
	moveWindow(newLeft, newTop);
	resizeOuterWindow(w, h);
	});
}


// moves the page to the value given in pos, relative to the
// top of the page if it is positive, or the bottom
// if it is negative.
function reposition(pos)
{
	if (pos < 0)
	{
		window.scrollTo(0, getBodyHeight() + pos);
	}
	else
	{	
		window.scrollTo(0, pos);
	}	
}

function getWindowLeft()
{
	var x = 0;
	if (typeof(top.screenLeft) != "undefined") 
	{
		// Explorer and others
		x = top.screenLeft;
	}
	else if (typeof(top.screenX) != "undefined")
	{
		// Mozilla and others
		x = top.screenX;
	}
	
	return x;
}

function getWindowTop()
{
	var y = 0;
	if (typeof(top.screenTop) != "undefined") 
	{
		// Explorer and others
		y = top.screenTop;
	}
	else if (typeof(top.screenY) != "undefined")
	{
		// Mozilla and others
		y  = top.screenY;
	}
	
	return  y;
}

function getWindowWidth()
{
	var width;
	if (self.innerWidth) // all except Explorer
	{
		width = self.innerWidth;
	}
	else if (document.documentElement && document.documentElement.clientWidth)
	{
		// Explorer 6 Strict Mode
		width = document.documentElement.clientWidth;
	}
	else if (document.body) // other Explorers
	{
		width = document.body.clientWidth;
	}
	
	return width;
}

function getWindowHeight()
{
	var height;
	if (self.innerHeight) // all except Explorer
	{
		height = self.innerHeight;
	}
	else if (document.documentElement && document.documentElement.clientHeight)
	{
		// Explorer 6 Strict Mode
		height = document.documentElement.clientHeight;
	}
	else if (document.body) // other Explorers
	{
		height = document.body.clientHeight;
	}
	
	return height;
}

function resizeInnerWindow(width, height)
{
	// TODO: rewrite this!
	resizeOuterWindow(width, height);
}

function resizeOuterWindow(width, height)
{
	if (top.dialogArguments)
	{
		// dialog boxes in IE don't support resizeTo
		top.dialogWidth = width + "px";	
		top.dialogHeight = height + "px";
	}
	else
	{
		top.resizeTo(width, height);
	}
}

function moveWindow(x, y)
{
	if (top.dialogArguments)
	{
		// dialog boxes in IE don't support moveTo
		top.dialogLeft = x + "px";	
		top.dialogTop = y + "px";
	}
	else
	{
		top.moveTo(x, y);
	}
}

function getBodyHeight()
{
	var height;
	if (document.documentElement && document.documentElement.clientHeight)
	{
		// IE 6
		height = document.documentElement.scrollHeight;
	}
	else if (document.body && document.body.scrollHeight)
	{
		// IE 5 and others
		height = document.body.scrollHeight;
	}
	else if (document.body && document.body.offsetHeight)
	{
		// Netscape 6/Mozilla
		height = document.body.offsetHeight;
	}
	else
	{
		// old NS (4)
		height = document.height;
	}
		
	return height;
}

function getBodyWidth() 
{
  var width = 0;
  if (typeof(window.innerWidth) == 'number')
   {
    // Non-IE
    width = window.innerWidth;
   } 
   else 
   {
    if (document.documentElement &&
        (document.documentElement.clientWidth || document.documentElement.clientHeight)) 
    {
      //IE 6+ in 'standards compliant mode'
      width = document.documentElement.clientWidth;
     } 
     else 
     {
       if (document.body && ( document.body.clientWidth || document.body.clientHeight)) 
       {
         //IE 4 compatible
         width = document.body.clientWidth;
      }
    }
  }
  
  return width;
}

function getBodyHeightTest()
{
	if (document.documentElement && document.documentElement.clientHeight)
	{
		// IE 6
		height = document.documentElement.scrollHeight;
		version = "IE6";
	}
	else if (document.body && document.body.scrollHeight)
	{
		height = document.body.scrollHeight;
		version = "IE+5";
	}
	else if (document.body && document.body.offsetHeight)
	{
		// Netscape
		height = document.body.offsetHeight;
		version = "NS";
	}
	else
	{
		height = document.height;
		version = "unknown";
	}
		
	return [ height, version, getScrollY() ];
}


function GetElement(id) 
{
	// don't need document.all for IE 5+ or other DOM browsers,
	// and in fact document.all is slower for IE than getElementById
	return document.getElementById(id);
}


function GetAbsoluteLeft(element)
{
	var left = 0;
	while (element != null && element != document)
	{
		left = left + element.offsetLeft;
		element = element.offsetParent;
	}
	return left;
}

function GetAbsoluteTop(element)
{
	var top = 0;
	while (element != null && element != document)
	{
		top += + element.offsetTop;
		element = element.offsetParent;
	}
	return top;
}

function computeHorizontalSpacing(element)
{
	var spacing = 0;
	
	if (element.currentStyle)
	{
		spacing += parseSpacing(element.currentStyle['marginLeft']) + 
		parseSpacing(element.currentStyle['borderLeftWidth']) +
		parseSpacing(element.currentStyle['paddingLeft']);
		
		spacing += parseSpacing(element.currentStyle['marginRight']) + 
		parseSpacing(element.currentStyle['borderRightWidth']) +
		parseSpacing(element.currentStyle['paddingRight']);
	} 
	else if (window.getComputedStyle)
	{
		var style = window.getComputedStyle(element, null);
		spacing += parseSpacing(style.getPropertyValue('margin-left')) + 
		parseSpacing(style.getPropertyValue('border-left-width')) +
		parseSpacing(style.getPropertyValue('padding-left'));
		
		spacing += parseSpacing(style.getPropertyValue('margin-right')) + 
		parseSpacing(style.getPropertyValue('border-right-width')) +
		parseSpacing(style.getPropertyValue('padding-right'));
	}
	
	return spacing;
}

// note: this function may have problems in Mozilla with margins
function computeVerticalSpacing(element)
{
	var spacing = 0;
	
	if (element.currentStyle)
	{
		spacing += parseSpacing(element.currentStyle['marginTop']) + 
		parseSpacing(element.currentStyle['borderTopWidth']) +
		parseSpacing(element.currentStyle['paddingTop']);
		
		spacing += parseSpacing(element.currentStyle['marginBottom']) + 
		parseSpacing(element.currentStyle['borderBottomWidth']) +
		parseSpacing(element.currentStyle['paddingBottom']);
	} 
	else if (window.getComputedStyle)
	{
		var style = window.getComputedStyle(element, null);
		
		spacing += parseSpacing(style.getPropertyValue('margin-top')) + 
		parseSpacing(style.getPropertyValue('border-top-width')) +
		parseSpacing(style.getPropertyValue('padding-top'));
		
		spacing += parseSpacing(style.getPropertyValue('margin-bottom')) + 
		parseSpacing(style.getPropertyValue('border-bottom-width')) +
		parseSpacing(style.getPropertyValue('padding-bottom'));
	}
	
	return spacing;
};

function GetElementHeight(element)
{
	if (element.offsetHeight)
	{
		return element.offsetHeight;
	}
	else if (document.defaultView)
	{	
		return parseInt(window.getComputedStyle(element, null).height);
	}
}

function GetElementWidth(element)
{
	if (document.defaultView)
	{
		return parseInt(document.defaultView.getComputedStyle(element,'').getPropertyValue('width'));
	}
	else
	{	
		return element.offsetWidth;
	}
}

// Returns the style that describes the elements current state.
// styleName: the hyphenated style as it would appear in CSS.
function getEffectiveStyle(element, styleName)
{
	if (element.currentStyle)
	{
		// IE uses, eg, "marginTop" instead of "margin-top"
		return element.currentStyle[normalizeStyleName(styleName)];
	} 
	else if (window.getComputedStyle)
	{
		return window.getComputedStyle(element, null).getPropertyValue(styleName);
	}
	else
	{	
		return "";
	}
}

// Returns the style with spaces removed and leading characters capitalized,
// as in 'border-left-width' -> 'borderLeftWidth'
function normalizeStyleName(style)
{
	var parts = style.split("-");
	var result = "";
	for (var i = 0; i < parts.length; i++)
	{
		if (i == 0)
		{
			result += parts[i];
		}
		else
		{
			var part = parts[i];
			result += part.substr(0, 1).toUpperCase() + part.substr(1);
		}
	}
	return result;
}

function findPosX(obj)
{
	var curleft = 0;
	while (obj.offsetParent)
	{
		curleft += obj.offsetLeft
		obj = obj.offsetParent;
	}

	return curleft;
}

function findPosY(obj)
{
	var curtop = 0;
	while (obj.offsetParent)
	{
		curtop += obj.offsetTop
		obj = obj.offsetParent;
	}
	return curtop;
}

function getMouseX(e)
{
	var posx = 0;
	if (!e) var e = window.event;
	if (e.pageX)
	{
		posx = e.pageX;
	}
	else if (e.clientX)
	{
		posx = e.clientX + document.body.scrollLeft;
	}
	
	return posx;
}

function getMouseY(e)
{
	var posy = 0;
	if (!e) var e = window.event;
	if (e.pageY)
	{
		posy = e.pageY;
	}
	else if (e.clientY)
	{
		posy = e.clientY + document.body.scrollTop;
	}
	
	return posy;
}

