/*
	This file contains cross-browser methods for event handling and DOM operations.
	All core classes (those who can be instantiated through the new constructor) can be extended.
	So can the DOM Level 2 interfaces.
	Also the Window class and the Document class can be extended.
	In client-side JavaScript the predefined objects window and document are instances of the Window
	and Document classes respectively.
	In all browsers but Internet Explorer all objects associated with a HTML-tag inherits from Object (which is the
	superclass for all classes).
	So does the document object. In Netscape 4 even the window object which is the global object in client-side JavaScript
	inherits from Object.

	Note that you can refer to any instance method of the Object class in a static way (as if it was a class method).
	This only applies for the Object class, not any other class.
	Like this:
	Object.prototype.aMethod = function() {
		// code goes here
	};
	alert(typeof Object.aMethod);
	You would expect the above alert to return 'undefined', because the method is referred as if
	it was a static (class) method.	And so it would for any other class than the Object class.
	This is due to the prototype-based inheritance in JavaScript and the fact that the Object class is the super
	class for any class and that the prototype object of any class is an instance of the class itself.
	If the class was say MyObject instead of Object, the alert would show 'undefined' as expected.	
*/
/*
	The idea with the addEventHandler method is to combine the different event models supported by the different browsers.
	In DOM Level 2 the event model combines and enhances the ideas behind IE event model and the Netscape 4 event model.
	First the event trickles down the document tree - which consist of the window object, the document object and any
	object associated to a HTML tag in the body part of the HTML document - to the target object.
	If any of the ancestors of the target has registered a capturing event handler, the handler is fired and the event is passed
	to the next registered capturing event handler (if any). Then the targets event handler is fired and the event is
	bubbling up the document tree again. Not all types of events bubbles up again though.
	In DOM Level 2 every object has a method called addEventListener which takes 3 arguments.
	First a string specifying the type of event (click, load, mousedown, keypress etc.), then the handler (function)
	to be fired when the event occurs and finally a boolean indicating whether or not the event should be
	captured by the handler.
	The addEventHandler method takes 4 arguments. The first is the object on which the event should be registered.

	To capture the event only makes sense for the window and the document object and any layer object in Netscape 4.
	A layer is a bit like an IFRAME element and a bit like a DIV element.
	The event model in Netscape 4 is called the trickle-down model because of the capture event mechanism.
	In DOM Level 2 the event is automatically routed to the next interested listener in the capturing phase.
	This is not the case in Netscape 4.	You'll have to use the routeEvent method or the handleEvent method
	of either the window, document or layer object in your event handler.
	If you're assigning onload or onunload event handlers for a window/frame in Netscape 4 every possible Layer object
	in the window/frame will also trigger the assigned event handler.
	That is probably not what you want, so to prevent the event from trickling down the DOM tree, you specify the doCapture argument as true.
	
	If you need to capture an event in DOM Level 2 you should use the addEventListener method instead of this addEventHandler method.
	The doCapture argument is only of use for a Netscape 4 browser.
	Note that if you register several handlers for the same event on an object via the addEventHandler method
	the order in which the handlers are executed is preserved. In addition the return value from the first assigned
	handler is returned (which for example is important for the keypress event).
	It seems that the addEventListener method does not return the return value of the specified handler.
	For example letting the handler return false on the keypress event of an INPUT element does not cancel the pressing of the key.
	You'll have to call the preventDefault method of the event object to obtain this functionality.
*/

// static helping functions
/*
Note that:
(obj != null)
is equivalent to:
(obj != undefined && obj !== null)
*/
function isDefined(obj, mayBeNull) {
	// IE has introduced the value 'unknown' for the typeof operator		
	if (typeof mayBeNull != "boolean" || !mayBeNull) {
		return (typeof obj != "undefined" && typeof obj != "unknown" && obj !== null);
	}
	return (typeof obj != "undefined" && typeof obj != "unknown");
}

Object.getObjectById = function(id, arr, counter) {
	if (!Object.isValidId(id)) {
		return null;
	}
	if (arr == null) {
		return null;
	}
	if (typeof counter != "number" || isNaN(counter)) {
		counter = 0;
	}
	var obj = null;
	while (obj == null && counter < arr.length) {
		if (id == arr[counter].id) {
			obj = arr[counter];
		}
		else if (arr[counter].children != null && !isNaN(parseInt(arr[counter].children.length, 10))) {
			obj = Object.getObjectById(id, arr[counter].children, 0); // recursion
		}
		counter++;
	}
	return obj;
}

Object.getRootObject = function(obj) {
	if (obj != null) {
		var root = obj;
		while (root.parentObj != null) {
			root = root.parentObj;
		}
		return root;
	}
	return null;
}

Object.isValidId = function(id) {
	if (typeof id != "string" || id == "") {
		alert('Object.js:\nYou must specify the id as a non-empty string!\nThe id:\n' + id + '\nis of type:\n' + (typeof id));
		return false;
	}
	/*	The characters : and . are necessary to support JavaServer Faces and Struts (respectively).
		Furthermore they're legal values according to W3C (see http://www.w3.org/TR/html401/types.html#type-id)
		even though they may cause trouble when the HTML element is referenced in JavaScript.	*/
	var found = new RegExp("[^0-9a-z_\\.\\-\\:]", "gi").exec(id);
	if (found != null) {
		alert('Object.js:\nThe sequence \'' + found[0] + '\' is not allowed in an id!');
		return false;
	}
	else if (id.match(/^[a-z]/i) == null) {
		alert('Object.js:\nThe id must begin with a letter!\nIt begins with \'' + id.charAt(0) + '\'.');
		return false;
	}
	return true;
}

Object.getCaptionObject = function(caption, accesskeyClassName) {
	if (typeof caption == "string" && caption != "") {
		var obj = new Object();
		// find first occurrence of * and make sure it is not the last character and is not preceeded by another *
		var found = new RegExp("\\*[a-zæøå]{1}","i").exec(caption);
		if (found != null && caption.indexOf('**') == -1) {
			obj['accesskey'] = found[0].substring(1);
			obj['accesskeyClassName'] = (typeof accesskeyClassName == "string" && accesskeyClassName != "") ? accesskeyClassName : "Accesskey";
			caption = caption.replace(found[0], '<span class="' + obj['accesskeyClassName'] + '">' + obj['accesskey'] + '<\/span>');
		}
		obj['caption'] = caption;
		return obj;
	}
	return null;
}

// should be called from the remote window
Object.getExecutingWindow = function(name) {
	if (typeof name == "string" && name != "") {
		var p = parent[name];
		if (p != null) {
			return p;
		}
	}
	return window;
}

// should be called from the controlling window
Object.getExecutingWindowName = function() {
	if (typeof window.name == "string" && window.name != "") {
		return window.name;
	}
	return "";
}

