/* functions for projex */
if (!console) {
	var console = {
		log: function(o){ return; }
	};
}

/**
 * get an element by id
 */
function projex_e(id) {
	if (typeof id == 'string')
		return document.getElementById(id);
	return id;
}

// add hidden input field to form 'formname'
// formname can be an object
function projex_addhidden(formname, name, value) {
	if (typeof formname == 'object')
		var f = formname;
	else
		var f = document.forms[formname];
	if (!f)
		return false;
	var i = document.createElement('INPUT');
	i.type = 'hidden';
	i.name = name;
	i.value = value;
	f.appendChild(i);
	return true;
}

function projex_submit_val(formname, inputname, inputval, ref) {
	var f = document.forms[formname];
	if (!f)
		return false;
	if (ref)
		f.elements['__ref'].value = ref;
	var i = document.createElement('INPUT');
	i.type = 'hidden';
	if (inputname)
		i.name = inputname;
	else
		i.name = 'button';
	i.value = inputval;
	f.appendChild(i);
	f.submit();
	return false;
}

function projex_submit(formname, inputname, ref) {
	return projex_submit_val(formname, inputname, 'ok', ref);
}

/* name alias */
function projex_submit_confirm(formname, inputname, ref) {
	return projex_confirm_submit(formname, inputname, ref);
}

function projex_confirm_submit(formname, inputname, ref) {
	var id = document.forms[formname].elements['id'].value;
	if ( (ref.indexOf('$ID') != -1) && id && (id != '0')) {
		ref = ref.replace(/\$ID/, id);
	}
	if (!document.forms[formname].elements['__noconfirmsubmit__'] &&
		confirm("Wollen Sie Ihre Daten speichern?")) {
		return projex_submit(formname, inputname, ref);
	} else {
		// don't go to next site, if form was not saved
		// at least once
		if (!id || (id == '0'))
			return false;
		document.location.href = ref;
	}
	return false;
}



function projex_confirm(question, dest) {
	if (confirm(question)) location = dest;
}

/**
 * @param question String
 * @param type String
 * @param action String
 * @param id String
 * @param callback String function _name_ only
 */
function projex_ajax_confirm(question, type, action, id, callback) {
	if (confirm(question)) {
		projex_jsonrequest(function(response, scope) {
			if (response) {
				var fn = eval(callback);
				fn(response);
			}
		}, type, action, id, [], this);
	}
}


/* Enlarges a textarea on demand
* Example:
* <textarea onKeyUp="resizeta(this)"></textarea>
*/
function projex_taresize(t) {
	var agt=navigator.userAgent.toLowerCase();
	a = t.value.split('\n');
	b=1;
	for (x=0;x < a.length; x++) {
		if (a[x].length >= t.cols) b+= Math.ceil(a[x].length/t.cols);
	}
	b+= a.length;
	if (b > t.rows && agt.indexOf('opera') == -1)
		t.rows = b;
}

function projex_popup(varurl, title, width, height) {
/*<?php
  if ($np) { ?>
  document.location = varurl;
  <?php } else { ?>
//  window.open(varurl, title, "width=800,height=600,scrollbars=yes");
*/
	var nw = window.open(varurl, "NewWindow",'width=' + width + ',height=' + height + ',scrollbars=yes');
	if (nw) {
		// TODO: help safari make up his mind...
		addEvent(nw, 'load', function() {
			projex.utils.adjustPopup( nw );
		});
	}
 /* <?php } ?> */
}

function projex_viewElement(obj, style) {
	var el = projex_e(obj);
	if (!el) return;
	el.style.display = (style||'block');
}

function projex_toggleElement(obj, style) {
	var el = projex_e(obj);
	if (!el) return;
	if (el.style.display != 'none')
		el.style.display = 'none';
	else
		el.style.display = (style||'');
}

function projex_hideElement(obj) {
	var el = projex_e(obj);
	if (!el) return;
	el.style.display = "none";
}

// properly escape url for get requests
function projex_escape(url) {
	if(encodeURIComponent)
		return encodeURIComponent(url);
	if(escape)
		return escape(url);
}

function projex_xmlhttp() {
	var xmlhttp;
	/*@cc_on @*/
	/*@if (@_jscript_version >= 5)
	// JScript gives us Conditional compilation, we can cope with old IE versions.
	// and security blocked creation of the objects.
	try {
	xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
	} catch (e) {
	try {
	xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	} catch (E) {
	xmlhttp = false;
	}
	}
	@end @*/
	if (!xmlhttp && typeof XMLHttpRequest!='undefined') {
		xmlhttp = new XMLHttpRequest();
	}
	projex.xmlhttp.push(xmlhttp);
	return xmlhttp;
}

function projex_setXmlHttpHeaders(xmlhttp) {
	var headers = {
		'X-Requested-With': 'XMLHttpRequest',
		'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
	};
	for (var name in headers) {
		try {
			xmlhttp.setRequestHeader(name, headers[name]);
		} catch(e) {
			if (console) console.log(e);
		}
	}
}

function projex_href(c, a, i, v) {
	var url = "base.php?__type__=" + c + "&action=" + a + "&id=" + i;
	if (!v)
		v = new Array();
	if (typeof v["__transform__"] == 'undefined' || v["__transform__"] !== 'json')
		v["__transform__"] = "js";
	for(var j in v) {
		url += "&" + j + "=" + v[j];
	}
	return url;
}

/**
 * @param {callback} callback to call
 * @param {c} class
 * @param {a} action
 * @param {i} id
 * @param {v} array of keys
 */
function projex_request(callback, c, a, i, v) {
	v["__transform__"] = "js"; /* we always want js back */
	var url = projex_href(c, a, i, v);
	var xh = projex_xmlhttp();
	projex_setXmlHttpHeaders(xh);
	xh.open("GET", url, true);
	xh.onreadystatechange = function() {
		if (xh.readyState == 4 && xh.status == 200) {
			var r = new Array();
			eval(xh.responseText); // this is expected to set r
			for(var xx in r)
				r = r[xx];
			eval(callback);
		}
	}
	xh.send(null);
}

/**
 * @param {cb} callback to call
 * @param {c} class
 * @param {a} action
 * @param {i} id
 * @param {v} array of keys
 * @param {s} scope of the callback function
 */
function projex_jsonrequest(cb, c, a, i, v, s) {
	s ? s = s : s = window;
	v["__transform__"] = "json";
	var url = projex_href(c, a, i, v);
	var xh = projex_xmlhttp();
	xh.open("GET", url, true);
	projex_setXmlHttpHeaders(xh);
	xh.onreadystatechange = stateChange;
	function stateChange() {
		if (xh.readyState == 4 && xh.status == 200) {
			try {
				var r = eval('('+xh.responseText+')');
				eval(cb(r,s));
				xh = null;
			}
			catch(e) { eval(cb({error: e},s)); }
		}
	}
	xh.send(null);
}

