
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Browser Compatibility Preload
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

// Not sure if this is necessary on any current browsers
// Taken from mozilla Object:Array:map reference
if (!Array.prototype.map)
{
  Array.prototype.map = function(fun /*, thisp*/)
  {
    var len = this.length;
    if (typeof fun != "function")
      throw new TypeError();

    var res = new Array(len);
    var thisp = arguments[1];
    for (var i = 0; i < len; i++)
    {
      if (i in this)
        res[i] = fun.call(thisp, this[i], i, this);
    }

    return res;
  };
}


/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Show/Hide/Delete objects
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

function px_GetById( id )
{
  var el = document.getElementById(id)
  //alert( typeof(el) )
  //alert( typeof('yeah') == "string" )
  //alert( 'yeah' instanceof string )
  return el
}

function px_Refresh() { window.location.reload(true) }
function px_Redirect( url ) { window.location = url }

function px_CursorWait()		{ document.body.style.cursor = 'wait' }
function px_CursorNormal()	{ document.body.style.cursor = '' }

function px_Get( id_or_obj )
{
	return typeof(id_or_obj) == "string" ? px_GetById( id_or_obj ) : id_or_obj
}

/* Returns an array of all items with ids prefixed by the given prefix.
filter: If not null, then the filter is called on the object, and it is not added to the list
	if the filter returns false.
The indices start at zero and the iteration stops as soon as an item does not exist.
*/
function px_GetAllByPrefix( prefix, filter )
{
	var items = []
	var count = 0
	for ( var ic = 0; obj = px_GetById(prefix + ic); ic++ )
	{
		if ( filter == null || filter(obj) )
			items[count++] = obj
	}
	return items
}

function px_Delete( obj )
{
	var obj = px_Get(obj)
	if ( obj != null && obj.parentNode != null ) obj.parentNode.removeChild( obj )
}

function px_IsVisible( obj )
{
	obj = px_Get(obj)
	return obj.style.display != 'none'
}

// eg: px_SetVisible( ['form1', 'form2'], true ) or px_SetVisible( 'form1', false )
function px_SetVisible( ids, show )
{
	if ( typeof(pxGlobShowHideDisp) == "undefined" ) pxGlobShowHideDisp = []

	ids = ids instanceof Array ? ids : [ids]

	for ( var id = 0; id < ids.length; id++ )
	{
		var el = px_Get( ids[id] )
		if ( el == null ) continue
		if ( show )
		{
			// show
			if ( el.style.display == 'none' )
			{
				var prev = pxGlobShowHideDisp[ids[id]]
				if ( prev == null )
					prev = pxGlobShowHideDisp[ids[id]] = ''
				el.style.display = prev
			}
			else
			{
				// save state, and do not modify, because it may be something like 'block'
				pxGlobShowHideDisp[ids[id]] = el.style.display
			}
		}
		else
		{
			// hide
			if ( el.style.display == 'none' )
			{
				// do nothing
			}
			else
			{
				// save current display state to global table
				pxGlobShowHideDisp[ids[id]] = el.style.display
				el.style.display = 'none'
			}
		}
	}
}

// Turns visibility on
function px_Hide( ids )
{
	px_SetVisible( ids, false )	
}

// Turns visibility off
function px_Show( ids )
{
	px_SetVisible( ids, true )	
}

// Toggles visibility on/off
// eg: px_ShowHide( 'form1' ) or px_ShowHide( ['form1', 'form2'] )
function px_ShowHide( ids )
{
	ids = ids instanceof Array ? ids : [ids]
	for ( var id = 0; id < ids.length; id++ )
	{
		var el = px_GetById( ids[id] )
		px_SetVisible( [ids[id]], el.style.display == 'none' )
	}
}

function px_Focus( obj )
{
  try
  {
    // IE throws exception if object is hidden
    px_Get( obj ).focus()
  }
  catch(err) {}
}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// AJAX
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