/*	Note that if you add a handler to the onload event of the window object, the body tag must NOT contain the onload attribute!
	Otherwise the onload attribute cancels the window.onload assignment. The same applies for the onunload event.
	Note that each registered handler will be passed the event object and an object of your choice (passObj).
	If you do not specify an object, the target object will be passed.
	The latter is very useful, when adding handlers in a loop where the target object is the loop variable.
	See TableSection.initMouseEffects in Table.js for an example.
	If you're doing dragging of any kind (mousemove event), you should use Object.addEventListener to capture
	the event instead of this method. It will perform better (less flickering), especially in IE.	*/
Object.addEventHandler = function(target, eventType, handler, doCapture, passObj) {
	if (target == null || typeof eventType != "string" || eventType == "" || typeof handler != "function") {
		return;
	}
/*	It is necessary in IE6 with SP1 to change the target from self to window.
	This is due to the fact that self == window (by value), but self !== window (by reference).
	What a bug Microsoft! */
	if (target == window) {
		target = window; // if the target reference is not changed from self to window the event will not be fired!
	}
	eventType = eventType.toLowerCase();
	if (eventType.indexOf('on') != 0) {
		eventType = 'on' + eventType;
	}
	if (typeof doCapture != "boolean") {
		doCapture = false;
	}
	/*	Both Opera, Mozilla and Netscape 4 supports the method captureEvents, but it seems
		to have no effect in neither Opera nor Mozilla.	*/
	if (doCapture && target.captureEvents != null && typeof Event != "undefined") {
		var eType = Event[eventType.substr(2).toUpperCase()];
		if (eType != null) {
			target.captureEvents(eType);
		}
	}
	if (target.eventTypes == null) {
		target.eventTypes = {};
	}
	if (target.eventTypes[eventType] == null || target[eventType] == null) {
		target.eventTypes[eventType] = [];
		if (target[eventType] != null) { // do not overwrite the event-handler if already defined.
			target.eventTypes[eventType][target.eventTypes[eventType].length] = { handler: target[eventType], passObj: passObj };
		}
		var targetObj = target; // It is necessary in a KHTML based browser to save target reference in another variable for use in the handler
		target[eventType] = function(eventObj) {
		/*	Even though the event object is global in Opera it is
			also automatically passed to the event handler as an argument. */
			if (eventObj == null) {
				var win = window;
				if (targetObj.parentWindow != null) {
					win = targetObj.parentWindow;
				}
				else if (targetObj.document != null && targetObj.document.parentWindow != null) {
					win = targetObj.document.parentWindow;
				}
				if (win != null && win.event != null) {
					eventObj = win.event;
				}
			}
			var rv, handlerObj, handlers = targetObj.eventTypes[eventType];
			for (var i = 0, l = handlers.length; i < l; i++) {
				handlerObj = handlers[i];
				// use apply to preserve scope for each handler
				if (i == 0) { // return the value of the first handler
					if (typeof Closure == "function") { // Netscape 4 goes here
						//closure = new Closure(handlerObj.handler, targetObj); // this does not work as intended in NS4
						//rv = closure(eventObj, handlerObj.passObj);
						rv = handlerObj.handler(eventObj, handlerObj.passObj); // scope is window (not preserved)
					}
					else if (typeof Function.apply == "function") {
						rv = handlerObj.handler.apply(targetObj, [eventObj, handlerObj.passObj]);
					}
				} else {
					if (typeof Closure == "function") {
						//closure = new Closure(handlerObj.handler, targetObj); // this does not work as intended in NS4
						//closure(eventObj, handlerObj.passObj);
						handlerObj.handler.__parent__ = targetObj;
						handlerObj.handler(eventObj, handlerObj.passObj); // scope is window (not preserved)
					}
					else if (typeof Function.apply == "function") {
						handlerObj.handler.apply(targetObj, [eventObj, handlerObj.passObj]);
					}
				}
			}
			return rv;
		}
	}
	if (!isDefined(passObj)) {
		passObj = target;
	}
	target.eventTypes[eventType][target.eventTypes[eventType].length] = { handler: handler, passObj: passObj };
	// avoid memory leak in IE
	if (window.attachEvent != null && window.addEventListener == null) {
		var win = window;
		if (target.parentWindow != null) {
			win = target.parentWindow;
		}
		else if (target.document != null && target.document.parentWindow != null) {
			win = target.document.parentWindow;
		}
		if (Object.handlers == null) {
			Object.handlers = [];
			// be careful not to create another cyclic reference in IE (anonymous function)
			win.attachEvent("onunload", Object.clearMemory);
		}
		Object.handlers.push({ target: target, eventType: eventType, handler: handler });
	}
	target = null;
}

// avoid memory leak in IE
Object.clearMemory = function() {
	if (Object.handlers != null) {
		var handlerObj;
		for (var i = 0, l = Object.handlers.length; i < l; i++) {
			handlerObj = Object.handlers[i];
			handlerObj.target.detachEvent(handlerObj.eventType, handlerObj.handler);
			Object.handlers[i] = null;
		}
	}
}

Object.removeEventHandler = function(target, eventType, handler, doCapture) {
	if (target == null || typeof eventType != "string" || eventType == "" || typeof handler != "function") {
		return;
	}
	if (target == window) { // see addEventHandler for explanation
		target = window;
	}
	eventType = eventType.toLowerCase();
	if (eventType.indexOf('on') != 0) {
		eventType = 'on' + eventType;
	}
	if (typeof doCapture != "boolean") {
		doCapture = false;
	}
	if (target.eventTypes != null && target.eventTypes[eventType] != null) {
		var handlerObj, handlers = target.eventTypes[eventType], newHandlers = [];
		for (var i = 0, l = handlers.length; i < l; i++) {
			handlerObj = handlers[i];		
			if (handler != handlerObj.handler) {
				newHandlers[newHandlers.length] = handlerObj;
			}
		}
		if (newHandlers.length == 0) {
			if (doCapture && target.releaseEvents != null && typeof Event != "undefined") {
				var eType = Event[eventType.substr(2).toUpperCase()];
				if (eType != null) {
					target.releaseEvents(eType);
				}
			}
			target.eventTypes[eventType] = null;
			target[eventType] = null;
		} else {
			target.eventTypes[eventType] = newHandlers;
		}
	}
}

