//##############################################################################
// AJAX Engine
// Author:  Simon A. Platten
// Date:    16/10/09
// Notes:   This file contains a generic suite of functions for
//          providing AJAX functionality.
//
//          When the page is loaded the AJAXConnect() routine is called
//          Use the routines AJAXGet or AJAXPost to send data to the
//          server.  Use the routine AJAXAddHander to add an XML
//          handler to the engine.
//##############################################################################
// History:
//  16/10/09  SP, Created
//##############################################################################

function RTrim(STRING){
while(STRING.charAt((STRING.length -1))==" "){
STRING = STRING.substring(0,STRING.length-1);
}
return STRING;
}

function LTrim(STRING){
while(STRING.charAt(0)==" "){
STRING = STRING.replace(STRING.charAt(0),"");
}
return STRING;
}

function Trim(STRING){
  STRING = LTrim(STRING);
  return RTrim(STRING);
}

//##############################################################################
// Function:    GetElementByID
// Parameters:  strID, the id of the element to find
//              objLevel, optional, could be parent or top
//##############################################################################
function GetElementByID( strID, objLevel )
{
  try{
    if ( document.getElementById )
      {
      if ( objLevel )
        return objLevel.document.getElementById( strID );
      return document.getElementById( strID );     
      }
    else if ( document.all )
      {
      if ( objLevel )
        return objLevel.document.all[ strID ];
      return document.all[ strID ];     
      }
  } catch( e ) {
    // Catch errors but don't report here....let caller handle
    //alert( "ERROR in GetElementByID: " + e.message );
  }    
  return null;
}
//##############################################################################
// Class:       AJAX_MESSAGE
// Notes:       Used internally to send messages from the client to the server
//
// Properties:
//  lngID                         - Unique message ID
//  lngTOTmrID                    - Timeout timer ID
//  lngElapsed                    - Number of seconds elapsed since request
//  strMethod                     - POST or GET
//  strURL                        - Where to send the message
//  strData                       - Data delimited by &
//  objClient                     - AJAX_CLIENT this message is from
//
// Methods:
//  AJAX_MESSAGE( Constructor )
//  DESTROY ( Destructor )        - JS doesn't support destructors, you call it!
//  Error                         - Internal error handler
//  StartTimeout                  - Starts server response timeout timer
//  StopTimeout                   - Stops timeout timer
//##############################################################################
// History:
//  05/08/08  SP, Created
//##############################################################################
function AJAX_MESSAGE( objClient, strMethod, strURL, strData )
{
  try {
    this.objClient   = objClient;
    this.lngID       = ++objClient.lngNextMsgID;
    this.lngElapsed  = 0;
    this.lngRetryCnt = 0;
    this.strMethod   = strMethod;
    this.strURL      = strURL;
    this.strData     = (strData == null || strData == undefined) ? "" : strData;
  } catch( e ) {
    this.Error( "AJAX_MESSAGE:", e );
  }
}
//##############################################################################
// Method:      DESTROY
// Parameters:  None
// Notes:       Call this when you are finished with the object to cleanup
//##############################################################################
AJAX_MESSAGE.prototype.DESTROY = function()
{
  try {
    this.StopTimeout();

    if ( window.CollectGarbage )
      {
  // Now 'try' and get IE to take out the garbage      
      window.CollectGarbage();
      }
  } catch( e ) {
    this.Error( "DESTROY:", e );
  }
}
//##############################################################################
// Method:      Error
// Parameters:  strMsg, the error messsage
//              Exception
//##############################################################################
AJAX_MESSAGE.prototype.Error = function( strMsg, Exception )
{
  this.strError = "AJAX_MESSAGE:" + strMsg;
  
  if ( Exception.message )
    this.strError += Exception.message;
  else
     this.strError += Exception;
  if ( this.objClient )
    this.objClient.Error( this.strError );     
}
//##############################################################################
// Method:      StopTimeout
// Parameters:  None
// Notes:       Stops the timeout timer
//##############################################################################
AJAX_MESSAGE.prototype.StopTimeout = function()
{
  try {
    if ( this.lngTOTmrID == 0 )
      return;
    clearTimeout( this.lngTOTmrID );
    this.lngTOTmrID = 0;
  } catch( e ) {
    this.Error( "StopTimeout:", e );
  }
}
//##############################################################################
// Method:      OneSecTimer
// Parameters:  None
// Notes:       Fired whilst we are waiting for a server response
//##############################################################################
AJAX_MESSAGE.prototype.OneSecTimer = function()
{
  ++this.lngElapsed;
  
  if ( this.lngElapsed >= this.objClient.lngTimeout )
    {
  // Timeout has occurred    
    this.objClient.Timedout( this.lngID );
    
  // This message has timed out, has all retry attempts been exhausted ?
    if ( ++this.lngRetryCnt >= this.objClient.lngRetries )
      {
      return;
      }
    this.objClient.objMsgQueue.Add( this, this.lngID );
    this.lngElapsed = 0;
    }
  // Keep the timer going until timeout or we have a response
  this.StartTimeout();
}
//##############################################################################
// Method:      StartTimeout
// Parameters:  None
// Notes:       Starts a timer which fires once a second until either a time
//              out occurs or we received a response from the server
//##############################################################################
AJAX_MESSAGE.prototype.StartTimeout = function()
{
  try {
    this.StopTimeout();
    var strFun = "new function() {\n" +
                    "\tvar objMsg = " + this.objClient.strInstanceName + 
                    ".FindActiveMsg( " + this.lngID + " );\n" +
                    "\tif ( objMsg == null )\n" +
                    "\t\treturn;\n" +
                    "\tobjMsg.OneSecTimer();\n" +
                    "}";                    
    this.lngTOTmrID = setTimeout( strFun, 1000 );
  } catch( e ) {
    this.Error( "StartTimeout:", e );
  }
}
// Properties
AJAX_MESSAGE.prototype.lngID;
AJAX_MESSAGE.prototype.lngTOTmrID;
AJAX_MESSAGE.prototype.lngRetryCnt;
AJAX_MESSAGE.prototype.strMethod;
AJAX_MESSAGE.prototype.strURL;
AJAX_MESSAGE.prototype.aryURI;
AJAX_MESSAGE.prototype.strData;
AJAX_MESSAGE.prototype.objClient;