function projex_table_iterator(rows, rowfunc, cellfunc) {
	var str = "";
	for(var rowname in rows["row"]) {
		var rowstr = "";
		for(var cellname in rows["row"][rowname]["cell"]) {
			var cell = rows["row"][rowname]["cell"][cellname];
			var name = cell["name"].replace(/"/g, '\\"');
			var value = cell["value"].replace(/"/g, '\\"');
			var descr = "";
			var num = "";
			var tag = "td";
			if (cell["descr"])
				descr = cell["descr"].replace(/"/g, '\\"');
			if (cell["num"])
				num = cell["num"].replace(/"/g, '\\"');
			if (cell["tag"])
				tag = cell["tag"].replace(/"/g, '\\"');
			/*	document.write(rowname + " " + cellname + " " + cellfunc + '("' + name + '","' + value + '","' + descr + '","' + num + '","' + tag + '");<br>'); */
			rowstr += eval(cellfunc + '("' + name + '","' + value + '","' + descr + '","' + num + '","' + tag + '");');
		}
		rowstr = rowstr.replace(/"/g, '\\"');
		str += eval(rowfunc + '("' + rowstr + '");');
	}
	return str;
}
function pager_limit_set(tableid) {
	var obj = projex_e('pager'+tableid);
	if (!obj)
		alert('Tablepager nicht aktiviert!');
	return table_limit_set(tableid, obj.options[obj.selectedIndex].text);
}


/**
 * add CalendarPop for a form field. HTML only has to look like this:
 * <input name="date" type="text"/><a name="calendar_date" id="calendar_date">openCal</a>
 *
 * then only add to onload event : addPicker(inputname, formname, format [,opts ]);
 *
 * @param {String} inputname the name of the input element holding the date value
 * @param {String} formname name of wrapping form
 * @param {String} format e.g. 'E, dd.MM.yyyy' (see CalendarPopup.js)
 * @param {Object} opts possible values:
 *		object.addTime = true
 		object.addTimeText = String text for prompt
 		object.addTimeDefault = String eg. 00:00:00
 		object.submitform = true submits on date insert
 *
 */
var addPicker = function() {

	var _inputname = '';
	var prefix = 'calendar_';

	var openCalendar = function(inputname, formname, format, e, opts) {

		_inputname = inputname;
		if (typeof projex.calendarDiv === 'undefined') {
			projex.calendarDiv = projex.dom.createElement('div',{'id':'projex-calendarDiv'});
			document.body.appendChild(projex.calendarDiv);
		}
		var date = new CalendarPopup('projex-calendarDiv');
		date.setMonthNames('Januar','Februar','März','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember');
		date.setDayHeaders('So','Mo','Di','Mi','Do','Fr','Sa');
		date.setWeekStartDay(1);
		date.setTodayText("Heute");
		date.setReturnFunction('pastePicked');
		date.select(document.forms[formname].elements[inputname], opts.ankerId, format);
		date.reposition(e);

	}

	return function(inputname, formname, format, _opts) {
		var opts = _opts || {};
		var openerel = projex_e(opts.ankerId);
		if (!openerel) {
			alert('Opener not found' + opts.ankerId);
			return;
		}
		projex.dom.addClass(openerel, "datepicker");
		addEvent(openerel, 'click', function(e) {
			openCalendar(inputname, formname, format, e, opts);
			// save set method to window
			window.pastePicked = function(y,m,d) {

				var box = document.forms[formname].elements[_inputname];
				box.value = formatDate(new Date(y,m-1,d,0,0,0), format);
				// optional arguments handling
				if (opts) {
					if (opts.addTime) {
						time = prompt(opts.addTimeText, '00:00:00');
						if (time == null)
							box.value += " " + opts.addTimeDefault;
						else
							box.value += " "+ time;
					}
					if (opts.submitform)
						return document.forms[formname].submit();
				}
			}
		});
	};
}();

/**
 * written by Dean Edwards, 2005
 * with input from Tino Zijdel, Matthias Miller, Diego Perini
 * http://dean.edwards.name/weblog/2005/10/add-event/
 */
function addEvent(element, type, handler) {
	if (element.addEventListener) {
		element.addEventListener(type, handler, false);
	} else {
		if (!handler.$$guid) handler.$$guid = addEvent.guid++;
		if (!element.events) element.events = {};
		var handlers = element.events[type];
		if (!handlers) {
			handlers = element.events[type] = {};
			if (element["on" + type]) {
				handlers[0] = element["on" + type];
			}
		}
		handlers[handler.$$guid] = handler;
		element["on" + type] = handleEvent;
	}
};
// a counter used to create unique IDs
addEvent.guid = 1;

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

function handleEvent(event) {
	var returnValue = true;
	event = event || fixEvent(((this.ownerDocument || this.document || this).parentWindow || window).event);
	var handlers = this.events[event.type];
	for (var i in handlers) {
		this.$$handleEvent = handlers[i];
		if (this.$$handleEvent(event) === false) {
			returnValue = false;
		}
	}
	return returnValue;
};

function fixEvent(event) {
	// add W3C standard event methods
	event.preventDefault = fixEvent.preventDefault;
	event.stopPropagation = fixEvent.stopPropagation;
	return event;
};
fixEvent.preventDefault = function() {
	this.returnValue = false;
};
fixEvent.stopPropagation = function() {
	this.cancelBubble = true;
};

function stopEvent(event) {
	var e = event || window.event;
	if (e.stopPropagation) {
		e.stopPropagation(); // for DOM-friendly browsers
		e.preventDefault();
	} else {
		e.returnValue = false; // for IE
		e.cancelBubble = true;
	}
}


// Find all tables with class sortable and make them sortable
// likewise for pageable tables
function sortables_init() {

	var tbls = document.getElementsByTagName("table");
	for ( var ti = 0, len = tbls.length; ti < len; ti++) {
		var thisTbl = tbls[ti];
		if (thisTbl.id) {
			var cookie = getCookieLocal('table');
			// load settings
			if (cookie) {
				var s = cookie.split('/');
				/* read cookie, set limit, limitstart, filtercol, filterval */
				thisTbl.setAttribute("limit", s[0]);
				thisTbl.setAttribute("limitstart", s[1]);
				thisTbl.setAttribute("limitend", s[2]);
				thisTbl.setAttribute("lastpage", s[3]);
				thisTbl.setAttribute("page", s[4]);
				thisTbl.setAttribute("filtercol", s[5]);
				thisTbl.setAttribute("filterval", s[6]);
				thisTbl.setAttribute("visiblerows", s[7]);
				thisTbl.setAttribute("sortby", s[8]);
				thisTbl.setAttribute("sortdir", s[9]);
				thisTbl.setAttribute("filter", decodeURIComponent(s[10]));
				projex_selectDefaultId('pager'.concat(thisTbl.id), s[0]);
			}
		}
		if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id))
			ts_makeSortable(thisTbl);
		if (((' '+thisTbl.className+' ').indexOf("pageable") != -1) && (thisTbl.id))
			__table_limit(thisTbl);
	}
}

// convert associative array a to string representation
function assoc2string(a) {
	var s = '';
	for(var k in a) {
		var v = a[k];
		if (!k)
			continue;
		s += '/' + k + '=' + encodeURIComponent(v);
	}
	return s;
}

function bool2desc(nonboolvalue) {
	if('t' == nonboolvalue)
		return '<img src="static/default/img/verified.gif"/>'
	else
		return '<img src="static/default/img/notverified.gif"/>';
}

function string2assoc(s) {
	var a = new Array();
	if (!s)
		return a;
	var pairs = s.split('/');
	for(var i = 0; i < pairs.length; i++) {
		var pair = pairs[i].split('=');
		if (pair.length != 2)
			continue;
		a[pair[0]] = decodeURIComponent(pair[1]);
	}
	return a;
}

function ts_setFilter_dynamic(sel) {
	var table = projex_e(sel.getAttribute("table"));
	var col = sel.getAttribute("filter");
	var val = sel.options[sel.selectedIndex].value;

	var filter = string2assoc(table.getAttribute('filter'));
	filter[col] = val;
	table.setAttribute('filter', assoc2string(filter));

	__table_limit(table);
}

// non-dynamic case
function ts_setFilter(sel) {
	var table = projex_e(sel.getAttribute("table"));
	var col = 1*sel.getAttribute("col");
	var val = sel.options[sel.selectedIndex].value;

	var filter = string2assoc(table.getAttribute('filter'));
	filter[col] = val;
	table.setAttribute('filter', assoc2string(filter));

	var ncols = table.rows[0].cells.length;
	/* do filter table, set attribute filtered on all
	   hidden rows */
	var visible = 0, i, c;
	for(i = 1, len = table.rows.length; i < len ; i++) {
		var v = true;
		for(c = 0; c < ncols; c++) {
			if (!filter[c])
				continue;
			val = filter[c];
			var x = table.rows[i].cells[c].innerHTML;
			if ( val && (x != val)
				&& (x.indexOf(val+'<br>') == -1)
				&& (x.indexOf('<br>' + val) == -1)
				&& (x.indexOf(val+'<BR>') == -1)
				&& (x.indexOf('<BR>' + val) == -1)) {
				// filtered
				v = false;
				break;
			}
		}
		if (!v) {
			table.rows[i].setAttribute("filtered", 1);
		} else {
			table.rows[i].setAttribute("filtered", 0);
			visible++;
		}
	}
	table.setAttribute("visiblerows", visible);
	__table_limit(table);
}

function natsort(a, b) {
	if (a.toLowerCase() > b.toLowerCase())
		return 1;
	if (a.toLowerCase() == b.toLowerCase())
		return 0;
	return -1;
}

// changes column to filter in table / cell
function ts_makeFilter(cell, table, col) {
	var dynamic = (table.getAttribute("dynamic")*1 > 0);
	var filter = string2assoc(table.getAttribute('filter'));
	if (dynamic) { // done in template
		// set default values
		var sel = null;
		for( var i = 1, len = table.rows[0].cells.length; i < len; i++ ) {
			var td = table.rows[0].cells[i];
			for ( var ci = 0, clen = td.childNodes.length; ci < clen; ci++) {
				if (td.childNodes[ci].tagName &&
					td.childNodes[ci].tagName.toLowerCase() == 'select') {
					sel = td.childNodes[ci];
					s = sel.getAttribute('filter');
					if (filter[s]) {
						__projex_selectDefault(sel, filter[s]);
						break;
					}
				}
			}
		}
		return;
	}
	var s = document.createElement("SELECT");
	var o = document.createElement("OPTION");
	o.value = "";
	o.text = cell.innerHTML;
	s.options.add(o, 0);
	var a = new Array(); // hold unique elements
	for(var i = 1, rlen = table.rows.length; i < rlen; i++) {
		var v = table.rows[i].cells[col].innerHTML;
		var l =v.split(/<br>/i);
		for(var el in l) {
			l[el] = l[el].replace(/^\s*(.*?)\s*$/, "$1");
			a.push(l[el]);
		}
	}
	a.sort(natsort);
	var val = '';
	for(var iopt in a) {
		if (a[iopt] == val)
			continue;
		val = a[iopt];
		if (!val || (val == " ") || val.length == 1 || (val == 'br')
			|| (val == 'BR'))
			continue;
		o = document.createElement("OPTION");
		o.value = val;
		o.text = val;
		s.options.add(o, s.options.length);
	}
	s.setAttribute("table", table.id);
	s.setAttribute("col", col);
	s.onchange = function() { ts_setFilter(this); };
	if (cell.getAttribute("filtercell")) {
		cell = document.getElementById(cell.getAttribute("filtercell"));
	}
	cell.innerHTML = "";
	cell.appendChild(s);
	if (filter[col]) {
		__projex_selectDefault(s, filter[col]);
		ts_setFilter(s);
	}

}

// make table sortable and filter
function ts_makeSortable(table) {
	if (table.rows && table.rows.length > 0)
		var firstRow = table.rows[0];
	if (!firstRow) return;
	var dynamic = (table.getAttribute("dynamic")*1 > 0);
	var sb = table.getAttribute("sortby");
	var sd = table.getAttribute("sortdir");
	var sortcell = null;
	var arr = '', dir = '';
	// We have a first row: assume it's the header, and make its contents clickable links
	for (var i=0;i<firstRow.cells.length;i++) {
		var cell = firstRow.cells[i];
		var txt = cell.innerHTML;
		if ((' '+cell.className+' ').indexOf("filtercol") != -1) {
			// find select box, fill in values
			ts_makeFilter(cell, table, i);
			// external filter box?
			if (!cell.getAttribute("filtercell"))
				continue;
		}
		if (dynamic)
			addEvent(cell, 'click', function() { table_dynamic_sort(this); });
		else
			addEvent(cell, 'click', function() { ts_resortTable(this); });

		if (txt != "") {
			if (sb && (sb == cell.getAttribute("sort"))) {
				sortcell = cell;
				if (sd == 'ASC') {
					arr = projex.ARROWUP;
					dir = 'up';
				} else {
					arr = projex.ARROWDOWN;
					dir = 'down';
				}
			}
			cell.innerHTML = '<span class="sortarrow" sortdir="' + dir + '">' + arr + '</span>'
					+ '<span style="font-size:11px;">' + txt + '</span>';
			arr = '';
			dir = '';
		}
	}
	if (!dynamic)
		ts_resortTable(sortcell);
}

function ts_getInnerText(el) {
	if (typeof el == "string") return el;
	if (typeof el == "undefined") { return "undefined" };
	if (el.innerText) return el.innerText;	//Not needed but it is faster
	var str = "";

	var cs = el.childNodes;
	var l = cs.length;
	for (var i = 0; i < l; i++) {
		switch (cs[i].nodeType) {
			case 1: //ELEMENT_NODE
				str += ts_getInnerText(cs[i]);
				break;
			case 3:	//TEXT_NODE
				str += cs[i].nodeValue;
				break;
		}
	}
	return str;
}

function table_limit_go(tableid, direction) {
	var table = projex_e(tableid);
	var page = 1*table.getAttribute("page");
	if (!page) page = 1;
	table.setAttribute("lastpage", page);
	page += direction;
	if (page > 0)
		table.setAttribute("page", page);
	return __table_limit(table);
}

function table_limit_next(tableid) {
	return table_limit_go(tableid, 1);
}

function table_limit_prev(tableid) {
	return table_limit_go(tableid, -1);
}

function table_limit_set(tableid, limit) {
	var table = projex_e(tableid);
	if (!limit) {
		alert("Limit not set");
		return false;
	}
	table.setAttribute("limit", limit);
	return __table_limit(table);
}

/* only show limitstart + limit rows of tableid */
function table_limit(tableid) {
	var table = projex_e(tableid);
	return __table_limit(table);
}
// @noact just recalculate limits
function __table_limit(table, noact) {
	var i;

	// map active table
	projex.table.element = table;

	/* get old limits */
	var lastpage = 1*table.getAttribute("lastpage");
	var page = 1*table.getAttribute("page");
	if (!page || (page<1))
		page = 1;

	var loader = projex_e("loader");
	if (loader)
			projex.loader.init(table, loader);

	var filter = string2assoc(table.getAttribute("filter"));
	var visrows = table.getAttribute("visiblerows");

	var sortby = table.getAttribute("sortby");
	var sortdir = table.getAttribute("sortdir");
	var customsort = table.getAttribute("customsort") || 0;
	var dofilter = false;
	// dynamic tables always reload from server
	var dynamic = (table.getAttribute("dynamic")*1 > 0);
	if (filter.length)
		dofilter = true;

	if (!dynamic && (!dofilter || !visrows))
		visrows = table.rows.length - 1;

	var limit = 1*table.getAttribute("limit");
	if (!limit)
		limit = 10; // default limit
	if ((visrows) && (visrows != "undefined")) {
		var pages = Math.floor((visrows - 1) / limit) + 1;
		if (pages < 1)
			pages = 1;
	}
	if (page > pages) {
		page = pages;
		table.setAttribute("page", page);
	}
	var limitstart = (page - 1) * limit + 1;
	var limitend = limitstart + limit;

	if (!noact) {
		if (!dynamic) {
			/* hide all rows */
			for(i = 1, len = table.rows.length; i < len; i++)
				table.rows[i].className = "hiddenrow";
			if (dofilter) {
				var v = 0;
				for(i = 1; v < limitend && i < len; i++) {
					if (table.rows[i].getAttribute("filtered") == 1)
						continue;
					v++;
					if (v >= limitstart && v < limitend && v < table.rows.length)
						table.rows[i].className = "visiblerow";
				}

			} else {
				if (limitend > table.rows.length)
					limitend = table.rows.length;
				/* trivial */
				for(i = limitstart; i < limitend; i++)
					table.rows[i].className = "visiblerow";
			}
		} else {
			/* new style */
			var req = table.getAttribute("request");
			var url = req + '/__transform__=js/';
			url += '__limitstart__=' + (limitstart - 1) + '/';
			url += '__limit__=' + limit + '/';
			if (customsort == '0') {
				if ((sortby != '') && (sortdir != '')) {
					url += '__sortby__=' + sortby + '/';
					url += '__sortdir__=' + sortdir + '/';
				}
			}
			for(var fk in filter) {
				if (!filter[fk])
					continue;
				url += fk + '=' + filter[fk] + '/';
			}
			url += '__total__=1/';
			var xh = projex_xmlhttp();
			xh.open("GET", url, true);
			xh.onreadystatechange = function() {
				var r = new Array();
				if (xh.readyState == 4 && xh.status == 200) {
					var totalcount = 0;
					try {
						eval(xh.responseText); // this is expected to set r
						for(var xx in r)
							r = r[xx];
						table_callback(table, r, totalcount);
					} catch(e) {
						var error = xh.responseText.match('ERROR.+[0-9a-zA-Z]');
						new projex.MessageBox.alert(e.message + " : " + error);
					}
				}
				if (loader)
					projex.loader.update(xh.readyState, r.length);
				r = null;
			}
			xh.send(null);
		}
	} /* if !noact */
	/* save settings */
	table.setAttribute("limit", limit);
	table.setAttribute("limitstart", limitstart);
	table.setAttribute("limitend", limitend);

	// make hovered table row active
	projex.table.addHover(table);

	var settings = new Array(limit, limitstart,
			limitend, lastpage, page,
			'', '', /* was filtercol/-val */
			visrows, sortby, sortdir,
			encodeURIComponent(table.getAttribute('filter'))
			);
	setCookieLocal('table', settings.join('/'));

	if (projex_e(table.id + '_limitstart'))
		projex_e(table.id + '_limitstart').innerHTML = limitstart;
	if (projex_e(table.id + '_limitend'))
		projex_e(table.id + '_limitend').innerHTML = limitend;
	if (projex_e(table.id + '_limit'))
		projex_e(table.id + '_limit').innerHTML = limit;
	if (projex_e(table.id + '_pagecurrent'))
		projex_e(table.id + '_pagecurrent').innerHTML = page;
	if (projex_e(table.id + '_pagetotal'))
		projex_e(table.id + '_pagetotal').innerHTML = (pages)?pages:"?";
	return true;
}

function table_dynamic_sort(cell) {
	var table = getParent(cell, 'TABLE');
	var newsortby = cell.getAttribute("sort");
	var sortby = table.getAttribute("sortby");
	var sortdir = table.getAttribute("sortdir");
	if (!newsortby)
		return;
	// find span
	var span = null;
	var ci = 0;
	for (ci=0;ci<cell.childNodes.length;ci++) {
		/* first span is the fake span for font-size */
		if (cell.childNodes[ci].tagName &&
			cell.childNodes[ci].tagName.toLowerCase() == 'span' &&
			cell.childNodes[ci].className.indexOf('sortarrow') != -1)
			span = cell.childNodes[ci];
	}
	// Delete any other arrows there may be showing
	var allspans = document.getElementsByTagName("span");
	for (ci=0;ci<allspans.length;ci++) {
		if (allspans[ci].className == 'sortarrow') {
				// in the same table as us?
			if (getParent(allspans[ci], "table") ==
				getParent(cell, "table")) {
				allspans[ci].innerHTML = '';
			}
		}
	}
	if (sortby != newsortby)
		sortdir = 'DESC'; // default sortdir
	else if (sortdir == 'ASC')
		sortdir = 'DESC';
	else if (sortdir == 'DESC')
		sortdir = 'ASC';
	table.setAttribute("sortby", newsortby);
	table.setAttribute("sortdir", sortdir);
	// re-request table
	if (sortdir == 'ASC')
		span.innerHTML = projex.ARROWUP;
	else
		span.innerHTML = projex.ARROWDOWN;
	__table_limit(table);
}

function table_callback(table, r, total) {
	var w = table.rows[0].cells.length;
	var f =	table.getAttribute("rowcallback");
	var tlen = table.rows.length;
	for(var i = 0, len = r.length; i < len; i++) {
		if (tlen <= (i + 1)) {
			// add row with w empty cells
			var row = table.insertRow(i + 1);
			for(var x = 0; x < w; x++)
				row.insertCell(x);
		}
		table.rows[i + 1].className="visiblerow";
		eval(f + "(r[i], table.rows[i + 1].cells, table.rows[i+1]);");
	}
	for(; (i + 1) < tlen; i++) {
		table.rows[i + 1].className="hiddenrow";
	}
	table.setAttribute("visiblerows", (total)?total:"undefined");
	//alert(table.getAttribute("visiblerows"));
	__table_limit(table, true); /* recalculate limits */
}

function ts_resortTable(lnk) {
	if (!lnk)
		return;
	// get the span
	var span, ci;
	for (ci=0;ci<lnk.childNodes.length;ci++) {
		if (lnk.childNodes[ci].tagName &&
			(lnk.childNodes[ci].tagName.toLowerCase() == 'span') &&
			(lnk.childNodes[ci].className.indexOf('sortarrow') != -1) )
			span = lnk.childNodes[ci];
	}
	var spantext = ts_getInnerText(span);
	var td = lnk;
	var column = td.cellIndex;
	var table = getParent(td,'TABLE');
	var tlen = table.rows.length;

	// Work out a type for the column
	if (table.rows.length <= 1) return;
	var itm = ts_getInnerText(table.rows[1].cells[column]);
	sortfn = ts_sort_caseinsensitive;
	if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d\d\d$/)) sortfn = ts_sort_date;
	if (itm.match(/^\d\d[\/-]\d\d[\/-]\d\d$/)) sortfn = ts_sort_date;
	if (itm.match(/\d\d\.\d\d\.\d\d\d\d/)) sortfn = ts_sort_germandate;
	if (itm.match(/^[?$]/)) sortfn = ts_sort_currency;
	if (itm.match(/^[\d\.]+$/)) sortfn = ts_sort_numeric;
	projex.SORT_COLUMN_INDEX = column;
	var firstRow = new Array();
	var newRows = new Array();
	var i, j;
	for (i=0;i<table.rows[0].cells.length;i++) { firstRow[i] = table.rows[0][i]; }
	for (j=1;j<tlen;j++) { newRows[j-1] = table.rows[j]; }

	newRows.sort(sortfn);
	var ARROW = '';
	if (span.getAttribute("sortdir") == 'down') {
		ARROW = projex.ARROWUP;
		newRows.reverse();
		span.setAttribute('sortdir','up');
	} else {
	ARROW = projex.ARROWDOWN;
	span.setAttribute('sortdir','down');
	}

	var nlen = newRows.length;
	// We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
	// don't do sortbottom rows
	for (i=0;i<nlen;i++) { if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) table.tBodies[0].appendChild(newRows[i]);}
	// do sortbottom rows only
	for (i=0;i<nlen;i++) { if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) table.tBodies[0].appendChild(newRows[i]);}

	// Delete any other arrows there may be showing
	var allspans = document.getElementsByTagName("span");
	for ( ci = 0, clen = allspans.length; ci < clen ;ci++) {
		if (allspans[ci].className == 'sortarrow') {
			if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
				allspans[ci].innerHTML = '';
			}
		}
	}
	span.innerHTML = ARROW;
	if (lnk.getAttribute("sort")) {
		table.setAttribute("sortby", lnk.getAttribute("sort"));
		if (span.getAttribute("sortdir") == 'up')
			table.setAttribute("sortdir", 'DESC');
		else
			table.setAttribute("sortdir", 'ASC');
	}
	__table_limit(table);
}

