// include ticker
var XPS_NUM_INCLUDES;
XPS_NUM_INCLUDES = XPS_NUM_INCLUDES - 1;

var XPS_LIB;
var XPS_DEBUG;
var XPS_INCLUDE_PATH_PUBLIC;
if (!XPS_INCLUDE_PATH_PUBLIC) var XPS_INCLUDE_PATH_PUBLIC = 'xps/';
if (!XPS_DEBUG) var XPS_DEBUG = false;
if (!XPS_LAYOUT_THEME) var XPS_LAYOUT_THEME = 'default';

// =============================================================================
// [ Library Functions ]
// =============================================================================
/**
* Dynamically loads a JS file.
*/
if (!window.XPS_include) {
	window.XPS_include = function(sources) {
		// cast to array
		if (sources.constructor == String)
			sources = Array(sources);
		
		// on first entry attach onload check for includes
		//if (XPS_DEBUG && !XPS_NUM_INCLUDES) {
		//	XPS_addEvent(window, 'load', _XPS_callBackInclude);
		//}
		
		// total number of includes
		XPS_NUM_INCLUDES += sources.length;
		
		// don't use the HEAD object due to readyState problems at the load
		// when using the HEAD object the script may be loaded during of after
		// the BODY object has been loaded
		
		for (var src in sources) {
			if (src in Array.prototype) continue;
			// append script element
			document.write('<script type="text/javascript" src="'+sources[src]+'"></script>');
		}
	}
}

/**
* Callback function for the XPS_include() function.
*/
if (!window._XPS_callBackInclude) {
	window._XPS_callBackInclude = function(docloaded) {
		if (XPS_NUM_INCLUDES != 0)
			_XPS_raise("one or more client side documents have not been loaded (" + XPS_NUM_INCLUDES + ")");
	}
}

/**
* Dynamically loads a style sheet.
*/
if (!window.XPS_loadStyleSheet) {
	window.XPS_loadStyleSheet = function(file) {
		document.write('<link href="'+file+'" rel="stylesheet" type="text/css">');
	}
}

if (!XPS_LIB) {
var includes = new Array();
	includes[0] = XPS_INCLUDE_PATH_PUBLIC + 'inc/tab/tab.lib.js';
	includes[1] = XPS_INCLUDE_PATH_PUBLIC + 'inc/xhr/xhr.lib.js';
	includes[2] = XPS_INCLUDE_PATH_PUBLIC + 'inc/xjmenu/xjmenu.lib.js';
	includes[3] = XPS_INCLUDE_PATH_PUBLIC + 'inc/xjmenu/xjmenuitem.inc.js';
	
	includes[4] = XPS_INCLUDE_PATH_PUBLIC + 'inc/js/template.inc.js';
	
	includes[5] = XPS_INCLUDE_PATH_PUBLIC + 'inc/js/xpsobject.inc.js';
	includes[6] = XPS_INCLUDE_PATH_PUBLIC + 'inc/js/xpselement.inc.js';
	includes[7] = XPS_INCLUDE_PATH_PUBLIC + 'inc/js/xpstextelement.inc.js';
	includes[8] = XPS_INCLUDE_PATH_PUBLIC + 'inc/js/xpsnumberelement.inc.js';
	includes[9] = XPS_INCLUDE_PATH_PUBLIC + 'inc/js/xpsdateelement.inc.js';
	includes[10] = XPS_INCLUDE_PATH_PUBLIC + 'inc/js/xpsselectelement.inc.js';
	includes[11] = XPS_INCLUDE_PATH_PUBLIC + 'inc/js/xpscheckboxelement.inc.js';
	
	XPS_include(includes);
	
	XPS_loadStyleSheet(XPS_INCLUDE_PATH_PUBLIC + 'inc/css/xpscore.css');
	XPS_loadStyleSheet(XPS_INCLUDE_PATH_PUBLIC + 'inc/css/xps.css');
}

// =============================================================================
// [ XPS ]
// =============================================================================
var xps = new Array();

