/* =============================================================================================================
 * FileName:     CBORD_UI.js
 * Created On:   04/24/2003
 * $Revision: #73 $
 * $DateTime: 2009/03/04 13:52:24 $
 * =============================================================================================================
 */

// TODO: make this a resource
var contactCBORDMsg = "\nPlease select OK and try again. If this issue continues,\nplease contact the helpdesk.";
var browser = navigator.appName;

window.onload = onDoneLoading;

// 'onload' event handling:  put functions to call here when the page is done loading (will be called from Ajax page rendering code too).
function onDoneLoading()
{
	resizeDivs();                   // must be done first, otherwise scrolling will not work properly
	setDivScrollPositions();        // re-poisition the screen to the previous spot (will be undone if a focus column is in use)
	setAllFocusElements();          // NOTE: this will cause a div to scroll so the focus element is visible
	
    // not all NetMenu apps use ping(), so only do this if function is defined.
	if (typeof ping == 'function') {
	    ping();
	}
}

// fixup the size of a split grid.
function resizeSplitGrid(topicId, topicIsBottom)
{
	var containerID = document.getElementById('splitGridContainer_' + topicId);
	if (containerID != null)
	{
		// if this does not exist, we have no data, so only manipulate the elements if they're there!
		var rootSubsetID = document.getElementById('rootSubsetID_' + topicId);
		if (rootSubsetID)
		{
			// these hidden elements will have been generated
			// master is the variable grid on which the scroll bars appear, slave is the fixed grid that scrolls in sync
			var masterID = document.getElementById('splitGridScrollingRows_' + topicId);
			var masterHeaderID = document.getElementById('splitGridScrollingHeader_' + topicId);
			var slaveID = document.getElementById('splitGridFixedRows_' + topicId);
			var slaveHeaderID = document.getElementById('splitGridFixedHeader_' + topicId);
			var controlsID = document.getElementById('splitGridControls_' + topicId);
			var containerDiv = document.getElementById(containerID.value);
			var masterDiv = document.getElementById(masterID.value);
			var masterHeaderDiv = document.getElementById(masterHeaderID.value);
			var targetDiv = document.getElementById(slaveID.value);
			var targetHeaderDiv = document.getElementById(slaveHeaderID.value);
			var controlsDiv = document.getElementById(controlsID.value);
			var table1ID = rootSubsetID.value + "_" + slaveID.value;
			var table2ID = rootSubsetID.value + "_" + masterID.value;
			adjustRowHeights(table1ID, table2ID);
			var headerHeight = adjustHeaderHeights(masterHeaderID.value, slaveHeaderID.value, headerHeight);
			var needFixedHeightAdjustment = setSplitGridWidths(masterDiv, masterHeaderDiv, targetDiv, targetHeaderDiv, table2ID);
			setSplitGridHeights(topicId, topicIsBottom, containerDiv, targetHeaderDiv, targetDiv, masterHeaderDiv, masterDiv, controlsDiv, needFixedHeightAdjustment, headerHeight);
		}
	}
}

// Resize the divs generated within this topic
function resizeDivsForThisTopic(topicId, topicIsBottom)
{   
	// currently we do not support resizing z-topics.  They are what they are.
	if (!topicIsBottom) return false;
	
	var adjustHeaderForScroll = false;
	var numResizableDIVs = document.getElementById("numResizableDIVs_" + topicId);
	if (numResizableDIVs != null && numResizableDIVs.value != 0)
	{
		var leftOverHeight = getDynamicHeight(topicId, topicIsBottom);
		var resizableDivListString = document.getElementById("resizableDIVs_" + topicId).value;
		var sizePercentListString = document.getElementById("rdSizePercents_" + topicId).value;
		if (numResizableDIVs.value > 1)
		{
			var resizableDivNameArray = resizableDivListString.split(',');
			var sizePercentListArray = sizePercentListString.split(',');
			for (var i=0; i < resizableDivNameArray.length; i++)
			{
				var div = document.getElementById(resizableDivNameArray[i]);
				var newHeight = leftOverHeight;
				if (div != null)
				{
					var sizePercent = sizePercentListArray[i];
					if (sizePercent.length > 0 && sizePercent != '*' && sizePercent != '100%')
					{
						if (sizePercent.indexOf('%') > 0)
						{
							sizePercent = sizePercent.substring(0,sizePercent.length-1);
						}
						
						newHeight = leftOverHeight * (sizePercent / 100);
					}
				}
				if (newHeight > 0)
				{
					// alert("applying new height of "+newHeight+" to div "+div.id);
					div.style.height = newHeight;
				}
			}
		}
		else
		{
			var divName = resizableDivListString;	// only a single div
			if (divName.length > 0)
			{
				var div = document.getElementById(divName);
				if (leftOverHeight > 0) 
				{
					// alert("applying new height of "+leftOverHeight+" to div "+div.id);
					div.style.height = leftOverHeight;
			   		adjustHeaderForScroll = true; 
				}
			}
		}
	}
	return adjustHeaderForScroll;
}

// return the nav menu div or null if not found
function getNavMenuDiv()
{
	// use master page mangled name
	var	navMenuDiv = document.getElementById('ctl00_navMenu');
	return navMenuDiv;
}

// changes the height of the dynamically resizeable divs (there always is one when this is called).
function resizeDivs()
{
	var myForm = GetCBORDForm();
	if (myForm != null)
	{
		if (myForm.elements != null)
		{
			// now deal with all the rest of the work. So far, these are mutually exclusive, but the future might be different.
			// must do this for each topic in the Z-list
			var ZorderTopicIds = document.getElementById('zOrderIds');
			if (ZorderTopicIds == null)
			{
				// no topics displayed (could be report or other layout), nothing to do, get out now
				return;
			}
			
			var adjustHeaderForScroll = false;
			var ZorderTopicArray = ZorderTopicIds.value.split(',');
			var topicIsBottom = true;
			for (var topicIndex=0; topicIndex < ZorderTopicArray.length; topicIndex++)
			{
				var topicId = ZorderTopicArray[topicIndex];
				
				// handle split grids first, so it can be factored into the current "height"
				resizeSplitGrid(topicId, topicIsBottom);
				
				// now resize the other divs
				if (resizeDivsForThisTopic(topicId, topicIsBottom))
				{
					adjustHeaderForScroll = true;
				}
				topicIsBottom = false;
			}
			
			// set the height of the nav menu to be what's available
			var navMenuDiv = getNavMenuDiv();
			if (navMenuDiv != null)
			{
				var navMenuHeight = document.body.clientHeight - getBannerOffset();
				if (navMenuHeight  > 0)
				{
					navMenuDiv.style.height = navMenuHeight;
				}
			}
			
			// set widths for grid columns
			if (myForm.length > 0)
			{
				for (var formElementIndex = 0; formElementIndex < myForm.length; formElementIndex++)
				{
					var myElement = myForm.elements[formElementIndex];
					// must deal with multiple grids, each with its own header widths.
					if (myElement.type == 'hidden' && IsSetHeaderWidthsID(myElement.id))
					{
						var gridSetValue = myElement.value;
						if (gridSetValue.length > 0)
						{
							var gridSetIDs = gridSetValue.split(',');
							setHeaderWidths(gridSetIDs[0], gridSetIDs[1], adjustHeaderForScroll);
						}
					}
				}
			}
		}
	}
}

function applyScrollBarAdjustment(headerTable, padding)
{
	var headerTableRow = headerTable.rows[0];
	//pad the grid header
	elemId = headerTable.id + "_pad";
	var elem = document.getElementById(elemId);
	if (elem != null) {
	    if (padding == 0) {
	        // remove it
	        headerTableRow.deleteCell(-1);
	    }
	    else {
	        elem.style.width = padding + "px";
	    }
	}
	else {
	    if (padding > 0) {
	        elem = headerTableRow.insertCell(-1);
	        elem.innerHTML = "&nbsp;";
	        elem.id = elemId;
	        elem.style.width = padding + "px";
	    }
	}
}

// return the height of the banner
function getBannerOffset()
{
	var bannerHeight = 0;
	var banner = document.getElementById("t0NavBanner");	// id is always 'owned' by the 0th topic; watch the generated name
	if (banner != null)
	{
		bannerHeight = banner.clientHeight;
	}
	return bannerHeight;
}

// return the leftover height after removing all the known div heights and the banner height (for the given topic)
function getDynamicHeight(topicId, topicIsBottom)
{
	var leftOverHeight;
	if (topicIsBottom)
	{
		leftOverHeight = document.body.clientHeight;
	}
	else
	{
	// WARNING: this code does not work
		var topicDiv = document.getElementById(topicId);
		if (topicDiv != null)
		{
			var bottomPad = convertPxToInteger(topicDiv.style.paddingBottom);
			var topPad = convertPxToInteger(topicDiv.style.paddingTop);
			leftOverHeight = topicDiv.clientHeight  - (bottomPad + topPad);
		}
		else
		{
			leftOverHeight = document.body.clientHeight;
		}
	}
	
	var numNonAbsoluteDIVs = document.getElementById('numNonAbsoluteDIVs_' + topicId);
	if (numNonAbsoluteDIVs != null && numNonAbsoluteDIVs.value > 0)
	{
		var nonAbsoluteDivListString = document.getElementById('nonAbsoluteDIVs_' + topicId).value;
		var nADArray = nonAbsoluteDivListString.split(',');
		var totalFixedHeight = 0;
		var debug = "calc: " + leftOverHeight;
		for (var i=0; i < nADArray.length; i++)
		{
			var divElem = document.getElementById(nADArray[i]);
			if (divElem != null)
			{
				var divHeight = getDivHeight(divElem);
				totalFixedHeight += divHeight;
				debug += " (" + divElem.id + ")"+divHeight;
			}
			else
			{
				reportClientException("Cannot find DIV element with name "+nADArray[i], "getDynamicHeight");
			}
		}
		// add in a fudge factor: for bottom topics (where did this come from?) and 26 for z-topics (based on experiments to get something working)
		// fudge factors seem to be related to margins, padding, and borders
		// WARNING: code for z-topics does not work
		var fudgeFactor = 6;
		if (topicIsBottom)
		{
			if (browser == "Netscape")
			{
				fudgeFactor = 10;	// seems to work for 14pt or lower fonts
			}
			else
			{
				fudgeFactor = 20;	//
			}
		}
		totalFixedHeight += fudgeFactor;
		leftOverHeight -= totalFixedHeight;
		//alert("leftOverHeight="+leftOverHeight + " "+debug);
	}
	
	// only adjust bottom topic by the banner
	if (topicIsBottom)
	{
		var bannerOffsetHeight = getBannerOffset();
		leftOverHeight -= bannerOffsetHeight;
	}
	// alert("final: " + leftOverHeight + "; debug: " + debug);
        	return leftOverHeight;
}

// return the current height of the given div (to the best of our ability given wierd browser stuff)
function getDivHeight(divElem)
{
	var divHeight = 0;
	// note, IE will return some height even if no children, but Mozzila style browsers return 0 (which is correct)
	if (divElem.hasChildNodes())
	{
		divHeight = divElem.clientHeight;
		// trick because sometimes the client height lies (first div seems to be the culprit); in this case, the offset height looks correct
		if (divHeight == 0)
		{
			divHeight = divElem.offsetHeight;
		}
	}
	return divHeight;
}

// convert a string that is a style unit with "px" to an integer; assumes val is of the form NNpx.  Returns 0 for the empty string.
function convertPxToInteger(val)
{
	if (val.length > 0)
	{
		var position = val.indexOf("px")+1;
		return Number(val.substring(0, val.length-position));
	}
	return 0;
}

function setHeaderWidths(detailTableID, headerTableID, adjustHeaderForScroll)
{
	var detailTableRow = document.getElementById(detailTableID+"_headerRootRow");
	var headerTable = document.getElementById(headerTableID);
	// only adjust if head table and detail table both exist
	if (detailTableRow && headerTable && headerTable.rows && headerTable.rows.length > 0)
	{
		var headerTableRow = headerTable.rows[0];
		var totalDetailWidth = 0;
		var detailColCount = detailTableRow.cells.length;
			
		for (var i = 0; i < detailColCount; i++)
		{
			var detailCell = detailTableRow.cells[i];
			var headerCell = headerTableRow.cells[i];
			if (detailCell && headerCell)
			{
				var cellWidth = detailCell.clientWidth;
				var paddingLeft = detailCell.style.paddingLeft;
				var paddingRight = detailCell.style.paddingRight;
				var leftPad = convertPxToInteger(paddingLeft);
				var rightPad = convertPxToInteger(paddingRight);
				var cellAdjust = cellWidth + (leftPad + rightPad)/2;
				totalDetailWidth += cellAdjust;
			}
		}
	    
		if (adjustHeaderForScroll)
		{
			var diff = headerTable.clientWidth - totalDetailWidth;
			if (diff < 0)
			{
				diff = 0;
			}
			applyScrollBarAdjustment(headerTable, diff);
		}
	}
}

// split grid routines
function setSplitGridWidths(variableGridRows, variableGridHeader, fixedGrid, fixedGridHeader, table2ID)
{
	var contentsDiv = getContentsDiv();
	if (contentsDiv == null) return;
	
	//start figuring out how much space we have horizontally
	var scrollingWidth = document.body.clientWidth;
	var fixedWidth = fixedGrid.clientWidth;
	var cumulativeWidth = fixedWidth + contentsDiv.offsetLeft;
	scrollingWidth -= cumulativeWidth;
	if (browser == "Netscape")
	{
			// mozilla browsers need this extra width, because they do not render the vertical scroll bar invariantly
			// for the overall browser window, the way IE does.
			scrollingWidth += 16; 
	}
	scrollingHeaderWidth = scrollingWidth - 16
	// set the slave header div to the same width as the slave grid div
	fixedGridHeader.style.width = fixedWidth + "px";
	// set the master header div to the remaining width
	variableGridHeader.style.width=scrollingHeaderWidth + "px";
	// set the master header div's left side against the slave div
	variableGridHeader.style.left = fixedWidth + "px";
	// do the same thing for the master grid div
	variableGridRows.style.width=scrollingWidth + "px";
	variableGridRows.style.left = fixedWidth + "px";
	// now, if we have a wider table than the master grid will hold, we'll
	// end up with a horizontal scroll bar, and we need to know that when
	// we go to set up the slave grid's height, so send back an indicator
	var table2 = document.getElementById(table2ID);
	var needFixedHeightAdjustment = false;
	if (table2.clientWidth > variableGridRows.clientWidth)
	{
		needFixedHeightAdjustment = true;
	}
	return needFixedHeightAdjustment;
}