function getParent(el, pTagName) {
	if (el == null) return null;
	else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase())	// Gecko bug, supposed to be uppercase
		return el;
	else
		return getParent(el.parentNode, pTagName);
}

function ts_sort_date(a,b) {
	// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
	aa = ts_getInnerText(a.cells[projex.SORT_COLUMN_INDEX]);
	bb = ts_getInnerText(b.cells[projex.SORT_COLUMN_INDEX]);
	/*
	aas = aa.split(', ');
	if (aas.length > 1)
		aa = aas[1];
	bbs = bb.split(', ');
	if (bbs.length > 1)
		bb = aas[1];
	*/
	if (aa.length == 10) {
		dt1 = aa.substr(6,4)+aa.substr(3,2)+aa.substr(0,2);
	} else {
		yr = aa.substr(6,2);
		if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
		dt1 = yr+aa.substr(3,2)+aa.substr(0,2);
	}
	if (bb.length == 10) {
		dt2 = bb.substr(6,4)+bb.substr(3,2)+bb.substr(0,2);
	} else {
		yr = bb.substr(6,2);
		if (parseInt(yr) < 50) { yr = '20'+yr; } else { yr = '19'+yr; }
		dt2 = yr+bb.substr(3,2)+bb.substr(0,2);
	}
	if (dt1==dt2) return 0;
	if (dt1<dt2) return 1;
	return -1;
}

