// Copyright (c) 2004 by One Degree, LLC. All rights reserved worldwide.
// utils.js
// -----------------------------------------------------------------------
DebugUtils.stoDbgTrc = false;
	

// BEGIN DROPDOWNFUNCS ------------------------------------------------------->


var UNITS = new Array("", "oz", "lbs", "pt", "qt", "gal", "g", "kg", "ml", "l",
	"pk");
	
/**
 * Populates the specified select element with the current choices for units.
 */
function makeUnits(control) {
	control.options.length = 0;
	for (var idx in UNITS) {
		control.options[control.options.length] =
			new Option(UNITS[idx], UNITS[idx]);
	}
}

// BEGIN IMGBUTTON ----------------------------------------------------------->


/**
 * @constructor
 * Represents an img element on the page that is being used as a button.
 *
 * @param imgElement A valid DOM reference to the img element on the page.
 * @param enabledURL The URL of the image to be displayed when the img is
 * enabled.
 * @param disabledURL The URL of the image to be displayed when the img is
 * disabled.
 * @param rolloverURL The URL of the image to be displayed when the img is
 * enabled and the mouse hovers over the img. An imgButton with no rolloverURL
 * will not perform a rollover.
 */
function ImgButton(imgElement, enabledURL, disabledURL, rolloverURL) {
	DebugUtils.doTrace(this, arguments, "var");
	this.imgElement = imgElement;
	this.enabledURL = enabledURL;
	this.disabledURL = disabledURL;
	this.rolloverURL = rolloverURL;
}

/**
 * Enables an ImgButton. Not cross-platform, as only IE supports "disabled"
 * for img's.
 */
ImgButton.prototype.enable = function enable() {
	DebugUtils.doTrace(this, arguments, "var");
	this.imgElement.src = this.enabledURL;
	this.imgElement.disabled = false;
}

/**
 * Disables an ImgButton. Not cross-platform, as only IE supports "disabled"
 * for img's.
 */
ImgButton.prototype.disable = function disable() {
	DebugUtils.doTrace(this, arguments, "var");
	this.imgElement.src = this.disabledURL;
	this.imgElement.disabled = true;
}

/**
 * Shows the rollover image for the imgButton, if any.
 */
ImgButton.prototype.showRollover = function showRollover() {
	DebugUtils.doTrace(this, arguments, "var");
	if (this.rolloverURL && this.rolloverURL != "") {
		this.imgElement.src = this.rolloverURL;
	}
}

/**
 * Hides the rollover image for the imgButton, if enabled.
 */
ImgButton.prototype.hideRollover = function hideRollover() {
	DebugUtils.doTrace(this, arguments, "var");
	this.imgElement.src = this.enabledURL;
}

ImgButton.prototype.getClassName = function() {
	return "ImgButton";
}

ImgButton.prototype.toString = function() {
	return "[" + this.getClassName + "]";
}


// BEGIN SELECTUTILS --------------------------------------------------------->


/**
 * @constructor
 */
function SelectUtils() {
	huckAnError("000004", "This object may not be instantiated: SelectUtils");
}

/**
 * Makes a new Select element with the specified Options.
 * @param values String[] An Array of Strings to be used as values for the
 * Select element's Options, keyed to the Strings that will be displayed in
 * the Select element.
 * @param events Function[] An Array of event handlers, keyed to Strings
 * representing the event type that the handler should handle.
 */
SelectUtils.createSelect = function(values, events) {
	var select = document.createElement("select");
	for (var label in values) {
		select.options[select.options.length] =
			new Option(values[label], label);
	}
	for (var eventType in events) {
		EventUtils.addEventListener(select, eventType, events[eventType]);
	}
	return select;
}
	
/**
 * Forces a selection in the options array of the specified Select element by
 * matching the Option's value to the specified criteria. If there is more than
 * one value that matches the criteria, only the first value will be found.
 */
SelectUtils.setSelectByVal = function(aSelect, criteria) {
	for (var i = 0; i < aSelect.options.length; i++) {
		if (aSelect.options[i].value == criteria) {
			aSelect.selectedIndex = i;
			break;
		}
	}
}

/**
 * Returns the value of the specified select element's current selection.
 */
SelectUtils.getSelectedVal = function(aSelect) {
	if (aSelect.selectedIndex >= 0) {
		var result = aSelect.options[aSelect.selectedIndex].value;
		return result;
	} else {
		return -1;
	}
}


// BEGIN IMGUTILS ------------------------------------------------------------>


