function getElementsByClassName(className, nodeName, parentElement, callback)
{
	if (!nodeName)
		nodeName = '*';

	if (!parentElement)
		parentElement = document;

	var results = [], regexp = /[.|+?()[\]]/.test(className), s, i = 0, element;

	if (parentElement.getElementsByClassName && nodeName == '*' && !regexp)
	{
		// convert to static array
		results = Array.slice(parentElement.getElementsByClassName(className), 0);

		if (callback)
			results.forEach(callback);
	}
	else if (document.evaluate && !regexp)
	{
		s = document.evaluate(
			".//" + nodeName + "[contains(concat(' ', @class, ' '), ' " + className + " ')]",
			parentElement,
			null,
			XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
			null
		);

		while ((element = s.snapshotItem(i++)))
		{
			results.push(element);
			if (callback)
				callback(element);
		}
	}
	else
	{
		s = parentElement.getElementsByTagName(nodeName);
		var re = getClassRegExp(className), elementClassName;

		while ((element = s[i++]))
		{
			elementClassName = element.className;
			if (	elementClassName.length &&
				(	elementClassName == className ||
					re.test(elementClassName)
				)
			)
			{
				results.push(element);
				if (callback)
					callback(element);
			}
		}
	}

	return results;
}

function ellipsis(root)
{
	if (ellipsis.__native == undefined)
	{
		ellipsis.__native = true;
		if (document.defaultView && document.defaultView.getComputedStyle)
		{
			var foo = document.createElement('div');
			foo.style.cssText = 'text-overflow:ellipsis;-o-text-overflow:ellipsis';
			if (!foo.style.cssText)
				ellipsis.__native = false;
		}
	}

	if (!ellipsis.__native)
	{
		if (!root)
			getElementsByClassName('ellipsis', null, null, ellipsis_do);
		else
			ellipsis_do(root);
	}
}

function ellipsis_do(root)
{
	var ellipsis_word = false;

	// speed things up
	if (root.clientWidth)
	{
		if (root.nodeName != 'TD')
		{
			root.style.width = root.clientWidth + 'px';
			root.style.position = 'fixed';
		}

		var s = [root], e, o, w;

		while ((e = s.pop()))
		{
			do
			{
				switch (e.nodeType)
				{
					case 1:
						if (e.style.display != 'none')
						{
							if (document.defaultView.getComputedStyle(e, null).getPropertyValue('overflow') == 'hidden')
							{
								ellipsis_overflow(e, ellipsis_word);
							}
							else
							{
								if ((o = e.nextSibling)) s.push(o);
								e = e.firstChild;
								break;
							}
						}

					default:
						e = e.nextSibling;
				}
			}
			while (e);
		}

		root.style.position = '';
	}
}

function ellipsis_overflow(p, ellipsis_word)
{
	var w = p.clientWidth;
	if (w && p.scrollWidth > w)
	{
		var s = [p], e, o, q, l;

		while ((e = s.pop()))
		{
			do
			{
				switch (e.nodeType)
				{
					case 1:
						if ((o = e.previousSibling)) s.push(o);
						e = e.lastChild;
						break;

					case 3:
						if (/[^\t\n\r ]/.test(e.data))
						{
							// Take an educated guess on what the length of the text should be
							q = e.nodeValue;
							l = Math.floor((q.length * w) / p.scrollWidth) - 1;
							if (l > 0)
							{
								do
								{
									e.nodeValue = q.substr(0, --l) + '...';
								}
								while (l && (p.scrollWidth > w || (ellipsis_word && e.nodeValue.charAt(l-1) != ' ')));

								if (l > 0) return;
							}

							e.nodeValue = '';
						}

					default:
						e = e.previousSibling;
				}
			}
			while(e);
		}
	}
}

// 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 (getById('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)
	{
		function checkMouseLeaveOrEnter(element, handler)
		{
			return function(e)
			{
				var target = e.relatedTarget || (e.type == 'mouseover' ? e.fromElement : e.toElement);
				while (target)
				{
					if (target == element)
						return;

					target = target.parentNode;
				}

				return handler.call(this, e);
			}
		}

		return function(element, type, handler)
		{
			if (type == 'mouseenter')
			{
				type = 'mouseover';
				handler = checkMouseLeaveOrEnter(element, handler);
			}
			else if (type == 'mouseleave')
			{
				type = 'mouseout';
				handler = checkMouseLeaveOrEnter(element, handler);
			}

			element.addEventListener(type, handler, false);
		}
	}
	else
	{
		function handleEvent(event)
		{
			event = event || fixEvent(window.event);

			// defeat IE resize bug
			if (event.type == 'resize')
			{
				var page = getPageDimensions();
				var currentDimensions = page.innerWidth + 'x' + page.innerHeight;

				if (this.__dimensions && this.__dimensions == currentDimensions)
					return true;

				this.__dimensions = currentDimensions;
			}

			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);

			// cleanup id-cache
			if (window.idCache)
				window.idCache = null;
		}

		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];
		}
	}
}();