function ts_sort_germandate(a,b) {
	// y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
	var aa = ts_getInnerText(a.cells[projex.SORT_COLUMN_INDEX]);
	var bb = ts_getInnerText(b.cells[projex.SORT_COLUMN_INDEX]);
	var d1 = aa.replace(/.*?(\d\d)\.(\d\d)\.(\d\d\d\d)/, "$3$2$1");
	var d2 = bb.replace(/.*?(\d\d)\.(\d\d)\.(\d\d\d\d)/, "$3$2$1");
	if (d1 > d2)
		return 1;
	if (d1 == d2)
		return 0;
	return -1;
}

function ts_sort_currency(a,b) {
	var aa = ts_getInnerText(a.cells[projex.SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
	var bb = ts_getInnerText(b.cells[projex.SORT_COLUMN_INDEX]).replace(/[^0-9.]/g,'');
	return parseFloat(aa) - parseFloat(bb);
}

function ts_sort_numeric(a,b) {
	var aa = parseFloat(ts_getInnerText(a.cells[projex.SORT_COLUMN_INDEX]));
	if (isNaN(aa)) aa = 0;
	var bb = parseFloat(ts_getInnerText(b.cells[projex.SORT_COLUMN_INDEX]));
	if (isNaN(bb)) bb = 0;
	return aa-bb;
}

// lower case string including ae, oe, ue
function utf8lower(s) {
	s = s.toLowerCase();
	s = s.replace(/\xE4/, 'ae');
	s = s.replace(/\xF6/, 'oe');
	s = s.replace(/\xFC/, 'ue');
	s = s.replace(/\xDF/, 'ss');
	return s;
}

function ts_sort_caseinsensitive(a,b) {
	var aa = utf8lower(ts_getInnerText(a.cells[projex.SORT_COLUMN_INDEX]));
	var bb = utf8lower(ts_getInnerText(b.cells[projex.SORT_COLUMN_INDEX]));
	if (aa==bb) return 0;
	if (aa<bb) return -1;
	return 1;
}

function ts_sort_default(a,b) {
	var aa = ts_getInnerText(a.cells[projex.SORT_COLUMN_INDEX]);
	var bb = ts_getInnerText(b.cells[projex.SORT_COLUMN_INDEX]);
	if (aa==bb) return 0;
	if (aa<bb) return -1;
	return 1;
}

// set default in select box
function projex_selectDefault(formname, fieldname, value) {
	var dd = document.forms[formname].elements[fieldname];
	if (!dd)
		return;
	__projex_selectDefault(dd, value);
}

function projex_selectDefaultId(id, value) {
	var dd = projex_e(id);
	if (dd)
		__projex_selectDefault(dd, value);
}

function __projex_selectDefault(dd, value) {
	for(var i = 1; i < dd.options.length; i++) {
		if (dd.options[i].value == value) {
			dd.options.selected = true;
			dd.value = dd.options[i].value;
			dd.selectedIndex = i;
			break;
		}
	}
}

function pi_openPrintPopup(url, tableid) {
	if (tableid) {
		/* append current selection to url */
		var table = projex_e(tableid);
		var dyn = 1*table.getAttribute('dynamic');
		if (table) {
			var u = '';

			var filter = string2assoc(table.getAttribute("filter"));
			var newfilter = new Array();
			for(var fk in filter) {
				if (!filter[fk])
					continue;
				if (dyn) {
					newfilter[fk] = filter[fk];
					continue;
				}
				if (fk.length > 1)
					continue;
				if (table.rows[0].cells[fk].getAttribute('sort')) {
					var col = table.rows[0].cells[fk].getAttribute('sort');
					newfilter[col] = filter[fk];
				}

			}
			for(var fk in newfilter)
				u += '/' + fk + '=' + encodeURIComponent(newfilter[fk]);

			var sortby = table.getAttribute("sortby");
			var sortdir = table.getAttribute("sortdir");
			var limit = 1*table.getAttribute("limit");
			var limitstart = 1*table.getAttribute("limitstart") - 1;
			if (limit)
				u += '/__limit__=' + limit;
			if (limitstart)
				u += '/__limitstart__=' + limitstart;
			if (sortby)
				u += '/__sortby__=' + sortby;
			if (sortdir)
				u += '/__sortdir__=' + sortdir;
			url += '/__filter__=' + encodeURIComponent(u);
		}

	}
	var myWin = window.open(url,'mywindow','width=800,height=600,scrollbars,menubar');
	myWin.focus();
}

function date_getlonly(usdate) {
	return date_getlonly2(usdate, false);
}

function date_getlonly2(usdate, noday) {
// YYYY-MM-DD HH:MM:SS
	var x = usdate.split(' ');
	var d = x[0].split('-');
	var jd = new Date(d[0], d[1] - 1, d[2]);
	var day = '';
	if (!noday)
		day = projex.weekdays[jd.getDay()] + ', ';
	return day + d[2] + '.' + d[1] + '.' +
		d[0];
}

function date_timeonly(usdate) {
// YYYY-MM-DD HH:MM:SS
	var x = usdate.split(' ');
	return x[1];
}

function date_getl(usdate) {
	var x = usdate.split(' ');
	var d = x[0].split('-');
	var t = x[1].split(':');
	var jd = new Date(d[0], d[1] - 1, d[2]);
	return projex.weekdays[jd.getDay()] + ', ' + d[2] + '.' + d[1] + '.' +
		d[0] + ' ' + t[0] + ':' + t[1];
}

/*
   name - name of the cookie
   value - value of the cookie
   [expires] - expiration date of the cookie
   (defaults to end of current session)
   [path] - path for which the cookie is valid
   (defaults to path of calling document)
   [domain] - domain for which the cookie is valid
   (defaults to domain of calling document)
   [secure] - Boolean value indicating if the cookie transmission requires
   a secure transmission
 * an argument defaults when it is assigned null as a placeholder
 * a null placeholder is not required for trailing omitted arguments
 */

function setCookie(name, value, expires, path, domain, secure) {
	var curCookie = name + "=" + escape(value) +
		((expires) ? "; expires=" + expires.toGMTString() : "") +
		((path) ? "; path=" + path : "") +
		((domain) ? "; domain=" + domain : "") +
		((secure) ? "; secure" : "");
	document.cookie = curCookie;
}

function setCookieLocal(name, value) {
	var d = new Date();
	d.setTime(d.getTime() + 86400000);
	var n = document.URL.split(/\?/);
	name = encodeURIComponent(n[1] + '/' + name);
	setCookie(name, value);
}

function getCookieLocal(name) {
	var n = document.URL.split(/\?/);
	name = encodeURIComponent(n[1] + '/' + name);
	var r = getCookie(name);
	return r;
}


/*
   name - name of the desired cookie
   return string containing value of specified cookie or null
   if cookie does not exist
 */
function getCookie(name) {
	var dc = document.cookie;
	var prefix = name + "=";
	var begin = dc.indexOf("; " + prefix);
	if (begin == -1) {
		begin = dc.indexOf(prefix);
		if (begin != 0) return null;
	} else
		begin += 2;
	var end = document.cookie.indexOf(";", begin);
	if (end == -1)
		end = dc.length;
	return unescape(dc.substring(begin + prefix.length, end));
}


/*
   name - name of the cookie
   [path] - path of the cookie (must be same as path used to create cookie)
   [domain] - domain of the cookie (must be same as domain used to
   create cookie)
   path and domain default if assigned null or omitted if no explicit
   argument proceeds
 */
function deleteCookie(name, path, domain) {
	if (getCookie(name)) {
		document.cookie = name + "=" +
			((path) ? "; path=" + path : "") +
			((domain) ? "; domain=" + domain : "") +
			"; expires=Thu, 01-Jan-70 00:00:01 GMT";
	}
}
/**
 * deals with the issue of possible javascript highjacking
 * in the public scope. though this doesn't apply to ceemes backend
 * apps, it has been implemented for testing issues.
 * see: http://www.fortifysoftware.com/servlet/downloads/public/JavaScript_Hijacking.pdf
 */
function decode_json(txt) {
	if (txt.substr(0,2) == "/*") {
		txt = txt.substr(2, txt.length);
		txt = txt.substr(0, (txt.length-2));
	}
	return txt;
}

/**
 * Search val in arr, return idx or -1 on failure
 * @param {Array} arr
 * @param {String} val
 */
function array_search(arr, val) {
	for(i = 0; i < arr.length; i++) {
		if (arr[i] == val)
			return i;
	}
	return -1;
}

var projex = {

	weekdays : ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
	SORT_COLUMN_INDEX: null,
	ARROWUP: '^',
	ARROWDOWN: '+',
	IDCOUNT: 0,
	Tabs: null,

	// provide an object for miscelleanous assignements
	// that need to be accessible globally
	// projex.values.mycustomGlobalVar = 'foo'
	values: {},

	// everything in here is loaded on window.loaded
	onWindowLoad: function() {
		// if projex.tabs.js is included, tabs will be loaded
		if (typeof DT == 'function') {
			projex.Tabs = new DT();
			projex.Tabs.init();
			projex.Tabs.updateHeight( 'auto' ); // 20081222 balbrecht changed. fixes double scrollbar.
		}

		// prepare ajax tables
		sortables_init();
		// remove autocomplete data on forms that have 'autocomplete="off"'
		projex.utils.preventAutocomplete();
		// group select lists if any select.name.contains('layout') == true
		projex.selectGrouper.init();
		projex.utils.enable();
		// page menues
		projex.pagemenu.init();

	},

	bind : function(fn, scope) {

		var scope = scope || window, args = [], i, len;
		for (i=2, len = arguments.length; i < len; ++i)
	        args.push(arguments[i]);
	    return function(event) {
	    	fn.apply(scope, [( event || window.event)].concat(args));
		}
	},

	/**
	 * @param content {STRING}
	 * @param mode {STRING}
	 */
	replace : function(content, mode) {

		switch (mode) {
			case 'ceemes_href':
				var rcontent = '';
				var regex = /href:.+[0-9a-zA-Z]\)/g;
				 	while ( (m = regex.exec(content)) != null) {

				 		var link = '', name = /\((.+)\)/.exec(m[0]), href = /href:(.+)\(/.exec(m[0]);

				 		if (name[1]) name = name[1];
				 		if (href[1]) href = href[1];

				 		if (name != '' && href != '') {
				 			link = '<a href="'+href+'" target="_blank"/>'+name+'</a>';
				 			rcontent += content.substr(0, (m.index + m[0].length)).replace(/href:.+[0-9a-zA-Z]\)/g, link);
				 			content = content.substr(m.index + m[0].length);
				 		}
				 	}
					return rcontent.concat(content);
				break;

			case 'linebreaks':
				return content.replace(/\n/g, '<br />');
			case 'komma2linebreaks':
				return content.replace(/\,\s/g, '<br />');
		}
	}
}

projex.pagemenu = {

	init: function() {
		var openlist = null;
		var wrapper = projex_e('page-nav');
		var menues = projex.dom.getByClass('menu', wrapper, 'ul', function(menu) {
			var toplinks = projex.dom.getByClass('top', menu, 'a', function(opener) {
				var ul = projex.dom.nextElement(opener);

				if (typeof projex.values.isIE6 !== 'undefined')
					projex.utils.fixIE6Overlays(ul);

				addEvent(menu, 'mouseover', function() {
					projex_hideElement(openlist);
					projex_viewElement(ul);
					openlist = ul;
				});
				addEvent(menu, 'mouseout', function(e) {
					if (projex.evt.get(e).t.nodeName == 'SELECT')
						return false;

					projex_hideElement(ul);
				});
			});
		});
	}
}

projex.treemenu = {

	ready: false,
	context: null,
	node: null,
	page: 0,
	timeout: null,

	add: function(id) {
		var treeicon = projex_e(id);
		addEvent(treeicon, 'mouseup', projex.bind(this.init, this));
	},

	init: function(e) {

		// detect right click
		if (projex.evt.rightclick(e)) {
			var evt = projex.evt.get(e);
			// save element that was clicked
			this.node = evt.t
			if (this.node.tagName.toLowerCase() == 'a')
				this.node = this.node.parentNode;

			// extract page id from element attribute
			var idstr = this.node.id.split('__p_')[1];

			if (/\_/.exec(idstr) == null)
				this.page = idstr;
			else {
				var idset = idstr.split('_');
				this.page = idset[idset.length-1];
			}
			this.get();
		}
	},

	get: function() {

		if (!this.ready) {

			// menu container
			var context = projex.dom.createElement('div', {className: 'context-menu'});

			// option deltree
			var deltree = projex.dom.createElement('a', {innerHTML: 'Delete tree', href: '#'});
			addEvent(deltree, 'click', projex.bind(this.deltree, this));

			// option copytree
			var copytree = projex.dom.createElement('a', {innerHTML: 'Copy tree', href: '#'});
			addEvent(copytree, 'click', projex.bind(this.copytree, this));

			// option open page, can be used to view page in a new tab STRG magic ;)
			var viewpage = projex.dom.createElement('a', {innerHTML: 'Open page', href: '#', target: 'Text'});
			addEvent(viewpage, 'click', projex.bind(this.viewpage, this, viewpage));

			// append to body
			context.appendChild(deltree);
			context.appendChild(copytree);
			context.appendChild(viewpage);
			document.body.appendChild(context);
			this.context = context;
			this.ready = true;
		}
		this.show();
	},


	show:function() {

		this.node.pos = projex.dom.getXY(this.node);

		var me = this;
		var style = this.context.style;
		var dim = projex.dom.getDimensions(this.context);
		style.left = (this.node.pos.left + 18) + "px";
		style.top  = (this.node.pos.top - dim.height - 2) + "px";
		style.display = "block";

		if (this.timeout)
			clearTimeout(this.timeout);

		this.timeout = setTimeout(function() {
			me.context.style.display = 'none';
			me.timeout = null;
		}, 4000);
	},

	deltree: function(obj) {
		return window.open('?page/deltreeform/'+this.page, '', 'width=640,height=480');
	},

	copytree: function() {
		return window.open('?page/copytreeform/'+this.page, '', 'width=640,height=480');
	},

	viewpage: function(e, anker) {
		anker.href = '?page/show/'+this.page;
	}
};

projex.input = {

	/**
	 * get caret position
	 * @return Object
	 */
	getCaretPosition: function(obj) {
		var start = -1;
		var end = -1;
		if (typeof obj.selectionStart !== 'undefined') {
			start = obj.selectionStart;
			end = obj.selectionEnd;
		} else if (document.selection && document.selection.createRange) {
			var M = document.selection.createRange();
			var Lp;
			try {
				Lp = M.duplicate();
				Lp.moveToElementText(obj);
			} catch(e) {
				Lp = obj.createTextRange();
			}
			Lp.setEndPoint("EndToStart",M);
			start = Lp.text.length;
			if (start > obj.value.length)
				start = -1;

			Lp.setEndPoint("EndToStart",M);
			end = Lp.text.length;
			if (end > obj.value.length)
				end = -1;
  		}
  		return {'start':start,'end':end};
	},

	/**
	 * set caret to given position
	 */
	setCaret: function(obj,l) {
  		obj.focus();
		if (obj.setSelectionRange)
    		obj.setSelectionRange(l,l);
  		else if(obj.createTextRange) {
			m = obj.createTextRange();
			m.moveStart('character',l);
			m.collapse();
			m.select();
		}
	}
}

projex.table = {

	addHover : function(tableObject) {
		var rows = tableObject.getElementsByTagName('tr');
		for ( var i = 1, len = rows.length; i < len ;i++) {
			var currentRow = rows[i];
			if (currentRow.hasHover)
				continue;
			addEvent(currentRow,'mouseover',function() { projex.dom.addClass(this,'over'); });
			addEvent(currentRow,'mouseout',function() { projex.dom.removeClass(this,'over'); });
			currentRow.hasHover = true;
		}
	},

	/*
     * @param obj DOM/Element(Input) that hold the search string
     * @param column String which column to search
     * params:
     * {
     *     cell: 1 // defines the column to search in
     * }
     */
    search: function(obj, column) {

        if ( !projex.table.element )
            return;

        var replace = true;
        var search = new RegExp(obj.value);
        var len = projex.table.element.rows.length;

        // reset table if we come back to zero length
        if ( obj.value.length == 0 )
            __table_limit(projex.table.element);

        // no search for single chars
        if ( obj.value.length < 2 )
            replace = false;

        for ( i = len-1; i > 0; i-- ) {

            var row = projex.table.element.rows[i];
            var cell = row.cells[column];

            // restore orig innerHTML
            if (typeof cell.backup === 'undefined') cell.backup = cell.innerHTML;
            else cell.innerHTML = cell.backup;

            // only do replacements if neccessary
            if ( replace ) {

                var match = search.exec(cell.innerHTML);

                if ( match != null ) {

                    cell.innerHTML = cell.innerHTML.replace(match[0], '<span style="color: red;">'+match[0]+'</span>');;
                    if (projex.dom.hasClass(row, 'hiddenrow'))
                        row.className = 'visiblerow';

                } else {
                    if (projex.dom.hasClass(row, 'visiblerow'))
                        row.className = 'hiddenrow';
                }
            }
        }
    },

    deleteRow: function(el, table) {
    	var _table = table || projex.table.element;
    	var limit = parseInt(_table.limit) || 10;
    	for (var i = 1; i < limit+1; i++) {
    		if (el == _table.rows[i]) {
    			_table.deleteRow(i);
    		}
    	}
	}
}

projex.dom = {


	add : function(element) {
		document.body.appendChild(element);
	},

	hasClass : function(el, classname) {
		if (el.className) {
			var a = el.className.split(' ');
			var strUpper = classname.toUpperCase();
			for ( var i = 0; i < a.length; i++ ) {
				if (a[i].toUpperCase() == strUpper)
					return true;
			}
		}
		return false;
	},

	addClass : function (el, classname) {
		if (el.className) {
			var a = el.className.split(' ');
			a[a.length] = classname;
			el.className = a.join(' ');
		} else
			el.className = classname;
	},

	removeClass : function (el, classname) {
		if (el.className) {
      		var a = el.className.split(' ');
			var strUpper = classname.toUpperCase();
			for ( var i = 0; i < a.length; i++ ) {
				if ( a[i].toUpperCase() == strUpper ) {
					a.splice(i, 1);
					i--;
				}
			}
			el.className = a.join(' ');
		}
	},

	nextElement : function(el) {

		if (el.nextSibling == null)
			 return el;
		else if (typeof el.nextSibling.tagName != 'undefined')
			return el.nextSibling;
		else
			return this.nextElement(el.nextSibling);

	},

	prevElement : function(el) {

		if (el.previousSibling == null) return el;
		if (typeof el.previousSibling.tagName != 'undefined')
			return el.previousSibling;
		else
			return this.prevElement(el.previousSibling);
	},

	/**
	 * @param {String} type the element type name
	 * @param {Object} attributes object
	 * @param {Array} children array of dom nodes to be appended to this element
	 */
	createElement : function(type, attributes, children) {

		if (!attributes) {
			var attributes = {
				styles: {}
			}
		}
		// if 'type' is not a string, browser will throw exception
		var element = document.createElement(type);

		// set properties to element
		for (var property in attributes) {
			if (property == 'content' || property == 'styles')
				continue;
			else if (property == 'className') {
				projex.dom.addClass(element, attributes[property]);
				continue;
			} else if (property == 'checked' && ( attributes[property] == 'checked' || attributes[property] == 'true' || attributes[property] === true )) {
				element.checked = true;
				element.defaultChecked = true;
				continue;
			}
			 else
				element[property] = attributes[property];
		}
		// set style attributes
		for (var style in attributes.styles)
			element.style[style] = attributes.styles[style];

		// apply content to element
		if (typeof attributes.content === 'string')
			element.appendChild(document.createTextNode(attributes.content));
		else if (typeof attributes.content === 'object')
			element.appendChild(attributes.content);
		else if (typeof attributes.innerHTML !== 'undefined')
			element.innerHTML = attributes.innerHTML;

		// append children if given
		else if (children) {
			for (var i = 0; i < children.length; i++) {
				if (children[i] != null && typeof children[i] === 'object')
					element.appendChild(children[i]);
			}
		}
		return element;
	},

	/**
	 * get x,y position of an element
	 */
	getXY : function(element) {
		var obj = projex_e(element);
		var x = 0, y = 0;
		if (obj.offsetParent) {
			x = obj.offsetLeft
			y = obj.offsetTop
			while (obj = obj.offsetParent) {
				x += obj.offsetLeft
				y += obj.offsetTop
			}
		}
		return {left:x,top:y,x:x,y:y};
	},

	getDimensions : function(element) {
		var display = element.style.display;
		if (display == 'block')
			return { width: element.offsetWidth, height: element.offsetHeight };
		var els = element.style;
		var originalVisibility = els.visibility;
		var originalPosition = els.position;
		var originalDisplay = els.display;
		els.visibility = 'hidden';
		els.position = 'absolute';
		els.display = 'block';
		var originalWidth = element.offsetWidth;
		var originalHeight = element.offsetHeight;
		els.display = originalDisplay;
		els.position = originalPosition;
		els.visibility = originalVisibility;
		return { width: originalWidth, height: originalHeight };
	},

	/**
	 * @setOpacity
	 * @param {Object} o element to apply opacity settings
	 * @param {Number} v value 0-100
	 */
	setOpacity : function(o, v) {
		o = o.style;
		o.opacity = (v / 100);
		o.MozOpacity = (v / 100);
		o.KhtmlOpacity = (v / 100);
		o.filter = "alpha(opacity=" + v + ")";
	},

	/**
	 * get elements by classname.
	 * @param {String} searchClass class name to search
	 * @param {Object} node DOM node [optional]
	 * @param {String} tag tag name [optional]
	 * @param {Object} callback function that retrieves each element as argument
	 */
	getByClass : function(searchClass,node,tag, callback) {
		var classElements = new Array();
		if ( node == null )
			node = document;
		if ( tag == null )
			tag = '*';
		var els = node.getElementsByTagName(tag);
		var elsLen = els.length;
		var pattern = new RegExp('(^|\\s)'+searchClass+'(\\s|$)');
		var i,j;
		for (i = 0, j = 0; i < elsLen; i++) {
			if ( pattern.test(els[i].className) ) {
				classElements[j] = els[i];
				if (callback)
					eval(callback(els[i]));
				j++;
			}
		}
		return classElements;
	},

	/**
	 * get a windows scroll position
	 */
	getScrollPosition : function( _w ) {
		var w = _w || window, x ,y;
		if (w.pageYOffset) {
			x = w.pageXOffset;
			y = w.pageYOffset;
		} else if (w.document.body.scrollTop) {
			x = w.document.body.scrollLeft;
			y = w.document.body.scrollTop;
		} else if (w.document.documentElement.scrollTop) {
			x = w.document.documentElement.scrollLeft;
			y = w.document.documentElement.scrollTop;
		}
		return {top:y,left:x,x:x,y:y};
	},

	getWindowDimension : function( _w ) {
		var w = _w || window, x ,y;
		// all but ie
		if (w.innerHeight) {
			x = w.innerWidth;
			y = w.innerHeight;
		}
		// ie strict mode
		else if (w.document.documentElement && w.document.documentElement.clientHeight) {
			x = w.document.documentElement.clientWidth;
			y = w.document.documentElement.clientHeight;
		}
		// quirksmode/other
		else if (w.document.body) {
			x = w.document.body.clientWidth;
			y = w.document.body.clientHeight;
		}
		return {width:x,height:y,x:x,y:y}
	},

	insertAfter : function(targetElement, newElement) {
		var parent = targetElement.parentNode;
		if (parent.lastChild == targetElement)
			parent.appendChild(newElement);
		else
			parent.insertBefore(newElement,targetElement.nextSibling);
	},

	insertBefore : function(targetElement, newElement) {
		var parent = targetElement.parentNode;
		parent.insertBefore(newElement,targetElement);
	}

}

projex.evt = {

	// a reference to the latest event target element
	lastTarget: null,

	rightclick: function(e) {

		var rightclick;
		if (!e) var e = window.event;
		if (e.which) rightclick = (e.which == 3);
		else if (e.button) rightclick = (e.button == 2);

		return rightclick;
	},

	/**
	 * get event attributes in a cross browser way
	 */
	get : function(e) {
		var keyCode, target;
		if (!e) e = window.event;
		if (e.keyCode) keyCode = e.keyCode;
		else if (e.which) keyCode = e.which;
		if (e.target) target = e.target;
		else if (e.srcElement) target = e.srcElement;
		if (target.nodeType == 3) // defeat Safari bug
			target = target.parentNode;
		return { e:e, c:keyCode, t:target }
	},

	/**
	 * get event position
	 */
	getXY : function(e) {
		var event = e || window.event, x = 0, y = 0;
		if (event.pageX || event.pageY)	{
			x = event.pageX;
			y = event.pageY;
		} else if (event.clientX || event.clientY) {
			x = event.clientX + document.body.scrollLeft;
			y = event.clientY + document.body.scrollTop;
		}
		return {left:x,top:y,x:x,y:y };
	},

	/**
	 * save last events
	 */
	recordTarget: function() {
		// save last click event
		addEvent(document, 'click', function(e) {
			projex.evt.lastTarget = projex.evt.get(e).t;
		});

	}
}

projex.utils = {

	enable: function() {
		projex.utils.enableDatepickers();
		projex.evt.recordTarget();
	},

	/**
	 * this function will correct 'falsy' autocomplete behavior
	 * for defined form elements.
	 *
	 * add attribute "autocomplete" with value "off" to desired
	 * form tag. Scenario will cause browser behavior  to change
	 * ('http://developer.mozilla.org/en/docs/How_to_Turn_Off_Form_Autocompletion')
	 */
	preventAutocomplete: function() {
		// fields we don't want to be autocomleted
		var exFields = ['login', 'password', 'password2'];
		window.setTimeout(function() {
			for (var i=0; i < document.forms.length; i++) {
				var feature = document.forms[i].getAttribute("autocomplete");
				if (feature == null)
					continue;
				for (var j = 0; j < document.forms[i].elements.length; j++) {
					var el = document.forms[i].elements[j];
					if (array_search(exFields, el.name) !== -1) {
						// get html attrib value instead of DOM value !
						var realvalue = el.getAttribute('value');
						var autovalue = el.value;
						if (realvalue != autovalue)
							el.value = '';
					}
				}
			}
		}, 200);
	},

	/**
	 * check/uncheck input[type=checkbox] elements with a certain classname
	 */
	toggleCheckboxByClassname: function(className) {
		var elements = projex.dom.getByClass(className);
		for (var e = 0; e < elements.length; e++)
			elements[e].checked = !elements[e].checked;
	},

	setArticlejump: function(obj) {
		var type = projex_e('switch-article-jump');
		type = type.options[type.selectedIndex].value;
		switch (type) {
			case 'nr': obj.elements['action'].value = 'jump'; break;
			case 'idx':obj.elements['action'].value = 'edit';obj.elements['id'].value = obj.elements['nr'].value; break;
		}
		return true;
	},

	/**
	 * 'input' elements with className 'date-input' will
	 * get an datepicker icon and functionality appended.
	 */
	enableDatepickers: function() {
		var ce = projex.dom.createElement;
		var addpicker = function(input) {

			var opts = {};

			// load calendar popup script if it wasn't included
			if (typeof CalendarPopup !== 'function')
				document.getElementsByTagName("head")[0].appendChild(ce('script', {'type': 'text/javascript', 'src': 'static/default/js/CalendarPopup.js'}));

			opts.ankerId = 'calendar_'+input.name;

			// append calendar link and image element to table cell
			projex.dom.insertAfter(
				input,
				ce('a', {'id': opts.ankerId}, [ce('img', {'src':'static/default/img/calendar.png'})])
			);

			// stupid ie workaround
			var formname = input.form.attributes['name'].nodeValue;
			addPicker(input.name, formname, 'dd.MM.yyyy', opts);
		}
		// find elements by class name an apply callback function
		projex.dom.getByClass('date-input', document, 'input', addpicker);
	},

	/*
	 * centers and window object
	 */
	adjustPopup: function( _window ) {
		var w, h, dim = projex.dom.getWindowDimension(_window);
		w = Math.min(dim.width, screen.availWidth);
		h = Math.min(dim.height, screen.availHeight);
		_window.moveTo((screen.availWidth-w)/2, (screen.availHeight-h)/2);
	},

	fixIE6Overlays: function(element) {

		var s = element.style;
		var src= 'javascript:false;';
		var prop = function(n){return n&&n.constructor==Number?n+'px':n;};
		var html = '<iframe class="bgiframe"frameborder="0"tabindex="-1"src="'+src+'"'+
		               'style="display:block;position:absolute;z-index:-1;'+
			               (s.opacity !== false?'filter:Alpha(Opacity=\'0\');':'')+
					       'top:'+(s.top=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderTopWidth)||0)*-1)+\'px\')':prop(s.top))+';'+
					       'left:'+(s.left=='auto'?'expression(((parseInt(this.parentNode.currentStyle.borderLeftWidth)||0)*-1)+\'px\')':prop(s.left))+';'+
					       'width:'+(s.width=='auto'?'expression(this.parentNode.offsetWidth+\'px\')':prop(s.width))+';'+
					       'height:'+(s.height=='auto'?'expression(this.parentNode.offsetHeight+\'px\')':prop(s.height))+';'+
					'"/>';
		element.insertBefore( document.createElement(html), element.firstChild );
	},

	/**
	 * handle result of a del request
	 * remove table row on success.
	 */
	afterRowDelete: function(response) {
		if (response.status) {
			// no errors
			if (parseInt(response.status.code) === 0) {
				// get row
				var row = getParent(projex.evt.lastTarget, 'TR');
				var odd = true;
				var i = 0;
				var blink = window.setInterval(function() {
					if (odd) {
						row.style.background = '#eee';
						odd = false;
					} else {
						row.style.background = '';
						odd = true;
					}
				}, 50);
				setTimeout(function() {
					clearInterval(blink);
					var table = getParent(row, 'TABLE');
					projex.table.deleteRow(row, table);
				}, 1000);
			// error case
			} else if (parseInt(response.status.code) === 1)
				new projex.MessageBox.alert(response.status.suberror);
		}
	}

};