// browser check
ua = navigator.userAgent;
xps['browser'] = new Array();
xps['browser']['ie'] = (navigator.appName == "Microsoft Internet Explorer");
xps['browser']['ie5'] = xps['browser']['ie'] && (ua.indexOf('MSIE 5') != -1);
xps['browser']['ie5_0'] = xps['browser']['ie'] && (ua.indexOf('MSIE 5.0') != -1);
xps['browser']['ie6'] = xps['browser']['ie'] && (ua.indexOf('MSIE 6') != -1);
xps['browser']['ie7'] = xps['browser']['ie'] && (ua.indexOf('MSIE 7') != -1);
xps['browser']['ie8'] = xps['browser']['ie'] && (ua.indexOf('MSIE 8') != -1);
xps['browser']['gecko'] = ua.indexOf('Gecko') != -1;
xps['browser']['safari'] = ua.indexOf('Safari') != -1;
xps['browser']['opera'] = ua.indexOf('Opera') != -1;
xps['browser']['mac'] = ua.indexOf('Mac') != -1;
xps['browser']['ns7'] = ua.indexOf('Netscape/7') != -1;
xps['browser']['ns71'] = ua.indexOf('Netscape/7.1') != -1;

// fake MSIE on Opera and if Opera fakes IE, Gecko or Safari cancel those
if (xps['browser']['opera']) {
	xps['browser']['ie'] = true;
	xps['browser']['gecko'] = false;
	xps['browser']['safari'] = false;
}
xps['browser']['iereal'] = xps['browser']['ie'] && !xps['browser']['opera'];
xps['browser']['select_by_windows_shell'] = xps['browser']['ie'] && xps['browser']['ie6'];

// IE settings
xps['ie'] = new Array();
xps['ie']['use_floating_blocks'] = true;

// events
xps['events'] = new Array();
xps['events']['dom_events'] = !!(window.addEventListener);
xps['events']['ie_events'] = !!(window.attachEvent);

// general settings
xps['register'] = new Array();
xps['register']['strip_white_spaces'] = true;

// xhr settings
xps['xhr'] = new Array();
xps['xhr']['asynchronous'] = true;
xps['xhr']['syncronize'] = {};

// onload statements
xps['onload_statements'] = [];

// enable 3rd parties libraries
xps['libraries'] = new Array();
xps['libraries']['prototype'] = true;

// menu
xps['xjmenus'] = {};

// classes
xps['objects'] = {};

if (!window.Prototype) {
	_XPS_raise('XPS requires the Prototype libary. Please include the Prototype library before loading the XPS library');
}

// =============================================================================
// [ Prototype ]
// =============================================================================
Object.extend(
		Element.Methods, { 
			updateXPS: function(element, content) { element.update(content); window.setTimeout('XPS_executeOnLoadStatements()', 10); },
			observeXPS: function(element, eventName, handler) { return(XPS_addEvent(element, eventName, handler)); },
			fireXPS: function(element, eventName, memo, bubble) { return(XPS_fireEvent(element, eventName)); }
		}
	)
;
Element.addMethods();

// recursive object extend
Object.extend = function(destination, source, recursive) {
	if (typeof(source) != "object") return source;
	if (!destination) destination = (source instanceof Array) ? [] : {};
	
	for (var property in source)
		destination[property] = (recursive !== true || typeof(source[property]) != "object" || source instanceof Array) ? source[property] : Object.extend(destination[property], source[property], recursive);
	return destination;
};

// =============================================================================
// [ Constants ]
// =============================================================================
/// @todo the variables should be deprecated
var ie = /MSIE/.test(navigator.userAgent);
var moz = !ie && navigator.product == "Gecko";

var DOM_EVENTS = !!(window.addEventListener);
var IE_EVENTS = !!(window.attachEvent);

var BIT1 = Math.pow(2,0);
var BIT2 = Math.pow(2,1);
var BIT3 = Math.pow(2,2);
var BIT4 = Math.pow(2,3);
var BIT5 = Math.pow(2,4);
var BIT6 = Math.pow(2,5);
var BIT7 = Math.pow(2,6);
var BIT8 = Math.pow(2,7);
var BIT9 = Math.pow(2,8);
var BIT10 = Math.pow(2,9);
var BIT11 = Math.pow(2,10);
var BIT12 = Math.pow(2,11);
var BIT13 = Math.pow(2,12);
var BIT14 = Math.pow(2,13);
var BIT15 = Math.pow(2,14);
var BIT16 = Math.pow(2,15);
var BIT_MARKS_DEFINED = true;