/**
 * @constructor
 * Incomplete and uncalled. Dynamic removal of the event handlers seems like
 * the way to go for other browsers, but then how do we get them back again to
 * enable? X platform stuff not as important for the admin side. Revisit later.
 */
function ImgUtils() {
	huckAnError("000004", "This object may not be instantiated: ImgUtils");
}

ImgUtils.enableImg = function(anImg) {
	
	// IE
	if (this.imgElement.disabled) {
		this.imgElement.disabled = false;
		
	// EOMB
	} else if (this.imgElement) {
		// Remove events?
		
	// Other?
	} else {
	}
}

	
// BEGIN EVENTUTILS ---------------------------------------------------------->


// The basis for these event utilities were found at
// http://www.ditchnet.org/wp/?p=6
// the addEventListener and getTarget methods were copied directly from that
// site (July 2005)
/**
 * @constructor
 * A static class for cross-platform event-related methods.
 */
function EventUtils() {
	huckAnError("000004", "This object may not be instantiated: EventUtils");
}

/**
 * Adds the specified callback function to the specified target as an event
 * handler for the specified event type. 
 *
 * @access static
 * @param HTMLElement target
 * @param string type
 * @param Function callback
 * @param boolean captures
 */
EventUtils.addEventListener = function(target, type, callback, captures) {
	// EOMB
	if (target.addEventListener) {
		target.addEventListener(type, callback, captures);
		
	// IE
	} else if (target.attachEvent && target.detachEvent) {
	  target.detachEvent("on" + type, callback, captures);
		target.attachEvent("on" + type, callback, captures);
		
	// IE 5 Mac and some others
	} else {
		target["on" + type] = callback;
	}
}

/**
 * Removes the specified callback function from the specified target as an
 * event handler for the specified event type. 
 *
 * @access static
 * @param HTMLElement target
 * @param string type
 * @param Function callback
 * @param boolean captures
 */
EventUtils.removeEventListener = function(target, type, callback, captures) {
	// EOMB
	if (target.removeEventListener) {
		target.removeEventListener(type, callback, captures);
		
	// IE
	} else if (target.detachEvent) {
		target.detachEvent("on" + type, callback, captures);
		
	// IE 5 Mac and some others
	} else {
		target["on" + type] = null;
	}
}

/**
 * @access static
 * @deprecated Incomplete implementation
 */
EventUtils.dispatchEvent = function(target, e) {
	// EOMB
	if (target.dispatchEvent) {
		target.dispatchEvent(e);
	// IE
	} else if (target.fireEvent) {
		target.fireEvent(e);
	}
}

/**
 * Retreives the HTML element that originated/received the event.
 *
 * @access static
 * @param Event evt
 */
EventUtils.getTarget = function(evt) {
	var result = "";
	
	// EOMB
	if (evt.target) {
		result = evt.target;
		
	// IE
	} else if (evt.srcElement) {
		result = evt.srcElement;
		
	// Other?
	} else {
	}
	return result;
}

/**
 * Retreives the Unicode of key events.
 *
 * @access static
 * @param Event evt
 */
EventUtils.getCharCode = function(evt) {
	var result = -1;
	
	// EOMB
	if (evt.charCode) {
		result = evt.charCode;
	
	// IE
	} else if (evt.keyCode) {
		result = evt.keyCode;
	
	// Older Netscape
	} else if (evt.which) {
		result = evt.which;
	
	// Other?
	} else {
	}
	return result;
}


// BEGIN ARRAYUTILS ---------------------------------------------------------->


/**
 * @constructor
 * A static class for cross-platform array-related methods.
 */
function ArrayUtils() {
	huckAnError("000004", "This object may not be instantiated: ArrayUtils");
}

/**
 * Concatenates non-numerical arrays together. The way in which conflicts
 * between keys are handled may be altered by the override argument.
 * @param array1 Array Technically, any Object could be used, but this will
 * likely result in unwanted behaviour, as Arrays will only copy
 * non-prototypical key-value pairs.
 * @param array2 Array As above.
 * @param override A boolean that controls which array "wins" when there are
 * key conflicts between the arrays. If set to true, values from the array2
 * argument will overwrite values in array1 for identical keys. Otherwise,
 * values from array1 will overwrite values from array2.
 * @returns a seperate copy of the concatenated Arrays. Like the concat method,
 * the original Arrays are unaffected, and object references within the Arrays
 * will be aliased.
 */
ArrayUtils.mapConcat = function mapConcat(array1, array2, override) {
	var result = new Array();
	for (var idx in array1) {
		result[idx] = array1[idx];
	}
	for (idx in array2) {
		if (override || result[idx] == undefined) {
			result[idx] = array2[idx];
		}
	}
	return result;
}

