/*
 *	Framework; JavaScript Framework with Ajax capabilities
 *	Developed by Ronni Persson, v0.1 - Public Domain
*/

var $extend = function(extendThis, extendWith, overwrite)
{
	if ($type(extendWith) != 'object') return;
	for (var n in extendWith)
	{
		if (!extendThis[n] || overwrite === true) extendThis[n] = extendWith[n];
	}
}

var $get = function(elmName, container) {
	container = container || document;
	return container.getElementById(elmName);
}

var $type = function(obj) {
	return (typeof obj).toLowerCase();
}

String.prototype.trim = function()
{
	return this.replace(/^ /, '').replace(/ $/, '');
}

$extend(Array.prototype, {
	indexOf: function(find)
	{
		for (var n = 0, j = this.length; n < j; n++)
		{
			if (this[n] == find) return n;
		}
		return -1;
	},
	pushArray: function(arr)
	{
		for (var n = 0, j = arr.length; n < j; n++)
		{
			this.push(arr[n]);
		}
	},
	foreach: function(fn)
	{
		for (var n = 0, j = this.length; n < j; n++)
		{
			fn.call(this[n]);
		}
	}
});

/*
$extend(Object.prototype, {
	Type: function() {
		return (typeof this).toLowerCase();
	}
});
*/

document.getElementsByClassName = function(className, container)
{
	container = container || document;
	var matches = [];
	var all = container.getElementsByTagName('*');
	for (var n = 0, j = all.length; n < j; n++)
	{
		if (all[n].className.split(" ").indexOf(className) != -1)
			matches.push(all[n]);
	}
	return matches;
}