var XPS_LAYOUTOBJECT_EVENTS = BIT1;
var XPS_LAYOUTOBJECT_READONLY = BIT2;
var XPS_LAYOUTOBJECT_STYLE = BIT3;
var XPS_LAYOUTOBJECT_TABLE = BIT4;
var XPS_LAYOUTOBJECT_NOLABEL = BIT5;
var XPS_LAYOUTOBJECT_DISABLED = BIT6;
var XPS_LAYOUTOBJECT_NAME = BIT7;
var XPS_LAYOUTOBJECT_XSF = BIT8;
var XPS_LAYOUTOBJECT_XSF_EXTENDED = BIT9;
var XPS_LAYOUTOBJECT_NOXHRICON = BIT10;
var XPS_LAYOUTOBJECT_FORCEEDIT = BIT11;

// =============================================================================
// [ Events ]
// =============================================================================
/**
* Add an event to the specified object.
*/
function XPS_addEvent(obj, evname, handler) {
	obj = XPS_elem(obj);
	if (!obj) return(obj);
	
	evname = evname.toLowerCase();
	if(evname.substr(0,2) == "on") evname = evname.substr(2);
	
	if (obj.type == 'radio') {
		var obj_collection = document.getElementsByName(obj.name);
	}
	else {
		var obj_collection = new Array(obj);
	}
	
	for (var i = 0; i < obj_collection.length; i++) {
		// add XPS events (for XPS_fireEvent() only)
		// Prototype.Element.fire() does not seem to work (1.6.0)
		if (!obj_collection[i].__xps_events) obj_collection[i].__xps_events = new Array();
		if (!obj_collection[i].__xps_events[evname]) obj_collection[i].__xps_events[evname] = new Array();
		obj_collection[i].__xps_events[evname][obj_collection[i].__xps_events[evname].length] = handler;
		
		// Prototype does not check if this element has an event listener
		if (!obj_collection[i].addEventListener && !obj_collection[i].attachEvent) continue;
		// add event listener
		Event.observe(obj_collection[i], evname, handler);
	}
	
	return(obj);
}

/**
* This functions fires an event on the specified object.
* 
* Usually the Event object is passed as an argument to traditionally defined
* events. The major drawback of this abstraction function is that the Event
* object is not accessible when this function is called. Instead of the Event
* object the object itself is passed as an argument to the function.
* 
* @see	XPS_addEvent(), XPS_delEvent(), XPS_fireEvent(), XPS_getEventSubject(),
* 		XPS_cancelEvent()
*/
function XPS_fireEvent(obj, evname) {
	obj = XPS_elem(obj);
	if (!obj) return(obj);
	
	evname = evname.toLowerCase();
	if(evname.substr(0,2) == "on") evname = evname.substr(2);
	
	// XPS events?
	if (!obj.__xps_events) return(obj);
	if (!obj.__xps_events[evname]) return(obj);
	
	// iterate through XPS events
	for (var i = 0; i < obj.__xps_events[evname].length; i++) obj.__xps_events[evname][i](obj);
	
	if (obj.returnValue == false) return(obj);
	return(obj);
}

/**
* Returns the subject from the event object.
* 
* This function returns the subject element on a function call.
*/
function XPS_getEventSubject(obj) {
	// empty inline event initialization
	if (obj == undefined) {
		if (xps['events']['ie_events'])
			return(window.event.srcElement);
		else {
			_XPS_raise("Event object not accessible through inlined events.");
			return(false);
		}
	}
	
	// string identifier?
	if (obj.constructor == String) return(XPS_elem(obj));
	
	// object identifier
	if (obj.tagName) return(obj);
	
	// event object
	var subject = false;
	if (obj.srcElement) subject = obj.srcElement;
	if (obj.target) subject = obj.target;
	
	// label clicked?
	if (subject.tagName == 'LABEL' && subject.htmlFor) {
		// fetch object associated with the label
		subject = XPS_elem(subject.htmlFor);
		if (!subject) return(false);
	}
	
	// this used
	if (obj.id) return(obj);
	
	// xps object
	if (obj.xps) return(obj);
	
	// subject
	return(subject);
}

/**
* Returns the event object.
*/
function XPS_getEventObject(e) {
	if (xps['events']['ie_events'] && !e) return(window.event);
	return(e);
}

/**
* Cancels an event.
*/
function XPS_cancelEvent(e) {
	e.returnValue = false;
	if (e.preventDefault)
		e.preventDefault();
	
	return(false);
}

