

Object.extend = function(dest, source, allowOverwrite)
{
	for (var prop in source)
	{
		if (source.hasOwnProperty(prop) && (allowOverwrite || !dest.hasOwnProperty(prop)))
			dest[prop] = source[prop];
	}

	return dest;
}

Object.extend(Array.prototype,
{
	indexOf: function(searchElement, fromIndex)
	{
		var l = this.length, i = 0;
		if (fromIndex)
		{
			i = fromIndex;
			if (i < 0)
			{
				i += l;
				if (i < 0) i = 0;
			}
		}

		while (i < l)
		{
			if (this[i] === searchElement) return i;
			i++;
		}

		return -1;
	},
	forEach: function(func, obj)
	{
		for (var i = 0, l = this.length; i < l; i++)
		{
			if (i in this)
				func.call(obj, this[i], i, this);
		}
	},
	filter: function(func, obj)
	{
		var res = [], val;
		for (var i = 0, l = this.length; i < l; i++)
		{
			if (i in this)
			{
				val = this[i]; // in case func mutates this
			        if (func.call(obj, val, i, this))
					res.push(val);
			}
		}

		return res;
	}
});

// Generics
['forEach', 'filter', 'slice'].forEach(
	function(func)
	{
		if (!(func in Array))
		{
			Array[func] = function(obj)
			{
				return this.prototype[func].apply(obj, Array.prototype.slice.call(arguments, 1));
			}
		}
	}
);

