﻿/// <reference path="EntireMochiKit.js"/>

// Comparators we need for this project...
registerComparatorHelper("ObjectsWithValuePropertyComparator", "Value");

//////////////////////////////////////////////////////////////////////////////////
// By centralizing some commonly-used string literals, we save ourselves 
// from making any mistakes in duplicating these very string values.
String.empty = "";
String.undefined = "undefined";

var addPropertiesWithValuesOfSameName = function(obj, propNames) {
   forEach(propNames, function(propName) {
      obj[propName] = propName;
   });
}

var DomEvent = new (function() { // these are for use in connecting to and signalling common dom events, instead of having 'magic' strings everywhere
   var domEventNames = ["onabort", "onblur", "onchange", "onclick", "ondblclick", "onerror", "onfocus",
							"onkeydown", "onkeypress", "onkeyup", "onload", "onmousedown", "onmousemove",
							"onmouseout", "onmouseover", "onmouseup", "onreset", "onresize", "onscroll",
							"onselect", "onsubmit", "onunload"];
   addPropertiesWithValuesOfSameName(this, domEventNames);
})();

KeyCode = new Object;
KeyCode.enter = 13;
KeyCode.pageup = 33;
KeyCode.pagedown = 34;
KeyCode.uparrow = 38;
KeyCode.downarrow = 40;

////////////////////////////////////////////////////////////////////////////////////
// The following is used for the JavaScript timeout - independent of the Session timeout
// This process will only help out the person who has walked away from the computer.  If they click and/or push keys around
// without actually going to the server for anything, they will still get "unusual" timeout problems unless they are
// delt with elsewhere
//
// We want this to run as a singleton...
////////////////////////////////////////////////////////////////////////////////////
// The interval is in minutes - no sense going crazy about it, let the timer run

//function AppTimer() {
//	var timerID = null;
//	var pathName = null;
//	var SetAppTimeout = null;

//	var SetAppTimeout = function() {
//		alert("timeout! " + currentDocument().location.pathname);
//		StartAppTimer();
//	}

//	var StartAppTimer = function() {
//		if(timerID) {
//			clearTimeout(timerID);
//		}
//		timerID = setTimeout(SetAppTimeout, 5000); //60000); // Testing
//	}

//	if (currentDocument().location.pathname.indexOf("Login") < 0) {
//		StartAppTimer();
//		connect(window, DomEvent.onload, function() {
//			connect(currentDocument(), DomEvent.onkeypress, function(eventArgs) {
//				StartAppTimer();
//			});
//			connect(currentDocument(), DomEvent.onclick, function(eventArgs) {
//				StartAppTimer();
//			});
//		});
//	}
//}
//if (AppTimer.instance == "undefined") {
//	AppTimer.instance = null;
//}
//AppTimer.getInstance = function () {
//	if (AppTimer.instance == null) {
//		AppTimer.instance = new AppTimer();
//	}
//	return AppTimer.instance;
//}
//formAppTimer = AppTimer.getInstance();
////////////////////////////////////////////////////////////////////////////////////
// TODO: the following can be deleted once we are in production
connect(window, DomEvent.onload, function() {
   if (window["m_LastErrorMessage"] != undefined && m_LastErrorMessage != String.empty) {
      displayErrorMessage(m_LastErrorMessage);
   }
   var loggingPane = null;
   var loggingPaneOpen = false;
   connect(currentDocument(), DomEvent.onkeypress, function(eventArgs) {
      if ((typeof (loggingPane) == String.undefined || loggingPaneOpen == false) && eventArgs.key().string == "~") {
         loggingPane = createLoggingPane(true);
         loggingPaneOpen = true;
         eventArgs.stop();
      } else if (loggingPane != null) {
         loggingPane.closePane();
         loggingPaneOpen = false;
      }
   });
});

//////////////////////////////////////////////////////////////////////////////////

function displayErrorMessage(message/*, elementToShowMessageBy*/) {//TODO: This needs to show the error message - use the optional elementToShowMessageBy to determine where to show the message...
   /// <summary>
   /// 
   /// </summary>
   var errorDiv = $("m_ErrorMessageDiv");
   if (errorDiv) {
      replaceChildNodes(errorDiv, createDOM(evalHTML(Format("<center>{0}</center>", message))));
      showElement(errorDiv);
      callLater(5, partial(hideElement, errorDiv));
   }
}
function IndicateChangeability(element, cssClass, title) { // give better name!
   /// <summary>
   /// 
   /// </summary>
   setNodeAttribute(element, "title", (title || ""));
   connect(element, DomEvent.onmouseover, partial(addElementClass, element, cssClass));
   connect(element, DomEvent.onmouseout, partial(removeElementClass, element, cssClass));
}
function Format(formatString, args) {
   /// <summary>
   /// 
   /// </summary>
   forEach(izip(count(), list(arguments).slice(1)), function(arg) {
      formatString = formatString.replace(new RegExp("\\{[" + arg[0] + "]{1}\\}", "g"), arg[1]);
   });
   return formatString;
}
function strip(str, stripStr) { return str.replace(stripStr, String.empty); }
function trim(str) { return ltrim(rtrim(str)); }
function ltrim(str) { return str.replace(/^\s+/, String.empty); }
function rtrim(str) { return str.replace(/\s+$/, String.empty); }

function ajaxResponseWrapper(onCallback, onErrback, onBoth) { // onBoth gets called after the others, no matter what happens
   /// <summary>
   /// This is to be used with the AjaxPro.NET calls, to better encapsulate what to do when it responds.
   /// </summary>
   return function(response) {
      if (response.error == null) {
         if (isFunction(onCallback)) onCallback(response.value, response.json);
         else if (onCallback != null) throw new Error("Value of onCallback parameter needs to be a function.");
      } else {
         if (isFunction(onErrback)) onErrback(response.error);
         else if (onErrback != null) throw new Error("Value of onErrback parameter needs to be a function.");
      }
      if (isFunction(onBoth)) onBoth();
      else if (onBoth != null) throw new Error("Value of onBoth parameter needs to be a function.");
   };
}
//function ajaxRequestsInProgress() {
//	var topWindow = getTopWindow();
//	return (topWindow.currentAjaxRequests) ? (topWindow.currentAjaxRequests.length > 0) : false;
//}