// =============================================================================
// [ Event Related Functions ]
// =============================================================================
/**
* This function enables a hot-key callback to be bound to an input element.
*/
function XPS_initializeHotKeysXPSElement(elem, hotkeys) {
	var obj = XPS_elem(elem);
	if (!obj) return(false);
	
	// hook event to object
	XPS_addEvent(obj, 'onkeydown', XPS_onKeyDownHotKeyXPSElement);
	
	// add hotkeys
	if (!obj.xps) obj.xps = {};
	obj.xps['js_hotkeys'] = hotkeys;
}

/**
* This function enables a hot-key callback to be bound to an input element.
*/
function XPS_onKeyDownHotKeyXPSElement(e) {
	var obj = XPS_getEventSubject(e);
	
	// determine keycode
	if (e.keyCode)
		var key_code = e.keyCode;
	else if (e.which)
		var key_code = e.which;
	else
		return(false);
	
	// iterate through hotkeys to determine whether a hotkey has been pressed
	if (obj.xps) {
		for (var i in obj.xps['js_hotkeys']) {
			if (i in Array.prototype) continue;
			
			if (key_code == i) {
				// callback
				obj.xps['js_hotkeys'][i](obj.id);
			}
		}
	}
}

function XPS_onBlurXPSElement(elements, callback) {
	if (!elements) return;
	
	window.document.observe('click', function(e) {
		var doCallback = true;
		//elements.each(function(o, i) {
		//	if (
		//		e.element() == o ||
		//		e.element().descendantOf(o)
		//	) doCallback = false;
		//});
		if (doCallback) callback(e.element());
	});
}

// =============================================================================
// [ General Functions ]
// =============================================================================
//var XPS_onLoadStatements = [];
XPS_addEvent(window, 'onload', XPS_executeOnLoadStatements);
function XPS_addOnLoadStatement(statement) {
	xps['onload_statements'][xps['onload_statements'].length] = statement;
}

function XPS_executeOnLoadStatements() {
	// prevent recursion
	if (window.__xps_execute_onload_statements) return;
	window.__xps_execute_onload_statements = true;
	xps['onload_statements'].each(function(o, i) { eval(o); }.bind(this));
	xps['onload_statements'].clear();
	window.__xps_execute_onload_statements = false;
}

function XPS_confirmHref(msg, href_yes, href_no) {
	XPS_addEvent(
		window,
		'onload',
		function() { if (confirm(msg)) { XPS_location(href_yes); } else { XPS_location(href_no); } }
	);
}

// =============================================================================
// [ General ]
// =============================================================================
/**
* Returns the HTML id based upon the XPS id set.
*/
function XPS_getHTMLID(prefix, id, index) {
	return(((prefix != null && prefix != '') ? prefix : '') + id + (((index != null && index != '') || index == '0') ? '_' + index : ''));
}

/**
* Retrieves a element from the document.
*/
function XPS_elem(id, nowarning) {
	var obj = $(id);
	if (!obj) {
		// try getting element by name
		obj = document.getElementsByName(id);
		if (!obj[0]) {
			// XPS auto-added element
			if (window[id]) return(window[id]);
			if (!nowarning) { _XPS_raise("element (" + id + ") not found in document"); return(false); }
		}
		obj = obj[0];
	}
	return(obj);
}

/**
* Returns the value of the specified element.
*/
function XPS_elem_value(id, nowarning) {
	var obj = document.getElementsByName(id);
	
	if (obj.length == 1)
		return(obj[0].value);
	else {
		// assume radio buttons
		for (var i = 0; i < obj.length; i++) {
			if (obj[i].checked) return(obj[i].value);
		}
	}
	return(false);
}

/**
* This function reloads the current page
*/
var XPS_location_callback;
function XPS_location(href) {
	if (!href) return;
	
	if (href.indexOf('javascript:') != -1) {
		eval(href.substr('javascript: '.length));
	}
	
	else {
		if (XPS_location_callback)
			return(XPS_location_callback(href));
		
		// reload the page
		document.location.href = href;
	}
}

/**
* Returns the parent element of the specified element.
*/
function XPS_parent(obj) {
	if (obj.constructor == String) obj = XPS_elem(obj);
	if (!obj) return(false);
	
	if (obj.parentNode)
		return(obj.parentNode);
	else
		if (obj.parentElement)
			return(obj.parentElement);
		else
			return(false);
}