function setSplitGridHeights(topicId, topicIsBottom, containerDiv, fixedGridHeader, fixedGridRows, variableGridHeader, variableGridRows, controlsDiv, needFixedHeightAdjustment, headerHeight)
{
	// get the current "overhead" height - this is the cumulative height of the fixed divs above us
	var leftOverHeight = getDynamicHeight(topicId, topicIsBottom);
	// subtract the amount necessary to compensate for the additional height incurred by the horizontal scroll bar
	var mainPanelHeight = leftOverHeight -  12;
	// set the containing div for the split divs to this
	containerDiv.style.height = mainPanelHeight + "px";
	// now subtract the split grid header to get the height of the grids themselves
	var varGridRowsHeight = mainPanelHeight  -  (fixedGridHeader.clientHeight + controlsDiv.clientHeight);
	// the fixed (slave) grid needs to be shorter, because it will not have the scroll bar at the bottom
	var fixedGridRowsHeight = varGridRowsHeight;
	if (needFixedHeightAdjustment)
	{
		fixedGridRowsHeight -= 16;
	}
	// now set the top and height style values for the two grids
	fixedGridHeader.style.top = controlsDiv.clientHeight;
	fixedGridHeader.style.height = headerHeight;
	variableGridHeader.style.top = controlsDiv.clientHeight;
	variableGridHeader.style.height = headerHeight;
	var detailTop = fixedGridHeader.clientHeight + controlsDiv.clientHeight;
	variableGridRows.style.top = detailTop + "px";
	variableGridRows.style.height = varGridRowsHeight + "px";
	fixedGridRows.style.top = detailTop + "px";
	fixedGridRows.style.height = fixedGridRowsHeight + "px";
}

function adjustHeaderHeights(sourceID, targetID) {
    // NOTE: the cell with the "_pad" has been removed; find another way to do this
	var sourceHeaderCell = document.getElementById("hdr_"+sourceID+"_pad");
	var targetHeaderCell = document.getElementById("hdr_"+targetID+"_pad");
	if (sourceHeaderCell && targetHeaderCell) {
	    if (sourceHeaderCell.clientHeight != targetHeaderCell.clientHeight) {
	        if (sourceHeaderCell.clientHeight > targetHeaderCell.clientHeight) {
	            targetHeaderCell.style.height = sourceHeaderCell.clientHeight + "px";
	            return sourceHeaderCell.clientHeight;
	        }
	        else {
	            sourceHeaderCell.style.height = targetHeaderCell.clientHeight + "px";
	            return targetHeaderCell.clientHeight;
	        }
	    }
	    else {
	        return sourceHeaderCell.clientHeight;
	    }
	}
}

function adjustRowHeights(table1Id, table2Id)
{
	var table1 = document.getElementById(table1Id);
	var table2 = document.getElementById(table2Id);
	var table1RowCount = table1.rows.length;
	var table2RowCount = table2.rows.length;
	if (table1RowCount != table2RowCount)
	{
		// this situation is theoretically unreachable, if the two grids are looking at the same buffer
		alert("fixed and scrolling table row counts are not the same: "+table1RowCount+" != '"+table2RowCount);
	}
	for (var i = 0; i < table1RowCount; i++)
	{
		var table1Row = table1.rows[i];
		var table2Row = table2.rows[i];
		var table1RowHeight = table1Row.clientHeight;
		var table2RowHeight = table2Row.clientHeight;

		if (table1RowHeight != table2RowHeight)
		{
			if (table1RowHeight > table2RowHeight)
			{
				table2Row.style.height = table1RowHeight + "px";
			}
			else
			{
				table1Row.style.height = table2RowHeight + "px";
			}
		}
	}
}

function scroll(source, horizontalTargetId, verticalTargetId)
{
	var sourceDiv = document.getElementById(source);
	var horizontalTargetDiv = document.getElementById(horizontalTargetId);
	var verticalTargetDiv = document.getElementById(verticalTargetId);
	var sourceX = sourceDiv.scrollLeft;
	var sourceY = sourceDiv.scrollTop;
	horizontalTargetDiv.scrollLeft = sourceX;
	verticalTargetDiv.scrollTop = sourceY;
}
// end split grid routines

// Find the main submit form: currently there can be only 1 (except for NN), so return the first; 
// if this changes then Master Page stuff gets in the way as it renames the form.  Might have to add global var w/ name of CBORD form
function GetCBORDForm()
{
	var myForm = null;
	if (document.forms.length == 1)
	{
		myForm = document.forms[0];
	}
	else if (document.forms.length > 0)
	{
		myForm = document.forms['CBORD'];
	}
	return myForm;
}

// find the main contents div
function getContentsDiv()
{
	var contentsDiv = document.getElementById("ctl00_MainContent_contents"); // this is an invariant coded into the page (and mangled by the master page)
	if (contentsDiv == null)
	{
		contentsDiv = document.getElementById("contents");			// some apps do not use master pages
	}
	return contentsDiv;
}

// generate an alert given a message index
function generateAlert(msgId)
{
	// convert non-numeric message id's
	if (typeof(msgId) == "string")
	{
		// convert message id into numeric index
	    msgId = eval(msgId);
	}
	var msg = messages[msgId];
	alert(msg);
}

// process a page link navigation menu click
function processRedirect(where)
{
	try {
	    // must include multi-session key if not already there
	    if (where.indexOf("_msk=") == -1) {
	        var msk = extractMultiSessionKey();
	        if (msk != null) {
	            if (where.indexOf("?") == -1) {
	                where += "?";
	            }
	            else {
	                where += "&";
	            }
	            where += "_msk=" + msk;
	        }
	    }
		window.location = where;
	}
	catch(e)
	{
		reportClientException(e, "processRedirect");
	}
}

// process a hierarchical navigation menu click
// Note: this must be a 'void' function, no return value as it is sometimes used in an <a> tag setting.
function processSelection(id)
{
	try
	{
		var checkType = "RequiredCheck";	//OnlyRequiredCheck is the default behaviour
		if (arguments.length > 1)
		{
			checkType = arguments[1];
		}
		
		if (checkType == null || checkType == "")
		{
			checkType = "RequiredCheck";
		}
		
		var msg = "";
		var processForm = GetCBORDForm();
		if (processForm != null && processForm.elements != null)
		{
			setTargetTopic(id);
			
			switch (checkType)
			{
				case "NoCheck":
					break;
				case "AllCheck":
					msg = checkModifiedNoSubmit(processForm);			
					break;	
				case "RequiredCheck":						
					msg = checkRequiredNoSubmit();	
					break;
				case "default":
					msg = "Incorrect check type specified in processSelection";
					break;	
			}
			
			if (msg != null && msg.length > 0)
			{
				alert(msg);
			}
			else
			{
				processAction("NavigationAction");
			}		
		}
	}
	catch(e)
	{
		reportClientException(e, "processSelection");
	}
}

// process a +/- click in a TreeSelect control 
// args:
//		control: id of the control that had the event (aka like a 'this')
//		menuItemId: the id of the menu item that had a +/- clicked
//		topicId: the id of the owning topic
//		panelId: the id of the panel to render (in the callback response)
function processTreeSelectorEvent(controlId, menuItemId, topicId, panelId)
{
	try
	{
		var panel = document.getElementById(panelId);
		if (panel != null)
		{
			var eventHandler = MakeEventHandler();
			// create post data
			var idValuePairs = new Object();
			// add the "topicAction" to the list of items to send back
			idValuePairs["controlID"] = controlId;
			idValuePairs["menuItemId"] = menuItemId;
			idValuePairs["topicId"] = topicId;
			idValuePairs["renderPanelId"] = panelId;
			idValuePairs["topScrollStr"] = panel.scrollTop;
			idValuePairs["leftScrollStr"] = panel.scrollLeft;
			// call the server event handler
			eventHandler.call("TreeSelectorAction", idValuePairs, renderControlEventCB);
		}
	}
	catch(e)
	{
		reportClientException(e, "processTreeEvent");
	}
}

// call back function for handling a response to a control event that needs to be re-rendered
function renderControlEventCB(responseNode) {
    // allow the rendering to do multiple divs
    var htmlNodes = responseNode.selectNodes("HTML");
    for (var index = 0; index < htmlNodes.length; index++) {
        var htmlNode = htmlNodes[index];
        if (htmlNode != null)
        {
        	var id = htmlNode.getAttribute("id");
        	var html = htmlNode.firstChild.data;
        	var myElement = document.getElementById(id);
        	if (myElement != null)
        	{
        		myElement.innerHTML = html;
        	}
        }        
    }

    // execute any script too
	var oScript = responseNode.selectSingleNode("Script");
	if (oScript != null)
	{
	    var script = oScript.firstChild.data;
		eval(script);
    }

}

// render the given HTML blocks and evaluate the script block in a response; then call the onDoneLoading function.
function renderControlEventWithOnDoneCB(responseNode) {
    renderControlEventCB(responseNode);
    // call our on-done-loading function
    onDoneLoading();
}

// process a 'select' event in a TreeSelect control
// action: copy the selection to the hidden element for this control so that when
// a 'submit' happens all will be well
function processTreeSelection(id, treeSelectionId, selectAction)
{
	try
	{
		var myElement = document.getElementById(treeSelectionId);
		if (myElement != null)
		{
			myElement.value = id;
			processAction(selectAction);
		}
	}
	catch(e)
	{
		reportClientException(e, "processTreeSelection");
	}
}

function setTreeSelectorToAll(elementId)
{
	try
	{
		var myElement = document.getElementById(elementId);
		if (myElement != null)
		{
			myElement.value = "-2";
		}
	}
	catch(e)
	{
		reportClientException(e, "setTreeSelectorToAll");
	}	
}

// process navigation menu +/- event.
function processMenuEvent(src)
{
	try
	{
		var eventHandler = MakeEventHandler();
		// create post data
		var idValuePairs = new Object();
		// add the "topicAction" to the list of items to send back
		idValuePairs["menuClick"] = src;
		// call the server event handler
		eventHandler.call("ProcessNavMenuClick", idValuePairs, renderControlEventCB);
	}
	catch(e)
	{
		reportClientException(e, "processMenuEvent");
	}
}

// process grid header checkbox toggle event.
function gridHeaderCheckBoxOnclick(topicId, gridName, columnXpath) {
    try {
        var eventHandler = MakeEventHandler();
        // create post data
        var idValuePairs = new Object();
        idValuePairs["topicId"] = topicId;
        idValuePairs["gridName"] = gridName;
        idValuePairs["columnXpath"] = columnXpath;
        // add changed data for sync
        addChangedData(GetCBORDForm(), idValuePairs);

        // call the server event handler (make sure we call the on-done function after this so grids resize properly)
        eventHandler.call("ToggleGridSelectionsEvent", idValuePairs, renderControlEventWithOnDoneCB);
    }
    catch (e) {
        reportClientException(e, "gridHeaderCheckBoxOnclick");
    }
}

// process grid group show/hide event.
function toggleGridGroupVisibility(showGroup, subSet, topicId, gridName, groupByValueList) {
    try {
        var eventHandler = MakeEventHandler();
        // create post data
        var idValuePairs = new Object();
        idValuePairs["showGroup"] = showGroup;
        idValuePairs["subSet"] = subSet;
        idValuePairs["topicId"] = topicId;
        idValuePairs["gridName"] = gridName;
        idValuePairs["groupByValueList"] = encodeURIComponent(groupByValueList);   // must encode here as this could be user-edited data (so it might have a '&')

        // add changed data for sync
        addChangedData(GetCBORDForm(), idValuePairs);
        
        // call the server event handler (make sure we call the on-done function after this so grids resize properly)
        eventHandler.call("ToggleGridGroupEvent", idValuePairs, renderControlEventWithOnDoneCB);
    }
    catch (e) {
        reportClientException(e, "toggleGridGroupVisibility");
    }
}

// process grid group toggle checkbox event.
function toggleGridGroupCheckBox(selectAttr, subSet, topicId, gridName, groupByValueList) {
    try {
        var eventHandler = MakeEventHandler();
        // create post data
        var idValuePairs = new Object();
        idValuePairs["selectAttr"] = selectAttr;
        idValuePairs["subSet"] = subSet;
        idValuePairs["topicId"] = topicId;
        idValuePairs["gridName"] = gridName;
        idValuePairs["groupByValueList"] = encodeURIComponent(groupByValueList);   // must encode here as this could be user-edited data (so it might have a '&')
        // add changed data for sync
        addChangedData(GetCBORDForm(), idValuePairs);

        // call the server event handler (make sure we call the on-done function after this so grids resize properly)
        eventHandler.call("ToggleGridCheckBoxEvent", idValuePairs, renderControlEventWithOnDoneCB);
    }
    catch (e) {
        reportClientException(e, "toggleGridGroupVisibility");
    }
}

//process a hierarchial navigation menu click w/o checking for modified elements.
function processSelectionNoModDataCheck(id)
{
	try
	{
		var ttElem = setTargetTopic(id);
		if (ttElem != null)
		{
			checkRequiredBeforeAction("NavigationAction");
		}
	}
	catch(e)
	{
		reportClientException(e, "processSelectionNoDataCheck");
	}
}

// set the value of the given element to be empty.  Do nothing if element does not exist.
function clearElementValue(elemName)
{
	var elem = document.getElementById(elemName);
	if (elem)
	{
		elem.value = '';
	}
}

// Set the value of the element 'setSelectedTarget'; this element is generated by the main rendering.
function setSelectedTarget(target)
{
	var ssTargetElem = document.getElementById("setSelectedTarget");
	if (ssTargetElem != null)
	{
		ssTargetElem.value = target;
	}
	return ssTargetElem;
}

//procss an anchor tag click to direct to a process and set the parm value
function processActionWithParm(action, parm)
{
	try
	{
		var setSelTargetElem = setSelectedTarget(parm);
		if (setSelTargetElem != null)
		{
			processAction(action);			
		}
	}
	catch (e)
	{
		reportClientException(e, "processActionWithParm");
	}
}

// process an anchor tag click to direct to a topic and set the parm value
function processALink(topicId, parm)
{
	try
	{
		var setSelTargetElem = setSelectedTarget(parm);
		if (setSelTargetElem != null)
		{
			processSelectionNoModDataCheck(topicId);			
		}
	}
	catch (e)
	{
		reportClientException(e, "processALink");
	}
}

// process a button click on the nav bar (TODO: make this the same as others, if possible)
function checkRequiredBeforeActionFromNavBar(action)
{
	try
	{
		// call the checkrequired routine
		var alertMessage = checkRequiredInSubmitForm();
		// if all is ok, just process this button normally (this uses the proper topic, which is not the same as the myForm here)
		if (alertMessage.length == 0)
		{
			processAction(action);
		}
		else
		{
			alert(alertMessage);
		}
	}
	catch(e)
	{
		reportClientException(e, "checkRequiredBeforeActionFromNavBar");
	}
}