/**
 * Deletes from the specified Array all key-value pairs whose keys match the
 * specified list of Strings.
 *
 * @param target Array The array from which elements are to be removed.
 * Technically may be any object, but passing a non-array may result in
 * unexpected behavior.
 * @param criteria [String] List of keys to be deleted from the target array.
 * @return Array The original Array, with appropriate keys deleted.
 */
ArrayUtils.deleteAllIn = function deleteAll(target, criteria) {
   for (var key in criteria) {
      try {
         delete target[criteria[key]];
      } catch (e) {
         //* Gulp!
      }
   }
   return target;
}

ArrayUtils.mapToString = function mapToString(array1, singleLine) {
	var eoe = singleLine?"":"\n"
	var result = "";
	var element;
	for (var idx in array1) {
	   if (array1[idx].splice != undefined) {
	      element = ArrayUtils.mapToString(array1[idx]);
	   } else {
	      element = array1[idx];
	   }
		result = result + "{" + idx + " : " + element + "}" + eoe;
	}
	return result;
}

ArrayUtils.pairsToString = function pairsToString(array1, singleLine, localObj) {
	var eoe = singleLine ? "" : "\n";
	var result = "";
	var element, render;
	for (var idx in array1) {
	   if (array1[idx].splice != undefined) {
	      element = ArrayUtils.pairsToString(array1[idx]);
	   } else {
	      element = array1[idx];
	   }
	   if (typeof(element) == "string") {
	      render = element;
	   } else {
	      render = (localObj && element.getClassName) ?
	         element.getClassName() : typeof(element);
	   }
		result = result + "{" + idx + " : " + render + "}" + eoe;
	}
	return result;
}

/**
 * Performs a shallow copy of an Array
 */
ArrayUtils.copy = function copy(array1) {
	result = new Array();
	for (var idx in array1) {
		result[idx] = array1[idx];
	}
	return result;
}


// BEGIN DOMUTILS ------------------------------------------------------------>


/**
 * @constructor
 * A static class for dealing with DOM traversal.
 */
function DOMUtils() {
	throw new AbstractConstructerInvokedException(this);
}

/**
 * Searches upwards through the DOM heirarchy, looking for the first
 * ancestor of the specified element that has the specified tag name. Does NOT
 * check the specified element itself.
 */
DOMUtils.containedByTag = function containedByTag(element, tagName) {
	tagName = tagName.toLowerCase();
	var target = element;
	while (target = target.parentNode) {
		if (target.tagName.toLowerCase() == tagName) break;
	}
	return target;
}

/**
 * Searches upwards through the DOM heirarchy, looking for the first
 * ancestor of the specified element that has the specified class. Is
 * compatible with multiple classes. Does NOT check the specified element
 * itself.
 * @param targetElement HTMLElement The HTML element to be tested.
 * @param className String The name of the class to be searched for.
 * @param-opt findSubClasses boolean Allows partial string matches if true,
 * matches only full strings otherwise. This allows classes to be named in such
 * a way as to allow a naming scheme for classes that relates them. For
 * example, given two elements with classes "adminText" and "adminTextGray"
 * respectively, we might search for elements that have the CSS class
 * "adminText". Setting findSubClasses to true would allow both elements
 * to be detected, whereas omitting the argument or declaring it false would
 * allow only the first element to be found.
 */
DOMUtils.containedByClass =
		function containedByClass(targetElement, className, findSubClasses) {
	className = className.toLowerCase();
	var targetClassName;
	while (targetElement = targetElement.parentNode) {
		if (DOMUtils.hasClass(targetElement, className, findSubClasses)) break;
	}
	return targetElement;
}

/**
 * Determines whether the specified element has the specified class as one of
 * its CSS classes.
 * @param targetElement HTMLElement The HTML element to be tested.
 * @param className String The name of the class to be searched for.
 * @param-opt findSubClasses boolean Allows partial string matches if true,
 * matches only full strings otherwise. This allows classes to be named in such
 * a way as to allow a naming scheme for classes that relates them. For
 * example, given two elements with classes "adminText" and "adminTextGray"
 * respectively, we might search for elements that have the CSS class
 * "adminText". Setting findSubClasses to true would allow both elements
 * to be detected, whereas omitting the argument or declaring it false would
 * allow only the first element to be found.
 */