/*	Cross-browser addEventListener method (uses Window/Document/Element.attachEvent in IE).
	There are some drawbacks when using attachEvent.
	Note that attachEvent may not execute the handlers in the order they were added!
	In theory this goes for addEventListener too. The standards does not require the implementation of this method to
	execute listeners in the order they were added, but Gecko-based browsers and Opera do so.
	Also note that the this keyword in a handler assigned by attachEvent will refer to the
	window object (global object), not the target object!
	To invoke the apply or call method on the handler method inside an anonymous function won't do the trick!
	Also note that attachEvent DOES pass the event object as the first argument to your handler
	as it should according to the W3C DOM Level 2 Event Model.
	But if your target already has an event handler	as an HTML attribute, the return value of this handler is ignored
	when assigning additional handlers with attachEvent. Then you must set the returnValue property of the event object.
	Note that Element.addEventListener honors the original signed return value, but you can overrule it by invoking
	the preventDefault method of the event object.	*/
Object.addEventListener = function(target, eventType, handler, doCapture) {
	if (typeof target == "object" && target != null && typeof eventType == "string" && eventType != "" && typeof handler == "function") {
		if (typeof doCapture != "boolean") {
			doCapture = false;
		}
		eventType = eventType.toLowerCase();
		if (eventType.indexOf("on") == 0) {
			eventType = eventType.substring(2);
		}
		if (target.addEventListener != null) {
			target.addEventListener(eventType, handler, doCapture);
		}
		else if (target.attachEvent != null) { // the event handler is automatically passed the event object as the first argument as it should according to the standard!!
			if (target.setCapture != null && doCapture) {
				target.setCapture();
			}
			target.attachEvent("on" + eventType, handler);
			// avoid memory leak in IE
			target = null;
		} else {
			Object.addEventHandler(target, eventType, handler, doCapture);
		}
	}
}

Object.removeEventListener = function(target, eventType, handler, doCapture) {
	if (typeof target == "object" && target != null && typeof eventType == "string" && eventType != "" && typeof handler == "function") {
		if (typeof doCapture != "boolean") {
			doCapture = false;
		}
		eventType = eventType.toLowerCase();
		if (eventType.indexOf("on") == 0) {
			eventType = eventType.substring(2);
		}
		if (target.removeEventListener != null) {
			target.removeEventListener(eventType, handler, doCapture);
		}
		else if (target.detachEvent != null) {
			if (target.releaseCapture != null && doCapture) {
				target.releaseCapture(); // triggers the onlosecapture event
			}
			target.detachEvent("on" + eventType, handler);
		} else {
			Object.removeEventHandler(target, eventType, handler);
		}
	}
};

if (typeof Event != "function") {
	window["Event"] = function() {};
}

Event.getEventTarget = function(e) {
	var target = null;
	if (e != null) {
		if (e.target != null) {
			target = e.target;
		}
		else if (e.srcElement != null) {
			target = e.srcElement;
		}
		if (target != null) { // fix Safari bug - if target should be a link Safari returns the text node and not the anchor node
			var nodeName = target.nodeName;
			if (typeof nodeName == "string" && nodeName.indexOf("text") > -1 && target.parentNode != null) {
				target = target.parentNode;
			}
		}
	}
	return target;
}

// should be used if the link (A-tag) contains other tags - like a SPAN-tag
Event.getLinkEventTarget = function(e) {
	var target = Event.getEventTarget(e);
	if (target != null) {
		var tagName = target.tagName;
		if (typeof tagName == "string" && tagName.toLowerCase() != "a") {
			target = Element.getAncestorElement(target, "a");
		}
	}
	return target;
}

Event.preventDefault = function(e) {
	if (isDefined(e)) {
		if (e.preventDefault != null) {
			e.preventDefault();
		} else {
			e.returnValue = false;
		}
	}
}

Event.stopEventPropagation = function(e) {
	if (isDefined(e)) {
		if (isDefined(e.stopPropagation)) {
			e.stopPropagation();
		}
		else if (typeof e.cancelBubble == "boolean") {
			e.cancelBubble = true;
		}
	}
}

// Should be MutationEvent and not Event?!?
Event.TYPE_HTML_EVENTS = "HTMLEvents";
Event.TYPE_UI_EVENTS = "UIEvents";
Event.TYPE_MOUSE_EVENTS = "MouseEvents";

Event.syntheticEvents = [Event.TYPE_HTML_EVENTS, Event.TYPE_UI_EVENTS, Event.TYPE_MOUSE_EVENTS]; // legal values - actually other values are legal in Opera and Firefox, but not in Safari and Konqueror

/* Cross-browser method to create a synthetic event.	*/
Event.createEvent = function(eventType, win) {
	if (win == null || win.document == null) {
		win = window;
	}
	var e = null;
	if (typeof eventType != "string" || eventType == "") {
		eventType = Event.TYPE_HTML_EVENTS;
	} else {
		var found = false;
		for (var i = 0, l = Event.syntheticEvents.length; i < l; i++) {
			if (eventType == Event.syntheticEvents[i]) {
				found = true;
				break;
			}
		}
		if (!found) {
			eventType = Event.TYPE_HTML_EVENTS;
		}
	}
	if (document.createEvent != null) {
		e = win.document.createEvent(eventType);
	}
	else if (document.createEventObject != null) {
		e = win.document.createEventObject();
	}
	return e;
}

