﻿
/**
 * @class Http (singleton)
 * @author Adam Grant
 * @created October 25, 2007
 * @copyright (C) 2007 Global Media Services, All rights reserved
 **/

var Http = new (function() {
	///// PRIVATE
	var defaultHeaders = {
		"X-Requested-With" : "XMLHttpRequest"
	};
	
	// XMLHttpRequest object factory
	function getRequester() {
		var requesters = [
			function() { return new ActiveXObject("MSXML2.XMLHTTP.3.0"); },
			function() { return new ActiveXObject("MSXML2.XMLHTTP"); },
			function() { return new ActiveXObject("Microsoft.XMLHTTP"); },
			function() { return new XMLHttpRequest(); }
		];
		
		for (var i = 0; i < requesters.length; i++) {
			try { return requesters[i](); } catch (e) { }
		}
		
		return null;
	}
	
	// Object cloner. If copy passed-in, it will be cloned, then source will 
	// append/overwrite the clone.
	function clone(source, copy) {
		var dest = new Object();
		if (copy) dest = clone(copy);
		for (var p in source) dest[p] = source[p];
		return dest;
	}
	
	// XMLHttpRequest onreadystatechange handler. All sorts of possibilities 
	// (Javascript eval, responseText, responseXML, JSON, etc.)
	// : NOTE : 03/05/2008 - options.onSuccess.apply and options.onComplete.apply were causing memory leaks;
	// since "this" is already passed-in as a parameter to the aforementioned delegates, it isn't really
	// necessary to run the delegates within the context of an XMLHttpRequest.
	function onReadyStateChange(options) {
		if (this.readyState == 4) {
			// : TRICKY : sometimes Firefox throws an exception when reading XMLHttpRequest's status property
			var status;
			try {
				status = this.status.toString();
			}
			catch (e) {
				status = "";
			}
			if (status.substr(0, 1) == "2") {
				if (options.id) document.getElementById(options.id).innerHTML = this.responseText;
				var contentType = this.getResponseHeader("Content-Type");
				if (/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i.test(contentType)) eval(this.responseText);
				if (options.onSuccess) {
					if (/^(text|application)\/((atom|rss|rdf)\+)?xml$/i.test(contentType)) options.onSuccess.apply(this, [this, this.responseXML]);
					else if (contentType == "text/x-json") options.onSuccess.apply(this, [this, eval("(" + this.responseText + ")")]);
					else options.onSuccess(this, this.responseText); //options.onSuccess.apply(this, [this, this.responseText]);
				}
				if (options.onComplete) {
					if (contentType == "text/xml") options.onComplete.apply(this, [this, this.responseXML]);
					else if (contentType == "text/x-json") options.onComplete.apply(this, [this, eval("(" + this.responseText + ")")]);
					else options.onComplete(this, this.responseText); //options.onComplete.apply(this, [this, this.responseText]);
				}
			}
			else {
				if (options.onFailure) options.onFailure.apply(this, [this, this.responseText]);
				if (options.onComplete) options.onComplete.apply(this, [this, this.responseText]);
			}
		}
	}
	
	// General XMLHttpRequest work-horse. All methods route through here.
	function http(method, url, headers, data, async, onReadyStateChange, options, userName, password) {
		var request = getRequester();
		var ts = "";
		if (request) {
			if (method == "GET" && data) url = url.indexOf("?") == -1 ? url + "?" + data : url.substr(0, url.indexOf("?")) + "?" + data;
			request.open(method, url, async, userName, password);
			if (headers) for (var h in headers) request.setRequestHeader(h, headers[h]);
			if (onReadyStateChange) request.onreadystatechange = function() { onReadyStateChange.apply(request, [options]); };
			request.send(method == "POST" ? data : "");
			if (!async && request.overrideMimeType && onReadyStateChange) onReadyStateChange.apply(request, [options]);
			return false; // successful exit
		}
		
		return true; // pass-through on create XMLHttpRequest failure
	}
	
	///// PUBLIC
	/**
	 * @method serialize
	 * @param form:HTMLElement; <FORM/> tag reference
	 * Converts form data into a URLEncoded string. Note: form buttons are not 
	 * sent.
	 **/
	this.serialize = function(form) {
		var a = new Array();
		var name, value;
		var v;
		for (var i = 0; i < form.length; i++) {
			var name = form[i].name;
			if (!form[i].disabled) {
				value = null;
				switch (form[i].type) {
				case "hidden":
				case "password":
				case "text":
				case "textarea":
					value = form[i].value;
					break;
				case "select":
				case "select-one":
					value = form[i].options[form[i].selectedIndex].value;
					break;
				case "select-multiple":
					for (var j = 0; j < form[i].options.length; j++) {
						if (form[i].options[j].selected) {
							a.push(escape(form[i].name) + "=" + escape(form[i].options[j].value));
						}
					}
					break;
				case "radio":
				case "checkbox":
					if (form[i].checked) value = form[i].value;
					break;
				}
				if (value != null) a.push(escape(form[i].name) + "=" + escape(form[i].value));
			}
		}
		return a.join("&");
	};
	
	/**
	 * @method link
	 * @param a:HTMLElement; <A/> tag reference
	 * @param options:Object; optional. See "options" reference for details.
	 * GETs requested link
	 **/
	this.link = function(a, options) {
		return this.request(a.href, options);
	};
	
	/**
	 * @method submit
	 * @param form:HTMLElement; <FORM/> tag reference
	 * @param options:Object; optional. See "options" reference for details.
	 * POSTs form data (or GET if form METHOD attribute is set to GET). Submit 
	 * buttons are not sent.
	 **/
	this.submit = function(form, options) {
		var o = clone(options, {method : form.method.toUpperCase(), headers : {"Content-Type" : "application/x-www-form-urlencoded"}});
		o.data = this.serialize(form);
		if (options.data) o.data += o.data ? "&" + options.data : options.data;
//		var a = new Array();
//		for (var p in o) {
//			a.push(p + " : " + o[p]);
//		}
//		alert(a.join("\n"));
		return this.request(form.action, o);
	};
	
	/**
	 * @method request
	 * @param url:String
	 * @param options:Object; optional. See "options" reference for details.
	 * Requests a URL using GET by default.
	 **/
	this.request = function(url, options) {
		var method = options && options.method == "POST" ? "POST" : "GET";
		var headers = options && options.headers ? clone(options.headers, defaultHeaders) : defaultHeaders;
		var data = options && options.data ? options.data : "";
		var async = options && options.async == false ? false : true;
		var userName = options && options.userName ? options.userName : undefined;
		var password = options && options.password ? options.password : undefined;
		
//		var a = new Array();
//		for (var p in headers) {
//			a.push(p + " : " + headers[p]);
//		}
//		alert(a.join("\n"));
		
		return http(method, url, headers, data, async, onReadyStateChange, options || {}, userName, password);
	};
	
	/**
	 * @method update
	 * @param id:String; HTMLElement to receive responseText
	 * @param url:String
	 * @param options:Object; optional. See "options" reference for details.
	 * Updates an HTMLElement's innerHTML with the response (upon success).
	 **/
	this.update = function(id, url, options) {
		var o = clone(options);
		o.id = id;
		return this.request(url, o);
	};
	
})();