// Recursive decomposition of a simple xml data tree into a JS hash type thing
// Example best explains this:
// <root> <jack value='sprat'/><jim value='1'/> <nest><one value='2'/></nest></root>
// becomes
// obj[jack => sprat, jim => 1, nest[one => 2] ]
function px_XmlDecompose( xml )
{
	var tab = []
	for ( var i = 0; i < xml.childNodes.length; i++ )
	{
		var el = xml.childNodes[i]
		if ( el.childNodes.length > 0 )
		{
			tab[el.nodeName] = px_XmlDecompose( el.childNodes[i] )
		}
		else
		{
			var v = el.getAttribute('value')
			switch ( v )
			{
				case 'false': v = false; break
				case 'true': v = true; break
			}
			tab[el.nodeName] = v
		}
	}
	return tab
}


function px_XmlNoCache( req ) { req.setRequestHeader( "If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT" ) }

function px_Busy()
{
	px_Delete( 'px_busy' )
	
	var div = document.createElement('div')
	div.className = 'px-busy'
	div.id = 'px_busy'
	var txt = document.createTextNode('Busy...')
	div.appendChild( txt )
	document.body.appendChild( div )
	div.style.left = (px_BodyWidth() / 2) - (div.offsetWidth / 2) + 'px'
	//alert( div.clientWidth )
	return div
}

// Synchronous px_XmlSimple().responseXML.firstChild 
function px_XmlS( url, query )
{
	return px_XmlSimple( url, query ).responseXML.firstChild
}

/* Executes XmlS. Returns an object with two members.
ok => true if response's root is <ok>
error => error message if !ok, which assumes error is of the form <error msg='description'/>
*/
function px_XmlS_Check( url, query )
{
	var xml = px_XmlS( url, query )
	var resp = new Object
	if ( xml.nodeName == 'ok' )
	{
		resp.ok = true
		resp.error = null
	}
	else
	{
		resp.ok = false
		resp.error = xml.getAttribute( 'msg' )
	}
	return resp
}

// px_XmlSimple via GET (Synchronous if finished is null)
function px_XmlSimple( url, query, finished )
{
	return px_XmlSimple_Internal( "GET", url, query, finished )
}

// px_XmlSimple via POST (Synchronous if finished is null)
function px_XmlSimplePOST( url, query, finished )
{
	return px_XmlSimple_Internal( "POST", url, query, finished )
}

/* Performs a simple synchronous or asynchronous javascript request (with XmlHttpRequest).
method: "GET" or "POST"
url: Address
query: Array of [name=value] pairs that will be escaped and added to the url. Only the values are escaped.
	When POSTing, the query is sent as the body. When GETting, the query is joined to the url with a '?'.
finished: If this is not null, then the function is executed asynchronously. This function is called with the
  same object that is returned from a synchronous call. An asynchronous call returns null.
*/
function px_XmlSimple_Internal( method, url, query, finished )
{
	var xml = px_XmlRequest()
	var async = finished != null
	if ( !async ) px_CursorWait()
	
	//alert( 'sending: ' + px_CombineUrl( url, px_MakeQueryKV(query) )  )
	if ( query != null )
		query = typeof(query) == "string" ? query : px_MakeQueryKV(query)

	watcher = function()
	{
		if ( xml.readyState == 4 )
		{
			finished( make_stat() )
		}
	}
	
	make_stat = function()
	{
		var stat = new Object
		stat.ok = xml.responseXML.firstChild.nodeName == 'ok'
		if ( stat.ok )	stat.error = null
		else
		{
			stat.error = xml.responseXML.firstChild.getAttribute('msg')
			if ( stat.error == null ) stat.error = "Unspecified error (ie ajax response was not <ok/>)"
		}
		stat.responseXML = xml.responseXML
		stat.responseText = xml.responseText
		stat.request = xml
		return stat
	}

	if ( method == "POST" )
	{
		xml.open( "POST", url, async )
		xml.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' )
		if ( async ) xml.onreadystatechange = watcher
		xml.send( query )
	}
	else if ( method == "GET" )
	{
		xml.open( "GET", px_CombineUrl( url, query ), async )
		// not necessary with POST because they are never cached.
		px_XmlNoCache( xml )
		if ( async ) xml.onreadystatechange = watcher
		xml.send( null )
	}
	else throw "query must be either 'GET' or 'POST'"

	if ( !async ) px_CursorNormal()

	if ( async ) 	return null
	else					return make_stat()
}