projex.MessageBox = function(element, options) {
	this.options = options || {};
	this.element = projex_e(element);
	this.title = projex.dom.createElement('div', {className: 'title', innerHTML: '<span>'+ (this.options.title || "") +'</span>' });
	this.contentdom = projex.dom.getByClass('box-content', this.element)[0] || projex.dom.createElement('div', {className: 'box-content'});
	this.applyOptions();
	this.setPosition();
};

projex.MessageBox.prototype = {

	setPosition : function() {
		var el = this.element, s = el.style, d = projex.dom, o = this.options;

		if (!d.hasClass(el, "MessageBox"))
			d.addClass(el,"MessageBox");

		s.visibility = "hidden";
		s.display = "block";

		if (o && o.position) {
			s.left = parseInt(o.position.left) + 'px';
			s.top = parseInt(o.position.top) + 'px';
		} else {
			// define vars
			var ws = d.getWindowDimension(),
			es = d.getDimensions(el),
			top = (((ws.height/3)) - (es.height/2));
			// set position
			s.left = ((ws.width/2)) - (es.width/2) + 'px';
			(top < 0) ? (s.top = 10 + 'px') : (s.top = top + 'px');
		}

		window.setTimeout(function() {s.visibility = "visible";}, 100);

	},

	applyOptions: function() {
		// shorthand
		var o = this.options;
		var s = this.element.style;

		// set dimensions of mbox
		if (o.dimension) {
			o.dimension.width ? (s.width = o.dimension.width) : s.width = s.width;
			o.dimension.height ? (s.height = o.dimension.height) : s.height = s.height;
		}
		// get box type, set default
		if (o.boxtype)
			this.addInterface(o.boxtype);
		else
			this.addInterface('drag-window');
	},

	addInterface: function(type) {

		switch (type) {

			case 'drag-window':

				if (this.element.isMessageBox)
					break;

				projex.dom.addClass(this.element, 'drag-window');

				var close = projex.dom.createElement('div', {className:'close', innerHTML: '<img src="static/default/img/mbox_close.gif"/>'});
				var drag = new projex.drag(this.element, this.title);
				var pane = projex.dom.createElement('div', {className:'pane'}, [this.title, close]);

				addEvent(close, 'click', projex.bind(this.close, this));
				this.element.insertBefore(pane, this.element.firstChild);
				this.element.isMessageBox = true;
				break;

			default:
				break;
		}
	},

	flush: function() {
		while (this.contentdom.childNodes[0])
		  this.contentdom.removeChild(this.contentdom.childNodes[0]);

	},

	close: function(e) {
		this.element.style.display = 'none';

		if (typeof this.options.onClose === 'function')
			this.options.onClose();
	},

	erase: function(e) {
		this.element.style.display = 'none';
	},

	registerCloseEvent: function() {
		var el = projex_e(this.options.closeElement);
		if (el)
			addEvent(el, this.options.closeEvent || 'click', projex.bind(this.close, this));
	}
};

