
		/**
		 * mfischer, 2005.10.14
		 *
		 * Make XmlHttpRequest communication cross-browser easier.
		 *
		 * Tested on:
		 * - FF 1.0.7, 1.5
		 * - IE 5.0, 5.5, 6.0 (as long as ActiveX is activated)
		 * - Opera 8.5
		 *
		 * Usage without callback:
		 *
		 * var oReq = new NcXmlRequest('your/server/url');
		 * var oArgs = {};
		 * oArgs.variable = 'value';
		 * var ret = oReq.send(oArgs);
		 * if (false == ret) {
		 *   alert(oReq.getLastError());
		 * } else {
		 *   alert(oReq.responseText);
		 * }
		 *
		 * Usage with callback:
		 *
		 * function callback(oXmlHttp) {
		 *   alert(oXmlHttp.responseText);
		 * }
		 *
		 * var oReq = new NcXmlRequest('your/server/url');
		 * var oArgs = {};
		 * oArgs.variable = 'value';
		 * var ret = oReq.send(oArgs, callback);
		 * if (false == ret) {
		 *   alert(oReq.getLastError());
		 * }
		 */

		function NcXmlRequest(url) {
			// Initial url; needs to be set before send() is callled
			this.url = '';
			// Name of the cookie used for caching if the browser supports it.
			this.sCookieName = 'ich_mag_ajax';
			this.iCookieDays = 5;
			if (typeof url != 'undefined') {
				this.url = url;
			}
			// Make use of closures so we find "this" object in the onreadystate-handler
			var me = this;
			// The raw XmlHttpRequest object created by the browser.
			this.oXmlHttp = null;
			// Error stack. Records all errors during lifetime. If a function
			// returns 'false', use <obj>.getLastError() to get a string
			// representation.
			this.aErrors = [];
			// Whether to allow multiple requests at the same time.
			// TODO: WARNING: NOTICE: not implemented, not supported, don't change the value! You've been warned!
			this.bAllowMultipleRequests = false;
			// State whether there's currently a request in progress. Only change within this class, may be monitored from everywhere.
			this.bRequestInProgress = false;
			// Argument stack used to convert JS variables to PHP POST data string
			this.aArgStack = [];
			/**
			 * Callback function which is internally called everytime a state
			 * changes on async communications. Waits until the data has been
			 * transfered and calls the user-defined callback.
			 *
			 * The handler to be successful called, the readyState has to be
			 * '4' and the status of the XmlHttpRequest needs to be '200' (HTTP
			 * status code).
			 */
			this.callback = function() {
				if (me.oXmlHttp && me.oXmlHttp.readyState && me.oXmlHttp.readyState == 4) {
					me.bRequestInProgress = false;
					if (me.oXmlHttp.status == 200) {
						me.realCallback(me.oXmlHttp);
					}
				}
			}
			/**
			 * Tries to return an XmlHttpRequest object.
			 * Does feature checking of the availability for various browsers.
			 *
			 * @return	mixed	False if no XmlHttpRequest could be created, the XmlHttpRequest on success
			 */
			this.getXmlHttpObject = function() {
				var oXmlHttp = null;
				var aIePrefix = ["MSXML2", "Microsoft", "MSXML", "MSXML3"];
				try {
					if (window.XMLHttpRequest) {
						if (!(oXmlHttp = new XMLHttpRequest())) {
							this.appendError("Could not instantiate XMLHttpRequest object");
							return false;
						}
						return oXmlHttp;
					} else if (window.ActiveXObject) {
						for (var i = 0, l = aIePrefix.length; i < l; i++) {
							try {
								if (!(oXmlHttp = new ActiveXObject(aIePrefix[i] + ".XmlHttp"))) {
									continue;
								}
								return oXmlHttp;
							} catch (e) { };
						}
						this.appendError("No compatible XmlHttpRequest implementation found");
						return false;
					} else {
						this.appendError("No compatible XmlHttpRequest implementation found");
						return false;
					}
				} catch (e) {
					this.appendError("Unknown exception was thrown during XmlHttpRequest instantiation");
					return false;
				}
			}
			/**
			 * Converts the argument object for POST request and sends the
			 * request. If a callback is provided, the request is done
			 * asynchronuous.
			 *
			 * The return value depends on whether a callback is used (async communication) or not.
			 *
			 * If no callback is used:
			 * - the XML http request object is returned OR false if a callback is in progress
			 *
			 * If a callback was provided:
			 * - true is returned and after successful receiving of the data the callback is called OR false if a callback is in progress
			 *
			 * @param	mixed		Any distinct Javascript variable to be sent (no ActiveX object or XPCOM, but everything else: float, int, string, pure objects)
			 * @para	reference	Optional callback function for asynchronous communication
			 * @return	mixed		Depends on the parameters passed, see description
			 */
			this.send = function(oArgs, fCallback) {
				if (this.url.length == 0) {
					this.appendError("No URL given where to send the data to. Assign value to <object>.url");
					return false;
				}
				if (!this.bAllowMultipleRequests && this.bRequestInProgress) {
					this.appendError("Multiple requests are not allowed and a request is currently in progress");
					return false;
				}
				if (!this.oXmlHttp) {
					if (!(this.oXmlHttp = this.getXmlHttpObject())) {
						this.appendError("send() is unable to instantiate a XmlHttpRequest object");
						return false;
					}
				}
				var sArgs = this.convertJsToPhp(oArgs);
				var xmlRoot;

				var bAsync = false;
				if (typeof fCallback != 'undefined') {
					bAsync = true;
					this.realCallback = fCallback;
				}
				this.oXmlHttp.open('POST', this.url, bAsync);
				this.oXmlHttp.setRequestHeader('Content-Length', sArgs.length);
				this.oXmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
				if (bAsync) {
					this.oXmlHttp.onreadystatechange = this.callback;
				}
				this.bRequestInProgress = true;
				this.oXmlHttp.send(sArgs);
				if (bAsync) {
					return true;
				} else {
					this.bRequestInProgress = false;
					return this.oXmlHttp;
				}
			};
			/**
			 * Convert a JS variable into a string, used by convertJsToPhp
			 *
			 * @param	string	Prefix to be used for this iteration
			 * @param	mixed	Any type of distinct Javascript variables (No ActiveX or XPCOM)
			 */
			this.convertJsToPhpRec = function (sPrefix, obj) {
				var key;
				for (key in obj) {
					switch (typeof obj[key]) {
						case 'object':
							this.convertJsToPhpRec(sPrefix + '[' + escape(key) + ']', obj[key]);
							break;
						default:
							// mfischer, 2004.10.18: idially we would use argStack.push, but IE5 doesn't implement it
							this.aArgStack[this.aArgStack.length] = sPrefix + '[' + escape(key) + ']=' + escape(obj[key]);
							break;
					}
				}
			};
			/**
			 * Convert a javascript variable (simply and complex ones but, only
			 * distinct types) into a string to be used to be sent as POST data
			 * to the server. The syntax used to build the string can directly
			 * parsed by PHP.
			 *
			 * The args parameter may look like:
			 * var oArgs = {};
			 * oArgs.cmd = 'command';
			 * oArgs.id = 3;
			 *
			 * @param	mixed	Any type of distinct Javascript variables (No ActiveX or XPCOM)
			 * @return	string	String representation for PHP POST data
			 */
			this.convertJsToPhp = function(args) {
				var i, l;

				this.aArgStack = [];

				for (i in args) {
					switch (typeof args[i]) {
						case 'object':
							this.convertJsToPhpRec(i, args[i]);
							break;
						default:
							// mfischer, 2004.10.18: idially we would use argStack.push, but IE5 doesn't implement it
							this.aArgStack[this.aArgStack.length] = escape(i) + '=' + escape(args[i]);
							break;
					}
				}

				var s = '';
				for (i = 0, l = this.aArgStack.length; i < l; i++) {
					if (i < l - 1) {
						s += this.aArgStack[i] + '&';
					} else {
						s += this.aArgStack[i];
					}
				}
				return s;
			};
			/**
			 * Append an error messge to the error message stack.
			 *
			 * @param	string	Error message
			 */
			this.appendError = function(sError) {
				this.aErrors[this.aErrors.length] = sError;
			};
			/**
			 * Retrieves the last error message or an empty string.
			 *
			 * @return	string	Last error message or empty.
			 */
			this.getLastError = function() {
				if (this.aErrors.length > 0) {
					return this.aErrors[this.aErrors.length - 1];
				} else {
					return "";
				}
			};
			/**
			 * Has this browser the capability to support Ajax?
			 *
			 * @param	boolean	Optiona: cache result in Cookie
			 * @return	boolean	True supports, False, doesn't
			 */
			this.supportsAjax = function(bCacheInCookie) {
				var sCookieVal;
				var bWorks = false;
				var bCache = false;
				if (typeof bCacheInCookie != 'undefined' && bCacheInCookie) {
					bCache = true;
					sCookieVal = NcGetCookie(this.sCookieName);
					if (sCookieVal != null) {
						// Trust the client
						if (sCookieVal && sCookieVal == 'ja, ajax ist super!') {
							return true;
						}
						return false;
					}
				}
				this.oXmlHttp = this.getXmlHttpObject();
				if (bCache) {
					if (this.oXmlHttp) {
						NcSetCookie(this.sCookieName, 'ja, ajax ist super!', this.iCookieDays);
					} else {
						NcSetCookie(this.sCookieName, 'nein, ajax is boese!', this.iCookieDays);
					}
				}
				if (this.oXmlHttp) {
					return true;
				} else {
					return false;
				}
			}
			/* not approved yet because of lack of proper error handling
			this.convertToSimpleObject = function(vars) {
				var obj = {}, name, value;
				for (var i = 0, l = vars.length; i < l; i++) {
					name = vars[i].getAttribute('name');
					if (!name) {
						this.appendError("No name attribute given");
						throw "No name attribute given";
					}
					value  = vars[i].getAttribute('value');
					if (!value) {
						if (vars[i].firstChild && vars[i].firstChild.nodeValue) {
							value = vars[i].firstChild.nodeValue;
						} else {
							this.appendError("No value found");
							// alert(this.oXmlHttp.responseText);
							throw "No value found";
						}
					}
					obj[name] = value;
				}
				return obj;
			}

			this.convertToMoreComplexObject = function(input) {
				var varName, varType;
				var obj = {};
				for (var i = 0, l = input.childNodes.length; i < l; i++) {
					varName = input.childNodes[i].getAttribute("name");
					varType = input.childNodes[i].getAttribute("type");
					switch (varType) {
						case 'string':
							obj[varName] = input.childNodes[i].getAttribute("value");
							break;
						case 'object':
							obj[varName] = this.convertToMoreComplexObject(input.childNodes[i]);
							break;
						default:
							throw "Unsupported type '" + varType + "'";
					}
				}
				return obj;
			}

			this.requestSimpleObject = function(xml) {
				var response = xml.getElementsByTagName('response');
				if (response.length == 0) {
					// kein <repsonse>-Tag, suche ein <serverror>-Tag
					var serverError = xml.getElementsByTagName('serverError');
					if (serverError.length == 0) {
						alert('Es konnte keine gültige Antwort vom Server empfangen werden.\nDie Antwort war:\n\n' + this.oXmlHttp.responseText);
						throw "No valid response found";
					}
					var obj = this.convertToSimpleObject(serverError[0].getElementsByTagName('var'));
					alert('Ein Serverseitiger Fehler ist aufgetreten:\n\n' + obj.errorMessage);
					return {};
				} else {
					return this.convertToSimpleObject(response[0].getElementsByTagName('var'));
				}
			}

			this.requestMoreComplexObject = function(xml) {
				var response = xml.getElementsByTagName('response');
				if (response.length == 0) {
					// kein <repsonse>-Tag, suche ein <serverror>-Tag
					var serverError = xml.getElementsByTagName('serverError');
					if (serverError.length == 0) {
						alert('Es konnte keine gültige Antwort vom Server empfangen werden.\nDie Antwort war:\n\n' + this.oXmlHttp.responseText);
						throw "No valid response found";
					}
					var obj = this.convertToSimpleObject(serverError[0].getElementsByTagName('var'));
					alert('Ein Serverseitiger Fehler ist aufgetreten:\n\n' + obj.errorMessage);
					return {};
				} else {
					return this.convertToMoreComplexObject(response[0]);
				}
			}
			*/
		}