function XPS_XHR_pending(elem, pending) {
	var obj = XPS_elem(elem);
	if (pending) {
		obj._src = obj.src;
		obj.src = XPS_INCLUDE_PATH_PUBLIC + '/ico/' + XPS_LAYOUT_THEME + '/reload_pending.gif';
	}
	else {
		obj.src = obj._src;
		obj._src = null;
	}
}

// =============================================================================
// [ Forms ]
// =============================================================================
function XPS_disable(elem, disabled) {
	var obj = XPS_elem(elem);
	if (!obj) return(false);
	
	obj.disabled = disabled;
	if (disabled) {
		obj.value = '';
		XPS_addCSSClass(obj, 'XPSDisabled');
	}
	else {
		// do not reset obj.value, obj may be already be enabled
		XPS_removeCSSClass(obj, 'XPSDisabled');
	}
}

/**
* Filters the data from prefixed and indexed data.
*/
function XPS_stripPrefixes(data, prefix, index) {
	var data_filtered = {};
	var reg = null;
	var match = null;
	for (var i in data) {
		if (i in Array.prototype) continue;
		
		if (match = i.match(new RegExp('^' + prefix + '(.*)' + ((index != '' || index == '0') ? '_' : '') + index + '$'))) {
			data_filtered[match[1]] = data[i];
		}
	}
	
	return(data_filtered);
}

/**
* Sets focus to an element.
*/
function XPS_focus(obj, select) {
	obj = XPS_elem(obj);
	
	if (obj) {
		if (obj.disabled == false && obj.visible())
			switch(obj.type) {
				case "hidden":
					_XPS_raise("cannot set the focus to a hidden element ("+obj.id+")");
					break;
				
				case "text":
				case "password":
				case "file":
					obj.focus();
					if (select) obj.select();
					break;
				
				case "checkbox":
				case "radio":
				case "textarea":
				case "select-one":
					obj.focus();
					break;
				
				default:
					if (obj.focus) obj.focus();
					if (select) if (obj.select) obj.select();
					break;
			}
	}
	
	return(true);
}

/**
* This function rebuilds a select element from an array.
* 
* @param	obj	ID or the reference to the element that should be requeryd.
* @param	arr	Array containing data should be requeryd.
* @returns	TRUE on success or FALSE on failure.
*/
function XPS_rebuildSelectElement(obj, arr, idc) {
	obj = XPS_elem(obj);
	if (!obj) return(false);
	if (obj.tagName != "SELECT") {
		_XPS_raise("Only SELECT elements can be used for XPS_reBuildSelectElement()");
		return(false);
	}
	
	if (!arr || arr.length == 0) { arr = new Array(); arr[0] = "[none]"; }
	
	// remove old options
	obj.innerHTML = "";
	
	// rebuild options
	var options = Array();
	for (var i in arr) {
		if (i in Array.prototype) continue;
		options[i] = document.createElement("OPTION");
		if (obj.options.add)
			obj.options.add(options[i], -1);
		else
			obj.appendChild(options[i]);
		options[i].value = i;
		options[i].innerHTML = arr[i];
	}
	
	if (idc) obj.value = idc;
	
	return(true);
}

/**
* Submits a form, usually invoked by an anchor submit button
* 
* @param	form	Object or ID specifying the form.
* @param	action	The action attribute of the form will be set if the
* 					action parameter has been set.
*/
var XPS_submit_callback;
function XPS_submit(form, action) {
	form = XPS_elem(form);
	if (!form) return;
	
	if (action) {
		// set action attribute
		form.action = action;
	}
	
	/// @todo Opera seems to fire the onsubmit event automatically
	// exclude Opera
	// Opera invokes the onsubmit function automatically
	//if (!(xps['events']['ie_events'] && !(form.fireEvent))) { }
	if (XPS_fireEvent(form, 'onsubmit')) 
		if (XPS_submit_callback) {
			XPS_submit_callback(form);
		}
		else {
			form.submit();
		}
	
	return;
}

// =============================================================================
// [ Navigation ]
// =============================================================================
/**
* Opens and centers a new window.
*/
function XPS_openWin(winurl, winname, scroll, width, height, top, left) {  
	XPS_openWinByRef(winurl, winname, scroll, width, height, top, left);
}