DOMUtils.hasClass =
		function hasClass(targetElement, className, findSubClasses) {
	className = className.toLowerCase();
	className = findSubClasses ? className : " " + className + " ";
	targetClassName = targetElement.className == undefined ?
		"" : " " + targetElement.className.toLowerCase() + " ";
	if (targetClassName.indexOf(className) > -1) {
		return true;
	} else {
		return false;
	}
}

/**
 * Adds a CSS class to the list of classes maintained by an element, if that
 * class is not already present.
 */
DOMUtils.addClass = function addClass(targetElement, className) {
   var result = false;
   if (! DOMUtils.hasClass(targetElement, className, false)) {
	   targetElement.className = targetElement.className + " " + className;
	   result = true;
	} else {
	   result = true;
	}
	return result;
}

/**
 * Removes a CSS class from the list of classes maintained by an element, if
 * it is present.
 */
DOMUtils.removeClass = function removeClass(targetElement, className) {
	if (!DOMUtils.hasClass(targetElement, className, false)) {
	   return true;
	}
   if (targetElement.className.toLowerCase() == className.toLowerCase()) {
      targetElement.className == "";
      return true;
   }
   
   altClassName = " " + className.toLowerCase() + " ";
   targetClassStr = " " + targetElement.className.toLowerCase() + " ";
   startIdx = targetClassStr.indexOf(altClassName);
   
   var newClassStr = "";
   if (startIdx == 0) {
      newClassStr = targetElement.substr(startIdx + className.length + 1);
   } else if (startIdx + className.length == targetElement.className.length) {
      newClassStr = targetElement.className.slice(0, startIdx - 1);
   } else {
      var strLeft = targetElement.className.slice(0, startIdx);
      var strRight = targetElement.className.slice(
         startIdx + className.length + 1, targetElement.className.length);
      newClassStr = strLeft + strRight;
   }
   
   targetElement.className = newClassStr;
   return true;
}


// BEGIN DEBUGUTILS ---------------------------------------------------------->


/**
 * @constructor
 * A static class for cross-platform debug-related methods.
 * Currently the only valid code is "sto"
 */
function DebugUtils() {
	huckAnError("000004", "This object may not be instantiated: DebugUtils");
}

/**
 * Fires trace information to alert() dialogs if debug tracing is enabled for
 * the appropriate module. In order to use this trace mechanism, classes must
 * support the following requirements:
 * 1) Methods of the class to be traced must not be anonymous; That is, they
 * must be declared with the function statement, not the Function constructor,
 * and must include the optional "functionname" parameter in that statement,
 * like so:
 *    Class.prototype.methodname = function functionname(param, param...) {...
 * For clarity, the function name and the method name should be identical,
 * although this is not a requirement of the DebugUtils object.
 * 2) The class to be traced must implement a getClassName() method that
 * returns some sort of string to identify the calling object. For clarity, the
 * name of the class should be returned.
 *
 * @param Object callerObj A reference to an instance of the class that called
 * this method.
 * @param Arguments args The Arguments object of the method that called this
 * method. Usually obtained by a reference to the implicit arguments object
 * present in every JavaScript function (JS 1.3 - 1.5 ~?) or the arguments
 * property of the Function object (JS ~ 1.2).
 * @param string module A string identifying the "module" to which the method
 * and/or class that called this method belongs. This string can be used to
 * group methods and classes together in any arbitrary fashion, for the
 * purposes of enabling and disabling the code elements being traced.
 *
 * Module codes for modules that support the DebugUtils trace feature are
 * noted in the class description.
 */
DebugUtils.doTrace = function(callerObj, args, module) {
	if (eval("DebugUtils." + module + "DbgTrc") == true) {
		var msg = "Entering ";
		msg = msg + callerObj.getClassName() + ".";
		msg = msg + arguments.caller + "(";
		for (var i = 0; i < args.length; i++) {
			msg = msg + args[i];
			if (i != (args.length -1)) {
				msg = msg + ", ";
			}
		}
		msg = msg + ")";
		alert(msg);
	}
}

DebugUtils.setTraceOn = function(module) {
	eval("DebugUtils." + module.toLowerCase() + "DbgTrc = true");
	alert("Debug tracing for " + module.toUpperCase() + " is ON");
}

DebugUtils.setTraceOff = function(module) {
	eval("DebugUtils." + module.toLowerCase() + "DbgTrc = false");
	alert("Debug tracing for " + module.toUpperCase() + " is OFF");
}