var framework = {
	browser: {
		ie: !!document.all,
		opera: !!window.opera,
		gecko: navigator.userAgent.indexOf('Gecko') != -1 && navigator.userAgent.indexOf('KHTML') == -1,
		safari: navigator.userAgent.indexOf('AppleWebKit') != -1
	},
	core: {
		CheckOptions: function(options, required, appendIfUnset)
		{
			if (!options)
			{
				framework.CatchError('.*: No options defined');
				return false;
			}
			var n = 0, j = 0;
			for (n = 0, j = required.length; n < j; n++)
			{
				if (!options[required[n]])
				{
					framework.CatchError('.*: options.'+required[n]+' are not defined');
					return false;
				}
			}
			if (appendIfUnset && typeof (appendIfUnset) == 'object')
			{
				for (n in appendIfUnset)
				{
					if (!options[n])
					{
						options[n] = appendIfUnset[n];
					}
				}
			}
			return true;
		}
	},
	event: {
		documentReady: false,
		readyBound: false,
		readyListeners: [],
		safariTimer: null,
		Add: function(element, event, listener)
		{
			if (!element)
			{
				framework.CatchError('.event.Add: element has no properties');
				return false;
			}
			if (element.addEventListener)
			{
				element.addEventListener(event, listener, false);
			}
			else if (element.attachEvent)
			{
				element.attachEvent('on'+event, listener);
			}
			else
			{
				element['on'+event] = listener;
			}
		},
		Remove: function(element, event, listener)
		{
			if (!element)
			{
				framework.CatchError('.event.Remove: element has no properties');
				return false;
			}
			if (element.removeEventListener)
			{
				element.removeEventListener(event, listener, false);
			}
			else if (element.detachEvent)
			{
				element.detachEvent('on'+event, listener);
			}
			else
			{
				delete element['on'+event];
			}
		},
		Ready: function(listener)
		{
			if (framework.event.documentReady)
			{
				listener.apply(document);
				return;
			}

			if ($type(listener) != 'function')
			{
				framework.event.documentReady = true;
				for (var n = 0, j = framework.event.readyListeners.length; n < j; n++)
				{
					framework.event.readyListeners[n]();
				}
				if (framework.browser.gecko || framework.browser.opera)
				{
					framework.event.Remove(document, 'DOMContentLoaded', framework.event.Ready);
				}
				return;
			}

			this.readyListeners.push(listener);
			if (!this.readyBound)
			{
				this.ReadyBind();
			}
		},
		ReadyBind: function()
		{
			if (framework.browser.gecko || framework.browser.opera)
			{
				framework.event.Add(document, 'DOMContentLoaded', framework.event.Ready);
			}
			else if (framework.browser.ie)
			{
				document.write("<scr" + "ipt id=__ie_init defer=true " + 
					"src=//:><\/script>");

				var script = document.getElementById("__ie_init");
				if (script)
					script.onreadystatechange = function() {
						if (this.readyState != 'complete') return;
						framework.event.Ready();
					};
			
				script = null;
			}
			else if (framework.browser.safari)
			{
				var safariTimer = this.safariTimer;
				safariTimer = setInterval(function(){
					if (document.readyState == 'loaded' || document.readyState == 'complete')
					{
						clearInterval(safariTimer);
						safariTimer = null;

						framework.event.Ready();
					}
				}, 10); 
			}
			else
			{
				framework.event.Add(window, 'load', framework.event.Ready);
			}
		}
	},
	selector: {
		Parse: function(selector)
		{
			var matches = framework.selector.Simple(selector);
			if (matches !== false) return framework.selector.CheckAttribute(matches, selector);

			// The advanced selectors:
			var selectors 	= [];
			var matches 	= [];

			if (selector.indexOf(',') != -1)	selectors 	 = selector.split(',');
			else								selectors[0] = selector;

			var match;
			while(selector = selectors.shift())
			{
				selector = selector.trim();
				match = framework.selector.Simple(selector);
				if (match)
				{
					for (var n = 0, j = match.length; n < j; n++)
					{
						matches.push(match[n]);
					}
				}
				else if (selector.indexOf(' ') != -1)
				{
					var container = document;
					var hierachy = selector.split(' ');
					var child;
					var tmpmatch;
					while(childSelector = hierachy.shift())
					{
						if (match)
						{
							matches = [];
							for (var n = 0, m = match, j = m.length; n < j; n++)
							{
								tmpmatch = framework.selector.CheckAttribute(framework.selector.Simple(childSelector, m[n]), childSelector);
								if (tmpmatch)
								{
									matches.pushArray(tmpmatch);
								}
							}
							match = matches;
						}
						else
						{
							match = framework.selector.Simple(childSelector);
						}
					}
				}
			}

			return matches;
		},
		CheckAttribute: function(elements, selector)
		{
			if (framework.test.attributeSelector.test(selector))
			{
				var attribute 			= framework.test.attributeSelector.exec(selector)[2];
				if (attribute == 'class') attribute = 'className';
				var attributeSelector 	= framework.test.attributeSelector.exec(selector)[4];
				var attributeValue		= framework.test.attributeSelector.exec(selector)[5];
				var returnElements 		= [];
				for (var n = 0, j = elements.length; n < j; n++)
				{
					if (elements[n] && elements[n][attribute])
					{
						var val = elements[n][attribute];
						switch (attributeSelector)
						{
							case '=':
								if (val == attributeValue)
								{
									returnElements.push(elements[n]);
								}
								break;
							case '|=':
								if (val.indexOf(attributeValue) == 0)
								{
									returnElements.push(elements[n]);
								}
								break;
							case '~=':
								if (val.split(' ').indexOf(attributeValue) != -1)
								{
									returnElements.push(elements[n]);
								}
								break;
							case '*=':
								if (val.indexOf(attributeValue) != -1)
								{
									returnElements.push(elements[n]);
								}
								break;
							case '^=':
								if (val.indexOf(attributeValue) == 0)
								{
									returnElements.push(elements[n]);
								}
								break;
							case '$=':
								if (val.length-attributeValue.length == val.indexOf(attributeValue) && val.indexOf(attributeValue) != -1)
								{
									returnElements.push(elements[n]);
								}
								break;
							default:
								returnElements.push(elements[n]);
								break;
						}
					}
				}
				return returnElements;
			}
			return elements;
		},
		// Simple selector (ID, className or tagName)
		Simple: function(selector, container)
		{
			container = container || document;
			var matches = [];

			if (framework.test.id.test(selector) && container === document)
			{
				return [$get(framework.test.id.exec(selector)[1], container)];
			}
			else if (framework.test.id.test(selector))
			{
				var match = container.getElementsByTagName('*');
				for (var n = 0, j = match.length; n < j; n++)
				{
					if (match[n].id == framework.test.id.exec(selector)[1])
					{
						matches.push(match[n]);
					}
				}
				return matches;
			}
			else if (framework.test.tagName.test(selector))
			{
				var m = container.getElementsByTagName(selector);
				for (var n = 0, j = m.length; n < j; n++)
				{
					matches.push(m[n]);
				}
				return matches;
			}
			else if (framework.test.className.test(selector))
			{
				return document.getElementsByClassName(framework.test.className.exec(selector)[1], container);
			}
			else if (selector == '*')
			{
				return container.getElementsByTagName('*');
			}
			else if (framework.test.attributeSelector.test(selector))
			{
				return framework.selector.Simple(framework.test.attributeSelector.exec(selector)[1]);
			}

			return false;
		}
	},
	test: {
		id: /^#([-a-z_0-9]+)$/i,
		tagName: /^([a-z]+)$/i,
		className: /^\.([-a-z_0-9]+)$/i,
		attributeSelector: /^([^ ]*?)\[(.*?)(([~\|\^\$\*]?=)"(.*?)")?\]$/i
	},
	errors: [],
	CatchError: function(error)
	{
		framework.errors.push(error);
	},
	Debug: function()
	{
		if (console && console.log)
			console.log(framework.errors);
		else
			alert(framework.errors);
	},
	Eval: function(script)
	{
		if (framework.browser.safari)
		{
			window.setTimeout(function(){
				window.eval(script);
			}, 0);
			return true;
		}
		window.eval(script);
		return true;
	},
	Extend: function(obj)
	{
		for(var n in obj)
			this[n] = obj[n];
	},
	FileExists: function(url)
	{
		var success = false;
		framework.ajax.Get(url, {
			async: false,
			onSuccess: function()
			{
				success = true;
			},
			onFailure: function()
			{
				success = false;
			}
		});
		return success;
	},
	GetElements: function(selector)
	{
		return framework.selector.Parse(selector);
	},
	GetOffset: function(obj)
	{
		if (obj && typeof (obj) == 'string')
			obj = $get(obj);

		var o = {top: 0, left: 0, width: obj.offsetWidth, height: obj.offsetHeight};
		while(obj.offsetParent)
		{
			o.top  += obj.offsetTop;
			o.left += obj.offsetLeft;
			obj = obj.offsetParent;
		}
		if (document.all)
		{
		}
		return o;
	},
	LoadModule: function(module)
	{
		framework.ajax.Get('framework.'+module+'.js', {
			async: false,
			responseType: 'script'
		});
	},
	NodeName: function(elm)
	{
		return elm.nodeName.toLowerCase();
	}
};