// process an action request, but check for any modified data on the form first
// NOTE: this function is not yet used (look for 'AllCheck')
function checkModifiedNoSubmit(myForm)
{
	try
	{
		var isDirty = false;
		var returnStatus = "";
		
		//check if any required element is modified
		returnStatus = checkRequiredNoSubmit();
		if (returnStatus.Length > 0)
		{
			return returnStatus;
		}
		
		//check if any data on the form has changed
		isDirty = isDirtyNoSubmit(myForm);
				
//      Current behavior does not check buffer modification. Should it?
//		//check if any buffer data has been marked as dirty (this element is not implmented!)
//		if (!isDirty)
//		{
//			var isDataModifiedInBuffer = document.getElementById("IsDataModified");
//			if (isDataModifiedInBuffer != null && isDataModifiedInBuffer.value == '1')
//			{
//				isDirty = true;
//				//alert("buffer dirty");
//			}
//		}
		
		if (isDirty)
		{
			// TODO: use proper message
			returnStatus = "Please Save or Cancel your data before exiting";
		}
		return returnStatus;
	}
	catch(e)
	{
		reportClientException(e, "checkModifiedNoSubmit");
	}
}

// process an action request, but check for required elements first
function checkRequiredBeforeAction(action)
{
	try
	{
		var alertMessage = checkRequiredInSubmitForm();	
		if (alertMessage.length == 0)
		{
			processAction(action);
		}
		else
		{
			alert(alertMessage);
		}
	}
	catch(e)
	{
		reportClientException(e, "checkRequiredBeforeAction");
	}
}

// Check for required elements in the submit form.
function checkRequiredNoSubmit()
{
	try
	{
		var alertMessage = checkRequiredInSubmitForm();
		return alertMessage;
	}
	catch(e)
	{
		reportClientException(e, "CheckRequiredNoSubmit");
	}
}

// internal function to do the work of checking for required values
// the checking includes required values on this submit form.
// returns either a detailed message (something is missing) or the empty string (all ok)
function checkRequiredInSubmitForm()
{
	var alertMessage = "";
	var numRequired = 0;
	
	var numRequiredElem = document.getElementById("numRequired");
	if (numRequiredElem != null)
	{
		numRequired = numRequiredElem.value;
	}
	
	// if there are required fields, check them
	if (numRequired > 0)
	{
		var reqIDsValue = document.getElementById("requiredIDs").value;
		var labelsValue = document.getElementById("requiredLabels").value;
		var reqIDs = reqIDsValue.split(",");
		var labels = labelsValue.split(",");
		
		for (var i=0; i<numRequired; i++)
		{
			var curId = reqIDs[i];
			var reqElement = document.getElementById(curId);
			// element may not be rendered (empty data or some such), don't panic, just ignore it
			if (reqElement != null)
			{
				// get current value based on element type (ignore all non-input elements)
				var reqValue = null;
				if (reqElement.type == 'text' || reqElement.type == 'textarea' || reqElement.type == 'password')
				{
					// trim trailing spaces for required values (biz logic will do this on retrieval!)
					// this will enforce the condition that a required value must be non-blank
					reqValue = trimTrailingChars(reqElement.value, ' ');
				}
				else if (reqElement.type == 'select-one')
				{
					reqValue = reqElement.value;
				}
				
				// for multi-select values (2 selection chooser UI), use special check: make sure the inner HTML is non-empty (exclude newlines)
				else if (reqElement.type=='select-multiple')
				{
				    reqValue = trimTrailingChars(reqElement.innerHTML, '\n');
				}
				
				// we may not have rendered an "input" because of read-only stuff, so don't check if the value is null
				if (reqValue != null)
				{
					// if required value is not there, ERROR
					// note, for single-select listbox, -100 corresponds to 'select a value' option which means there is no selection yet.
					if (reqValue.length == 0 || (reqElement.type == 'select-one' && reqValue == -100 ))
					{
						// if this is the first time, setup the alert message, otherwise, add a comma
						if (alertMessage.length == 0)
						{
							alertMessage = messages[mJSRequiredValueMissing];
						}
						else
						{
							alertMessage += ", ";
						}
						alertMessage += labels[i];
					}
				}
			}
		}
	}
	return alertMessage;
}

function processDDMenuActionWithConfirm(selectElem, msg)
{
	try
	{
		// Invariant: the value of the selected dropdown list entry is the action
		var action = selectElem.value;
		if (action.length > 0)
		{
			var alertMessage = messages[msg];
			if (confirm(alertMessage))
			{
				var temp = action.split(",");
				var strings  = temp.length;
				if (temp.length == 1)
				{
					checkRequiredBeforeActionFromNavBar(action);
				}
				else
				{
					processSelection(temp[0]);
				}
			}
			else
			{
				// user has cancelled, so set the list box back to the 0 index.
				selectElem.selectedIndex=0;
			}
		}
	}
	catch(e)
	{
		reportClientException(e, "processDDMenuAction");
	}
}
// process a drop down menu action
function processDDMenuAction(selectElem)
{
	try
	{
		// Invariant: the value of the selected dropdown list entry is the action
		var action = selectElem.value;
		if (action.length > 0)
		{
			var temp = action.split(",");
			var strings = temp.length;
			if (temp.length == 1)
			{
				checkRequiredBeforeActionFromNavBar(action);
			}
			else
			{
				processSelection(temp[0]);
			}
		}
	}
	catch(e)
	{
		reportClientException(e, "processDDMenuAction");
	}
}

// process an action in an XmlHttpRequest submitting frame.
function processAction(action)
{
	try
	{
		var myForm = GetCBORDForm();
		if (myForm != null)
		{
			internalProcessAction(myForm, action);
		}
	}
	catch(e)
	{
		reportClientException(e, "processAction");
	}
}

// confirm and then process acton or do nothing
function confirmAction(action, msg)
{
    // if a server request is already in progress just return with wait message. 
	if (ActionHandler.isCallInProgress())
	{
		var waitMsg = messages[mJSPleaseWait];
		alert(waitMsg);
		return;		// do not submit twice
}
	
	if (confirm(msg))
	{
	    ActionHandler._endSubmit();
		processAction(action);
	}
}

// confirm and then process acton or do nothing
function confirmOrDenyAction(action, denyAction, msg)
{
    // if a server request is already in progress just return with wait message. 
	if (ActionHandler.isCallInProgress())
	{
		var waitMsg = messages[mJSPleaseWait];
		alert(waitMsg);
		return;		// do not submit twice
	} 
	if (confirm(msg))
	{
	    ActionHandler._endSubmit();
		processAction(action);
	}
	else
	{
	    ActionHandler._endSubmit();
	    processAction(denyAction);
	}
}

// confirm and then process acton or do nothing
function confirmActionIfModified(action, msg)
{
    // if a server request is already in progress just return with wait message. 
	if (ActionHandler.isCallInProgress())
	{
		var waitMsg = messages[mJSPleaseWait];
		alert(waitMsg);
		return;		// do not submit twice
}
	
	var myForm = GetCBORDForm();
	var isDirty = isDirtyNoSubmit(myForm);
	
	//check if any buffer data has been marked as dirty (this element is not implmented!)
	if (!isDirty)
	{
		var isDataModifiedInBuffer = document.getElementById("IsBufferModified");
		if (isDataModifiedInBuffer != null && isDataModifiedInBuffer.value == '1')
		{
			isDirty = true;
			//alert("buffer dirty");
		}
	}

    if (isDirty)
    {
	    if (confirm(msg))
	    {
	        ActionHandler._endSubmit();
		    processAction(action);
	    }
	}
	else
	{
	    processAction(action);
	}
}

// Internal function to set action in page, change status and submit the given form (do not call from HTML!)
function internalProcessAction(processForm, action)
{
	try
	{
		if (processForm.elements)
		{
			// keep track that a submit was done, and don't allow another one
			if (ActionHandler.isCallInProgress())
			{
				var msg = messages[mJSPleaseWait];
				alert(msg);
				return;		// do not submit twice
			}
					
			// send Div scroll positions to server
			sendDivsThatScrolled();
			
			// scrape the form for changes
			var idValuePairs = getChangedData(processForm);

			// do the postback call if optional validation succeeded
			if (idValuePairs != null)
			{
				// add the "topicAction" to the list of items to send back
				idValuePairs["topicAction"] = action;
				ActionHandler.call("PostbackAction", idValuePairs, processActionResponseHandler, "", "xml");
			}
		}
		else
		{
			alert("No elements in form");
		}
	}
	catch(e)
	{
		reportClientException(e, "internalProcessAction");
	}
}

// call back to process a response from a PostBack Action
function processActionResponseHandler(responseNode)
{
	// render new page elements (if present)
	var htmlChunkNode = responseNode.selectSingleNode("HTMLChunks");
	if (htmlChunkNode != null)
	{
		var htmlNodeList = htmlChunkNode.childNodes;
		for (var i=0; i<htmlNodeList.length; i++)
		{
			var htmlNode = htmlNodeList[i];
			var id = htmlNode.getAttribute("id");
			var html = htmlNode.firstChild.data;
			var myElement = document.getElementById(id);
			if (myElement != null)
			{
				myElement.innerHTML = html;
			}
			else
			{
				// time to add a new div
				createNewDiv(id, html);
			}
		}
	}
	
	// evaluate script (if present)
	var oScript = responseNode.selectSingleNode("Script");
	if (oScript != null)
	{
	    var script = oScript.firstChild.data;
	    eval(script);
	}
	// call our on-done-loading function
	onDoneLoading();
}

// helper function to put the passed value into the title tag
function setTitleTag(id)
{
	var titleTag = document.getElementsByTagName("title");
	titleTag[0].text = id;
	titleTag[0].normalize();
}

// helper function to create a div element with the given id and contents
function createNewDiv(id, contents)
{
	var myForm = GetCBORDForm();
	var newDiv = myForm.appendChild(document.createElement('div'));
	newDiv.id = id;
	newDiv.innerHTML = contents;
	return newDiv;
}

// helper function to create a div element with the given id and contents
function createDivIfNeeded(id, contents)
{
    var divElem = document.getElementById(id);
    if (divElem != null)
    {
        divElem.innerHTML = contents;
    }
    else
    {
        divElem = createNewDiv(id, contents);
    }

	return divElem;
}

// onUnloadPage event handler: check for data changes
function processExitPage(evt)
{
	try
	{
		var myForm = GetCBORDForm();
		if (myForm != null)
		{
			if (myForm.elements != null)
			{	
				if (ActionHandler.isCallInProgress())
				{
					return; 
				}
				
				// send Div scroll positions to server
				// While a good idea, this does not seem to work consistently (the XmlHttpRequest object gets a status of 0 and that is bad)
				// sendDivsThatScrolled();
			
				// on exit of page, synch any data changes
				if (isDirtyNoSubmit(myForm))
				{
					// TODO: use proper message
				    return "Unsaved data on page.  Continuing will result in data loss.";
				}
			}
			else
			{
				// myForm.elements is null - should never get here...
				alert("CBORD error:  elements is null in processExitPage. "+contactCBORDMsg);
			}
		}
		else
		{
			// myForm is null - could be this page is from report.aspx or some other non-data page (but still has our event handler, which is ok)
			// just ignore 
		}
	}
	catch(e)
	{
		// reportClientException(e, "processExitPage");
		// not safe to call reportClientException from here...
		alert("Error during processExitPage: "+e+contactCBORDMsg);
	}
}

// helper function for isDirty for a select (single or multi)
function IsSelectDirty(myElement)
{
	var isDirty = false;
	// for a list box to be dirty, the option must change AND it must have some defaultSelected value
	var selectHasDefault = false;
	for (var j=0; j<myElement.length; j++)
	{
	    var myOption=myElement.options[j];
		if (myOption.defaultSelected)
		{
			selectHasDefault = true;
			break;
		}
	}
	// only check if there is a default selected
	if (selectHasDefault)
	{
		for (var j=0; j<myElement.length; j++)
		{
			var myOption=myElement.options[j];
			if(myOption.defaultSelected != myOption.selected)
			{
				isDirty = true;
				break;
			}
		}
    }
    return isDirty;
}

// return true if the given form contains changed (aka dirty) data.
function isDirtyNoSubmit(myForm)
{
	try
	{
		var isDirty = false;
		var formIndex = 0;
		while (formIndex < myForm.length && !isDirty)
		{
			var myElement = myForm.elements[formIndex];
			if (myElement.id.length > 0 || myElement.name.length > 0)	// need name here for radio buttons
			{
				switch (myElement.type)
				{
					case 'radio':
					case 'checkbox':
						if(myElement.defaultChecked != myElement.checked)
						{
							isDirty = true;
						}
						break;
					
					case 'select-one':
					case 'select-multiple':
						if (IsSelectDirty(myElement))
						{
							isDirty = true;
						}
						break;
					
					case 'text':
					case 'textarea':
					case 'password':
						if (myElement.value != myElement.defaultValue)
						{
							isDirty = true;
						}
						break;
			    }
			}
			formIndex++;
		}
		return isDirty;			
	}
	catch(e)
	{
		reportClientException(e, "IsDirtyNoSubmit");
	}
}

// return id/value pairs of changed data for sending back to server.
// format = id=value& (where & is a seperator, not terminator)
function getChangedData(myForm)
{
	try
	{
		var idValuePairs = new Object();
		addChangedData(myForm, idValuePairs);		
		return idValuePairs;
	}
	catch(e)
	{
		reportClientException(e, "getChangedData");
	}
}