Object.extend(String.prototype,
{
	trim: function()
	{
		return this.replace(/^\s+|\s+$/, '');
	},
	ucFirst: function()
	{
		return this.charAt(0).toUpperCase() + this.substr(1);
	},
	escapeHtml: function()
	{
		var escapeChars = {
			'&': '&amp;',
			'<': '&lt;',
			'>': '&gt;',
			'"': '&quot;'
		};

		return this.replace(/[&<>"]/g, function(c) { return escapeChars[c]; });
	},
	unescapeHtml: function()
	{
		var namedEntities = {
			'amp': '&',
			'lt': '<',
			'gt': '>',
			'quot': '"',
			'euro': String.fromCharCode(8364)
		};

		return this.replace(
			/&(amp|lt|gt|quot|euro|#\d+);/g,
			function(c,n)
			{
				return namedEntities[n] || String.fromCharCode(n.substr(1));
			}
		);
	}
});

Object.extend(Function.prototype,
{
	bind: function()
	{
		var handler = this, args = Array.slice(arguments, 0), obj = args.shift();

		return function()
		{
			return handler.apply(obj, args.concat(Array.slice(arguments, 0)));
		}
	}
});

RegExp.escape = function(string)
{
	return string.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
}

// handy
function Set()
{
	var set = {}, i = arguments.length;
	while (i--)
		set[arguments[i]] = 1;

	return set;
}

// execute methods on DOMload
var DomLoaded =
{
	onload: [],
	loaded: function()
	{
		if (DomLoaded.done) return;
		DomLoaded.done = true;

		if (DomLoaded.timer)
			clearInterval(DomLoaded.timer);

		if (DomLoaded.onload.length)
			DomLoaded.execute();
	},
	load: function(fireThis)
	{
		DomLoaded.onload.push(fireThis);

		if (DomLoaded.onload.length == 1)
		{
			if (document.addEventListener)
				document.addEventListener('DOMContentLoaded', DomLoaded.loaded, false);
			else
			{
				DomLoaded.timer = setInterval(function()
				{
					if (document.getElementById('domLoaded'))
						DomLoaded.loaded();
				}, 10);
			}

			// generic fallback
			addEvent(window, 'load', DomLoaded.loaded);
		}
	},
	execute: function()
	{
		var func = DomLoaded.onload.shift();

		if (typeof func == 'function')
			func();

		if (DomLoaded.onload.length)
			setTimeout(DomLoaded.execute, 10);
	}
}

// generic eventhandling
var addEvent = function()
{
	if (document.addEventListener)
	{
		return function(element, type, handler)
		{
			element.addEventListener(type, handler, false);
		}
	}
	else
	{
		function handleEvent(event)
		{
			event = event || fixEvent(window.event);

			var handlers = this.events[event.type], returnValue;
			for (var i in handlers)
			{
				if (handlers.hasOwnProperty(i) && handlers[i].call(this, event) === false)
					returnValue = false;
			}

			return returnValue;
		}
		function fixEvent(event)
		{
			event.preventDefault = preventDefault;
			event.stopPropagation = stopPropagation;
			event.target = event.srcElement;

			return event;
		}
		function preventDefault() { this.returnValue = false; }
		function stopPropagation() { this.cancelBubble = true; }
		function removeAllEvents()
		{
			while ((cachedEvent = eventCache.pop()))
				removeEvent(cachedEvent.element, cachedEvent.type, cachedEvent.handler);
		}

		var guid = 1;
		var eventCache = [];

		return function(element, type, handler)
		{
			if (!handler.$$guid) handler.$$guid = guid++;
			if (!element.events) element.events = {};
			if (!element.events[type])
			{
				element.events[type] = {};
				if (element['on' + type]) element.events[type][0] = element['on' + type];
				element['on' + type] = handleEvent;
			}

			element.events[type][handler.$$guid] = handler;

			if (type != 'unload')
			{
				if (!eventCache.length)
					addEvent(window, 'unload', removeAllEvents);

				eventCache.push(
					{
						element: element,
						type: type,
						handler: handler
					}
				);
			}
		}
	}
}();

var removeEvent = function()
{
	if (document.removeEventListener)
	{
		return function(element, type, handler)
		{
			element.removeEventListener(type, handler, false);
		}
	}
	else
	{
		return function(element, type, handler)
		{
			if (element.events && element.events[type] && handler.$$guid)
				delete element.events[type][handler.$$guid];
		}
	}
}();



function addClass(element, className)
{
	if (element.className.length)
	{
		var classes = getClassList(element);
		if (classes.indexOf(className) == -1)
		{
			classes.push(className);
			setClassList(element, classes);
		}
	}
	else
		element.className = className;
}

function removeClass(element, className)
{
	var classes = getClassList(element), index;
	if ((index = classes.indexOf(className)) > -1)
	{
		delete classes[index];
		setClassList(element, classes);
	}
}

function replaceClass(element, oldclass, newclass)
{
	var classes = getClassList(element), index;
	if ((index = classes.indexOf(oldclass)) > -1 && classes.indexOf(newclass) == -1)
	{
		classes[index] = newclass;
		setClassList(element, classes);
	}
}

function hasClass(element, className)
{
	var elementClassName = element.className;
	return	elementClassName.length &&
		(	elementClassName == className ||
			getClassRegExp(className).test(elementClassName)
		);
}

function getClassList(element)
{
	return element.className.length ? element.className.split(/\s+/) : [];
}

function setClassList(element, classes)
{
	element.className = classes.join(' ');
}

var regExpCache = {};
function getClassRegExp(className)
{
	if (!(className in regExpCache))
		regExpCache[className] = new RegExp('(^|\\s)' + className + '(\\s|$)');

	return regExpCache[className];
}



function toggleSectionDisplay(img)
{
	var display, cat = document.getElementById(img.id + 'List');
	if (cat)
	{
		if (img.src == getPreloadImage('sectionShowIcon'))
		{
			img.src = getPreloadImage('sectionHideIcon');
			cat.style.display = '';
			ellipsis(cat);
			delete hideSections[img.id];
		}
		else
		{
			img.src = getPreloadImage('sectionShowIcon');
			cat.style.display = 'none';
			hideSections[img.id] = 1;
		}

		if (!skipcookie)
		{
			var temp = [];
			for (var category in hideSections)
				if (hideSections.hasOwnProperty(category))
					temp.push(category);

			setCookie('klipklapcookie', temp.join(','));
		}
	}
}

// AJAX
function getXmlHttpUrl(application, type, action, queryStringExtras)
{
	var xmlHttpUrl = window.xmlHttpUrl || window.BaseURL + 'xmlhttp/xmlHttp.php';
	var url = xmlHttpUrl + '?application='	+ application
						+ (type ? '&type=' + type : '')
						+ (action ? '&action=' + action : '')
						+ (queryStringExtras ? '&' + queryStringExtras : '');

	return url;
}

if (!window.XMLHttpRequest)
{
	window.XMLHttpRequest = function()
	{
		// http://blogs.msdn.com/xmlteam/archive/2006/10/23/using-the-right-version-of-msxml-in-internet-explorer.aspx
		var types = [
			'MSXML2.XMLHTTP.6.0',
			'MSXML2.XMLHTTP.3.0'
		];

		for (var i = 0; i < types.length; i++)
		{
			try
			{
				return new ActiveXObject(types[i]);
			}
			catch(e) {}
		}

		return undefined;
	}
}

function Ajax()
{
	// this is a singleton implementation :)

	if (this == window)
		return new Ajax();

	if (Ajax.instance)
		return Ajax.instance;

	// default options
	this.options = {
		type:		'xml',
		method:		'GET',
		async:		false,
		contentType:	'application/x-www-form-urlencoded',
		encoding:	'ISO-8859-15',
		nocache:	false,
		handler:	null,
		appendSid:	false
	};
	this.requestObjectSync = null;
	this.requestObjectAsync = null;
	this.queue = [];
	this.busy = false;

	return (Ajax.instance = this);
}

Object.extend(Ajax.prototype,
{
	getRequestObject: function(async, forceNewObject)
	{
		var requestObjectName = 'requestObject' + (async ? 'Async' : 'Sync');
		if (this[requestObjectName] === null || forceNewObject)
			this[requestObjectName] = new XMLHttpRequest();

		return this[requestObjectName];
	},
	sendRequest: function(url, options, data)
	{
		options = Object.extend(options || {}, this.options);

		var requestObject = this.getRequestObject(options['async'], is.opera && options['async']);

		if (requestObject)
		{
			if (options['async'] && this.busy)
			{
				this.addToQueue(url, options, data);
			}
			else
			{
				if (options['async'])
					this.busy = true;

				url += appendURLParms(url, { output: options['type'].toLowerCase() });

				if (!options['headers'])
					options['headers'] = {};

				if (options['method'].toUpperCase() == 'POST')
				{
					/* Force "Connection: close" for older Mozilla browsers to work
					 * around a bug where XMLHttpRequest sends an incorrect
					 * Content-length header. See Mozilla Bugzilla #246651.
					*/
					if (requestObject.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
						options['headers']['Connection'] = 'close';

					if (!('Content-Type' in options['headers']))
						options['headers']['Content-Type'] = options.contentType +
							(options.encoding ? '; charset=' + options.encoding : '');

					if (data == undefined) data = '';
					if (options['appendSid'])
						data += appendDataParms(data, { sid: getSessionId() });
				}
				else
				{
					options['method'] = 'GET';
					data = null;

					if (options['nocache'])
					{
						options['headers']['Cache-Control'] = 'no-cache';
						url += appendURLParms(url, { nocache: new Date().getTime() });
					}
				}

				requestObject.open(options['method'].toUpperCase(), url, !!options['async']);

				for (var header in options['headers'])
				{
					if (options['headers'].hasOwnProperty(header))
					{
						requestObject.setRequestHeader(header, options['headers'][header]);
					}
				}

				if (options['async'])
				{
					requestObject.onreadystatechange = this.defaultHandler.bind(this, options);
					requestObject.send(data);
				}
				else
				{
					requestObject.send(data);
					return this.defaultHandler(options);
				}
			}

			return true;
		}

		return false;
	},
	addToQueue: function(url, options, data)
	{
		this.queue.push({url: url, options: options, data: data});
		this.checkQueue();
	},
	checkQueue: function()
	{
		if (!this.busy && this.queue.length)
		{
			var request = this.queue.shift();
			this.sendRequest(request['url'], request['options'], request['data']);
		}
	},
	defaultHandler: function(options)
	{
		var result = null;
		var requestObject = this.getRequestObject(options['async']);

		if (requestObject.readyState == 4)
		{
			if (!requestObject.status || (requestObject.status >= 200 && requestObject.status < 400))
			{
				switch (options['type'].toLowerCase())
				{
					case 'json':
						result = this.parseJSON(requestObject.responseText);
						break;
					case 'xml':
						result = this.validateXML(requestObject);
						break;
					case 'text':
					default:
						result = requestObject.responseText;
				}

				if (options['handler'])
					result = options['handler'](result);
			}

			if (options['async'])
			{
				requestObject.onreadystatechange = function() {}
				this.busy = false;
				this.checkQueue();
			}
		}

		return result;
	},
	validateXML: function(requestObject)
	{
		var xmlDoc = requestObject.responseXML, documentElement = null;

		if (xmlDoc)
			try { documentElement = xmlDoc.documentElement; } catch(e) {}

		if (!documentElement && requestObject.responseText && window.DOMParser)
		{
			var parser = new DOMParser();
			xmlDoc = parser.parseFromString(requestObject.responseText, 'text/xml');
			documentElement = xmlDoc.documentElement;
		}

		if (xmlDoc)
		{
			if (documentElement)
			{
				if (documentElement.tagName == 'parsererror' || documentElement.tagName == 'error')
				{
					if (debug) alert('Error in XML respons:\n' + documentElement.firstChild.nodeValue);
				}
				else
				{
					return xmlDoc;
				}
			}
			else
			{
				if (xmlDoc.parseError)
				{
					if (debug) alert('Error parsing XML respons:\n' + xmlDoc.parseError.reason + '\n' + url);
				}
				else
				{
					if (debug) alert('No valid data in XML respons');
				}
			}
		}
		else
		{
			if (debug) alert('The responseXML object was empty');
		}

		return false;
	},
	parseJSON: function(string)
	{
		try
		{
			return /^("(\\.|[^"\\\n\r])*"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+$/.test(string)
				&& eval('(' + string + ')');
		}
		catch (e) {}

		if (debug) alert('Error parsing JSON respons: ' + string);

		return false;
	}
});