/**
* Opens and centers a new window and returns the window handle.
*/
function XPS_openWinByRef(winurl, winname, scroll, width, height, top, left) {
	var leftPosition;
	var topPosition;
	
	if (!top && !left) {
		leftPosition = (screen.width) ? (screen.width - width) / 2 : 0;
		topPosition = (screen.height) ? (screen.height - height) / 2 : 0;
	}
	else {
		leftPosition = left;
		topPosition = top;
	}
	
	if (!winname) winname = document.uniqueID;
	
	newwin = window.open(winurl,winname,'scrollbars='+scroll+',toolbar=0,location=0,menubar=0,resizable=1,top='+topPosition+',left='+leftPosition+',width='+width+',height='+height);
	if (newwin) newwin.focus();
	
	// do not return newwin, anchored javascript events will not work correctly
	return(newwin);
}

/**
* This function replaces a value in a action query string
* 
* @param	href		Query to parse
* @param	parameter	String or an array of string with parameters that
* 						should be replaced.
* @param	value		Variable or an array of variables with values matching
* 						the parameters.
* @param	update		Forces the document.location.href to be updated with the
* 						new action query parameter.
*/
function XPS_replaceActionQueryParameterValue(href, parameter, value, update) {
	if (href == null) href = document.location.href;
	
	// force valid formatting of the href
	var queryStart = href.indexOf('?');
	
	// javascript function call without a href?
	if ((queryStart == -1) && (href.substring(0, 11).toLowerCase() == 'javascript:')) return(href);
	
	if (queryStart == -1) queryStart = href.length;
	var sURL = href.substr(0, queryStart) + '?';
	var sQuery = href.substr(queryStart + 1);
	if (sQuery.length && sQuery.substr(0, 1) != '&') sQuery = '&' + sQuery;
	
	// cast to Array
	if (parameter.constructor != Array) {
		_parameter = new Array(1);
		_parameter[0] = parameter;
		parameter = _parameter;
	}
	if (value.constructor != Array) {
		_value = new Array(1);
		_value[0] = value;
		value = _value;
	}
	
	for (var i in parameter) {
		if (i in Array.prototype) continue;
		parameter[i] = '&' + parameter[i] + '=';
		
		var iParamOffset = sQuery.indexOf(parameter[i]);
		
		if (iParamOffset != -1) {
			// determine value length
			//).search(/([&\),\"\s=])/));
			iValueLength = 
				sQuery.
					substr(iParamOffset + parameter[i].length).
						indexOf('&');
			
			// parameter end not found
			if (iValueLength == -1) {
				iValueLength = 
					sQuery.
						substr(iParamOffset + parameter[i].length).
							length;
			}
			
			// delete parameter from query
			sQuery = 
				sQuery.substr(0, iParamOffset) + 
				sQuery.substr(iParamOffset + parameter[i].length + iValueLength);
		}
		
		// add parameter
		sQuery =
			parameter[i] + 
			value[i] + 
			sQuery;
	}
	
	if (update == true) XPS_location(sURL + sQuery);
	
	return(sURL + sQuery);
}

function XPS_URLEncode(plaintext) {
	// The Javascript escape and unescape functions do not correspond
	// with what browsers actually do...
	var SAFECHARS = "0123456789" +					// Numeric
					"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +	// Alphabetic
					"abcdefghijklmnopqrstuvwxyz" +
					"-_.!~*'()";					// RFC2396 Mark characters
	var HEX = "0123456789ABCDEF";
	
	var encoded = "";
	for (var i = 0; i < plaintext.length; i++ ) {
		var ch = plaintext.charAt(i);
	    if (ch == " ") {
		    encoded += "+";				// x-www-urlencoded, rather than %20
		} else if (SAFECHARS.indexOf(ch) != -1) {
		    encoded += ch;
		} else {
		    var charCode = ch.charCodeAt(0);
			if (charCode > 255) {
			    alert( "Unicode Character '" 
                        + ch 
                        + "' cannot be encoded using standard URL encoding.\n" +
				          "(URL encoding only supports 8-bit characters.)\n" +
						  "A space (+) will be substituted." );
				encoded += "+";
			} else {
				encoded += "%";
				encoded += HEX.charAt((charCode >> 4) & 0xF);
				encoded += HEX.charAt(charCode & 0xF);
			}
		}
	} // for
	
	return(encoded);
};