function px_CombineUrl( url, query )
{
	if ( query == null ) return url
	if ( typeof(query) == "array" ) query = px_MakeQueryKV( query )
	return url + '?' + query	
}

// makes prefix0=ary[0]&prefix1=ary[1]... (URL escaped)
function px_MakeQueryList( prefix, ary )
{
	var query = ''
	prefix = encodeURIComponent(prefix)
	for ( var i = 0; i < ary.length; i++ )
	{
		query += prefix + i + "=" + encodeURIComponent(ary[i]) + "&"
	}
	return query.substr(0, query.length-1)
}

// makes ary[0]=ary[1]&ary[2]=ary[3]... (odd indices escaped)
function px_MakeQueryKV( ary )
{
	var query = ''
	for ( var i = 0; i < ary.length; i += 2 )
	{
		query += ary[i] + "=" + encodeURIComponent(ary[i+1]) + "&"
	}
	return query.substr(0, query.length-1)
}

// Takes an xml object that has a set of <object> tags and inserts them into a list box
function px_ListBoxFromXml( xml, into )
{
	into = px_Get(into)
	for ( var i = 0; i < xml.childNodes.length; i++ )
	{
		var el = xml.childNodes[i]
		var value = el.getAttribute('value')
		px_ListAdd( into, el.firstChild.nodeValue, value )
	}
}

// Expects a reply of <anything_root> <anything_again> <option>option1</option>...
function px_GetListBox( url, into )
{
	into = px_Get(into)
	var xml = px_XmlRequest()
	xml.onreadystatechange = function()
	{
		if ( xml.readyState < 4 )				into.innerHTML = '<option>Retrieving Items...</option>'
		else if ( xml.readyState == 4 )
		{
			into.innerHTML = ''
			px_ListBoxFromXml( xml.responseXML.firstChild, into )
		}
	}
	xml.open( "GET", url, true )
	px_XmlNoCache( xml )
	xml.send( null )
}

// Returns a progress message, depending on the status of the Xml Request.
function px_BusyMsg( readyState )
{
	var txt = ''
	switch( readyState )
	{
		case 0: txt = 'Preparing Request...'; break
		case 1: txt = 'Sending Request...'; break
		case 2: txt = 'Sent Request...'; break
		case 3: txt = 'Awaiting Reply...'; break
		case 4: txt = 'Done'; break
	}
	return txt
}

function px_XmlRequest()
{
	var xmlHttp
	try
	{
		// Firefox, Opera 8.0+, Safari
		xmlHttp = new XMLHttpRequest()
		if ( xmlHttp.overrideMimeType ) xmlHttp.overrideMimeType('text/xml')
	}
	catch (e)
	{    
		// Internet Explorer
		try
		{
			xmlHttp = new ActiveXObject("Msxml2.XMLHTTP")
		}
		catch (e)
		{      
			try
			{
				xmlHttp = new ActiveXObject("Microsoft.XMLHTTP")
			}
			catch (e)
			{
				alert("Your browser does not support AJAX!")
				return false
			}
		}
	}
	// IE6 is not cool with this. I don't know how to fix it.
	// xmlHttp.nocache = function() { px_XmlNoCache( this ) }
	return xmlHttp
}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Session management common to browser and server
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