projex.MessageBox.alert = function(message) {

	// build dom nodes, prepare content
	var content = projex.dom.createElement('div', {className: 'box-content'}, [projex.dom.createElement('p', {'content': message})]);
	var container = projex.dom.createElement('div', {}, [content]);
	projex.dom.add(container);

	// display
	new projex.MessageBox(container, { boxtype: 'drag-window' });

};

projex.drag = function(node, trigger, axis) {
	if (!node || !trigger)	return;
	this.trigger = trigger;
	this.node = node;
	this.axis = axis;
	this.dragMoveEvent = projex.bind(this.dragGo, this);
	this.dragStopEvent = projex.bind(this.dragStop, this);

	// define dummy custom event functions
	this.listeners = {
		'onStart': function(e) {},
		'onDrop' : function(e) {},
		'onEnd'  : function(e) {}
	}

	addEvent(this.trigger, 'mousedown', projex.bind(this.dragStart, this));
};

projex.drag.prototype = {

	dragStart : function(e) {
		if (projex.evt.rightclick(e))
			return false;

		e = projex.evt.getXY(e);
		addEvent(document, 'mousemove', this.dragMoveEvent);
		addEvent(document, 'mouseup', this.dragStopEvent);
		this.listeners.onStart(e);
		var elPos = projex.dom.getXY(this.node);
		this.cursorStartX = e.x;
		this.cursorStartY = e.y;
		this.elStartLeft  = elPos.left;
		this.elStartTop   = elPos.top;
	},

	dragGo : function(e) {
		e = projex.evt.getXY(e);
	//	stopEvent(e);
		if (typeof this.axis !== 'undefined') {
			if (this.axis == 'x') this.node.style.left = (this.elStartLeft + e.x - this.cursorStartX) + "px";
			if (this.axis == 'y') this.node.style.top  = (this.elStartTop  + e.y - this.cursorStartY) + "px";
		} else {
			this.node.style.left = (this.elStartLeft + e.x - this.cursorStartX) + "px";
			this.node.style.top  = (this.elStartTop  + e.y - this.cursorStartY) + "px";
		}
	},

	dragStop : function(e) {
		this.listeners.onDrop(e);
		removeEvent(document, 'mousemove', this.dragMoveEvent);
		removeEvent(document, 'mouseup', this.dragStopEvent);
		this.listeners.onEnd(e);
	}
};