// =============================================================================
// [ Layout ]
// =============================================================================
/**
* Returns whether a element has the specified class name.
*/
function XPS_usesCSSClass(elemOrID, className) {
	var elem = XPS_elem(elemOrID);
	return (" " + XPS_trim(elem.className) + " ").indexOf(" " + XPS_trim(className) + " ") > -1;
}

/**
* Adds a class name in the class attribute from a specific element.
*/
function XPS_addCSSClass(elemOrID, className) {
	var elem = XPS_elem(elemOrID);
	className = XPS_trim(className);
	if (XPS_usesCSSClass(elem, className)) return(false);
	elem.className += " " + className;
	return(true);
}

/**
* Removes a class name in the class attribute from a specific element.
*/
function XPS_removeCSSClass(elemOrID, className) {
	var elem = XPS_elem(elemOrID);
	className = XPS_trim(className);
	if(!XPS_usesCSSClass(elem, className)) return(false);
	elem.className = XPS_trim(elem.className.replace(className, "").replace(/\s+/g, " "));
	return(true);
}

/**
* Replaces a class name in the class attribute from a specific element.
*/
function XPS_replaceCSSClass(elemOrID, fromClass, toClass) {
	if(XPS_removeCSSClass(elemOrID, fromClass)) XPS_addCSSClass(elemOrID, toClass);
}

// returns the absolute top position an element
function XPS_get_absolute_top(elem) {
	if (elem.offsetParent)
		if (arguments[1])
			return(XPS_get_absolute_top(elem.offsetParent, elem.offsetTop + arguments[1]));
		else
			return(XPS_get_absolute_top(elem.offsetParent, elem.offsetTop));
	else
		return(arguments[1]);
}

// returns the absolute left position of an element
function XPS_get_absolute_left(elem) {
	// correct offset when the relative attributes does not apply to the left attribute
	// nice try, but still IE margin/padding issues
	//if (elem.style.position == 'relative' && !elem.style.left) { ((arguments[1]) ? arguments[1] -= elem.offsetLeft : arguments[1] = 0 - elem.offsetLeft) }
	if (elem.offsetParent)
		if (arguments[1])
			return(XPS_get_absolute_left(elem.offsetParent, elem.offsetLeft + arguments[1]));
		else
			return(XPS_get_absolute_left(elem.offsetParent, elem.offsetLeft));
	else
		return(arguments[1]);
}

// returns wether elem is a child of parent
function XPS_is_child(elem, parent) {
	if (elem != parent)
		if (elem.parentNode)
			return(XPS_is_child(elem.parentNode, parent));
		else
			return(false);
	else
		return(true);
}

/**
* Toggles the block display.
*/
function XPS_toggleBlock(elem) {
	if (elem.constructor == String)
		elem = XPS_elem(elem);
	
	if (!elem) return(false);
	
	if (elem.style.display == 'none') {
		elem.style.display = '';
		return('visible');
	}
	else {
		elem.style.display = 'none';
		return('hidden');
	}
	
	return(true);
}

function XPS_setOnBlurCallback(elem, callback) {
	var obj = XPS_elem(elem);
	
	XPS_addEvent(document, 'onclick', XPS_onClickDocumentCallback);
	
	function XPS_onClickDocumentCallback(e) {
		var obj_event = XPS_getEventSubject(e);
		
		if (!XPS_is_child(obj_event, obj) && callback) callback(obj_event);
	}
}

function XPS_dialogMenu(parameters) {
	var table = new Element('table', { cellPadding: 0, cellSpacing: 0, border: 0, style: parameters['style'] || null });
	var tbody = new Element('tbody');
	var row = new Element('tr');
	columns = {};
	for (var i in parameters['options']) {
		if (i in Array.prototype) continue;
		
		// icon
		if (parameters['options'][i]['icon']) {
			columns[i] = new Element('td');
			columns[i].update(new Element('a', { href: parameters['options'][i]['href'] || 'javascript: void(0);' }).observeXPS('click', parameters['options'][i]['callback']).update(new Element('img', { src: parameters['options'][i]['icon'], className: 'XPSIcon' })));
			row.insert({ bottom: columns[i] });
			row.insert({ bottom: new Element('td').update('&nbsp;') });
		}
		
		// text
		if (parameters['options'][i]['label']) {
			columns[i] = new Element('td');
			columns[i].update(new Element('a', { href: parameters['options'][i]['href'] || 'javascript: void(0);' }).observeXPS('click', parameters['options'][i]['callback']).update(parameters['options'][i]['label']));
			row.insert({ bottom: columns[i] });
			row.insert({ bottom: new Element('td').update('&nbsp;') });
		}
	}
	table.insert(tbody.insert(row));
	return(table);
}