function px_LoadSession( url )
{
	return px_XmlDecompose( px_XmlS(url) )
}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Document building functions
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////

// Adds a text node to the object
function px_DomGenText( toObj, txt )
{
	return px_Get(toObj).appendChild( document.createTextNode( txt ) )
}

// Adds an element to the object
function px_DomGen( toObj, tagName, attribs )
{
	var obj = px_Get(toObj).appendChild( document.createElement( tagName ) )
	if ( attribs != null )
	{
		if ( attribs.length % 2 != 0 ) throw "attribs must be key,value pairs"
		for ( var i = 0; i < attribs.length; i += 2 )
		{
			obj.setAttribute( attribs[i], attribs[i + 1] )
		}
	}
	return obj
}

// Deletes all children of the object
function px_DomClear( obj )
{
	obj = px_Get(obj)
	while ( obj.childNodes.length > 0 )
		obj.removeChild( obj.childNodes[0] )
}

/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////
// Standard GUI item manipulations and queries
/////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////


function px_BodyWidth()							{ return document.documentElement.clientWidth }
function px_TxtFieldValue( obj )		{ return px_Get(obj).value }
function px_ComboGetSel( obj )			{ return px_Get(obj).value }
function px_ComboSetSel( obj, sel ) { return px_Get(obj).value = sel }

function px_ListAdd( obj, name, value )
{
	obj = px_Get(obj)
	var opt = document.createElement( 'option' )
	var txt = document.createTextNode( name )
	opt.appendChild( txt )
	if ( value != null ) opt.setAttribute( 'value', value )
	obj.appendChild( opt )
}


/* Retrieves px_GetAllByPrefix(), and sets all of their ticks either on or off, depending
on their previous state:
All On         => All Off
Anything Else  => All On
*/
function px_TickAll( prefix )
{
	var items = px_GetAllByPrefix( prefix )
	var count = 0
	for ( var i = 0; i < items.length; i++ )
		if ( items[i].checked ) count++
	
	var on = count != items.length
	
	for ( var i = 0; i < items.length; i++ )
		items[i].checked = on
}

/* Returns an array of the names of all items as retrieved by px_GetAllByPrefix(), 
whos 'checked' attribute is true.
*/
function px_TickedListNames( prefix )
{
	var items = px_GetAllByPrefix( prefix, function(a) { return a.checked } )
	var names = []
	for ( var i = 0; i < items.length; i++ )
		names[i] = items[i].name
	return names
}


/*
Returns filtered items in a list box.
Return: An array of strings, which are the values inside the <option> tags.
list:
  The list box, a <select> item.
filter:
  0:  all
  1:  only selected
  -1: only unselected
*/
function px_ListFiltered( list, filter )
{
	list = px_Get(list)
	var all = []
	var count = 0
	for ( var i = 0; i < list.childNodes.length; i++ )
	{
		var opt = list.childNodes[i]
		if (	(filter == 0) ||
					(filter == 1 && opt.selected) ||
					(filter == -1 && !opt.selected) )
		{
			all[count++] = opt.firstChild.nodeValue
		}
	}
	return all
}

function px_ListAll( list )						{ return px_ListFiltered( list, 0 ); }
function px_ListSelected( list )			{ return px_ListFiltered( list, 1 ); }
function px_ListUnselected( list )		{ return px_ListFiltered( list, -1 ); }


// ROV = Rollover

function px_ROV_Org()
{
	if ( !document.px_ROV_OrgStore ) document.px_ROV_OrgStore = new Object();
	return document.px_ROV_OrgStore;
}

function px_ROV_Exit( id )
{
	if ( px_ROV_Org()[id] ) px_GetById(id).src = px_ROV_Org()[id];
}

function px_ROV_Enter( id, hot )
{
	var img = px_GetById(id)
	if ( px_ROV_Org()[id] == null ) px_ROV_Org()[id] = img.src
	img.src = hot
}