// Add any changed data elements to the array of id/value pairs.
function addChangedData(myForm, idValuePairs) {

    for (var i = 0; i < myForm.length; i++) {
        var myElement = myForm.elements[i];
        switch (myElement.type) {
            case 'radio':
                if (myElement.defaultChecked != myElement.checked && myElement.checked) {
                    // radio buttons should have a name attribute (as that is what makes them part of the same group); use id if name is not present
                    var rbName = myElement.name ? myElement.name : myElement.id;
                    idValuePairs[rbName] = myElement.value; 	// this is critical for proper synch of a Radio button; it should use the name! and the value of the input control is what is returned (for the checked radio button ONLY)
                }
                break;

            case 'checkbox':
                if (myElement.defaultChecked != myElement.checked) {
                    idValuePairs[myElement.id] = myElement.checked;
                }
                break;

            case 'select-one':
                for (var j = 0; j < myElement.length; j++) {
                    var myOption = myElement.options[j];
                    if (myOption.defaultSelected != myOption.selected && myOption.selected) {
                        idValuePairs[myElement.id] = encodeURIComponent(myOption.value); 	// must encode values because of "special" characters that may be part of the 'value' field of a LB
                        break; 	// out of for loop
                    }
                }
                break;

            case 'select-multiple':
                {
                    var selectedValues = "";
                    for (var j = 0; j < myElement.length; j++) {
                        var myOption = myElement.options[j];
                        if (myOption.selected) {
                            selectedValues += myOption.value + ",";
                        }
                    }
                    if (selectedValues.length > 0) {
                        idValuePairs[myElement.id] = encodeURIComponent(selectedValues.substr(0, selectedValues.length - 1)); 	// must encode values because of "special" characters that may be typed into text boxes
                    }
                }
                break;

            case 'text':
            case 'textarea':
            case 'password':
                var isDefault = myElement.attributes["isDefault"];
                if (isDefault && isDefault.value == "true") 
                {
                    myElement.value = "";
                }
                if (myElement.value != myElement.defaultValue) {
                    try {

                        var validate = myElement.attributes["validate"]; // attribute name must match the one used in HTMLUIControl.cs
                        if (validate) {
                            var validationResponse = eval(validate.value);
                            if (validationResponse == 1) {
                                return null;
                            }
                        }
                    }
                    catch (e) {
                        // ignore at this level because otherwise the submit never happens and we never see the internal exception
                    }
                    var cleansedValue = removeBadCharacters(myElement.value);
                    idValuePairs[myElement.id] = encodeURIComponent(cleansedValue); // must encode values because of "special" characters that may be typed into text boxes
                }
                break;

            case 'hidden':
                if (myElement.id != null && myElement.id.length > 0) // guard against non-CBORD hidden elements, like VIEW STATE
                {
                    // don't bother with certain "client-only" values
                    if (sendHiddenElementInSubmit(myElement.id)) {
                        // w3c says no default value for hidden elements, so send 'em all back and check on the server
                        var elemVal = myElement.value == null ? "" : myElement.value;
                        idValuePairs[myElement.id] = elemVal;
                    }
                }
                break;
            default: // ignore things we don't know about, as we didn't create them
                break;
        } // end swtich
    } // end for each form element
}

// only send hidden elements that matter
function sendHiddenElementInSubmit(hiddenId)
{
	if (hiddenId == "__VIEWSTATE") return false;
	
	// these are only generated for the top-most topic, so id is fixed
	if (hiddenId == "numRequired") return false;
	if (hiddenId == "requiredIDs") return false;
	if (hiddenId == "requiredLabels") return false;
	if (hiddenId == "zOrderIds") return false;
	
	// these have additional stuff at the end, so only compare the first part
	var nonAbsoluteDIVs = "nonAbsoluteDIVs";
	if (hiddenId.length > nonAbsoluteDIVs.length && hiddenId.substring(0,nonAbsoluteDIVs.length) == nonAbsoluteDIVs) return false;
	var numNonAbsoluteDIVs = "numNonAbsoluteDIVs";
	if (hiddenId.length > numNonAbsoluteDIVs.length && hiddenId.substring(0,numNonAbsoluteDIVs.length) == numNonAbsoluteDIVs) return false;
	var resizableDIVs = "resizableDIVs";
	if (hiddenId.length > resizableDIVs.length && hiddenId.substring(0,resizableDIVs.length) == resizableDIVs) return false;
	var rdSizePercents = "rdSizePercents";
	if (hiddenId.length > rdSizePercents.length && hiddenId.substring(0,rdSizePercents.length) == rdSizePercents) return false;
	var numResizableDIVs = "numResizableDIVs";
	if (hiddenId.length > numResizableDIVs.length && hiddenId.substring(0,numResizableDIVs.length) == numResizableDIVs) return false;
	if (IsSetHeaderWidthsID(hiddenId)) return false;
	var splitGridContainer = "splitGridContainer";
	if (hiddenId.length > splitGridContainer.length && hiddenId.substring(0, splitGridContainer.length) == splitGridContainer) return false;
	var splitGridScrollingRows = "splitGridScrollingRows";
	if (hiddenId.length > splitGridScrollingRows.length && hiddenId.substring(0, splitGridScrollingRows.length) == splitGridScrollingRows) return false;
	var splitGridScrollingHeader = "splitGridScrollingHeader";
	if (hiddenId.length > splitGridScrollingHeader.length && hiddenId.substring(0, splitGridScrollingHeader.length) == splitGridScrollingHeader) return false;
	var splitGridFixedRows = "splitGridFixedRows";
	if (hiddenId.length > splitGridFixedRows.length && hiddenId.substring(0, splitGridFixedRows.length) == splitGridFixedRows) return false;
	var splitGridFixedHeader = "splitGridFixedHeader";
	if (hiddenId.length > splitGridFixedHeader.length && hiddenId.substring(0, splitGridFixedHeader.length) == splitGridFixedHeader) return false;
	var splitGridControls = "splitGridControls";
	if (hiddenId.length > splitGridControls.length && hiddenId.substring(0, splitGridControls.length) == splitGridControls) return false;
	var rootSubsetID = "rootSubsetID";
	if (hiddenId.length > rootSubsetID.length && hiddenId.substring(0, rootSubsetID.length) == rootSubsetID) return false;
	return true;
}

// is the given id one of our 'setHeaderWidthsIDs' elements?
function IsSetHeaderWidthsID(id)
{
	var setHeaderWidthsIDs = "setHeaderWidthsIDs";
	return (id.length > setHeaderWidthsIDs.length && id.substring(0, setHeaderWidthsIDs.length) == setHeaderWidthsIDs);	
}

// Remove Bad unprintable characters from the given value
// For now, just consider all characters less than x20; keep x09, x0A, and x0D
function removeBadCharacters(value)
{
	var result = "";
	var len = value.length;
	for (var i=0; i<len; i++)
	{
		var curChar = value.charCodeAt(i);
		if (curChar < 32)
		{
			if (curChar == 9 || curChar == 10 || curChar == 13)
			{
				result += value.charAt(i);
			}
			// ignore all others by removing them from the output
		}
		else
		{
			result += value.charAt(i);
		}
	}
	return result;
}

// placeholder for testing.
function validate(id,myForm)
{
	// alert("data change at "+id);
}

// set the status of the owning window from a modal dialog box
function setStatusFromModal(status)
{
	try
	{
		if (window.dialogArguments != null)
		{
			window.dialogArguments.top.status=status;
		}
	}
	catch(e)
	{
		reportClientException(e, "setStatusFromModal");
	}
}

// copy the value from the calendar element into the given element.
function copyFromCalendar(source)
{
	var target = source.parentNode.previousSibling.childNodes[0];
	if (target != null)
	{
		var targetElem = getById(target.name);
	}
	
	// the following conditional is to support the different DOM in Mozilla for schedule selection
	if (targetElem == null)
	{
		targetElem = source.parentNode.previousSibling.childNodes[1];
	}
	
	// the following conditional is to support the different DOM in Mozilla for advanced search selection
	if (targetElem == null)
	{
		targetElem = source.parentNode.previousSibling.previousSibling.childNodes[0];
	}
	
	if (targetElem != null)
	{
		if (messages[mJSDateFormat] == "mdy")
		{ 
			targetElem.value = fnGetMonth()+messages[mJSYearDelimiter]+fnGetDay()+messages[mJSYearDelimiter]+fnGetYear();
		}
		else if (messages[mJSDateFormat] == "dmy")
		{
			targetElem.value=fnGetDay()+messages[mJSYearDelimiter]+fnGetMonth()+messages[mJSYearDelimiter]+fnGetYear();
		}
		else if (messages[mJSDateFormat]  == "ymd") 
		{
			targetElem.value=fnGetYear()+messages[mJSYearDelimiter]+fnGetMonth()+messages[mJSYearDelimiter]+fnGetDay();
		}
		else if (messages[mJSDateFormat] == "ydm")
		{
			targetElem.value=fnGetYear()+messages[mJSYearDelimiter]+fnGetDay()+messages[mJSYearDelimiter]+fnGetMonth();
		}
	}
}

// report an exception during javacript processing (will re-direct to error page)
function reportClientException(e, location)
{
	var exceptionMsg = "";
	if (e.number != null)
	{
		exceptionMsg="script error ("+(e.number  &  0xFFFF)+") : "+e.description+" in "+location;
	}
	else
	{
		exceptionMsg="script error: "+e+" in "+location;
	}
	
	var myForm = GetCBORDForm();
	if (myForm != null)
	{
		var myElement = document.getElementById("clientException");
		if (myElement != null)
		{
			myElement.value = exceptionMsg;
			//dumpMessageToScreen("clientException: "+exceptionMsg);
			internalProcessAction(myForm, "SynchronizeData");
		}
		else
		{
			alert(exceptionMsg);
		}
	}
	else
	{
		alert(exceptionMsg+" "+window.location);
	}
}

// take a value and stick it in an element, and then process the action
function processLinkAction(elemValue, elemName, action)
{
	try
	{
		var menuForm = GetCBORDForm();
		var elem = document.getElementById(elemName);
		elem.value = elemValue;
		internalProcessAction(menuForm, action);
	}	
	catch(e)
	{
		reportClientException(e, "processLinkAction - "+action);
	}	
}

var setFocusElements = null;

// set the focus to the element with the given id and optionally the selection too.  Do not change if disabled.
function setFocus(id, withSelect)
{
	if (setFocusElements == null)
	{
		setFocusElements = new Object();
	}
	setFocusElements[id] = withSelect;
}

// set the focus to the element with the given id and optionally the selection too.  Do not change if disabled.
function doSetFocus(id, withSelect)
{
	var elem = document.getElementById(id);
	if (elem && !elem.disabled && elem.type != 'hidden' && elem.isTextEdit && elem.focus)
	{
	    try {
	        var f = function() { document.getElementById(id).focus(); };

		    // delay the execution of the focus call to give IE some time
		    // 1/3 a second is sometimes long enough to get the 'title' out of the way or whatever, but not always 
		    // (problem happens in IE when button is clicked w/ mouse and the mouse is not moved 
		    // and also in IE when you use the return key)
            setTimeout(f, 300);
 		    if (withSelect && (elem.type == 'select-one' || elem.type == 'select-multiple'))
		    {
			    elem.select();
		    }
		}
		catch (e)
		{
		    // ignore any problems here
		}
	}
}

function setAllFocusElements()
{
	if (setFocusElements != null)
	{
		for (id in setFocusElements)
		{
			doSetFocus(id, setFocusElements[id]);
		}
	}
	setFocusElements = null;
}

// function to immediately go to a topic
// NOTE: this function will not work if page submits, so watch out for onOnLoadEvents.
function gotoTopic(topicID)
{
	var destination = pageName + '?topicID=' + topicID;
	processRedirect(destination);
}

// function to simulate a navigation action for use by advanced search ONLY
function gotoAdvSearchTopic(topicID)
{
	try
	{
		var targetTopicElem = setTargetTopic(topicID);
		if (targetTopicElem != null)
		{
			checkRequiredBeforeAction("OpenZTopicAction");
		}
	}
	catch(e)
	{
		reportClientException(e, "gotoAdvSearchTopic");
	}
}

// set the framework-defined element for the new target topic (navigation action)
function setTargetTopic(targetTopicID)
{
	var targetTopicElem = document.getElementById("targetTopicID");
	if (targetTopicElem != null)
	{
		targetTopicElem.value = targetTopicID;
	}
	return targetTopicElem;
}

// go directly to the given topic if not dirty,
// otherwise, add the topicID to the given element and call the normal IsDirty function.
function gotoTopicIfNotDirty(topicID, locationElemID)
{
	var form = GetCBORDForm();
	if (IsDirtyNoSubmit(form))
	{
		// save goto location before submit
		var locationElem = document.getElementById(locationElemID);
		locationElem.value = topicID;
		
		// use normal IsDirty to get proper processing of actions.
		IsDirty(form);
	}
	else
	{
		// is dirty is false
		gotoTopic(topicID);
	}
}

// check for the enter key and ignore it
// the parameter is ignored
function ignoreEnterKey(ignored, evt)
{
	return checkForEnterKey(null, evt);
}

// check for the enter key and submit the action if pressed
function checkForEnterKey(action, evt)
{
	return checkKey(action,evt,13);
}

// function to check for the given key code and perform the given action if pressed.
// return proper event propagation (true/false) so that event bubbling stops if we process things
function checkKey(action, evt, keyCode) {
    try {
        if (eventIsKeyCode(evt, keyCode)) {
            if (action) {
                processAction(action);
            }
            return false;
        }
    }
    catch (e) {
        reportClientException(e, "checkKey");
    }
    return true;
}

// check for the enter key and submit the action w/ given parm, if pressed
function checkForEnterKeyWithParm(action, parm, evt) {
    return checkKeyWithParm(action, parm, evt, 13);
}

// function to check for the given key code and perform the given action if pressed.
// return proper event propagation (true/false) so that event bubbling stops if we process things
function checkKeyWithParm(action, parm, evt, keyCode) {
    try {
        if (eventIsKeyCode(evt, keyCode)) {
            if (action) {
                processActionWithParm(action, parm);
            }
            return false;
        }
    }
    catch (e) {
        reportClientException(e, "checkKeyWithParm");
    }
    return true;
}

// return true if the given event was a key press of the given char code.
function eventIsKeyCode(evt, keyCode) {
    if (evt) {
        var charCode = (evt.charCode) ? evt.charCode :
						((evt.keyCode) ? evt.keyCode :
						((evt.which) ? evt.which : 0));

        if (charCode == keyCode) {
            return true;
        }
    }
    return false;
}

// remove a trailing colon from a label string
function removeTrailingChar(parm, charToRemove)
{
	if (parm == null) return parm;
	if (typeof(parm) != "string") return parm;
	
	var chrToCheck = parm.substr(parm.length-1,1);
	if (chrToCheck == charToRemove)
	{
		return parm.substr(0,parm.length-1);
	}
	return parm;
}

// Message with replaceable string parms.
function message(msgstr, parms)
{
	var parmsCount= new Number(parms.length);
	var msgLength = msgstr.length;
	var msg = "";
	var i = 0;
	while (i<=msgLength)
	{
		var chr = "";
		chr = msgstr.substr(i,1);
		var chr1 = "";
		if ( i != msgLength)
		{
			chr1 = msgstr.substr(i+1,1);
		}
		else
		{
			chr1 = "%";
		}
		
		if (chr == "%" && chr1 !="%")
		{
			if  (isNaN(Number(chr1)) || Number(chr1)> parmsCount )
			{
				alert("Message function called with invalid replaceable parm specified"+contactCBORDMsg);
			}
			else
			{
				msg = msg+parms[Number(chr1)];
			}
			i++; 
		}
		else if (chr == "%")
		{
			i++; 
		}
		else
		{
			msg = msg+chr;
		}
		i++;
	}
	alert(msg);
}