// =============================================================================
// [ Strings ]
// =============================================================================
/**
* Trims whitespaces from the beginning and end of a string.
*/
function XPS_trim(s) {
	if((null == s) || (typeof(s) != "string") || !s.length) return("");
	return s.replace(/^[ \t\n]*/, "").replace(/[ \t\n]*$/, "");
}

// message
function _XPS_raise(msg) {
	if (XPS_DEBUG) alert("XPS JS Lib warning: " + msg);
}

// =============================================================================
// [ IE Hide / Show SELECT elements ]
// =============================================================================
xps['register']['hide_select_counter'] = 0;
function XPS_hideSelectElements() {
	if (!xps['browser']['select_by_windows_shell']) return;
	xps['register']['hide_select_counter']++;
	XPS_addCSSClass(document.body, 'XPSHideSelectElements');
}

function XPS_showSelectElements() {
	if (!xps['browser']['select_by_windows_shell']) return;
	xps['register']['hide_select_counter']--; if (xps['register']['hide_select_counter'] < 0) xps['register']['hide_select_counter'] = 0;
	if (xps['register']['hide_select_counter'] == 0) XPS_removeCSSClass(document.body, 'XPSHideSelectElements');
}

// =============================================================================
// [ IE Auto Resize SELECT elements ]
// =============================================================================
function XPS_autoResizeSelectElement(elem) {
	if (!xps['browser']['ie']) return;
	var obj = XPS_elem(elem);
	if (!obj) return;
	
	// only force this behavior for .XPSAutoResizeSelectElement classes
	if (
		!XPS_usesCSSClass(obj, 'XPSAutoResizeSelectElement')
	) return(false);
	
	// create replacing element
	obj.__xps_auto_resized_replace_element = document.createElement('DIV');
	obj.__xps_auto_resized_replace_element.runtimeStyle.display = 'none';
	obj.parentNode.appendChild(obj.__xps_auto_resized_replace_element);
	
	XPS_addEvent(obj, 'onmouseover', _XPS_autoResizeSelectElementOnMouseOver);
	XPS_addEvent(obj, 'onchange', _XPS_autoResizeSelectElementOnChange);
	XPS_addEvent(document, 'onmousedown', _XPS_autoResizeSelectElementOnMouseDown);
}

function _XPS_autoResizeSelectElementOnMouseOver(e) {
	var obj = XPS_getEventSubject(e);
	
	if (document.__xps_auto_resized_select_element == obj) return;
	
	if (document.__xps_auto_resized_select_element) {
		_XPS_autoResizeSelectElementReset(document.__xps_auto_resized_select_element);
	}
	
	// show replace element
	obj.__xps_auto_resized_replace_element.runtimeStyle.width = obj.offsetWidth + 'px';
	obj.__xps_auto_resized_replace_element.runtimeStyle.display = 'block';
	
	document.__xps_auto_resized_select_element = obj;
	obj.runtimeStyle.position = 'absolute';
	obj.runtimeStyle.top = (obj.offsetTop - 3) + 'px';
	obj.runtimeStyle.width = 'auto';
}

function _XPS_autoResizeSelectElementOnChange(e) {
	var obj = XPS_getEventSubject(e);
	
	if (document.__xps_auto_resized_select_element) {
		_XPS_autoResizeSelectElementReset(document.__xps_auto_resized_select_element);
	}
}

function _XPS_autoResizeSelectElementOnMouseDown(e) {
	var obj = XPS_getEventSubject(e);
	
	if (
		document.__xps_auto_resized_select_element &&
		(e.srcElement != document.__xps_auto_resized_select_element)
	) {
		_XPS_autoResizeSelectElementReset(document.__xps_auto_resized_select_element);
	}
}

function _XPS_autoResizeSelectElementReset(elem) {
	var obj = XPS_elem(elem);
	if (!obj) return(false);
	
	// hide replace element
	obj.__xps_auto_resized_replace_element.runtimeStyle.display = 'none';
	
	// restore element display properties
	obj.runtimeStyle.width = '';
	obj.runtimeStyle.position = '';
	obj.runtimeStyle.top = '';
	document.__xps_auto_resized_select_element = null;
}