/**
 * Make element lists sortable
 * @param element Object DO node (the element to be moved while dragging)
 * @param dragstart Object DOM node (the element that triggers dragging)
 * @param container Object DOM node/String id (element that holds all items to be sorted)
 * @param axis String [optional] determine whiche drag directions are allowed (x,y)
 */
projex.dragSort = function(element, dragstart, container, axis) {
	this.domNodeType = 'div';
	this.axis = axis;
	this.container = projex_e(container);
	this.dragObject = new projex.drag(element, dragstart, axis);
	this.registerListeners();
}

projex.dragSort.prototype = {

	/**
	 * read out all drag nodes and store relevant information
	 */
	collectDropZoneItems: function() {
		var collection = this.container.getElementsByTagName(this.domNodeType);
		var elements = [];
		var len =  collection.length;
		for (var i=0; i < len; i++) {
			elements.push({
				'el' : collection[i],
				'pos': projex.dom.getXY(collection[i]),
				'dim': projex.dom.getDimensions(collection[i])
			});
		}
		this.collection = elements;
	},

	/**
	 * look for all member nodes in collection. find closest node to
	 * event position.
	 */
	findTarget: function(e) {
		this.collectDropZoneItems();
		var axis = this.axis;
		var drop = projex.evt.getXY(e);
		for (var i=0; i<this.collection.length; i++) {
			var c = this.collection[i];
			// drop event pos axis value is smaller than next elements axis position.
			// insert element before...
			if (drop[axis] < c.pos[axis]) {
				return c;
			}
		}
		// return last element in collection
		return this.collection[this.collection.length-1];
	},
	registerListeners: function() {
		var me = this;
		// override native object properties
		this.dragObject.listeners = {
			'onStart': function(e) {
				projex.dom.addClass(me.dragObject.node, 'dragRow');
			},
			'onDrop' : function(e) {
				projex.dom.insertBefore(me.findTarget(e).el, me.dragObject.node);
				me.resetStyles();
			}
		}
	},
	resetStyles: function() {
		projex.dom.removeClass(this.dragObject.node, 'dragRow');
		this.dragObject.node.style.position = '';
		this.dragObject.node.style.left = '';
		this.dragObject.node.style.top = '';
	}
}