//TODO: refactor all "check" 
// validate based on regular expression:
//	id = id of input element on page
//	locale = current locale (unused)
//	what = (translated) label associated with this element
//	regExpr = regular expression to use for match
//  msgId = message resource id to use (must have 2 replaceable parms: label and the 'bad character')
// validationContext = whether this came from an onBlur or a submit (1 = onblur, 0 = submit) - must agree with HTMLUIControl.MakeJSValidationCall
// all validation functions MUST return a numeric value for onBlur function chaining (0 = success, 1 = failure)
function checkRegExprAlert(id, locale, what, regExpr, msgId, validationContext)
{
	var elem = getById(id);
	var val = elem.value;
	var re = new RegExp(regExpr);  
	var matched = re.exec(val);
	if (matched == null || matched[0].length != val.length)
	{
		if (validationContext == 0)
		{
			return 1; // failure without alert
		}
	    try
		{
			var parms = new Array(1);
			parms[0]= "'"+removeTrailingChar(what,":")+"'";
			if (typeof(msgId) == "string")
			{
				// convert message id into numeric index
			    msgId = eval(msgId);
			}
        	var alertMessage = messages[msgId];
        	message(alertMessage, parms);
		}
		catch (e)
		{
	    	// alert('exception: '+e);  // ignore exceptions during message generation
			alert("Invalid input: "+val);
		}
		elem.value = elem.defaultValue;
		elem.focus();
	}
	return 0;
}

// Alert user if "what" is not integer numeric and within the given range (inclusive).
// all validation functions MUST return a numeric value for onBlur function chaining
// note: locale argument is currently unused
function checkNumericRangeAlert(id, locale, what, min, minIncluded, max, maxIncluded, validationContext)
{
	var mini= Number(min);
	var maxi = Number(max);
	var ok = isNumericRange(locale, id, min, minIncluded, max, maxIncluded)
	if (!ok)
	{
		if (validationContext == 0)
		{
			return 1; // failure without alert
		}
		var parms = new Array(3);
		parms[0]= "'"+removeTrailingChar(what,":")+"'";
		parms[1]= min;
		parms[2]= max;
		var alertMessage = messages[mJSIntegerRange];
		message(alertMessage,parms);
		var elem = getById(id);
		elem.value = elem.defaultValue;
		elem.focus();
	}
	return 0;
}

// Alert user if "what" is not integer numeric and within the given range (inclusive).
// all validation functions MUST return a numeric value for onBlur function chaining
// note: locale argument is currently unused
function checkNumericRangeNonBlankAlert(id, locale, what, min, minIncluded, max, maxIncluded, validationContext) {
    var mini = Number(min);
    var maxi = Number(max);
    var ok = isNumericRangeNonBlank(locale, id, min, minIncluded, max, maxIncluded)
    if (!ok) {
        if (validationContext == 0) {
            return 1; // failure without alert
        }
        var parms = new Array(3);
        parms[0] = "'" + removeTrailingChar(what, ":") + "'";
        parms[1] = min;
        parms[2] = max;
        var alertMessage = messages[mJSIntegerRange];
        message(alertMessage, parms);
        var elem = getById(id);
        elem.value = elem.defaultValue;
        elem.focus();
    }
    return 0;
}

// Alert user if "what" is not numeric and within the given range.  Range inclusivness is determined by parms.
// all validation functions MUST return a numeric value for onBlur function chaining
function checkDecimalRangeAlert(id, locale, what, dec, min, minIncluded, max, maxIncluded, validationContext)
{
	var mini= Number(min);
	var maxi = Number(max);
	var decimals = Number(dec);
	var ok = isDecimalRange(locale, id, min, minIncluded, max, maxIncluded, decimals);
	if (!ok)
	{
		if (validationContext == 0)
		{
			return 1; // failure without alert
		}
		var parms = new Array(4);
		parms[0]= "'"+removeTrailingChar(what,":")+"'";
		parms[1]= min;
		parms[2]= max;
		parms[3]= decimals;
		var alertMessage = messages[mJSDecimalRange];
		message(alertMessage,parms);
		var elem = getById(id);
		elem.value = elem.defaultValue;
		elem.focus();
	}
	return 0;
}

// Alert user if "what" is not numeric and within the given range.  Range inclusivness is determined by parms.
// all validation functions MUST return a numeric value for onBlur function chaining
function checkDecimalRangeAlertNonBlank(id, locale, what, dec, min, minIncluded, max, maxIncluded, validationContext) {
    var mini = Number(min);
    var maxi = Number(max);
    var decimals = Number(dec);
    var ok = isDecimalRangeNonBlank(locale, id, min, minIncluded, max, maxIncluded, decimals);
    if (!ok) {
        if (validationContext == 0) {
            return 1; // failure without alert
        }
        var parms = new Array(4);
        parms[0] = "'" + removeTrailingChar(what, ":") + "'";
        parms[1] = min;
        parms[2] = max;
        parms[3] = decimals;
        var alertMessage = messages[mJSDecimalRange];
        message(alertMessage, parms);
        var elem = getById(id);
        elem.value = elem.defaultValue;
        elem.focus();
    }
    return 0;
}


// check a value to make sure it is a positive number less than or equal to the given maximum.
// all validation functions MUST return a numeric value for onBlur function chaining
function checkPositiveNumericAlert(id, locale, what, max, validationContext)
{
	try
	{
		var min = "1";
		checkNumericRangeAlert(id, locale, what, min, true, max, true, validationContext);
	}
	catch(e)
	{
		reportClientException(e, "checkPositiveNumericAlert");
	}
	return 0;	
}

/// Alert user if "what" is not numeric having at most numDecimals after the decimal point, and positive (greater than zero).
// all validation functions MUST return a numeric value for onBlur function chaining
function checkPositiveDecimalAlert(id, locale, what, numDecimals, max, validationContext)
{
	try
	{
		var min= "0";
		checkDecimalRangeAlert(id, locale, what, numDecimals, min, false, max, true, validationContext);
	}
	catch(e)
	{
		reportClientException(e, "checkPositiveDecimalAlert");
	}		
	return 0;	
}

/// Alert user if "what" is not numeric having at most numDecimals after the decimal point, and non-negative (greater than or equal to zero).
// all validation functions MUST return a numeric value for onBlur function chaining
function checkNonNegativeDecimalAlert(id, locale, what, numDecimals, max, validationContext)
{
	try
	{
		var min= "0";
		checkDecimalRangeAlert(id, locale, what, numDecimals, min, true, max, true, validationContext);
	}
	catch(e)
	{
		reportClientException(e, "checkNonNegativeDecimalAlert");
	}		
	return 0;	
}

//function setDefaultDateToToday(elementId)
//{
//	return;
//	var element = document.getElementById(elementId);
//	if (element != null)
//	{
//		var elementValue = element.value;
//		if (elementValue.length == 0)
//		{
//			var today = new Date();
//			elementValue = today.getMonth() + 1;
//			elementValue += "/";
//			elementValue += today.getDate();
//			elementValue += "/";
//			elementValue += today.getYear();
//			element.value = elementValue;
//		}
//	}
//}
// date validation:
//	id = id of input element on page
//	locale = current locale
//	what = (translated) label associated with this element
//	dummy = (kluge) ignored parameter used to force locale and what to be generated
// all validation functions MUST return a numeric value for onBlur function chaining
function checkDateAlert(id,locale,what,dummy, validationContext)
{
	try
	{
	
		var ok = isDate(locale, id);
		if (!ok)
		{
			if (validationContext == 0)
			{
				return 1; // failure without alert
			}
			var parms = new Array(1);
			parms[0]= "'"+removeTrailingChar(what,":")+"'";
			var alertMessage = messages[mJSDate];
			message(alertMessage,parms);
			var elem = getById(id);
			elem.value = elem.defaultValue;
			elem.focus();
		}
	}
	catch(e)
	{
		reportClientException(e, "checkDateAlert");
	}		
	return 0;	
}

//function setDefaultDateToToday(elementId)
//{
//	return;
//	var element = document.getElementById(elementId);
//	if (element != null)
//	{
//		var elementValue = element.value;
//		if (elementValue.length == 0)
//		{
//			var today = new Date();
//			elementValue = today.getMonth() + 1;
//			elementValue += "/";
//			elementValue += today.getDate();
//			elementValue += "/";
//			elementValue += today.getYear();
//			element.value = elementValue;
//		}
//	}
//}
// date validation:
//	id = id of input element on page
//	locale = current locale
//	what = (translated) label associated with this element
//	dummy = (kluge) ignored parameter used to force locale and what to be generated
// all validation functions MUST return a numeric value for onBlur function chaining
function checkQueryDateAlert(id,locale,what,dummy, validationContext)
{
	try
	{
	
				// valid dates are good
		if (isDate(locale, id))
		{
			return 0;
		}
    	// access value
		var elem = getById(id);
		if (elem == null || elem.value.length == 0)
		{
			return 0;	// no value is OK (use required value checking to enforce non-empty)
		}
		var curValue = elem.value;
		curValue = curValue.toLowerCase();
		
		var todayStr = messages[mJSToday];
		if (curValue.length>4 && (curValue.substr(0,5) == todayStr.toLowerCase()))
		{
			if (curValue.length>5)
			{
				// so is today-7 or today+5
				var ext = curValue.substr(5);

				if (! isNaN(ext))
				{
					return 0;
				}
			}
			else
			{
				return 0;
			}
		}
		if (validationContext == 0)
		{
			return 1; // failure without alert
		}
		var parms = new Array(1);
		parms[0]= "'"+removeTrailingChar(what,":")+"'";
		var alertMessage = messages[mJSDate];
		message(alertMessage,parms);
		var elem = getById(id);
		elem.value = elem.defaultValue;
		elem.focus();

	}
	catch(e)
	{
		reportClientException(e, "checkDateAlert");
	}		
	return 0;	
}
// time validation:
//	id = id of input element on page
//	locale = current locale
//	what = (translated) label associated with this element
//	dummy = (kluge) ignored parameter used to force locale and what to be generated
// all validation functions MUST return a numeric value for onBlur function chaining
function checkTimeAlert(id,locale,what,dummy, validationContext)
{
	try
	{
		var ok = isTime(locale, id);
		if (!ok)
		{
			if (validationContext == 0)
			{
				return 1; // failure without alert
			}
			var parms = new Array(1);
			parms[0]= "'"+removeTrailingChar(what,":")+"'";
			var alertMessage = messages[mJSTime];
			message(alertMessage,parms);
			var elem = getById(id);
			elem.value = elem.defaultValue;
			elem.focus();
		}
	}
	catch(e)
	{
		reportClientException(e, "checkDateAlert");
	}		
	return 0;	
}



// Boolean Validation routines

// is this a valid date
function isDate(locale,id)
{
	var elem = getById(id);
	try
	{
		// a blank is a valid date. use check required to check for blank
		if (elem == null || elem.value == "")
		{
			return true;
		}
		var testDate = ParseDate(elem.value,locale);
		if (testDate.length==0)
		{
			return false;
		}
		elem.value =  testDate;                     
		return true;
	}
	catch(e)
	{
		reportClientException(e, "isDate");
	}
}

function checkDateOfBirthAlert(id,locale,what,dummy, validationContext)
{
	try
	{
	    var ok = isDate(locale, id);
		if (!ok)
		{
			if (validationContext == 0)
			{
				return 1; // failure without alert
			}
			var parms = new Array(1);
			parms[0]= "'"+removeTrailingChar(what,":")+"'";
			var alertMessage = messages[mJSDate];
			message(alertMessage,parms);
			var elem = getById(id);
			elem.value = elem.defaultValue;
			elem.focus();
		}
		var today = new Date();
		var elem = getById(id);
		var value = ParseDate(elem.value,locale);
		var inputDate = new Date(value);
		if (inputDate > today)
		{
			if (validationContext == 0)
			{
				return 1; // failure without alert
			}
			var parms = new Array(1);
			parms[0]= "'"+removeTrailingChar(what,":")+"'";
			var alertMessage = messages[mJSDateOfBirth];
			message(alertMessage,parms);
			var elem = getById(id);
			elem.value = elem.defaultValue;
			elem.focus();
		}
	}
	catch(e)
	{
		reportClientException(e, "checkDateOfBirthAlert");
	}		
	return 0;	
}

// Weight Date check alert
// First check for valid date, then check for a future date and display
// the mJSWeightDate message in resource file
function checkWeightDateAlert(id,locale,what,dummy, validationContext)
{
	try
	{
	    var ok = isDate(locale, id);
		if (!ok)
		{
			if (validationContext == 0)
			{
				return 1; // failure without alert
			}
			var parms = new Array(1);
			parms[0]= "'"+removeTrailingChar(what,":")+"'";
			var alertMessage = messages[mJSDate];
			message(alertMessage,parms);
			var elem = getById(id);
			elem.value = elem.defaultValue;
			elem.focus();
		}
		var today = new Date();
		var elem = getById(id);
		var value = ParseDate(elem.value,locale);
		var inputDate = new Date(value);
		if (inputDate > today)
		{
			if (validationContext == 0)
			{
				return 1; // failure without alert
			}
			var parms = new Array(1);
			parms[0]= "'"+removeTrailingChar(what,":")+"'";
			var alertMessage = messages[mJSWeightDate];
			message(alertMessage,parms);
			var elem = getById(id);
			elem.value = elem.defaultValue;
			elem.focus();
		}
	}
	catch(e)
	{
		reportClientException(e, "checkWeightDateAlert");
	}		
	return 0;	
}

// is this a valid time
function isTime(locale,id)
{
	var elem = getById(id);
	try
	{
		// a blank is a valid time. use check required to check for blank
		if (elem == null || elem.value == "")
		{
			return true;
		}
		try
		{
		var testTime = Date.parse(elem.value);
		}
		catch(e)
		{
	    	return false
		}
		return true;
	}
	catch(e)
	{
		reportClientException(e, "isTime");
	}
}

// parse a date in the given locale's format
function ParseDate(s,locale) 
{	
    var del = locale.substring(0,1);
    var fmt = locale.substring(1,4)

	// split the input into multiple strings
	// Support for 3 alternate deliminters with the first delimiter as the default.
	var v = ""; // return empty string on error.

	var a1 = s.split(del); 
	if ((a1.length!=3)) 
	{ 
	
		return v;
	}	 
	if (a1.length==3) 
	{
		var na=a1;
	}
	
	if (!isPositiveInteger( na[0]) || !isPositiveInteger(na[1]) || !isPositiveInteger(na[2])) 
	{
		return v; 
	}
	
	var d = 0; // day
	var m = 0; // month
	var y =0; // year
	
	if (fmt == "mdy")
	{ 
		m=na[0];
		d=na[1];
		y=na[2];
	}
	else if (fmt == "dmy")
	{
		d=na[0];
		m=na[1];
		y=na[2]; 
	}
	else if (fmt  == "ymd") 
	{
		y=na[0];
		m=na[1];
		d=na[2];
	}
	else if (fmt == "ydm")
	{
		y=na[0];
		d=na[1];
		m=na[2];
	}
	else
	{
	return v;
	}
	
	// basic check: digits in range
	if ( y<1000 || y.length>4 || m<1 || m>12||d<1 || d>31)
	{		
		return v;
	}
	
	// extended check: parse as datetime in javascript: this allows us to check for invalid days such as 4/30 or 2/30
	var dateTestString = m+'/'+d+'/'+y;
	var dateTest = new Date(dateTestString);
	if (dateTest.getDate() != d)
	{
		return v;
	}
	
	// The default delimiter is in the year bucket.
	v = na[0]+del+na[1]+del+na[2]; 
	return v;
}