/**
 * Fires variable information to alert() dialogs if debug tracing is enabled for
 * the appropriate module.
 * 1) Methods of the class from which these reports are requested must not be
 * anonymous; That is, they must be declared with the function statement, not
 * the Function constructor, and must include the optional "functionname"
 * parameter in that statement, like so:
 *    Class.prototype.methodname = function functionname(param, param...) {...
 * For clarity, the function name and the method name should be identical,
 * although this is not a requirement of the DebugUtils object.
 * 2) The class making the report request must implement a getClassName() method
 * that returns some sort of string to identify the calling object. For clarity,
 * the name of the class should be returned.
 *
 * @param Object callerObj A reference to an instance of the class that called
 * this method.
 * @param Arguments args The Arguments object of the method that called this
 * method. Usually obtained by a reference to the implicit arguments object
 * present in every JavaScript function (JS 1.3 - 1.5 ~?) or the arguments
 * property of the Function object (JS ~ 1.2).
 * @param string info The information being reported. This string is reported
 * verbatim, so any information that needs to be included, like variable names
 * should be inserted into the string before the call is made.
 * @param string module A string identifying the "module" to which the method
 * and/or class that called this method belongs. This string can be used to
 * group methods and classes together in any arbitrary fashion, for the
 * purposes of enabling and disabling the code elements being traced.
 *
 * Module codes for modules that support the DebugUtils information feature are
 * noted in the class description.
 */
DebugUtils.doInfo = function(callerObj, args, info, module) {
	if (eval("DebugUtils." + module + "DbgInf") == true) {
		var msg = "In ";
		msg = msg + callerObj.getClassName() + ".";
		msg = msg + arguments.caller + "\n";
		msg = msg + info;
		alert(msg);
	}
}

DebugUtils.setInfoOn = function(module) {
	eval("DebugUtils." + module.toLowerCase() + "DbgInf = true");
	alert("Debug info for " + module.toUpperCase() + " is ON");
}

DebugUtils.setInfoOff = function(module) {
	eval("DebugUtils." + module.toLowerCase() + "DbgTrc = false");
	alert("Debug info for " + module.toUpperCase() + " is OFF");
}


// --------------------------------------------------------------------------->


function makeListWithArgs(descriptor, sepChar, argChar) {
	var names = new Array(), args = new Array();
	if (descriptor != undefined && descriptor != null && descriptor != "") {
		var inter = descriptor.split(sepChar);
		for (var idx in inter) {
			args[idx] = inter[idx].split(argChar);
			names[idx] = args[idx].splice(0, 1);
			if (args[idx].length && args[idx].length == 1) {
				args[idx] = args[idx][0];
			}
		}
	}
	return new Array(names, args);
}	

function stringArg(s) {
	try {
		if (s == undefined || s == null) {
			s = "";
		}
	} catch (e) {
		s = "";
	}
	return s;
}

function ifBlank(s, m) {
   return s == "" ? s : m;
}


// BEGIN EXCEPTIONS AND ERRORS ----------------------------------------------->


function huckAnError(errorNum, errorMsg) {
	alert("Application Error (#1Dx" + errorNum + "): " + errorMsg);
}

/**
 * @constructor
 */
function DuplicateKeyException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

DuplicateKeyException.prototype.caught = function caught() {
	alert("DuplicateKeyException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function ArrayIndexOutOfBoundsException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

ArrayIndexOutOfBoundsException.prototype.caught = function caught() {
	alert("ArrayIndexOutOfBoundsException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function IDontKnowException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

IDontKnowException.prototype.caught = function caught() {
	alert("IDontKnowException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function AbstractMethodInvokedException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

AbstractMethodInvokedException.prototype.caught = function caught() {
	alert("AbstractMethodInvokedException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function AbstractConstructorInvokedException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

AbstractConstructorInvokedException.prototype.caught = function caught() {
	alert("AbstractConstructorInvokedException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function NoSuchElementException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

NoSuchElementException.prototype.caught = function caught() {
	alert("NoSuchElementException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function DataIsAmbiguousException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

DataIsAmbiguousException.prototype.caught = function caught() {
	alert("DataIsAmbiguousException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function UnrecognizedSymbolException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

UnrecognizedSymbolException.prototype.caught = function caught() {
	alert("UnrecognizedSymbolException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function CannotProcessParameterException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

CannotProcessParameterException.prototype.caught = function caught() {
	alert("CannotProcessParameterException in " + this.caller.getClassName() + this.msg);
}

/**
 * @constructor
 */
function UnknownArgumentException(caller, msg) {
	this.caller = caller;
	if (msg) {
		this.msg = ":" + msg;
	}
}

UnknownArgumentException.prototype.caught = function caught() {
	alert("UnknownArgumentException in " + this.caller.getClassName() + this.msg);
}