/* Cross-browser method to initialize a synthetic event (is not necessary in the MS DOM Event Model).	*/
Event.initEvent = function(e, eventType, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget) {
	if (e != null && e.initEvent != null && typeof eventType == "string" && eventType != "") {
		eventType = eventType.toLowerCase();
		if (eventType.indexOf('on') == 0) {
			eventType = eventType.substr(2);
		}
		if (typeof canBubble != "boolean") {
			canBubble = true;
		}
		if (typeof cancelable != "boolean") {
			cancelable = true;
		}
		if (e.initMouseEvent != null) {
			e.initMouseEvent(eventType, canBubble, cancelable, view, detail, screenX, screenY, clientX, clientY, ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
		}
		else if (e.initUIEvent != null) {
			e.initUIEvent(eventType, canBubble, cancelable, view, detail);
		} else {
			e.initEvent(eventType, canBubble, cancelable);
		}
	}
}

/* Cross-browser method to invoke possible event handler for a synthetic event.	*/
Event.dispatchEvent = function(e, eventType, target) {
	if (e != null && typeof eventType == "string" && eventType != "" && target != null) {
		eventType = eventType.toLowerCase();
		if (eventType.indexOf('on') != 0) {
			eventType = 'on' + eventType;
		}
		if (target.dispatchEvent != null) {
			target.dispatchEvent(e);
		}
		else if (target.fireEvent != null) {
			target.fireEvent(eventType, e);
		}
	}
}

/*	The pageX property IS adjusted for possible scrolling of the page, but the property is not defined in IE!
	The clientX property is NOT adjusted for possible scrolling of the page.
	This goes for pageY and clientY as well.	*/
Event.getPageX = function(e) {
	var x = 0;
	if (e != null) {
		if (typeof e.pageX == "number") {
			x = e.pageX;
		}
		else if (typeof e.clientX == "number") {
			x = e.clientX + Window.getScrollX();
		}
	}
	return x;
}

Event.getPageY = function(e) {
	var y = 0;
	if (e != null) {
		if (typeof e.pageY == "number") {
			y = e.pageY;
		}
		else if (typeof e.clientY == "number") {
			y = e.clientY + Window.getScrollY();
		}
	}
	return y;
}

/*
Event.ALT_MASK is 1
Event.CONTROL_MASK is 2
Alt GR is 3 - it is a combination of the ctrl key and the alt key held down simultaneously.
Event.SHIFT_MASK is 4
CTRL + SHIFT is 6		
In IE and Opera the ctrlKey property is true when Alt GR is held down.
*/

Event.isAltDown = function(e) {
	if (e == null && window.event != null) {
		e = window.event;
	}
	if (e != null && e.modifiers != null && typeof Event != "undefined") {
		return (e.modifiers == Event.ALT_MASK);
	}
	return (e != null && typeof e.altKey == "boolean" && e.altKey);
}

// The meta key is the APPLE key in MacOS
Event.isMetaDown = function(e) {
	if (e == null && window.event != null) {
		e = window.event;
	}
	if (e != null && e.modifiers != null && typeof Event != "undefined") {
		return (e.modifiers == Event.META_MASK);
	}
	return (e != null && typeof e.metaKey == "boolean" && e.metaKey);
}

Event.isCtrlDown = function(e) {
	if (e == null && window.event != null) {
		e = window.event;
	}
	if (e != null && e.modifiers != null && typeof Event != "undefined") {
		return (e.modifiers == Event.CONTROL_MASK);
	}
	return (e != null && typeof e.ctrlKey == "boolean" && e.ctrlKey);
}

Event.isShiftDown = function(e) {
	if (e == null && window.event != null) {
		e = window.event;
	}
	if (e != null && e.modifiers != null && typeof Event != "undefined") {
		return (e.modifiers == Event.SHIFT_MASK);
	}
	return (e != null && typeof e.shiftKey == "boolean" && e.shiftKey);
};

if (typeof KeyEvent != "function") {
	window["KeyEvent"] = function() {};
}

/*	The value of the which/keyCode property will not necessarily be the same onkeypress as onkeydown and onkeyup!
	In Mozilla the value of keyCode property is saved in the charCode property (and the value of the keyCode
	property is set to 0), if the event	type is keypress.	*/
KeyEvent.getKeyCode = function(e) {
	if (e == null && window.event != null) {
		e = window.event;
	}
	if (e == null || e.type == null || e.type.indexOf("key") == -1) {
		alert('Object.js:\nThe event object must be of type keypress, keydown or keyup!');
		return 0;
	}
	var which = parseInt(e.which, 10);
	if (isNaN(which)) {
		which = 0;
	}
	var keyCode = parseInt(e.keyCode, 10);
	if (isNaN(keyCode)) {
		keyCode = 0;
	}
	var charCode = parseInt(e.charCode, 10);
	if (isNaN(charCode)) {
		charCode = 0;
	}
	return Math.max(Math.max(which, keyCode), charCode);
}

/*	PrintScreen can only be caught by the keyup event in Firefox. Cannot be caught in Opera and IE.
	NumLock and ScrollLock cannot be caught by the keypress event.	*/
KeyEvent.nonPrintableKeys = {
	8: "backspace", 9: "tab", 13: "return", 19: "pause", 27: "escape", 32: "space",
	33: "pageup", 34: "pagedown", 35: "end", 36: "home", 37: "left", 38: "up",
	39: "right", 40: "down", 44: "printscreen", 45: "insert", 46: "delete",
	144: "numlock", 145: "scrolllock"
}

KeyEvent.functionKeys = {
	112: "f1", 113: "f2", 114: "f3", 115: "f4", 116: "f5", 117: "f6", 118: "f7", // collides with the letters from p to v
	119: "f8", 120: "f9", 121: "f10", 122: "f11", 123: "f12" // collides with the letters from w to z
}

KeyEvent.isPrintableKey = function(e) {
	if (e != null && typeof e.type == "string" && e.type.indexOf("key") > -1) {
		var key = KeyEvent.getKeyCode(e);
		return (!isNaN(key) && !KeyEvent.isFunctionKey(e) && !KeyEvent.isNonPrintableKey(e));
	}
	return false;
}

KeyEvent.isNonPrintableKey = function(e) {
	if (e != null && typeof e.type == "string" && e.type.indexOf("key") > -1) {
		var key = KeyEvent.getKeyCode(e);
		return (!isNaN(key) && KeyEvent.nonPrintableKeys["" + key] != null);
	}
	return false;
}

/*	Should be invoked onkeydown because the key code collides with lowercase characters for the keypress event.	*/
KeyEvent.isFunctionKey = function(e) {
	if (e != null && typeof e.type == "string" && e.type.indexOf("key") > -1) {
		var key = KeyEvent.getKeyCode(e);
		return (!isNaN(key) && KeyEvent.functionKeys["" + key] != null);
	}
	return false;
}

KeyEvent.isLeftArrowKey = function(e) {
	if (e != null && typeof e.type == "string" && e.type.indexOf("key") > -1) {
		var key = KeyEvent.getKeyCode(e);
		return KeyEvent.isLeftArrowKeyCode(key);
	}
	return false;
}

KeyEvent.isLeftArrowKeyCode = function(key) {
	key = parseInt(key, 10);
	return (!isNaN(key) && (key == 37 || key == 63234));
}

KeyEvent.isRightArrowKey = function(e) {
	if (e != null && typeof e.type == "string" && e.type.indexOf("key") > -1) {
		var key = KeyEvent.getKeyCode(e);
		return KeyEvent.isRightArrowKeyCode(key);
	}
	return false;
}

KeyEvent.isRightArrowKeyCode = function(key) {
	key = parseInt(key, 10);
	return (!isNaN(key) && (key == 39 || key == 63235));
}

KeyEvent.isUpArrowKey = function(e) {
	if (e != null && typeof e.type == "string" && e.type.indexOf("key") > -1) {
		var key = KeyEvent.getKeyCode(e);
		return KeyEvent.isUpArrowKeyCode(key);
	}
	return false;
}

KeyEvent.isUpArrowKeyCode = function(key) {
	key = parseInt(key, 10);
	return (!isNaN(key) && (key == 38 || key == 63232));
}

KeyEvent.isDownArrowKey = function(e) {
	if (e != null && typeof e.type == "string" && e.type.indexOf("key") > -1) {
		var key = KeyEvent.getKeyCode(e);
		return KeyEvent.isDownArrowKeyCode(key);
	}
	return false;
}

KeyEvent.isDownArrowKeyCode = function(key) {
	key = parseInt(key, 10);
	return (!isNaN(key) && (key == 40 || key == 63233));
}

KeyEvent.isArrowKey = function(e) {
	if (e != null && typeof e.type == "string" && e.type.indexOf("key") > -1) {
		var key = KeyEvent.getKeyCode(e);
		return KeyEvent.isArrowKeyCode(key);
	}
	return false;
}

KeyEvent.isArrowKeyCode = function(key) {
	key = parseInt(key, 10);
	return (!isNaN(key) && ((key >= 37 && key <= 40) || (key >= 63232 && key <= 63235)));
}

/*	Checks if a keystroke matches the specified regular expression/pattern (matchObj).
	Always allows non-printable keys (see KeyEvent.nonPrintableKeys), function keys,
	the ctrl key, the alt key and the meta key.
	Since key codes differs in keydown and keypress you should invoke this
	method on both keyup and keypress! Like this:
	 onkeydown="return KeyEvent.isValidKey(event, '[0-9]', true);" onkeypress="return KeyEvent.isValidKey(event, '[0-9]', true);"
*/
KeyEvent.isValidKey = function(e, matchObj, doMatch) {
	function isValidKey() {
		if (typeof doMatch != "boolean") {
			doMatch = true;
		}
		var ch = String.fromCharCode(key);
		var found = regExp.exec(ch);
		if (doMatch) {
			return (found != null);
		}
		return (found == null);
	}
	if (e == null && window.event != null) {
		e = window.event;
	}
	if (e == null || e.type == null) {
		throw new Error('Object.js:\nThe first argument must be the event object!');
	}
	var regExp;
	if (typeof matchObj == "string" && matchObj != "") {
		regExp = new RegExp(matchObj);
	}
	else if (matchObj != null && matchObj.constructor == RegExp) {
		regExp = matchObj;
	}
	var key = KeyEvent.getKeyCode(e);
	if (!isNaN(key) && regExp != null) {
		if (e.type == "keydown") {
			if (KeyEvent.isFunctionKey(e) || KeyEvent.isNonPrintableKey(e) || Event.isCtrlDown(e) || Event.isAltDown(e) || Event.isMetaDown(e)) {
				KeyEvent.allowedKey = true;
				return true;
			}
			KeyEvent.allowedKey = false; // reset
			if (Event.isShiftDown(e)) {
				return isValidKey();
			}
		}
		else if (e.type == "keypress") { // the keypress event is not invoked in IE for the CTRL, ALT and SHIFT keys.
			if (KeyEvent.allowedKey) {
				KeyEvent.allowedKey = false;
				return true;
			}
			/*	Function keys collides with some lowercase characters - see the object KeyEvent.functionKeys - so
				we don't check for function keys here. */
			if (KeyEvent.isNonPrintableKey(e) || Event.isCtrlDown(e) || Event.isAltDown(e) || Event.isMetaDown(e)) {
				KeyEvent.allowedKey = true;
				return true;
			}
			// key is printable, now match against regular expression
			return isValidKey();
		}
	}
	return true;
};

if (typeof MouseEvent != "function") {
	window["MouseEvent"] = function() {};
}

/*	In IE and Safari e.button is 1, when left clicking.
	In Firefox and Opera e.button is 1, when middle-clicking (as expected).
	So this method actually returns true, when middle-clicking and in a standard browser.
	Also note that e.button is 2, when right-clicking in IE.
	So e.button is 4, when middle-clicking in IE. Very nice!! ¤%#¤&!# 	*/
MouseEvent.isLeftButtonDown = function(e) {
	return (e != null && (e.button == 0 || e.button == 1));
}

MouseEvent.isRightButtonDown = function(e) {
	return (e != null && e.button == 2);
}

// should be moved to new file Array.js
Array.isArray = function(arr) {
	return (arr != null && arr.constructor == Array);
}

if (Array.prototype.indexOf == null) { // is defined in JavaScript 1.6
	Array.prototype.indexOf = function(target) {
		if (target != null) {
			for (var i = 0, l = this.length; i < l; i++) {
				if (this[i] == target) {
					return i;
				}
			}
		}
		return -1;
	}
}

if (Array.prototype.forEach == null) {
	Array.prototype.forEach = function(iterator) {
		if (typeof iterator == "function") {
			for (var i = 0, l = this.length; i < l; i++) {
				iterator(this[i]);
			}
		}
	}
}

Array.prototype.remove = function(target, compareByRef) {
	if (target != null) {
		if (typeof compareByRef != "boolean") {
			compareByRef = false;
		}
		var element, found;
		for (var i = 0, l = this.length; i < l; i++) {
			found = false;
			element = this[i];
			if (compareByRef) {
				if (element === target) {
					found = true;
				}
			} else {
				if (element == target) {
					found = true;
				}
			}
			if (found) {
				this.splice(i, 1);
				return element;
			}
		}
	}
	return null;
};

if (typeof Window != "function") { // Opera 9.01 goes here to
	window["Window"] = function() {};
}

Window.getScrollX = function(win) {
	if (win == null || win.document == null) {
		win = window;
	}
	var doc = win.document;
	var x = 0;
	if (win.scrollX != null) { // W3C
		x = win.scrollX;
	}
	else if (Document.isStrictMode(doc) && doc.documentElement != null && doc.documentElement.scrollLeft != null) { // IE strict mode
		x = doc.documentElement.scrollLeft;
	}
	else if (doc.body != null && doc.body.scrollLeft != null) { // IE quirks mode
		x = doc.body.scrollLeft; // In strict mode this is 0 in IE
	}
	else if (win.pageXOffset != null) { // Netscape 4
		x = win.pageXOffset;
	}
	return x;
}

Window.getScrollY = function(win) {
	if (win == null || win.document == null) {
		win = window;
	}
	var doc = win.document;
	var y = 0;
	if (win.scrollY != null) { // W3C
		y = win.scrollY;
	}
	else if (Document.isStrictMode(doc) && doc.documentElement != null && doc.documentElement.scrollTop != null) { // IE strict mode
		y = doc.documentElement.scrollTop;
	}
	else if (doc.body != null && doc.body.scrollTop != null) { // IE quirks mode
		y = doc.body.scrollTop; // In strict mode this is 0 in IE
	}
	else if (win.pageYOffset != null) { // Netscape 4
		y = win.pageYOffset;
	}
	return y;
}

Window.getWindowWidth = function(win) {
	if (win == null || win.document == null) {
		win = window;
	}
	var doc = win.document;
	var w = 0;
	if (win.innerWidth != null) {
		w = win.innerWidth;
	}
	else if (Document.isStrictMode(doc) && doc.documentElement != null && doc.documentElement.clientWidth != null) {
		w = doc.documentElement.clientWidth;
	}
	else if (doc.body != null && doc.body.clientWidth != null) {
		w = doc.body.clientWidth;
	}
	return w;
}

Window.getWindowHeight = function(win) {
	if (win == null || win.document == null) {
		win = window;
	}
	var doc = win.document;
	var h = 0;
	if (win.innerHeight != null) {
		h = win.innerHeight;
	}
	else if (Document.isStrictMode(doc) && doc.documentElement != null && doc.documentElement.clientHeight != null) {
		h = doc.documentElement.clientHeight;
	}
	else if (doc.body != null && doc.body.clientHeight != null) {
		h = doc.body.clientHeight;
	}
	return h;
}

// if called from mouseover handler, the handler should return true to suppress browser's own status
Window.setStatus = function(msg) {
	if (document.getElementById != null) { // then the browser understands try - catch (hopefully)
		// be sure there is no newline or carriage return characters in the msg variable!
		window.setTimeout('try{window.status="' + msg + '";}catch(ex){}', 1);
	} else {
		window.setTimeout('window.status="' + msg + '";', 1);
	}
};

if (typeof Document != "function") {
	/*	If this class is called Document and assigned as a property of the window
		object (window["Document"] = function() {}), applets won't be displayed in IE (6 & 7)!?!
		Applies when using the object tag as well as the applet tag.
		(window.Document is the "editable document" in which is called currentDocument in Mozilla)	*/
	Document = function() {};
}

Document.getDocumentWidth = function(doc) {
	if (doc == null) {
		doc = document;
	}
	var w = 0;
	if (Document.isStrictMode(doc) && doc.documentElement != null && doc.documentElement.offsetWidth != null) {
		w = doc.documentElement.offsetWidth;
	}
	else if (doc.body != null && doc.body.offsetWidth) {
		w = doc.body.offsetWidth;
	}
	return w;
}

Document.getDocumentHeight = function(doc) {
	if (doc == null) {
		doc = document;
	}
	var h = 0;
	if (Document.isStrictMode(doc) && doc.documentElement != null && doc.documentElement.offsetHeight != null) {
		h = doc.documentElement.offsetHeight;
	}
	else if (doc.body != null && doc.body.offsetHeight) {
		h = doc.body.offsetHeight;
	}
	return h;
}

/*	The property document.compatMode is 'CSS1Compat' if there is a doctype tag with an url to a dtd.
	It is 'BackCompat' otherwise. Opera and Mozilla also supports this property.	*/
Document.isStrictMode = function(doc) {
	if (doc == null) {
		doc = document;
	}
	return (typeof doc.compatMode == "string" && doc.compatMode == "CSS1Compat");
}

/*	Sets the domain to the parent domain - any subdomain is removed.
	This means that 'www.company.com' becomes 'company.com'.	*/
Document.setParentDomain = function(win) {
	if (win == null || win.document == null) {
		win = window;
	}
	var arr = win.location.hostname.split(".");
	if (arr.length >= 2) { // important to set domain when length is 2, not just > 2 - otherwise access is denied!
		var str = "";
		for (var i = arr.length - 2; i < arr.length; i++) {
			str += "." + arr[i];
		}
		win.document.domain = str.substring(1);
	}
};

/*	The following functions should be methods of the Node interface, but Internet Explorer does
	not support this interface, so the first argument is the Node instance. */
if (typeof Node != "function") {
	window["Node"] = function() {};
};

Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;

/*	XMLSerializer.serializeToString(nodeOrTag) is not good enough.
	It uses upper-case letters for HTML tags and is not supported by IE6.	*/
Node.serialize = function(node) {
	var str = "";
	if (node != null) {
		if (window.ActiveXObject != null && node.nodeType == Node.TEXT_NODE) { // Cannot add a property to a text node in IE
			return node.nodeValue;
		}
		var isHTML = (typeof node.innerHTML == "string");
		var nName = node.nodeName;
		var lName = nName.toLowerCase();
		function isEndTagForbidden() {
			var tagNames = ["link", "meta", "input", "img"];
			if (isHTML) {
				for (var ix = 0, l = tagNames.length; ix < l; ix++) {
					if (lName == tagNames[ix]) {
						return true;
					}
				}
			}
			return false;
		}
		switch (node.nodeType) {
			case Node.ELEMENT_NODE: // NS4 cannot parse this switch, it expects Node to be defined, but says that Node is not defined!?
				str = '<' + ((isHTML) ? lName : nName); // HTML/XHTML should be in lower case
				var attr, startTag, index;
				// IE6 discards the style attribute in attributes. Node.getAttribute("style") equals HTMLElement.style (an object, not a string)
				for (var i = 0; i < node.attributes.length; i++) {
					attr = node.attributes[i];
					str += Node.serialize(attr);
					if (typeof attr.name == "string" && typeof attr.value == "string" && attr.value != "" && attr.value != "null" && attr.value.indexOf("function") != 0) { // this is necessary in IE
						// IE6 adds proprietary attributes for HTMLElements, which we filters out
						if (isHTML && typeof node.outerHTML == "string") {
							index = node.outerHTML.indexOf('>');
							if (index > -1) {
								startTag = node.outerHTML.substring(0, index);
								if (startTag.indexOf(attr.name.toLowerCase()) == -1) {
									continue;
								}
							}
						}
						str += ' ' + ((isHTML) ? attr.name.toLowerCase() : attr.name) + '="' + attr.value + '"';
					}
				}
				var style = (isHTML) ? node.getAttribute("style") : null;
				if (style != null && typeof style.cssText == "string" && style.cssText != "") { // IE6 goes here, not Opera, nor Firefox. Note that IE and Opera may convert the CSS property values specified in the style attribute.
					str += ' style="' + style.cssText.toLowerCase() + '"';
				}
				if (isEndTagForbidden(lName)) {
					str += ' \/>';
				} else {
					str += '>';
					for (var j = 0; j < node.childNodes.length; j++) {
						str += Node.serialize(node.childNodes[j]);
					}				
					str += '<\/' + ((isHTML) ? lName : nName) + '>';
				}
				break;
			case Node.ATTRIBUTE_NODE:
				// this case is handled by the element case since the output depends on the parent element node
				break;
			case Node.TEXT_NODE:
				str = node.nodeValue;
				break;
			case Node.CDATA_SECTION_NODE:
				str = '<![CDATA[' + node.nodeValue + ']]>';
				break;
			case Node.ENTITY_REFERENCE_NODE:
				break;
			case Node.ENTITY_NODE:
				break;
			case Node.PROCESSING_INSTRUCTION_NODE:
				// Only IE6 seems to go here, but the nodeName 'xml' is not included in nodeValue. Furthermore this type of node is encountered AFTER the document node!?
				str = '<?xml ' + node.nodeValue + '?>';
				break;
			case Node.COMMENT_NODE:
				if (node.nodeValue.indexOf("CTYPE") == 0) { // IE6 seems to consider <!xx xx> as a HTML comment, where x can be (almost) any character
					str += '<!DO' + node.nodeValue + 'd">';
				} else {
					str = '<!--' + node.nodeValue + '-->';
				}
				break;
			case Node.DOCUMENT_NODE:
				if (node.documentElement != null && node.documentElement.nodeName.toLowerCase() == "html") {
					if (node.doctype != null) {
						str += '<!DOCTYPE ' + node.doctype.name + ' PUBLIC "' + node.doctype.publicId + '" "' + node.doctype.systemId + '">';
					}
				}
				else if (window.XMLSerializer != null && !isHTML) {
					return new XMLSerializer().serializeToString(node);
				}
				for (var n = 0; n < node.childNodes.length; n++) {
					str += Node.serialize(node.childNodes[n]);
				}
				break;
			case Node.DOCUMENT_TYPE_NODE:
				// Opera 9.02 does not support this node type, but it does support the doctype property of the document node, so we handle it there for every browser
				//str = '<!DOCTYPE ' + node.name + ' PUBLIC "' + node.publicId + '" "' + node.systemId + '">';
				break;
			case Node.DOCUMENT_FRAGMENT_NODE:
				for (var n = 0; n < node.childNodes.length; n++) {
					str += Node.serialize(node.childNodes[n]);
				}				
				break;
			case Node.NOTATION_NODE:
				break;
			default:
				break;
		}
	}
	return str;
}

Node.selectNodes = function(xpathExp, node, namespaceResolver) {
	if (node != null && typeof xpathExp == "string") {
		if (typeof XPathResult == "function") { // Opera 9.01 does not support XPathEvaluator, so we use document.evaluate and document.createNSResolver
			// Note that Opera 9 has native methods selectNodes and selectSingleNode!
			var doc = (node.ownerDocument != null) ? node.ownerDocument : node;
			if (doc != null && typeof doc.createNSResolver == "function") {
				var nsResolver = doc.createNSResolver(doc.documentElement);
				var result = doc.evaluate(xpathExp, node, nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
				var nodes = [];
				if (result != null) {
					var element;
					while ((element = result.iterateNext()) != null) {
						nodes.push(element);
					}
				}
				return nodes;
			}
		}
		else if (window.ActiveXObject != null) {
			node.ownerDocument.setProperty('SelectionLanguage', 'XPath');
			if (namespaceResolver != null && typeof namespaceResolver == "object") {
				var ns = '';
				for (var p in namespaceResolver) {
					if (typeof namespaceResolver[p] == "string") {
						ns += ' xmlns:' + p + '="' + namespaceResolver[p] + '"';
					}
				}
				ns = ns.substring(1);
				node.ownerDocument.setProperty('SelectionNamespaces', ns);
			} else {
				node.ownerDocument.setProperty('SelectionNamespaces', 'xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"');
			}
			return node.selectNodes(xpathExp);
		}
	}
	return [];
}

Node.selectSingleNode = function(xpathExp, node, namespaceResolver) {
	if (node != null && typeof xpathExp == "string") {
		if (typeof XPathResult == "function") { // Opera 9.01 does not support XPathEvaluator, so we use document.evaluate and document.createNSResolver
			var doc = (node.ownerDocument != null) ? node.ownerDocument : node;
			if (doc != null && typeof doc.createNSResolver == "function") {
				var nsResolver = doc.createNSResolver(doc.documentElement);
				var result = doc.evaluate(xpathExp, node, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
				if (result != null) {
					return result.singleNodeValue;
				}
			}
		}
		else if (window.ActiveXObject != null) {
			node.ownerDocument.setProperty('SelectionLanguage', 'XPath');
			if (namespaceResolver != null && typeof namespaceResolver == "object") {
				var ns = '';
				for (var p in namespaceResolver) {
					if (typeof namespaceResolver[p] == "string") {
						ns += ' xmlns:' + p + '="' + namespaceResolver[p] + '"';
					}
				}
				ns = ns.substring(1);
				node.ownerDocument.setProperty('SelectionNamespaces', ns);
			} else {
				node.ownerDocument.setProperty('SelectionNamespaces', 'xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"');
			}
			return node.selectSingleNode(xpathExp);
		}
	}
	return null;
};

if (typeof NodeFilter != "function") {
	window["NodeFilter"] = function() {};
};

NodeFilter.FILTER_ACCEPT = 1;
NodeFilter.FILTER_REJECT = 2;
NodeFilter.FILTER_SKIP = 3;
NodeFilter.SHOW_ALL = -1;
NodeFilter.SHOW_ELEMENT = 1;
NodeFilter.SHOW_ATTRIBUTE = 2;
NodeFilter.SHOW_TEXT = 4;
NodeFilter.SHOW_CDATA_SECTION = 8;
NodeFilter.SHOW_ENTITY_REFERENCE = 16;
NodeFilter.SHOW_ENTITY = 32;
NodeFilter.SHOW_PROCESSING_INSTRUCTION = 64;
NodeFilter.SHOW_COMMENT = 128;
NodeFilter.SHOW_DOCUMENT = 256;
NodeFilter.SHOW_DOCUMENT_TYPE = 512;
NodeFilter.SHOW_DOCUMENT_FRAGMENT = 1024;
NodeFilter.SHOW_NOTATION = 2048;

if (typeof Element != "function") {
	window["Element"] = function() {};
}

Element.matchesTagNames = function(element, tagNames) {
	for (var i = 0; i < tagNames.length; i++) {
		if (element.tagName.toLowerCase() == tagNames[i].toLowerCase()) {
			return true;
		}
	}
	return false;
}

Element.getAncestorElement = function(element, tagName) {
	if (element != null) {
		var tagNames = (typeof tagName == "string") ? tagName.split('|') : null;
		if (tagNames != null) {
			while (element != null && element.tagName != null && !Element.matchesTagNames(element, tagNames)) {
				element = element.parentNode;
			}
			if (element != null && element.tagName != null && Element.matchesTagNames(element, tagNames)) {
				return element;
			}
		}
	}
	return null;
};

/*	The following functions should be methods of the HTMLElement interface, but Internet Explorer does
	not support this interface, so the first argument is the HTMLElement instance. */
if (typeof HTMLElement != "function") {
	window["HTMLElement"] = function() {};
}

/*	In Safari 1.3 and 2.0.4 the computed style object is null, if display is 'none' for the element.
	To fix that the display property is temporarily set to 'block' and changed back.
	But this temporarily change may cause undesired effects and can therefore be controlled through the
	argument autoCorrect. Default value is true.	*/
HTMLElement.getComputedStyle = function(element, pseudo, win, autoCorrect) {
	if (element != null) {
		if (win == null || win.document == null) {
			win = window;
		}
		if (win.getComputedStyle != null) {
			return win.getComputedStyle(element, pseudo);
		}
		else if (win.document.defaultView != null && win.document.defaultView.getComputedStyle != null) { // Safari goes here
			var style = win.document.defaultView.getComputedStyle(element, pseudo);
			if (style == null) { // Safari goes here
				if (typeof autoCorrect != "boolean") {
					autoCorrect = true;
				}
				if (autoCorrect) {
					var oldDisplay = element.style.display;
					element.style.display = 'block';
					style = win.document.defaultView.getComputedStyle(element, pseudo);
					element.style.display = oldDisplay;
				}
			}
			return style;
		}
		else if (element.currentStyle != null) { // IE
			return element.currentStyle;
		}
	}
	return null;
}

HTMLElement.parseIntCSSProperty = function(element, propertyName) {
	if (element != null && element.style != null && typeof propertyName == "string" && propertyName != "") {
		var result = parseInt(element.style[propertyName], 10);
		if (isNaN(result)) {
			var style = HTMLElement.getComputedStyle(element);
			if (style != null) {
				result = parseInt(style[propertyName], 10);
			}
		}
		return result;
	}
	return NaN;
}

/*	The useOffset argument forces use of the offsetWidth. See http://developer.mozilla.org/en/docs/DOM:element.offsetWidth.
	This is useful because some browsers - like Konqueror - subtracts the width of a possible scrollbar in the width property of the computed style object. Must be a bug?! Check Safari!
	This may not be desirable - for instance when resizing in DWindow.js. Furthermore Opera 9.1 is buggy when calculating width from the computed style object.
	It seems that any border widths are added.	*/
HTMLElement.getWidth = function(element, useOffset) {
	if (element != null) {
		var w = (typeof useOffset == "boolean" && useOffset) ? NaN : HTMLElement.parseIntCSSProperty(element, "width");
		if (isNaN(w)) {
			var offsetWidth = parseInt(element.offsetWidth, 10);
			if (!isNaN(offsetWidth)) {
				w = offsetWidth;
				var blw = HTMLElement.parseIntCSSProperty(element, "borderLeftWidth");
				w -= (isNaN(blw)) ? 0 : blw;
				var brw = HTMLElement.parseIntCSSProperty(element, "borderRightWidth");
				w -= (isNaN(brw)) ? 0 : brw;
				var pl = HTMLElement.parseIntCSSProperty(element, "paddingLeft");
				w -= (isNaN(pl)) ? 0 : pl;
				var pr = HTMLElement.parseIntCSSProperty(element, "paddingRight");
				w -= (isNaN(pr)) ? 0 : pr;
			}
		}
		if (w >= 0) {
			return w;
		}
	}
	return NaN;
}

/*	See HTMLElement.getWidth	*/
HTMLElement.getHeight = function(element, useOffset) {
	if (element != null) {
		var h = (typeof useOffset == "boolean" && useOffset) ? NaN : HTMLElement.parseIntCSSProperty(element, "height");
		if (isNaN(h)) {
			var offsetHeight = parseInt(element.offsetHeight, 10);
			if (!isNaN(offsetHeight)) {
				h = offsetHeight;
				var btw = HTMLElement.parseIntCSSProperty(element, "borderTopWidth");
				h -= (isNaN(btw)) ? 0 : btw;
				var bbw = HTMLElement.parseIntCSSProperty(element, "borderBottomWidth");
				h -= (isNaN(bbw)) ? 0 : bbw;
				var pt = HTMLElement.parseIntCSSProperty(element, "paddingTop");
				h -= (isNaN(pt)) ? 0 : pt;
				var pb = HTMLElement.parseIntCSSProperty(element, "paddingBottom");
				h -= (isNaN(pb)) ? 0 : pb;
			}
		}
		if (h >= 0) {
			return h;
		}
	}
	return NaN;
}

/* 	To use the pageX/clientX property of the event object does not give the same result as this recursive calculation.	*/
HTMLElement.getOffsetX = function(element, x) {
	if (isDefined(element)) {
		if (typeof x != "number" || isNaN(x)) {
			x = 0;
		}
		if (typeof element.offsetLeft == "number") {
			x += element.offsetLeft;
			if (isDefined(document.body) && element != document.body && isDefined(element.offsetParent)) {
				x = HTMLElement.getOffsetX(element.offsetParent, x);
			}
		}
		return x;
	}
	return 0;
}

/* 	To use the pageY/clientY property of the event object does not give the same result as this recursive calculation.	*/
HTMLElement.getOffsetY = function(element, y) {
	if (isDefined(element)) {	
		if (typeof y != "number" || isNaN(y)) {
			y = 0;
		}
		if (typeof element.offsetTop == "number") {	
			y += element.offsetTop;
			if (isDefined(document.body) && element != document.body && isDefined(element.offsetParent)) {
				y = HTMLElement.getOffsetY(element.offsetParent, y);
			}
		}
		return y;
	}
	return 0;
}

HTMLElement.setClassName = function(element, newClassName) {
	if (element != null) {
		if (typeof element.className == "string" && typeof newClassName == "string" && newClassName != "") {
			if (element.className.indexOf(newClassName) == -1) {
				element.className = newClassName;
			}
		}
	}
}

HTMLElement.addClassName = function(element, newClassName) {
	if (element != null) {
		if (typeof element.className == "string" && typeof newClassName == "string" && newClassName != "") {
			if (element.className.indexOf(newClassName) == -1) {
				if (element.className != "") {
					element.className += " " + newClassName;
				} else {
					element.className = newClassName;
				}
			}
		}
	}
}

/*	oldClassName must be a non-empty string */
HTMLElement.replaceClassName = function(element, oldClassName, newClassName, appendIfNotFound) {
	if (element != null) {
		if (typeof element.className == "string" && typeof oldClassName == "string" && oldClassName != "" && typeof newClassName == "string") {
			if (element.className.indexOf(oldClassName) > -1) {
				element.className = element.className.replace(oldClassName, newClassName);
				if (newClassName == "") {
					element.className = element.className.replace(/\s$/,""); // trailing
					element.className = element.className.replace(/^\s/,""); // leading
				}
			}
			else if (typeof appendIfNotFound == "boolean" && appendIfNotFound) {
				HTMLElement.addClassName(element, newClassName);
			}
		}
	}
}