// date span start field validation:
//	id = id of input element on page
//	locale = current locale
//	what = (translated) label associated with this element
//	dummy = (kluge) ignored parameter used to force locale and what to be generated
// all validation functions MUST return a numeric value for onBlur function chaining
function checkDateSpanStartAlert(id,locale,what,dummy,validationContext)
{
	try
	{
		// valid dates are good
		if (isDate(locale, id))
		{
			return 0;
		}
		
		// number of week (1..7) is ok
		if (isNumericRange(locale, id, 1, 7))
		{
			return 0;
		}
		
		// access value
		var elem = getById(id);
		if (elem == null || elem.value.length == 0)
		{
			return 0;	// no value is OK (use required value checking to enforce non-empty)
		}
		var curValue = elem.value;
		curValue = curValue.toLowerCase();
		
		// today is ok
		var todayStr = messages[mJSToday];
		if (curValue.length>4 && (curValue.substr(0,5) == todayStr.toLowerCase()))
		{
			if (curValue.length>5)
			{
				// so is today-7 or today+5
				var ext = curValue.substr(5);

				if (! isNaN(ext))
				{
					return 0;
				}
			}
			else
			{
				return 0;
			}
		}
		
		// day of week name is OK, that is, one of: monday, tuesday, ... sunday is ok
		var aDay = messages[mJSMonday];
		if (curValue == aDay.toLowerCase())
		{
			return 0;
		}
		aDay = messages[mJSTuesday];
		if (curValue == aDay.toLowerCase())
		{
			return 0;
		}
		aDay = messages[mJSWednesday];
		if (curValue == aDay.toLowerCase())
		{
			return 0;
		}
		aDay = messages[mJSThursday];
		if (curValue == aDay.toLowerCase())
		{
			return 0;
		}
		aDay = messages[mJSFriday];
		if (curValue == aDay.toLowerCase())
		{
			return 0;
		}
		aDay = messages[mJSSaturday];
		if (curValue == aDay.toLowerCase())
		{
			return 0;
		}
		aDay = messages[mJSSunday];
		if (curValue == aDay.toLowerCase())
		{
			return 0;
		}
		
		// Not one of the good things, then it is bad
		if (validationContext == 0)
		{
			return 1; // failure without alert
		}
		var parms = new Array(1);
		parms[0]= "'"+removeTrailingChar(what,":")+"'";
		var alertMessage = messages[mJSDateSpanStart];
		message(alertMessage, parms);
		if (elem != null)
		{
			elem.value = elem.defaultValue;
			elem.focus();
		}
	}
	catch(e)
	{
		reportClientException(e, "checkDateSpanStartAlert");
	}		
	return 0;	
}

/*************************************************************************/ 
/*Function name :isPositiveInteger(theString) */ 
/*Usage of this function :test for an +ve integer */ 
/*Input parameter required:thedata=string for test whether is +ve integer*/ 
/*Return value :if is +ve integer,return true */ 
/* else return false */ 
/*function require :isDigit */ 
/*************************************************************************/ 
function isPositiveInteger(theString) 
{ 
	var theData = new String(theString) 
	if (!isDigit(theData.charAt(0))) 
	if (!(theData.charAt(0)== '+')) 
	return false 

	for (var i = 1; i < theData.length; i++) 
		if (!isDigit(theData.charAt(i))) 
			return false;
	return true;
}

/**********************************************************************/ 
/*Function name :isDigit(theDigit) */ 
/*Usage of this function :test for an digit */ 
/*Input parameter required:thedata=string for test whether is digit */ 
/*Return value :if is digit,return true */ 
/* else return false */ 
/**********************************************************************/ 
function isDigit(theDigit) 
{ 
	var digitArray = new Array('0','1','2','3','4','5','6','7','8','9'); 

	for (var j = 0; j  < digitArray.length; j++) 
		{
			if (theDigit == digitArray[j]) 
				return true ;
		} 
	return false ;
} 

// check numberic input for math expressions
// arguments: elem -- input element to check
function isMath(locale, elem, decimals)
{
    var currencySymbol = locale.substring(4,1);
	var currencyGroupChar = locale.substring(5,1);
	var currencyPointChar = locale.substring(6,1);
	var decimalGroupChar = locale.substring(7,1); 
	var decimalPointChar = locale.substring(8,1);

 	var result = isFraction(currencySymbol, currencyGroupChar, currencyPointChar, decimalGroupChar, decimalPointChar, elem, decimals);
	result = isMultiplication(currencySymbol, currencyGroupChar, currencyPointChar, decimalGroupChar, decimalPointChar,  elem, decimals);
	result= convertDisplayToNumeric(result, currencySymbol, currencyGroupChar,currencyPointChar, decimalGroupChar, decimalPointChar);
	return  result;
}

// allow a fration expression in numeric input (a/b).
function isFraction(currencySymbol, currencyGroupChar, currencyPointChar, decimalGroupChar, decimalPointChar, elem, dec)
{
	var decimals = Number(dec);
	var temp = 0;
	var result = elem.value;
	var testvals = result.split("/");
	if (testvals.length == 2)
	{
	  var testvals2 = testvals[0].split(" ");
	  var plusfact = 0;
	  if (testvals2.length == 2)
	  {
		testvals[0]= testvals2[1];
		plusfact = testvals2[0];
	  }
	  testvals[0] =  convertDisplayToNumeric(testvals[0], currencySymbol, currencyGroupChar,currencyPointChar, decimalGroupChar, decimalPointChar);
	  testvals[1] = convertDisplayToNumeric(testvals[1], currencySymbol, currencyGroupChar,currencyPointChar, decimalGroupChar, decimalPointChar);
	  plusfact =   convertDisplayToNumeric(plusfact, currencySymbol, currencyGroupChar,currencyPointChar, decimalGroupChar, decimalPointChar);
	  if (!isNaN(testvals[0]) && !isNaN(testvals[1]) &&!isNaN(plusfact) && testvals[1] > 0)
	  {
		temp = testvals[0] / testvals[1];
		temp = roundNumber(temp, decimals);
		result =  temp + plusfact; 
		result = new String(result);
		elem.value = result;
	  }
	}
	return result;
}

// allow a simple multiplication expression in numeric input (a*b).
function isMultiplication(currencySymbol, currencyGroupChar, currencyPointChar,  decimalGroupChar, decimalPointChar,elem, dec)
{
	var decimals = Number(dec);
	var result = elem.value;
	var testvals = result.split("*");
	if (testvals.length == 2)
	{
	    testvals[0] = convertDisplayToNumeric(testvals[0], currencySymbol, currencyGroupChar,currencyPointChar, decimalGroupChar, decimalPointChar);
	    testvals[1] = convertDisplayToNumeric(testvals[1], currencySymbol, currencyGroupChar,currencyPointChar, decimalGroupChar, decimalPointChar);
        if (!isNaN(testvals[0]) && !isNaN(testvals[1]) )
        {
		    result = testvals[0] * testvals[1] ; 
		    result = new String(result); 
		    elem.value = result;
		}
	}
	return result;
}

//test for integer value
//arguments
//    s     value to check.
function isInteger(s)
{
	return (s.toString().search(/^-?[0-9]+$/) == 0);
}

// Is the value of the form element "id"  numeric and positive.
// arguments:
//		locale		encoded locale format ifo see HTMLUICONTROL.CultureStringForJavascript()
//		id			id of element in form (must be input element and this is not checked)
//		min			numeric minimum 
//		minIncluded	bool, where true means inclusive range for min; false exclusive
//		max			numeric maximum
//		maxIncluded bool, where true means inclusive range for max; false exclusive
function isNumericRange(locale, id, min, minIncluded, max, maxIncluded)
{
	try
	{
		var elem = getById(id);
		var decimalPointChar = locale.substring(8,1); 
		if (elem != null && elem.value.length > 0)
		{
			// n.0 is not allowed, even though it could be converted.
			if (elem.value.indexOf(decimalPointChar) > -1)
			{
				return false;
			}
			var tempNum = isMath(locale, elem, 0);
			
			// test for number and integer
			if (isNaN(tempNum) || !isFinite(tempNum) || !isInteger(tempNum) )
			{
				return false;
			}
			// test that number is within the required range (inclusive or exclusive)
			return numberIsInRange(tempNum, min, minIncluded, max, maxIncluded);
		}
		return true;	// empty values are ok
	}
	catch(e)
	{
		reportClientException(e, "isNumericRange");
	}
}

// Is the value of the form element "id"  numeric and positive.
// arguments:
//		locale		encoded locale format ifo see HTMLUICONTROL.CultureStringForJavascript()
//		id			id of element in form (must be input element and this is not checked)
//		min			numeric minimum 
//		minIncluded	bool, where true means inclusive range for min; false exclusive
//		max			numeric maximum
//		maxIncluded bool, where true means inclusive range for max; false exclusive
function isNumericRangeNonBlank(locale, id, min, minIncluded, max, maxIncluded) {
    try {
        var elem = getById(id);
        var decimalPointChar = locale.substring(8, 1);
        if (elem != null && elem.value.length > 0) {
            // n.0 is not allowed, even though it could be converted.
            if (elem.value.indexOf(decimalPointChar) > -1) {
                return false;
            }
            var tempNum = isMath(locale, elem, 0);

            // test for number and integer
            if (isNaN(tempNum) || !isFinite(tempNum) || !isInteger(tempNum)) {
                return false;
            }
            // test that number is within the required range (inclusive or exclusive)
            return numberIsInRange(tempNum, min, minIncluded, max, maxIncluded);
        }
        return false; // empty values are not ok
    }
    catch (e) {
        reportClientException(e, "isNumericRange");
    }
}

// internal helper function to test a number within a range
function numberIsInRange(tempNum, min, minIncluded, max, maxIncluded)
{
	// use a range of [min, max]
	if (minIncluded && maxIncluded)
	{
		if ( (tempNum < min) || (tempNum > max))
		{
			return false;
		}
	}
	// use a range of [min, max)
	else if (minIncluded)
	{
		if ( (tempNum < min) || (tempNum >= max))
		{
			return false;
		}
	}
	// use a range of (min, max]
	else if (maxIncluded)
	{
		if ( (tempNum <= min) || (tempNum > max))
		{
			return false;
		}
	}
	// use a range of (min, max)
	else
	{
		if ( (tempNum <= min) || (tempNum >= max))
		{
			return false;
		}
	}
	return true;			
}

function roundNumber(num, dec)
{
  var decfact = Number(Math.pow(10, Number(dec)));
  var val = Number(num);
  val = val * decfact;
  val = Math.round(val);
  return   val / decfact;  
}
	
// Is the value of the form element "id"  nummeric and within the given range (min/max).
function isDecimalRange(locale, id, min, minIncluded, max, maxIncluded, decimals)
{
	try
	{
		var elem = getById(id);
		if (elem != null && elem.value.length > 0)
		{
			var tempNum = isMath(locale, elem,decimals);
			// check that value is numeric
			if (isNaN(tempNum) || (!isFinite(tempNum)) )
			{
				return false;
			}
		
			// check number of decimal places
			var str = elem.value;
			var pos = str.indexOf(".") + 1;
			if (pos >0 && (str.length - pos) > decimals)
			{
				return false;
			}
			
			// test that number is within the required range (inclusive or exclusive)
			return numberIsInRange(tempNum, min, minIncluded, max, maxIncluded);
		}
		return true;	// empty values are ok
	}
	catch(e)
	{
		reportClientException(e, "isDecimalRange");
	}
}

// Is the value of the form element "id"  nummeric and within the given range (min/max).
function isDecimalRangeNonBlank(locale, id, min, minIncluded, max, maxIncluded, decimals) {
    try {
        var elem = getById(id);
        if (elem != null && elem.value.length > 0) {
            var tempNum = isMath(locale, elem, decimals);
            // check that value is numeric
            if (isNaN(tempNum) || (!isFinite(tempNum))) {
                return false;
            }

            // check number of decimal places
            var str = elem.value;
            var pos = str.indexOf(".") + 1;
            if (pos > 0 && (str.length - pos) > decimals) {
                return false;
            }

            // test that number is within the required range (inclusive or exclusive)
            return numberIsInRange(tempNum, min, minIncluded, max, maxIncluded);
        }
        return false; // empty values are not ok
    }
    catch (e) {
        reportClientException(e, "isDecimalRange");
    }
}


// lookup the element in the current document forms collection.
function getById(id)
{
	try
	{
		return document.getElementById(id);
	}
	catch(e)
	{
		var myForm = GetCBORDForm();
		var elem = myForm.elements[id];
		return elem;
	}
}

// function to format a currency value (for use with computed columns)
// val		-- value to format
// symbol	-- currency symbol to use ('' ok)
// groupSep	-- separator character for each group to left of decimal
// decSep	-- separtor for decimal point
// decPlaces-- number of places to right of decimal point
// groupSize-- array of sizes of each group to left of decimal point, can be null, if defined, 1st element is number of dimensions (technical reasons)
// currencyNegativePattern -- number that indicates which patter to use (we support patterns 0 and 1 only).
function formatCurrency(val, symbol, groupSep, decSep, decPlaces, groupSizes, currencyNegativePattern)
{
	// if our value is not really a number or is infinity, just give up and put out nothing
	// (real error handling should be done elsewhere)
	var numForCheck = new Number(val);
	if (isNaN(numForCheck) || !isFinite(numForCheck))
	{
		return symbol;
	}

	var decPart = formatDecimal(val,groupSep,decSep,decPlaces,groupSizes,false);
	var retVal = '';
	// must format negative currencies according to locale's rules: 16 patterns for negative currencies, 5 for positive (we support a subset of the available patterns)
	if (numForCheck < 0)
	{
		if (currencyNegativePattern == 0)
		{
			retVal = '(' + symbol + decPart.substring(1) + ')';
		}
		else
		{
			retVal = symbol + decPart;
		}
	}
	else // this is positive currency pattern 0 (only support this one)
	{
		retVal = symbol + decPart;
	}
	return retVal;
}