//##############################################################################
//##############################################################################
// Class:       AJAX_HANDLER
// Notes:       Used internally to manage XML responses
//
// Properties:
//
// Methods:
//  AJAX_HANDLER( Constructor )
//##############################################################################
// History:
//  10/08/08  SP, Created
//##############################################################################
function AJAX_HANDLER( strNode, pCallback )
{
  this.strNode    = strNode;
  this.pCallback  = pCallback;
  this.lngCalls   = 0;
  this.lngExeTime = 0;    
}
// Properties
AJAX_HANDLER.prototype.strNode;
AJAX_HANDLER.prototype.pCallback;
AJAX_HANDLER.prototype.lngCalls;
AJAX_HANDLER.prototype.lngExeTime;

//##############################################################################
//##############################################################################
// Class:       AJAX_LOG_ENTRY
// Notes:       Used internally to manage logs
//
// Properties:
//
// Methods:
//  AJAX_LOG_ENTRY( Constructor )
//##############################################################################
// History:
//  10/08/08  SP, Created
//##############################################################################
function AJAX_LOG_ENTRY()
{
  this.strTime         = "";
  this.msTime          = 0;
  this.strResponseTime = "";
  this.strMethod       = "";
  this.strID           = "";
  this.strURL          = "";
  this.strData         = "";
}
// Properties
AJAX_LOG_ENTRY.prototype.strTime;
AJAX_LOG_ENTRY.prototype.msTime;
AJAX_LOG_ENTRY.prototype.strResponseTime;
AJAX_LOG_ENTRY.prototype.strMethod;
AJAX_LOG_ENTRY.prototype.strID;
AJAX_LOG_ENTRY.prototype.strURL;
AJAX_LOG_ENTRY.prototype.strData;

