/*	help.js 
	this file goes with helpServer.php to provide context sensitive help
*/

//  util.js part of maasterHead, called before this script
//      contains the following functions
//      addEvent, getElementByClass, findParentWithClass, showProps, 
//      ( for ie/win ) needShim, placeShim, hideShim

//  ie also needs help making the indicator clickable
//    currently there is a question mark at far right on labels
//    since it is only called for ie, use ie dom
function addIEHotSpot(el) {
  if (( el.style.position == "" ) ||
      ( el.style.position == "static" )) {
    el.style.position = "relative";
  }
  var added = document.createElement("a");
  added.style.position = "absolute";
  added.style.top = "0px";
  added.style.left = (el.offsetWidth - 18) + "px";
  added.style.height = el.offsetHeight + "px";
  added.style.width = "18px";
  el.appendChild(added);
}

function getHelp(e) {
  var blockDefault = false;
  e = e || window.event;   // w3 || ie
  var targ  = e.currentTarget || e.srcElement;   // w3 || ie
  targ = findParentWithClass(targ, "Help");
  HelpViewer.clearData();
  HelpViewer.getMouseXY(e);
  HelpViewer.checkClassOverride(targ);
  if ( HelpViewer.field == "" )  HelpViewer.checkFormElement(targ);
  if ( HelpViewer.field == "" )  HelpViewer.checkLabelElement(targ);
  if ( HelpViewer.field == "" )  blockDefault = HelpViewer.checkAnchorElement(targ);
  if (HelpViewer.field > "") {
    var obj = new help(SomeHandler);
    obj.gethelp(HelpViewer.form, HelpViewer.field);
    if ( blockDefault ) {
      return HelpViewer.blockDefault(e);
    }
  }
}