// function to format a decimal value (for use with computed columns)
// follows rules outlined in NumberFormatInfo for grouping, etc.
// val		-- value to format
// groupSep	-- separator character for each group to left of decimal
// decSep	-- separtor for decimal point
// decPlaces-- number of places to right of decimal point
// groupSize-- array of sizes of each group to left of decimal point, can be null, if defined, 1st element is number of dimensions (technical reasons)
// removeTrailingZeros -- true/false indication of whether to trim trailing zeros
function formatDecimal(val, groupSep, decSep, decPlaces, groupSizes, removeTrailingZeros)
{
	// if our value is not really a number or is infinity, just give up and put out nothing
    // (real error handling should be done elsewhere)
	var numForCheck = new Number(val);
	if (isNaN(numForCheck) || !isFinite(numForCheck))
	{
		return '';
	}

	// Without this round, toFixed will round differently than C#. Example for rounding to two places: 1.235 rounds to 1.23, but 1.2351 rounds to 1.24. 
	if (decPlaces > 0) {
	    val = Math.round(val * Math.pow(10, decPlaces)) / Math.pow(10, decPlaces);
	}
	
	// create value with proper number of decimal places
	var v = val.toFixed(decPlaces);

	// now fix up decimal point character and groups: format left and right parts separately
	var splitVal = v.split('.');
	var leftOfPoint = splitVal[0];
	var left = '';
	var right = '';
	var pos = leftOfPoint.length-1;
	
	// allow no groups at all (groupsizes is not an array)
	if (typeof(groupSizes)=="object")
	{
		var groupSize = 0;
		var gsIndex = 1;	// first element of array is ignored (so that we always have at least two elements for inline constructor)
		var gsLastIndex = groupSizes.length - 1;
		// handle all the sizes before the last one
		while (gsIndex < gsLastIndex)
		{
			groupSize = groupSizes[gsIndex];
			if (pos >= groupSize)
			{
				left = leftOfPoint.substr((pos-groupSize)+1,groupSize) + left;
				pos = pos - groupSize;
				if (pos >= 0) left = groupSep + left;	
			}
			gsIndex++;
		}
		groupSize = groupSizes[gsIndex];
		// last group size takes all the rest
		while (pos >= groupSize)
		{
			left = leftOfPoint.substr((pos-groupSize)+1,groupSize) + left;
			pos = pos - groupSize;
			if (pos >= 0) left = groupSep + left;	
		}
	}
	
	// add remainder
	if (pos >= 0)
	{
		left = leftOfPoint.substr(0,pos+1) + left;
	}
	
	// remove trailing zeros if requested
	right = splitVal[1];
	// just because it could happen... when there is no decimal point (like "Infinity")
	if (right == null)
	{
		right = '';
	}
	
	if (removeTrailingZeros)
	{
		right = trimTrailingChars(right, '0');
	}
	
	// only use a decimal point if something is to the right
	var result = left;
	if (right.length > 0)
	{
		result = result + decSep + right;
	}
	// fixup nagative number
	if (result.substr(0, 2) == "-,") {
	    result = "-" + result.substr(2);
	}
	return result;
}

// return the given string will all trailing characters removed
function trimTrailingChars(str, theChar)
{
	if (str == null) return "";				// get out if somehow we have a null
	if (typeof(str) != "string") return "";	// get out if somehow we do not have a string
	
	var result = str;	// start with the given string as the result
	
	var len = str.length;
	while (len > 0 && str.charAt(len-1) == theChar)
	{
		len--;
	}
	
	if (len == 0)
	{
		result = "";
	}
	else if (len < str.length)
	{
		result = str.substr(0,len);
	}
	return result;
}

// open help
function openHelp(ignored,helpFileName)
{
	try
	{
		window.open(helpFileName,'_blank','height=768,width=1024,top=50,left=50,status=no,toolbar=false,location=no,menubar=no,scrollbars=yes,resizable=yes');
	}
	catch(e)
	{
		reportClientException(e, "openHelp");
	}
}

// special function to allow only one checkbox for admin unit types in a grid
// DEPENDS on layout of grid: we currently use columns 3, 4, 5, 6, 7
function setAdjacentCheckBoxesInUnitAdminGrid(checkbox)
{
	// create prefix of grid column id (note: format is 'c' + grid# + '_' + row# + '_' + col#)
	var idParts = checkbox.id.split('_');
	var idPrefix = idParts[0] + '_' + idParts[1] + '_';
	for (var i=3; i<8; i++)
	{
		var curId = idPrefix + i;
		var currentCB = document.getElementById(curId);
		
		if (currentCB.id != checkbox.id)
		{
			currentCB.checked = false;
		}
	}
}

// special function to allow only one checkbox for admin unit types in a form
// DEPENDS on layout of form: we currently use form element numbers 7, 8, 9, and 10
function setAdjacentCheckBoxesInUnitAdminForm(checkbox,first)
{
	// create prefix of editForm id (note: format is 'e' + panelName + element#)
	var idPrefix = checkbox.id.substring(0,checkbox.id.length-1);
	for (var i=first; i<first+5; i++)
	{
		var curId = idPrefix + i;
		var currentCB = document.getElementById(curId);

		if (currentCB.id != checkbox.id)
		{
			currentCB.checked = false;
		}
		else
		{
			currentCB.checked = true;
		}
	}
}

// function to compute the value of a summary column in an edit grid
// parameters:
//		colInTable		- some column in the table (sibling of column with given index)
//		columnPrefix	- prefix used by grid to identify columns
//		columnIndex		- index of column in row (used as part of generated column id)
//		nonDisplayedSum	- numeric value of summary columns that are not currently displayed
//		rangeStart		- start index of row being displayed (part of generated column id) - 1 origin
//		rangeEnd		- end index of row being displayed (part of generated column id) - 1 origin
//		currencySymbol	- locale currency symbol for value parsing of displayed stuff
//		decimalGroupChar- locale decimal grouping symbol for value parsing of displayed stuff
//		decimalPointChar- locale decimal point symbol for value parsing of displayed stuff
// NOTE: calls to this function are generated by the Edit grid.
function computeGridSummary(colInTable, columnPrefix, columnIndex, nonDisplayedSum, rangeStart, rangeEnd, currencySymbol, decimalGroupChar, decimalPointChar)
{
	try
	{
		var sum = new Number(nonDisplayedSum);			// set up accumulator
		// find our table for access to its rows and cell collections
		// structure is: table row[n] cell table row cell[k] element? (where n is number of displayed rows in the grid and k is the number of displayed columns, the last element is optional)
		// algorithm: find table going up
		if (colInTable == null) {
		    return sum; // only grid headers displayed (most likely); just return non-displayed sum
		}
		
		var myTable = colInTable.parentNode;
		while (myTable != null && myTable.tagName != 'TABLE')
		{
			myTable = myTable.parentNode;
		}
		
		// bad structure, give up
		if (myTable == null) {
		    // alert("No table found");
		    return sum;
		}
		
		// column ids are generated using a row numbering of rangeStart-1..rangeEnd-1 (think of "Listing rangeStart to rangeEnd of total, but 0 origin")
		var rowIndexInTable = 0;
		var skipRow = 0;
		var rowID = rangeStart - 1;
		while (rowID < rangeEnd)
		{
			skipRow = 0;
			// this must match the GenID method in class ColumnElement plus the row ID generated by ColumnGroup class
			// the columns we are looking for may or may NOT be form elements...computed columns are in named td objects
			var colElemId = columnPrefix + '_' + rowID + '_' + columnIndex;
			
			// if column is a form element, it will be here and we are done
			var colValue = document.getElementById(colElemId);
			
			// not a form element, look for it in the table/row/cell object hierarchy
			if (colValue == null)
			{
				var row = myTable.rows[rowIndexInTable];
				if (row != null)
				{
					// For a Grid, the structure of a row is one cell with a single table in it (and nothing else -- hence the childNodes[0]
					// must also account for grids with a group-by clause (and hence grouping headers)
					var rowCell = row.cells[0];
					if (rowCell == null)
					{
						// alert("bad row: rangeStart="+rangeStart+"; rangeEnd="+rangeEnd+"; rowID="+rowID);
						return sum;
					}
					var rowTable = rowCell.childNodes[0];
					// grids with column grouping have extra rows 
					if (rowTable.tagName == 'TABLE') 
					{
						// that table has one row, and then the actual columns
						var innerRow = rowTable.rows[0];
						// this is what we expect
						if (innerRow != null && innerRow.tagName == 'TR')
						{
							var col = innerRow.cells[columnIndex];
							if (col != null)
							{
								colValue = col;
								//alert("found "+colElemId+" by table lookup: "+colValue.innerText);
							}
						}
						//else
						//{
							// alert('inner row is not what we expect!');
						//}
					}
					else
					{
						//alert("skipping a row with index "+rowID+": "+rowTable);
						skipRow = 1;
						rowID--;
					}
				}
				//else
				//{
				//	alert("row is null");
				//}
			}
			
			// still nothing, try getElementById (DOM Level 1 function)
			if (colValue == null && skipRow == 0)
			{
				colValue = document.getElementById(colElemId);
			}
			
			if (colValue != null)
			{
				var innerVal = null;
				if (colValue.tagName == 'TD')
				{
					innerVal = colValue.innerHTML;
				}
				else
				{
					innerVal = colValue.value;
				}
				
				if (innerVal == null)
				{
					// alert("did not find a value for "+colValue.id);
					return sum;		// bad here, then give up
				}
				
				//tranlate a negative number represented by (NNN) into -NNN, remove currency symbol, grouping char's and convert decimal point
				// NOTE: this routine is called with type specific arguments, so it is OK to duplicate the group and point chars
				var colValAsNumber = convertDisplayToNumeric(innerVal, currencySymbol, decimalGroupChar, decimalPointChar, decimalGroupChar, decimalPointChar);

				if (!isNaN(colValAsNumber))
				{
					sum += colValAsNumber;
				}
				//else
				//{
					// alert("value "+innerVal+" is not a number");
				//}
			}
			//else
			//{
				//if (skipRow == 0)
				//{
					// alert("no value found for "+colElemId);
				//}
			//}
			rowIndexInTable++;
			rowID++
			
		} // end while more rows
		return sum;
	}
	catch(e)
	{
		reportClientException(e, "computeGridSummary");
	}
	return 0;	
}

// convert a displayed numeric value into a true number
// display can be currency, negative currency, and may contain a grouping symbol (like a ',')
//		currencySymbol	- locale currency symbol for value parsing of displayed stuff
//		currencyGroupChar- locale currency grouping symbol for value parsing of displayed stuff
//		currencyPointChar- locale currency point symbol for value parsing of displayed stuff
//		decimalGroupChar- locale decimal grouping symbol for value parsing of displayed stuff
//		decimalPointChar- locale decimal point symbol for value parsing of displayed stuff
function convertDisplayToNumeric(displayVal, currencySymbol, currencyGroupChar, currencyPointChar, decimalGroupChar, decimalPointChar)
{
	// already a number, then done
	if (typeof(displayVal) == "number")
	{
		return displayVal;
	}
		
	// this is one of those other types, it must come first in the conditional test sequence
	if (typeof(displayVal) == "boolean" )
	{
		return displayVal;
	}
	
	// have a string, so try removing currency stuff
	if (typeof(displayVal) == "string")
	{
		var retVal = displayVal
		retVal=retVal.replace(" ","");
		if (retVal == "")
		{
			return "Na"
		}
		// is this a currency value (must have currency symbol)
		if (displayVal.indexOf(currencySymbol) != -1)
		{
			//tranlate a negative number represented by (NNN) into -NNN
			retVal = displayVal.replace(')','');
			retVal = retVal.replace('(','-');
			
			// deal with currency by removing the currency symbol,  may not be in first position
			retVal = retVal.replace(currencySymbol,'');
			
			// remove all grouping characters and translate decimal point if necessary
			var removeGroupRE = eval('/\\'+currencyGroupChar+'/g');
			retVal = retVal.replace(removeGroupRE,'');			
			
			if (currencyPointChar != '.')
			{
				var decimalPointRE = eval('/\\'+currencyPointChar+'/g');
				retVal = retVal.replace(decimalPointRE,'.');
			}
		}
		else // consider it a decimal
		{				
			// remove all grouping characters and translate decimal point if necessary
			var removeGroupRE = eval('/\\'+decimalGroupChar+'/g');
			retVal = retVal.replace(removeGroupRE,'');			
			
			if (decimalPointChar != '.')
			{
				var decimalPointRE = eval('/\\'+decimalPointChar+'/g');
				retVal = retVal.replace(decimalPointRE,'.');
			}
		}
		retVal = new Number(retVal);
		return retVal;
	}
	// TODO: what do to with other types?

	return new Number(displayVal);
}

// simple function to show a video in a new window
function showVideo(name,src)
{
	//var url = "./playvid.asp?speed=300&rec="+ src+"&name="+name;
	
	// temp for demo
	var url = "./catfish.html";
	var popupWin = window.open(url,"popup", "scrollbars=no,width=320,height=283,top = 200, left = 300");
	//popupWin.opener.top.name = "opener";
	popupWin.focus();
}

// simple function to show a picture in a new window
function showPicture(name,src)
{
	var url = src;
	var popupWin = window.open(url,"popup", "scrollbars=no,width=320,height=283,top = 200, left = 300");
	//popupWin.opener.top.name = "opener";
	popupWin.focus();
}

//================== action processing functions ==============================================================

/*
 * ActionHandler is an object that encapsulates the transaction
 *     request and callback logic for processing 'submit' style actions
 *
 * success( ) provides success case logic
 * failure( ) provides failure case logic
 * call( ) calling this member starts the transaction request.
 */
