/**
 * This package provides RPC facilities using two popular techniques.  The first
 * is by using a hidden iframe and altering its src attribute.  This technique
 * is handy for its simplicity in this package.  The second and more powerful
 * technique uses the XMLHttpRequest object, and provides a complete object-
 * oriented API for performing complex RPC requests and building RPC-based
 * web applications.
 *
 * Usage:
 *
 * iframe-based:
 *
 * <script language="javascript" type="text/javascript" src="/js/rpc.js"> </script>
 * <script language="javascript" type="text/javascript">
 *
 * // define your rpc handler function (to be called by your
 * // server-side rpc action
 * rpc_handler = new Function ("// handle the server-side response here");
 *
 * </script>
 *
 * <!-- create the invisible iframe element to make calls for us -->
 * <iframe id="rpc-caller" style="border: 0px none; width: 0px; height: 0px">
 * </iframe>
 *
 * <!-- make the rpc call as a user event -->
 * <a
 *     href="#"
 *     onclick="return rpc_call ('/index/myapp-rpc-action?someVal=value')"
 * >click me!</a>
 *
 * In your server-side rpc action, your output should be as follows:
 *
 * <html>
 *   <head>
 *     <meta http-equiv="pragma" content="no-cache" />
 *   </head>
 *   <body onload="window.parent.rpc_handler ('return value 1', 'return value 2', 'etc.')">
 *     ...
 *   </body>
 * </html>
 *
 * XMLHttpRequest-based:
 *
 * <script language="javascript" type="text/javascript" src="/js/rpc.js"> </script>
 * <script language="javascript" type="text/javascript">
 *
 * var rpc = new rpc ();
 *
 * var myapp = {
 *   url: '/index/myapp-rpc-action',
 *
 *   hello: function (name) {
 *     rpc.call (
 *       this.url + '?method=hello&name=' + name,
 *       function (request) {
 *         // response handling logic goes here
 *         alert (eval (request.responseText));
 *       }
 *     );
 *
 *     return false;
 * }
 *
 * </script>
 *
 * <a href="#" onclick="return myapp.hello ('joe')">hey joe</a>
 *
 * See also: saf.Misc.RPC
 *
 */

// override this function by defining your own!!!
var rpc_handler = new Function ("return false;");

function rpc_call (src) {
	document.getElementById ('rpc-caller').src = src;
	return false;
}

function rpc_decode (txt) {
	entities = ['&quot;', '&gt;', '&lt;', '\\n', '\\r'];
	proper = ['"', '>', '<', "\n", "\r"];
	for (i = 0; i < entities.length; i++) {
		txt = txt.replace (entities[i], proper[i]);
	}
	return txt;
}

/**
 * Stores a copy of the most recently created rpc object.  Required by the
 * process() method.  For this reason, it is advisable to create a single
 * global rpc object for your entire web application.
 */
var rpc_global;

/**
 * This function creates a new rpc object, which provides an abstraction
 * over the JavaScript XMLHttpRequest object.
 */
function rpc () {
	this.agent = navigator.userAgent.toLowerCase ();
	this.msie = ((this.agent.indexOf ('msie') != -1) && (this.agent.indexOf ('opera') == -1));
	if (arguments.length > 1) {
		this.setHandler (arguments[0]);
		this.setErrorHandler (arguments[1]);
	} else if (arguments.length == 1) {
		this.setHandler (arguments[0]);
		this.setErrorHandler (this._error);
	} else {
		this.setHandler (this._handler);
		this.setErrorHandler (this._error);
	}
	this.init ();
}

/**
 * This method is separate from the constructor so that the process() method
 * may reset the request header list.
 */
rpc.prototype.init = function () {
	if (this.msie) {
		this.request = new ActiveXObject ('Microsoft.XMLHTTP');
	} else {
		this.request = new XMLHttpRequest ();
	}
	this.request.onreadystatechange = this.process;
	rpc_global = this;
}

/**
 * A default handler for successful requests.
 */
rpc.prototype._handler = function (request) {
	return false;
}

/**
 * A default handler for unsuccessful requests.
 */
rpc.prototype._error = function (request) {
	alert ('Error: ' + request.statusText);
}

/**
 * Aborts the current request.
 */
rpc.prototype.abort = function () {
	return this.request.abort ();
}

/**
 * Returns the complete set of headers (labels and values) as a string.
 */
rpc.prototype.headers = function () {
	return this.request.getAllResponseHeaders ();
}

/**
 * Returns the string value of a single header label.
 */
rpc.prototype.header = function (label) {
	return this.request.getResponseHeader (label);
}

/**
 * Adds a header to the next request to be sent.
 */
rpc.prototype.addHeader = function (label, value) {
	return this.request.setRequestHeader (label, value);
}

/**
 * Assings the method, destination URL, and other optional
 * attributes (async flag, username, and password) of a pending
 * request.
 */
rpc.prototype.open = function (method, url) {
	if (arguments.length == 5) {
		return this.request.open (method, url, arguments[2], arguments[3], arguments[4]);
	} else if (arguments.length == 4) {
		return this.request.open (method, url, arguments[2], arguments[3]);
	} else if (arguments.length == 3) {
		return this.request.open (method, url, arguments[2]);
	}
	return this.request.open (method, url);
}

/**
 * Transmits the request, optionally with a postable string or DOM
 * data object.
 */
rpc.prototype.send = function () {
	if (arguments.length > 0) {
		return this.request.send (arguments[0]);
	}
	if (this.msie) {
		return this.request.send ();
	} else {
		return this.request.send (null);
	}
}

/**
 * Handles the state change events.
 */
rpc.prototype.process = function () {
	if (rpc_global.request.readyState == 4) {
		if (rpc_global.request.status == 200) {
			// okay, dispatch handler
			rpc_global.handler (rpc_global.request);
		} else {
			// error, dispatch handler
			rpc_global.error (rpc_global.request);
		}

		// clear header cache
		rpc_global.init ();
	}
}

/**
 * Sets the current request handler to the specified function.
 */
rpc.prototype.setHandler = function (handler) {
	this.handler = handler;
}

/**
 * Sets the current error handler to the specified function.
 */
rpc.prototype.setErrorHandler = function (error) {
	this.error = error;
}

/**
 * Calls open(), send(), and optionally setHandler() all in one
 * command.  Skips setHandler() if the second parameter, which
 * should be a function, is missing.
 */
rpc.prototype.call = function (url) {
	if (arguments.length > 1) {
		this.setHandler (arguments[1]);
	}
	this.open ('GET', url, true);
	this.send ();
}