//##############################################################################
//##############################################################################
// Class:       AJAX_CLIENT
// Notes:       Used to interface clients to server
//
// Properties:
//  strInstanceName               - Name of object instance in script
//  strError                      - Last error message
//  strDebug                      - Element name of debug DIV in document
//  lngErrCnt                     - Error counter
//  lngTxCnt                      - Messages sent counter
//  lngRxCnt                      - Messages received counter
//  lngTOCnt                      - Timeout counter
//  lngTimeout                    - Server response timeout in seconds
//  lngRetries                    - The number at attempts of sending a message
//  lngServiceInterval            - How often to server message queue, millisecs
//  lngServiceTmrID               - Service timer ID
//  lngNextMsgID                  - Next available message ID
//  blnBusy                       - Control status
//  blnDebug                      - Debug mode status, true=enabled
//  blnEnabled                    - Control status, use Enable() to set
//  objDebug                      - Debug log
//  objMsgQueue                   - AJAX_MESSAGE queue
//  objTransQueue                 - Acitve transaction queue  
//  objTxLog                      - Messages that have been sent
//  objRxLog                      - Messages that have been received
//  objXMLRoot                    - Current XML root node from response
//  OkToSubmit                    - Optional callback routine
//
// Methods:
//  AJAX_CLIENT( Constructor )
//  DESTROY ( Destructor )        - JS doesn't support destructors, you call it!
//  AddHandler                    - Adds an XML response handler
//  Debug                         - Set debug mode
//  DuplicateMessage              - Looks for duplicate messages
//  Enable                        - Used to enable and disable control
//  Error                         - Internal error handler
//  FindActiveMsg                 - Finds a message by its ID in the active que
//  FindXMLnode                   - Finds an XML node in the current response
//  GetDebugElement               - Returns the element for debugging
//  GetFunctionName               - Returns the function name
//  GetXMLvalue                   - Extract data from found XML node
//  GetXMLString                  - Returns the raw XML text
//  GET                           - Send request using GET method
//  Log                           - Logs a message, only when debugging enabled
//  MessageReceived               - Called when message response received
//  MessageSent                   - Called when message successfully sent
//  POST                          - Send request using POST method
//  PurgeTransQueue               - Empties transaction queue
//  ServiceQueue                  - Service message queue
//  RemoveHandler                 - Removes and XML handler
//  RemoveMsgFromTransQueue       - Removes msg from active transaction queue
//  ResponseHandler               - Deals with XML server responses
//  Send                          - Send message to server
//  Stop                          - Stop servicing message queue
//  Start                         - Start servicing message queue
//  Timeout                       - Set server response timeout
//  Timedout                      - Called when a message timeout occurs
//
// Usage:
//  Create an instance of the object:
//    var AJ = new AJAX_CLIENT( "AJ" );
//    AJ.AddHandler( "node", Callback );
//    AJ.POST( "...", "data=1" );
//##############################################################################
// History:
//  05/08/08  SP, Created
//  02/10/08  CIG, Modified to now also store the raw XML text received in strXML.
//			  The text can be accessed using the new function GetXMLString
//##############################################################################
function AJAX_CLIENT( strInstanceName )
{
  try {
  // Initialise properties 
    this.strInstanceName  = strInstanceName;
    this.strDebug         = this.strInstanceName + "_debug";
    this.strError         = "";
    this.lngErrCnt        = 0;
    this.lngTxCnt         = 0;
    this.lngRxCnt         = 0;
    this.lngTOCnt         = 0;
    this.lngNextMsgID     = 0;
    this.blnBusy          = false;
    this.blnEnabled       = false;
    this.objMsgQueue      = new STACK();
    this.objTransQueue    = new STACK();
    this.objDebug         = new STACK();
    this.objTxLog         = new STACK( 10 );
    this.objRxLog         = new STACK( 10 );
    this.objHandlers      = new STACK();
    this.objXMLRoot       = null;
    this.OkToSubmit       = null;
// Attempt to connect to xmlHttp
    this.objXMLHttp       = null;     
  	try {
  		this.objXMLHttp = new XMLHttpRequest();
  	} catch(e) {
  		var xmlHttpVersions = new Array( 'MSXML2.XMLHTTP.6.0',
  													'MSXML2.XMLHTTP.5.0',													
  													'MSXML2.XMLHTTP.4.0',													
  													'MSXML2.XMLHTTP.3.0',													
  													'MSXML2.XMLHTTP',																																																				
  													'Microsoft.XMLHTTP' );
  		for( var i=0; i<xmlHttpVersions.length && this.objXMLHttp == null; i++ )
  			{
  			try {
  				this.objXMLHttp = new ActiveXObject( xmlHttpVersions[i] );
  			} catch(e) { }
  			}
  	}  	
  	if ( this.objXMLHttp == null )
      throw( "Cannot create XMLHttpRequest object" );          
    this.Debug( false );
    this.Timeout( 5 );
    this.Retries( 5 );  
  // Install timer to service message queue
    this.Start( 200 );    
  // Enable communications    
    this.Enable( true );
  } catch ( e ) {
    this.Error( "", e );
  }      
}
//##############################################################################
// Method:      DESTROY
// Parameters:  None
// Notes:       Call this when you are finished with the object to cleanup
//##############################################################################
AJAX_CLIENT.prototype.DESTROY = function()
{
  try{
  // Prevent client from communicating
    this.Enable( false );
  // Stop service timer
    this.Stop();
  // Remove queues  
    if ( this.objDebug )
      this.objDebug.DESTROY();
    if ( this.objMsgQueue )
      this.objMsgQueue.DESTROY();
    if ( this.objTransQueue )
      this.objTransQueue.DESTROY();
  // Remove arrays  
    if ( this.objTxLog )
      this.objTxLog.DESTROY();
    if ( this.objRxLog )
      this.objRxLog.DESTROY();
    if ( this.objHandlers )
      this.objHandlers.DESTROY();
  // Make sure that any resources use by this object are free of any reference
    this.strInstanceName    = null;
    this.strError           = null;
    this.strDebug           = null;
    this.lngErrCnt          = null;
    this.lngTxCnt           = null;
    this.lngRxCnt           = null;
    this.lngTOCnt           = null;
    this.lngTimeout         = null;
    this.lngRetries         = null;
    this.lngServiceInterval = null;
    this.lngServiceTmrID    = null;
    this.blnDebug           = null;
    this.blnBusy            = null;
    this.objDebug           = null;
    this.objXMLHttp         = null;
    this.objXMLRoot         = null;
    this.objMsgQueue        = null;
    this.objTransQueue      = null;
    this.objTxLog           = null;
    this.objRxLog           = null;
    this.objHandlers        = null;
    this.OkToSubmit         = null;

    if ( window.CollectGarbage )
      {
  // Now 'try' and get IE to take out the garbage      
      window.CollectGarbage();
      }
  } catch( e ) {
    this.Error( "DESTROY:", e );
  }
}
//##############################################################################
// Method:      Error
// Parameters:  strMsg, the error messsage
//              Exception
//##############################################################################
AJAX_CLIENT.prototype.Error = function( strMsg, Exception )
{
  this.strError = "AJAX_CLIENT:";
  
  if ( strMsg && strMsg.length )
    this.strError += strMsg;
  if ( Exception ) 
    {  
    if ( Exception.message )
      this.strError += Exception.message;
    else
       this.strError += Exception;
    }       
  this.lngErrCnt++;
}
//##############################################################################
// Method:      GetDebugElement
//##############################################################################
AJAX_CLIENT.prototype.GetDebugElement = function()
{
  try{
    if ( !top.winDebugger || top.winDebugger == undefined )
      {
  // Create a new window for the debugger    
      top.winDebugger = window.open( "/debug.html", "AJAXdebugger", 
                      "location=0, status=0,toolbar=0,width=1024,height=768" );
      }                                             
    if ( !top.winDebugger )
      return;
  // Does this window already contain the element we are interested in?        
    var el = GetElementByID( this.strDebug, top.winDebugger );
    
    if ( el )
      return el;
  // Is there a body element already in this document?
    var objBody = top.winDebugger.document.getElementsByTagName( "body" )[0];
    
    if ( objBody )
      {
  // Make sure we have our div tag for floating windows
      el = top.winDebugger.document.createElement( "div" );

      if ( el )
        {
        el.id = this.strDebug;
        objBody.appendChild( el );
        }
      }
  } catch ( e ) {
    //alert( "GetDebugElement:", e );
  }
}
//##############################################################################
// Method:      Debug
// Parameters:  blnState, true to enable, false to disable
//##############################################################################
AJAX_CLIENT.prototype.Debug = function( blnState )
{
  this.blnDebug = blnState;
}
//##############################################################################
// Method:      PurgeTransQueue
// Parameters:  none
// Notes:       Empties transaction queue
//##############################################################################
AJAX_CLIENT.prototype.PurgeTransQueue = function()
{
  try {
    // Clear out the transaction queue
    this.lngErrCnt += this.objTransQueue.Length();
    this.objTransQueue.DESTROY();
  } catch ( e ) {
    this.Error( "PurgeTransQueue:", e );
  }
}
//##############################################################################
// Method:      FindActiveMsg
// Parameters:  lngID, Message ID to find
// Returns:     object reference to found message or null if not found
//##############################################################################
AJAX_CLIENT.prototype.FindActiveMsg = function( lngID )
{
  try {
    if ( this.objTransQueue )
      return this.objTransQueue.Find( lngID );
  } catch ( e ) {
    this.Error( "FindActiveMsg:", e );
  }
  return null;
}
//##############################################################################
// Method:      RemoveMsgFromTransQueue
// Parameters:  lngID, Message ID to find
//##############################################################################
AJAX_CLIENT.prototype.RemoveMsgFromTransQueue = function( objMsg )
{
  try {
    if ( objMsg == null )
      return;
  // Remove the message from the active transactions queue    
    this.objTransQueue.Remove( objMsg.lngID );
  } catch ( e ) {
    this.Error( "RemoveMsgFromTransQueue:", e );
  }
}
//##############################################################################
// Method:      MessageSent
// Parameters:  lngID, Message ID to find
//##############################################################################
AJAX_CLIENT.prototype.MessageSent = function( lngID )
{
  try {
  // Increment TX counter
    this.lngTxCnt++;   
    var objMsg = this.FindActiveMsg( lngID );
    
    if ( objMsg )
      {
  // Insert message into log      
      var objLog = new AJAX_LOG_ENTRY();
      var DTnow = new Date();
      var strTD = DTnow.toLocaleString();
      objLog.msTime    = DTnow.getTime();
      objLog.strTime   = strTD.substring( strTD.indexOf(" ") + 1 );
      objLog.strMethod = objMsg.strMethod;
      objLog.strID     = objMsg.lngID;
      objLog.strURL    = objMsg.strURL;
      objLog.strData   = objMsg.strData.substring(0, 32);
      this.objTxLog.Push( objLog );
      }
  } catch ( e ) {
    this.Error( "MessageSent:", e );
  }
}
//##############################################################################
// Method:      Log
// Parameters:  strMsg, the message to log
//##############################################################################
AJAX_CLIENT.prototype.Log = function( strMsg )
{
  try {
      if ( this.blnDebug )
        {
        var DTnow = new Date();
        this.objDebug.Push( DTnow.toLocaleString() + " " + strMsg );
        }
  } catch ( e ) {
    this.Error( "Log:", e );
  }
}
//##############################################################################
// Method:      MessageReceived
// Parameters:  lngID, Message ID to find
//##############################################################################
AJAX_CLIENT.prototype.MessageReceived = function( lngID )
{
  try {
  // Remove the message from the active transactions queue    
    this.lngRxCnt++;
    var objMsg = this.FindActiveMsg( lngID );
    
    if ( objMsg )
      {
  // Insert message into log      
      var objLog = new AJAX_LOG_ENTRY();
      var DTnow = new Date();
      var strTD = DTnow.toLocaleString();
      objLog.msTime    = DTnow.getTime();  
      objLog.strTime   = strTD.substring( strTD.indexOf(" ") + 1 );
      objLog.strResponseTime = "";
       
  // Match Tx Log entry with received message
      for( var t=0; t<this.objTxLog.Length(); t++ )
        {
        var objTxLogEntry = this.objTxLog.Peek( t );

        if ( parseInt(objTxLogEntry.strID, 10) == parseInt(objMsg.lngID, 10) )
          {                  
          objLog.strResponseTime = objLog.msTime - objTxLogEntry.msTime;
          break;
          }
        }  
      objLog.strMethod = objMsg.strMethod;
      objLog.strID     = objMsg.lngID;
      objLog.strURL    = objMsg.strURL;
      objLog.strData   = this.strXML;   // The actual response from the server
      this.objRxLog.Push( objLog );
  // Remove this message from transaction queue      
      this.RemoveMsgFromTransQueue( objMsg );        
      }    
  } catch ( e ) {
    this.Error( "MessageReceived:", e );
  }
}
//##############################################################################
// Method:      Timedout
// Parameters:  lngID, Message ID to find
//##############################################################################
AJAX_CLIENT.prototype.Timedout = function( lngID )
{
  try {
    this.lngTOCnt++;
    var objMsg = this.FindActiveMsg( lngID );
  // Remove this message from transaction queue     
    if ( objMsg )
      this.RemoveMsgFromTransQueue( objMsg );
  // Free up connection so another transaction can commence.      
    this.blnBusy = false;
  } catch ( e ) {
    this.Error( "Timeout:", e );
  }
}
//##############################################################################
// Method:      Timeout
// Parameters:  lngTimeout, server response timeout in seconds
//##############################################################################
AJAX_CLIENT.prototype.Timeout = function( lngTimeout )
{
  this.lngTimeout = lngTimeout;
}
//##############################################################################
// Method:      Retries
// Parameters:  lngRetries, how many retry attempts at sending a message
//##############################################################################
AJAX_CLIENT.prototype.Retries = function( lngRetries )
{
  this.lngRetries = lngRetries;
}
//##############################################################################
// Method:      DuplicateMessage
// Parameters:  objMsg, Message to check for
// Returns:     true if message exists in either the pending message queue or
//              the active transaction queue.  false if not. 
//##############################################################################
AJAX_CLIENT.prototype.DuplicateMessage = function( objMsg )
{
  try {
    for( var pass=1; pass<=2; pass++ )
      {
      var Queue;
      if ( pass == 1 )
        Queue = this.objMsgQueue;
      else
        Queue = this.objTransQueue;
      if ( !Queue )
        continue;
  // Is there a message in the queue exactly the same as this ?
      for( var m=0; m<Queue.Length(); m++ )    
        {
        var objItem = Queue.Peek( m );
        
        if ( objItem == null )
          continue;
  // If we find a match abort immediately        
        if ( objItem.strMethod == objMsg.strMethod &&
             objItem.strURL    == objMsg.strURL    &&
             objItem.strData   == objMsg.strData )
          return true;
        }
      }                
  } catch ( e ) {
    this.Error( "DuplicateMessage:", e );
  }
  return false;
}
//##############################################################################
// Method:      POST
// Parameters:  strURL, Where to send the request
//              strData, The data to send 
//##############################################################################
AJAX_CLIENT.prototype.POST = function( strURL, strData )
{
  try {
    if ( this.OkToSubmit && this.OkToSubmit( this ) == false )
      return false;
    if ( this.blnEnabled != true )
      return false;
    var objMsg = new AJAX_MESSAGE( this, "POST", strURL, strData );

    if ( this.DuplicateMessage( objMsg ) == true )
      return false;        
    this.objMsgQueue.Add( objMsg, objMsg.lngID );
    return true;
  } catch ( e ) {
    this.Error( "POST:", e );
  }
  return false;
}
//##############################################################################
// Method:      GET
// Parameters:  strURL, Where to send the request
//              strData, The data to send 
//##############################################################################
AJAX_CLIENT.prototype.GET = function( strURL, strData )
{
  try {
    if ( this.OkToSubmit && this.OkToSubmit( this ) == false )
      return false;
    if ( this.blnEnabled != true )
      return false;      
    var objMsg = new AJAX_MESSAGE( this, "GET", strURL, strData );
    
    if ( this.DuplicateMessage( objMsg ) == true )
      return false;        
    this.objMsgQueue.Add( objMsg, objMsg.lngID );
    return true;
  } catch ( e ) {
    this.Error( "GET:", e );
  }
  return false;        
}
//##############################################################################
// Method:      Stop
// Parameters:  None
//##############################################################################
AJAX_CLIENT.prototype.Stop = function()
{
  try {
    if ( this.lngServiceTmrID == 0 )
      return;
    clearTimeout( this.lngServiceTmrID );
    this.lngServiceTmrID = 0;      
  } catch ( e ) {
    this.Error( "Stop:", e );
  }
}
//##############################################################################
// Method:      ResponseHandler
// Parameters:  None
// Notes:       Called on state change of the XMLHttpRequest object
//##############################################################################
AJAX_CLIENT.prototype.ResponseHandler = function()
{
  try {
    if ( this.objXMLHttp == null )
      return;
    if ( !(this.objXMLHttp.readyState == 4 && this.objXMLHttp.status == 200) )
      return;
  // We have a response form the server
    var rootNode = "";
  
		var xmlResponse = this.objXMLHttp.responseXML;
		this.strXML = this.objXMLHttp.responseText;
	// Catch potential IE and Opera errors
  	if ( xmlResponse == undefined || 
         xmlResponse == null      || 
         !xmlResponse.documentElement )
      throw( "No XML in response" );

	// Catch potential Firefox errors
		rootNode = xmlResponse.documentElement.nodeName;

  	if ( rootNode == "parsererror" )
      throw( "Invalid XML response" );
    else if ( rootNode == "fatal" )
      {
  // Special case for fatal PHP Exceptions that halt execution
      errmsg = this.FindXMLnode( "errmsg" );
      errmsg = this.GetXMLvalue( errmsg );
  // Remove all messages from transaction queue      
      this.PurgeTransQueue();        

      if ( errmsg != null )
        throw( "FATAL PHP Exception:", "<p>" + errmsg + "</p>" );
      return;
      }      
  	this.objXMLRoot = xmlResponse.documentElement;    	
  // Look for an error at the root
    var errmsg = this.FindXMLnode( "error" );
    errmsg     = this.GetXMLvalue( errmsg );

		if ( errmsg )
      throw( errmsg );
  // Where did this response come from ?
    var ajaxIP  = this.FindXMLnode( "ajax" );
    var ajaxMID = this.FindXMLnode( "ajmid" );
    ajaxIP      = this.GetXMLvalue( ajaxIP );               
    ajaxMID     = this.GetXMLvalue( ajaxMID );      
    
    if ( ajaxMID == null )
      throw( "No message ID in response!" );         
  // Find the appropriate handler for this root node
    if ( this.objHandlers == null )
      throw( "No handlers to deal with response!" );
    for( var h=0; h<this.objHandlers.Length(); h++ )
      {
      var objHandler = this.objHandlers.Peek( h );
      
      if ( objHandler == null )
        throw( "objHandlers is null" );
      if ( rootNode == objHandler.strNode )
        {
        var pCallback = objHandler.pCallback;
        
        if ( pCallback == null || pCallback == undefined )
          break;
  // Call the Handler, time execution        
        var objStartTime = new Date();
        pCallback( this );        
        var objEndTime = new Date();                                 
        objHandler.lngExeTime = Math.abs( objEndTime - objStartTime );
  // Increment calls counter        
        objHandler.lngCalls++;
  // We can now remove this message
        this.MessageReceived( ajaxMID );
        break;
        }
      }
  } catch( e ) {
    this.Error( "ResponseHandler:", e );
  }
  // We had a response of some description we should free up the resource so
  // another transaction can commence.  
  this.blnBusy = false;  
}
//##############################################################################
// Method:      Send
// Parameters:  None
// Notes:       Sends this message to the server
//##############################################################################
AJAX_CLIENT.prototype.Send = function()
{
  try {
  // If we are busy with another transaction then do not attempt to send
  // another.  
    if ( this.blnBusy == true )
      return;
  // Is there anything in the queue ?
    if ( this.objMsgQueue.Length() == 0 )
      return;
  // Pop one item from the message queue
    var objMsg = this.objMsgQueue.Pop();
    
    if ( objMsg == null )
      return;
  // Flag that we are busy a transaction, preventing other messsages from being
  // sent until this transaction has completed.      
    this.blnBusy = true;
  // Add this message to the active transaction queue
    this.objTransQueue.Add( objMsg, objMsg.lngID );                
  // Send it    
    var strAddr = objMsg.strURL;
  // If the URL doesn't have a ? then append one before appending the parameters    
    if ( strAddr.indexOf( "?" ) == -1 )
      strAddr += "?";         
    else
      strAddr += "&";     
  // Set-up response handler
    var objClient = this;      
    this.objXMLHttp.onreadystatechange = function() {
        objClient.ResponseHandler();
      }        
  // We have a connection send our message
    if ( objMsg.strMethod == "POST" )
      {      
      this.objXMLHttp.open( objMsg.strMethod, strAddr, true );    
  	  this.objXMLHttp.setRequestHeader(
         "Content-Type", "application/x-www-form-urlencoded; charset=UTF-8" );
      this.objXMLHttp.send( "ajmid=" + objMsg.lngID + "&" + objMsg.strData );
    	}
    else
      {
  // Get method
      this.objXMLHttp.open( objMsg.strMethod, 
                    strAddr + "ajmid=" + objMsg.lngID + "&" + objMsg.strData, 
                    true );
      this.objXMLHttp.send( null );
      }
    this.MessageSent( objMsg.lngID );
  // Start response timeout timer
    objMsg.StartTimeout();    
  } catch ( e ) {
    this.Error( "Send:", e );
  }
}
//##############################################################################
// Method:      FormatSize
// Parameters:  lngLength, the length in bytes
// Returns:     formatted length for display 
//##############################################################################
AJAX_CLIENT.prototype.FormatSize = function( lngLength )
{
  try {
    if ( lngLength < (1024 * 1024) )
      return sprintf("%.4f", (lngLength / 1024) ) + " Kb";
    return sprintf("%.4f", (lngLength / (1024 * 1024)) ) + " Mb";
  } catch( e ) {
    this.Error( "FormatSize:", e );
  }
}
//##############################################################################
// Method:      FormatURL
// Parameters:  strURL, the url to format
// Returns:     stripped version of URL 
//##############################################################################
AJAX_CLIENT.prototype.FormatURL = function( strURL )
{
  try {
// Remove any parameters after the file  
    var strFile = strURL.substring( 0, strURL.toLowerCase().indexOf( ".php" ) + 4 );
// Remove http://     
    strFile = strFile.replace( "http://", "" );
// Remove 127.0.0.1:8080/
    strFile = strFile.replace( "127.0.0.1:8080/", "" );    
    return strFile;
  } catch( e ) {
    this.Error( "FormatURL:", e );
  }
}//##############################################################################
// Method:      ServiceQueue
// Parameters:  None
//##############################################################################
AJAX_CLIENT.prototype.ServiceQueue = function()
{
  try {
  // Continute to service queue
    this.Start( this.lngServiceInterval );
    
    if ( window.frameElement )
      {
      var FrameEl = window.frameElement;
      var pWindow = window.parent;
  // Walk back up the tree        
      var tmpNode = FrameEl.parentNode;        
      
      while ( tmpNode )
        {
        if ( tmpNode.style         && 
             tmpNode.style.display &&
             tmpNode.style.display == "none" )
          {
  // We have found a parent element that is not visible, there is no point
  // in this AJAX object being enabled!
          this.Enable( false );
          return;                    
          }                          
        if ( tmpNode.nodeName == "BODY" )
          {
          if ( pWindow == null )
            break;
          tmpNode = pWindow.frameElement;
          pWindow = pWindow.parent;
          }
        else                                  
          tmpNode = tmpNode.parentNode;
        }
      this.Enable( true );
      }      
  // Send the next message    
    this.Send();

    if ( this.blnDebug )
      {
  // Attempt to obtain this objects DIV tag      
      var el = this.GetDebugElement();      

      if ( el )
        {
  // We have it, now put something in it!
        var DTnow = new Date(), HTML = "";        
        
        HTML += "<div class=\"dialogtitle\" " +
                     "style=\"background-Color: #000000;\">" +
                "&nbsp;";
        if ( window.frameElement )
          HTML += window.frameElement.src + "&nbsp;";                        
        HTML += "[" + this.strInstanceName + "]" +
                ", Date/Time:" + DTnow.toLocaleString() +
                ", Tx:" + this.lngTxCnt +
                ", Rx:" + this.lngRxCnt +
                ", Errs:" + this.lngErrCnt +
                ", Timeouts: " + this.lngTOCnt +
              "</div>";
        if ( this.strError.length > 0 )
          {              
          HTML += "<div style=\"background-Color: #ff0000; " +
                               "color: #ffffff; " +
                               "font-weight: bold;\">" +
                    "Last Err: " + String(this.strError).substring(0, 128) +
                  "</div>";                                       
          }                
        HTML += "<table border=\"0\" " + 
                        "cellpadding=\"0\" " + 
                        "cellspacing=\"0\" " +
                        "width=\"100%\">" +
                  "<tr>" +                
                    "<td rowspan=\"2\">";
        HTML += "<div class=\"dialogtitle\">" +
                  "XML Handlers:" +
                "</div>" +
                "<div style=\"width:100%; " +
                           "height:416px; " +
                           "border: 1px solid #000000; " +
                           "background-Color: #ffffff; " +
                           "color: #000000; " +
                           "overflow:auto;\" " +
                   "id=\"" + this.strInstanceName + "_hand\" >" +
        "<table border=\"0\" " +
               "cellspacing=\"1\" " +
               "cellpadding=\"0\" " +
               "width=\"100%\">" +
          "<thead>" +               
          "<tr>" +
            "<th class=\"dialogtitle\">NODE</th>" +
            "<th class=\"dialogtitle\">HANDLER</th>" +
            "<th class=\"dialogtitle\">CALLS</th>" +
            "<th class=\"dialogtitle\">EXE TIME</th>" +
           "</tr>" +
           "</thead>" +
           "<tbody>";
        for( var n=0; n<this.objHandlers.Length(); n++ )
          {
          var objHandler = this.objHandlers.Peek( n );
          
          if ( objHandler == null )
            continue;                                              
          HTML += "<tr>" +
                    "<td class=\"dialogtext\" align=\"center\">" + 
                      String(objHandler.strNode).substring(0, 16) +
                    "</td>" +
                    "<td class=\"dialogtext\" align=\"left\">";                                          
          strName = this.GetFunctionName( objHandler.pCallback );
            
          if ( strName )
            HTML += strName;
          HTML +=   "</td>" +
                    "<td class=\"dialogtext\" align=\"center\">" +
                      objHandler.lngCalls + 
                    "</td>" + 
                    "<td class=\"dialogtext\" align=\"center\">" +
                      objHandler.lngExeTime +
                    "</td>";
          HTML += "</tr>";                   
          }                           
        HTML +=   "</tbody>" +
                  "</table>" +
                "</div>" +                          
                  "</td><td>";
        HTML += "<div class=\"dialogtitle\">" +
                  "Waiting to be sent:" +
                "</div>" +
                "<div style=\"width:100%; " +
                           "height:200px; " +
                           "border: 1px solid #000000; " +
                           "background-Color: #ffffff; " +
                           "color: #000000; " +
                           "overflow:auto;\" " +
                   "id=\"" + this.strInstanceName + "_wts\" >" +
        "<table border=\"0\" " +
               "cellspacing=\"1\" " +
               "cellpadding=\"1\" " +
               "width=\"100%\" " +
               "bgcolor=\"#000000\">" +
          "<thead>" +                
          "<tr>" +
            "<th class=\"dialogtitle\" width=\"50\">METHOD</th>" +
            "<th class=\"dialogtitle\" width=\"30\">ID</th>" +
            "<th class=\"dialogtitle\" width=\"120\">URL</th>" +
            "<th class=\"dialogtitle\">DATA SIZE</th>" +
           "</tr>" +
           "</thead>" +
           "<tbody>";                                                                                              
        for( var m=0; m<this.objMsgQueue.Length(); m++ )                                                                                
          {
          var objMsg = this.objMsgQueue.Peek( m );
          
          if ( objMsg == null )
            continue;
          HTML += "<tr>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"center\">" +
                      objMsg.strMethod +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"right\">" +
                      objMsg.lngID +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"left\">" +
                      this.FormatURL( objMsg.strURL ) +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"right\" nowrap>" +
                      this.FormatSize( objMsg.strData.length )
                    "</td>" +
                  "</tr>";
          }
        HTML +=   "</tbody>" +
                  "</table>" +
                "</div>" +                          
                    "</td><td>";
        HTML += "<div class=\"dialogtitle\">" +
                  "Waiting for response:" +
                "</div>" +
                "<div style=\"width:100%; " +
                           "height:200px; " +
                           "border: 1px solid #000000; " +
                           "background-Color: #ffffff; " +
                           "color: #000000; " +
                           "overflow:auto;\" " +
                   "id=\"" + this.strInstanceName + "_wfr\" >" +
        "<table border=\"0\" " +
               "cellspacing=\"1\" " +
               "cellpadding=\"1\" " +
               "width=\"100%\" " +
               "bgcolor=\"#000000\">" +
          "<thead>" +                
          "<tr>" +
            "<th class=\"dialogtitle\" width=\"50\">METHOD</th>" +
            "<th class=\"dialogtitle\" width=\"30\">ID</th>" +
            "<th class=\"dialogtitle\" width=\"60\">ELAPSED</th>" +
            "<th class=\"dialogtitle\">URL</th>" +
           "</tr>" +
           "</thead>" +
           "<tbody>";              
        for( var m=0; m<this.objTransQueue.Length(); m++ )                                                                                
          {
          var objMsg = this.objTransQueue.Peek( m );
          
          if ( objMsg == null )
            continue;
          HTML += "<tr>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"center\">" +
                      objMsg.strMethod +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"center\">" +
                      objMsg.lngID +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"center\">" +
                      objMsg.lngElapsed +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"left\">" +
                      this.FormatURL( objMsg.strURL ) +
                    "</td>" +
                  "</tr>";
          }                    
        HTML +=   "</tbody>" +
                  "</table>" +
                "</div>" +                          
                    "</td>" +                
                  "</tr>" +
                  "<tr>" +                
                    "<td>";
        HTML += "<div class=\"dialogtitle\">" +
                  "Sent:" +
                "</div>" +
                "<div style=\"width:100%; " +
                             "height:200px; " +
                             "border: 1px solid #000000; " +
                             "background-Color: #ffffff; " +
                             "color: #000000; " +
                             "overflow:auto; " +
                             "float: left;\" " +
                     "id=\"" + this.strInstanceName + "_tx\" >" +
        "<table border=\"0\" " +
               "cellspacing=\"1\" " +
               "cellpadding=\"1\" " +
               "width=\"100%\" " +
               "bgcolor=\"#000000\">" +
          "<thead>" +                
          "<tr>" +
            "<th class=\"dialogtitle\" width=\"50\">TIME</th>" +
            "<th class=\"dialogtitle\" width=\"50\">METHOD</th>" +
            "<th class=\"dialogtitle\" width=\"30\">ID</th>" +
            "<th class=\"dialogtitle\" width=\"120\">URL</th>" +
            "<th class=\"dialogtitle\">DATA SIZE</th>" +
           "</tr>" +
           "</thead>" +
           "<tbody>";                                     
        for( var m=0; m<this.objTxLog.Length(); m++ )
          {
          var objLog = this.objTxLog.Peek( m );
          
          if ( objLog == null )
            continue;                               
          HTML += "<tr>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"center\" nowrap>" + 
                      objLog.strTime +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"center\">" + 
                      objLog.strMethod +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"right\">" + 
                      objLog.strID +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"left\">" + 
                      this.FormatURL( objLog.strURL ) +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"right\" nowrap>" +
                      this.FormatSize( objLog.strData.length ) + 
                    "</td>" +
                  "</tr>";                    
          }           
        HTML +=   "</tbody>" +
                  "</table>" +
                "</div>" +                          
                    "</td><td>";                
        HTML += "<div class=\"dialogtitle\">" +
                  "Received:" +
                "</div>" +
                "<div style=\"width:100%; " +
                             "height:200px; " +
                             "border: 1px solid #000000; " +
                             "background-Color: #ffffff; " +
                             "color: #000000; " +
                             "overflow:auto; " +
                             "float: right;\" " +
                     "id=\"" + this.strInstanceName + "_rx\" >" +
        "<table border=\"0\" " +
               "cellspacing=\"1\" " +
               "cellpadding=\"1\" " +
               "width=\"100%\" " +
               "bgcolor=\"#000000\">" +
          "<thead>" +               
          "<tr>" +
            "<th class=\"dialogtitle\" width=\"50\">ms</th>" +
            "<th class=\"dialogtitle\" width=\"50\">METHOD</th>" +
            "<th class=\"dialogtitle\" width=\"30\">ID</th>" +
            "<th class=\"dialogtitle\" width=\"120\">URL</th>" +
            "<th class=\"dialogtitle\">DATA SIZE</th>" +
           "</tr>" +
           "</thead>" +
           "<tbody>";
        for( var m=0; m<this.objRxLog.Length(); m++ )
          {
          var objLog = this.objRxLog.Peek( m );
          
          if ( objLog == null )
            continue;                               
          HTML += "<tr>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"center\" nowrap>" + 
                      objLog.strResponseTime +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"center\">" + 
                      objLog.strMethod +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"right\">" + 
                      objLog.strID +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"left\">" + 
                      this.FormatURL( objLog.strURL ) +
                    "</td>" +
                    "<td class=\"dialogtext\" bgcolor=\"#ffffff\" align=\"right\" nowrap>" +
                      this.FormatSize( objLog.strData.length ) +
                    "</td>" +
                  "</tr>";                    
          }           
        HTML +=   "</tbody>" +
                  "</table>" +
                "</div>" +                          
                    "</td>" +
                  "</tr>" +
                "</table>";
        HTML += "<div class=\"dialogtitle\" " +
                     "style=\"background-Color: #000000;\">" +
                "&nbsp;Log" +
              "</div>" +
              "<div style=\"width:100%; " +
                           "height:200px; " +
                           "border: 1px solid #000000; " +
                           "background-Color: #ffffff; " +
                           "color: #000000; " +
                           "overflow:auto; " +
                           "float: right;\" " +
                   "id=\"" + this.strInstanceName + "_log\" >";
        for( var m=0; m<this.objDebug.Length(); m++ )
          HTML += this.objDebug.Peek(m) + "<br />";                   
        HTML += "</div>";                                   
        el.innerHTML = HTML;                                                         
        }              
      }      
  } catch ( e ) {
  alert( e.message )
    this.Error( "ServiceQueue:", e );
  }
}
//##############################################################################
// Method:      Start
// Parameters:  lngServiceInterval, frequency to service message queue ( ms )
//##############################################################################
AJAX_CLIENT.prototype.Start = function( lngServiceInterval )
{
  try {
  // Ensure any existing timer is stopped and removed  
    this.Stop();
  // Install timer to service queue
    this.lngServiceInterval = lngServiceInterval;
    this.lngServiceTmrID = setTimeout( "new function() { " +
                                  this.strInstanceName + ".ServiceQueue();" +
                                        "}", 
                                       this.lngServiceInterval ); 
  } catch ( e ) {
    this.Error( "Start:", e );
  }
}
//##############################################################################
// Function:    FindXMLnode
// Parameters:
//  strNode, node to find
//  strRoot, Optional node to start search from
// Returns:     Object to found node or null if not found
//##############################################################################
AJAX_CLIENT.prototype.FindXMLnode = function( strNode, objRoot )
{
  var obj = null;
  
  try {
    if ( (objRoot == undefined || objRoot == null) && this.objXMLRoot != null )
      obj = this.objXMLRoot.getElementsByTagName(strNode);
    else
      obj = objRoot.getElementsByTagName(strNode);
    
    if ( obj == undefined || obj == null || obj.length == 0 )
      return null;
  } catch ( e ) {
    this.Error( "FindXMLnode:", e );
  }
  return obj;
}
//##############################################################################
// Function:    GetXMLvalue
// Parameters:
//  objNode  Use FindXMLNode() to obtain
// Returns:     The data if it exists or null if no data found
//##############################################################################
AJAX_CLIENT.prototype.GetXMLvalue = function( objNode )
{
  if ( objNode == null     || 
       objNode.length == 0 || 
       objNode.item(0).firstChild == null )
    return null;       
  return String(objNode.item(0).firstChild.data);        	
}