var ActionHandler = {
	// set the scope so that the yahoo connection utility calls our methods properly; see the yahoo documentation
	scope:null,
	// are we currently processing a call?
	_callInProgress:false,
	// client-supplied "how to deal with this response" function
	_processResponse:null,
	// client-supplied arguements for the _processResponse function.
	_processResponseArguements:{},
	// client-supplied response type for the _processResponse function.
	_responseType:null,
	// wait message to use at start
	_waitMsgStart:"Sending request, please wait...",		// TODO: get from resources
	// wait message to use at while processing response
	_waitMsgMid:"Processing response...",		// TODO: get from resources

	// function called if server response was successful
	success:function(o)
	{
		try 
		{
			this._beginSubmit(this._waitMsgMid);
			if(o.responseText != undefined)
			{
				var response = o.responseText;
				//  check the error status of the returned response before evaluating the script
				var xmlDoc =  Sarissa.getDomDocument();
				xmlDoc = (new DOMParser()).parseFromString(response, "text/xml");
				xmlDoc.setProperty("SelectionNamespaces", "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
				xmlDoc.setProperty("SelectionLanguage", "XPath");
			
				var errorNode = xmlDoc.selectSingleNode("/Response/Status/Error");
				if (errorNode == null)
				{
					throw "Missing status information in the response from the server";
				}
				
				//If there has been an error redirect to error page
				if (errorNode.firstChild.data != "NONE")
				{
					var javascriptNode = xmlDoc.selectSingleNode("/Response/Status/ErrorScript");
					var script = "";
					
					if (javascriptNode != null)
					{
						script = javascriptNode.firstChild.data;
						if (script == null)
						{
							script = "";
						}
					}
					
					if (script.length > 0)
					{
						eval(script);
					}
				}
				else
				{
					responseNode = xmlDoc.selectSingleNode("/Response/Data");
					switch (this._responseType)
					{
						case "xml":
							//Pass the XML payload of the response to the handler function.
							this._processResponse(responseNode, this._processResponseArguements);
							break;
						case "text":
							//Pass the text payload of the response to the handler function.
							this._processResponse(responseNode.firstChild.data, this._processResponseArguements);
							break;
						default :
							throw "Unknown responseType '"+this._responseType+"' in ActionHandler success call";
					}
				}
			}
		}
		catch (e)
		{
			// we can't call reportClientException(e, "success handler") in a callback
			alert("ActionHandler success callback generated an exception: "+e.name+": "+e.description+contactCBORDMsg);
		}
		finally
		{
			// must be done last....otherwise exit page will cause re-entrent call which is bad, very bad
			this._endSubmit();
		}
	},

	// function called if server response fails
	failure:function(o)
	{
		var result = "A "+o.statusText+" error has occured with the error code: "+o.tId+" "+o.status;
		alert(result+contactCBORDMsg);
		// must be done last....otherwise exit page will cause re-entrent call which is bad, very bad
		this._endSubmit();
		// try to recover by refreshing the page (DOES NOT WORK)
		// window.location = window.location;
	},

	/*
	* Builds the parameters to pass in the request
	* toAppendData: Associative array of the data to be included
	*/
	_buildRequestParms:function(toAppendData)
	{
		var postData = "";
		for (var obj in toAppendData)
		{
			var parmName = obj;
			var parmValue = toAppendData[obj];
						
			if (parmName == null || parmName.length == 0)
			{
				throw "Empty parm passed to ActionHandler request";
			}
			postData = postData + "&" + parmName + "=" + parmValue;
		}		
		return postData;			
	},

	// function to call a server method and process the response.
	call:function(action, idValuePairs, responseHandler, responseHandlerArgs, responseType)
	{
		//set the scope of this to our instance.
		this.scope = this;
		// save client-supplied response handling info
		this._processResponse = responseHandler;
		this._processResponseArguements = responseHandlerArgs;
		this._responseType = responseType;
		var postData = "action="+action+"&responseType="+responseType+this._buildRequestParms(idValuePairs);
		// this needs to be the last thing you do before you call the async request.
		this._beginSubmit(this._waitMsgStart);
		YAHOO.util.Connect.asyncRequest("POST", "Ajax.aspx", this, postData);
	},

	// call to change the "in progress indicator"
	_beginSubmit:function(waitMsg)
	{
		window.document.body.style.cursor="wait";
		window.status = waitMsg;
		var waitDiv = document.getElementById('pleaseWait');
		if (waitDiv != null)
		{
			waitDiv.style.visibility = 'visible';
			var waitDivText = document.getElementById('waitText');
			if (waitDivText != null)
			{
				waitDivText.innerHTML = waitMsg;
			}
		}
		this._callInProgress = true;
	},

	// call to hide the "in progress indicator"
	_endSubmit:function()
	{
		window.status = "Done.";
		window.document.body.style.cursor="default";
		var waitDiv = document.getElementById('pleaseWait');
		if (waitDiv != null)
		{
			waitDiv.style.visibility = 'hidden';
		}
		this._callInProgress = false;
	},

	isCallInProgress:function()
	{
		return this._callInProgress;
	}

};

function dumpMessageToScreen(message)
{
	var targetElement = getContentsDiv();
	if (targetElement != null)
	{
		// can we create the element without this span? can this be done by "reflection"?
		var child = document.createElement("<p>");
		child.innerHTML = message;
		//alert(child.innerHTML);
		targetElement.insertBefore(child,null);
	}
}

// ============== End of ActionHandler Functions =================================================================

// ====================== BEGIN UI Control Event Functions =============================
function MakeEventHandler()
{
	var handler = {};	
	// client-supplied "how to deal with this response" function
	handler._processResponse = null;

	// function called if server response was successful
	handler.success =function(o)
	{
		try 
		{
			if(o.responseText != undefined)
			{
				var response = o.responseText;
				//  check the error status of the returned response before evaluating the script
				var xmlDoc =  Sarissa.getDomDocument();
				xmlDoc = (new DOMParser()).parseFromString(response, "text/xml");
				xmlDoc.setProperty("SelectionNamespaces", "xmlns:xsl='http://www.w3.org/1999/XSL/Transform'");
				xmlDoc.setProperty("SelectionLanguage", "XPath");
			
				var errorNode = xmlDoc.selectSingleNode("/Response/Status/Error");
				if (errorNode == null)
				{
					throw "Missing status information in the response from the server";
				}
				
				//If there has been an error redirect to error page
				if (errorNode.firstChild.data != "NONE")
				{
					var javascriptNode = xmlDoc.selectSingleNode("/Response/Status/ErrorScript");
					var script = "";
					
					if (javascriptNode != null)
					{
						script = javascriptNode.firstChild.data;
						if (script == null)
						{
							script = "";
						}
					}
					
					if (script.length > 0)
					{
						eval(script);
					}
				}
				else
				{
					responseNode = xmlDoc.selectSingleNode("/Response/Data");
					//Pass the XML payload of the response to the handler function.
					this._processResponse(responseNode);
				}
			}
		}
		catch (e)
		{
			// we can't call reportClientException(e, "success handler") in a callback
			alert("EventHandler success function generated an exception: "+e+contactCBORDMsg);
		}
	};

	// function called if server response fails
	handler.failure = function(o)
	{
		var result = o.tId + " " + o.status + " " + o.statusText;
		alert("EventHandler transaction failed.  The error is: " + result+contactCBORDMsg);
	};

	/*
	* Builds the parameters to pass in the request
	* toAppendData: Associative array of the data to be included
	*/
	handler._buildRequestParms = function(toAppendData)
	{
		var postData = "";
		for (var obj in toAppendData)
		{
			var parmName = obj;
			var parmValue = toAppendData[obj];
						
			if (parmName == null || parmName.length == 0)
			{
				throw "Empty parm passed to EventHandler request";
			}
			postData = postData + "&" + parmName + "=" + parmValue;
		}		
		return postData;			
	};

	// function to call a server method and process the response.
	handler.call = function(action, idValuePairs, responseHandler) {
	    // set the scope so that the yahoo connection utility calls our methods properly; see the yahoo documentation
	    this.scope = this;
	    // the multi-session key must be included with all callbacks.
	    var msk = extractMultiSessionKey();
	    if (msk) {
	        idValuePairs["_msk"] = msk;
	    }
	    // save client-supplied response handling info
	    this._processResponse = responseHandler;
	    var postData = "action=" + action + "&responseType=xml" + this._buildRequestParms(idValuePairs);
	    // this needs to be the last thing you do before you call the async request.
	    YAHOO.util.Connect.asyncRequest("POST", "Ajax.aspx", this, postData);
	};
	return handler;
}

// extract the multi-session key from form element or url
function extractMultiSessionKey() {
    var msk = null;
    var mskEl = document.getElementById("_msk");
    if (mskEl != null) {
        msk = mskEl.value;
    }
    // try to get the msk out of the current url
    else {
        var url = window.location + " "; // make sure it's a string
        var position = url.indexOf("_msk=")
        if (position >= 0) {
            msk = url.substring(position + 5);
            var position = msk.indexOf("&");
            if (position >= 0) {
                msk = msk.substring(0, position);
            }
        }
    }
    return msk;
}

// clear the contents of the given 'div' element; if possible, remove it from the DOM.
function clearElement(elementId)
{
	var element = document.getElementById(elementId);
	if (element != null)
	{
		var parentElement = element.parentNode;
		if (parentElement != null)
		{
			// this is the prefered behavior
			parentElement.removeChild(element);
		}
		else
		{
			// this is to evoke equivalent behavior in older browsers
			element.innerHTML = "";
		}
	}
}

// ====================== END UI Control Event Functions =============================


// ====================== BEGIN Floating Panel functionality =============================

function closePanel(panelName)
{
	try
	{
		var panelNameArray = panelName.split(",");

		for (var i=0; i<panelNameArray.length; i++)
		{
			var panelDiv = document.getElementById(panelNameArray[i]);
			if (panelDiv == null) 
			{
				alert("Cannot find "+panelNameArray[i]+contactCBORDMsg);
			}
			else
			{
				panelDiv.style.visibility = "hidden";
			}
		}
		
		var eventHandler = MakeEventHandler();		
		var idValuePairs = new Object();
		idValuePairs["panelIdStr"] = panelName;
		// call the server to handle the event
		eventHandler.call("ClosePanel", idValuePairs, controlEventNullResponse);
	}
	catch (e)
	{
		alert("Something is wrong...refresh page now confirm box");
	}
}

function openPanel(panelName)
{
	try
	{
		var panelNameArray = panelName.split(",");

		for (var i=0; i<panelNameArray.length; i++)
		{
			var panelDiv = document.getElementById(panelNameArray[i]);
			if (panelDiv == null) 
			{
				alert("Cannot find "+panelNameArray[i]+contactCBORDMsg);
			}
			else
			{
				panelDiv.style.visibility = "visible";
			}
		}
		
		var eventHandler = MakeEventHandler();
				
		var idValuePairs = new Object();
		idValuePairs["panelIdStr"] = panelName;
		// call the server to handle the event
		eventHandler.call("OpenPanel", idValuePairs, controlEventNullResponse);
	}
	catch (e)
	{
		alert("Something is wrong...refresh page now confirm box");
	}
}

function swapPanel(panelToOpenId, panelToCloseId, newPanelHeight)
{
	var panelToOpen = document.getElementById(panelToOpenId);
	var panelToClose = document.getElementById(panelToCloseId);
	panelToClose.style.visiblity="hidden";
	panelToClose.style.height=0;
	panelToOpen.style.visibility="visible";
	panelToOpen.style.height=newPanelHeight;
}
// ====================== END Floating Panel functionality =============================

// server callable function to set the scroll position of a newly rendered div
function setScrollPosition(divId, scrollTop, scrollLeft)
{
	if (divsToScrollOnRender == null)
	{
		divsToScrollOnRender = new Object();
	}
	divsToScrollOnRender[divId] = scrollTop+","+scrollLeft;
}

// call this on the page "done loading event"
function setDivScrollPositions()
{
	if (divsToScrollOnRender != null)
	{
		for (var divId in divsToScrollOnRender)
		{
			setDivScrollPos(divId, divsToScrollOnRender[divId]);
		}
	}
	divsToScrollOnRender = null;
}

// set the scroll position of the given div; 2nd argument should be of the form "top,left".
function setDivScrollPos(divId, scrollPositionsStr)
{
	if (scrollPositionsStr != null)
	{
		var scrollPosArray = scrollPositionsStr.split(',');
		var divElem = document.getElementById(divId);
		if (divElem != null && scrollPosArray.length == 2)
		{
		    var oldTop = divElem.scrollTop;
			divElem.scrollLeft = scrollPosArray[1];
		    divElem.scrollTop = scrollPosArray[0];
		    //var debugMsg = "setting scrollpos for div: " + divId + "; top=" + scrollPosArray[0] + "; left=" + scrollPosArray[1] + " actual: id=" + divElem.id + "; top=" + divElem.scrollTop + "; left=" + divElem.scrollLeft + " old top: " + oldTop;
		    //alert(debugMsg);
		}
	}
}

// handle div scroll events, by remembering which divs have scrolled
var divsThatScrolled = null;
// wait for the divs to finish rendering before setting their scroll position
var divsToScrollOnRender = null;

// on Scroll Event
function divOnScroll(divElem)
{
	if (divsThatScrolled == null)
	{
		divsThatScrolled = new Object();
	}
	divsThatScrolled[divElem.id] = divElem.scrollTop + "," + divElem.scrollLeft;
	// debug code:
	//document.title = "count="+scrollCount+"; divId=" + divElem.id + "; scrollTop=" + divElem.scrollTop + "; clientHeight=" + divElem.clientHeight;
}

// function called before a submit that remembers all the div positions
function sendDivsThatScrolled()
{
	if (divsThatScrolled != null)
	{
		for (var divId in divsThatScrolled)
		{
		    //alert("sending scrollpos for div: " + divId + "; " +divsThatScrolled[divId]);
		    updateDivScrollPos(divId, divsThatScrolled[divId]);
		}
		divsThatScrolled = null;
	}
}

// send the new posistion to the server
function updateDivScrollPos(divId, scrollPositions)
{
	var scrollPos = scrollPositions.split(",");
	if (scrollPos.length == 2)
	{
		var eventHandler = MakeEventHandler();
		var args = new Object();
		args["divId"] = divId;
		args["topPosStr"] = scrollPos[0];
		args["leftPosStr"] = scrollPos[1];
		eventHandler.call("SetDivScrollPosition", args, controlEventNullResponse);
	}
}

// we do not care what the response is in this case
function controlEventNullResponse(responseNode)
{
}

// set the style class dynamically for the given div; ignore if not found
function setDivStyleClass(divID, styleClassName)
{
	var div = document.getElementById(divID);
	if (div != null)
	{
		div.className=styleClassName;
	}
//	else
//	{
//		// for debugging:
//		alert("setDivStyleClass: Could not find div "+divID);
//	}
}
function onFocusToggle(element,text,style)
{

    if (element.value == text)
    {
        element.value = "";

    }
    element.className = style;
}
function onBlurToggle(element,text,style) {

     attr = element.attributes["isDefault"];
     if (element.value.length == 0) {
         element.value = text;
         element.className = style;         
         if (attr == null) {
             element.setAttribute("isDefault", "true");
         }
     }
     else {
         if (attr != null) {
             element.setAttribute("isDefault", "false");
         }
     }
}


// ====================== BEGIN EventHandler functionality ============================
function createEvents()
{
	var aTags = document.getElementsByTagName("a");
	for (var i=0; i<aTags.length; i++)
	{
		var hrefStr = aTags[i].href;
		if (hrefStr.substring(0,11)=="javascript:")
		{
			if (browser != "Netscape")
			{
				aTags[i].attachEvent("onclick", returnFalseIE);
			}
		}
	}
}

function returnFalseIE(evt)
{
	var elem = evt.srcElement;
	var hrefStr = elem.href;
    if (hrefStr.substring(0,11)=="javascript:")
	{
		var functionCall = hrefStr.substring(11, hrefStr.length);
		eval(functionCall);
		return false;
	}
	return true;
}
// ====================== END EventHandler functionality ============================