var HelpViewer = {
  form: "",
  field: "",
  help: "",
  sample: "",
  mouseXY: new Array(0, 0),
  
  init: function() {
    var aBodies = document.getElementsByTagName("body");
    if (aBodies.length > 0) {
      this.helpBalloon = document.getElementById("helpContent");
      if ( !this.helpBalloon ) {
	var el = document.createElement("div");
	el.id = "helpContent";
	aBodies[0].appendChild(el);
	el.innerHTML = "<div id=\"helpText\"></div><div id=\"helpBottom\"></div>";
	this.helpBalloon = el;
	this.needShim = needShim();
      }
      
      var aHelps = getElementsByClass(document, "Help");
      for (var i = 0; i < aHelps.length; i++) {
	addEvent(aHelps[i], "click", getHelp, false);
	if ( this.needShim == true ) {
	  addIEHotSpot(aHelps[i]);
	}
      }
    } else {
      setTimeout(HelpViewer.init, 100);
    }
  },
  
  doHelp: function(tHelpData) {
    var aData = tHelpData.split("|");
    this.form = aData[0];
    this.field = aData[1];
    this.help = aData[2];
    this.sample = aData[3];	
    addEvent(this.helpBalloon, "click", function() {HelpViewer.close();}, false);
    this.close(); // hide until done with open process
    this.open();
  },
  
  close: function() {
    this.helpBalloon.className = "hide";
    if ( this.needShim ) hideShim();
  },
  
  open: function() {
    var tShow = "<p>" + this.help + "</p>";
    if ( this.sample > "" ) 
    tShow += "<p class=\"sample\">Sample: " + this.sample + "</p>";
    var el = document.getElementById("helpText");
    if ( el ) {
      el.innerHTML = tShow;
    } else {
      alert("Couldn't get helpText");
    }
    this.helpBalloon.style.left = (this.mouseXY[0] - 20) + "px";
    this.helpBalloon.style.top = (this.mouseXY[1] - (this.helpBalloon.offsetHeight - 10)) + "px";
    this.helpBalloon.className = "show";
    if ( this.needShim ) placeShim(this.helpBalloon.style.top,
				   this.helpBalloon.style.left, 150,  100);
  },
  
  getMouseXY: function(e) {
    var scrollTop = 0;
    var scrollLeft = 0;
    // first if: docElement is for std mode, body is for quirks
    if (document.documentElement && document.documentElement.scrollTop) {
      scrollTop = document.documentElement.scrollTop;
      scrollLeft = document.documentElement.scrollLeft;
    } else {
      scrollTop = document.body.scrollTop;
      scrollLeft = document.body.scrollLeft;
    }
    if (e.clientX) {
      // in Safari, clientY acts like pageY, it includes the scroll
      if ( e.clientY > window.innerHeight ) {
	scrollTop = 0;
	scrollLeft = 0;
      }
      this.mouseXY[0] = e.clientX + scrollLeft;
      this.mouseXY[1] = e.clientY + scrollTop;
    } else {
      this.mouseXY[0] = e.pageX;
      this.mouseXY[1] = e.pageY;
    }
  },
  
  clearData: function() {
    this.form = "";
    this.field = "";
    this.help = "";
    this.sample = "";
  },
  
  checkClassOverride: function(targ) {
    var aClasses = targ.className.split(" ");
    for (var i = 0; i < aClasses.length; i++) {
      if (aClasses[i].substr(0, 5) == "help-") {
	var aHelpData = aClasses[i].split("-");
	if (aHelpData.length > 2) {
	  this.form = aHelpData[1];
	  this.field = aHelpData[2];
	} else {
	  this.field = aHelpData[1];
	}
	i = aClasses.length;
      }
    }
  },
  
  checkFormElement: function(targ) {
    if ( targ.form ) {
      this.form = targ.form.name;
      this.field = targ.name;   // form el has name, label doesn't
      if (( typeof this.field == "undefined" ) && ( targ.tagName.toLowerCase() == "label")) {
	this.field = this.getLabelFor(targ);
      }
    }
  },
  
  getLabelFor: function(targ) {
    var fieldName = targ.getAttribute('for');   // w3c way
    if ( typeof fieldName != "string" ) 
    fieldName = targ.attributes['for'].nodeValue;   // ie/win
    if ( typeof(fieldName) == "undefined" ) 
    fieldName = "";
    return fieldName;
  },
  
  checkLabelElement: function(targ) {
    if ( targ.tagName.toLowerCase() == "label" ) {
      this.field = this.getLabelFor(targ);
      if ( this.field > "" ) {
	if ( document.getElementsByName ) {
	  var aNames = document.getElementsByName(this.field);
	  for ( var i = 0; i < aNames.length; i++ ) {
	    if ( aNames[i].form ) this.form = aNames[i].form.name;
	  }
	} else {
	  alert("document.getElementsByName not supported");
	}
      }
    }
  },
  
  checkAnchorElement: function(targ) {
    var blockDefault = false;
    if ( targ.tagName.toLowerCase() == "a" ) {
      var aParts = targ.href.split("?");
      if ( aParts.length > 1 ) {
	var aParams = aParts[1].split("&amp;");
	if (aParams.length == 1) aParams = aParts[1].split("&");
	for ( i = 0; i < aParams.length; i++) {
	  aParts = aParams[i].split("=");
	  if (aParts[0].toLowerCase() == "form") this.form = aParts[1];
	  if (aParts[0].toLowerCase() == "field") this.field = aParts[1];
	}
	blockDefault = true;
      }
    }
    return blockDefault;
  },
  
  blockDefault: function(e) {
    if (e.preventDefault)
    e.preventDefault();	//W3C
    e.returnValue = false;	//IE
    if (e.stopPropagation)	//stop event bubbling
    e.stopPropagation();	//W3C
    e.cancelBubble = true;	//IE
    return e.returnValue;	//for traditional event model
  }
  
}

var SomeHandler = {
  gethelp: function(result) {
    HelpViewer.doHelp(result);
  }
}

/*	 init  */
addEvent(window, "load", function() {HelpViewer.init()}, false);


// --------  pasted in from call to Tools/helpServer.php?client  ----------
// --------   if you change the php help class this must be regenerated ---
 