//##############################################################################
// Function:    GetXMLString
// Parameters:
// Returns:     The raw XML text if it exists or null if no data found
//##############################################################################
AJAX_CLIENT.prototype.GetXMLString = function()
{
  return this.strXML;
}

//##############################################################################
// Function:    GetFunctionName
// Parameters:  strCode
// Notes:       Pass this routine a function pointer and it will return
//              the name of the function
// Returns:     Function Name
//##############################################################################
AJAX_CLIENT.prototype.GetFunctionName = function( strCode )
{            
  try {
    if ( strCode != null && strCode != undefined )
      {
      var strBits = String(strCode).split("function ");
    
      if ( !strBits || strBits.length < 2 )
        return "Unnamed Handler";
      strBits = strBits[1].split("(");
      
      if ( !strBits )
        return null;
      return strBits[0];
      }
  } catch ( e ) {
    this.Error( "GetFunctionName:", e );
  }
  return "";
}
//##############################################################################
// Function:    AddHandler
// Parameters:  strNode, the XML root node name
//              pCallback, call back function to call when response is received
// Returns:     None
//##############################################################################
AJAX_CLIENT.prototype.AddHandler = function( strNode, pCallback )
{
  try {
    var objHandler = new AJAX_HANDLER( strNode, pCallback );
    
    if ( this.objHandlers.Find( strNode ) == null )    
      this.objHandlers.Add( objHandler, strNode );
  } catch ( e ) {
    this.Error( "AddHandler:", e );
  }
}
//##############################################################################
// Function:    RemoveHandler
// Parameters:  strNode, the XML root node name
// Returns:     None
//##############################################################################
AJAX_CLIENT.prototype.RemoveHandler = function( strNode )
{
  try {
    this.objHandlers.Remove( strNode );
  } catch ( e ) {
    this.Error( "RemoveHandler:", e );
  }
}
//##############################################################################
// Function:    Enable
// Parameters:  blnState, true to enable, false to disable
// Returns:     None
//##############################################################################
AJAX_CLIENT.prototype.Enable = function( blnState )
{
  try {
    this.blnEnabled = blnState;
  } catch ( e ) {
    this.Error( "Enable:", e );
  }
}
// Properties
AJAX_CLIENT.prototype.strInstanceName;
AJAX_CLIENT.prototype.strError;
AJAX_CLIENT.prototype.strDebug;
AJAX_CLIENT.prototype.strXML;
AJAX_CLIENT.prototype.lngErrCnt;
AJAX_CLIENT.prototype.lngTxCnt;
AJAX_CLIENT.prototype.lngRxCnt;
AJAX_CLIENT.prototype.lngTOCnt;
AJAX_CLIENT.prototype.lngTimeout;
AJAX_CLIENT.prototype.lngRetries;
AJAX_CLIENT.prototype.lngServiceInterval;
AJAX_CLIENT.prototype.lngServiceTmrID;
AJAX_CLIENT.prototype.blnBusy;
AJAX_CLIENT.prototype.blnDebug;
AJAX_CLIENT.prototype.blnEnabled;
AJAX_CLIENT.prototype.objXMLHttp;
AJAX_CLIENT.prototype.objXMLRoot;
AJAX_CLIENT.prototype.objMsgQueue;
AJAX_CLIENT.prototype.objTransQueue;
AJAX_CLIENT.prototype.objTxLog;
AJAX_CLIENT.prototype.objRxLog;
AJAX_CLIENT.prototype.objHandlers;
AJAX_CLIENT.prototype.OkToSubmit;