function show(element) {
   /// <summary>
   /// 
   /// </summary>
   removeElementClass(element, "Invisible");
   addElementClass(element, "Visible");
}
function hide(element) {
   /// <summary>
   /// 
   /// </summary>
   addElementClass(element, "Invisible");
   removeElementClass(element, "Visible");
}
function GetElementsByTagNameAndAttributeValue(tagName, attribute, value) {
   /// <summary>
   /// 
   /// </summary>
   return filter(function(e) { return HasAttributeValue(e, attribute, value); }, list(document.getElementsByTagName(tagName)));
}
function HasAttributeValue(obj, attribute, value) {
   /// <summary>
   /// 
   /// </summary>
   return (HasAttribute(obj, attribute) && GetAttributeValue(obj, attribute).toString().toUpperCase() == value.toString().toUpperCase());
}
function GetAttributeValue(obj, attribute) {
   /// <summary>
   /// 
   /// </summary>
   return (obj[attribute] || getNodeAttribute(obj, attribute));
}
function HasAttribute(obj, attribute) {
   /// <summary>
   /// 
   /// </summary>
   return (obj[attribute] != null || getNodeAttribute(obj, attribute) != null);
}
function GetNodesByAttributeValue(nodeToWalk, attribute, value) {
   /// <summary>
   /// 
   /// </summary>
   var matchingNodes = [];
   nodeWalk(nodeToWalk, function(node) {
      if (node[attribute] != null && node[attribute].toUpperCase() == value.toUpperCase()) {
         matchingNodes.push(node);
      }
      return node.childNodes;
   });
   return matchingNodes;
}
// get a css property value from a css class name (e.g. if you want to read the 'background-color' value 
// of a CSS class, you would call it like this: getCSSValue("background-color", "MyCSSClassName");)
function getCSSValue(styleName, cssClass) {
   /// <summary>
   /// 
   /// </summary>
   // create an element that can't be seen & set its css class to the one passed in 
   var dummy = DIV({ "id": Guid.Get(), "class": cssClass, style: "display:none; visibility:hidden" }, null);
   // add to DOM in order to make the css class 'take' and be able to read the values
   appendChildNodes(currentDocument().body, dummy);
   // read the value off of element
   var value = getStyle(dummy, styleName);
   // remove element
   removeElement(dummy);
   return value;
}
// code originally taken from Telligent's Community Server and left unmodified (except for the name - was changed 
// from getposOffset) this code helps determine positioning of elements of the page relative to each other
function GetPositionOffset(elem, offsetType) {
   /// <summary>
   /// 
   /// </summary>
   var totalOffset = (offsetType == "left") ? elem.offsetLeft : elem.offsetTop;
   var parentElem = elem.offsetParent;
   while (parentElem != null) {
      totalOffset = (offsetType == "left") ? totalOffset + parentElem.offsetLeft : totalOffset + parentElem.offsetTop;
      parentElem = parentElem.offsetParent;
   }
   return totalOffset;
}
// will return *only* the query string portion of any url, if it exists
function GetQueryString(url) {
   /// <summary>
   /// 
   /// </summary>
   url = (url || document.URL);
   var qs = null;
   if (url.indexOf("?") >= 0) {
      qs = url.split("?", 2)[1];
      if (qs.indexOf("#") >= 0) qs = qs.split("#")[0];
   }
   return qs;
}
// provides unique identifier, though should not be considered 'truly' globally unique - mainly here for
// use of providing a unique id only within the context of a page... the basics of this are based on 
// solution provided here: http://www.thescripts.com/forum/post2042003-3.html
var Guid = new (function() {
   this.empty = "00000000-0000-0000-0000-000000000000";
   this.Empty = this.empty;
   this.GetNew = function() {
      var fourChars = function() {
         return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1).toUpperCase();
      }
      return Format("{0}{1}-{2}-{3}-{4}-{5}{6}{7}", fourChars(), fourChars(), fourChars(), fourChars(), fourChars(), fourChars(), fourChars(), fourChars());
   };
   this.Get = this.GetNew; // this is only here because there are places in our code that call Get instead of GetNew...
})();
function RemoveFromArray(array, value) { //TODO: Can't this be replaced by a MochiKit function?
   /// <summary>
   /// Modifies and returns array...
   /// </summary>
   var indexOfElementToRemove = findValue(array, value);
   array.splice(indexOfElementToRemove, 1);
   return array;
}
/*
function prepend(array, value) { //TODO: Can't this be replaced by a MochiKit function?
/// <summary>
/// Modifies and returns array...
/// </summary>
array.unshift(value);
return array;
}
*/
function SetAssociatedElementDisplay(/* element OR id */elementToPosition, /* element OR id */elementToReference, optionalHeightOffsetAdjustment, optionalWidthOffsetAdjustment) {
   /// <summary>
   /// 
   /// </summary>
   elementToPosition = $(elementToPosition);
   elementToReference = $(elementToReference);
   debugger;
   var absolutePosition = getAbsolutePosition(elementToReference);
   setElementPosition(elementToPosition, { x: absolutePosition.x + (optionalWidthOffsetAdjustment || 0), y: absolutePosition.y + elementToReference.offsetHeight - (optionalHeightOffsetAdjustment || 1) });
}
// From mochikit trac at http://trac.mochikit.com/wiki/ParsingHtml
// Slightly modified from original by Jason Bunting
function evalHTML(value) {
   /// <summary>
   /// 
   /// </summary>
   if (typeof (value) != "string") return null;
   value = trim(value);
   if (value.length == 0) return null;
   var parser = MochiKit.DOM.DIV();
   setHtml(parser, value);
   var child;
   var html = MochiKit.DOM.currentDocument().createDocumentFragment();
   while ((child = parser.firstChild)) {
      html.appendChild(child)
   }
   return html;
}
// partial attribute values can be used with this function
function getNodeByAttribute(containerNode, attributeName, attributeValue) {
   /// <summary>
   /// 
   /// </summary>
   var nodeToWalk = (containerNode || MochiKit.DOM.currentDocument());
   var firstMatchingNode = null;
   MochiKit.Base.nodeWalk(nodeToWalk, function(elem) {
      if (firstMatchingNode == null && getNodeAttribute(elem, attributeName) != null && getNodeAttribute(elem, attributeName).indexOf(attributeValue) >= 0) {
         firstMatchingNode = elem;
      }
      return elem.childNodes;
   });
   return firstMatchingNode;
}
function getNodesByAttribute(nodeToWalk, attribute) {
   /// <summary>
   /// 
   /// </summary>
   var nodeToWalk = (nodeToWalk || MochiKit.DOM.currentDocument());
   var matchingNodes = [];
   nodeWalk(nodeToWalk, function(node) {
      if (node[attribute] != null || getNodeAttribute(node, attribute) != null) {
         matchingNodes.push(node);
      }
      return node.childNodes;
   });
   return matchingNodes;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/* The code below could be made better, but works for now - it is useful in handling key events and filter such */
CustomKeyEvent = function(evt) {
   this.ctor(evt);
};
MochiKit.Base.update(CustomKeyEvent.prototype, {
   m_Event: null,
   m_NumberKeys: null,
   m_BasicOperationKeys: null,
   ctor: function(evt) {
      m_Event = evt;

      m_NumberKeys = new Object();
      m_NumberKeys["keypress"] = [46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 110, 190]; //TODO: Extract decimal/period to new routine (46) - also only one period/decimal per field
      m_NumberKeys["keydown"] = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 110, 190]; //TODO: Extract decimal/period to new routine (110, 190)

      m_BasicOperationKeys = new Object();
      m_BasicOperationKeys["keypress"] = [0];
      m_BasicOperationKeys["keydown"] = [8, 9, 17, 27, 35, 36, 37, 39, 45, 46, 144];
      m_BasicOperationKeys["keydown"]["shift"] = [9];
      m_BasicOperationKeys["keydown"]["ctrl"] = [65, 67, 86, 88];
      m_BasicOperationKeys["keydown"]["alt"] = [9];
   },
   FilterForInteger: function() {
      if (!(this.IsNumber() || this.IsBasicOperation())) {
         m_Event.stop();
      }
   },
   IsNumber: function() {
      return some(m_NumberKeys[m_Event.type()], this.IsMatch);
   },
   IsBasicOperation: function() {
      return some(m_BasicOperationKeys[m_Event.type()], this.IsMatch);
   },
   IsMatch: function(keyCode) {
      log(keyCode, " ", m_Event.key().code)
      return (keyCode == m_Event.key().code);
   }
});
function CreateIntegerTextbox(textbox) {
   /// <summary>
   /// 
   /// </summary>
   textbox = $(textbox);
   var onkeypressId = connect(textbox, DomEvent.onkeypress, function(eventArgs) {
      var keyEventHandler = new CustomKeyEvent(eventArgs);
      keyEventHandler.FilterForInteger();
   });
   var onkeydownId = connect(textbox, DomEvent.onkeydown, function(eventArgs) {
      var keyEventHandler = new CustomKeyEvent(eventArgs);
      keyEventHandler.FilterForInteger();
   });
   textbox.RemoveIntegerTextbox = function() {
      disconnect(onkeydownId);
      disconnect(onkeypressId);
   };
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
// In IE 6 and earlier, select elements have a greater z-index than anything on 
// the page, including divs that are supposed to be laid over the top of them
function ToggleSelectVisibility() {
   /// <summary>
   /// 
   /// </summary>
   var selects = document.getElementsByTagName("select");
   forEach(selects, function(s) {
      s.style.visibility = (s.style.visibility.toLowerCase() == "hidden") ? "visible" : "hidden";
   });
}
function connectMany(element, eventList, fun) {
   /// <summary>
   /// Use this for when you want to use the same event handler for many events of the same object - returns 
   /// array of objects that look like this: { "eventName":eventName, "connectionId":connectionId }
   /// </summary>
   var connectIdentifiers = [];
   forEach(eventList, function(eventName) {
      connectIdentifiers.push({ "eventName": eventName, "connectionId": connect(element, eventName, fun) });
   });
   return connectIdentifiers;
}
function connectAll(list, signal, func) {
   /// <summary>
   /// Use this for when you want to use the same event handler for the same event of more than one object - returns 
   /// array of objects that look like this: { "listItem":item, "connectionId":connectionId }
   /// </summary>
   return map(function(item) { return { "listItem": item, "connectionId": connect(item, signal, func) }; }, list);
}
function connectArgs(element, eventName, obj, func /*, 1..n args */) {
   /// <summary>
   /// connectArgs is for when you want to handle an event with a method that exists on an object and pass arguments to it...
   /// </summary>
   var argsForFunc = list(arguments).slice(4);
   return connect(element, eventName, function() {
      func.apply(obj, argsForFunc);
   });
}

function connectOnce(src, signal, func) {
   /// <summary>
   /// A variation of the MochiKit connect(src, signal, func) function, this disconnects after signal fires once
   /// </summary>
   var signalId = connect(src, signal, function() {
      func();
      disconnect(signalId);
   });
}
function getElementsByTagNameAndAttributeValue(tagName, attribute, value) {
   /// <summary>
   /// 
   /// </summary>
   var elements = [];
   forEach(list(document.getElementsByTagName(tagName)), function(element) {
      if (element[attribute] != null && element[attribute].toUpperCase() == value.toUpperCase()) {
         elements.push(element);
      }
   });
   return elements;
}

function createThickBoxUrl(pageUrl, queryStringNameValuePairs, width, height) {
   /// <summary>
   /// Assists in (read: centralizes) creating the URL string for opening a page in a ThickBox (using IFrames)
   /// </summary>
   var partialQueryString = queryString((queryStringNameValuePairs || {}));
   return Format((partialQueryString != String.empty) ? "{0}?{1}&{2}" : "{0}?{2}", pageUrl, partialQueryString, Format("TB_iframe=true&width={0}&height={1}", width, height));
}

function enableElement(/* 1..n element(s) */element) {
   /// <summary>
   /// Will set value of "disabled" property to false for single or multiple element(s) (and/or the DOM IDs thereof))
   /// </summary>
   forEach((arguments.length > 1 ? list(arguments) : (isArrayLike(element) && !element.options) ? element : [element]), function(el) { if (el) { $(el).disabled = false; } });
}

function disableElement(/* 1..n element(s) */element) {
   /// <summary>
   /// Will set value of "disabled" property to true for single or multiple element(s) (and/or the DOM IDs thereof))
   /// </summary>
   forEach((arguments.length > 1 ? list(arguments) : (isArrayLike(element) && !element.options) ? element : [element]), function(el) { if (el) { $(el).disabled = true; } });
}
function removeChildNodes(node) {// This is a simple alias that adds a bit o' semantic meaning for clearer intent
   return replaceChildNodes(node);
}

function getValue(element) {
   return getElement(element).value;
}
function setValue(element, value) {
   return getElement(element).value = value;
}
function getHtml(element) {
   return getElement(element).innerHTML;
}
function setHtml(element, value) {
   return getElement(element).innerHTML = value;
}
function getText(element) {
   return scrapeText(element);
}
function setText(element, value) {
   return setHtml(element, value);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// select list helper functions - these are not very MochiKit-ish, but use regular DOM stuff (i.e. need to rewrite?)

function getSelectedValue(selectList) {
   /// <summary>
   /// For easily obtaining the selected value of an HTML select element
   /// </summary>
   selectList = $(selectList);
   return selectList.options[selectList.selectedIndex].value;
}

function getSelectedText(selectList) {
   /// <summary>
   /// For easily obtaining the selected text of an HTML select element
   /// </summary>
   selectList = $(selectList);
   return selectList.options[selectList.selectedIndex].text;
}

function setSelectedText(selectList, value) {
   /// <summary>
   /// For easily replacing the selected text of an HTML select element
   /// </summary>
   selectList = $(selectList);
   selectList.options[selectList.selectedIndex].text = value;
}

function removeSelectListOption(optionPropertyName, selectList, optionPropertyValue) {
   /// <summary>
   /// Removes from the drop down list that option which has the value of optionValue
   /// </summary>
   /// <param name="selectList" type="Select">
   /// The HTML select element (or its DOM ID value) to remove the option from.
   /// </param>
   /// <param name="nameValueArray" type="Array">
   /// The value of the value property of the option to remove from the list.
   /// </param>
   selectList = $(selectList); // in case the ID was passed in, we grab the element
   if (selectList != null && selectList.options != null) {
      for (i = 0; i < selectList.options.length; i++) {
         if (selectList.options[i][optionPropertyName] == optionPropertyValue) {
            selectList.options[i] = null;
            break;
         }
      }
   }
}
var removeSelectListOptionByValue = partial(removeSelectListOption, "value");
var removeSelectListOptionByText = partial(removeSelectListOption, "text");

function replaceSelectListOptions(selectList, nameValueArray) {
   /// <summary>
   /// Replaces all existing options in the select list with options created using values from the provided array of data.
   /// </summary>
   /// <param name="selectList" type="Array">
   /// The HTML select element (or its DOM ID value) to replace options within.
   /// </param>
   /// <param name="nameValueArray" type="Array">
   /// An array of any objects that have both 'Name' and 'Value' properties.
   /// </param>
   selectList = $(selectList); // in case the ID was passed in, we grab the element
   if (selectList != null
	&& selectList.options != null
	&& nameValueArray != null
	&& nameValueArray.length > 0) {
      clearSelectListOptions(selectList);
      appendSelectListOptions(selectList, convertToSelectListOptions(nameValueArray));
   }
}
function clearSelectListOptions(/* 1..n element(s) */selectList) {
   /// <summary>
   /// Will remove all options from single or multiple HTML select element(s) (and/or the DOM IDs thereof))
   /// </summary>
   forEach((arguments.length > 1 ? list(arguments) : [selectList]), function(sl) { if (sl) { sl.length = 0; } });
}
function appendSelectListOptions(selectList, options) {
   /// <summary>
   /// 
   /// </summary>
   addSelectListOptions(selectList, options, false);
}
///+++
function addSelectListOptions(selectList, options, prependOptions) {
   /// <summary>
   /// 
   /// </summary>
   selectList = $(selectList);
   if (options != null && options.length > 0) {
      options = (prependOptions) ? (extend(options, cloneOptions(selectList.options))) : options;
      selectList.options.length = 0;
      for (var i = 0; i < options.length; i++) {
         selectList.options[i] = options[i];
      }
   }
}
function cloneOptions(options) {
   /// <summary>
   /// Does just what you think it does - clones an array of select list Options.
   /// </summary>
   return map(function(o) { return new Option(o.text, o.value, o.defaultSelected, o.selected); }, options);
}
function getNameValuePairs(arrayLikeObject, nameProperty, valueProperty) { //TODO: how useful is this?
   nameProperty = (nameProperty || "text");
   valueProperty = (valueProperty || "value");
   return map(function(arrayListObjectItem) {
      return { Name: arrayListObjectItem[nameProperty], Value: arrayListObjectItem[valueProperty] };
   }, arrayLikeObject);
}
function convertToSelectListOptions(nameValueArray, selectedValue) {
   /// <summary>
   /// 
   /// </summary>
   selectedValue = (selectedValue || String.empty);
   var options = [];
   if (nameValueArray != null && nameValueArray.length > 0) {
      for (i = 0; i < nameValueArray.length; i++) {
         if (selectedValue != null && nameValueArray[i].Value.toString() == selectedValue.toString()) {
            options[i] = new Option(nameValueArray[i].Name, nameValueArray[i].Value, true, true);
         } else {
            options[i] = new Option(nameValueArray[i].Name, nameValueArray[i].Value);
         }
      }
   }
   return options;
}
function prependSelectListOptions(selectList, options) {
   /// <summary>
   /// 
   /// </summary>
   selectList = $(selectList);
   if (options != null && options.length > 0 && selectList != null) {
      var arrayOfOriginalOptions = list(selectList.options);
      clearSelectListOptions(selectList);
      appendSelectListOptions(selectList, options);
      appendSelectListOptions(selectList, arrayOfOriginalOptions);
   }
}
function setSelectedSelectListOption(selectList, value, fireSignal) {
   /// <summary>
   /// Sets the selected properties for the specified list by value
   /// </summary>
   selectList = $(selectList);
   for (i = 0; i < selectList.options.length; i++) {
      if (selectList.options[i].value == value) {
         selectList.options[i].selected = true;
         selectList.options[i].defaultSelected = true;
         break;
      }
   }
   if (fireSignal) signal(selectList, DomEvent.onchange);
}
function setSelectedSelectListTextOption(selectList, text, fireSignal) {
   /// <summary>
   /// Sets the selected properties for the specified list by text
   /// </summary>
   selectList = $(selectList);
   for (i = 0; i < selectList.options.length; i++) {
      if (selectList.options[i].text == text) {
         selectList.options[i].selected = true;
         selectList.options[i].defaultSelected = true;
         break;
      }
   }
   if (fireSignal) signal(selectList, DomEvent.onchange);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
// MochiKit-based dom manipulation.....
function getListOptions(nameValuePairArray) {
   /// <summary>
   /// Creates a list of OPTION objects for use with a SELECT object.
   /// </summary>
   /// <param name="possibleFunction" type="Object">
   /// An array of objects that have "Name" and "Value" properties (they may have others as well, obviously); e.g. [{Name:'foo', Value:1}, {Name:'bar', Value:2}]
   /// </param>
   return map(function(nameValuePair) { return OPTION({ "value": nameValuePair.Value }, nameValuePair.Name); }, nameValuePairArray);
}

///////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////
// Below are functions to assist development/testing/debugging - shouldn't need these in production
///////////////////////////////////////////////////////////////////////////////////////////////////////
function ListObjectKeys(obj) {
   /// <summary>
   /// Lists, to the native console of the browser, the names of properties of a JavaScript 
   /// object, which is useful in API discovery and debugging
   /// </summary>
   var totalKeys = keys(obj).length;
   for (var i = 0; i < totalKeys; i++) {
      logWarning(keys(obj)[i]);
   }
}

function LogObjectKeysAndValues(objects) {
   /// <summary>
   /// Lists, to the native console of the browser, the names and values of properties 
   /// of a JavaScript object, which is useful in API discovery and debugging
   /// </summary>
   forEach(list(arguments), function(obj) {
      log("The following is a list of object keys and values for: ", repr(obj));
      var allItems = items(obj);
      for (var i = 0; i < allItems.length; i++) {
         var item = allItems[i];
         if (isFunction(item[1])) {
            log(item[0], " : (This is a function)");
         } else if (isArrayLike(item[1])) {
            LogObjectKeysAndValues(item[1]);
         } else {
            log(item[0], " : ", item[1]);
         }
      }
      log("--------------------------------------------");
   });
}
function callIfFunction(possibleFunction/*, args*/) { //TODO: look into the value of actually using this (after extending it to allow 1..n arguments)
   /// <summary>
   /// If the value passed to it is a function, this will execute it. This is mainly used for building widgets...
   /// </summary>
   if (isFunction(possibleFunction)) possibleFunction();
}
function Timer() {
   /// <summary>
   /// Provides for simple client-side performance testing - nothing too fancy. Instantiate this 
   /// class, call start() to begin timer, stop() to do just that - the elapsedTime property will
   /// contain, in milliseconds, the amount of elapsed time (!!!)
   /// </summary>

   // private functions and variables
   var that = this;
   var startTime = null;
   var endTime = null;

   // public functions and properties
   this.elapsedTime = 0;
   this.start = function() { startTime = new Date(); };
   this.stop = function() { endTime = new Date(); that.elapsedTime = (endTime - startTime); };
}


function callAll(/* args */) {
   /// <summary>
   /// This function will take each argument that _is_not_ a function and pass it to each 
   /// of the arguments that _is_ a function. Order of the arguments is insignificant.
   /// </summary>
   var funcArguments = filter(function(arg) { return (isFunction(arg) == false); }, list(arguments));
   var functions = filter(function(arg) { return (isFunction(arg)); }, list(arguments));
   forEach(functions, function(f) {
      forEach(funcArguments, f);
   });
}

function displayServerError(error) {
   LogObjectKeysAndValues(error);
   displayErrorMessage(error.Message);
}

function getIEVersion() { // adapted slightly from http://support.microsoft.com/kb/167820
   /// <summary>
   /// The JScript function below can be used to determine the version of the browser it is running on from 
   /// a client-side script. The function runs on the large majority of browsers currently available and returns 
   /// the major version number for any Microsoft Internet Explorer browser, and zero (0) for others. Use of 
   /// this function assures that the script will be compatible with future versions of the Internet Explorer. 
   /// </summary>
   var ua = window.navigator.userAgent;
   var msie = ua.indexOf("MSIE ");
   return ((msie > 0) ? parseInt(ua.substring(msie + 5, ua.indexOf(".", msie))) : 0);
}

function isFunction(possibleFunction) {
   /// <summary>
   /// Indicates whether or not provided value is a function.
   /// </summary>
   /// <param name="possibleFunction" type="Object">
   /// Any object for which a desire to know if it is a function exists.
   /// </param>
   return (typeof (possibleFunction) == typeof (Function));
}
function ensureNotNull(/*1...n arguments*/) {
   if (some(arguments, isNull)) throw new Error("No arguments may be null.");
}
function ensureNotNullOrUndefined(/*1...n arguments*/) {
   if (some(arguments, isUndefinedOrNull)) throw new Error("No arguments may be null or undefined.");
}
function requireArguments(/*1...n arguments*/args) {
   if (some(arguments, isUndefinedOrNull)) {
      throw new Error("Some required arguments not supplied.");
   }
}

function getTabMetaData(tabDomId, targetUrl, addAction, editAction, actionHandler, selectedCssClassName, unselectedCssClassName) { // builds tab metadata objects
   return {
      "tabDomId": tabDomId,
      "targetUrl": targetUrl,
      "enableAdd": (addAction != null),
      "addAction": addAction,
      "enableEdit": (editAction != null),
      "editAction": editAction,
      "actionHandler": actionHandler, /* this will hold a function pointer */
      "selectedCssClassName": selectedCssClassName,
      "unselectedCssClassName": unselectedCssClassName
   };
}

function getCookieValue(name) {
   var dc = document.cookie;
   var prefix = name + "=";
   var begin = dc.indexOf("; " + prefix);
   if (begin == -1) {
      begin = dc.indexOf(prefix);
      if (begin != 0) return null;
   } else {
      begin += 2;
   }
   var end = document.cookie.indexOf(";", begin);
   if (end == -1) {
      end = dc.length;
   }
   return unescape(dc.substring(begin + prefix.length, end));
}

function setCookieValue(name, value) {
   document.cookie = Format("{0}={1}", name, value);
}

function deleteCookie(name) {
   var cookieDate = new Date();
   cookieDate.setTime(cookieDate.getTime() - 1);
   document.cookie = Format("{0}=; expires={1}", name, cookieDate.toGMTString());
}

function getTopWindow() {
   var topWindow = window;
   while (topWindow.parent != topWindow) {
      topWindow = topWindow.parent;
   }
   return topWindow;
}
function getTopDocument() {
   return getTopWindow().document;
}
function getTopBody() {
   return getTopDocument().body;
}

function getAbsolutePosition(element) {
   var pos = elementPosition(element);
   var topWindow = window;
   while (topWindow.parent != topWindow) {
      var tempPos = getOffsets(topWindow.frameElement);
      pos.x += tempPos.x;
      pos.y += tempPos.y;
      topWindow = topWindow.parent; // eventually get us out of the loop
   }
   return pos;
}

function getOffsets(element) {
   var y = 0;
   var x = 0;
   while (element != null) {
      y += element.offsetTop;
      x += element.offsetLeft;
      element = element.offsetParent;
   }
   return { "x": x, "y": y };
}

var StyleBuilder = new (function() {
   // internals -------------------------------------------
   var styleBuilder = String.empty;
   var that = this;
   var styleCreator = function(defaultUnit, styleName, value) {
      styleBuilder += Format("{0}:{1}{2};", styleName, value, (typeof (value) == "string" ? String.empty : defaultUnit));
      return that;
   };
   var createBasicStyle = partial(styleCreator, String.empty);
   var createPixelUnitStyle = partial(styleCreator, "px");

   // -----------------------------------------------------

   // public methods --------------------------------------
   this.backgroundColor = partial(createBasicStyle, "background-color");
   this.border = partial(createBasicStyle, "border");
   this.clearBoth = partial(createBasicStyle, "clear", "both");
   this.floatLeft = partial(createBasicStyle, "float", "left");
   this.height = partial(createPixelUnitStyle, "height");
   this.left = partial(createPixelUnitStyle, "left");
   this.margin = partial(createPixelUnitStyle, "margin");
   this.marginLeft = partial(createBasicStyle, "margin-left");
   this.marginRight = partial(createBasicStyle, "margin-right");
   this.minHeight = partial(createPixelUnitStyle, "min-height");
   this.opacity = function(val) {
      styleBuilder += Format("filter:alpha(opacity={0});-moz-opacity:{1};opacity:{1};", (val * 100), val);
      return that;
   };
   this.padding = partial(createPixelUnitStyle, "padding");
   this.positionAbsolute = partial(createBasicStyle, "position", "absolute");
   this.positionRelative = partial(createBasicStyle, "position", "relative");
   this.textAlignRight = partial(createBasicStyle, "text-align", "right");
   this.top = partial(createPixelUnitStyle, "top");
   this.visibilityHidden = partial(createBasicStyle, "visibility", "hidden");
   this.width = partial(createPixelUnitStyle, "width");
   this.fontWeight = partial(createBasicStyle, "font-weight");
   this.zIndex = partial(createBasicStyle, "z-index");
   this.color = partial(createBasicStyle, "color");

   this.genericStyle = createBasicStyle; // this allows for those styles we have not yet designed this for - e.g. StyleBuilder.genericStyle("border", "solid 1px #000").width(9).end();
   this.concat = function(arbitraryStyle) { // this allows for those styles we have not yet designed this for - e.g. StyleBuilder.concat("border:solid 1px #000").width(9).end();
      styleBuilder += arbitraryStyle;
      return that;
   };
   this.end = function(returnObject) { // this must be called to get the built style
      returnObject = (returnObject || false);
      var temp = styleBuilder;
      styleBuilder = "";
      return (returnObject) ? { style: temp} : temp;
   };
   // -----------------------------------------------------
})();

function isInDOMTree(node) {
   return !!(findUltimateAncestor(node).body); // Essentially, if the farthest-back ancestor of our node has a 'body' property, we assume it is because the node is *in* the page's DOM - these heuristics are *very* simplistic but seem to work
}

function findUltimateAncestor(node) {
   /// <summary>
   /// Typically, this will go up the DOM tree until it reaches the Document node (body); however, if 
   /// the element is not a part of the document, i.e. it is just part of a programmatically-created DOM 
   /// structure that has yet to be added, it will return something other than the Document node (body)
   /// </summary>
   var ancestor = coerceToDOM(node);
   while (ancestor.parentNode) {
      ancestor = ancestor.parentNode;
   }
   return ancestor;
}
function provideOnLoadEvent(node) {
   /// <summary>
   /// Since all DOM elements other than <body> and <frameset> are without the "onload" event, but 
   /// it can, at times, prove useful to know _when_ an arbitrary DOM element is added to the DOM, 
   /// we provide this as a way to 'fake' or synthesize the event. This function will check, every 
   /// twentieth of a second, to see if our element is a part of the DOM; as soon as we know that it 
   /// is, we signal that the "onload" event for the element has taken place.
   /// </summary>
   if (isInDOMTree(node)) {
      signal(node, "onload", node);
   } else {
      setTimeout(partial(arguments.callee, node), 50);
   }
}
function provideOnUnloadEvent(node) {
   /// <summary>
   /// Since all DOM elements other than <body> and <frameset> are without the "onunload" event, but 
   /// it can, at times, prove useful to know _when_ an arbitrary DOM element is removed from the DOM, 
   /// we provide this as a way to 'fake' or synthesize the event. This function will check, every 
   /// twentieth of a second, to see if our element is no longer a part of the DOM; as soon as we know 
   /// that it is not, we signal that the "onunload" event for the element has taken place.
   /// </summary>
   var addedToDomAlready = isUndefinedOrNull(arguments[1]) ? isInDOMTree(node) : arguments[1];
   if (addedToDomAlready) {
      if (!isInDOMTree(node)) {
         signal(node, "onunload", node);
      } else {
         setTimeout(partial(arguments.callee, node, addedToDomAlready), 50);
      }
   } else {
      connect(node, "onload", partial(arguments.callee, node, true));
   }
}
function provideLoadRelatedEvents(node) {
   provideOnUnloadEvent(node); provideOnLoadEvent(node);

}
function withClientSideExceptionLogging(func) { //TODO: Does this actually work as expected?!
   try {
      return func();
   } catch (e) {
      logError(e);
   }
}

function incrementallyProcess(workerCallback, data, chunkSize, timeout, completionCallback) {
   /// <summary>
   /// Assists in processing large quantities of data so that the UI doesn't freeze 
   /// up. The following code example should sufficiently illustrate how it works:
   /// 
   ///	incrementallyProcess(function(item) {
   ///		log(item); // in this function we do something with the item, here we are just logging it
   ///	}, [1,2,3,4,5], 1, 1000, function() {
   ///		alert("done!"); // once everything is done, we specify what, if anything, we want to happen
   ///	});
   /// 
   /// </summary>
   /// <param name="workerCallback" type="Function">
   /// The function to execute for each member of the data array; a single item is passed in for each execution.
   /// </param>
   /// <param name="data" type="Array">
   /// For each item in this array, the workerCallback function will be called, passing the item to the workerCallback.
   /// </param>
   /// <param name="chunkSize" type="Number">
   /// How many items from the data array to process before waiting to process the next chunk of data.
   /// </param>
   /// <param name="timeout" type="Number">
   /// The amount of time to wait between the processing of each 'chunk' of data.
   /// </param>
   /// <param name="completionCallback" type="Function">
   /// The function to execute when all of the members of the data array have been processed by the workerCallback function.
   /// </param>
   var itemIndex = 0;
   (function() {
      var remainingDataLength = (data.length - itemIndex);
      var currentChunkSize = (remainingDataLength >= chunkSize) ? chunkSize : remainingDataLength;
      if (itemIndex < data.length) {
         while (currentChunkSize--) {
            workerCallback(data[itemIndex++]);
         }
         setTimeout(arguments.callee, timeout);
      } else if (completionCallback) {
         completionCallback();
      }
   })();
}

function setCheckedValue(checkBox, value) {
   /// <summary>
   /// Because of an apparent bug, IE 6 will not allow for the setting of the checked property 
   /// on a checkbox until it has been added to the DOM, but you can set, instead, the defaultChecked 
   /// property to produce the same effect. See http://bytes.com/forum/thread799167.html
   /// In the case of an IE6 DOM element already created, we still need to set the checked property.
   /// </summary>
   var ieVersion = getIEVersion();
   //var checkboxPropertyToUse = (ieVersion > 0 && ieVersion <= 6 && ($(checkBox) == "null")) ? "defaultChecked" : "checked";
   //checkBox[checkboxPropertyToUse] = value;
   if (ieVersion > 0 && ieVersion <= 6) {
      checkBox.defaultChecked = value;
      checkBox.checked = value;
   } else {
      checkBox.checked = value;
   }
}

function purge(domElement) {
   /// <summary>
   /// Because of IE's poor memory management problems, memory leaks are a common 
   /// problem. Douglas Crockford created this function to assist in avoiding memory 
   /// leaks when removing elements from the DOM - modified a bit for our purposes.
   /// See: http://www.crockford.com/javascript/memory/leak.html
   /// </summary>
   var attributes = domElement.attributes, index, length, name, children;
   if (attributes) {
      length = attributes.length;
      for (index = 0; index < length; index++) {
         name = attributes[index].name;
         if (isFunction(domElement[name])) {
            domElement[name] = null;
         }
      }
   }
   children = domElement.childNodes;
   if (children) {
      length = children.length;
      for (index = 0; index < length; index++) {
         purge(domElement.childNodes[index]);
      }
   }
}

function copy(obj) {
   /// <summary>
   /// Creates deep copies of objects.
   /// </summary>
   return cloneObject(obj); // we are wrapping 
}
function copyViaJSONSerialization(obj) {
   /// <summary>
   /// Serializes to JSON and then deserializes again to produce a copy. While 
   /// not as fast as other implementations, it works and is easy to understand.
   /// </summary>
   return evalJSON(serializeJSON(obj));
}
function cloneObject(obj) {
   /// <summary>
   /// Creates deep copies of objects - this WILL cause stack overflows if object has cyclical references.
   /// See http://www.irt.org/script/879.htm for inspiration for this function (our implementation is more robust).
   /// This should really only be used on data objects that are created via JSON, not on objects constructed from 
   /// constructor functions - if used on objects created with constructor functions, the cloned objects may cause 
   /// unexpected behavior if used for anything other than just their data (e.g. checking their constructor property)
   /// ALSO: null values cause the "constructor" error too.
   /// </summary>
   var clone;
   if (obj.constructor == Array) {
      clone = [];
      for (var i = 0; i < obj.length; i++) {
         clone[i] = cloneObject(obj[i]);
      }
   } else if (obj.constructor == Object || (obj.constructor != Number &&
				obj.constructor != String && obj.constructor != Date && obj.constructor != Boolean &&
				obj.constructor != RegExp && obj.constructor != Function)) { // make sure this contains all built-in types...we want only objects built from non-native constructor functions
      clone = new Object(); // use the generic constructor
      for (var property in obj) clone[property] = cloneObject(obj[property]);
   } else if (obj.constructor != Function) {
      clone = obj;
   }
   return clone;
}

function addMethod(/* typically, 'this' */obj, methodName, func) {
   /// <summary>
   /// Convenience function for building JavaScript 'classes' - adds methods to the 
   /// prototype (i.e. functions that can be shared across instances); allows you to 
   /// keep all code *within* the constructor function, rather than placing function 
   /// additions to the prototype outside of it, e.g.
   /// 
   ///	function Rectangle(w, h) {
   ///		this.width = w;
   ///		this.height = h;
   ///		addMethod(this, "getArea", function() { return this.width * this.height; });
   ///	}
   /// 
   ///	// Instead of then having to do this *outside* of the constructor function:
   ///	Rectangle.prototype.getArea = function() { return this.width * this.height; };
   /// 
   /// </summary>
   if (!(obj.constructor.prototype[methodName])) {
      obj.constructor.prototype[methodName] = func;
   }
}

/*
What we really need is a databinding framework of some sort for data entry widgets - something 
where we configure things (or use convention, if possible) and then feed data to it and let it 
do the work of filling our objects, etc. and then let it handle the updates, etc.

Basically, we need something like the WilsonUIMapper (with two-way databinding), but for JavaScript stuff!
*/
function DataChangeMonitor(baselineData, propertyMap) {
   requireArguments(baselineData);
   var that = this;
   var differences = [];
   that.updateData = function(data) {
      forEach(findDifferences(baselineData, data), function(difference) {
         signal(that, "ondatachange", difference, data);
      });
      baselineData = data;
   };
   that.setBaselineData = function(data) { baselineData = data; };
}

function Difference(propertyName, oldValue, newValue) {
   this.Property = propertyName;
   this.OldValue = oldValue;
   this.NewValue = newValue;
}
Difference.ArrayDifferenceIndexerFormatString = "[{0}|{1}]"; // <index within old object> | <index within new object>
Difference.ArrayDifferenceIndexerRegExp = /\[-{0,1}\d{1,}\|-{0,1}\d{1,}\]/;

function arrayChangeMonitor(oldDataArray, newDataArray, matchedItemFunction, removedItemFunction, newItemFunction) {
   var total = newDataArray.length;
   for (var i = 0; i < oldDataArray.length; i++) {
      var matchIndex = findValue(newDataArray, oldDataArray[i]);
      if (matchIndex != -1) {
         total--;
         matchedItemFunction(oldDataArray[i], newDataArray[matchIndex], i, matchIndex);
      } else {
         removedItemFunction(oldDataArray[i], i);
      }
   }
   if (total > 0) {
      for (var i = 0; i < newDataArray.length; i++) {
         var matchingValueIndex = findValue(oldDataArray, newDataArray[i]);
         if (matchingValueIndex == -1) {
            newItemFunction(newDataArray[i], i);
         }
      }
   }
}
function findDifferences(oldObject, newObject) {
   var propertyChanges = [];
   var objectGraphPath = [];
   (function(oldObj, newObj) {
      if (oldObj.constructor == Array) {
         arrayChangeMonitor(oldObj, newObj, function(oldMatch, newMatch, oldIndexOf, newIndexOf) {
            objectGraphPath.push(Format(Difference.ArrayDifferenceIndexerFormatString, oldIndexOf, newIndexOf));
            findDifferences(oldMatch, newMatch);
            objectGraphPath.pop();
         }, function(removedItem, oldIndexOf) {
            objectGraphPath.push(Format(Difference.ArrayDifferenceIndexerFormatString, oldIndexOf, -1));
            propertyChanges.push(new Difference(objectGraphPath.join(""), removedItem, null));
            objectGraphPath.pop();
         }, function(addedItem, newIndexOf) {
            objectGraphPath.push(Format(Difference.ArrayDifferenceIndexerFormatString, -1, newIndexOf));
            propertyChanges.push(new Difference(objectGraphPath.join(""), null, addedItem)); // null is used since object is no longer in array
            objectGraphPath.pop();
         });
      } else if (oldObj.constructor == Object || (oldObj.constructor != Number &&
					oldObj.constructor != String && oldObj.constructor != Date && oldObj.constructor != Boolean &&
					oldObj.constructor != RegExp && oldObj.constructor != Function)) { // make sure this contains all built-in types...we want to allow objects built from custom constructor functions
         for (var property in oldObj) {
            objectGraphPath.push(((objectGraphPath.length > 0) ? "." : "") + property);
            if (oldObj[property].constructor != Function) {
               arguments.callee(oldObj[property], newObj[property]);
            }
            objectGraphPath.pop();
         }
      } else if (oldObj.constructor != Function) {
         if (oldObj != newObj) {
            propertyChanges.push(new Difference(objectGraphPath.join(""), oldObj, newObj));
         }
      }
   })(oldObject, newObject);
   return propertyChanges;
}

String.prototype.regexIndexOf = function(regex, startpos) {
   var indexOf = this.substring(startpos || 0).search(regex);
   return (indexOf >= 0) ? (indexOf + (startpos || 0)) : indexOf;
}

String.prototype.regexLastIndexOf = function(regex, startpos) {
   regex = (regex.global) ? regex : new RegExp(regex.source, "g" + (regex.ignoreCase ? "i" : "") + (regex.multiLine ? "m" : "")); // reconstruct the regex
   if (typeof (startpos) == "undefined") {
      startpos = this.length;
   } else if (startpos < 0) {
      startpos = 0;
   }
   var stringToWorkWith = this.substring(0, startpos + 1);
   var lastIndexOf = -1;
   var nextStop = 0;
   while ((result = regex.exec(stringToWorkWith)) != null) {
      lastIndexOf = result.index;
      regex.lastIndex = ++nextStop;
   }
   return lastIndexOf;
}

function registerComparatorHelper(name, requiredProperties, /* optional */comparisonProperties) {
   /// <summary>
   /// Convenience function that assists us in registering comparators which work based on the following:
   /// 
   ///	*	The assumption that if two objects share a certain list of 
   ///		properties (param: requiredProperties), they are the same
   ///		for comparison purposes.
   /// 
   ///	*	The assumption that we want to compare *both* objects against
   ///		the same list of properties (e.g. we want to compare object
   ///		X's "Z" property against object Y's "Z" property) and that the
   ///		comparison is not necessarily for identity but for value (i.e.
   ///		we don't use the === operator in our comparisons).
   /// 
   /// </summary>
   /// <param name="requiredProperties" type="Array">
   /// An array containing the list of properties that both objects 
   /// must have to be considered valid for comparisons. If this object
   /// is NOT an array, it will be converted to a string and considered
   /// as the only property name to use.
   /// </param>
   /// <param name="comparisonProperties" type="Array">
   /// An array containing the list of properties that will be used
   /// to compare each object. This is optional; if not specified,
   /// the requiredProperties parameter will be used for this purpose.
   /// If this object is NOT an array, it will be converted to a string 
   /// and considered as the only property name to use.
   /// </param>
   if (requiredProperties.constructor != Array) requiredProperties = [requiredProperties.toString()];
   comparisonProperties = (comparisonProperties || list(requiredProperties));
   if (comparisonProperties.constructor != Array) comparisonProperties = [comparisonProperties.toString()];
   var testFunction = function(a, b) {
      return every(requiredProperties, function(propName) {
         return a.hasOwnProperty(propName) && b.hasOwnProperty(propName);
      });
   };
   var comparisonFunction = function(a, b) {
      return (every(comparisonProperties, function(propName) {
         return a[propName] == b[propName];
      })) ? 0 : 1;
   };
   registerComparator(name, testFunction, comparisonFunction);
}

var setDivFocus = function(element) {
   var hiddenFocus = INPUT({ type: "text", style: "width:1px;height:1px;" });
   appendChildNodes(element, hiddenFocus);
   hiddenFocus.focus();
   removeElement(hiddenFocus);
};

function NameValuePair(name, /* optional */value) {
   /// <summary>
   /// Strongly-typed convenience constructor function for name/value pairs.
   /// You can call it in three ways:
   ///	1. With a string value for the first parameter (name), and a value of any time for the second parameter (value)
   ///	2. With a single string value as a parameter, with no other parameters (the string will be used for both name and value)
   ///	3. With any object as the only parameter, that has a Name and a Value property - those will be used to set the name and value properties of this object.
   /// </summary>
   if (name.Name && name.Value) {
      this.Name = name.Name;
      this.Value = name.Value;
   } else {
      this.Name = name;
      this.Value = (value || name);
   }
}

function addDays(date, days) {
   /// <summary>
   ///	This modifies and returns the date
   /// </summary>
   date.setDate(date.getDate() + days);
   return date;
}

function toHtml(domNode) {
   /// <summary>
   /// For testing, can be used like so: toHtml(DIV(null, SPAN(null, "Hello")));
   /// </summary>
   return (emitHTML(domNode)).join(String.empty);
}

function getConstructorName(obj) {
   /// <summary>
   /// Returns the name of an object's constructor as a string.
   /// </summary>
   var funcNameRegex = /function (.{1,})\(/;
   var results = (funcNameRegex).exec(obj.constructor.toString());
   return (results && results.length > 1) ? results[1] : "";
}
function getLongDateString(date) {
   var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
   var days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", ];
   var monthnumber = date.getMonth();
   var monthname = months[monthnumber];
   var monthday = date.getDate();
   var year = date.getUTCFullYear();
   var dayofweek = date.getDay();
   var dayname = days[dayofweek];
   return dayname + ", " + monthname + ' ' + monthday + ', ' + year;
}
function getShortDateString(date) {
   return (date.getMonth() + 1) + "/" + date.getDate() + "/" + date.getUTCFullYear();
}
function getFormattedDate(date) {
   return date.getUTCFullYear() + "/" + (date.getMonth() + 1) + "/" + date.getDate();
}


// From Chapter 3 of "Developing More-Secure Microsoft® ASP.NET 2.0 Applications" by Dominick Baier (2006)
// Changes: changed name of the function from "checkEntropy" to "meetsEntropyRequirements"
function meetsEntropyRequirements(password, requiredBits) {
   // First, determine the type of characters used.
   var usesUpperCaseAlpha = false;
   var usesLowerCaseAlpha = false;
   var usesNumerics = false;
   var usesPunctuation = false;

   var CharCount = 0;
   var CurrentChar = '';

   for (CharCount = 0; CharCount < password.length; CharCount++) {
      CurrentChar = password.charAt(CharCount);

      if ("ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf(
      CurrentChar.toUpperCase()) > -1) {
         if (CurrentChar.toUpperCase() == CurrentChar) {
            usesUpperCaseAlpha = true;
         }
         else {
            usesLowerCaseAlpha = true;
         }
      }
      else if ("0123456789".indexOf(CurrentChar) > -1) {
         usesNumerics = true;
      }
      else if (("!.-=+_,@#$%^&*()<>[];:\\/?{}|~'" + '"' +
      "`").indexOf(CurrentChar) > -1) {
         usesPunctuation = true;
      }

      if (usesUpperCaseAlpha &&
       usesLowerCaseAlpha &&
       usesNumerics &&
       usesPunctuation) {
         // Shortcut charscan if all are met
         break;
      }
   }

   var permutations = 0;

   if (usesUpperCaseAlpha) {
      permutations += 26;
   }
   if (usesLowerCaseAlpha) {
      permutations += 26;
   }
   if (usesNumerics) {
      permutations += 10;
   }
   if (usesPunctuation) {
      permutations += 32;
   }

   var lPower = Math.pow(permutations, password.length);
   var lLog = Math.log(lPower) / Math.log(2);

   return (lLog >= requiredBits);
}

//function isNumeric(obj) {
//	// We must assume that it's a textbox
//	var value = getValue(obj);
//	if (checkNumber(value)) {
//		return true;
//	} else {
//		obj.focus();
//		obj.select();
//		return false;
//	}
//}
//function checkNumber(value) {
//	var number = "0123456789."; 
//	var valid = true;
//	for (var i = 0;  i < value.length;  i++) {
//		ch = value.charAt(i);
//		for (var j = 0;  j < number.length;  j++) {
//			if (ch == number.charAt(j))
//			break;
//		}
//		if (j == number.length) {
//			valid = false;
//			break;
//		}
//	}
//	if (!valid)	{	
//		alert("Please enter only numbers and '.'.");
//		return false;
//	}
//	return true;
//}