/**
 * display a loading div on top of it's data table.
 * implemented @function '__table_limit'
 */
projex.loader = {
	/**
	 * @param {Object} table the ajax data table
	 * @param {Object} loader html div element
	 */
	init: function(table, loader) {
		this.table = table;
		this.loader = loader;

		projex.dom.addClass(this.loader, 'loadState');

		var position = this.loader.getAttribute("position");
		if (position) {
			position = position.split(',');
			this.loader.style.left =  position[1] + "px";
			this.loader.style.top = position[0] +"px";
		} else {
			this.loader.style.left = projex.dom.getXY(this.table).left +"px";
			this.loader.style.top = projex.dom.getXY(this.table).top +"px";
		}
		projex_viewElement(this.loader,'block');
	},

	/**
	 * @param {String} rs xmlHttpRequest Object readystate property
	 * @param {Number} count projex 'r.length' property
	 */
	update: function(rs, count) {
		if (rs !== 4)
			this.set('block', '<span><img src="static/default/img/loading.gif"/><br/>Loading...</span>');
		else if (rs === 4 && count === 0)
			this.set('block', '<span>keine Daten</span>', 10, true);
		else
			this.set('none', '', 100);
	},

	/**
	 * @param {String} display block/none style property
	 * @param {String} content html content
	 * @param {Number} opacity 0-100
	 */
	set: function(display, content, opacity, timeout) {
		this.loader.style.display = display;
		this.loader.innerHTML = content;
		if (opacity)
			projex.dom.setOpacity(this.table, opacity);
		if (timeout) {
			var loader = this.loader;
			window.setTimeout(function() {
				projex_hideElement(loader);
			}, 2000);
		}
	}
};

/**
 * @projex.selectGrouper
 * parse document, find select lists containing string 'layout' as name.
 * restructure options and warp in optgroups. rebuild list.
 * disable locally:
 * set 'selectGrouper.handle = false' before document is loaded. (inline js)
 * disable globally:
 * remove 'selectGrouper.init()' @load events, default.js
 */
projex.selectGrouper = {

	firstOptionEmpty: false,

	handle: true,

	list: [],

	getSelectElements: function() {
		var doc = document.getElementsByTagName('select');
		var els = [];
		for (var i=0, len = doc.length;i<len;i++) {
			if (doc[i].name.indexOf('layout') != -1)
				els.push(doc[i]);
		}
		return els;
	},

	init: function() {
		if (!this.handle) return;
		var els = this.getSelectElements();
		if (els.length > 0) {
			for (var i=0, len = els.length;i<len;i++)
				this.parseList(els[i]);
		}
	},

	parseList: function(el) {
		// clear list
		this.list = [];

		// improve likelyness that el really is a layout list
		if (el.options.length <= 1)
			return;
		else if (el.options[1]) {
			var s = el.options[1].text.split("/");
			if (s.length == 0)
				return;
		} else if (!el.getAttribute('onChange'))
			return;

		var group = [], i, next, curr, len = el.options.length, catname, existed = false;
		var empty = new Option();

		// parse and sort options
		for ( i = 0; i < len; i++ ) {

			// prepare string arrays of current/next option
			curr = el.options[i].text.split("/");
			catname = curr[curr.length-2];

			// don't care about empty options
			if (typeof catname === 'undefined')
				continue;

			// see if we already have this group (sorting problem ceemes)
			existed = this.appendToGroup(catname, el.options[i]);
			if (existed)
				continue;

			// save option to current group
			group.push(el.options[i]);

			el.options[i+1] ? next = el.options[i+1].text.split("/") : next = [];
			// next option in same group ?
			if (curr[curr.length-2] == next[next.length-2])
				continue;
			// else save group, set name, flush group
			else {
				group.ident = catname;
				if (curr.length > 2)
					curr.pop();
				// save category name string
				group.optname = curr.join(" / ");
				this.list.push(group);
				group = [];
			}
		}
		this.rebuildList(el);
	},

	appendToGroup: function( name, object ) {
		for (var i = 0; i < this.list.length; i++ ) {
			var item = this.list[i];
			if ( item.ident == name ) {
				item.push(object);
				return true;
			}
		}
		return false;
	},

	rebuildList: function(el) {

		var list = this.list;

		// remove all options
		while (el.options.length > 0)
			el.removeChild(el.options[0]);
		el.options[0] = new Option();

		// rebuild options and append to select element wrapped in optgroups
		for ( var i = 0, len = list.length ; i < len; i++) {
			var group = list[i], optgroup, j;
			optgroup = document.createElement('optgroup');
			optgroup.label = group.optname;
			for (j=0;j<group.length;j++) {
				// serve ie && moz
				var option = document.createElement('option');
				if (group[j].defaultSelected) {
					option.selected = true;
					option.defaultSelected = true;
				}
				option.value = group[j].value;
				option.innerHTML = " " + group[j].text.split("/").pop();
				optgroup.appendChild(option);
			}
			el.appendChild(optgroup);
		}
	}
}

/**
 * store all xmlhttp request objects in collection.
 * provide functionality to abort all current requests.
 */
projex.xmlhttp = {

	collection: [],

	push: function(o) {
		this.collection.push(o);
	},

	/**
	 * return number of active request objects (readyState < 4)
	 */
	loading: function() {
		for (var i=0, a = 0, len = this.collection.length; i < len; i++) {
			if (this.collection[i].readyState < 4)
				a+=1;
		}
		return a;
	},

	flush: function() {
		for (var i=0, len = this.collection.length; i < len; i++) {
			this.collection[i].onreadystatechange = function(){};
			this.collection[i].abort();
		}
	},

	cancelRequests: function(e) {
		projex.xmlhttp.flush();
	}
}

function setTopWindowTitle(title) {
	top.window.document.title = title;
}

addEvent(window, 'load', projex.onWindowLoad);
window.onbeforeunload = projex.xmlhttp.cancelRequests;