//##############################################################################
//##############################################################################
// Some useful utility functions
//##############################################################################
//##############################################################################

//##############################################################################
// Function:    EncodeData
// Parameters:  strData, data to encode
// Returns:     Encoded string
//##############################################################################
function EncodeData(strData)
{
	// The Javascript escape and unescape functions do not correspond
	// with what browsers actually do...
  var SAFECHARS = "0123456789" +					// Numeric
  				        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +	// Alphabetic
  				        "abcdefghijklmnopqrstuvwxyz" +
  				        "-_.!~*'()";					// RFC2396 Mark characters
  var HEX = "0123456789ABCDEF";
	var plaintext = strData;
	var encoded = "";

  for (var i = 0; i < plaintext.length; i++ )
    {
		var ch = plaintext.charAt(i);

    if (ch == " ")
		  encoded += "+";				// x-www-urlencoded, rather than %20
		else if (SAFECHARS.indexOf(ch) != -1)
		  encoded += ch;
		else
      {
      var charCode = ch.charCodeAt(0);

			if (charCode > 255)
        {
		    alert( "Unicode Character '"
                      + ch
                      + "' cannot be encoded using standard URL encoding.\n" +
			          "(URL encoding only supports 8-bit characters.)\n" +
    					  "A space (+) will be substituted." );
				encoded += "+";
				}
      else
        {
				encoded += "%";
				encoded += HEX.charAt((charCode >> 4) & 0xF);
				encoded += HEX.charAt(charCode & 0xF);
        }
		  }
	   }
  return encoded;
}
//##############################################################################
// Function:    Encode64
// Parameters:  strData, data to encode
// Returns:     Encoded string
//##############################################################################
var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