/*
 * AJAX
 *
 * TO-DO:
 * 
 */

framework.Extend({
	ajax: {
		core: {
			defaultOptions: {
				async: true,
				charset: 'UTF-8',
				password: '',
				responseType: 'html',
				username: '',
				onRequest: function(){},
				onSuccess: function(){},
				onFailure: function(){}
			},
			XMLHttpRequestObjects: [
				function () { return new XMLHttpRequest() },
				function () { return new ActiveXObject("Msxml2.XMLHTTP") },
				function () { return new ActiveXObject("Msxml3.XMLHTTP") },
				function () { return new ActiveXObject("Microsoft.XMLHTTP") }
			],
			BuildQuery: function(data)
			{
				var q = '';
				for (var n in data)
				{
					q = (q ? '&' : '') + n + '=' + data[n];
				}
				return q;
			},
			GetXMLHttpRequestObject: function()
			{
				var requestObject;
				for (var n = 0, j = framework.ajax.core.XMLHttpRequestObjects.length; n < j; n++)
				{
					try
					{
						requestObject = framework.ajax.core.XMLHttpRequestObjects[n]();
					}
					catch(e)
					{
						continue;
					}
					break;
				}
				return requestObject;
			}
		},
		requestObject: false,
		requestInProgress: false,
		queue: [],
		AbortQueue: function()
		{
			framework.ajax.queue = [];
		},
		Call: function(url, method, data, options)
		{
			// If request is in progress, queue this request
			if (framework.ajax.requestInProgress == true)
			{
				framework.ajax.queue.push({url: url, method: method, data: data, options: options});
				return;
			}
			framework.ajax.requestInProgress = true;
			// Get saved requestObject
			var requestObject = framework.ajax.requestObject;
			// If no requestObject is created, create one
			if (!requestObject)
			{
				framework.ajax.requestObject = requestObject = framework.ajax.core.GetXMLHttpRequestObject();
			}
			// Catch errors:
			if (!requestObject) return framework.CatchError('AJAX calls is not supported');
			if (!url) 			return framework.CatchError('.ajax.Call: No URL specified');
			// Set default values:
			if (!method) 		method = 'get';
			if (!data)			data = {};
			if (!options)		options = {};
			// Set unset options to default:
			for (var n in framework.ajax.core.defaultOptions)
			{
				if (options[n] == null)
					options[n] = framework.ajax.core.defaultOptions[n];
			}
			// Receive data string:
			var dataStr = framework.ajax.core.BuildQuery(data);
			// Open connection:
			requestObject.open(method, url, options.async, options.username, options.password);
			//
			if (method == 'post')
				requestObject.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset="+options.charset);
			// Set up event listener
			requestObject.onreadystatechange = function() {
				switch(requestObject.readyState)
				{
					// The request is complete -
					case 4:
						// - lets check the status code:
						if (requestObject.status == 200 || requestObject.status == 302 || requestObject.status == 0)
						{
							// Success
							// Now get correct data format
							switch (options.responseType)
							{
								case 'json':
									var response = eval(requestObject.responseText);
									break;
								case 'xml':
									var response = requestObject.responseXML;
									break;
								case 'script':
									framework.Eval(requestObject.responseText);
									break;
								case 'html':
								case 'text':
								default:
									var response = requestObject.responseText;
									break;
							}
							options.onSuccess.call(requestObject, response);
						}
						else
						{
							// Failure
							options.onFailure.call(requestObject, requestObject.status, requestObject.statusText);
						}

						framework.ajax.requestInProgress = false;
						if (framework.ajax.queue.length > 0)
						{
							var newCall = framework.ajax.queue.shift();
							framework.ajax.Call(newCall.url, newCall.method, newCall.data, newCall.options);
						}
						break;
				}
			}
			// Do the request
			requestObject.send(dataStr);
			options.onRequest.call(requestObject);
		},
		Get: function(url, options) // helper function for .ajax.Call
		{
			framework.ajax.Call(url, 'get', {}, options);
		},
		Post: function(url, data, options) // helper function for .ajax.Call
		{
			framework.ajax.Call(url, 'post', data, options);
		},
		SetDefaultOptions: function(options)
		{
			if (!($type(options) == 'object')) return;
			for (var n in options)
			{
				framework.ajax.core.defaultOptions[n] = options[n];
			}
		}
	}
});
