// JUG Core library - 27th Feb 2010//====== Global Variables =========// Mouse coordinates (JugMouseCoords.x,JugMouseCoords.y)var JugMouseCoords = {x:0,y:0};// Object being draggedvar JugDragElem = null;// Object receiving mouse move messagesvar JugMoveElem = null;// Collection of DIVs not in document, recorded so "hidden" elements can still be found using JugElemvar JugDivs = {};	// map from ID to div//====== Exceptions =========// class SessionExpiry - thrown as an exception after handling server calls when the session has expiredfunction SessionExpiry(){}SessionExpiry.prototype.session = true;// Function for alerting user to an exceptionfunction JugShowException( e ){	if( e.session )	{		// Session has expired but has been recovered	}	else if( e.lineNumber == undefined )	{		alert( "Exception: " + e.message + " (" + e.FileName + "#" + e.line + ")" );	}	else	{		alert( "Exception: " + e.message + " (" + e.FileName + "#" + e.lineNumber + ")" );	}}//======= Unique ID counter =========var JugIdCounter = 0;function JugGetUniqueId( prefix ){	JugIdCounter += 1;	if( prefix === undefined || prefix == "" )	{		return "UID" + JugIdCounter;	}	else	{		return prefix + JugIdCounter;	}}//====== Basic helper funcions =========// Lookup a document or JUG element by IDfunction JugElem( id ){	// RETURNS integer element ID	var elem = document.getElementById( id );	if( elem == null )	{		elem = JugDivs[ id ];		if( !elem )		{			throw new Error( "Element " + id + " not found" );		}	}	return elem;}// Check if a document or JUG element existsfunction JugElemExists( id ){	// RETURNS bool	var elem = document.getElementById( id );	if( elem == null )	{		elem = JugDivs[ id ];		if( !elem )		{			return false;	// element not found		}	}	return true;}// Clear an element's id to remove it from the DOM or JUGfunction JugClearId( id ){	var elem = document.getElementById( id );	if( elem == null )	{		delete JugDivs[ id ];	}	else	{		elem.id = "";	// clear element's id so it is no longer named in the DOM		if( document.getElementById( id ) )		{			alert( "element id not changed" );		}	}}//====== Basic map funcions =========// A map is an associative array// Create a string representation of a map, of the form "{ k1 : v1, ... }"function PrintMap( map ){	var result = "{";	var sep = "";	var key;	for( key in map )	{		result = result + sep + key + ":" + map[ key ];		sep = ", ";	}	return result + "}";}// Invert a map from k-to-v to produce a map from v-to-kfunction InvertMap( map ){	var newMap = {};	for( elem in map )	{		newMap[ map[ elem ] ] = elem;	}	return newMap;}// Return an array holding the key values of a mapfunction KeysOfMap( map ){	var result = [];	for( var k in map )	{		result.push( k );	}	return result;}// Return an array holding the values of a mapfunction ValuesOfMap( map ){	var result = [];	for( var k in map )	{		result.push( map[k] );	}	return result;}function JoinMaps( map1, map2 ){	var result = {};	for( var k1 in map1 )	{		result[ k1 ] = map1[ k1 ];	}	for( var k2 in map2 )	{		result[ k2 ] = map2[ k2 ];	}	return result;}//===== Get size of page (may not all be visible =====/function JugGetWindowArea(){	// RETURNS { integer left, integer top, integer width, integer height }	var w;	var h;	if( typeof( window.innerWidth ) == 'number' )	{	    // Netscape		w = window.innerWidth;		h = window.innerHeight;	} 	else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) 	{	    //IE 6+ in 'standards compliant mode'		w = document.documentElement.clientWidth;		h = document.documentElement.clientHeight;	} 	else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) 	{	    //IE 4 compatible		w = document.body.clientWidth;		h = document.body.clientHeight;	}	var x;	var y;	if( typeof( window.pageYOffset ) == 'number' ) 	{		//Netscape compliant		x = window.pageXOffset;		y = window.pageYOffset;	} else if( document.body && document.body.scrollLeft !== undefined && document.body.scrollTop !== undefined )	{		//DOM compliant		x = document.body.scrollLeft;		y = document.body.scrollTop;	} else if( document.documentElement && document.documentElement.scrollLeft !== undefined && document.documentElement.scrollTop !== undefined )	{		//IE6 standards compliant mode		x = document.documentElement.scrollLeft;		y = document.documentElement.scrollTop;	}	else	{		x = 0;		y = 0;	}	return { left : x, top : y, width : w, height : h };}//====== Get area of an element =======function JugGetElementArea( elem ){	// elem    integer element ID of some element	// RETURNS { integer left, integer top, integer width, integer height } giving area of element	if( elem.offsetParent )	{		// the element has a parent so its top left coordinates are relative to the those of the parent object		var p = elem;	// walk the ancestor list, starting with the given element		var left = 0;	// accumulate the coordinates of the top left corner		var top = 0;		for(;;)		{			left += p.offsetLeft;			top += p.offsetTop;			p = p.offsetParent;		// climb the ancestor tree			if( p == null )			{				return { top: top, left: left, width: elem.offsetWidth, height : elem.offsetHeight };			}		}	}	else	{		// the element has no parent object, so its coordinates are absolute		return { top: elem.y, left: elem.x, width: elem.width, height : elem.height };	}}//====== Check if a point is within some area ========function JugWithin( point, area ){	return area.left <= point.x && point.x < area.left + area.width	   	&& area.top <= point.y && point.y < area.top + area.height;}//====== Constrain area to page =======function JugConstrainToPage( area ){	// area    { integer left, integer top, integer width, integer height } describing some area	// RETURNS { integer left, integer top, integer width, integer height } giving area trimmed to fit page		var page = JugGetWindowArea();		if( area.top < 0 )	{		area.height += area.top;	// reduce height so next test works		area.top = 0;	}	if( area.top + area.height >= page.height - 1 )		{		area.top = page.height - area.height - 1;	}	if( area.left < 0 )	{		area.width += area.Left;	// reduce width so next test works		area.left = 0;	}	if( area.left + area.width >= page.width - 1 )	{		area.left = page.width - area.width - 1;	}		return area;}//====== Constrain area to window =======function JugConstrainToWindow( area ){	// area    { integer left, integer top, integer width, integer height } describing some area	// RETURNS { integer left, integer top, integer width, integer height } giving area trimmed to fit window		var win = JugGetWindowArea();	if( area.top < win.top )	{		area.height -= win.top - area.top;	// reduce height so next test works		area.top = win.top;	}	if( area.top + area.height >= win.top + win.height - 20 )				// lost an extra 20 since IE counts scroll bars	{		area.top = win.top + win.height - area.height - 20;	}	if( area.left < win.left )	{		area.width -= win.left - area.left;	// reduce width so next test works		area.left = win.left;	}	if( area.left + area.width >= win.left + win.width - 20 )	{		area.left = win.left + win.width - area.width - 20;	}		return area;}//====== Handle values ======// convert a number to a hex string representation of at least a given widthfunction toHex( n, width ){	var h = n.toString( 16 );	while( h.length < width )	{		h = "0" + h;	}	return h;}// convert an rgb or rgba colour value to a hex colour valuefunction colourToHex( c ){	function hexrgba(r,g,b,a)	{		return toHex( r, 2 ) + toHex( g, 2 ) + toHex( b, 2 );	}	function hexrgb(r,g,b)	{		return toHex( r, 2 ) + toHex( g, 2 ) + toHex( b, 2 );	}	return "#" + eval( "hex" + c );}//====== Handle styles ======function getStyle( elem ) {    if( elem.currentStyle ) 	{        return elem.currentStyle;    }	else if( window.getComputedStyle ) 	{        return window.getComputedStyle( elem, "" );    }}var styleMap = { "background-color":"backgroundColor", "color":"color" };function getStyleElement( elem, cssStyle ) {    if( elem.currentStyle ) 	{        return elem.currentStyle[ styleMap[ cssStyle ] ];    }	else if( window.getComputedStyle ) 	{        return window.getComputedStyle( elem, "" ).getPropertyValue( cssStyle );    }}//====== Number Formatting =======// Format a number as a string with leading zeroesfunction JugNumberToStringPadZero( value, width ){	var s = "" + value;	while( s.length < width )	{		s = "0" + s;	}	return s;}//====== Date Handling ========var JugDays = ["--","1st","2nd","3rd","4th","5th","6th","7th","8th","9th","10th",		       "11th","12th","13th","14th","15th","16th","17th","18th","19th","20th",		       "21st","22nd","23rd","24th","25th","26th","27th","28th","29th","30th","31st" ];var JugMonths = ["---","January","February","March","April","May","June","July","August","September","October","November","December"];// Convert from date value ("YYYY-MM-DD") to date string ("nnth Month YYYY")function JugDateValueToString( dateValue ){	if( dateValue == "0000-00-00" )	{		return "";	}	else	{		var year = parseInt( dateValue.substr(0,4) ); 		var month = parseInt( dateValue.substr(5,2) );		var day = parseInt( dateValue.substr( 8,2) );		return JugDays[ day ] + " " + JugMonths[ month ] + " " + year;	}}// Convert from date value ("YYYY-MM-DD") to date structure {day:d,month:m,year:y}function JugDateValueToStruct( dateValue ){	if( dateValue == "" )	{		return { day:0, month:0, year:0 };	}	else	{		var year = parseInt( dateValue.substr(0,4) ); 		var month = parseInt( dateValue.substr(5,2) );		var day = parseInt( dateValue.substr( 8,2) );		return { day:day, month:month, year:year };	}}// Convert date structure to date valuefunction JugDateStructToValue( dateStruct ){	return 		    JugNumberToStringPadZero( dateStruct.year, 4 ) 			+ "-" + JugNumberToStringPadZero( dateStruct.month, 2 ) 			+ "-" + JugNumberToStringPadZero( dateStruct.day, 2 );}// Get date structure for todayfunction JugDateToday(){	var d = new Date();	return { day: d.getDate(), month: d.getMonth() + 1, year: d.getFullYear() };}// Get date structure for yesterdayfunction JugDateYesterday(){	var d = new Date();	d.setDate( d.getDate()-1 );		return { day: d.getDate(), month: d.getMonth() + 1, year: d.getFullYear() };}//====== Actions =========// Send a message to an element and any child elementsfunction JugSendMessage( element, eventName, args ){	// First check this element	if( element.JugType !== undefined && element[ eventName ] !== undefined )	{		// this is a JUG element that defines a handler for the event		var result = element[ eventName ].apply( this, args );		if( result === false )		{			return false;	// do not pass message on		}	}		// Now check children - assume no more nodes are added, but some may be deleted	var n = element.childNodes.length;	var i;	for( i=0; i < n; i++ )	{		if( element.childNodes[i] !== undefined && JugSendMessage( element.childNodes[i], eventName, args ) === false)		{			return false;	// stop trying		}	}		return true;	// keep trying}//====== Default Button interface =========var JugDefaultButtonId = null;	// the current default button object id// Get the default buttonfunction JugGetDefaultButton(){	return JugDefaultButtonId;}// Set the default buttonfunction JugSetDefaultButton( id ){	// id	integer ID of JUG button that is to become the default		JugElem( id ).setDefault();	JugDefaultButtonId = id;}// Make all buttons be non-defaultfunction JugSetNoDefaultButton(){	if( JugDefaultButtonId != null )	{		JugElem( JugDefaultButtonId ).setNoDefault();		JugDefaultButtonId = null;	}}//==== Simple Pop Up confirmation dialog ====function JugPopUpConfirm( legend, okText, okAction, cancelText, cancelAction, defaultButton, normalStyle, defaultStyle, depressedStyle ){	// Displays a simple two-button pop up modal dialog	// legend -         The legend text for the dialog	// okText -         The text for the OK button	// action -         Function that is called if the OK button is clicked	// cancelText -     The text for the cancel button	// cancelAction -	null, or function that is called if the Cancel button is clicked	// defaultButton -  Indicates which button is the default - cancelText, okText or "" for neither	// normalStyle -    Style class name for the buttons in their normal state, default to JugNormalButton	// defaultStyle -   Style class name for the default button, if any, default to JugDefaultButton	// depressedStyle - Style class name for buttons when depressed, default to JugDepressedButton		if( normalStyle === undefined )	{		normalStyle = "JugNormalButton";	}	if( defaultStyle === undefined )	{		defaultStyle = "JugDefaultButton";	}	if( depressedStyle === undefined )	{		depressedStyle = "JugDepressedButton";	}		var background = document.createElement("div");	background.style.position = "absolute";	background.style.filter = "alpha(opacity=50)";	background.style.MozOpacity = ".50";	background.style.opacity = ".50";	background.style.background = "#000000";	background.style.position ="absolute";	background.style.top = "0px";	background.style.left = "0px";	background.style.width = "100%";	background.style.height = "100%";    // create a div that can be positioned	var dialog = document.createElement("div");	dialog.style.position = "absolute";	dialog.style.top = "100px";	dialog.style.left = "100px";	// remember any existing default button	var oldDefaultButton = JugDefaultButtonId;	JugSetNoDefaultButton();	// disable any default button	// create a table within the positioned div	var content = document.createElement("table");	content.style.background = "#ffffff";	content.style.border = "4px solid #999999"	var row1 = content.insertRow(-1);		// the legend row	var row2 = content.insertRow(-1);		// the buttons row	var row1col1 = row1.insertCell(-1);		// the legend text	var row2col1 = row2.insertCell(-1);	var row2col2 = row2.insertCell(-1);	dialog.appendChild(content);	// add the text legend to the first row	var text = document.createTextNode( legend );	row1col1.colspan = 2;	row1col1.appendChild( text );	// add the cancel button to the second row	var cancelButton = document.createElement("input");	cancelButton.type = "button";	cancelButton.value = cancelText;	cancelButton.JugType = "popupButton";	// give the cancel button an id in case it is the default	cancelButton.id = JugGetUniqueId( "JugPopUpConfirm" );			// generate a new unique ID for the cancel button	cancelButton.setNoDefault = function(){};	// define the onclick action for the cancel button - it just closes the dialog	function clickCancel()	{		// the cancel button has been clicked 		if( cancelAction )		{			try{				cancelAction();	// run the user action			}			catch( e )			{				JugShowException( e );			}		}		// remove the dialog		dialog.parentNode.removeChild( dialog );		background.parentNode.removeChild( background );				// restore the default button		JugSetDefaultButton( oldDefaultButton );			return false;	// do not propagate <return> message if default button clicked	}	// setup the button's style	and actions	if( defaultButton == cancelText )	{		cancelButton.className = defaultStyle;		JugDefaultButtonId = cancelButton.id;		cancelButton.onReturn = clickCancel;	}	else	{		cancelButton.className = normalStyle;	}	cancelButton.onclick = clickCancel;	row2col1.appendChild( cancelButton );	// add the ok button to the second row	var okButton = document.createElement("input");	okButton.type = "button";	okButton.value = okText;	okButton.JugType = "popupButton";	// give the ok button an id in case it is the default	okButton.id = JugGetUniqueId( "JugPopUpConfirm" );			// generate a new unique ID for the ok button	okButton.setNoDefault = function(){};	// define the onclick action for the ok button - it runs the supplied OK Action and closes the dialog	function clickOk()	{		// the ok button has been clicked 		try{			okAction();	// run the user action		}		catch( e )		{			JugShowException( e );		}				// remove the dialog		dialog.parentNode.removeChild( dialog );		background.parentNode.removeChild( background );		// restore the default button		JugSetDefaultButton( oldDefaultButton );					return false;	// do not propagate <return> message if default button clicked	}	// setup the button's style	and actions	if( defaultButton == okText )	{		okButton.className = defaultStyle;		JugDefaultButtonId = okButton.id;		okButton.onReturn = clickOk;	}	else	{		okButton.className = normalStyle;	}	okButton.onclick = clickOk;		row2col2.appendChild( okButton );	// display the transparent grey background and the dialog on top of it	document.body.appendChild(background);	document.body.appendChild(dialog);}//==== General Pop Up dialog class ====// JUG factory functionfunction JugSetupPopupDialog( id, props ){	// id		string id of element that is to be converted into a JUG popup dialog	// props	string property set of the JUG dialog	//			{  }		// get the element that is to be converted	var elem = JugElem( id );	elem.parentNode.removeChild( elem );	// remember the moveable frame as a JUG object that is not in the DOM until needed	JugDivs[ id ] = elem;	    // create a div that can be positioned	var dialog = document.createElement("div");	dialog.style.position = "absolute";	dialog.style.background = "#ffffff";	dialog.style.top = "100px";	dialog.style.left = "100px";	dialog.appendChild( elem );	// create a background to make the dialog modal	var background = document.createElement("div");	background.style.position = "absolute";	background.style.filter = "alpha(opacity=50)";	background.style.MozOpacity = ".50";	background.style.opacity = ".50";	background.style.background = "#000000";	background.style.position ="absolute";	background.style.top = "0px";	background.style.left = "0px";	background.style.width = "100%";	background.style.height = "100%";	var oldDefault = null;		// this is set to the old default button while the dialog is showing		elem.show = function()	{		// display the transparent grey background and the dialog on top of it		document.body.appendChild(background);		document.body.appendChild(dialog);				oldDefault = JugDefaultButtonId;		JugSetNoDefaultButton();	// disable any default button	}		elem.hide = function()	{		background.parentNode.removeChild(background);		dialog.parentNode.removeChild(dialog);				// restore the old default button		if( oldDefault )		{			JugSetDefaultButton( oldDefault );				oldDefault = null;		}	}		elem.setPosition = function( x, y )	{		var area = { left : x, top : y, width : dialog.offsetWidth, height : dialog.offsetHeight };		area = JugConstrainToPage( area );		dialog.style.left = area.left + "px";		dialog.style.top = area.top + "px";	}}//====== Standard Button interface =========// JUG factory functionfunction JugSetupButton( id, props ){	// id		string id of button element that is to be converted into a JUG button	// props	string property set of the JUG button	//			{ function props, optional string status }		// get the button element that is to be converted	var buttonElem = JugElem( id );	// pick out the properties	var clickAction = props.action;	var statusMessage = props.status;	var normalStyle = props.normal;	var defaultStyle = props.def;	var disabledStyle = props.disable;	var depressedStyle = props.depressed;	var name = ( buttonElem.name == "" ? "Button " + id : buttonElem.name );	// fill in the defaults for the various styles	if( normalStyle === undefined )	{		normalStyle = "JugNormalButton";	}	if( defaultStyle === undefined )	{		defaultStyle = "JugDefaultButton";	}	if( depressedStyle === undefined )	{		depressedStyle = "JugDepressedButton";	}	if( disabledStyle === undefined )	{		disabledStyle = "JugDisabledButton";	}	var enabled = true;		if( statusMessage === undefined )	{		statusMessage = "";	}	// if no action property is defined, use the object's onclick handler	if( clickAction === undefined )	{		if( buttonElem.onclick )		{			clickAction = buttonElem.onclick;		}		else		{			clickAction = function(){ alert( "No action defined for button " + id + "(" + name + ")" ); };		}	}	// variable to hold timeout object used to run button script asynchronously	var actionTimer = null;	// called asynchronously when button is clicked	function runClickAction()	{		clickAction( buttonElem );			// call the user defined click action, passing the button element as a parameter		// restore the styling		if( JugDefaultButtonId == id )		{			buttonElem.className = defaultStyle;		}		else if( enabled )		{			buttonElem.className = normalStyle;		}		else		{			// not enabled, so button action must've disabled it otherwise we couldn't be clicked in the first place			buttonElem.className = disabledStyle;		}		window.status = "";					// clear the window status		actionTimer = null;	}	// onclick event handler for button	buttonElem.onclick = function( evt )	{		if( enabled && actionTimer == null )		{			buttonElem.className = depressedStyle;			window.status = statusMessage;					// display the status message while the action runs			actionTimer = setTimeout( runClickAction, 1 );	// schedule the action to run		}	}		var amDefault = false;	// record whether this button is the default button	buttonElem.className = normalStyle;		// initially use normal styling		// render this as the default button	buttonElem.setDefault = function()	{		if( JugDefaultButtonId != null && JugElemExists(JugDefaultButtonId) )	// check the button is not hidden		{			JugElem( JugDefaultButtonId ).setNoDefault();		}		buttonElem.className = defaultStyle;		JugDefaultButtonId = id;		amDefault = true;	}	// render this as normal, non-default, button	buttonElem.setNoDefault = function()	{		// this button is not default		if( enabled )		{			buttonElem.className = normalStyle;		}		else		{			buttonElem.className = disabledStyle;		}		amDefault = false;	}	// event handler called when RETURN is pressed	buttonElem.onReturn = function()	{		if( amDefault )		{			if( enabled && actionTimer == null )			{				// RETURN pressed and this is the default button, so count it as a click				buttonElem.className = depressedStyle;				window.status = statusMessage;				actionTimer = setTimeout( runClickAction, 1 );			}			return false;	// do not pass the message on		}		else		{			return true;	// pass the message on		}	}	// Enable / disable the button	buttonElem.enable = function( enb )	{		enabled = enb;		if( enabled )		{			buttonElem.className = normalStyle;		}		else		{			buttonElem.className = disabledStyle;		}	}		// Get whether the button is enabled	buttonElem.isEnabled = function()	{		return enabled;	}		buttonElem.JugType = "button";}//========= Sticky Note ========// JUG factory functionfunction JugShowSticky( text, id, styleName ){	// text		text of the sticky	// id		string id of element where the note is to be placed, or null if it is to be centred	var container = document.createElement('div');	// div holding the sticky note		if( styleName )	{		container.className = styleName;	}	else	{		container.style.background = "#F0F0F0";		container.style.borderStyle = "outset";	}	container.style.position = "absolute";	container.style.width = 120 + "px";	if( id )	{		var area = JugGetElementArea( JugElem( id ) );		container.style.left = (area.left + area.width) + "px";		container.style.top = (area.top + area.height) + "px";	}	else	{		var area = JugGetWindowArea();		container.style.left = (area.left + area.width / 2) + "px";		container.style.top = (area.top + area.height / 2) + "px";	}		container.onmousedown = function()	{		container.parentNode.removeChild( container );	}		container.deleteSticky = function()	{		container.parentNode.removeChild( container );		return true;	}		container.appendChild( document.createTextNode( text ) );	document.body.appendChild( container );		container.JugType = "sticky";}function JugDeleteStickies(){	JugSendMessage( document.body, "deleteSticky", [] );}//============ Tabbed display ====================// JUG factory functionfunction JugCreateTabbedDisplay( id, props ){	// id		string id of div element that is to become a tabbed display	// props	string property set of the JUG tabbed display	//			{ string tabList, function change }  - tablist is comma separated list of tab-name:div-name	var tabs = props.tabList.split( "," );	// array of "tab names : div names" for the tabs in the set	var change = eval( props.change );		// find the element that is to be the tab set	var tabsElem = JugElem( id );	// create a div to push the tab bodies below the tab header	var space = document.createElement( 'div' );	space.style.width = 1 + "px";	space.style.height = 1 + "px";	tabsElem.appendChild( space );		// currently don't know the height, but it gets set properly once it is known	var gap = 10;			// gap between tabs in the tab header	var x = 0;				// becomes the width of the tab header	var h = 0;				// becomes the height of the tab header	var tabSet = {};		// map from div name to tab header div object	var bodySet = {};		// map from div name to tab body div object	var enabledSet = {};	// map from div name to enabled bool	var currentTab = "";	function selectTab( divName )	{		if( enabledSet[ divName ] )		{			for( name in tabSet )			{				var tab = tabSet[ name ];				var tabBody = bodySet[ name ];				if( name == divName )				{					// this is the tab that is to be selected					tabBody.style.display = "";					tab.selected = true;					tab.style.backgroundColor = "#c0c0c0";					tab.style.borderBottomWidth = 0;					tab.style.height = h - 4;										JugSendMessage( tabBody, "onDisplay", [] );					if( change )					{						change( currentTab, divName );					}					currentTab = divName;				}				else				{					// this tab is not selected					tabBody.style.display = "none";					tab.selected = false;					tab.style.backgroundColor = "#f0f0f0";					tab.style.borderBottomWidth = "1px";					tab.style.height = h - 5;				}			}		}	}	function enableTab( divName, enabled )	{		enabledSet[ divName ] = enabled == true;	}	var i;	for( i in tabs )	{		var tabsSplit = tabs[ i ].split( ":" );		var tabName = tabsSplit[ 0 ];		var divName = tabsSplit[ 1 ];				var text = document.createTextNode( tabName );		var div = JugElem( divName );		var tab = document.createElement( 'a' );				tab.style.backgroundColor = "#f0f0f0";		tab.style.border = "1px solid #000000";		tab.style.borderBottomWidth = 0;		tab.style.padding = "2px 1em 2px 1em";		tab.style.textDecoration = "none";				tab.style.position = "absolute";		tab.style.left = (tabsElem.offsetLeft + x) + "px";		tab.style.top = tabsElem.offsetTop + "px";				tab.selected = false;		// set up the event handlers for the tab 		tab.onmouseover = function( tab )		{			return function()			{				if( tab.selected )				{					tab.style.backgroundColor = "#c0c0c0";				}				else				{					tab.style.backgroundColor = "#d0d0d0";				}				tab.oldCursor = tab.style.cursor;				tab.style.cursor = "pointer";			}		}( tab );				tab.onmouseout = function( tab )		{			return function()			{				if( tab.selected )				{					tab.style.backgroundColor = "#c0c0c0";				}				else				{					tab.style.backgroundColor = "#f0f0f0";				}				tab.style.cursor = tab.oldCursor;			}		}( tab );				tab.onclick = function( divName )		{			return function()			{				if( currentTab != divName )				{					selectTab( divName );				}			}		}( divName );		tab.JugType = "tab";		tab.onDisplay = function( tab )		// called when element gets displayed		{			return function()			{				tab.style.top = tabsElem.offsetTop + "px";			}		}( tab );		tab.onResize = function( tab )		// called when window gets resized		{			return function()			{				tab.style.top = tabsElem.offsetTop + "px";			}		}( tab );		// remember the tab in the tab set		tabSet[ divName ] = tab;				// add the tab to the tab set header		tab.appendChild( text );		tabsElem.appendChild( tab );		tabsElem.appendChild( div );		// calculate the size of the tab set header		x = x + tab.offsetWidth + gap;		h = Math.max( h, tab.offsetHeight );		// hide the tab body//		div.style.display = "none";		div.style.backgroundColor = "#c0c0c0";		div.style.border = "1px solid #000000";		div.style.padding = "2px 1em 2px 1em";		// remember the tab body in the set of tab bodies		bodySet[ divName ] = div;				// enabled by default		enabledSet[ divName ] = true;			} // end loop over tab names		// set the spacers height to the correct value	space.style.height = h;	tabsElem.select = selectTab;	tabsElem.enable = enableTab;	tabsElem.JugType = "TabSet";	var firstDivName = tabs[ 0 ].split( ":" )[ 1 ];		tabsElem.onJugLoaded = function()					// called when the page has been loaded	{		// All loaded, now select the first tab		selectTab( firstDivName );	};}//============ Menu and MenuItem =========// JUG factory function for Menufunction JugCreateMenu( id, props ){	// id		string id of table element that is to become a menu	// props	string property set of the JUG menu - use {}		// find the div element that is to be the menu	var menuElem = JugElem( id );	JugClearId( id );		// clear element's ID as it is going to be given to the floating div	// create a div to float the menu	var container = document.createElement('div');	// div holding the entire modal frame	container.id = id;	container.style.position = "absolute";	container.style.left = 0 + "px";	container.style.top = 0 + "px";	container.style.background = "#FFFFFF";	container.appendChild( menuElem );		// prevent the div's children's text being selected	container.onselectstart = function () { return false; } // ie  	container.onmousedown = function () { return false; } // mozilla	container.JugType = "menu";	JugDivs[ id ] = container;	// add container to set of divs not in document	var oldDefaultButtonId = null;	// the ID of the button that was default when the frame is shown	// Create methods for hiding/showing the frame		var area = null;		var parent = null;	container.show = function( foreground, background )	{		document.body.appendChild( container );				// now position the dialog at the mouse coordinates		container.style.left = (JugMouseCoords.x - 8) + "px";		container.style.top = (JugMouseCoords.y - 8) + "px";		// adjust to fit page		area = JugConstrainToPage( JugGetElementArea( container ) );		container.style.left = area.left + "px";		container.style.top = area.top + "px";				JugSendMessage( this, "ready", [this,foreground,background] );		parent = JugMoveElem;		JugMoveElem = container;				oldDefaultButtonId = JugDefaultButtonId;		JugSetNoDefaultButton();	}	container.hide = function()	{		container.parentNode.removeChild( container );				JugMoveElem = parent;		JugSetDefaultButton( oldDefaultButtonId );		oldDefaultButtonId = null;	}		container.selected = function()	{		container.hide();		if( parent != null )		{			// ripple up the tree......			parent.hide();		}	}		container.ready = function()	{		// we do nothing - this message is intended for our children	}		container.mouseMove = function()	{		if( !JugWithin( JugMouseCoords, area ) )		{			container.hide();		}	}}// JUG factory function for MenuItemfunction JugCreateMenuItem( id, props ){	// id		string id of text input that is to become a menu item	// props	string property set of the JUG menu item	//			{ function action, value result }		var action = props.action;		// take the element and use it as the menu legend	var elem = JugElem( id );	var oldBackground = getStyleElement( elem, "background-color" );	var oldForeground = getStyleElement( elem, "color" );	elem.newForeground = props.foreground;	elem.newBackground = props.background;		// prevent the anchor's text being selected	elem.onselectstart = function () { return false; } // ie  	elem.onmousedown = function () { return false; } // mozilla	var parent = null;	elem.enabled = true;		// whether this menu item is enabled		elem.onmouseout = function()	{		this.style.cursor = originalCursor;		if( elem.enabled )		{			elem.style.color = oldForeground;			elem.style.backgroundColor = oldBackground;		}	}	elem.onmouseover = function()	{		originalCursor = this.style.cursor;				// remember the cursor style so it can be restored		this.style.cursor = "pointer";					// set the cursor to "pointer" while the mouse is over the button		if( elem.enabled )		{			if( elem.newBackground )			{				elem.style.color = elem.newForeground;			}			if( elem.newForeground )			{				elem.style.backgroundColor = elem.newBackground;			}		}	}	elem.ready = function( container, foreground, background )	{		parent = container;		// remember the foreground/background colours as properties of the element so any derivative can access them		elem.newForeground = foreground;		elem.newBackground = background;	}		elem.onmouseup = function()	{		if( elem.enabled )		{			parent.selected();			if( action != null )			{				action( elem );			}		}	}		elem.id = id;	elem.JugType = "menuItem";}// JUG factory function for SubMenufunction JugCreateSubMenu( id, props ){	// id		string id of menu item	// props	string property set of the JUG menu value	//			{ child name of sub menu }		var menuItem = JugElem( id );	// the menu item element		var child = props.child;		// name of child menu to display if this item is selected	menuItem.onmouseup = function()	{		if( menuItem.enabled )		{			var foreground = ( props.foreground ? props.foreground : menuItem.newForeground );			var background = ( props.background ? props.background : menuItem.newBackground );				var subMenu = JugElem( child );			subMenu.show( foreground, background );		}	}}// JUG factory function for OptionalItem menu itemsfunction JugCreateOptionalItem( id, props ){	// id		string id of optional item	// props	string property set of the JUG menu value	//			{ optional = function to check whether element is enabled, optionalForeground = colour to use if disabled }	var elem = JugElem( id );	var optionalTest = props.optional;		var oldForeground = getStyleElement( elem, "color" );	var oldReady = elem.ready;	elem.ready = function()	{		if( optionalTest && optionalTest( elem ) )		{			elem.enabled = true;			elem.style.color = oldForeground;		}		else		{			elem.enabled = false;			elem.style.color = props.optionalForeground;		}				if( oldReady )		{			oldReady.apply( this, arguments );		}	}}// JUG factory function for dynamic menusfunction JugCreateDynamicMenu( id, props ){	// id		string id of dynamic menu item	// props	string property set of the JUG dynamic menu	//			{ optional = function to check whether element is enabled, optionalForeground = colour to use if disabled }	var menuElem = JugElem( id );	var n = menuElem.childNodes.Length;		var action = props.action;	var maxHeight = (props.maxHeight ? props.maxHeight : 200 );	var oldForeground;	var oldBackground;		var oldReady = menuElem.ready;	menuElem.ready = function( container, foreground, background )	{		var parent = container;				// Remove any existing nodes from the menu		var menu = menuElem.firstChild;		// the menu is a div that contains a div with the menu items in it 		while( menu.hasChildNodes() )		{	        menu.removeChild( menu.firstChild );		}		var contents = props.content( menuElem );		var recents = contents.recents;		var values = contents.values;				var itemHeight;	// this is set to the height of an item, allowing us to scroll to a particular item when a key is struck - all items are the same height		var items = new Array();	// builds into an array of the items, allowing them to be selected by key-down		var currentItem = null;		// refers to currently selected item				function addItem( menu, e, v )		{			var d = document.createElement( 'div' );			var text = document.createTextNode( e );			d.appendChild( text );				menu.appendChild( d );			items.push( d );						// remember height of item			itemHeight = JugGetElementArea( d ).height;						d.onmouseover = function()			{				originalCursor = this.style.cursor;				// remember the cursor style so it can be restored				this.style.cursor = "pointer";					// set the cursor to "pointer" while the mouse is over the item								if( currentItem != null )				{					// some other item is selected (by key clicks) and so must be de-selected					currentItem.onmouseout();				}				currentItem = d;		// the highlighted item						oldForeground = d.style.color;				if( foreground )				{					d.style.color = foreground;				}				oldBackground = d.style.backgroundColor;				if( background )				{					d.style.backgroundColor = background;				}			}						d.onmouseout = function()			{				this.style.cursor = originalCursor;				if( currentItem != null && currentItem != d )				{					// some other item is selected (by key clicks) and so must be de-selected					currentItem.onmouseout();				}								currentItem = null;								d.style.color = oldForeground;				d.style.backgroundColor = oldBackground;			}			d.onmouseup = function()			{				parent.selected();				if( action != null )				{					action( v );				}			}		}				// add the recents 		for( e in recents )		{			addItem( menu, e, recents[ e ] );		}				if( recents.length != 0 && values.length != 0 )		{			// add a dividing bar to separate the recents from the main list			var bar = document.createElement( 'hr' );			menu.appendChild( bar );		}				// add the top scroll bar, zero sized until proved to be needed		var topBar = document.createElement( 'div' );		topBar.style.height = "0px";		topBar.style.backgroundColor = "#202020";		menu.appendChild( topBar );				// add div that clips the area for the main list of values, with a div inside it to hold the actual list		var clipArea = document.createElement( 'div' );		menu.appendChild( clipArea );		var scrollArea = document.createElement( 'div' );		clipArea.appendChild( scrollArea );		// add the bottom scroll bar, zero sized until proved to be needed		var bottomBar = document.createElement( 'div' );		bottomBar.style.height = "0px";		bottomBar.style.backgroundColor = "#202020";		menu.appendChild( bottomBar );		// add the main list of values		items = new Array();		// reset the array of items so we do not record the recents list		for( e in values )		{			addItem( scrollArea, e, values[ e ] );		}		var actualHeight = JugGetElementArea( clipArea ).height;		var minOffset = -(actualHeight - maxHeight);	// most negative offset leaves last list element at the bottom of the scroll area		if( actualHeight > maxHeight )		{			// reveal the scroll bars			topBar.style.height = "10px";			bottomBar.style.height = "10px";			// add the mouse-over handlers			var interval;										// interval object			// when the mouse moves over one of the scroll bars, start an interval timer that scrolls the area in the appropriate direction			function makeMouseOver( delta )			{				function doScroll()				{					var top = parseInt( scrollArea.style.top );					scrollArea.style.top = Math.min( 0, Math.max( minOffset, top + delta ) ) + "px";				}				return function()				{					interval = setInterval( doScroll, 5 );				}			}			topBar.onmouseover = makeMouseOver( +1 );			bottomBar.onmouseover = makeMouseOver( -1 );			// when the mouse moves out of the scroll bar, cancel the timer			topBar.onmouseout = function()			{				clearInterval( interval );			}			bottomBar.onmouseout = topBar.onmouseout;				var w = parseInt( JugGetElementArea( clipArea ).width );			// position the clip area relative so the scroll area within it can be positioned absolute to effect the scrolling			clipArea.style.position = "relative";			clipArea.style.width = w + "px";			clipArea.style.height = maxHeight + "px";			// make the clip area hide the scroll area where it overflows the clip area			clipArea.style.overflow = "hidden";			clipArea.style.overflowX = "hidden";			// place the scroll area in an absolute position within the clip area			scrollArea.style.position = "absolute";			scrollArea.style.width = w + "px";			scrollArea.style.height = maxHeight + "px";			scrollArea.style.top = "0px";			scrollArea.style.left = "0px";		}				// handle key-down events				var searchString = "";	// the search string typed so far		var searchTimer;		// timer that clears the search string				menuElem.onKeydown = function( key )		{			function clearSearch()			{				searchString = "";			}			clearTimeout( searchTimer );			searchTimer = setTimeout( clearSearch, 500 );						searchString = searchString + key.toUpperCase();						var index = 0;			for( e in values )			{				var name = e.toUpperCase();				if( name.length >= searchString.length && name.substring( 0, searchString.length ) == searchString )				{					scrollArea.style.top = Math.max( minOffset, - index * itemHeight ) + "px";					if( currentItem != null )					{						currentItem.onmouseout();					}					currentItem = items[ index ];					currentItem.onmouseover();					break;				}				index += 1;			}			return false;		}				menuElem.onReturn = function()		{			if( currentItem != null )			{				currentItem.onmouseup();			}			return false;		}				if( oldReady )		{			oldReady.apply( this, arguments );		}	}}//============ Grid =========// JUG factory function for Menufunction JugCreateGrid( id, props ){	// id		string id of grid element	// props	string property set of the JUG grid	var container = JugElem( id );		var legends = props.legends.split(",");	var highliteStyle = props.highlite;	var normalStyle = props.normal;	var action = props.action;		// work out which columns are smart	var smart = new Array();	var i;	for( i in legends )	{		if( legends[ i ].substring(0,1) == '*' )		{			smart[ i ] = true;			legends[ i ] = legends[ i ].substring(1);		}		else		{			smart[ i ] = false;		}	}	var data = new Array();								// the data placed in the grid	var table;											// the table that is placed inside the container	var current = -1;	var rows = new Array();		// array of the table elements representing the rows			container.clear = function()	{		// Delete any existing data		data = new Array();		current = -1;		rows = new Array();				// Empty the container		while( container.hasChildNodes() )		{		  container.removeChild( container.firstChild );		}				// Create the table		table = document.createElement('table');		container.appendChild( table );		table.cellSpacing = "0px";		// add the header row		var row = table.insertRow(-1);		var i;		for( i in legends )		{			var col = row.insertCell(-1);			var text = document.createTextNode( legends[ i ] );			col.appendChild( text );			if( i != legends.length-1 )			{				var gapCol = row.insertCell(-1);				var gapText = document.createTextNode( "\u00a0\u00a0" );				gapCol.appendChild( gapText );			}		}				// add the rule		var ruleRow = table.insertRow(-1);		var ruleCol = ruleRow.insertCell(-1);		ruleCol.colSpan = legends.length * 2 - 1;		// span col+gap		ruleCol.appendChild( document.createElement( 'hr' ) );	}	function highlight( n )	{		if( n != -1 )		{			rows[ n ].className = highliteStyle;						var cells = rows[ n ].cells;			var i;			for( i=0; i<cells.length; i+=2 )		// skip over gap columns			{				if( cells[ i ].firstChild.duplicate )				{					cells[ i ].firstChild.style.visibility = "";				}			}		}	}		function unhighlight( n )	{		if( n != -1 )		{			rows[ n ].className = normalStyle;		// highlight the row			var cells = rows[ n ].cells;			var i;			for( i=0; i<cells.length; i+=2 )		// skip over gap columns			{				if( cells[ i ].firstChild.duplicate )				{					cells[ i ].firstChild.style.visibility = "hidden";				}			}		}	}		function makeMouseOver( n )	{		return function()		{			unhighlight( current );			highlight( n );			current = n;		}	}		function makeMouseOut()	{		return function()		{			unhighlight( current );			current = -1;		}	}		function makeMouseUp( n )	{		return function()		{			if( action )			{				action( n, container );			}		}	}		container.append = function( elements )	{		var count = Math.min( elements.length, legends.length );		if( count > 0 )		{			var row = table.insertRow(-1);			row.className = normalStyle;			row.onmouseover = makeMouseOver( data.length );			row.onmouseout = makeMouseOut();			row.onmouseup = makeMouseUp( data.length );						data.push( elements );			// add this row to the table of data			rows.push( row );						var i;			for( i=0; i < count; i++ )			{				var col = row.insertCell(-1);				var div = document.createElement('div');	// create a div inside the cell so we can make it invisible while the cell remains visible				col.appendChild( div );				var text = document.createTextNode( elements[ i ] );				div.appendChild( text );				if( data.length != 1 && data[ data.length - 2 ][ i ] == elements[ i ] && smart[i] )				{					div.style.visibility = "hidden";					div.duplicate = true;			// marks as a duplicate				}				else				{					div.style.visibility = "";					div.duplicate = false;			// mark as not a duplicate				}				if( i != count-1 )				{					var gapCol = row.insertCell(-1);					var gapText = document.createTextNode( "\u00a0\u00a0" );					gapCol.appendChild( gapText );				}			}		}	}	container.get = function( n )	{		return data[ n ];	}		container.clear();	container.JugType = "grid";	}//============ ToolTip =========// JUG factory function for tool tipfunction JugCreateToolTip( id, props ){	// id		string id of element	// props	string property set of the JUG tool tip	var object = JugElem( id );		var toolTipText = props.tip;	var interval = ( props.interval ? props.interval : 1000 );	var container = document.createElement( 'a' );	object.parentNode.replaceChild( container, object );	container.appendChild( object );		var tip = document.createElement( 'div' );	tip.style.position = "absolute";	tip.style.top = 0 + "px";	tip.style.left = 0 + "px";	tip.style.width = 100 + "px";	tip.style.borderStyle = "outset";	tip.style.padding = 5 + "px";	tip.style.borderWidth = "2px";	tip.style.backgroundColor = "#f0f0f0";		var tipText = document.createTextNode( toolTipText );	tip.appendChild( tipText );	var timer = null;			// timer used to fire tooltip	var visible = false;		// whether the tool tip is displayed	var hide = false;			// whether to hide the tool tip	var position = null;		// the position where the mouse stopped	tip.onmousemove = function()	{		container.removeChild( tip );	// stop displaying the tool tip	}	function tick()	{		timer = null;				if( hide )		{			return;		}				// Decide where to place the tool tip		var cursorHeight = 15;		var area = { left : JugMouseCoords.x, top : JugMouseCoords.y + cursorHeight, width : tip.offsetWidth, height : tip.offsetHeight };		tip.style.left = area.left + "px";		tip.style.top = area.top + "px";				// Make it visible		container.appendChild( tip );		visible = true;				// Remember the position of the mouse		position = JugMouseCoords;	}		container.onmouseover = function()	{		if( timer != null )		{			clearTimeout( timer );		}		timer = setTimeout( tick, interval );	}		container.onmouseout = function()	{		if( visible )		{			container.removeChild( tip );	// stop displaying the tool tip			visible = false;		}		if( timer != null )		{			clearTimeout( timer );			timer = null;		}	}		container.onmousemove = function()	{		if( visible )		{			if( JugMouseCoords.x == position.x && JugMouseCoords.y == position.y )			{				// mouse hasn't actually moved - must be a screen resize gitter			}			else			{				container.removeChild( tip );	// stop displaying the tool tip				visible = false;			}		}		if( timer != null )		{			clearTimeout( timer );		}		timer = setTimeout( tick, interval );	}	var originalOnFocus = object.onfocus;	object.onfocus = function()	{		if( visible )		{			container.removeChild( tip );	// stop displaying the tool tip			visible = false;		}		if( timer != null )		{			clearTimeout( timer );			timer = null;		}		hide = true;		if( originalOnFocus )		{			originalOnFocus();		}	}		var originalOnBlur = object.onblur;	object.onblur = function()	{		hide = false;		if( originalOnBlur )		{			originalOnBlur();		}	}		if( object.JugType )	{		// object is already a Jug Object so preserve its type	}	else	{		object.JugType = "toolTip";	}}//============ TextField =========// JUG factory function for a text fieldfunction JugCreateTextField( id, props ){	// id		string id of input text element or textarea	// props	string property set of the JUG text field	var object = JugElem( id );	var change = eval( props.change );	var update = eval( props.update );	var originalValue = object.value;	// remember the original value of the text field	var previousValue = object.value;	// remember the previous value reported		var editable = true;	object.onkeydown = function( e )	{		if( editable )		{			return true;		}		else		{			if( e !== undefined && e.stopPropagation )			{				e.stopPropagation();			}			else			{				event.cancelBubble = true;			}			return false;		}	}		object.onkeyup = function()	{		if( object.value == originalValue )		{			if( previousValue != originalValue && change )			{				change( true );		// changed back to original value			}		}		else		{			if( previousValue == originalValue && change )			{				change( false );	// changed to a different value			}		}				if( update )		{			update();		}		previousValue = object.value;	}	object.SetValue = function( v )	{		if( previousValue != originalValue && change )		{			change( true );	// back to (new) original value		}		object.value = v;		originalValue = v;		previousValue = v;	}	object.EditValue = function( v )	{		object.value = v;		if( object.value == originalValue )		{			if( previousValue != originalValue && change )			{				change( true );		// changed back to original value			}		}		else		{			if( previousValue == originalValue && change )			{				change( false );	// changed to a different value			}		}		previousValue = object.value;	}	object.GetValue = function()	{		return object.value;	}		object.HasChanged = function()	{		return object.value != originalValue;	}	object.onSetEditable = function( e )	{		editable = e;		return true;		// pass the message on	}		object.JugType = "textField";	}//============ CountWords =========// JUG factory function for a Count Words fieldfunction JugCreateCountWordsField( id, props ){	// id		string id of input text field	// props	string property set of the JUG Count Words field	//				changeCount - function called when field's word count changes		var object = JugElem( id );	var change = eval( props.changeCount );	function countWords( val )	{		var count = 0;		var i=0;		// skip leading spaces		while( i < val.length && val.charAt( i ) <= " " )		{			i += 1;		}		if( i == val.length )		{			// entirely blank, so count is left at zero		}		else		{			// found the start of the first word			count = 1;			// look for beginning of each subsequent word			while( i < val.length )			{				if( val.charAt( i ) <= " " && i+1 < val.length && val.charAt( i+1 ) > " " )				{					// found a space character followed by a non-space					count += 1;				}				i += 1;			}		}		return count;	}		// count existing words	var currentCount = countWords( object.value );	change( currentCount );	// callback so app can display initial count	// chain new onChange handler onto the object	var originalOnkeyup = object.onkeyup;		object.onkeyup = function()	{		// update the word count		var newCount = countWords( object.value );		if( newCount != currentCount )		{			// word count has changed			currentCount = newCount;			change( currentCount );		}		// call original key-up handler if one is defined		if( originalOnkeyup )		{			return originalOnkeyup();		}		else		{			return true;		}	}		// chain new SetValue function	var originalSetValue = object.SetValue;		object.SetValue = function( value )	{		// update the word count		var newCount = countWords( value );		if( newCount != currentCount )		{			// word count has changed			currentCount = newCount;			change( currentCount );		}				// call original SetValue if one is defined		if( originalSetValue )		{			originalSetValue( value );		}	}	if( object.JugType )	{		// object is already a Jug Object so preserve its type	}	else	{		object.JugType = "countWords";	}}//============ CheckBox =========// JUG factory function for a check boxfunction JugCreateCheckBox( id, props ){	// id		string id of checkbox	var object = JugElem( id );		var change = eval( props.change );		var originalValue = object.checked;		// remember the original value of the text field	var previousValue = object.checked;		// remember the previous value reported	var editable = true;		// whether the check box can be changed by the user	var notify = null;			// function to call when value changes to notify anyone who has registered interest	object.onclick = function()	{		if( !editable )		{			return false;		}				if( object.checked == originalValue )		{			if( previousValue != originalValue && change )			{				change( true );		// changed back to original value			}		}		else		{			if( previousValue == originalValue && change )			{				change( false );	// changed to a different value			}		}		previousValue = object.checked;				// tell anyone who's interested that the check box has changed		if( notify )		{			notify( object.checked );		}	}		object.SetValue = function( value )	{		if( previousValue != originalValue && change )		{			change( true );		}		object.checked = value;		originalValue = value;		previousValue = value;	}	object.GetValue = function()	{		return object.checked;	}	object.onSetEditable = function( e )	{		editable = e;		return true;		// pass the message on	}		object.JugRegister = function( callback )	{		var oldNotify = notify;		notify = function( v )		{			if( oldNotify )			{				oldNotify( v );			}			callback( v );		}	}		object.JugType = "checkBox";	}//============ DropDown =========// JUG factory function for a drop downfunction JugCreateDropDown( id, props ){	// id		string id of select drop down	// props	{ change } - string property set of the JUG drop down	//				// props.change		a function(asOriginal) that is called when the drop down's value changes from or to the drop down's original value	//					asOriginal gives whether the drop down now has its original value		var object = JugElem( id );		var displayedStrings = [];				// array of displayed strings - one for each element in the drop down	var selectableValues = [];				// array of selectable value objects - one for each element in the drop down		var i;	for( i=0; i < object.options.length; i++ )	{		displayedStrings.push( object.options[ i ].text );		selectableValues.push( object.options[ i ].value );	}	var change = eval( props.change );		// on-change handler (diffs from original state)	var update = eval( props.update );		// on-update handler (changes at all)	var clicking = false;					// whether the user is clicking to change the drop down (rather than a programmatic call)		var originalValue = object.value;		// remember the original value of the select drop down	var previousValue = object.value;		// remember the previous value reported	var originalIndex = 0;					// index of selected element before a change is made		var editable = true;					// whether the drop down can be changed by the user	// Set the list of displayed options and their associated values	object.SetOptions = function( items )	{		// items - map from displayed string to selected value object				// remove existing elements		while( object.length != 0 )		{			object.remove(0);		}		// create an option element for each of the items				var index = 0;	// index position of next item in drop down				displayedStrings = [];		selectableValues = [];		for( itemText in items )		{			var option = document.createElement( 'option' );						// set the option to display the displayed string and have a value of the index for the item			option.text = itemText;			option.value = index;						// note the displayed string and selected value object for the item			displayedStrings[ index ] = itemText;			selectableValues[ index ] = items[ itemText ];						// add the option to the drop down			try{				object.add( option, null ); // standards compliant			}			catch( ex )			{				object.add( option );	// IE only			}						index += 1;	// count the index position of the item in the drop down		}		originalValue = object.value;		// remember the original value of the select drop down		previousValue = object.value;		// remember the previous value reported	}		// Get the options	object.GetOptions = function()	{		var options = [];		for( i in displayedStrings )		{			options[ displayedStrings[ i ] ] = selectableValues[ i ];		}		return options;	}		// mouse down event handler for drop down - called before the drop down changes value	object.onmousedown = function()	{		clicking = true;		return editable;		// kill the mouse down event if not editable	}	// mouse up event handler for drop down	object.onmouseup = function()	{		clicking = false;		originalIndex = object.selectedIndex;	// assume the value has changed, so remember it for next time		return editable;		// kill the mouse down event if not editable	}	// on click event handler for drop down	object.onclick = function()	{		if( object.value == originalValue )		{			if( previousValue != originalValue && change )			{				change( true );		// call the on-change handler, true => changed back to original value			}		}		else		{			if( previousValue == originalValue && change )			{				change( false );	// call the on-change handler, false => changed to a different value			}		}				if( update )		{			update();		}				previousValue = object.value;	}		// Select the item with the given return value	object.SetValue = function( value )	{		// value - result object value of item to select		for( v in selectableValues )		{			if( selectableValues[ v ] == value )			{				// element v has the value we seek				object.SetIndex( v );				return;			}		}		// not found - ignore	}		// Get the return value of the currently selected item	object.GetValue = function()	{		return selectableValues[ object.selectedIndex ];	}	// Select the item with the given displayed text	object.SetSelected = function( text )	{		// text - display name of item to make current				object.value = text;	// try changing it in case the value is not legal		if( object.value == text )		{			// the value has changed, so must be legal			if( clicking && change )			{				var oldClicking = clicking;				clicking = false;								// not nested and change handler defined				if( previousValue != originalValue && value == originalValue )				{					// previous value reported was not the original value but we have now changed back to the original					change( true );		// true => changed backed to original value				}				else if( previousValue == originalValue && value != originalValue )				{					// previous value reported was the original value but we have now changed to something different					change( false );		// false => value different from original				}				// not what we've just reported				previousValue = text;								clicking = oldClicking			}		}	}		// Get the displayed text of the current item	object.GetSelected = function()	{		return displayedStrings[ object.selectedIndex ];	}		// Selected the item with the given index	object.SetIndex = function( i )	{		// i - index of item to make current		if( selectableValues[ i ] !== undefined )		// test if the index is legal - ignore if not		{			object.selectedIndex = i;			// change the drop down			var value = selectableValues[ i ];			if( clicking && change )			{				// not nested and change handler defined				var oldClicking = clicking;				clicking = false;								if( previousValue != originalValue && value == originalValue )				{					// previous value reported was not the original value but we have now changed back to the original					change( true );		// true => changed backed to original value				}				else if( previousValue == originalValue && value != originalValue )				{					// previous value reported was the original value but we have now changed to something different					change( false );		// false => value different from original				}				previousValue = value;								clicking = oldClicking;			}		}	}		// Get the index of the currently selected item	object.GetIndex = function()	{		return object.selectedIndex;	}	// Get the index of the item selected before the drop down was clicked	object.GetOriginalIndex = function()	{		return originalIndex;	}		// Get the index of the item selected before the drop down was clicked	object.GetOriginalValue = function()	{		return selectableValues[ originalIndex ];	}		// Get the text of an element	object.GetElementText = function( index )	{		return displayedStrings[ index ];	}		// Get the value of an element	object.GetElementValue = function( index )	{		return selectableValues[ index ];	}		// Get the number of options	object.GetCount = function()	{		return displayedStrings.length;	}		// Set whether the drop down is editable	object.onSetEditable = function( e )	{		editable = e;		return true;		// pass the message on	}		object.JugType = "dropDown";	}//====== RadioButtons interface =========// JUG factory functionfunction JugSetupRadioButtons( id, props ){	// id		string id of text input element that is to become a JUG radio button bank	// props	string property set of the JUG radio button bank	//			{ function action }	var action = eval( props.modify );	var change = eval( props.change );	var editable = true;	// take the default text value from the input element and treat it as a list of radio button values	var textElem = JugElem( id );	var legends = textElem.value.split(",");	// Create an anchor that is to represent the radio button bank	var a = document.createElement( 'a' );	a.name = "JugRadioButtons";	// give anchor a name otherwise it isn't counted in document.anchors	var currentValue = null;	// remember current value as an integer offset	var originalValue = null;	// 	var previousValue = null;	// remember previously reported value	// function	function selectButton( n, act )	{		var i;		for( i=0; i < a.childNodes.length; i++ )		{			var child = a.childNodes[ i ];			if( i == n )			{				// this is the button that is now depressed				child.style.borderStyle = "inset";				if( currentValue == n )				{					// no change				}				else				{					currentValue = n;					if( act !== undefined )					{						actionTimer = setTimeout( act, 1 );	// schedule the radio button set's click handler to fire					}					if( currentValue == originalValue )					{						if( previousValue != originalValue && change )						{							change( true );							previousValue = currentValue;						}					}					else					{						if( previousValue == originalValue && change )						{							change( false );							previousValue = currentValue;						}					}				}			}			else			{				// this button is not depressed				child.style.borderStyle = "outset";			}		}	}		// function to get current value of radio buttons, ie. index of the one that is set	a.GetValue = function()	{		return currentValue;	// returns null if none set, 0 if first set, 1 if second set etc	}		// function to set current value of radio buttons	a.SetValue = function( n )	{		if( previousValue != originalValue && change )		{			change( true );	// back to (new) original value		}		currentValue = n;		previousValue = n;		originalValue = n;		selectButton( n, undefined );	}		a.onSetEditable = function( e )	{		editable = e;		return true;		// pass the message on	}		// function to make onclick handlers for the radio buttons	function makeClick( n )	{			// n		integer index of the radio button		// RETURNS	function that is the onclick handler for the radio button				return function()		{			// variable to hold timeout object used to run button script asynchronously			var actionTimer = null;						if( !editable )			{				// ignore the click			}			else if( action == undefined )			{				selectButton( n, undefined );			}			else			{				// called asynchronously when button is clicked				function runClickAction()				{					action( n );		// call the user defined handler for the radio button set, passing the index of the radio button that was clicked					actionTimer = null;				}							// restyle the radio buttons to depress the selected one				selectButton( n, runClickAction );			}		}	}		// for each radio button in the set, create an anchor that displays it	var i;	for( i=0; i < legends.length; i++ )	{		var b = document.createElement( 'a' );					// create an anchor for the radio button		var text = document.createTextNode( legends[i] );		// create the text for the radio button		b.appendChild( text );									// add the text to the radio button anchor		b.style.borderStyle = "outset";		b.style.padding = 0 + "px";		b.onclick = makeClick( i );		a.appendChild( b );										// add the radio button anchor to the set anchor	}	// replace the text input element with the radio button set anchor	textElem.parentNode.replaceChild( a, textElem );		a.id = id;	a.JugType = "radioButtons";}//====== RadioSet interface =========// JUG factory functionfunction JugSetupRadioSet( id, props ){	// id		string id of text input element that is to become a JUG radio set	// props	string property set of the JUG radio button set	//			{ function action }	var action = eval( props.modify );	var change = eval( props.change );	var editable = true;	// take the default text value from the input element and treat it as a list of radio button values	var textElem = JugElem( id );	var legends = textElem.value.split(",");		// Create an anchor that is to represent the radio button set	var a = document.createElement( 'a' );	a.name = "JugRadioButtons";	// give anchor a name otherwise it isn't counted in document.anchors	var currentValue = 0;	// remember current value as a mask	var originalValue = 0;	// 	var previousValue = 0;	// remember previously reported value	// function	function clickButton( n, act )	{		if( editable )		{			var power = Math.pow(2,n);			var child = a.childNodes[ n ];			if( (currentValue & power) == 0 )			{				// button not currently selected, so depress it				child.style.borderStyle = "inset";				currentValue = currentValue | power;			}			else			{				// button is currently selected, so unpress it				child.style.borderStyle = "outset";				currentValue = currentValue - power;			}				if( act !== undefined )			{				actionTimer = setTimeout( act, 1 );	// schedule the radio button set's click handler to fire			}				if( currentValue == originalValue )			{				if( previousValue != originalValue && change )				{					change( true );					previousValue = currentValue;				}			}			else			{				if( previousValue == originalValue && change )				{					change( false );					previousValue = currentValue;				}			}		}			}	// function to get current value of radio buttons	a.GetValue = function()	{		return currentValue;	}	// function to set current value of radio buttons	a.SetValue = function( mask )	{		if( previousValue != originalValue && change )		{			change( true );	// back to (new) original value		}		currentValue = mask;		previousValue = mask;		originalValue = mask;		var i;		for( i=0; i < legends.length; i++ )		{			var power = Math.pow(2,i);			var child = a.childNodes[ i ];			if( (mask & power) != 0 )			{				child.style.borderStyle = "inset";		// depress the button			}			else			{				child.style.borderStyle = "outset";		// unpress the button			}		}	}	a.onSetEditable = function( e )	{		editable = e;		return true;		// pass the message on	}		// function to make onclick handlers for the radio buttons	function makeClick( n )	{			// n		integer index of the radio button		// RETURNS	function that is the onclick handler for the radio button				return function()		{			// variable to hold timeout object used to run button script asynchronously			var actionTimer = null;						if( action == undefined )			{				clickButton( n, undefined );			}			else			{				// called asynchronously when button is clicked				function runClickAction()				{					action( n );		// call the user defined handler for the radio button set, passing the index of the radio button that was clicked					actionTimer = null;				}							// restyle the radio buttons to toggle the selected one				clickButton( n, runClickAction );			}		}	}		// for each radio button in the set, create an anchor that displays it	var i;	for( i=0; i < legends.length; i++ )	{		var b = document.createElement( 'a' );					// create an anchor for the radio button		var text = document.createTextNode( legends[i] );		// create the text for the radio button		b.appendChild( text );									// add the text to the radio button anchor		b.style.borderStyle = "outset";		b.style.padding = 0 + "px";		b.onclick = makeClick( i );		a.appendChild( b );										// add the radio button anchor to the set anchor	}	// replace the text input element with the radio button set anchor	textElem.parentNode.replaceChild( a, textElem );		a.id = id;	a.JugType = "radioSet";}//============ Drop Down / Edit box combo Selector =========// JUG factory function for Selectorfunction JugCreateSelector( id, props ){	// id		string id of drop down selector element	// props	string property set of the JUG drop down selector menu	var elem = JugElem( id );	var maxItems = props.maxItems;	var change = eval( props.change );			// change called when change-status alters (whether value is same as original)	var update = eval( props.update );			// update called when a new value is chosen	var validate = props.validate;	var fg = getStyleElement( elem, "color" );	var bg = getStyleElement( elem, "background-color" );	var originalValue = elem.value;	// remember the original value of the text field	var previousValue = elem.value;	// remember the previous value reported	var selecting = false;	// whether we are processing a mouse-down over a list item	var editable = true;	// create a div that is to be the moveable frame	var container = document.createElement('div');	// div holding the entire moveable frame	container.style.position = "absolute";	container.style.top = 0 + "px";	container.style.left = 0 + "px";	container.style.background = "#FFFFFF";	container.style.opacity = 1.0;	container.style.filter = "alpha(opacity=100)";	// IE	container.style.borderStyle = "outset";	container.style.overflow = "hidden";	container.style.overflowX = "hidden";	var list = document.createElement('div');	// div holding the list of options	container.appendChild( list );		// remember the moveable frame as a JUG object that is not in the DOM until needed	JugDivs[ id ] = container;	var fullList;					// array of strings which is the list of all chooseable items	var currentList;				// array of strings currently displayed			var anchors = new Array();		// array of anchor objects that are the items in the drop down list	var current = -1;				// -1 => none selected		function highlight( i )	{		if( i != -1 )		{			if( anchors[i] )			{				anchors[i].style.backgroundColor = fg;				anchors[i].style.color = bg;			}			else			{				alert( "undefined highlight " + i );			}		}	}		function unhighlight( i )	{		if( i != -1 )		{			if( anchors[i] )			{				anchors[i].style.backgroundColor = bg;				anchors[i].style.color = fg;			}			else			{				alert( "unhighlight undefined " + i );			}		}	}		function createDropDown()	{		// remove all children from list div		while( list.hasChildNodes() )		{		  list.removeChild( list.firstChild );		}		// factory for mouse over event handler that fires when mouse moves over a list item - it moves the highlight		function makeMouseOver( i )		{			return function()			{				unhighlight( current );				highlight( i );				current = i;			}		}		// factory for mouse out event handler that fires when mouse moves out of a list item - it removes the highlight		function makeMouseOut( i )		{			return function()			{				unhighlight( current );				current = -1;			}		}		// factory for mouse up event handler that fires on mouse up over a list item - it selects the item		function makeMouseUp( i )		{			return function()			{				unhighlight( current );				elem.value = currentList[ current ];				current = -1;				createDropDown();				showDropDown();				selecting = false;			}		}		anchors = new Array();				// remove items not matching current text		var items = new Array();		var text = elem.value.toUpperCase();		var i;		for( i in fullList )		{			var listElementText =  fullList[ i ].toUpperCase();			if( listElementText.indexOf( text ) == 0 && listElementText != text )			{				// this list item starts with the current text value, so include it in the drop down				items.push( fullList[ i ] );			}		}				// add new children to list div		currentList = new Array();		for( i in items )		{			var a = document.createElement( 'a' );			anchors[ i ] = a;			a.onmouseover = makeMouseOver( i );			a.onmouseout = makeMouseOut( i );			a.onmouseup = makeMouseUp( i );			a.onmousedown = function()			{				selecting = true;			};			list.appendChild( a );			var text = document.createTextNode( items[ i ] );			currentList[ i ] = items[ i ];			a.appendChild( text );			var br = document.createElement( 'br' );			list.appendChild( br );						if( maxItems != undefined && currentList.length >= maxItems )			{				break;			}		}				if( elem.value == originalValue )		{			if( previousValue != originalValue && change != null )			{				change( true );				previousValue = elem.value;			}		}		else		{			if( previousValue == originalValue && change != null )			{				change( false );				previousValue = elem.value;			}		}		current = -1;	// nothing selected	}		elem.SetChoices = function( newList )	{		fullList = newList;		createDropDown();	}		// make the drop down list visible	function showDropDown()	{		if( !editable )		{			return;		}		if( currentList.length == 0 )		{			// no items, so hide it			if( container.parentNode )			{				container.parentNode.removeChild( container );			}			if( update )			{				update();			}			}		else		{			// adjust backdrop to fit the window			var e = JugGetElementArea( elem );					// get the edit box			container.style.left = e.left + "px";				// position the drop down list below the edit box			container.style.top = (e.top + e.height) + "px";				document.body.appendChild( container );				// display the drop down list						var containerArea = JugGetElementArea( container );			container.style.width = Math.max( containerArea.width, e.width ) + "px";		// adjust the drop down's width																						// this seems to make the width grow over time !!!!!!!		}	}	// hide the drop down list	function hideDropDown()	{		if( !editable )		{			return;		}		if( container.parentNode )		{			container.parentNode.removeChild( container );		}				if( update )		{			update();		}		selecting = false;	}	var timer = null;		// timer used to run createDropDown after update has happened		// event fires on key down in edit box - fields special keys, moving the highlight item or making a selection	elem.onkeydown = function( e )	{		if( !editable )		{			return false;		}				var key;		if( window.event && window.event.keyCode )		{			key = window.event.keyCode;		}		else if( e.keyCode )		{			key = e.keyCode;		}		else if( e.which )		{			key = e.which;		}		else		{			key = 0;		}		if( key == 40 )		{			// down			if( current + 1 == anchors.length )			{				return false;			}			unhighlight( current );			current += 1;			highlight( current );			return false;		}		else if( key == 38 )		{			// up			if( current == -1 )			{				return false;			}			unhighlight( current );			current -= 1;			highlight( current );			return false;		}		else if( key == 9 )		{			// tab			if( current != -1 )			{				unhighlight( current );				elem.value = currentList[ current ];				current = -1;				createDropDown();				hideDropDown();			}			return true;		// allow tab to propagate		}				function refreshDropDown()		{			createDropDown();			showDropDown();		}		timer = setTimeout( refreshDropDown, 1 );	// schedule the createDropDown function to run once the edit box has been updated with this character				return true;	}		elem.onReturn = function()	{		if( current == -1 )		{			// nothing highlighted so not for us			return true;		}		else		{			unhighlight( current );			elem.value = currentList[ current ];			current = -1;			createDropDown();			hideDropDown();			return false;		}	}	elem.onSetEditable = function( e )	{		editable = e;		return true;		// pass the message on	}		elem.GetValue = function()	{		return elem.value;	}	elem.SetValue = function( value )	{		if( previousValue != originalValue && change )		{			change( true );	// back to (new) original value		}				elem.value = value;		originalValue = value;		previousValue = value;		createDropDown();	}	elem.HasChanged = function()	{		return elem.value != originalValue;	}	fullList = new Array();	createDropDown();	// event fires when the user clicks into the text box - it pops up the list	elem.onfocus = function()	{		showDropDown();		elem.select();		return false;	}	// event fires when user clicks away from the text box - it hides the list	elem.onblur = function()	{		if( selecting )		{			// lost focus because the mouse is being clicked on a list item, so leave the list in place		}		else		{			hideDropDown();		}	}	elem.JugType = "selector";	}//============ Plain Text ====================// JUG factory function for an uneditable plain text field - appears as plain text not an edit boxfunction JugCreatePlainTextField( id, props ){	// id		string id of input text element or textarea	// props	string property set of the JUG text field	var object = JugElem( id );	// span object 	var value = "";		object.SetValue = function( v )	{		// remove the existing children of the span		while( object.hasChildNodes() )		{			object.removeChild(object.childNodes[0]);		}				// create a new text node as the child of the span		var text = document.createTextNode( v );		object.appendChild( text );				// remember the text value		value = v;	}	object.GetValue = function()	{		return value;	}	object.JugType = "plainTextField";	}//============ Element Enabler =========// JUG factory function for Element Enablerfunction JugCreateEnabler( id, props ){	// id		string id of element	// props	string property set of the JUG enabler	//			{ source, condition } - source is optional checkbox name, condition is optional function that evaluates whether element is enabled in a special way	var elem = JugElem( id );		var source = props.source;		var condition = eval( props.condition );		function notify( value )	{		// The source check box has changed to <value> so re-evaluate enable condition		if( condition )		{			elem.disabled = !condition( value );	// evaluate custom condition		}		else		{			elem.disabled = !value;					// evaluate default condition, which is enable if checkbox is set		}	}		if( source )	{		// Hook a function onto the on-loaded message handler that registers a notifier callback with the source element		var oldLoaded = elem.onJugLoaded;	// remember any previous callback		elem.onJugLoaded = function()		{			if( oldLoaded )			{				oldLoaded();	// fire previous callback			}			// Register notify as a callback so it fires if the source checkbox is changed			JugElem( source ).JugRegister( notify );		}	}}//============ DynamicDropDown =========//============ Pop Up Pane ====================//============ Field Set ====================//============ Char Buttons ====================//============ Pop Up Dialog ====================//============ Overlay ====================// JUG factory functionfunction JugCreateOverlay( id, props ){	// id		string id of div element that is to become the overlay display	// props	string property set of the JUG overlay display	//			{ string ovrList } - ovrlist is comma separated list of div-name		// find the element that is the overlay container	var overlayContainer = JugElem( id );	var overlayNames = props.overlayList.split( "," );	// array of "div names" for the overlays in the display	if( overlayNames.count == 0 )	{		throw new Error( "no overlays" );	}		var currentOverlay = overlayNames[0];		overlayContainer.show = function( name )	{		if( currentOverlay == name )		{			// already shown, so do nothing		}		else		{			JugElem( currentOverlay ).style.display = "none";			currentOverlay = name ;			JugElem( currentOverlay ).style.display = "inline";		}	}		var i;	for( i in overlayNames )	{		var divName = overlayNames[ i ];		var div = JugElem( divName );				// add the overlay element's div to the overlay container		overlayContainer.appendChild( div );		// hide the overlay element		div.style.display = "none";	} // end loop over overlay names		overlayContainer.JugType = "OverlaySet";	overlayContainer.onJugLoaded = function()					// called when the page has been loaded	{		// All loaded, now select the first tab		JugElem( currentOverlay ).style.display = "inline";	};}//============ Slideshow ====================function JugCreateSlideshow( id, props ){	// id		string id of div element that is to become the slideshow	// props	string property set of the JUG slideshow	//			{ string folder - url of folder containing images	//			  string imageList - comma separated list of image names relative to that folder	//			  integer fadeSpeed - time for fade in msec (default 20)	//			  integer showTime - time each image is displayed (default 5000msec)	//			} 		// find the element that is the slideshow container	var slideshowContainer = JugElem( id );	if( props.fadeSpeed === undefined )	{		props.fadeSpeed = 20;	}	if( props.showTime === undefined )	{		props.showTime = 5000;	}		var fadeSpeed = 100 / props.fadeSpeed;	var showTime = props.showTime;		var folderName = props.folder;	var imageNames = props.imageList.split( "," );		// array of relative "image names"	if( imageNames.count <= 1 )	{		throw new Error( "no images" );	}		var images = [];	var i;	for( i in imageNames )	{		images.push( folderName  + '/' + imageNames[ i ] );	}	var next = 1;		// next image to fade in	var level = 100;	// opacity level	var timer;				function tick()	{		var p = slideshowContainer.firstChild;		if( level >= 100 )		{			current = next;			next = (next + 1) % images.length;			level = 0;			slideshowContainer.style.backgroundImage = "url(" + images[current] + ")";			p.src = images[next];			p.style.opacity = 0;			p.style.filter = 'alpha(opacity=0)';			setTimeout( tick, showTime );		}		else		{			level += fadeSpeed;			p.style.filter='progid:DXImageTransform.Microsoft.Alpha(Opacity=' + level + ')';			p.style.opacity = level/100;//			p.style.filter = 'alpha(opacity=' + level + ')';			setTimeout( tick, 100 );		}	}	var img = document.createElement( "img" );	slideshowContainer.appendChild( img );		slideshowContainer.style.backgroundImage = "url(" + images[0] + ")";	slideshowContainer.style.backgroundRepeat = 'no-repeat';	img.src = images[1];	setTimeout( tick, 0 );	slideshowContainer.JugType = "Slideshow";}//============ Date Picker ====================function JugCreateDatePicker( id, props ){	// id		string id of span element that is to become the date picker	// props	string property set of the JUG overlay display	//			{  } - 		// find the element that is the date picker container	var pickerContainer = JugElem( id );		function makeClickFunction( func, value )	{		return function()		{			func( value );		}	}	function createKeypadTable( clickFunction )	{		var keypadTable = document.createElement('table');				function addRow( values )		{			var row = keypadTable.insertRow(-1);			for( v in values )			{				var cell = row.insertCell(-1);								var button = document.createElement('a');				button.onclick = makeClickFunction( clickFunction, values[v] );				button.style.borderStyle = "outset";				cell.align = "center";				var buttonText = document.createTextNode( v );				button.appendChild(buttonText);				cell.appendChild( button );			}		}				addRow( {"1":1, "2":2, "3":3 } );		addRow( {"4":4, "5":5, "6":6 } );		addRow( {"7":7, "8":8, "9":9 } );		addRow( {"<":-1, "0":0, ">":10 } );			return keypadTable;	}		function createMonthTable( clickFunction )	{		var monthTable = document.createElement('table');				function addRow( values )		{			var row = monthTable.insertRow(-1);			for( v in values )			{				var cell = row.insertCell(-1);				var button = document.createElement('a');				button.onclick = makeClickFunction( clickFunction, values[v] );				button.style.borderStyle = "outset";				cell.align = "center";				var buttonText = document.createTextNode( v );				button.appendChild(buttonText);				cell.appendChild( button );			}		}				addRow( {"Jan":1, "Feb":2, "Mar":3 } );		addRow( {"Apr":4, "May":5, "Jun":6 } );		addRow( {"Jul":7, "Aug":8, "Sep":9 } );		addRow( {"Oct":10, "Nov":11, "Dec":12 } );			return monthTable;	}		var currentDate = {year:0, month:0, day:0};		var selectedDay = document.createElement( 'span' );	var selectedMonth = document.createElement( 'span' );	var selectedYear = document.createElement( 'span' );	selectedDay.appendChild( document.createTextNode('--') );	selectedMonth.appendChild( document.createTextNode('---') );	selectedYear.appendChild( document.createTextNode('----') );	function setDay( v )	{		selectedDay.removeChild( selectedDay.firstChild );		selectedDay.appendChild( document.createTextNode( JugDays[v] ) );	}	function setMonth( v )	{		selectedMonth.removeChild( selectedMonth.firstChild );		selectedMonth.appendChild( document.createTextNode( JugMonths[v] ) );	}	function setYear( v )	{		selectedYear.removeChild( selectedYear.firstChild );		if( v == 0 )		{			selectedYear.appendChild( document.createTextNode( "----" ) );		}		else		{			selectedYear.appendChild( document.createTextNode( v ) );		}	}	function clickDay( value )	{		if( value == -1 )		{			currentDate.day -= 1;		}		else if( value == 10 )		{			currentDate.day += 1;		}		else		{			currentDate.day = currentDate.day % 10 * 10 + value;			if( currentDate.day > 31 )			{				currentDate.day = value;			}		}		currentDate.day = Math.max( currentDate.day, 1 );		currentDate.day = Math.min( currentDate.day, 31 );		setDay( currentDate.day );	}		function clickMonth( value )	{		if( value == -1 )		{			currentDate.month -= 1;		}		else if( value == 10 )		{			currentDate.month += 1;		}		else		{			currentDate.month = value;		}		currentDate.month = Math.max( currentDate.month, 1 );		currentDate.month = Math.min( currentDate.month, 12 );		setMonth( currentDate.month );	}		function clickYear( value )	{		if( value == -1 )		{			currentDate.year -= 1;		}		else if( value == 10 )		{			currentDate.year += 1;		}		else		{			currentDate.year = currentDate.year % 1000 * 10 + value;		}		currentDate.year = Math.max( currentDate.year, 1 );		currentDate.year = Math.min( currentDate.year, 9999 );		setYear( currentDate.year );	}		function setYesterday()	{		currentDate = JugDateYesterday();		setDay( currentDate.day );		setMonth( currentDate.month );		setYear( currentDate.year );	}		function setToday()	{		currentDate = JugDateToday();		setDay( currentDate.day );		setMonth( currentDate.month );		setYear( currentDate.year );	}		function setNone()	{		currentDate.day = 0;		currentDate.month = 0;		currentDate.year = 0;		setDay( currentDate.day );		setMonth( currentDate.month );		setYear( currentDate.year );	}		var pickerTable = document.createElement('table');	var dateRow = pickerTable.insertRow(-1);	dateRow.insertCell(-1).appendChild( selectedDay );	dateRow.insertCell(-1).appendChild( selectedMonth );	dateRow.insertCell(-1).appendChild( selectedYear );	var hr1 = pickerTable.insertRow(-1).insertCell(-1);	hr1.colSpan = "3";	hr1.appendChild( document.createElement('hr') );		var pickerRow = pickerTable.insertRow(-1);	pickerRow.insertCell(-1).appendChild( createKeypadTable( clickDay ) );	pickerRow.insertCell(-1).appendChild( createMonthTable( clickMonth ) );	pickerRow.insertCell(-1).appendChild( createKeypadTable( clickYear ) );	var hr2 = pickerTable.insertRow(-1).insertCell(-1);	hr2.colSpan = "3";	hr2.appendChild( document.createElement('hr') );		function makeButton( row, legend, click )	{		var button = document.createElement( 'a' );		button.appendChild( document.createTextNode( legend ) );		button.onclick = click;		button.style.borderStyle = "outset";		var cell = row.insertCell(-1);		cell.align = "center";		cell.appendChild( button );	}		var buttonRow = pickerTable.insertRow(-1);		makeButton( buttonRow, 'Yesterday', setYesterday );	makeButton( buttonRow, 'Today', setToday );	makeButton( buttonRow, 'Clear', setNone );		pickerContainer.show = function( d )	{		var date = JugDateValueToStruct( d );				setDay( date.day );		setMonth( date.month );		setYear( date.year );				pickerContainer.appendChild( pickerTable );	}	pickerContainer.hide = function( d )	{		pickerContainer.removeChild( pickerTable );		return JugDateStructToValue( currentDate );	}		pickerContainer.JugType = "datePicker";	}//============ Table Maker ====================//============ Mapped Text ====================//============ Text Set ====================//============ Button Set ====================//======= Component Factory =========// An array, indexed by jug object style, of jug element conversion functions. Each function takes an element id and property set as parametersvar JugFactory = { 	"button" : JugSetupButton, 					// standard button	"popupDialog" : JugSetupPopupDialog,		// popup dialog	"tabset" : JugCreateTabbedDisplay,			// a tabbed display	"menu" : JugCreateMenu,						// a pop up menu	"menuItem" : JugCreateMenuItem,				// a simple text menu item 	"subMenu" : JugCreateSubMenu,				// a menu item that pops up a sub-menu	"optionalItem" : JugCreateOptionalItem,		// an optional menu item (one that may be disabled)	"dynamicMenu" : JugCreateDynamicMenu,		// a dynamically populated menu	"grid" : JugCreateGrid,						// grid	"toolTip" : JugCreateToolTip,				// tool tip	"textField" : JugCreateTextField,			// text field	"countWords" : JugCreateCountWordsField,	// count words in text field	"checkBox" : JugCreateCheckBox,				// check box	"dropDown" : JugCreateDropDown,				// drop down	"radioButtons" : JugSetupRadioButtons,		// set of radio buttons with text legends (at most one can be depressed)	"radioSet" : JugSetupRadioSet,				// a radio button set with text legends (any number can be depressed)	"selector" : JugCreateSelector,				// edit box / drop down combo selector	"plainText" : JugCreatePlainTextField,		// plain text element	"enable" : JugCreateEnabler,				// element enabler	"overlay" : JugCreateOverlay,				// overlay	"datePicker" : JugCreateDatePicker,			// date picker	"slideshow" : JugCreateSlideshow			// photo slide show	};//======= Start the Jug Framework ========function JugStart(){	try{		//=== Convert buttons etc ===		var jugObjects = [];	// formed into array of elements that need converting				// function to assemble list of jug objects, with deepest first		function findObjects( elem )		{			// depth first search			var i;			for( i=0; i < elem.childNodes.length; i++ )			{				findObjects( elem.childNodes[ i ] );			}						if( elem.hasAttribute && elem.hasAttribute( "jug" ) )			{				// Safari, Firefox				jugObjects.push( elem );			}			else if( elem.jug )			{				// IE				jugObjects.push( elem );				}		}				findObjects( document.body );				// Convert each object		var i;		for( i=0; i < jugObjects.length; i++ )		{			var obj = jugObjects[ i ];			// convert the jug attribute to a property set			var props;			try{				props = eval("({" + obj.getAttribute( "jug" ) + "})" );	// add round brackets to ensure braces are not treated as block begin/end			}			catch( e )			{				throw new Error( "eval error - " + obj.getAttribute( "jug" ) );			}			// take the kind property and apply the appropriate jug factory for each style in the list			var kinds = props.kind.split(" ");			var id = obj.id;			if( id == "" )			{				throw new Error( "Jug object has no id property: " + obj );			}			var k;			for( k=0; k < kinds.length; k++ )			{				var kind = kinds[k];				if( !JugFactory[ kind ] )				{					throw new Error( "no kind " + kind + ", element " + id );				}				JugFactory[ kind ]( id, props );			}		}			// Send the Loaded message		JugSendMessage( document.body, "onJugLoaded", [] );				//=== set up the global event handlers so we can check key/mouse events ===				// Update the mouse coords, as (result.x, result.y), from a window event		function setMouseCoords( e )		{			var posx = 0;			var posy = 0;			if( e === undefined )			{				e = window.event;			}			if( e.type == "keydown" )			{				// ignore this (can have junk as coords)			}			else if( e.pageX )			{if( e.pageX > 100000 ){	// mystery FF bug - wild values for pageX/Y	alert( "page " + e.pageX + " " + typeof( e.pageX ) + " clientX " + e.clientX + " tpe " + e.type );}				JugMouseCoords = { x: e.pageX, y: e.pageY };			}			else if ( e.clientX )			{				JugMouseCoords = { x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft, y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop };			}			else			{				// no coord information, so don't update coords			}		}			function keyDown( e )		{			setMouseCoords( e );	// update mouse coordinates				// find out which key has been pressed			var key;			if( window.event && window.event.keyCode )			{				key = window.event.keyCode;			}			else if( e.which )			{				key = e.which;			}			else if( e.keyCode )			{				key = e.keyCode;			}			else			{				key = 0;			}					// determine what kind of element the click is aimed at, because we want to allow "enter" in multi-line edit boxes			var element;			if( e.target )			{				element = e.target;			}			else			{				// Explorer				element = e.srcElement;			}			if( key == 13 )			{				// "enter" key pressed								if( element.nodeName == "TEXTAREA" )				{					return true;	// RETURN allowed in multiline text boxes				}				else				{					// pass onReturn message to any Jug elements wanting it					JugSendMessage( document.body, "onReturn", [] );										// stop Firefox handling the key anyway					if( e.stopPropagation )					{						e.stopPropagation();						e.preventDefault();					}								return false;				}			}			else if( element.tagName == "BODY" )			{				// key aimed at BODY means no input box has focus				JugSendMessage( document.body, "onKeydown", [ String.fromCharCode(key) ] );				return false;			}			else			{				// key aimed at some input box				return true;			}		}				var dragOffset = null;	// offset from top left corner of object being dragged to focus point				function mouseDown( e )		{			setMouseCoords( e );			return true;		}				function mouseMove( e )		{			var start = JugMouseCoords;			setMouseCoords( e );										if( JugMoveElem != null )			{				JugMoveElem.mouseMove();			}						if( JugDragElem == null )			{				return true;			}			else			{				if( dragOffset == null )				{					// first drag, so remember focus point					var corner = JugGetElementArea( JugDragElem );					dragOffset = { left: start.x - corner.left, top: start.y - corner.top };				}					// drag element				var area = { left : JugMouseCoords.x - dragOffset.left, top : JugMouseCoords.y - dragOffset.top, 							 width : JugDragElem.offsetWidth, height : JugDragElem.offsetHeight };				area = JugConstrainToPage( area );				JugDragElem.style.left = area.left + "px";				JugDragElem.style.top = area.top + "px";				return false;			}		}				function mouseUp( e )		{			setMouseCoords( e );						if( JugDragElem == null )			{				return true;			}			else			{				// finish dragging				JugDragElem = null;				dragOffset = null;				return false;			}		}				if( document.addEventListener )		{			document.addEventListener( "keydown", keyDown, true ); 			document.addEventListener( "mousedown", mouseDown, true ); 				document.addEventListener( "mousemove", mouseMove, true );			document.addEventListener( "mouseup",   mouseUp, true );		}		else if( document.attachEvent )		{			// Explorer			document.attachEvent( "onkeydown", keyDown );			document.attachEvent( "onmousedown", mouseDown );				document.attachEvent( "onmousemove", mouseMove );			document.attachEvent( "onmouseup",  mouseUp );		}				window.onresize = function()		{			JugSendMessage( document.body, "onResize", new Array() );		}		window.onscroll = function()		{			JugSendMessage( document.body, "onResize", new Array() );		}	}	catch( e )	{		JugShowException( e );	}}//=================== XML Interface =================== // Create an XML document by parsing some textfunction JugMakeXMLDoc( xmlText ){	if( typeof ActiveXObject != "undefined" )	{		//Internet Explorer		var xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );		xmlDoc.async = "false";		xmlDoc.loadXML( xmlText );		return xmlDoc;	}	else	{		// assume W3C compliant		var parser = new DOMParser();		return parser.parseFromString( xmlText, "text/xml" );	}}// Create a new XML document with just an outer tag having the given namefunction JugNewXMLDoc( tagName ){	if( typeof ActiveXObject != "undefined" )	{		//Internet Explorer		var xmlDoc = new ActiveXObject( "Microsoft.XMLDOM" );		xmlDoc.async = "false";		var text = "<" + tagName + "/>";		xmlDoc.loadXML( text );		return xmlDoc;	}	else	{		// assume W3C compliant		var xmlDoc = document.implementation.createDocument( "", tagName, null );		return xmlDoc;	}}// Produce text representation of an XML documentfunction JugXMLToText( doc ){	if( typeof ActiveXObject != "undefined" )	{		//Internet Explorer		return doc.xml;	}	else	{		// Mozilla interface		var serializer = new XMLSerializer();		return serializer.serializeToString( doc );	}}// Create an XML document representing a Javascript objectfunction JugObjectAsXML( obj ){	// obj in a Javascript object	// the result is an XML document representing the object	//	the XML document can be converted back to an object by calling JugXMLAsObject	var doc = JugNewXMLDoc( "object" );		// add a value's type and value to an xml node	function addValue( value, node, name )	{		switch( typeof value )		{			case "boolean":				node.setAttribute( "type", "boolean" );				if( value )				{					node.setAttribute( "value", "true" );				}				else				{					node.setAttribute( "value", "false" );				}				break;			case "string":				node.setAttribute( "type", "string" );				node.setAttribute( "value", value );				break;			case "number":				node.setAttribute( "type", typeof value );				node.setAttribute( "value", value );				break;			case "object":				if( value instanceof Array )				{					// the object is actually an array					node.setAttribute( "type", "array" );					var i;					for( i=0; i < value.length; i++ )					{						var arrayItem = doc.createElement( 'item' );						node.appendChild( arrayItem );						arrayItem.setAttribute( "index", i );						addValue( value[ i ], arrayItem, "index " + i );					}				}				else				{					// the object is a general one					node.setAttribute( "type", "object" );					addObject( value, node );				}				break;			default:				throw new Error( "JugXMLAsObject: unhandled object " + name + " type " + typeof value );		}	}		function addObject( obj, node )	{		for( attr in obj )		{			var element = doc.createElement( 'element' );			node.appendChild( element );			element.setAttribute( "name", attr );			addValue( obj[ attr ], element, attr );		}	}		addObject( obj, doc.documentElement );		return doc;}// Create a Javascript object from an XML descriptionfunction JugXMLAsObject( doc ){	// doc is an XML document object, whose outer tag must be "object"	//	typically the XML would be produced by calling JugObjectAsXML	// the result is a Javascript object		if( doc.documentElement.tagName != "object" )	{		throw new Error( "JugXMLAsObject: doc is " + doc.documentElement.tagName + " not object" );	}		function buildValue( node, name )	{		var type = node.getAttribute( "type" );		if( type == "boolean" )		{			var value = node.getAttribute( "value" );			if( value == "true" )			{				return true;			}			else if( value == "false" )			{				return false;			}			else			{				throw new Error( "JugXMLAsObject: " + name + " = invalid boolean " + value );			}		}		else if( type == "number" )		{			var value = node.getAttribute( "value" );			return parseFloat( value );		}		else if( type == "string" )		{			var value = node.getAttribute( "value" );			if( value )			{				return value;			}			else			{				return "";	// lack of value means empty string because PHP bug doesn't let us give attribute an empty value			}		}		else if( type == "object" )		{			return buildObject( node );		}		else if( type == "array" )		{			return buildArray( node );		}		else		{			throw new Error( "JugXMLAsObject: " + name + " unknown type " + type );		}	}		function buildArray( node )	{		var a = [];				var nodes = node.childNodes;		var i;		for( i=0; i < nodes.length; i++ )		{			var node = nodes[ i ];			var index = node.getAttribute( "index" );			a[ index ] = buildValue( node, "array" );		}				return a;	}		function buildObject( objectNode )	{		var obj = {};				var nodes = objectNode.childNodes;		var i;		for( i=0; i < nodes.length; i++ )		{			var node = nodes[ i ];			if( node.nodeType == 1 )	// 1 => element node			{				if( node.nodeName == "element" )				{					var name = node.getAttribute( "name" );					obj[name] = buildValue( node, name );				}				else				{					throw new Error( "JugXMLAsObject: unknown tag " + node.nodeName );				}			}		}				return obj;	}	return buildObject( doc.documentElement );}//============ Save and Restore field values =========function JugSaveFields( fieldNames ){	var state = {};	var i;	for( i in fieldNames )	{		var name = fieldNames[ i ];		var elem = JugElem( name );		if( elem.JugType )		{			if( elem.GetValue )			{				state[ name ] = elem.GetValue();			}			else			{				state[ name ] = name + " is " + elem.JugType;			}		}		else		{			if( elem.tagName == "INPUT" )			{				if( elem.type == "text" )				{					state[ name ] = elem.value;				}				else if( elem.type == "checkbox" )				{					state[ name ] = elem.checked;				}				else				{					state[ name ] = name + " is INPUT " + elem.type;				}			}			else if( elem.tagName == "SELECT" )			{				state[ name ] = elem.value;			}			else			{				state[ name ] = name + " is " + elem.tagName;			}		}	}	return state;}function JugRestoreFields( state ){	var i;	for( name in state )	{		var elem = JugElem( name );		if( elem.JugType )		{			if( elem.SetValue )			{				elem.SetValue( state[ name ] );			}			else			{				throw new Error( "Cannot set " + name );			}		}		else		{			if( elem.tagName == "INPUT" )			{				if( elem.type == "text" )				{					elem.value = state[ name ];				}				else if( elem.type == "checkbox" )				{					elem.checked = state[ name ];				}				else				{					throw new Error( "Cannot set " + name );				}			}			else if( elem.tagName == "SELECT" )			{				elem.value = state[ name ];			}			else			{				throw new Error( "Cannot set " + name );			}		}	}}function JugSetDropDownOptions( name, list ){	// remove existing elements from the drop down	// list is an object mapping display name to value	var elem = JugElem( name );		var n = elem.options.length;	var i;	for( i=0; i < n; i++ )	{		elem.remove(0);	}	// add the new elements	var name;	for( name in list )	{		var opt = document.createElement('option');		opt.text = name;		opt.value = list[ name ];		try		{			elem.add( opt, null );		}		catch(ex) 		{			// IE only			elem.add( opt );		}	}}//=================== HTTP Interface =================== function HTTP(){	// Constructor - allocates an HTTP Request object	if( window.XMLHttpRequest ) 	{		this.Request = new XMLHttpRequest();	} 	else if( window.ActiveXObject ) 	{		this.Request = new ActiveXObject("Microsoft.XMLHTTP");	}	else	{		throw new Error( "Cannot create HTTP object" );	}}// Synchronous GetHTTP.prototype.Get = function( url ){	this.Request.open( "GET", url, false );	this.Request.send("");	return this.Request.responseText;}// Asynchronous GetHTTP.prototype.GetAsync = function( url, completion ){	this.Request.onreadystatechange = function()	{		if( this.readyState == 4 )		{			if( this.status == 200 )			{				completion( this.responseText );			}			else			{				completion( "" );			}		}	}	this.Request.open( "GET", url, true );	this.Request.send("");}// Synchronous PostHTTP.prototype.Post = function( url, data ){	this.Request.open( "POST", url, false );	this.Request.send(data);	return this.Request.responseText;}// Asynchronous PostHTTP.prototype.PostAsync = function( url, data, completion ){	this.Request.onreadystatechange = function()	{		if( this.readyState == 4 )		{			if( this.status == 200 )			{				completion( this.responseText );			}			else			{				completion();			}		}	}	this.Request.open( "POST", url, true );	this.Request.send(data);}//=================== Server Interface =================== // class ServerRequest - for making requests of a serverfunction ServerRequest( url, onSessionExpiry ){	// Constructor - allocates a server request object	//	url is address to which requests are posted	//	onSessionExpiry is a function that is called if the session expires	//		if not provided, an exception is thrown if the session expires	this.url = url;	this.onSessionExpiry = onSessionExpiry;}ServerRequest.prototype.call = function( actionName, params, sessionRecoveryAction, username, password ){	// method to make a remote call to the server	// actionName - string giving name of action to invoke	// params - object giving any parameters required by the action	// sessionRecoveryAction - function that is called after a session is recovered	// RETURNS - object giving any result	// EXCEPTION - thrown if call fails or server returns an exception	var responseText;	// set to the text of the response from the server	try{		// form up the request as an XML string representing the action and its parameters		var requestText;		if( username )		{			// need to encrypt params			var paramsText = JugXMLToText( JugObjectAsXML( params ) );			var cipherText = JUGEncrypt( paramsText, password );			var request = { action:actionName, param:{username:username, cipherText:cipherText} };			requestText = JugXMLToText( JugObjectAsXML( request ) );		}		else		{			var request = { action:actionName, param:params };			requestText = JugXMLToText( JugObjectAsXML( request ) );		}		// post the request to the server		var http = new HTTP;		responseText = http.Post( this.url, requestText );		// display the request and results on the console for debugging purposes		if( window.console )		{			window.console.log("Request=" + requestText + "; Result=" + responseText);		}	}	catch( ex )	{		// generally failed to communicate		throw new Error( "Exception " + ex.message + " calling " + this.url + " with " + actionName );	}	// form up the response as an object	var responseObject;	// set to the object received from the server	try{		var responseXML = JugMakeXMLDoc( responseText );		responseObject = JugXMLAsObject( responseXML );	}	catch( e )	{		// failed to construct the object - either XML is invalid or does not describe an object		throw new Error( "Exception calling " + this.url + " with " + actionName + ": " + responseText );	}		if( responseObject.outcome == "success" )	{		// call succeeded		if( username )		{			var response = JUGDecrypt( responseObject.result, password );			var responseXML;			try{				responseXML = JugMakeXMLDoc( response );			}			catch( e )			{				// failed to construct the object - either XML is invalid or does not describe an object				throw new Error( "Exception calling " + this.url + " with " + actionName + ": " + response );			}			return JugXMLAsObject( responseXML );		}		else		{			return responseObject.result;	// return the result object		}	}	else if( responseObject.outcome == "exception" )	{		// call failed		throw new Error( "Error returned calling " + this.url + " with " + actionName + ": " + responseObject.error );	}	else if( responseObject.outcome == "expired" )	{		// session has expired		if( this.onSessionExpiry && sessionRecoveryAction )		{			this.onSessionExpiry( sessionRecoveryAction );			throw new SessionExpiry();	// special exception that is ignored by JugShowException		}		else		{			throw new Error( "Session expired" );		}	}	else	{		// unexpected response		throw new Error( "Unexpected response calling " + this.url + " with " + actionName + ": " + responseText );	}}ServerRequest.prototype.download = function( actionName, params ){	// method for making a server call that downloads data	// actionName - string giving name of action to invoke	// params - object giving any parameters required by the action	// RETURNS - nothing	try{		// form up the request as an XML string representing the action and its parameters		var request = { action:actionName, param:params };		var requestText = JugXMLToText( JugObjectAsXML( request ) );		var url = this.url + "?request=" + encodeURIComponent( requestText ) + "&id=";		// display the request on the console for debugging purposes		if( window.console )		{			window.console.log("Download Request=" + url);		}		// Note that for IE, the user must Enable File Download AND Automatic Prompting in Tools/InternetOptions/Security/InternetZone/CustomLevel/Downloads		var win = window.open( url );		if( !win )		{			alert( "Cannot open window" );		}	}	catch( ex )	{		throw new Error( "Exception " + ex.message + " getting download URL for " + this.url + " with " + actionName );	}}ServerRequest.prototype.callXD = function( actionName, params, resultCallback ){	// method to make a remote cross-domain call to the server	// actionName - string giving name of action to invoke	// params - object giving any parameters required by the action	// resultCallback - function that is called with the result of the server call once it is retrieved	// RETURNS - nothing, the call's result is asynchronously handed to the callback function called <resultActionName>	// EXCEPTION - thrown if call fails or server returns an exception	try{		// form up the request as an XML string representing the action and its parameters		var request = { action:actionName, param:params };		var requestText = JugXMLToText( JugObjectAsXML( request ) );				// allocate a unique ID for the script tag		var id = JugGetUniqueId( "SCRIPT" );		// Function called when cross-domain result is retreived		function callback( responseText )		{			// responseText - XML text description of response object			// resultAction - function to be called with the response object if call is successful			// id - unique identifer of the script tag used to get the result from the cross domain call					// display the request on the console for debugging purposes			if( window.console )			{				window.console.log("XD Response " + id + " = " + responseText );			}					// form up the response as an object			var responseObject;	// set to the object received from the server			try{				var responseXML = JugMakeXMLDoc( responseText );				responseObject = JugXMLAsObject( responseXML );			}			catch( e )			{				// failed to construct the object - either XML is invalid or does not describe an object				throw new Error( "Exception calling " + this.url + " with " + actionName + ": " + responseText );			}			if( responseObject.outcome == "success" )			{				// call succeeded				resultCallback( responseObject.result );	// deliver the result object			}			else if( responseObject.outcome == "exception" )			{				// call failed				throw new Error( "Error returned from cross domain call: " + responseObject.error );			}			else			{				// unexpected response				throw new Error( "Unexpected response calling " + this.url + " with " + actionName + ": " + responseText );			}					// Delete the script tag			var script = JugElem( id );			script.parentNode.removeChild( script );		}		// pass the request as the target of a SCRIPT tag		var scriptTag = document.createElement('SCRIPT');		scriptTag.type = 'text/javascript';		scriptTag.src = this.url + "?request=" + encodeURIComponent( requestText ) + "&id=" + id;		scriptTag.id = id;		scriptTag.callback = callback;		document.body.appendChild( scriptTag );	// start to load the script and so make the request				// display the request on the console for debugging purposes		if( window.console )		{			window.console.log("XD Request=" + requestText);		}	}	catch( ex )	{		// generally failed to communicate		throw new Error( "Exception " + ex.message + " calling " + this.url + " with " + actionName );	}}ServerRequest.prototype.callAsync = function( actionName, params, resultCallback ){	// method to make a remote call to the server	// actionName - string giving name of action to invoke	// params - object giving any parameters required by the action	// sessionRecoveryAction - function that is called after a session is recovered	// RETURNS - object giving any result	// EXCEPTION - thrown if call fails or server returns an exception	var responseText;	// set to the text of the response from the server	try{		// form up the request as an XML string representing the action and its parameters		var request = { action:actionName, param:params };		var requestText = JugXMLToText( JugObjectAsXML( request ) );				// Function called when async result is retreived		function completion( responseText )		{			if( responseText )			{				// POST completed successfully				// display the request on the console for debugging purposes				if( window.console )				{					window.console.log("Async Response = " + responseText );				}				// form up the response as an object				var responseObject;	// set to the object received from the server				try{					var responseXML = JugMakeXMLDoc( responseText );					responseObject = JugXMLAsObject( responseXML );				}				catch( e )				{					// failed to construct the object - either XML is invalid or does not describe an object					throw new Error( "Exception calling " + this.url + " with " + actionName + ": " + responseText );				}				if( responseObject.outcome == "success" )				{					// call succeeded					if( resultCallback )					{						resultCallback( responseObject.result );	// deliver the result object					}				}				else if( responseObject.outcome == "exception" )				{					// call failed					throw new Error( "Error returned from async call: " + responseObject.error );				}				else				{					// unexpected response					throw new Error( "Unexpected response calling " + this.url + " with " + actionName + ": " + responseText );				}			}			else			{				// POST failed				throw new Error( "Post of async call failed" );			}		}		// post the request to the server		this.asyncHttp = new HTTP;	// kill off any existing async post		responseText = this.asyncHttp.PostAsync( this.url, requestText, completion );				// display the request and results on the console for debugging purposes		if( window.console )		{			window.console.log("Async request = " + requestText );		}	}	catch( ex )	{		// generally failed to communicate		throw new Error( "Exception " + ex.message + " calling " + this.url + " with " + actionName );	}}