function Encode64(strData)
{
  var encoded = "";
  var chr1, chr2, chr3;
  var enc1, enc2, enc3, enc4;
  var i = 0;

   do {
      chr1 = strData.charCodeAt(i++);
      chr2 = strData.charCodeAt(i++);
      chr3 = strData.charCodeAt(i++);

      enc1 = chr1 >> 2;
      enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
      enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
      enc4 = chr3 & 63;

      if (isNaN(chr2)) {
         enc3 = enc4 = 64;
      } else if (isNaN(chr3)) {
         enc4 = 64;
      }

      encoded = encoded + keyStr.charAt(enc1) + keyStr.charAt(enc2) +
         keyStr.charAt(enc3) + keyStr.charAt(enc4);
   } while (i < strData.length);

   return encoded;
}
//##############################################################################
// Function:    Decode64
// Parameters:  strData, data to decode
// Returns:     Decoded string
//##############################################################################
function Decode64(strData)
{
  try{
    var output = "";
    var chr1, chr2, chr3;
    var enc1, enc2, enc3, enc4;
    var i = 0;
    
    // remove all characters that are not A-Z, a-z, 0-9, +, /, or =
    var input = strData.replace(/[^A-Za-z0-9\+\/\=]/g, "");
    
    do {
      enc1 = keyStr.indexOf(input.charAt(i++));
      enc2 = keyStr.indexOf(input.charAt(i++));
      enc3 = keyStr.indexOf(input.charAt(i++));
      enc4 = keyStr.indexOf(input.charAt(i++));
    
      chr1 = (enc1 << 2) | (enc2 >> 4);
      chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
      chr3 = ((enc3 & 3) << 6) | enc4;
    
      output = output + String.fromCharCode(chr1);
    
      if (enc3 != 64) {
         output = output + String.fromCharCode(chr2);
      }
      if (enc4 != 64) {
         output = output + String.fromCharCode(chr3);
      }
    } while (i < input.length);
  } catch(e) {
    alert( "ERROR in Decode64: " + e.message );
  }    
 return output;
}