// $Id: help.js,v 1.11 2006/10/03 18:18:10 rer Exp $
// Notes:
// - Watch out for recursive references - call inside a try/catch block if uncertain
// - Objects are serialized to PHP class name JPSpan_Object by default
// - Errors are serialized to PHP class name JPSpan_Error by default
//
// See discussion below for notes on Javascript reflection
// http://www.webreference.com/dhtml/column68/
function JPSpan_Serialize(Encoder) {
    this.Encoder = Encoder;
    this.typeMap = new Object();
};

JPSpan_Serialize.prototype = {
  
  typeMap: null,
  
  addType: function(cname, callback) {
    this.typeMap[cname] = callback;
  },
  
  serialize: function(v) {
    
    switch(typeof v) {
      //-------------------------------------------------------------------
            case 'object':
            
                // It's a null value
	    if ( v === null ) {
                    return this.Encoder.encodeNull();
	    }
	    
	    // Get the constructor
	    var c = v.constructor;
	    
	    if (c != null ) {
	      
	      // It's an array
	      if ( c == Array ) {
		return this.Encoder.encodeArray(v,this);
	      } else {
		
		// Get the class name
		var match = c.toString().match( /\s*function (.*)\(/ );
		
		if ( match == null ) {
		  return this.Encoder.encodeObject(v,this,'JPSpan_Object');
		}
		
		// Strip space for IE
		var cname = match[1].replace(/\s/,'');
		
		// Has the user registers a callback for serializing this class?
		if ( this.typeMap[cname] ) {
		  return this.typeMap[cname](v, this, cname);
		  
		} else {
		  // Check for error objects
		  var match = cname.match(/Error/);
		  
		  if ( match == null ) {
		    return this.Encoder.encodeObject(v,this,'JPSpan_Object');
		  } else {
		    return this.Encoder.encodeError(v,this,'JPSpan_Error');
		  }
		  
		}
	      }
	    } else {
	      // Return null if constructor is null
	      return this.Encoder.encodeNull();
	    }
            break;
            
            //-------------------------------------------------------------------
            case 'string':
	    return this.Encoder.encodeString(v);
            break;
            
            //-------------------------------------------------------------------
            case 'number':
	    if (Math.round(v) == v) {
	      return this.Encoder.encodeInteger(v);
	    } else {
	      return this.Encoder.encodeDouble(v);
	    };
            break;
            
            //-------------------------------------------------------------------
            case 'boolean':
	    if (v == true) {
	      return this.Encoder.encodeTrue();
	    } else {
	      return this.Encoder.encodeFalse();
	    };
            break;
            
            //-------------------------------------------------------------------
            default:
	    return this.Encoder.encodeNull();
            break;
    }
    return this.Encoder.encodeNull();
  }
}

// $Id: help.js,v 1.11 2006/10/03 18:18:10 rer Exp $
// See: http://jpspan.sourceforge.net/wiki/doku.php?id=encoding
function JPSpan_Encode_Xml() {
  this.Serialize = new JPSpan_Serialize(this);
};

JPSpan_Encode_Xml.prototype = {
  
  // Used by rawpost request objects
  contentType: 'text/xml; charset=UTF-8',
  
  encode: function(data) {
    return '<?xml version="1.0" encoding="UTF-8"?><r>'+this.Serialize.serialize(data)+'</r>';
  },
  
  encodeInteger: function(v) {
    return '<i v="'+v+'"/>';
  },
  
  encodeDouble: function(v) {
    return '<d v="'+v+'"/>';
  },
  
  // Need UFT-8 encoding?
  encodeString: function(v) {
    return '<s>'+v.replace(/&/g, '&amp;').replace(/</g, '&lt;')+'</s>';
  },
  
  encodeNull: function() {
    return '<n/>';
  },
  
  encodeTrue: function() {
    return '<b v="1"/>';
  },
  
  encodeFalse: function() {
    return '<b v="0"/>';
  },
  
  // Arrays being with indexed values - properties added second
  encodeArray: function(v, Serializer) {
    var indexed = new Array();
    var a = '';
    for (var i=0; i<v.length; i++) {
      indexed[i] = true;
      a += '<e k="'+i+'">'+Serializer.serialize(v[i])+'</e>';
    };
    
    for ( var prop in v ) {
      if ( indexed[prop] ) {
	continue;
      };
      // Assumes prop obeys Javascript naming rules
      a += '<e k="'+prop+'">'+Serializer.serialize(v[prop])+'</e>';
    };
    return '<a>'+a+'</a>';
  },
  
  encodeObject: function(v, Serializer, cname) {
    var o='';
    for (var prop in v) {
      o += '<e k="'+prop+'">'+Serializer.serialize(v[prop])+'</e>';
    };
    return '<o c="'+cname.toLowerCase()+'">'+o+'</o>';
  },
  
  encodeError: function(v, Serializer, cname) {
    var e = new Object();
    if ( !v.name ) {
      e.name = cname;
      e.message = v.description;
    } else {
      e.name = v.name;
      e.message = v.message;
    };
    return this.encodeObject(e,Serializer,cname);
  }
}
//---------------------------------------------------------------------------------
// Based loosely on nsXmlRpcClient: http://mozblog.mozdev.org/nsXmlRpcClient.js
// @version $Id: help.js,v 1.11 2006/10/03 18:18:10 rer Exp $
//---------------------------------------------------------------------------------

// Decorates a normal JS exception for client side errors
// @param Error
// @param string error code
function JPSpan_Client_Error(e, code) {
  e.name = 'Client_Error';
  e.code = code;
  return e;
};

//---------------------------------------------------------------------------------

function JPSpan_HttpClient() {};
JPSpan_HttpClient.prototype = {
  xmlhttp: null,
  userhandler: null,
  timeout_id: null,
  
  // @throws Error code 1000
  init: function() {
    try {
      // Mozilla / Safari
      this.xmlhttp = new XMLHttpRequest();
    } catch (e) {
      // IE
      var MSXML_XMLHTTP_PROGIDS = new Array(
					    'MSXML2.XMLHTTP.5.0',
					    'MSXML2.XMLHTTP.4.0',
					    'MSXML2.XMLHTTP.3.0',
					    'MSXML2.XMLHTTP',
					    'Microsoft.XMLHTTP'
					    );
      var success = false;
      for (var i=0;i < MSXML_XMLHTTP_PROGIDS.length && !success; i++) {
	try {
	  this.xmlhttp = new ActiveXObject(MSXML_XMLHTTP_PROGIDS[i]);
	  success = true;
	} catch (e) {}
      }
      if ( !success ) {
	throw JPSpan_Client_Error(
				  new Error('Unable to create XMLHttpRequest.'),
				  1000
				  );
      }
    }
  },
  
  // Place an synchronous call (results returned directly)
  // @param object request object for params and HTTP method
  // @return string response text
  // @throws Error codes 1001 and 1002
  call: function (request) {
    
    if ( !this.xmlhttp ) {
      this.init();
    }
    
    if (this.callInProgress()) {
      throw JPSpan_Client_Error(
				new Error('Call in progress'),
				1001
				);
    };
    
    
    request.type = 'sync';
    request.prepare(this.xmlhttp);
    this.xmlhttp.setRequestHeader('Accept-Charset','UTF-8');
    request.send();
    
    if ( this.xmlhttp.status == 200 ) {
      return this.xmlhttp.responseText;
    } else {
      var errorMsg = '['+this.xmlhttp.status
      +'] '+this.xmlhttp.statusText;
      var err = new Error(errorMsg);
      err.headers = this.xmlhttp.getAllResponseHeaders();
      throw JPSpan_Client_Error(err,1002);
    }
  },
  
  // Place an asynchronous call (results sent to handler)
  // @param object request object for params and HTTP method
  // @param object handler: user defined object to be called
  // @throws Error code 1001
  asyncCall: function (request,handler) {
    
    var callName = null;
    if ( arguments[2] ) {
      callName = arguments[2];
    }
    
    if ( !this.xmlhttp ) {
      this.init();
    }
    
    if (this.callInProgress()) {
      throw JPSpan_Client_Error(
				new Error('Call in progress'),
				1001
				);
    };
    
    this.userhandler = handler;
    
    if ( this.userhandler.onInit ) {
      try {
	this.userhandler.onInit(callName);
      } catch(e) {
	this.displayHandlerError(e);
      }
    }
    
    request.type = 'async';
    request.prepare(this.xmlhttp);
    this.xmlhttp.setRequestHeader('Accept-Charset','UTF-8');
    
    var self = this;
    
    this.timeout_id = window.setTimeout(function() {
      self.abort(self, callName);
    },request.timeout);
    
    
    this.xmlhttp.onreadystatechange = function() {
      self.stateChangeCallback(self, callName);
    }
    
    request.send();
  },
  
  
  // Checks to see if XmlHttpRequest is busy
  // @return boolean TRUE if busy
  callInProgress: function() {
    
    switch ( this.xmlhttp.readyState ) {
      case 1:
      case 2:
      case 3:
      return true;
      break;
      default:
      return false;
      break;
    }
    return false;
  },
  
  // Callback for timeouts: aborts the request
  // @access private
  abort: function (client, callName) {
    
    if ( client.callInProgress() ) {
      
      client.xmlhttp.abort();
      var errorMsg = 'Operation timed out';
      
      if ( callName ) {
	errorMsg += ': '+callName;
      }
      
      if ( client.userhandler.onError ) {
	var ex = JPSpan_Client_Error(new Error(errorMsg), 1003);
	try {
	  client.userhandler.onError(ex, callName);
	} catch (e) {
	  client.displayHandlerError(e);
	}
      }
      
    }
  },
  
  // Called from stateChangeCallback if an error occurs in
  // in handler object
  // @access private
  displayHandlerError: function(e) {
    var errorMsg = "Error in Handler\n";
    if ( e.name ) {
      errorMsg += 'Name: '+e.name+"\n";
    };
    if ( e.message ) {
      errorMsg += 'Message: '+e.message+"\n";
    } else if ( e.description ) {
      errorMsg += 'Description: '+e.description+"\n";
    };
    if ( e.fileName ) {
      errorMsg += 'File: '+e.fileName+"\n";
    };
    if ( e.lineNumber ) {
      errorMsg += 'Line: '+e.lineNumber+"\n";
    };
    alert(errorMsg);
  },
  
  // Callback for asyncCalls
  // @access private
  stateChangeCallback: function(client, callName) {
    
    switch (client.xmlhttp.readyState) {
      
      // XMLHTTPRequest.open() has just been called
      case 1:
      if ( client.userhandler.onOpen ) {
	try {
	  client.userhandler.onOpen(callName);
	} catch(e) {
	  client.displayHandlerError(e);
	}
      }
      break;
      
      // XMLHTTPRequest.send() has just been called
      case 2:
      if ( client.userhandler.onSend ) {
	try {
	  client.userhandler.onSend(callName);
	} catch(e) {
	  client.displayHandlerError(e);
	}
      }
      break;
      
      // Fetching response from server in progress
      case 3:
      if ( client.userhandler.onProgress ) {
	try {
	  client.userhandler.onProgress(callName);
	} catch(e) {
	  client.displayHandlerError(e);
	}
      }
      break;
      
      // Download complete
      case 4:
      
      window.clearTimeout(client.timeout_id);
      
      try {
	switch ( client.xmlhttp.status ) {
	case 200:
	  if ( client.userhandler.onLoad ) {
	    try {
	      client.userhandler.onLoad(client.xmlhttp.responseText, callName);
	    } catch (e) {
	      client.displayHandlerError(e);
	    }
	  }
	  break;
	  
	  // Special case for IE on aborted requests
	case 0:
	  // Do nothing
	  break;
	  
	default:
	  if ( client.userhandler.onError ) {
	    try {
	      var errorMsg = '['+client.xmlhttp.status
		+'] '+client.xmlhttp.statusText;
	      var err = new Error(errorMsg);
	      err.headers = this.xmlhttp.getAllResponseHeaders();
	      client.userhandler.onError(JPSpan_Client_Error(err,1002), callName);
	    } catch(e) {
	      client.displayHandlerError(e);
	    }
	  }
	  break;
	}
	
      } catch (e) {
	// client.xmlhttp.status not available - failed requests
      }
      break;
    }
  }
}

// $Id: help.js,v 1.11 2006/10/03 18:18:10 rer Exp $
// Base request "class"
function JPSpan_Request(encoder) {
  this.encoder = encoder;
}
JPSpan_Request.prototype = {
  
  // Instance of an encoder
  encoder: null,
  
  // The URL of the server
  serverurl: '',
  
  // The actual URL the request is sent to (may be modified for GET requests)
  requesturl: '',
  
  // Body of request (for HTTP POST only)
  body: '',
  
  // Remote method arguments list
  args: null,
  
  // Type of request (async / sync)
  type: null,
  
  // Instance of XMLHttpRequest
  http: null,
  
  // Timeout in milliseconds for requests
  timeout: 20000,
  
  // Add an argument for the remote method
  // @param string argument name
  // @param mixed value
  // @return void
  // @throws Error code 1004
  addArg: function(name, value) {
    if ( !this.args ) {
      this.args = [];
    }
    var illegal = /[\W_]/;
    if (!illegal.test(name) ) {
      this.args[name] = value;
    } else {
      throw JPSpan_Client_Error(
				new Error('Invalid parameter name ('+name+')'),
				1004
				);
    }
  },
  
  // Reset the request object
  // @return void
  // @access public
  reset: function() {
    this.serverurl = '';
    this.requesturl = '';
    this.body = '';
    this.args = null;
    this.type = null;
    this.http = null;
    this.timeout = 20000;
  },
  
  // Used internally by request objects to build the request payload
  // @protected
  // @abstract
  build: function() {},
  
  // Used by JPSpan_HTTPClient to prepare the XMLHttpRequest object
  // @param XMLHttpRequest
  // @return void
  // @protected
  // @abstract
  prepare: function(http) {},
  
  // Used by JPSpan_HTTPClient to call send on the XMLHttpRequest object
  // @return void
  // @protected
  // @abstract
  send: function(http) {}
};

// @version $Id: help.js,v 1.11 2006/10/03 18:18:10 rer Exp $

// For building raw (not urlencoded) HTTP POST requests
function JPSpan_Request_RawPost(encoder) {
  
  var oParent = new JPSpan_Request(encoder);
  
  // Builds the post body
  // @protected
  // @throws Error code 1006
  oParent.build = function() {
    try {
      this.body = this.encoder.encode(this.args);
    } catch (e) {
      throw JPSpan_Client_Error(e, 1006);
    };
    this.requesturl = this.serverurl;
  };
  
  // Called from JPSpan_HttpClient to prepare the XMLHttpRequest object
  // @param XMLHttpRequest
  // @protected
  // @throws Error codes 1005, 1006 and 1007
  oParent.prepare = function(http) {
    this.http = http;
    this.build();
    switch ( this.type ) {
    case 'async':
      try {
	this.http.open('POST',this.requesturl,true);
      } catch (e) {
	throw JPSpan_Client_Error(new Error(e),1007);
      };
      break;
    case 'sync':
      try {
	this.http.open('POST',this.requesturl,false);
      } catch (e) {
	throw JPSpan_Client_Error(new Error(e),1007);
      };
      break;
    default:
      throw JPSpan_Client_Error(
				new Error('Call type invalid '+this.type),
				1005
				);
      break;
    };
    this.http.setRequestHeader('Content-Length', this.body.length);
    this.http.setRequestHeader('Content-Type',this.encoder.contentType);
  };
  
  // Send the request
  // @protected
  oParent.send = function() {
    this.http.send(this.body);
  };
  
  return oParent;
};
// $Id: help.js,v 1.11 2006/10/03 18:18:10 rer Exp $
// Base class for generated classes
function JPSpan_RemoteObject() {}

JPSpan_RemoteObject.prototype = {
  
  // Switch to asyncronous mode
  // @param Object user defined handler to call
  // @access public
  Async: function(userHandler) {
    this.__initResponseHandler(this,userHandler);
    this.__callState = "async";
  },
  
  // Switch to syncronous mode. Be warned: timeouts not supported!
  // @access public
  Sync: function() {
    this.__responseHandler = null;
    this.__callState = "sync";
  },
  
  // Returns the instance of XMLHttpRequest being used by
  // JPSpan_HttpClient. Allows you to bypass the APIs and
  // access it directly, for things like setting / getting HTTP 
  // headers - calling open() or send() not recommended
  // @return XMLHttpRequest
  // @access public
  GetXMLHttp: function() {
    if ( !this.__client ) {
      this.__initClient();
    }
    return this.__client.xmlhttp;
  },
  
  // Called when a error occurs in making the request
  // on the client-side. Typically these be transport errors
  // e.g. server HTTP status code != 200
  // Replace with your own function as required
  // @access public
  clientErrorFunc: function(e) {
    
    try {
      var errorMsg = '['+e.name+'] '+e.message;
    } catch (ex) {
      var errorMsg = '[Client_Error] '+e;
    }
    
    if ( e.client && e.call ) {
      errorMsg = errorMsg + ' while calling '+e.client+'.'+e.call+'()';
    }
    
    alert(errorMsg);
    
  },
  
  // Timeout for async requests in milliseconds
  // @access public
  timeout: 20000,
  
  // Called when a error in handling the response from
  // the server (e.g. the response was junk or some PHP
  // error occurred)
  // Replace with your own function as required
  // @access public
  
  serverErrorFunc: function(e) {
    
    try {
      var errorMsg = '['+e.name+'] '+e.message;
    } catch (ex) {
      var errorMsg = '[Server_Error] '+e;
    }
    
    if ( e.client && e.call ) {
      errorMsg = errorMsg + ' while calling '+e.client+ '.'+e.call+'()';
    }
    
    alert(errorMsg);
    
  },
  
  // Called when the application running on the server
  // returns an error (e.g. a divide by zero error).
  // When making async calls, local error methods
  // will be called first (if they exist)
  // Replace with your own function as required
  // @access public
  applicationErrorFunc: function(e) {
    
    try {
      var errorMsg = '['+e.name+'] '+e.message;
    } catch (ex) {
      var errorMsg = '[Application_Error] '+e;
    }
    
    if ( e.client && e.call ) {
      errorMsg = errorMsg + ' while calling '+e.client+ '.'+e.call+'()';
    }
    
    alert(errorMsg);
    
  },
  
  // Private stuff from here...
  // @var string Url to server handler
  // @access private
  __serverurl: null,
  
  // @var JPSpan_Request subclass object
  // @access private
  __request: null,
  
  // @var JPSpan_HttpClient
  // @access private
  __client: null,
  
  // @var Object handlers responses to async calls
  // @access private
  __responseHandler: null,
  
  // @var string type of calls to make: sync or async
  // @access private
  __callState: 'sync',
  
  // @var string Name of the remote class for error messages
  // @acess private
  __remoteClass: '',
  
  // Initialize the XmlHttpClient
  // @access private
  __initClient: function() {
    this.__client = new JPSpan_HttpClient();
  },
  
  // Sets up the response handler
  // @access private
  __initResponseHandler: function(self,userHandler) {
    
    self.__responseHandler = new Object();
    
    self.__responseHandler.context = self;
    
    self.__responseHandler.userHandler = userHandler;
    
    self.__responseHandler.onInit = function(callName) {
      var initFunc = callName+'Init';
      if ( this.userHandler[initFunc] ) {
	try {
	  this.userHandler[initFunc]();
	} catch(e) {
	  self.__client.displayHandlerError(e);
	}
      }
    },
    
    self.__responseHandler.onOpen = function(callName) {
      var openFunc = callName+'Open';
      if ( this.userHandler[openFunc] ) {
	try {
	  this.userHandler[openFunc]();
	} catch(e) {
	  self.__client.displayHandlerError(e);
	}
      }
    },
    
    self.__responseHandler.onSend = function(callName) {
      var sendFunc = callName+'Send';
      if ( this.userHandler[sendFunc] ) {
	try {
	  this.userHandler[sendFunc]();
	} catch(e) {
	  self.__client.displayHandlerError(e);
	}
      }
    },
    
    self.__responseHandler.onProgress = function(callName) {
      var progressFunc = callName+'Progress';
      if ( this.userHandler[progressFunc] ) {
	try {
	  this.userHandler[progressFunc]();
	} catch(e) {
	  self.__client.displayHandlerError(e);
	}
      }
    },
        
    self.__responseHandler.onLoad = function(response, callName) {
      
      try {
	dataFunc = eval(response);
	
	try {
	  data = dataFunc();
	  
	  if ( this.userHandler[callName] ) {
	    try {
	      this.userHandler[callName](data);
	    } catch(e) {
	      // Error in handler method (e.g. syntax error) - display it
	      self.__client.displayHandlerError(e);
	    }
	  } else {
	    alert('Your handler must define a method '+callName);
	  }
	  
	} catch (e) {
	  
	  e.client = self.__responseHandler.context.__remoteClass;
	  e.call = callName;
	  
	  if ( e.name == 'Server_Error' ) {
	    this.context.serverErrorFunc(e);
	  } else {
	    
	    var errorFunc = callName+'Error';
	    
	    if ( this.userHandler[errorFunc] ) {
	      try {
		this.userHandler[errorFunc](e);
	      } catch(e) {
		// Error in handler method (e.g. syntax error) - display it
		self.__client.displayHandlerError(e);
	      }
	    } else {
	      this.context.applicationErrorFunc(e);
	    }
	    
	  }
	  
	}
	
      } catch (e) {
	
	e.name = 'Server_Error';
	e.code = 2006;
	e.response = response;
	e.client = self.__responseHandler.context.__remoteClass;
	e.call = callName;
	this.context.serverErrorFunc(e);
	
      }
      
    };
    
    self.__responseHandler.onError = function(e, callName) {
      e.client = self.__responseHandler.context.__remoteClass;
      e.call = callName;
      this.context.clientErrorFunc(e);
    };
    
  },
  
  // Call remote procedure (passes onto __asyncCall or __syncCall)
  // @access private
  __call: function(url,args,callName) {
    
    if ( !this.__client ) {
      this.__initClient();
    }
    
    
    this.__request.reset();
    this.__request.serverurl = url;
    this.__request.timeout = this.timeout;
    
    for(var i=0; i < args.length; i++) {
      this.__request.addArg(i,args[i]);
    };
    
    if ( this.__callState == "async" ) {
      return this.__asyncCall(this.__request,callName);
    } else {
      return this.__syncCall(this.__request);
    }
    
  },
  
  // Call remote procedure asynchronously
  // @access private
  __asyncCall: function(request, callName) {
    
    try {
      this.__client.asyncCall(request,this.__responseHandler,callName);
    } catch (e) {
      this.clientErrorFunc(e);
    }
    
    return;
    
  },
  
  // Call remote procedure synchronously
  // @access private
  __syncCall: function(request) {
    
    try {
      var response = this.__client.call(request);
      
      try {
	var dataFunc = eval(response);
	
	try {
	  return dataFunc();
	} catch (e) {
	  
	  if ( e.name == 'Server_Error' ) {
	    this.serverErrorFunc(e);
	  } else {
	    this.applicationErrorFunc(e);
	  }
	  
	}
	
      } catch (e) {
	e.name = 'Server_Error';
	e.code = 2006;
	e.response = response;
	this.serverErrorFunc(e);
      }
      
    } catch(e) {
      this.clientErrorFunc(e);
    }
    return null;
  }
  
};


function help() {
  
  var oParent = new JPSpan_RemoteObject();
  
  if ( arguments[0] ) {
    oParent.Async(arguments[0]);
  }
  
  oParent.__serverurl = '/Tools/helpServer.php?help';
  
  oParent.__remoteClass = 'help';
  
  oParent.__request = new JPSpan_Request_RawPost(new JPSpan_Encode_Xml());
  
  // @access public
  oParent.gethelp = function() {
    var url = this.__serverurl+'/gethelp/';
    return this.__call(url,arguments,'gethelp');
  };
  
  return oParent;
}
