/**
* -----------------------------------------------------
*					Utilities
* -----------------------------------------------------
*/
setProtos();
function UtilWrapper(d,w) {
	var _x = this
		, Util
		, zval
		, DOM
		, z = 100;
	_x.util = {
		toString: function() {
			return 'xutil is loaded!';
		}
	, zVal: new (function() {
			var _z = this;
			_z.toString = function() {
				return parseInt(z, 10);
			}; /**get equiv*/
			_z.set = function(iAdd) {
				iAdd = iAdd || 0;
				if (iAdd > 0) {
					z += parseInt(iAdd, 10);
				} else {
					z++;
				}
				return z;
			};
	 })()
	, openBoxCache: []	
	, key: function(iLen) {
		var sKey = ''
		, isKey = ''
		, i = 0
		, aRanges = ['48,9', '65,25', '97,25'];
		iLen = !iLen ? 4 : iLen;
		while (i < iLen) {
			var aRange = String(aRanges[Math.round(Math.random() * 2)]).split(',');
			isKey += String(aRanges[Math.round(Math.random() * 2)]) + ',';
			sKey += String.fromCharCode(Math.round(parseInt(aRange[0], 10) +
						(Math.random() * parseInt(aRange[1], 10))));
			i++;
		}
		return sKey;
	}
	, getXY: function(e) {
			e = e || event;
			if (!e) {
				return [100, 100];
			}
			try {
				var wdim = $.DOM.viewPort(), 
					left = e.pageX || (e.clientX + wdim[2]) || 0, 
					top  = e.pageY || (e.clientY + wdim[3]) || 0;
			}
			catch (e) {
				return [0, 0];
			}
			return [left, top];
	}
	, preventDefault: function(e) {
			if (e.preventDefault) {
				e.preventDefault();
			}
			else {
				e.returnValue = false;
		 	}
	}
	, getEvt: function(evt, bPreventDefault) {
		evt = evt || event;
		if (!evt) {
			return { evt: null, src: null, type: null };
		} else {
			if (bPreventDefault) {
				Util.preventDefault(evt);
			}
			var eXY = Util.getXY(evt),
				btt = evt.button || evt.which || null,
				bleft = evt.type.match(/mousedown/i)
					    && (($.appcache.isIe && btt === 1) || btt === 1),
			esrc = (evt.srcElement || evt.target);
			return { evt: evt,
				src: (((esrc || {}).nodeType === 3) ? esrc.parentNode : esrc), //safari fix (ie didn't like it)
				type: evt.type,
				pos: eXY,
				bttn: btt,
				bttnleft: bleft
			};
		} //TODO: button left mousedown detection doesn't seem to work
	}
		//</Util.getEvt>
		/**??*/
	, checkCurrentEvent: function(e, oEl) {
		e = e || event;
		var target = e.srcElement || e.target;
		if (target !== oEl) {
			return false;
		}
		var related = e.relatedTarget || e.toElement;
		while (related != oEl && related.nodeName != 'BODY') {
			related = related.parentNode;
		}
		if (related === oEl) {
			return true;
		}
		return false;
	}
	, eventFire: function(el, etype){
        if (el.fireEvent) {
          (el.fireEvent('on' + etype));
		}  
        else {
          var evObj = document.createEvent('Events');
          evObj.initEvent(etype, true, false);
 		  el.dispatchEvent(evObj);
        }
     }
	 , handle: function(etype, fn, nobubble, el){
       var obj = el,
           fnGo;
       nobubble = nobubble || false;
       if (obj.attachEvent) {
        fnGo = function(e){
          e = e || event;
          e.cancelBubble = nobubble;
          fn.apply(obj, arguments);
        };
        obj.attachEvent('on' + etype, fnGo);
       } else {
        nobubble = nobubble || false;
        fnGo = function(e){
          e = e || event;
          if (nobubble) {e.stopPropagation();}
          fn.apply(obj, arguments);
        };
        obj.addEventListener(etype, fnGo, !nobubble || true);
       }
     }
	 /** 
	  * 12/05/2010
	  * twee functies om type van variabelen te checken
	  * @param {Object} somevar
	  */
	 , whatType: function(somevar){
        return String(somevar.constructor)
              .split(/\({1}/)[0]
              .replace(/^\n/,'').substr(9);
     }
	 , isType: function(variable,type){
 		if ((typeof variable).match(/undefined|null/i) || 
            (type === Number && isNaN(variable)) ){
           return variable
        }
         return variable.constructor === type;
     }
	 , quot: function(inp){
            var inpa = inp.split(':'), ret=[];
            inpa.each(function(item){
                ret.push("'"+item+"'");
            });
            return ret.join(':');
     },
     /**
      * alle diactritische characters in 1 regex,
      * voor bv formuliervelden naam etc.
      */
     diacritics: function(){
        return new RegExp(
             ['[\\.\\-a-z\\s]|',            //.- en spatie
              '[\\300-\\306\\340-\\346]|',  // A, a
              '[\\310-\\313\\350-\\353]|',  // E, e
              '[\\314-\\317\\354-\\357]|',  // I, i
              '[\\322-\\330\\362-\\370]|',  // O, o
              '[\\331-\\334\\371-\\374]|',  // U, u
              '[\\321-\\361]|',             // N, n
              '[\\307-\\347]}',				// C, c
			  '[^\\302]'
			  ]              
             .join(''),'gi');
     }
	};
	Util = _x.util;
	return Util;
};

   /**
    * setProtos: augment native objects. 
    * Loads when loading this library in a scripttag.
    */
  function setProtos() {
            /*
             LEKT!
             Function.prototype.delay = function(delay,args) {
                var args = Array.prototype.slice.call(arguments),
                    timeout = args[0],
                    fn      = this,
                    runner  = function(){
                                 fn.apply(this,Array.prototype.slice.call(args,1));
                              };
                    
                setTimeout(runner, delay*1000);
            };
            
            if (!Function.prototype.then){
                Function.prototype.then = function(callback){
                  (function(){this();callback();})();  
                };
            }*/

            if (!Array.prototype.each) {
                Array.prototype.each = function(fn) {
                    var i = -1, l = this.length;
                    while (++i<l) {
                        fn(this[i]);
                    }
                };
            }
            if( !Array.prototype.clone) {
                Array.prototype.clone = function(){
                    var len = this.length, ret = [];
                    while(len--){
                        ret.push(this[len]);
                    }
                    return ret.reverse();
                }
                
            }
            if (!String.prototype.insert) {
                String.prototype.insert = function(pos, insrt) {
                    var a = this.split('');
                    a.splice(pos, 0, insrt);
                    return a.join('');
                };
            }
            if (!String.prototype.trim) {
                String.prototype.trim = function() {
                    return this.replace(/^\s|^\s+|\s+$|\s$/mg, '');
                };
            }
			if (!String.prototype.htmlEscape) {
				String.prototype.htmlEscape = function(){
					return this.replace(/</g,'&lt;');
				};
			}
            if (!String.prototype.highlight){
                String.prototype.highlight = function(pattern){
                    if (!pattern){
                        return this.tohtml()
                                .replace(/(<span.*?highlight.*?>)(.*?<)(\/span>)/gi,'$2');
                                              /*function(a,b,c){
                                                 //$.DOM.debug(c);
                                                 return c.replace(/</, '');
                                              });*/
                        //return this.tohtml().replace(/(<span class="highlight">)(.*)(<\/span>)/gi,'$2');
                    } else {
                        return this.replace(
                            new RegExp('('+pattern+')','gi'),'<span class="highlight">$1</span>');    
                    }
                };
            }
            if (!String.prototype.tohtml){
                String.prototype.tohtml = function(){
                    return this.replace(/&lt;/g,'<')
                               .replace(/&gt;/g,'>');
                }
            }
            if (!String.prototype.stripHtml){
                String.prototype.stripHtml = function(){
                    return this.replace(/<.*?>/g,'');
                }
            }
            if (!Array.prototype.contains) {
                Array.prototype.contains = function(val) {
                    var i = this.length;
                    while (i--) {
                        if (this[i] === val) {
                            return true;
                        }
                    }
                    return false;
                };
            }
            if (!Date.prototype.add) {
                Date.prototype.add = function(ymd, plus) {
                    /**
                    * optellen/aftrekken van huidige datum
                    * zet datum met optelling van dag, maand
                    * of jaar obv [ymd] en update de
                    * verschillende waarden (en het
                    * kalendertje). One size fits all.
                    * Wordt via loopAdd gestart, niet direct
                    * @param {Boolean} plus
                    * @param {Number} ymd
                    */
                    switch (ymd) {
                        case 1:
                            this.setDate(plus ? this.getDate() + 1 : this.getDate() - 1);
                            break;
                        case 2:
                            this.setMonth(plus ? this.getMonth() + 1 : this.getMonth() - 1);
                            break;
                        case 3:
                            this.setFullYear(plus ? this.getFullYear() + 1 : this.getFullYear() - 1);
                            break;
                        case 4:
                            this.setHours(plus ? this.getHours() + 1 : this.getHours() - 1);
                            break;
                        case 5:
                            this.setMinutes(plus ? this.getMinutes() + 1 : this.getMinutes() - 1);
                            break;
                        default:
                            this.setDate(plus ? this.getDate() + 1 : this.getDate() - 1);
                    }
                    return this;
                };
            }
            if (!String.prototype.trim) {
                String.prototype.trim = function(){
                    return this.replace(/^\s+|\s+$/g,'');
                };
            }
            /** returns string object repeated [iN] times
            *   'repeatme'.repeat(2) returns 'repeatmerepeatme'
            */
            if (!String.prototype.repeat) {
                String.prototype.repeat = function(iN) {
                    var retVal = '';
                    do {
                        retVal += this;
                    } while (--iN && iN > -1);
                    return retVal;
                };
            }
            /** returns number padded with zero's at the left side,
            *  number of zero's dependent on [iBase]
            *  Example: (5).padLeftZero(1000) returns 0005
            */
            if (!Number.prototype.padLeftZero) {
                Number.prototype.padLeftZero = function(iBase) {
                    iBase = iBase && iBase % 10 === 0 ? iBase : 10;
                    var len = String(iBase).length - String(this).length;
                    return this < iBase ? ('0').repeat(len) + this : this;
                };
            }
            if (!Number.prototype.toPx){
                Number.prototype.toPx = function() {
                    return String(this.toFixed(3))+'px';
                }
            }
            if (!Number.prototype.toPercent){
                Number.prototype.toPercent = function() {
                    return String(this.toFixed(3))+'%';
                }
            }

            if (!Date.prototype.toFormat) {
                Date.prototype.toFormat = function(formatString) {
                    //NOFORMAT
                    if (!formatString) {
                        return this;
                    }
                    var WEEKDAYS = ('zondag,maandag,dinsdag,woensdag,' +
                            'donderdag,vrijdag,zaterdag').split(','),
            MONTHS = ('januari,februari,maart,april,mei,' +
                        'juni,juli,augustus,september,oktober,' +
                        'november,december').split(','),
            FORMATS = {
                dy: WEEKDAYS[this.getDay()] + ' ' +
                this.getDate(),
                ddy: WEEKDAYS[this.getDay()] + ' ' +
                (this.getDate()).padLeftZero(),
                dd: (this.getDate()).padLeftZero(),
                d: this.getDate(),
                mn: MONTHS[this.getMonth()],
                mm: (this.getMonth() + 1).padLeftZero(),
                m: this.getMonth() + 1,
                yy: String(this.getFullYear()).substr(2),
                yyyy: this.getFullYear(),
                hr: this.getHours(),
                HR: (this.getHours()).padLeftZero(),
                mi: this.getMinutes(),
                MI: (this.getMinutes()).padLeftZero(),
                se: this.getSeconds(),
                SE: (this.getSeconds()).padLeftZero(),
                ms: this.getMilliseconds(),
                MS: (this.getMilliseconds()).padLeftZero(1000)
            },
            formatDT = [formatString.split(/\s|\/|\-|:|HR|MI|SE|MS/i),
                        (formatString.split(/\s/))[(formatString.split(/\s/)).length - 1]],
            formatDate = formatDT[0],
            formatTime = formatDT[1].split(/\:/) || [],
            delim = formatString.match('/') || formatString.match('-') || ' ',
            delimTime = formatString.match(':') || '';
                    //FORMAT
                    if (!delim) {
                        return this;
                    }

                    return (formatDate.length > 0 ? function() {
                        var aD = [];
                        for (var i = 0; i < formatDate.length; i++) {
                            if (formatDate[i] !== '') {
                                aD.push(FORMATS[formatDate[i]] || '??');
                            }
                        }
                        return aD.join(delim);
                    } () : '') +
            (formatTime.length > 0 ? (formatDate.length < 1 ? '' : ' ') +
            function() {
                var aT = [];
                for (var i = 0; i < formatTime.length; i++) {
                    if (formatTime[i].match(/se|mi|hr|ms/i)) {
                        aT.push(FORMATS[formatTime[i]] || '??');
                    }
                }
                return aT.join(delimTime) || '';
            } () : '');
                };

                Date.prototype.between = function(mindate, maxdate) {
                    return this >= mindate && this <= maxdate;
                };
            }
    };
/**
 * @author kooi
 */
//XmlHTTPRequest engine (simple constructor with timeout)
 function XHRWrapper(d,w) {
 	var  _x = this
		 ,XHR
	     ,Remote
		 ,aborted
		 ,wto    = null      
		 ,bIE 	 = $.appcache.isIe
		 ,decode = function(sEncodedString) {
                    return decodeURIComponent(sEncodedString).replace(/\&#37;/gi,'%');
         }
		 ,encode = function(sStringToEncode) {
		 			if (!sStringToEncode.replace){return true;}
                    return encodeURIComponent(sStringToEncode.replace(/\%/g,'&#37;'));
         };

	  _x.xhrbasic = {
			 toString : function(){return 'xremotes is loaded'}
						,toString : function(){return 'xremotes is loaded'}
			,rCall: function(sURL, oQ, fnCallback, maxTime,noResponse,doGet){
			  //not very elegant, but working for now
				if (!oQ || !sURL) {
					debug(-1,Array.prototype.splice.call(arguments));
					return 1;
				}
				//defaults
				maxTime = maxTime ? maxTime : 5;
				fnCallBack = fnCallback ? fnCallback : function(){
					return true;
				};
				
				var oRs  = this, 
				   sErr  = '(EN) sorry, your browser can\'t process this page as it should\n\n' +
				          '(NL) sorry, uw internet-programma kan deze pagina niet goed verwerken', 
					  t  = null, 
				   iSec  = 0, 
				    now  = new Date().getTime(), 
			    sHeader  = 'Content-Type', 
			 sHeaderVal  = 'application/x-www-form-urlencoded',
			  sProtocol  = doGet ? 'GET' : 'POST',
	   	    serializeXML = function(oResp){ 
								var xDoc = new $.xmlparser(oResp);
	   	         				return xDoc;
	   					   },
       serializeResponse = function(/*string*/sResp){
					if (!sResp) {
						return false;
					}
					var aR = sResp.split('#!#'); 
					return {
						error: decode(aR[0]),
						data:  decode(aR[1])
					};
				};
				
				aborted = false;
				
				/**serialize query to proper querystring*/
				oQ.serialize = function(){ 
					var aRet = [], t = this;
					for (var lbl in t) {
						if (lbl !== 'serialize' && t.hasOwnProperty(lbl)) {
							aRet.push(lbl + '=' + encode(t[lbl]));
						}
					}
					if (location.href.match(/fortests/i)){
						oQ.fortest = 1;
					}
					oQ.serialized = true;
					return aRet.join('&');
				};
				
				/**setup xmlHttp or quit w/mssg*/
				try {
					var oHttp = bIE 
					 ? new ActiveXObject("Msxml2.XMLHTTP") || new ActiveXObject("Microsoft.XMLHTTP") 
					 : new XMLHttpRequest();
				} 
				catch (e) {
					alert(sErr + '\n' + e.message);
					return false;
				}
				
				/**private:void/set callback/readystate-action w/timeout*/
				function onReady(){
					var oResp = null;
					if (oHttp.readyState < 4) {
						var timePassed = new Date().getTime() - now;
						if (timePassed >= maxTime * 1000) {
							alert(['Contact met de server ging verkeerd',
								   'Timeout ' + maxTime + ' seconden overschreden: ',
								   'afgebroken.',
								   'Duurt te lang: server mogelijk offline, ',
								   'probeer later nog eens...'].join('\n')
							      );
							aborted = true;
							oHttp.abort();
							oHttp = null;
						}
					} else {
						if (aborted) {
							oHttp = null;
							return true;
						}
						if (String(noResponse).match(/parsex/i) || !noResponse)  {
							if (String(noResponse).match(/parsex/i)){
								var xDoc = new $.xml.wrapper(oHttp.responseXML); 
								oResp.data  = xDoc.getFirstNode('XhrData',true);
								oResp.error = xDoc.getFirstNode('XhrError',true);
							} else {
								var response = oHttp.responseText;
								try { oResp  = serializeResponse(response)}
								catch(e){
									oResp = serializeResponse(response);
								}
							}
						}
						if (fnCallback) {
							fnCallback(oResp);
						}
						oHttp = null;
						return 1;
					}
				} /**end set callback*/
				
				/**send the request*/
				oHttp.onreadystatechange = onReady;
				if (location.href.match(/fortests/i) && oQ instanceof Object){
					oQ.fortest = true;	
				}
				oQ = oQ instanceof Object ? oQ.serialize() : oQ;
				sURL += !oQ.serialized && doGet ? '?' + oQ : '';
				oHttp.open(sProtocol, sURL);
				
				if (!doGet){
					oHttp.setRequestHeader(sHeader, sHeaderVal);
					oHttp.send(oQ);
				} else {
					oHttp.send();
				}
			
			}
	  };
	 XHR = _x.xhrbasic;
	 return XHR;
}
/**
*  -----------------------------------------------------
*				   TOOLTIPS stuff
    23/01/2010: refactored. Voor XHR (alles binnen
    1 container) kon het niet meer zo zijn dat de 
    tip-attributen/eigenschappen/methods in een (gewrapt) 
    HTMLObject waren vervat, omdat bij navigeren naar
    een andere 'page' dat HTMLObject verloren gaat.
    Daarom nu alles naar een externe tipcache, met voor
    de tips de id van het object waar de tip moet ver-
    schijnen. Betekent dat het object in kwestie wel
    een id moet hebben. Ofwel tevoren (in de html) erop
    zetten, ofwel zorgen dat id bewaard blijft ondanks
    content-wisseling
*  -----------------------------------------------------
*/

function TipsWrapper(d,w) {
	var _x = this
	, i
	, append = 'append'
	, Tips
	, hideDestroy = false
	, Util
	, DOM
	, Drag
	, debug
	, wait
    , tipcache;
	
	_x.tips = {
		toString: function() { return 'module tips is loaded!'; }
        , openTips: {}
        , tipcache: {}
		, onLoad: function() {
		/** 
		 * adding mouseover handler unobtrusive and only
		 * for elements with a tooltip, and only for elements
		 * we want to have a tooltip.
		 * RK 20090912
		 * RK 20090914: IE-compatible now
		 */
			Util = $.util;
			Drag = $.drag;
			docbody = $(d.body || d.documentElement);
			debug = $.DOM.debug;
			docbody.handle('mouseover',Tips.toolTipInit);
			Tips.onloadDone = true;
            tipcache = Tips.tipcache;
		}
		, renewTip: function(el,txt,reshow){
            var tc = tipcache[el.id],
                oAttr = tc.tipAttributes;
			if (tc && oAttr){
				tc.tipAttributes.txt = txt;
				tc.tipAttributes.tipEl.txt.html(txt);
				if (reshow){
					var f = function(){tc.tipOpen=false; Tips.showToolTip(true,el,1);};
					setTimeout(f,1);
				}
			}
		}
		, setSimpleTip: function(onBox, txt, props, completeString) {
			var attrStr = completeString ? txt :
						"tt:'cursor':'pointer','txt':'" + txt + "'," +
						"'IgnoreMinWidth':'1'";
			if (props) {
				for (var p in props) {
					attrStr += ",'" + p + "':'" + props[p] + "'";
				}
			}
			onBox.setAttribute('title',attrStr);
			onBox.handle('mouseover',Tips.toolTipInit);
		}
		/** 
		 * ---------Edit default tooltip attributes here----------------------
		 * values can be overridden by setting them in the title attribute
		 * -------------------------------------------------------------------
		 */
		, defaults: {
		    sticky: 1				/** tooltip follows mouse movement */
		  , closer: 'X'		  		/** default character or image for close box [try &#8730;]; */
		  , minw: 50				/** minimum tipwidth */
		  , maxw: parseInt(($.DOM.viewPort()[0] / 4), 10) /** maximum tipwidth */
		  , maxh: parseInt(($.DOM.viewPort()[1] / 4), 10) /** maximum tipheight */
		  , ignorewidth: 0			/** 1=ignore min/max tipwidth */
		  , ignoreminwidth: 0		/** 1=ignore minimum tipwidth */
		  , aligntoelement: 0		/** 1=align tip to bottom left of element */
		  , opacity: 0				/** 0.01-0.99 opacity (transparency) of tip */
		  , cursor: 'default'		/** css cursor of the triggering element */
		  , allowfix: 0				/** 1=allow temporary fix (no hide on mouseout) on click */
		  , overflowx: 'hidden'		/** '' or 'auto' sets horizontal overflow (scroll) */
		  , overflowy: 'hidden'		/** '' or 'auto' sets vertical overflow (scroll) */
		  , timeout: 0				/** set timeout after which tip hides itself in seconds */
		  , width: 0				/** Width in pixels */
		  , height: ''				/** Height in pixels e.g. '200px' */
		  , checklen: 0				/** trunctate text to length of this value */
		  , fixed: 0				/** fixed: only allow close on clicking the header */
		  , delay: 0			    /** the time in seconds before the tooltip is showed */
		  , txt: null
		  , txtid: null
		  , isXML: false
		}
		/** ---------End Edit default tooltip attributes-------- */
		
		/** Tips.serializeAttributes START */
		, serializeAttributes: function(oEl, attributesSupplied) {
			
			var cleaned = ((oEl.el||{}).title || oEl.getAttribute('tipDef') ||
						   oEl.getAttribute('tipdef') ||
						   'notip')
   						   .replace(/\n|\r\n|\r|\cM/mg, '')
   						   .replace(/\s\s+/mg, ' ')
						   .replace(/^\n\s+/mg, '')
						   .trim();

			if (cleaned !== 'notip' && cleaned.match(/^tt\:/i)) {
				var oTipAttributes = {},
					pairs = cleaned.replace(/^tt\:|^tt\:\s+/i, '')
								 .trim()
								 .replace(/\;$|\,$/g, '')
								 .replace(/'\s+\,|'\s\,|'\s+\;|\s\;/g, "',")
								 .replace(/\,\s+'|\,\s'|\;\s+'|\;\s'/g, ",'")
								 .replace(/';'/g, "','")
								 .split(/','/g);

				// clone default values
				for (var lbl in Tips.defaults) {
					oTipAttributes[lbl] = Tips.defaults[lbl];
				}
				for (var i = 0; i < pairs.length; i++) {
					var pair = pairs[i].trim()
									 .replace(/^\'/, '')
									 .replace(/\'$/, '')
									 .replace(/'\s+:|\s:'/g, "':")
									 .replace(/:\s+'|\s:'/g, ":'")
									 .split(/':'/);
					if (pair && pair[1]) {
						oTipAttributes[pair[0].trim().replace(/^'|'$/g, '').toLowerCase()] =
						   pair[1].replace(/'$/, '').trim();
					}
				}
				if (oEl.el.title && oEl.el.title.match(/^tt\:/i)) {
                    oEl.el.tipDef = oEl.el.title; //save it  
					oEl.el.title = oTipAttributes.replacetitle || '';
				}
				//oEl.tipAttributes = oTipAttributes;
                tipcache[oEl.id].tipAttributes = oTipAttributes;
				return oTipAttributes;
			} else {
				return null;
			}
		}
		/** Tips.serializeAttributes END */
		/** 
		 * Tips.txtFromXHR haalt tooltip mbv XmlHttpRequest
		 * van de server.
		 * @param {Object} q
		 * @param {Function} callback
		 */
		, txtFromXHR: function(q, callback) {
			var cleanref = location.href.match(/\?/) ?
				 			 location.href.substr(0,location.href.indexOf('?'))
							: location.href, //achja
				url = cleanref+'/xhrsvc.aspx',
				query = q,
				maxTime = 2;
			_x.xhrbasic.rCall(url, query, callback, maxTime);
		}
		/** Tips.txtFromXHR END */
		/**
		 * Progress.NET: check tooltip enabling via classname
		 * @param {HTMLObject} el (het element dat gecheckt wordt)
		 * @remarks: IE lag weer eens op majeure wijze dwars
		 */
		, checkTipBoxInTTElement: function(el) {
			if (!el || !el.classContains('containsTipTxt') 
			    || el.getAttribute('tipdef')){
					return true;
			}
			 var contentEl = $({tags:'div,span',classes:'ttContent',fromEl:el})[0];
				 contentEl.el.id = contentEl.id = Util.key(6);
				 el.setAttribute(
						"tipdef",
						["tt:",
						 "'txtid':'" + contentEl.id + "',",
						 (contentEl.getAttribute("title")
						 	? "'header':'"+contentEl.getAttribute("title") +"',"
							: ""),
						 "'cursor':'default',",
						 "'allowfix':'1'"
						].join('')
					);
		}
		, parentCheck: function(el){
			    if (!el || !el.getAttribute){return null;}
                
				if (tipcache[el.id] || 
                    el.getAttribute('title').match(/^tt/i) || 
					el.getAttribute('tipDef',true).match(/^tt/i) ) {
						return el;
					}
				var currentEl = el;	
				if (el.parentNode()){
					while ( el = el.parentNode() ){
						var isTtl =   el.getAttribute('title').match(/^tt/i) || 
									  el.getAttribute('tipDef',true).match(/^tt/i) ||
									  el.getAttribute('tipAttributes') !== '';
						if (isTtl){
							return el;
						}
					}
				}
				return currentEl;
		}
        , hasTip: function(elw){
            return elw && elw.el && elw.el.getAttribute 
			           ? elw.el.getAttribute('title') &&
                         elw.el.getAttribute('title').match(/^tt/i)
					   : null;
        }
		/** Tips.toolTipInit START */
		, toolTipInit: function(e) {
			if (document.readyState && document.readyState !== 'complete') {
				return false;
			}
			var ev     = Util.getEvt(e),
				elsrc  = Tips.parentCheck($(ev.src,true)),
                hastip = Tips.hasTip(elsrc);
                if (hastip && !(elsrc.id in tipcache)){
                    tipcache[elsrc.id] = {srcEl:elsrc};
                } else if (hastip && elsrc.id in tipcache){
                    //redo the element
                    elsrc.el.title = '';
                    tipcache[elsrc.id].srcEl = elsrc;
                }
                else if(!(elsrc.id in tipcache)){
                    return true;
                }
				var tc = tipcache[elsrc.id],
                    oAttr = tc.tipAttributes || null;
				stop  = !elsrc || oAttr === 'noTip';
             
			/** fixes fogbug 1492 (https://uocg.fogbugz.com/default.asp?1492)? */
			if (stop) {
				return true;
			}
			
			//Tips.checkTipBoxInTTElement(elsrc);

			/**
			 * does it concern a fixed or temporary fixed box 
			 * and is it still open? => nothing to do
			 * is the existing tip opened|closed? => close or open it
			 */
			if (oAttr) {
                if (elsrc.el !== tc.srcEl.el) {
                        tc.srcEl = $(elsrc.id,true);
                        Tips.applyHandlers(tc.srcEl);
                }

				if (tc.tipOpen && (tc.srcEl.fixClicked
					|| tc.tipAttributes.fixed)) {
					return true;
				}
				if (tc.tipOpen) {
					Tips.hideTooltip(e, elsrc);
				}
				if (tc.tipAttributes.tipEl) {
					if (!tc.tipOpen) {
						Tips.showToolTip(e, elsrc);
					}
					return true;
				}
				Tips.setToolTip(e, elsrc);
                
			} else {
				oAttr = Tips.serializeAttributes(elsrc);
					Tips.setToolTip(e, elsrc);
                    Tips.applyHandlers(elsrc);
			}
		}
		, cleanTip: function(tiptxt){
			return tiptxt.replace(/&amp;/g,'&')
			             .replace(/\[br\]/g,' <br />');
		}
        /** 
         * 20100106: element voor innerhtml altijd vernieuwen
         * @param {Object} e
         * @param {Object} oEl
         */
		, setToolTip: function(e, oEl) {
			e = e || event;
			if (!e) {return true;}
            var ev = $.util.getEvt(e);
			oEl = $(ev.src,true) || oEl;
            thistip = tipcache[oEl.id];
            thistip.srcEl = $(oEl.id,true);
			var sId = 'tip_' + Util.key(6),
				oAttr = thistip.tipAttributes,
				tipBox,
				aXY = Util.getXY(e),
				aCoords = [aXY[0] + 3, aXY[1] + 3],
				sTxt = Tips.cleanTip((oAttr.txt || $(oAttr.txtid,true).innerHTML() || 'no txt?')),
				ignore = 0;
			thistip.tipOpen = false;
            
            if (oAttr.allowfix){
                oAttr.sticky = false;
                
            }
			if (sTxt.match(/^XHR/i)) {
				var sTxtOld = sTxt,
					Q = sTxt.split(/::/),
					callback = function(rObj) {
						sTxt = rObj.error  !== '0'
								? rObj.error.substr(0,500)
								: rObj.data;
            /** 
             * replacements zijn voor XML-berichten. Moet hier al gebeuren,
             * anders pikt de sissiesbrowser van microsof de whitespace niet
             * op. Pretty stupid, maar dat was bekend.
             */
						$(oAttr.txtid).html(
						       sTxt.replace(/\n|\r\n|\r/g,'\n')
							       .replace(/&lt;pre&gt;/i,'<pre>')
								   .replace(/&lt;\/pre&gt;/i,'</pre>')
							   );
						oAttr.isXML = true;
						return Tips.setToolTip(e,oEl);
					};
				return  Tips.txtFromXHR(Q.join('&'), callback);
			}

			/**
			* 2009/09/04: headerfunctionaliteit voor een xml-geformatteerd
			* bericht via het txt-attribuut.
			* Dit is vooralsnog gemaakt voor de xml-berichten van studielink,
			* voor Niek. Kan ook meer algemeen worden ingezet om headers 
			* boven tooltips te vervangen. Zou zelfs de headerdefinitie via
			* attributen kunnen vervangen (waarmee je flexibeler ben in de
			* manier waarop je tooltips definieert).
			* DONE: dit moet gestructureerder. Verder moet, als de tekst
			* te lang is, txt naar txtid worden omgezet (hoewel de tekst
			* in het tipdef-attribuut behoorlijk lang kan zijn). 
			* Via xslt of al in het framework? => via xslt (tooltips.xsl)
			* Zie webtest/organisatiespace/inschrijving/studielink/inkomendlijst.cs
			* en webtest/control/textlabel/textlabel.cs
			*/
			if (sTxt && oAttr.isXML) {
				oAttr.header = "ZenderXML";
				oAttr.allowfix = 1;
				/** overflow plain lijkt niet te werken (in IE>6) */
				oAttr.overflowy = 'auto';
				oAttr.overflowx = 'auto';
				if ($.appcache.ie6){
					oAttr.overflow = 'auto';
				}
				oAttr.delay  = 0.7;
				oAttr.height = '400';
				oAttr.width  = '600';
				oAttr.ignorewidth = '1';
				oAttr.isxml= true;
			}
			if (oAttr.header) {
				tipBox = Tips.externalBoxWithHeader(
							{ id: sId,
							  header: oAttr.header,
							  text: sTxt || 'no txt?',
							  opaque: parseFloat(oAttr.opacity)
							}).hidden();
				tipBox.addCssClass('curve5').txt.addCssClass('bottomcurve5');
				tipBox.txt.css({marginTop:'0.5em'});
				tipBox.close.html(oAttr.closer);
			} else {
				tipBox = Tips.externalBox(
					{
						id: sId,
						text: sTxt,
						opaque: parseFloat(oAttr.opacity)
					}
				).hidden();
				tipBox.addCssClass('curve5').txt.addCssClass('curve5');
				tipBox.css({background:'transparent'});
			}
			
			tipBox.assocEl = oEl.id;
			oAttr.tipEl = tipBox;
			
			if (oAttr.fixed && !oAttr.header) {
				oAttr.fixed = null;
			}
			
			if (oAttr.cursor) {
				oEl.css({cursor:
					oAttr.cursor === 'pointer' 
					? $.appcache.cursorPointer
					: oAttr.cursor});
			}

			/**
 			 * Hoogte instellen, behalve als ignoreheight
 			 * is ingesteld
 			 */
			if (oAttr.height || tipBox.offsetHeight() > oAttr.maxh)
			{ 
				ignore = oAttr.ignoreheight || oAttr.ignoremaxheight;
				tipBox.css({height: 
							tipBox.offsetHeight() > oAttr.max && !ignore
							? oAttr.maxh+'px'
							: oAttr.height+'px'});
				if (!ignore && tipBox.offsetHeight() === oAttr.maxh){
					tipBox.css({overflowY : 'auto'});
				}

			}
			
			/**
			 * TODO
			 */
			if (oAttr.width){oAttr.ignoreWidth = 1; oAttr.ignoreminwidth = 1;}
			/**
 			 * Breedte instellen, behalve als ignorewidth
 			 * is ingesteld
 			 */
			if (oAttr.width || tipBox.offsetWidth() > oAttr.maxw) {
				 ignore = oAttr.ignorewidth || oAttr.ignoreminwidth;
				 tipBox.css({width:
				    tipBox.offsetWidth() > oAttr.maxw && !ignore 
					? oAttr.maxw +'px'
					: oAttr.width +'px'
				   });
			}
			/** 
			 * box iets breder als tekstbreedte kleiner is dan 
			 * totale breedte (anders passen controls niet in
			 * header)
			 */
			if (tipBox.txt.abs().offsetWidth() < tipBox.offsetWidth()) {	
				tipBox.css({width:(tipBox.offsetWidth()+25)+'px'});
			}
			/** na breedte-check moet txtbox weer relatief worden */
			tipBox.txt.rel();
			
			if ($.appcache.ie6 || $.appcache.isOpera && (oAttr.overflowx || oAttr.overflowy)) {
				tipBox.txt.css({overflow: oAttr.overflowx || oAttr.overflowy});
			} else {
				tipBox.txt.css({overflowX:oAttr.overflowx || Tips.defaults.OverflowX});
				tipBox.txt.css({overflowY:oAttr.overflowy || Tips.defaults.OverflowY});
			}

			if (oAttr.isXML) { tipBox.txt.css({whiteSpace: 'nowrap'}); }


			/** fixed property: close-/draghandler inschakelen */
			if (oAttr.header && (oAttr.allowfix || oAttr.fixed)) {
				var oClose = tipBox.close;
				oClose.html('&#9679;'); //'&#8730;';
				if (oAttr.fixed) {
					Drag.setDragHandlerOnEl(tipBox,tipBox);
				}
				oClose.handle('click', function(e) {
					Tips.hideTooltip(e,oEl);
				});
				Tips.setSimpleTip(oClose, 'Sluiten');
			}
			//Tips.applyHandlers(oEl);
			tipBox.posit = Tips.positionTip;
			/** oEl.tipattributes terug naar huidige attributen wijzen */
			//oEl.tipAttributes = oAttr;
			thistip.tipSetDone = true;
	    	Tips.showToolTip(e, thistip.srcEl);
		}
        ,tipfix: function(e) {
                    var tc  = tipcache[this.id],
                        oEl = $(this.id),//tc.srcEl,
                        tipBox, oAttr;
                        
                    if (!oEl) {debug(-1,';~(');return true;}
                    if (oEl.fixClicked) {return true;}
                    
                    oAttr = tc.tipAttributes;
                    tipBox = oAttr.tipEl;
                    
                    tipBox.css({zIndex : Util.zVal.set(2)});
                    tipBox.addCssClass('CanMove');
                    
                    Drag.setDragHandlerOnEl(tipBox);
                    
                    //by god man WHY NOT?
                    oEl.unhandle('mouseout', oEl.moutfn);
                    oEl.m_out = false;
                    
                    tipBox.close.html(oAttr.closer);
                    if (oAttr.sticky) {
                            oEl.unhandle('mousemove', 
                                         oEl.movefn);
                            oEl.m_move = false;
                    }
                    oEl.fixClicked = true;
                    tc.tipAttributes.fixed = true;
         }
		/** Tips.setToolTip END */
		/** Tips.applyHandlers START */
		, applyHandlers: function(oEl) {
            //oEl = $(tipcache[oEl.id].srcEl.id,true);
			var oAttr   = tipcache[oEl.id].tipAttributes,
				moutfn  = function(e) { Tips.hideTooltip(e, oEl); },
				clickfn = function(e) { 
                                   if (!tipcache[oEl.id].tipAttributes.fixed) {
					                     Tips.hideTooltip(e);
				                   }
                          },
				movefn = function(e) { Tips.moveTip(e, oEl); },
                tipObj = tipcache[oEl.id];
                if (oEl !== tipObj.srcEl){
                    oEl = tipObj.srcEl;
                }
            
            if (oAttr.allowfix && oAttr.header && !oEl.fixClicked) {
                   oEl.handle('click',Tips.tipfix);
            }
               
			if (!oEl.m_out && (oAttr.fixed || !tipObj.fixClicked)) {
				 oEl.handle('mouseout', moutfn);
				 oEl.moutfn = moutfn;
				 oEl.m_out = true;
			}
			if (!oEl.clickClose && !(oAttr.allowfix || oAttr.fixed || oEl.fixClicked)) {
				oEl.handle('click', clickfn);
				oEl.clickfn = clickfn;
				oEl.clickClose = true;
			}
			if (oAttr.sticky && !oEl.m_move && (!oEl.fixClicked || !oAttr.allowfix)) {
				oEl.handle('mousemove', movefn);
				oEl.movefn = movefn;
				oEl.m_move = true;
			}
			if (!oEl.tipClickH && !(oAttr.allowfix && oAttr.header)) {
				oAttr.tipEl.txt.handle('click',clickfn);
				oEl.tipClickH = true;
			}
            
            
            oEl.handled = true;
		}
		/** Tips.applyHandlers END */
		/** Tips.moveTip START */
		, moveTip: function(e, oEl) {
			
			var ev = Util.getEvt(e), tc;
			oEl = oEl || Tips.parentCheck(this || ev.src) || null;
            tc = tipcache[oEl.id];
            if (oEl.fixClicked) {return true;}  
			if (!ev || !oEl || !tc.tipAttributes) {
				/**may occur when tooltip was just destroyed (timer)*/
				return true;
			}
			var oTip = tc.tipAttributes.tipEl || null, 
			    aXY1 = ev.pos, aXY = {
				  left: aXY1[0],
				  top: aXY1[1]
			    };
			if (!oEl || tc.fixClicked || !oTip) {
				return true;
			}
            
			/** do send the tip element, not oEL */
			oTip.posit(aXY, aXY1, oTip, true);
		}
		, shiftUp: function(tipEl,bottomVal){
			var dims = tipEl.dim();
			tipEl.setLT(dims.left,dims.top-5);
			if ((dims.top+dims.height) >= bottomVal){
				Tips.shiftUp(tipEl,bottomVal);
			}
		}
		, shiftDown: function(tipEl,topVal){
			var dims = tipEl.dim();
			tipEl.setLT(dims.left,dims.top+5);
			if (dims.top <= topVal){
				Tips.shiftDown(tipEl,topVal);
			}
		}
		, shiftLeft: function(tipEl,rightVal){
			var dims = tipEl.dim();
			tipEl.setLT(dims.left-5,dims.top);
			if (dims.right >= rightVal){
				Tips.shiftLeft(tipEl,rightVal);
			}
		}
		, shiftRight: function(tipEl,leftVal){
			var dims = tipEl.dim();
			tipEl.setLT(dims.left+5,dims.top);
			if (dims.left <= leftVal){
				Tips.shiftLeft(tipEl,leftVal);
			}
		}
		/** Tips.moveTip END */
		/** 
		 * Tips.positionTip START
		 * heavily overhauled
		 * @param {Object} aXY  (left/top tip)
		 * @param {Array}  aXY1 (current mousepos)
		 * @param {Object} tip (the tip node)
		 * @param {Bool}   bMove (started from move (sticky tips))
		 */
		, positionTip: function(aXY, aXY1, tip, bMove) {
			if (!tip.assocEl){
				return true;
			}
			tip.abs().hidden(); /** tip gets dimensions with this */
			var oAttr = tipcache[tip.assocEl].tipAttributes,
				vp = $.DOM.viewPort(), /** [0=w,1=h,2=scrleft,3=scrtop] */
				iScrBar = $.appcache.scrbarW+5,
				iDistnce = oAttr.aligntoelement ? 2 : 15,
				spills = {
					rightOver  : [aXY.left + tip.offsetWidth() >= (vp[0] + vp[2]) - iScrBar,
								  (vp[0] + vp[2]) - iScrBar],
					bottomOver : [aXY.top + tip.offsetHeight() >= (vp[1]+vp[3])-iScrBar, vp[1]+vp[3]- iScrBar],
					topOver    : [aXY.top < vp[3],vp[3]],
					leftOver   : [aXY.left < vp[2], vp[2]]
				},
				needsReset =    spills.rightOver[0] 
							 || spills.bottomOver[0] 
							 || spills.topOver[0]
							 || spills.leftOver[0];

			if (needsReset && !$.appcache.isIe6){
		      tip.setLT(aXY.left+iDistnce,aXY.top + 3); /** eerst plaatsen */
			  with (spills){
				if (rightOver[0])  {Tips.shiftLeft(tip,rightOver[1]);}
				if (leftOver[0])   {Tips.shiftRight(tip,leftOver[1]);}
				if (topOver[0])    {Tips.shiftDown(tip,topOver[1]);}
				if (bottomOver[0]) {Tips.shiftUp(tip,bottomOver[1]);}
			  }
			  return tip;
			}
  		  tip.setLT((aXY.left + iDistnce),(aXY.top + 3));
		}
		/** Tips.positionTip END */
		
		/** Tips.showToolTip START */		
		, showToolTip: function(e, oEl, noDelay) {
			e = e || event;
			oEl = oEl || Tips.parentCheck($(e.srcElement || e.target)) || null;
			if (!oEl){
				return true;
			}
            var tc = tipcache[oEl.id];
            
            oEl = tc.srcEl;
            
			if (tc.fixClicked && tc.tipOpen) {
				return true;
			}
            
			var oAttr = tc.tipAttributes, 
			    oElparent = oEl.parentNode(), 
				oTip = oAttr.tipEl, 
				oEldim = oEl.dim(), 
				aXY1 = Util.getXY(e), 
				aXY = oAttr.aligntoelement ? {
				        left: oEldim.left + oEl.offsetWidth(),
				        top: oEldim.top + (Math.round(oEl.offsetHeight() / 2)) + 5
			           } : {
				        left: aXY1[0],
					    top: aXY1[1]
			           };

			/**
			 * if parent has a tip open, close it first
			 * recursive (parent checks parent checks parent...)
			 */
            var parentTip = oElparent ? tipcache[oElparent.el.id] : null;
			if (parentTip && parentTip.tipOpen &&
				parentTip.hideTip && !(parentTip.tipAttributes.fixed ||
				parentTip.tipAttributes.allowfix))
			{
					Tips.hideTip(e, oElparent);
			}
			
			if (tc.tipOpen) {
				return true;
			}
			
			if (oTip.fixwh) {
				try {
					oTip.css({width:oTip.fixwh[0],height:oTip.fixwh[1]});
				} catch (e) { }
			}

			oTip.txt.css({height:oAttr.height ? oAttr.height + 'px' : ''});

			/** position the tip */
			Tips.positionTip(aXY, aXY1, oTip);

			/** in case of explicit txt height, this *is* important */
			oTip.css({height:'auto'});

			/** 
			 * with aligntoelement=true the possibility exists that
			 * the tip gets a negative left position, when the trigger
			 * element spans two lines. In that case the attribute is
			 * set to false and the tip is reshown (recursive).
			 */
			if (oTip.offsetLeft < 0) {
				oAttr.aligntoelement = false;
				Tips.showToolTip(e, oEl);
			}

			oTip.css({zIndex:Util.zVal.set()});
			oTip.zi = Util.zVal;

			tc.tipOpen = true;
			Tips.openTips[oTip.id] = oTip;
			if (oAttr.delay &&!noDelay) {
			    var fncallback = function(){ 
					if (oEl.tipOpen){oTip.visible();}
					clearTimeout(0); 
				};
			    setTimeout(fncallback, parseInt(oAttr.delay*1000,10));
				return true;
		    } else {
				return oTip.visible();
			}
		}
		/** Tips.showToolTip END */

		/** 
		 *  hideTooltip: destroys or hides the tooltip.
		 *  ->if the boolean hideDestroy is true, the
		 *  tip is destroyed every time. Else all tips
		 *  get hidden and are destroyed when the
		 *  page using this module unloads. I suppose
		 *  destroying every time is the least prone to
		 *  memory leaks but hey ... let them make
		 *  better browsers then.
		 */
		, hideTooltip: function(e, oEl, force) {

			e = e || event;
			if (e != 'noEvt'){
			 var ev = Util.getEvt(e);
			 /**
			  * ev.src.tipAttributes could be lost when destroying
			  * the trigger element and mouseout fired before destroying 
			  */
			 if (!ev || !ev.evt) {
				return true;
			 }
			}
            
            if (ev && $(ev.src).fixClicked) {return true;}
            
            function realId(id){
                 return id.replace(/closer|txt|tip_/gi,'');
            }
            
			var elsrc = function(){
                        var id;
                        if (oEl && oEl.classContains && !oEl.classContains('headclose')) {
                            id = realId(oEl ? oEl.id : this.id ? this.id : ev.src.id);
                        } else {
                            id = oEl ? oEl.id : this.id ? this.id : ev.src.id;
                        }
                        return Tips.parentCheck($(id, true)) || null;
                    }(),
                tc    = tipcache[elsrc.id],
				oTip  = tc.tipAttributes.tipEl,
				relat = e.relatedTarget,
                oAttr = tc.tipAttributes,
				children = oTip ? oTip.el.childNodes : null;

			if (!oTip || !tc.tipOpen) {
				return true;
			}
			
			/** alleen als ding daadwerkelijk is vastgeklikt */
			if (e.type === 'mouseout' && tc.fixClicked) {
				return true;
			}

			/**use recursion to remove tips in tips*/
			if (children.length) {
				
				for (i = 0; i < children.length; i++) {
					var c = $(children[i]);
					if (c && c.id in tipcache &&
                        tipcache[c.id].tipOpen && tipcache[c.id].hideTip) {
						Tips.hideTip(e, c);
					}
				}
			}
							
			if (oAttr.header && oAttr.allowfix && !hideDestroy) {
				//Drag.removeDragHandler(oTip);
			}
			
			if (tc.fixClicked) {
				tc.fixClicked = false;
			}
			oTip.hide();//fadeOut('#EEE',0.3);
			tc.tipOpen = false;
            
			delete Tips.openTips[oTip.id];
			return true;
		}  /** Tips.hideTooltip END */
       
		, hideAllTips: function(){
			for (var tip in Tips.openTips){
				if (Tips.openTips[tip].assocEl){
					Tips.hideTooltip('noEvt',$(Tips.openTips[tip].assocEl,true),true);
				}
				delete Tips.openTips[tip];
			}
		}
	    , externalBoxWithHeader: function(oBoxObj) {
			var sId  = oBoxObj.id, 
				sHead = oBoxObj.header || '&nbsp;',
				sText = oBoxObj.text,
				oExt = $({id:sId, elType:'div',
						  styleclass:'ExtBox shadow3ro'}).html(sHead),
				oCloser = $({id: sId + 'closer', elType:'div',
							 styleclass:'headclose',related:oExt}).html('&nbsp;'),
				oTxt = $({id:sId + 'txt',elType:'div',
						  styleclass:'txt',related:oExt}).html(sText);
			oExt.txt = oTxt; 
			oExt.close = oCloser;
			/**
			* long lines without whitespace are truncated without mercy,
			* unless arranged for in calling function(s)
			*/
			oTxt.css({overflow:'hidden',marginTop:'0.5em',position:'relative'});
			oCloser.css({cursor:$.appcache.cursorPointer});

			if (oBoxObj.coords) {
				oExt.setLT(oBoxObj.coords.left, oBoxObj.coords.top);
			}
			
			/* set width and height @ the receiver side */

			/**all boxes are stored*/
			Util.openBoxCache.push(oExt);
			return oExt;
		},
		externalBox: function(oBoxObj) {
			var sId = 	oBoxObj.id,
						sText = oBoxObj.text,
						oExt = $({id:sId, elType:'div',
						          styleclass:'ExtBox shadow3ro'}),
						oTxt = $({id:sId + 'txt',elType: 'div',
								  styleclass:'txt',related:oExt});
			oExt.txt = oTxt;
			oTxt.html(sText);
			/**long lines without whitespace are truncated*/
			oTxt.css({overflow:'hidden'});
			if (oBoxObj.coords) {
				oExt.setLT(oBoxObj.coords.left, oBoxObj.coords.top);
			}
			/**all boxes are stored*/
			Util.openBoxCache.push(oExt);
			return oExt;
		}
	};     /** Tips END */
	/** for coding convenience we create a local pointer to _x.tips */
	Tips = _x.tips;
	/** ...and finally we start the load handler of this module */
	Tips.onLoad();
}
/**
* -----------------------------------------------------
*				    DRAG stuff
* -----------------------------------------------------
*/
function DragWrapper(d,w) {
	var _x = this
	, cache = _x.appCache
	, dragBox = null
	, dragFrame = null
	, dragging = false
	, debug = null
	, dragDirect = false
	, docBody = (d.body || d.documentElement)
	, Drag
	, DOM
	, Util;
	/**
	 * private: create a dragbox template, to
	 * use as the placeholder drag object while 
	 * dragging. Created at first mousedown event
	 * detected, after that it's available for all
	 * drag operations. See Drag.initDragFrame.
	 */
	function setDragFrame() {
			dragFrame = $({id:'dragFrame',
						   elType:'div',
						   styleclass:(dragDirect 
						   				? 'dragFrame' 
										: 'dragFrame dragNoContent')
						   })
		     .css({background:'#EEE',border: '1px dotted '+dragDirect ? 'transparent':'red'})
			 .abs()
			 .setOpaque((dragDirect ? 0.6 : 0.5));
			if (!dragFrame.zi) {
				dragFrame.css({zIndex:Util.zVal.set()});
				dragFrame.zi = Util.zVal;
			}
			if (!dragDirect){
				dragFrame.css({border:'1px dotted #c0c0c0'});
			}
			dragFrame.boundaries = $.DOM.viewPort();
	}

	_x.drag = {
		toString: function() { return 'module drag is loaded!'; }
		, onLoad: function() {
				Util  = _x.util;
				DOM   = $.DOM;
				debug = $.DOM.debug;
				docBody = $(docBody);
			Drag.onloadDone = true;
			/**
			 * set mousedown handler for draggable elements
			 * (as defined per classname)
			 */
			$.DOM.getElementsByClassNames('draggable','div,span',$($.appcache.body)).each(
			 	function(div){
					div.handle('mousedown',Drag.dragInit);
				});
		}
		, initDragFrame: function(ev){
			if (!dragFrame){setDragFrame();}
			dragFrame.cloneDim(dragBox)
					 .currentPos()
			         .css({zIndex:500000});
			dragFrame.ltwidth = [ev.pos[0] - dragFrame.lt[0]||0, ev.pos[1] - dragFrame.lt[1]||0];
			dragging = true;
		}
		, dragInit: function(e) {
			e = e || event;
            if (e.type === 'click'){return true;}
			if (e.returnValue){e.returnValue = true;}
			var ev,
			    oOrigin = $((e.srcElement || e.target)),
				noDrag = !(oOrigin.classContains(/draggable/i,1))
						 || oOrigin.classContains(/headclose/i,1);
			if (noDrag || !oOrigin) { 
			   return oOrigin.classContains(/headclose/i,1)
			   			? $.tips.hideTooltip(e,oOrigin,true)
						: false;
			}
			/** 
			 *  handler only applies to headerelement, so
			 *  text can be selected as usual. Prevention
			 *  of bubbling is only applied to the header
			 *  in other words.
			 */
			ev = Util.getEvt(e, 1);
			dragBox = $(oOrigin.dragBox) || oOrigin;
			if (dragBox){
				Drag.initDragFrame(ev);
				docBody.handle('mousemove', Drag.doDrag);
				docBody.handle('mouseup', Drag.stopDrag, true);
				dragging = true;
				/** box always on top */
				dragBox.css({zIndex:Util.zVal.set()});
			}
		}
		, doDrag: function(e) {
			e = e || event;
			var ev = Util.getEvt(e);
			if (!ev.evt || !dragging) {
				return true;
			}
			dragFrame.show();
			
			if (dragging) {	
			    if ($.appcache.isIe){e.returnValue=false;}
				var vp = dragFrame.boundaries,
					cp = dragFrame.lt,
					nowPos = ev.pos,
					iXsize = (nowPos[0] - dragFrame.ltwidth[0]) - (cp[0]),
					iYsize = (nowPos[1] - dragFrame.ltwidth[1]) - (cp[1]),
					mX = cp[0] + iXsize,
					mY = cp[1] + iYsize,
					ivp = dragFrame.inViewport(iXsize, iYsize),
					to = null;

				if (!ivp) {
					dragFrame.addCssClass('dragFrameAtBoundary');
					to = 
					 setTimeout(function() { 
					   dragFrame.replaceCssClass('dragFrameAtBoundary',
					   							 'dragFrame'); }, 
					   100);
				}
				dragFrame.setLT(
					ivp[1], ivp[2]
					).currentPos();
				if (dragDirect) {
					dragBox.clonePos(dragFrame);
				}
				if (to) {
					clearTimeout(to);
					to = null;
				}
				return true;
			}
		}
		, stopDrag: function(e) {
            var ev = $.util.getEvt(e || event);
			if (!dragBox) {
				return true;
			}
			dragBox.clonePos(dragFrame);
			dragFrame.hidden().setLT(-9999,0);
			docBody.unhandle('mousemove', Drag.doDrag);
			docBody.unhandle('mouseup', Drag.stopDrag);
			dragging = false;
		    dragBox = null; /** remove pointer to tipBox */
			return true;
		}
		, setDragHandlerOnEl: function(dragEl, dragBox, className) {
			var dragHandler = $(dragEl);
			dragHandler.addCssClass('elDraggable');
			dragHandler.dragBox = dragBox || dragEl;
			dragHandler.handle('mousedown',Drag.dragInit);
		}
		, removeDragHandler: function(dragEl, className) {
			var dragHandler = $(dragEl),
					classN = className || 'elDraggable',
					re = new RegExp(classN, 'gi');
			dragHandler.removeCssClass(re);
			dragHandler.unhandle('mousedown', Drag.dragInit);
		}
	};
	Drag = _x.drag;
	Drag.onLoad();
	return Drag;
}
// ---XML utilities---
/*
 * Delivers a constructor to wrap an xml data island
 * and adds some extra functionallity to it
 */
function XmlParse(d,w){
	var _x = this;
	
	_x.xml = {
		 toString: function(){return 'xml wrapper loaded';}
		,wrapper: function() {
			var xm    = this,
			proto = arguments.callee.prototype;
			xm.xml    = arguments[0];
			
			if (!xm._protoset){ 
			/**
		 	* byid: geeft element of waarde uit xml nodelist
		 	* id is case insensitive 
		 	* @param {String} id
		 	* @param {Bool} getvalue
		 	*/
			proto.byid = function(id,getValue){
				if (!arguments.length) {
					return {'data':'nopes'};
				}
			    var self = this.xml, 
				 	 sId = new RegExp(arguments[0],'i'),
					 Nodes = self.childNodes,
					 i = -1;
				while (++i < Nodes.length) {
					if (Nodes[i].getAttribute('id').match(sId)) {
						return arguments[1] 
								? Nodes[i].firstChild.nodeValue 
								: Nodes[i].firstChild;
					}
				}
				return 'not found';
			}
			
			proto.getFirstNode = function(tagName,val){
				var tags = this.xml.getElementsByTagName(tagName); 
				if (tags) {
				  return val ? tags[0].firstChild.nodeValue
				             : tags[0].firstChild;
				} else {return ''}
			};
			
			//-attrVal(id,attribuut): 
			// geeft de waarde van een [attribuut]
			// voor xml-element met [id]
			proto.attrVal = function(){
				if (arguments.length < 2) return 'params insufficient'
				var self = this.xml, 
					sNodeId = arguments[0], 
					sAttr = arguments[1], 
					Node = xm.NodeById(sNodeId) || null;
				if (Node) return Node.getAttribute(sAttr)
				return 'noValue'
			};
			xm._protoset = true;
		  }
		  
		  proto.docFromString = function(xmlStr){
		  	var xDoc;
		  	// Mozilla and Netscape browsers
    		if (d.implementation.createDocument) {
        		var parser = new DOMParser();
        		xDoc = parser.parseFromString(xmlStr, "text/xml");
    		// MSIE
    		} else if (window.ActiveXObject) {
        		xDoc = new ActiveXObject("Microsoft.XMLDOM");
        		xDoc.async="false";
        		xDoc.loadXML(xmlStr);
    		}
    		return xDoc;

		  }
		}
	}
 }
/**
 *  Leak free DOM Elements wrapper.
 *  Returns a global $ operator to address
 *  DOM Elements. Every element that gets
 *  addressed by $ will be wrapped. If an
 *  element does not exist and no parameters
 *  are supplied to create it, $ return null.
 *  Run DOMWrapper and get $ for handling
 *  DOM elements (create, add handlers, add
 *  inline css, destroy, manipulate).
 *  All methods of the DOMWrapper object
 *  are private. Some native objects are
 *  augmented (see setProtos).
 *  replacement for DOMWRAPPER
 *  (c) 2009-12 NICon/Renzo Kooi
 */
 function DOMWrapper(param){

   if (!(this instanceof DOMWrapper)){
       return  new DOMWrapper(param);
   }
   
   var apptimer = Timer(),
  	  slf = this, proto = DOMWrapper.prototype,
      d = document,
      w = window,
	  docbody = d.body||d.documentElement||d.getElementsByTagName('body')[0],
      DOM = Domhelpers(),
	  htmlElement = document.getElementsByTagName('html')[0],
	  noWrap = /^(br|html|script|style)$/i;

  proto.toString = function(){return 'DOMWrapper in place';};

  /**--------------------- private functions block ------------------------ */
  /** 
   *  [Timer]
   *  timer constructor:  initializes a timer object with a 
   *  stop method and a toString method that returns the 
   *  ended property (i.e. the time passed between start and
   *  stop).
   *  @return {Object}
   *   /w start: starttime (autostart on instance creation)
   *      stop: method, calculates time and returns its value
   */
  function Timer(){
  	    var start = new Date,
		    end = 'timer running...',
			ended = '';
		return{
	  	 start: function(){start = new Date;},
		 stop: function(mssg) {
			var stoppedAt = (new Date - start);
			ended = [(mssg ? mssg+': ' : ''),(stoppedAt/1000)+' sec (+/- 15ms)'].join('') 
			return ended;
		 },
		 toString: function(){return ended;}
		}
  }

  /**
   * [Wrap] 
   * the actual element wrapper, private. Caches and
   * returns the element wrapped in an object
   * that contains its own methods in its prototype chain.
   * the wrapped element is stored in a cache of the DOMWrapper
   * object and the element itself within the wrapped object.
   * After wrapping the DOM-element is available as [obj].el.
   * Example:
   * var myDiv = $('myDiv');
   * ^ now myDiv is wrapped and all methods are available.
   *   if you want to address the element directly, use
   *   myDiv.el (for example 'myDiv.el.offsetHeight')
   */
  function Wrap(el){
   if (!el) {return true;}
   if (!(this instanceof Wrap))  {
    return new Wrap(el);
   } else {
    var t = this,
        proto = Wrap.prototype;

     /**
      * if methods ar not in the Wrap prototype
      * chain, they are attached here.
      * This happens only once. After that 
      * the prototype methods will be available 
      * for every subsequently wrapped object
      */
    if (!proto._WrapDone) {
      var m = methods();
      for (var l in m) {
       proto[l] = m[l];
      }
      proto._WrapDone = true;
    }
    t.el = el;
    t.id = el.id;
	/** 
	 * add wrapped element to the cache and
	 * create a handlers array as one of its
	 * properties (for later use)
	 */
    DOM.elCache[t.id] = t;
    //DOM.elCache[t.id].handlers = [];
	DOM.elCache[t.id].created = new Date().getTime();
    return t;
    }
  }

  /** 
    * $:
    * retrieves, wraps & retrieves or creates new element. Public
    * The core method of this library. $ wraps the element and thus provides
    * several extended methods and/or properties to work with the
    * element in a non detached way.
    * if el is a string, first the internal element cache is
    * inspected, and if the element is not found, the DOM.
    * if el is an object, this triggers the creation of a new
    * DOM element. The syntax for the el Object:
    * elType:  {String} the element type (div, p etc)
    *  id:     {String} the identifier
    *  inject: {Bool} if inject is defined, the element is inserted
    *          into the DOM tree (within the [related] element. Other-
    *          wise the new element is appended at the bottom of
    *          the DOM tree.
    *  related: {DOM Element} the element the new Element will be inserted
    *          into Element [related]. Used together with [inject]
    *          If [related] is not defined the element will be
    *          inserted at the top of the DOM tree (i.e. the body)
    * a global or namespaced pointer to $ can function as the execution
    * agent of this wrapper. It is created automatically on initialization
    * of the DOMWrapper Object, so window.$ points to DOMWrapper().$.
    * You can define your own namespace (see parameters of the
    * top level constructor (DOMWrapper), but $ will allways be 
    * available as default namespace (if you use this lib). In case 
    * of your own namespace, it will function like an alias of $
    * @param {Object||String} el
    */
  function $(el,renew){

   if (!el){return null;}
   
   el = !el.elType && !el.eltype ? (el.id || el) : el;
   /** 
    * param can be either an object or a string. First
    * a check is done for el existence in the cache 
    * of already wrapped elements. If so, that element
    * is returned
    */
   if (!renew && el in DOM.elCache) {
    return DOM.elCache[el];
   }

   if (renew && el in DOM.elCache) {
	 //delete DOM.elCache[el];
	 //better: reuse the wrap, renew the element
	 DOM.elCache[el].el = d.getElementById(DOM.elCache[el].id);
	 DOM.elCache[el].created = new Date().getTime();
   }

   /**
    * El was not in the cache, so it may be a native 
    * (non wrapped) DOM-element. It is passed as the
    * DOM-element itself. If it's a node of type 1 (a
    * real DOM-node), we'll wrap it. We make an 
    * exception for elements that don't need
    * wrapping (br,html,script,style => as defined in 
    * the private variable (regexp) noWrap).
    */
	if (el.nodeType && el.nodeType === 1
			 && !el.tagName.match(noWrap)) {
			 	
        /** supply an id if there isn't one */
        if (!el.id || el.id.trim() === ''){
            el.id = DOM.randomId(5);
        }
		/** return the wrapped element */
        return Wrap(el);
    }

    /**
     * el.tag means we request an array of subnodes with tagnames
     * as defined in the parameter object el.tags. el.tags is a 
     * string, containing a comma delimited list of tagnames 
     * (like 'a,div,p' or just 'div'). This returns a (real)
     * array of wrapped elements that meet the given tagname
     * criteria.
     */
	if (el.tags){
		if (el.classes){
			return DOM.getElementsByClassNames(el.classes,el.tags,(el.fromEl||null));
		}
		return DOM.getElementsByTagNames(el.tags,(el.fromEl||null));
	}

    /** 
     * el is a string and tested for being an identifier of 
     * an existing DOM node. It in't wrapped (that was 
     * checked earlier), so if it is an existing DOM node
     * it is wrapped and the wrapped element is returned.
     */
	 if ((typeof el).match(/string/i)) {
	 	var elRaw = d.getElementById(el); //DOM.byId(el); 
	 	if (elRaw) {
	 		return Wrap(elRaw);
	 	}
	 } 

    /** 
     * last cases: create an element. Denoted
     * by the fact that the paremeter el is an object
     * with the property eltype.
     */
    if (el instanceof Object && el.hasOwnProperty('elType' || 'eltype')) {
      el.inject = !el.createOnly ? true : false;
      var nwEl = d.createElement(el.elType);
      nwEl.id = el.id || DOM.randomId(6);
      if (el.styleclass){
          nwEl.className = el.styleclass;
      }
      if (nwEl.innerHTML){
       nwEl.innerHTML = el.content || '';
        } else if ((el.elType||el.eltype).match(/input/i)){
           nwEl.value = el.content || '';
       }

      if (el.inject) {
		var related = el.related ? $(el.related) : $(docbody);
        related.el.appendChild(nwEl);
     }
     return Wrap(nwEl);
   } /** element creation END */
  
   /** $ => no result (no conditions met)? return null */
   return null; 
  } /** method $ END */

  /**
  * remove obj tree (can be html element or js object)
  * public. Base code from Douglas Crockford
  * @param {Object} obj
  */
  function purge(obj) {
  /**
   * TODO: this one doesn't work right.
   */
  obj = obj || d.getElementsByTagName('body')[0];
     var a = obj.attributes, i, l, n;
     if (a) {
       l = a.length;
        for (i = 0; i < l; i += 1) {
         n = a[i].name;
         if (obj[n] instanceof Function) {
            try {
        delete obj[n];
       }
       catch (err) {
        obj[n] = null;
       }
       finally {}
         }
       }
     }
     a = obj.childobjs;
     if (a) {
      l = a.length;
       for (i = 0; i < l; i += 1) {
        purge(obj.childobjs[i]);
       }
     }
     if (obj.parentNode && obj.parentNode.removeChild) {
     obj.parentNode.removeChild(obj);
    }
   }
    /**
     *  methods:
     *  the actual methods container. Gets injected into the
     *  prototype chain (of the Wrap object) once, at the start of
     *  the script. Private function used by DOMWrapper. 
     *  Because the methods are in the Wrap constructor prototype 
     *  chain, they are *not* connected to the wrapped element. 
     *  Methods are applied to the [wrapped DOM element].el,
     *  so the element itself doesn't contain pointers to 
     *  javascript objects, iow no closures and thus preventing 
     *  memory leaks.
     */
  function methods(){
     return {
	  /** 
	   * delivers some information about the wrapped element
	   */
     toString: function(){
          return ['wrapped element: [',
                      this.id,']; element type: ',
                      this.el.tagName].join('');
     },
	 getAllTxt: function(){    
	    var alltxt = [];
   		loop(this.el);
	    function loop(el){
		   if (el.nodeType === 3) {
		   	 alltxt.push(el.nodeValue);
		   }	
	       var children = el.childNodes;
		   if (children){
	       for (var i = 0; i < children.length; i++) {
		   	if (children[i].childNodes.length) {
		   		loop(children[i]);
		   	}
		   	if (children[i].nodeType === 3) {
		   		alltxt.push(children[i].nodeValue);
		   	}
		   }
	     }
	   }
	   return alltxt.join(' ');
	 },
	 matchTxt: function(matcher){
	  	return this.getAllTxt().match(matcher);
	  },
     getXPath: function(expression){
	  	var rootEl = this.el || docbody;
	 		var ret = []
			   ,xresult = d.evaluate(expression,rootEl, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null)
			   ,result = xresult.iterateNext();
		    while (result) {
					ret.push(result);
					result = xresult.iterateNext();
			}
		return ret;
	 },
     getXTxt: function(expression){
	  	var rootEl = this.el || docbody;
	 		var ret = []
			   ,xresult = d.evaluate(expression,rootEl, null, XPathResult.ANY_TYPE, null)
			   ,result = xresult.iterateNext();
		    while (result) {
					ret[ret.length] = result.textContent;
					result = xresult.iterateNext();
			}
		return ret.join('\n');
	 },
	 
	 getChildrenX: function(){
	  	var xp = document.evaluate(".//p",this.el,null,XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null)
	      , result = xp.iterateNext(),
		  ret = [];
	    while (result){
		  ret.push(result);
	      result = xp.iterateNext();
	    }
		return ret;
	 },
	 matchTxtX: function(matcher){
	  	var children = document.evaluate('*/text()',document,null,XPathResult.ANY_TYPE, null);;
		if (l) {
			for (; i < l; i++) {
				
				if (children[i].childNodes.length) {
					$(children[i]).matchTxt(matcher);
				}
				
				if (children[i].nodeType === 3 && children[i].nodeValue && children[i].nodeValue.match(matcher)) {
					match = true; break;
				}
			}
		}
		return match;
	 },
	  
	  /**
	   * set Elements opacity style
	   * @param {Float} opacityValue (0.01 - 1.0)
	   */
	  setOpaque: function(opacityValue){
		if ($.appcache.isIe) {
			this.el.style.filter = 'alpha(opacity=' + opacityValue * 100 + ')';
		} else {
			this.css({opacity:opacityValue});
		}
		return this;
	  },
      /**
       * Copy the xy-position of the Element [oFrom] to
       * this Element
       * @param {DOM Element} oFrom
       */
	  copyPos: function(oFrom) {
		   this.setLT(oFrom.offsetLeft(), oFrom.offsetTop());
		   return this.currentPos();
	  },
	  /**
	   * Set left and top position of this Element
	   * @param {Integer} l (left position)
	   * @param {Integer} t (top position)
	   * @param {String} lunit (px, em, %, pt)
	   * @param {String} tunit (px, em, %, pt)
	   */
	  setLT: function(l,t,lunit,tunit){
	  			return this.css(
				 {
				  left: l + (lunit||'px'),
				  top:  t + (tunit||'px')
				  }
				);
	  },
      /**
       * element fullscreen method
       */
      fullScreen: function(){
          var vp = DOM.viewPort(),
               handle = this.handle,
               el =this;
               
          this.css({height:(vp[3]+vp[1])+'px',
                    width:((vp[0]-($.appcache.isIe ? 1 :$.appcache.scrbarW))+vp[2])+'px'});
          $.util.handle('resize',function(){el.fullScreen();},null,w);
          return this;
      },
	  /**
	   * return current position of this Element 
	   * as an array [left,top]
	   */
	  currentPos: function() {
				this.lt = [this.el.offsetLeft, this.el.offsetTop];
				return this;
	  },
	  /**
	   * return the offsetWidth of the element
	   */
	  offsetWidth: function(){
		return this.el.offsetWidth;
	  },
	  /**
       * return the offsetHeight of the element
       */
	  offsetHeight: function(){
	  	return this.el.offsetHeight;
	  },
      /**
       * return the offsetTop of the element
       */
	  offsetTop: function(){
	  	return this.el.offsetTop;
	  },
      /**
       * return the offsetLeft of the element
       */
	  offsetLeft: function(){
	  	return this.el.offsetLeft;
	  },
	  /**
       * return the Elements parentNode
       */
	  parentNode: function(){
	  	return $(this.el.parentNode) || null;
	  },
	  /** 
	   * check if the element is in the current
	   * viewport. Returns an array with 2 ...
	   * TODO
	   * booleans: [in viewport left
	   * @param {Integer} iX current X position (horizontal)
	   * @param {Integer} iY current Y position (vertical)
	   */
	  inViewport: function(iX, iY) {
				iX = iX || 0;
				iY = iY || 0;
				var el = this.el,
				    vp = DOM.viewPort(),
					scrSize = 25,
					elsize = [el.offsetLeft + iX, el.offsetTop + iY,
								el.offsetLeft + iX + el.offsetWidth - 2,
								el.offsetTop + iY + el.offsetHeight - 2],
					invp = [elsize[0] >= vp[2] &&
							elsize[1] >= vp[3] &&
							elsize[2] <= (vp[0] + vp[2]) - scrSize &&
							elsize[3] <= (vp[1] + vp[3]) - scrSize];
				invp.push(elsize[0] <= vp[2]
							? vp[2] + 2
							: elsize[2] >= (vp[0] + vp[2]) - scrSize
								? ((vp[0] + vp[2]) - scrSize + 2) - el.offsetWidth
								: elsize[0], elsize[1] <= vp[3]
									? vp[3] + 2
									: elsize[3] >= (vp[1] + vp[3]) - scrSize
										? (vp[1] + vp[3]) - scrSize + 2 - el.offsetHeight
										: elsize[1]
						 );
				return invp;
	  },
	  setModal: function(bOpaque, bHide) {
			var mWin, vp = Dom.viewPort();
			if (bOpaque) {
				if (!$('modalOpaque')) {
					mWin = $({id:'modalOpaque',eltype:'div',styleclass:'isModal'});
					mWin.s.backgroundColor = "#FFF";
					mWin.css({opacity:0.4}); /**NB! ie*/
				} else {
					mWin = Dom.$('modalOpaque');
				}
			} else {
				mWin = Dom.$('modal') || Dom.nwEl('modal', 'div', 'isModal');
			}
			if (bHide) {
				mWin.hide();
				if (docbody.style.overflow) {
					docbody.style.overflow = mWin.saveOF;
				}
				return true;
			}
			mWin.s.zIndex = 10000;
			mWin.setLT(0, 0).append().show().setWH(vp[0], vp[1]);
			mWin.saveOF = docbody.style.overflow || '';
			if (docbody.style.overflow) {
				docbody.style.overflow = 'hidden';
			}
	  },
	  /*overname einde */
	  appendChild: function(el){
		this.el.appendChild(el);
        return this;
	  },
	  insertBefore:function(el,elFirst){
		this.el.insertBefore(el,elFirst || this.el.firstChild);
        return this;
	  },
	  getCS: function(sProp,toInt){
            var elem = this.el;
            if (elem.currentStyle) {
                return toInt ? parseInt(elem.currentStyle[sProp],10) : elem.currentStyle[sProp] || 0;
            } else if (w.getComputedStyle) {
                var compStyle = w.getComputedStyle(elem, null)[sProp];
                return toInt ? parseInt(compStyle,10) : compStyle || 0;
            }
            return String(elem.style[sProp]||0); //str is for opera
     },
     innerHTML: function(){
         return this.el.innerHTML;
     },
     /**
      * let op: je kunt hier alleen individuele style properties
      * meegeven (dus niet background, maar backgroundColor enz.)
      * @param {Object} hoverStyle -> diverse style properties
      *         die bij muisover worden ingesteld. Bij muisuit
      *         worden originele props weer hersteld.
      */
     styleHoverInit: function(hoverStyle){
        if (!this.hoverHandled){
            var el = this;
            el.oldStyle = {};
            for (var l in hoverStyle){
                if (typeof hoverStyle[l] === 'string') {
                    el.oldStyle[l] = el.getCS(l);
                }
            }
            this.handle('mouseover',function(){DOM.styleHover(el,hoverStyle);});
            this.handle('mouseout',function(){DOM.styleHover(el);});
            this.hoverHandled = true;
            return this;
        }
     },
     /**
      * inserts HTML into the element. If the
      * first argument is boolean and true,
      * the HTML is appended to existing HTML
      */
     html: function(){
       var args = Array.prototype.slice.call(arguments)
           , add = args[0] === true
		   , insrt = args[0] === 'insert';
       args = (add || insrt) ? args.slice(1) : args;
       
       this.el.innerHTML = (insrt 
	                        ? args.join('')+this.innerHTML() : 
							 add 
							  ? this.innerHTML() + args.join('') 
							  : args.join(''));
       return this;
      },
      text: function(){
       var args = Array.prototype.slice.call(arguments),
	       el = this.el,
	       tn = d.createTextNode(args.join(''));
       el.appendChild(tn);
       return this;
     },
     removeClass: function(name) {
         this.el.className = this.el.className.replace(name,"");
         return this;
     },
     /**
      * appends style rules to the element (inline).
      * silently return the wrapped element
      * if no [cssobj] was supplied or [cssobj]
      * is not an object. The rule identifiers must
      * be dhtml-identifiers here
      * (e.g. text-align = textAlign)
      * @param {Object} cssobj
      */
     css: function(cssobj){
       if (!cssobj || !(cssobj instanceof Object)) {
        return this;
       }
	   if ((this.el||this).style){
       	for (var l in cssobj) {
			try {this.el.style[l] = cssobj[l];}
			catch(e){}
		}
       }
       return this;
     },
	 getElementsByTagName: function(tagName){
	 	var nodes  = this.el.getElementsByTagName(tagName);
		nodes.each = function(){Array.prototype.each.apply(this,arguments)};
		return nodes;
	 },
	 childTags: function(tagnames){
	 	return DOM.getElementsByTagNames(tagnames,this);
	 },
	 /** casInsensitive is a nogo IE<8, so thats what we need the 0 for */
	 getAttribute: function(attr,caseInsensitive){
	 	return this.el.getAttribute(attr,0) || this[attr] || '';
	 },
	 setAttribute: function(attr,value){
	 	this.el.setAttribute(attr,value);
		return this;
	 },
	 focus: function(){
	 	try {this.el.focus();}
		catch(e){}
		return this;
	 },
	 getClassname: function(){
	 	return String(this.el.className);
	 },
	 setClassname: function(classname){
	 	this.el.className = classname;
	 	return this;
	 },
	 classContains: function(name){
	 	var currentClass = String(this.el.className);
	 	return name instanceof RegExp
		       ? currentClass.match(name)
			   : (currentClass.split(' ')).contains(name);
	 },
	 getFirstTag:function(tagname){
	 	return this.el.getElementsByTagName(tagname)[0];
	 },
	 nodeName: function(){
	 	return this.el.nodeName;
	 },
	 AmIA: function(name){
	 	name = new RegExp(name,"i");
	 	return this.el.nodeName.match(name);
	 },
	 hidden: function(){
		return this.show('',true);
	 },
	 visible: function(){
		return this.show();
	 },
     /**
      * associates a handler function [fn] with
      * an event [etype] for the element. The
      * handler function is added to the handler
      * array. The handler is applied to the
      * object, not the element,
      * which prevents unwanted closures.
      * @param {String}       etype
      * @param {Function}     fn
      * @param {Bool}        [bubble]
      * @param {HTMLElement} [obj]
      * TODO: dit lekt (nog steeds!)
      */
     handle: function(etype, fn, nobubble, el){
       var obj = this.el,
	       h = DOM.handlerCache,
           i = h.length;
       nobubble = nobubble || false;
       if (this.el.attachEvent) {
	   	h[i] = function(){
			return fn.apply(obj, arguments);
		};
	   	this.el.attachEvent('on' + etype, h[i]);
	   } else {
	   	h[i] = function(){
			return fn.apply(obj, arguments);
		};
	   	this.el.addEventListener(etype, h[i], !nobubble || true);
	   }
       return this;
     },
	 /** 
	  * TODO
	  * @param {Object} eType
	  * @param {Object} fn
	  */
	 unhandle: function(eType, fn) {
		var handlercache = DOM.handlerCache,
            currentId = this.id,
			unhtst = 'unhandle for '+currentId+' init';
		handlercache.each(function(handler){
		   if (handler === fn) {
            var elem = $(currentId).el;
            if (elem) {
                if (elem.removeEventListener) {
                    elem.removeEventListener(eType, handler, true);
                } else if (elem.detachEvent) {
                     elem.detachEvent('on' + eType, handler);
                }
            }
			unhtst = 'unhandle for '+currentId+' ok';
		   	handler = null;
			return true;
		   }
		   return true;
		  });
		return this;
	 },
     centerMe : function(forceredo){
        if (this.isCentered && !forceredo){return this;}
	  	var cE   = this.abs(),
         wdim = DOM.viewPort(),
         wh   = cE.dim(),
		 dimV = wdim[3]+(0.5*wdim[1]),
		 dimH = wdim[2]+(0.5*wdim[0]);
        cE.css({
             left  :  Math.round((dimH)-0.5*wh.width)+'px',
             top   :  Math.round((dimV)-(0.5*wh.height))+'px'
         });
	   this.isCentered = true;
       return this;
	},
    /**
     * makes an element visible
     * can take argument [spec] on how
     * the element should be displayed
     * (e.g. 'block'). Default spec = ''
     * @param {String} spec
     */
    show: function(spec,keephidden){
       this.css({display:spec || ''});
       if (this.classContains(/hidden/i)){
           this.removeCssClass(/hidden/g);
       }
       if (!keephidden){
           this.el.style.visibility = 'visible';
       }
       return this;
    },
    /**
     * hides an element
     * (setting display to none)
     */
    hide: function(){
       this.el.style.display = 'none';
       this.el.style.visibility = 'hidden';
       return this;
    },
	getValue: function(){
		return this.el.value ? String(this.el.value).trim() : this.innerHTML().trim();
	},
    /**
     * briefly shows a colored mask over the
     * element that fades away. Uses FadeIn
     * to achieve the effect
     * @param {String} color
     * @param {Number} ms
     */
    getAttention: function(color, startwith, ms, cb){
     /**
      *  if a previous fade runs, kill it
      */
       if (this.fadeTimer) {
        clearInterval(this.fadeTimer);
        this.fadeTimer = null;
       }
       this.fadeIn.apply(this,[color,startwith,ms,cb]);
       return this;
    },
    pulsate: function(color, startwith, ms, repeat, tempo){
       ms = ms || 0.1;
       tempo = tempo || 1;
       repeat = repeat || 1;
       DOM.repeatfx(this.fadeIn,
                       this,
                       Array.prototype.slice.call(arguments,0,3),
                       repeat,((startwith*ms*1000)*tempo));
       /**
        *  if a previous fade runs, kill it
        */
       if (this.fadeTimer) {
        clearInterval(this.fadeTimer);
        this.fadeTimer = null;
       }
       return this;
    },
	clonePos: function(fromEl){
		return this.copyPos(fromEl);
	},
    /**
     * Applies absolute dimensions from element
     * [fromEl] to this element
     * @param {HTMLElement} fromEl
     */
    cloneDim: function(fromEl){
       if (!fromEl) {
        return this;
       }
       var el = $(fromEl),
	       oldPos = el.getCS('position') || '',
	       nwdims = el.dim();
           this.css({width  : nwdims.width + 'px',
                     height : nwdims.height + 'px',
                     top    : nwdims.top + 'px', /** TODO! */
                     left   : nwdims.left + 'px'});
       return this;
    },
    cloneStyle: function(fromEl){
       if (!fromEl) {
        return this;
       }
       var s = fromEl.el.style, el = this.el;
       for (var l in s){
           el.s = fromEl.getCS(l);
           el.borderColor = 'transparent';
       }
       return this;
    },
	AnimateHeight: function(iTarget, uTotalTime, callback){
       units = 'px'; // only supports integer pixel values for now
       var ele = this.el, sCssProp = 'height';
       var startValue = this.offsetHeight() || 0;
       var disp = iTarget - startValue; // total displacement
       var freq = Math.PI / (2 * uTotalTime); // frequency
       var startTime = new Date().getTime();
       var tmr = setInterval(
        function() {
          var elapsedTime = new Date().getTime() - startTime;
           if (elapsedTime < uTotalTime) {
             var f = Math.abs(Math.sin(elapsedTime * freq));
             ele.style[sCssProp] = Math.round(f * disp + startValue) + 'px';
           } else {
             clearInterval(tmr);
             ele.style[sCssProp] = iTarget + 'px';
			 if (callback){
				  	callback();
			 }
           }
          }, 10
        );
    },

	/**
	  animatie cssprop
	 */
	AnimateCss: function(sCssProp, iTarget, uTotalTime, callback){
       units = 'px'; // only supports integer pixel values for now
       var ele = this.el;
       var startValue = parseInt(this.getCS(sCssProp),10) || 0;
       var disp = iTarget - startValue; // total displacement
       var freq = Math.PI / (2 * uTotalTime); // frequency
       var startTime = new Date().getTime();
       var tmr = setInterval(
        function() {
          var elapsedTime = new Date().getTime() - startTime;
           if (elapsedTime < uTotalTime) {
             var f = Math.abs(Math.sin(elapsedTime * freq));
             ele.style[sCssProp] = Math.round(f * disp + startValue) + 'px';
           } else {
             clearInterval(tmr);
             ele.style[sCssProp] = iTarget + 'px';
			 if (callback){
				  	callback();
			 }
           }
          }, 10
        );
    },
	/**
	 * animatie rgb
	 * css prop als Dom prop meegeven!
	 */
    animateRGB: function(sCssProp, targetColor, uTotalTime,callback){	
     var ele = this.el;
     var startC = DOM.parseColor(this.getCS(sCssProp)); // start colors
     var targetC = DOM.parseColor(targetColor); // target colors
     var disp = { // color displacements
                  r: targetC.r - startC.r,
                  g: targetC.g - startC.g,
                  b: targetC.b - startC.b
                };
     var freq = Math.PI / (2 * uTotalTime); // frequency
     var startTime = new Date().getTime();
     var tmr = setInterval(
                function() {
                 var elapsedTime = new Date().getTime() - startTime;
                 if (elapsedTime < uTotalTime) {
                   var f = Math.abs(Math.sin(elapsedTime * freq));
                   ele.style[sCssProp] = DOM.rgbToHex(
                   Math.round(f * disp.r + startC.r),
                   Math.round(f * disp.g + startC.g),
                   Math.round(f * disp.b + startC.b));
                } else {
                  clearInterval(tmr); // stop iterations
                  ele.style[sCssProp] = targetC.s;
				  if (callback){
				  	callback();
				  }
                }
               }, 10 // timer interval
              );
    },
    /**
     * fades in with [color], starting with opacity
     * [i], with an interval of [ms]
     * @param {String} color /default: el.backgroundcolor or white
     * @param {Float} i /default: 1.0
     * @param {Integer} ms /default: 5
     */
    fadeIn: function(color, opac, ms, callback){
	   opac = opac || 1.0;
	   color = color || this.getCS('backgroundColor') || '#FFF';
	   ms = ms || 5;
       var el  = this.el,
	       me  = this,
		   mask = $(el.id + 'mask') || $({
                       elType: 'div',
                       id: el.id + 'mask',
                       inject: 'append'
                     }).abs();
       step = function(){
        if (opac < 0.03) {
          mask.hide();
          clearInterval(me.fadeTimer);
          me.fadeTimer = null;
          if (callback && callback instanceof Function){callback();}
          return this;
        }
        mask.setOpaque(opac);
        opac -= $.appcache.isIe ? 0.03 : 0.01;
       };
       me.show();
       mask
           .css({opacity:opac,
                 backgroundColor: color,
				 zIndex: (this.getCS('zIndex',1)+1) || 10000
                })
		   .cloneDim(this)
           .show();
       /** if a previous fade still runs, kill it */
       if (me.fadeTimer) {
        clearInterval(me.fadeTimer);
        me.fadeTimer = null;
       }
       me.fadeTimer = setInterval(step, ms);
    },
    /**
     * TODO
     * fades out with [color], starting with opacity
     * [i], with an interval of [ms]
     * @param {String} color /default: el.backgroundcolor or white
     * @param {Float} i /default: 0.0
     * @param {Integer} ms /default: 5
     */
    fadeOut: function(color, opac, ms, callback){
   	   opac = opac || 0.4;
	   color = color || this.getCS('backgroundColor') || '#FFF';
	   ms = ms || 1;
       var el = this.el,
	       me = this,
		   mask =
		     $(el.id + 'mask') ||
			 $({elType: 'div',
                id: el.id + 'mask',
                inject: 'insert'
               }),
	       step = function(){
            if (opac >= 0.7) {
              clearInterval(me.fadeTimer);
              me.fadeTimer = null;
              mask.hide();
              me.hide();
              return me;
            }
			mask.setOpaque(opac);
            opac += $.appcache.isIe ? 0.1 : 0.03;
          };
        mask
 		   .abs()
		   .setOpaque(opac)
	       .cloneDim(this)
	       .css( {backgroundColor: color})
           .show();
       mask.css({zIndex:500});
       /** if a previous fader still runs, kill it */
       if (me.fadeTimer) {
        clearInterval(me.fadeTimer);
        me.fadeTimer = null;
       }
       me.fadeTimer = setInterval(step,ms);
       return this;
    },
    /**
     * positions the element absolute
     * (so it gets width and height)
     */
    abs: function(){
       this.el.style.position = 'absolute';
       return this;
    },
    /**
     * positions the element relative
     */
    rel: function(){
       this.el.style.position = 'relative';
       return this;
    },
    /**
     * removes the element from the DOM tree
     */
    remove: function(){
       var el = this.el, from = (el.parentNode || docbody), id = el.id;
       purge(el);
       delete DOM.elCache[id];
    },
	/**
	 * get element dimensions
	 */
	dim: function(){
		var el  = this.el,
		    pos = DOM.findPos(el);
	  	return {
			top: pos.top,
			left: pos.left,
			height: this.offsetHeight(),
			width: this.offsetWidth(),
			bottom: pos.top + this.offsetHeight(),
			right: pos.left + this.offsetWidth()
		};
	 },
	 scrollTop: function(){
	 	return DOM.findPos(this.el)[0];
	 },
	 /**
	  * remove className [className] from element
	  * @param {String} className
	  */
	 removeCssClass: function(className){
	 	return this.replaceCssClass(className,'');
	 },
	 /**
	  * add className(s) to element
	  * @param {Array || String} className
	  */
	 addCssClass: function (className){
        if (this.classContains(className)){return true;}
	  	if (className instanceof Array){
			className = ' '+className.join(' ');
		} else {
		    className = ' '+className;
		}
		if (!String(this.el.className)){
			this.el.className = className;
		}
		if (!this.el.className.match(className)){
	  	  this.el.className += ' '+className;
		}
        this.el.className = this.el.className.trim();
		return this;
	  },
	 replaceCssClass: function(oldname,newname){
	 	/** oldname may be a regex */
		var classNow = this.el.className;
	 	this.el.className = classNow.replace(oldname,newname).trim().replace(/\s+/g,' ');
		return this;
	 }
    };
   }/** methods Function END */


  /**--------------------- private functions block END ---------------------*/
   
  /** 
   * [Domhelpers]
   * helper functions to be used in the DOM. The helper functions
   * are defined in one large object, that is returned.
   * @return {Object} containing all helper functions.
   * usage: var DOM = Domhelpers();
   * now you can do something like: var sw = DOM.scrollbarWidth();
   */
   function Domhelpers(){
   	return {
		timerObj: Timer,
		NodeListToArray: function(nodelist){
		var node, ret = [];
		if (nodelist){
		  for (node in nodelist){
		  	if (nodelist[node] && (nodelist[node].tagName || nodelist[node].nodeType)){
				ret.push(nodelist[node]);
			}
		  }
		}
		return ret;
	  },
      scrollBarWidth: function() {
            docbody.style.overflow = 'hidden'; 
            var width = docbody.clientWidth;
            docbody.style.overflow = 'scroll'; 
            width -= docbody.clientWidth; 
            if (!width) {
                width = document.body.offsetWidth - document.body.clientWidth;
            }    
            docbody.style.overflow = ''; 
            return width; 
      },
      fullScreen: function(el){
          if (!el){return true;}
          el = $(el);
          var sw = $.appcache.scrbarW;
          var vp = DOM.viewPort(),
               handle = this.handle;
          el.css({height:(vp[3]+vp[1])+'px',width:(vp[0]-sw+vp[2])+'px'});
          //w.handle('scroll',function(){this.fullscreen();});
          return this;
      },
	  handleEnter: function(e,callback){
	  	e = e || event;
		if (e.keyCode && e.keyCode === 13){
			callback();
		}
	  },
      /**
       * modal dialog: geeft een gecentreerde box met kopje, 
       * boodschap, de mogelijkheid voor een button en een callback
       * @param {Object} param (kopje,content,bttnTxt,callback)
       *  
       */
      modalMessage: function(param){
	  	if ($('modalBox')){DOM.modalDestroy();}
        var vp = DOM.viewPort(),
            handlerDone   = $('modalLayer',true) ? true : false, 
            betweenLayer  = ($('modalLayer',true) || $({elType:'div',styleclass:'modal',id:'modalLayer'}))
                             .show()
                             .setOpaque(0.4)
                             .fullScreen(),
            modalBox      = $({elType:'div',styleclass:'modalBox',id:'modalBox'})
                            .html(param.kopje),
            modalCloser   = $({elType:'div',styleclass:'closer',id:'modalCloser',related:modalBox})
                             .html('<b>X</b>')
                             .css({cursor:$.appcache.cursorPointer}),
            modalTxt      = $({elType:'div',styleclass:'modalTxt',id:'modalTxt',related:modalBox})
                            .html(param.content);
        modalBox.css({width:modalTxt.offsetWidth+'px'});
        if (modalBox.offsetWidth() > 350){
            modalBox.css({width:350+'px'}).centerMe();
        }                       
        modalBox.centerMe(1);
        modalTxt.animateRGB('color','#313a71', 1000);
        
        if (param.bttnTxt) {
            var modalBttnDiv  = $({elType:'div',styleclass:'modalBttnDiv',id:'modalBttnDiv',related:modalTxt}),
                modalBttn     = $({elType:'button',styleclass:'modalBttn',id:'modalBttn',related:modalBttnDiv})
                                 .html(param.bttnTxt);
			if (param.callback){
            		modalBttn.handle ('click',param.callback);
			}
        }
		
        modalCloser.handle ('click',DOM.modalDestroy);
        //if (!handlerDone) { /**TODO ?? */
            $.util.handle('resize', function(){
                modalBox.centerMe(1);
            }, null, w);
            $.util.handle('scroll', function(){
                modalBox.centerMe(1);
                betweenLayer.fullScreen()
            }, null, w);
       // }
      },
      modalDestroy: function(){
        purge($('modalBox').el);
        delete DOM.elCache.modalBox;
        delete DOM.elCache.modalTxt;
        delete DOM.elCache.modalBttnDiv;
        delete DOM.elCache.modalBttn;
        delete DOM.elCache.modalCloser;
        $('modalLayer').hide();
      },
	  /**
	   * [hex]
	   * convert 
	   * @param {Object} n
	   * @param {Object} digits
	   * @param {Object} prefix
	   */
	  hex: function(n, digits, prefix) {
         var p = '', n = Math.ceil(n);
         if (prefix) p = prefix;
         n = n.toString(16);
          for (var i=0; i < digits - n.length; ++i) {
            p += '0';
          }
       return p + n;
      },
	  /**
	   * [rgbToHex]
	   * convert RGB values to hex-string
	   * @param {Object} r
	   * @param {Object} g
	   * @param {Object} b
	   * @return {String} (hex, e.g. #FFFFC0)
	   */
      rgbToHex: function(r, g, b) {
        return DOM.hex((r << 16) | (g << 8) | b, 6, '#');
      },
	  /**
	   * [xStr]
	   * check if all given arguments are strings 
	   * @param {String} s
	   * @return Boolean true/false
	   */
	  xStr: function(s){
        for(var i=0; i<arguments.length; ++i){
			if(typeof(arguments[i])!=='string') {
				return false;
			}
		}	
        return true;
      },
	  /**
	   * [parseColor]
	   * return a color values object from colorstring [c]
	   * @param {String} c
	   * @return {Object} 
	   *  /w props: n (decimal), r (red), g (green), b (blue), s (hex string)
	   */
	  parseColor: function (c){
       var o = {};
        if (DOM.xStr(c)) {
         if (c.indexOf('rgb')!=-1) {
           var a = c.match(/(\d*)\s*,\s*(\d*)\s*,\s*(\d*)/);
           o.r = parseInt(a[1]) || 0;
           o.g = parseInt(a[2]) || 0;
           o.b = parseInt(a[3]) || 0;
           o.n = (o.r << 16) | (o.g << 8) | o.b;
         } else {
           pn(parseInt(c.substr(1), 16));
         }
        } else {
         pn(c);
        }
        o.s = DOM.hex(o.n, 6, '#');
		
        return o;
		
        function pn(n) { // parse num
         o.n = n || 0;
         o.r = (o.n & 0xFF0000) >> 16;
         o.g = (o.n & 0xFF00) >> 8;
         o.b = o.n & 0xFF;
        }
      },
	  /**
	   * [styleHover]
	   * apply [hoverStyle] on hover,
	   * store the old style in property old.style
	   * can be used for mouseover/mouseout
	   * (where mouseout only needs [el] param)
	   * @param {Object} el
	   * @param {Object} hoverStyle e.g. {backgroundColor:"#999"}
	   * @return true
	   */
      styleHover: function(el,hoverStyle){
           if (el.hovered){
                el.css(el.oldStyle);
                el.hovered = false;
               return true;
           }
        el.hovered = true;
        el.css(hoverStyle);
        return true;
       },
	  /** 
	    * [viewPort]
	    * retrieve the dimensions of the current viewport
	    * @return array [width, height,scrollLeft,scrollTop]
		* 2010/06/17: weer eens aangepast voor IE
		*/
        viewPort: function() {
    		var iWidth = 0, iHeight = 0, iTop = 0, iLeft = 0, body;
    		if ('innerWidth' in w) {
				iWidth = w.innerWidth;
				iHeight = w.innerHeight;
			}
			else if (d.documentElement) {
					body = d.documentElement;
					iWidth = body.clientWidth;
					iHeight = body.clientHeight;
			}
			else if ('clientWidth' in docbody) {
						var body = d.getElementsByTagName('body')[0];
						iWidth = body.clientWidth;
						iHeight = body.clientHeight;
			}	
    		if ('pageYOffset' in w) {
    			iTop = w.pageYOffset;
    			iLeft = w.pageXOffset;
    		} else if (d.documentElement || 'clientWidth' in docbody) {
    			iTop = body.scrollTop;
    			iLeft = body.scrollLeft;
    		}
    		return [iWidth, iHeight, iLeft, iTop];
      },
      randomId: function(iLen) {
	    var sId    = ''
 	       ,isId   = ''
		   ,i       = 0
		   ,aRanges = ['48,9','65,25','97,25'];
        iLen = !iLen ? 4 : iLen;
         while (i<iLen) {
          var aRange  = String(aRanges[Math.round(Math.random()*2)]).split(',');
          isId       += String(aRanges[Math.round(Math.random()*2)])+',';
          sId        += String.fromCharCode(Math.round(parseInt(aRange[0],10)+
		                  (Math.random()*parseInt(aRange[1],10))) );
          i++;
        }
        return  sId;
	  },
	  objLen: function(obj){
				var len=0,l;
				for (l in obj){
					if (obj.hasOwnProperty(l)){len++;}
				}
				return len;
	  },
	  cleanupElCache: function(force){
	  	   var DOMCache = DOM.elCache;
	 	   for (var l in DOMCache){
		   	if (String(DOMCache[l]).match(/undef/i)){
				delete DOMCache[l];
			}
			if (DOMCache[l] && (DOMCache[l].created)){
				var now = new Date().getTime();
				if ((now - DOMCache[l].created > 60000) || force){
					DOMCache[l].el = null;
					purge(DOMCache[l]);
					delete DOMCache[l];
				}
			}
		   }
	  },
	  elCache: {},  /** cached elements */
	  handlerCache: [], /** cached methods */
	  /**
	   * [findPos]
	   * Find absolute top (y) and left (x)
	   * position of element [obj]. Base
	   * code from PPK, adjusted for use
	   * in scrolled objects (need the
	   * boolean [isParent] for that)
	   * Private.
	   * IE8: fixed elements don't have
	   * an offsetParent 
	   * @param {DOMElement} obj
	   * @return object {left,top}
	   */
	   findPos: function(obj){
            var isParent = false,
                wobj = $(obj),
				vwport = DOM.viewPort(),
				curPos = {left:0,top:0};
				
            if (wobj && wobj.getCS('display').match(/none/i)){
                wobj.hidden().css({display:'block'});
            }

            if (obj.offsetParent || obj.parentNode) {
             do {
                  curPos.left += obj.offsetLeft;
			      curPos.top  += obj.offsetTop;
				  obj = $.appcache.isIe8 && !obj.offsetParent ? obj.parentNode : obj.offsetParent
			    } while (obj && obj != docbody); 
		   } 
           return curPos;
      },
	  /**
	    * [getElementsByTagNames]
	 	* @param {String} tagnames => comma delimited string with tagnames
	 	* @param {Object} rootEl => the element containing the wanted elements
	 	* @return an array of elements with tagnames defined in [tagnames]
	 	* note: if we didn't have to live with IE, this could have been a
	 	* htmlElement prototype handler. And this would have worked too.
	 	* Damned browser! NodeList.prototype doesn't work either...
	 	* So, IE can't do implicit cast of NodeType to array. Method to
	 	* convert NodeList to Array added (Util.NodeListToArray)
	 	*/
	  getElementsByTagNames: function(tagnames, rootEl, noWrap){
	  	 if (!tagnames){return [];}
		 rootEl = rootEl || docbody;
		 tagnames = tagnames.split(',') || ['*'];
		 
		 /** gebruik xpath als het kan (sneller) */
		 if ($.appcache.candoXPath){
		 	var els = rootEl.getXPath('.//'+tagnames.join('|.//')), ret=[];
			if (noWrap){
				return els; 
			}
			els.each(function(item){
				 ret.push($(item,true));
			});
			return ret;
		 }
		 
		 var elementNodes = [];
		  tagnames.each(
		     function(elstr){
		 	  var  nodes = rootEl.el.getElementsByTagName(elstr);
		      nodes.each = function(){Array.prototype.each.apply(this,arguments)};
		        if (nodes.length>0){
			        nodes.each(
				       function(node){
					   	 	elementNodes.push($(node,true));
					   }
					); /**deep closure ;-)*/
			    }
			 });
		  return elementNodes;
	  },
	  getFromXPath: function(expression,rootEl){
	  	    rootEl = rootEl || docbody;
	 		var ret = []
			   ,xresult = d.evaluate(expression,rootEl, null, XPathResult.UNORDERED_NODE_ITERATOR_TYPE, null)
			   ,result = xresult.iterateNext();
		    while (result) {
					ret[ret.length]= result;
					result = xresult.iterateNext();
			}
		return ret;
	  },	
	  /**
	   * getElementById met gebruik van xpath als het kan
	   * @param {String} id
	   * @param {Object} rootEl
	   * @param {Boolean} noWrap
	   */
	  byId: function(id,rootEl,noWrap){
	  	rootEl = rootEl || docbody;
		return $.appcache.candoXPath
		        ? DOM.getFromXPath(".//*[@id='"+id+"']",rootEl).singleNodeValue
		        : rootEl.getElementById(id);
	  },
	  /** 
	   * for the hack of it, je zou ook case insensitive met xpath kunnen zoeken op deze manier:
	   * $('content').getXPath(".//div[contains(translate(@class,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'),'FOLD')]");
	   * dan heb je in 1 keer de hele handel bij elkaar
	   * ... AND done
	   */
	  getElementsByClassNames: function(classnames, tagnames, rootEl, noWrap){
	  	 if (!classnames){return [];}
		 rootEl = rootEl || docbody;
		 tagnames = tagnames || '*';
		 
		 /** 
		  * xpath gebruiken als het kan
		  * nb, xpath 1.0. Anders hadden we upper-case/matches oid kunnen gebruiken
		  */
		 if ($.appcache.candoXPath){
		 	var  classContains = "contains(translate(@class,'abcdefghijklmnopqrstuvwxyz','ABCDEFGHIJKLMNOPQRSTUVWXYZ'),'",
			     classTerm = "[" +classContains
				                 +classnames
			                      .toUpperCase().split(",")
							      .join( "') or " +classContains )
							     +"')]";
			var xPath = './/'+tagnames.split(',').join(classTerm+'|.//')+classTerm;

		 	var els = rootEl.getXPath(xPath), ret=[];
			if (noWrap){
				return els; 
			}
			els.each(function(item){
				 ret.push($(item,true));
			});
			return ret;
		 }
		 classnames = classnames.split(',');		 
		 var elements = DOM.getElementsByTagNames(tagnames,rootEl)
		     ,elementNodes = [];
		 elements.each(
		     function(node){
                   if (node) {
				   	  var re = new RegExp(classnames.join('|'),'gi');
					  if (node.classContains(re)){
					  	elementNodes.push(node);
					  }
			      }
			}); /** elements.each */
		 return elementNodes;
	  },
	  waitFor: function(what, action) {
		var counter = 0; //max 5 sec
		function chkwhat() {
			var tst = what instanceof Function ? what() : $[what];
			if (!tst && counter < 5000) {
				counter += 10;
				setTimeout(chkwhat, 10);
			} else {
				if (counter >= 5000) {
					if (action instanceof Function) {
						clearTimeout(0);
						return action();
					}
					defaultStatus += ' Module ' + what + ' may not be loaded ...';
					clearTimeout(0);
					return true;
				}
				clearTimeout(0);
				return action();
			}
		}
		chkwhat();
	  },
	  /* LEKT?
	   * wait: function(delay) {
  			return {
    		 then: function (callback) {
      			setTimeout(callback, delay*1000);
    		}
  	      };
	  },*/
      statAdd: function(arg){
          defaultStatus += arg;
      },
	  listObj: function(obj) {
        var result = [];
         for (var l in obj) {
            if ((obj.hasOwnProperty && obj.hasOwnProperty(l)) 
			      && !(obj[l] instanceof Function)) {
                 result.push(l + ': ' + obj[l]);
            }
         }
        return result;
      },
      debugHide: function(e){
	  	e = e || event;
        var box = $('debugScreen'),
		    link = e.srcElement || e.target;
		if (!e.ctrlKey){return true;}
	    link.blur();
        if (box.getCS('display').match(/none/i)){
            return box.show();
        } else { 
            return box.hide();
        }
      },
      debug: function(){
		var devinf = $('devNdebug'),
			debugBox = $('debugScreen') ||
					   $({id:'debugScreen',elType:'div',inject:'append'})
					     .css({width:'22%',right:'0',top:'30px',height:'65%',overflow:'hidden',
						       fontColor:'green',background:'#EEE',zIndex:1000,
  		        	     	   border:'1px solid #999',padding:'5px',
						       fontWeight:'bold',zIndex:15,position:'absolute',
						       textAlign:'center'}).html('Berichten &amp; debuginfo').addCssClass('draggable'),
			debugTxt = $('debugTxt') ||
						  $({id:'debugTxt',elType:'div',related:debugBox}).abs()
	              		  .css({padding:'6px',fontWeight:'normal',fontSize:'85%',
						        lineHeight:'140%',
						        background:'#FFFFF0',zIndex:1000,textAlign:'left',
  		        	            border:'1px solid red',padding:'8px',marginTop:'0.7em',
						        left:'5px',bottom:'5px',top:'1.6em',right:'5px',overflow:'auto'})
						 .html(devinf ? devinf.innerHTML() : ''),
			line = '<div><span class="warn">&nbsp;'+new Date().toFormat('HR:MI:SE')+' &gt;</span> _mssg_ </div>',
			lines = [],
			args = Array.prototype.slice.call(arguments);
        if (arguments[0] === 'hidden') {
			debugBox.hide();
			return true;
		}
		args.each(function(item){
			var l = line;
			lines[lines.length] = l.replace(/_mssg_/i,item);
		});
		debugTxt.html('insert',lines.reverse().join(''));
       	
       //debugTxt.el.scrollTop = debugTxt.offsetHeight();
		return true;
	  }
     };
	}/** DOM end */

    /** 
     * point top level (window.) $ to local $,
     * so the top level namespace is $
     */
    w.$ = $;
	/** publish a few methods in the $ namespace*/
	$.DOM = DOM;
	$.handle = methods().handle;
	$.apptimer = apptimer;
    $.purge = purge;
    /** use or create a namespace */
    var namespace = param.namespace || 'Wrapped';
	w[namespace]  = $; /** double reference */
	$.loadWait = $.DOM.waitFor;
	$.wait = $.DOM.wait;
	/**
	 * save much used stuff in an appcache
	 */
	$.appcache = {
		isIe6: navigator.appName.match(/internet expl/i) &&
								parseFloat((navigator.appVersion).split(/MSIE|,/i)[1]) < 7,
		isOpera: window.opera || null,
		isIe7: navigator.appName.match(/internet expl/i) &&
									parseFloat((navigator.appVersion).split(/MSIE|,/i)[1]) > 6,
		isIe8: navigator.appName.match(/internet expl/i) &&
									parseFloat((navigator.appVersion).split(/MSIE|,/i)[1]) > 7,
		isIe: (document.all && !window.opera) || null,
		isMoz: window.HTMLBodyElement ? true : false,
		isWebkit:  navigator.userAgent.toLowerCase().indexOf('webkit') > -1,
		cursorPointer: this.isIE ? 'hand' : 'pointer',
		body: docbody,
		candoXPath: 'evaluate' in d,
		clearPeriodically: true,
        scrbarW: 22 /*DOM.scrollBarWidth()*/
	};
    DOM.debug('using XPath: '+$.appcache.candoXPath);
	/** 
	 * Add include libraries (defined in the parameter property of the arguments object)
	 * @param {Object} constructr -> the constructor as it is send in an array of 
	 * constructors. The constructors have to be defined of course
	 */
    if (param.includes && param.includes instanceof Array){
		param.includes.each (
		 	 function(constructr){
             	constructr.apply($,[d,w]);
         });
    }
    w[namespace].toString = function() {return '['+namespace +' initialized]';};
    
	/**
	 * run an optional startup function
	 */
    if (param.startUp){
		param.startUp();
	}
	
	/**
	 * initialize a debug screen (hidden or visibile)
	 */
	apptimer.stop('js-verwerktijd');

	w.onerror = function(a,b,c){
            /**TODO: fout op server loggen*/
			return DOM.debug(
			             '<b>fout</b>: '+a,
			             '<b>in</b>: '+b.substr(b.lastIndexOf('/')+1),
						 '<b>regel</b>: '+c);
	};

	
	if (w.showDebugScreen === 1){
        DOM.debug(apptimer);
	} else {
		DOM.debug('hidden');
		DOM.debug(apptimer);
	}
	if ($.appcache.clearPeriodically){
		setInterval(DOM.cleanupElCache,120000);
	}
	w.onbeforeunload = function(){DOM.cleanupElCache(true);};
	return 1;
} /** DOMWrapper END */

/** -----------------------------------------------------
*   MODULE main
*   scripting voor RGOc.
*   NOTES:
*   voor publicaties een uitzondering op het instellen
*   van externe links. Dit is in IE enorm traag (zo'n
*   200 links, pfft). Dank wederom MS.
*   20100117: opgeruimd (branch6)
*  -----------------------------------------------------
*/
function MainWrapper(d,w){
  var _x = this,
     Main,
     Menu,
     Bericht,
     RSH,
     nwEl,
     xhr,
     appCache,
     debug,
     sitemap;
  _x.main = {
    debug : _x.DOM.debug,
    onLoad: function(){
          appCache = _x.appcache;
          xhr = _x.xhrbasic.rCall;
          Main.mainLoader();
    },
    mainLoader: function(){
      debug = Main.debug;
      /** xUri nu relatief t.o.v. huidige directory */
      appCache.xUri      = '/service/services.asp';
      appCache.wait      = $('wait');
      appCache.pages     = {}; /** pagecache */
      appCache.content   = $('content');
      appCache.defaultPage = 'home';
      appCache.currentHash = location.hash || '#home';
      appCache.pageload  = false;
      appCache.menuLinks   = {};
      appCache.menuCurrent = '';
	  appCache.querystring = null;
      sitemap = _x.menu.sitemap('home/sitemap');
      /**
       * Histyory-emulatie:
       * monitoren van location.hash-wijziging. Alle pagewijzigingen komen
       * hier vandaan. Links in de page wijzigen de hash. Dat wordt
       * hiermee gedetecteerd (elke 500 milliseconden check, dus 2
       * keer per seconde. Dat is voldoende voor snappy responses).
       */
      setInterval(
        function(){
          /**
           * opera houdt niet van '?' in de hash-value
           */
          var hashNow = window.opera ? '#'+location.href.split(/#/)[1] : location.hash;
          if (hashNow !== appCache.currentHash) {
            Main.updatePageCache();
            appCache.currentHash = hashNow;
            RSH.loadFromHash(hashNow);
            return true;
          }
          return false;
           }, 200);
      /**
       * menugegevens opslaan
       * kruimelhandlers initialiseren
       */
      Menu.setMenuIds();
      Main.setLinkBlur();
      defaultStatus = 'Rob Giel Onderzoekcentrum';
     
      /**
       * waarschuwing ie<7 gebruikers
       * TODO
       */
      if (appCache.isIe6) {
        Main.ie6Warn();
      }
      /**
       * IE < 8 history emulatie via iframe, initialisatie hier
       * 03/2009: IE8 uitgesloten, die kan het met de hash af.
       */
      if (appCache.isIe && !appCache.isIe8){
        _x.RSH.IEHashInit();
      }
      /** eerste keer laden of reload,
       *  wat er geladen moet worden
       *  wordt via de hashvalue vastgesteld
       *  FF doet soms 2x xhr, maar ach...
       */
	  RSH.loadFromHash();
      $.util.handle('keyup',Main.emptyPageCache,false,d);
	  $('debuggerview').handle('click',$.DOM.debugHide);
      return true;
    },
    /**
     * waarschuwing en linkjes IE6
     * TODO: modalwin van maken
     */
    ie6Warn: function(){
       $.DOM.modalMessage(
         {
        kopje: 'Waarschuwing',
        content:  $('ie6warn').innerHTML()
      }
       );
    },
    /**
     *  focus weghalen bij klik op elke link
     *  (dus geen stippellijntjes die blijven staan)
     */
    setLinkBlur: function(){
      /**blur on click*/
      var hrefs = $('container').childTags('a');
      hrefs.each(
         function(item){
          item.handle(
            'click',
            function(){
              this.blur();
              }
          );
          item.handle('mouseover',function(){
            defaultStatus=''
            return false;
            }
          );
        }
      );
     },
     /**
      * voor pages met uit/invouwmechanisme: state onthouden
      * (scheelt ook xh-requests (bv bij onderzoekssamenvattingen)
      * aangepast zodat bij elke page-change de huidige page-state
      * wordt opgeslagen (ivm tooltipellende)
      */
     updatePageCache: function(){
       var pageId = appCache.currentHash.replace(/%20/g,' ').substr(1);
       if (pageId in appCache.pages){
         appCache.pages[pageId] = $('content').innerHTML();
       }
     },
     emptyPageCache: function(e){
       e = e || event;
	   var pageId = location.hash.substr(1);
	   
       if (!(e.shiftKey && e.keyCode === 46)){return true;}
       for (var page in appCache.pages){
           delete appCache.pages[page];
       }
	   Main.loadPage(pageId);
       $.DOM.modalMessage(
          {
              kopje:   'pageCache leeg',
              content: 'De cache is geleegd en de huidige pagina opnieuw geladen.'
          }
       );
       return true;
     },
     /**
      * ihb nieuws: laat alleen kopjes zien
      */
     showHeadersOnly: function(){
      var meerLinks = $.DOM.getElementsByClassNames('fold','div',appCache.content),
	      imglink = '<img src="../img/isdicht.gif" style="margin-bottom:-2px;"/>';
      meerLinks.each( function(item){
        item.removeCssClass(/open/gi);
        var divhide = $(item.id.replace(/_\d+$/g)+'Txt',true);
        if (divhide) {
          divhide.addCssClass('hidden');
        }
      });
      if (!$('kopjes').innerHTML().match(/klik op een/i)) {
        $('kopjes').html(true,
          ' (klik op '+imglink+' om de bijbehorende tekst te lezen)');
      }
      Main.updatePageCache();
     },
     /**
      * laat een divje zien of verbergt het
      * let op: het wrappen van zo'n element moet iedere keer
      * opnieuw (want als je vanuit de cache weer in content
      * plaatst, corresponderen de gebruikte gewrapte elementen
      * niet meer met wat er in die content staat. Daarom:
      * true als 2e parameter, waarmee het element opnieuw wordt
      * gewrapped.
      * Voor alle situaties (inline links, uitklapkopjes, inline
      * 'minder' links enz)
      */
     ToonVerberg: function(e){
      e = e || event;
      $.tips.hideAllTips();
       var el     = $.util.getEvt(e).src,
        selector  = el.id.match(/_\d$/) ? $(el.id.replace(/_\d$/,''),true) : $(el,true),
        html,
        divtoshoworhide,
        foldId,
        /**
         * TODO: nodig of niet?
         * trekt even de aandacht bij inklappen inline meer-tekst
         */
        scrollBack = function(){
          if (!selector.el.tagName.match(/div/i)) {
               w.scrollTo(0, selector.el.parentNode.offsetTop);
               selector.animateRGB(
                'color',
                '#313a71',400,
                 function(){
                   selector.animateRGB('color','#FF9A30',800);}
                 );
          }
        };
      /**
       * bij html in het geklikte element moet het
       * juiste element er eerst bij worden gezocht.
       * Geldt ihb voor DIV-elementen (nieuwskoppen)
       */
      if (!selector.classContains('fold')) {
        var parent = el.parentNode;
        while (parent = parent.parentNode) {
          if (parent && $(parent).classContains('fold')){
            foldId = parent.id;
            selector = $(parent,true);
            break;
          }
        };
      } else {
        foldId = selector.id;
      }
      divtoshoworhide = $(foldId+'Txt',true);
      /**
       * abstracts remote ophalen. Te herkennen aan
       * |[getal]| in id
       */
      if (selector.id.match(/(\|\d+\|)/i) && !divtoshoworhide) {
          return Main.getRemoteAbstract(selector);
      }
      if (!divtoshoworhide){
        return true;
      }
      if (divtoshoworhide.classContains(/hidden/i)){
        selector.addCssClass('open');
        /**
         * inline 'meer' wordt verborgen
         * zorg dat er onder de uitklaptekst
         * een 'inline minder' linkje staat
         */
        if (selector.classContains('inline')) {
          selector.css({
            visibility: 'hidden'
          });
        }
        else {
          selector.addCssClass('close');
        }
        divtoshoworhide
            .removeClass(/hidden/gi)
            .css({color:'#FFFFFF'})
            .animateRGB('color','#313a71',1000,
              function(){
                divtoshoworhide.css({color:'inherit'});
               Main.updatePageCache();
               }
            );
        } else {
          selector.removeCssClass(/open/gi);
          divtoshoworhide.animateRGB('color','#FFFFFF',150,
              function(){
                divtoshoworhide.addCssClass('hidden');
                divtoshoworhide.css({color:'#313a71'});
                Main.updatePageCache();
              scrollBack();});
          if (selector.classContains('inline')) {
            selector.css({
              visibility: 'visible'
            });
          } else {
            selector.removeCssClass(/close/gi);
          }
         }
		 return true;
     },
	 filterList: function(el,publist){
	 	var fn = function(){Main.doFilter(el,publist);}
		    ,to = setTimeout(fn,15)
		    ,fr = $('filterResult',true).show(); 
	 	fr.html(fr.innerHTML().replace(/\d/g,'?'));
	 	return true;
	 },
	 /**
	  * TODO: hier zit denk ik een stevig geheugenlek (IE/FF).
	  * @param {Object} el
	  * @param {Object} publist
	  */
     doFilter: function(el,publist){
       var filterEl  = $(el,true) || $('filterTerm',true),
	    f, 
		content = $('content',true),
        isEn = $('enSrch',true),
        val  = filterEl.el.value.trim(),
        filterStr = filterEl.el.value
               .replace(/(\[|\.|\$|\^|\{|\}|\(|\)|\-|\*|\]|\+)/g,'\\$1')
               .replace(/ /g,'\\s+')
               .replace(/<|>/,''),
		zoeklijst = $('zoeklijst',true),
		fragment,
        rows = (function(){
				  var getList = publist ? 'p' : 'li';
				  fragment = d.createDocumentFragment();
				  fragment.appendChild(zoeklijst.el.cloneNode(true));
				  f = $(fragment.firstChild,true);
				  return f.getElementsByTagName(getList);
			     }
				)(),
        counter = 0,
        allcount = rows.length,
        filter,
        resultEl = $('filterResult',true),
        lst =  isEn ? 'none' : 'geen',
        fnd = isEn ? ' found' : ' gevonden',
        notfnd = isEn ? ' nothing found' : ' niets gevonden',
        searchedVal = (val==='' ||  val === '*')
                ? '[filter: <b>'+lst+'</b>]' : '[filter: <b>'+filterEl.el.value+'</b>]';
         
	    if (!val){
			rows.each(function(item){
				if (item) {
					var wrappedItem = $(item,true);
					wrappedItem.removeCssClass(/hidden/g);
				}
			});
		} else {
         filter = new RegExp(filterStr,'ig');
		 rows.each(function(item){
				if (item) {
					var eItem = $(item,true)
					   , itemChk = item.nodeName.match(/li/i) ? $(eItem.getFirstTag('div')): eItem;
					if (itemChk.matchTxt(filter)) {
						eItem.removeCssClass(/hidden/g);
					}
					else {
						eItem.addCssClass('hidden');
						counter++;
					}
				}
		 });
		}
        counter = ' &gt; '+(counter === allcount ? notfnd : (allcount-counter)+fnd);

        resultEl.html(searchedVal+counter);
		zoeklijst.innerHTML = fragment.firstChild.innerHTML;
		
        if (!el) {
             filterEl.el.select()
        };
		/**
		 * todo: kijken of we de id's uit de oorspronkelijke lijst kunnen
		 * handhaven, dan hoeft dit ook niet meer. Is nog vrij lastig...
		 * DONE
		 */
		//if (!publist && !$.appcache.isIe) {
		  //Main.dynamicHandling(zoeklijst);
		//}
		fragment = null;
		$.DOM.cleanupElCache();
        return true;
     },
     /**
      * [getRemoteAbstract]
      * onderzoekssamenvattingen, samenvatting ophalen
      * via toonVerbeg. Als de samenvatting al bestaat
      * komt het hier niet meer uit (id kopje wordt aangepast)
      * @param {Object} el (uit het geklikte element (kopje))
      */
     getRemoteAbstract: function(el){
       var abstractId = (el.id.split)('|')[1],
         abstrEl = $(el.id+'remoteTxt',true).removeCssClass(/hidden/gi),
         title   = $(el.id,true).addCssClass('open'),
         callback = function(oResp) {
           abstrEl.html(oResp.data)
              .css({color:'#FFFFFF'})
              .animateRGB('color','#313a71',1000,
                function(){
                  abstrEl.css({color:'inherit'});
                  Main.updatePageCache();
                }
               );
          abstrEl.el.id = abstrEl.el.id.replace(/remote/i,'');
          Main.updatePageCache();
         };
       xhr(
         appCache.xUri,
        { call: el.id.match(/abstren/i) ? 'getAbstrItemEn' : 'getAbstrItem',
          id: abstractId,
          noTipHide: false },
          callback );
    },
    /**
     * [xWait]
     * laat wachtplaatje zien alvorens
     * [callback] uit te voeren
     * @param {Function Ref} callback
     */
    xWait: function(callback){
      if ($('wait')){
          $('wait').removeCssClass('hidden');
          var cb = function(){
            callback();
            clearTimeout(0);
            setTimeout(function(){$('wait').addCssClass('hidden');},1000);
          };
          setTimeout(cb,1);
        }
    },
    /**
     * [loadPage]
     * laadt page met id [pageId], ofwel XHR, of
     * uit cache. Als XHR, dan wordt die page na
     * ophalen in appCache.pages gezet. Als
     * [pageId] niet wordt meegeleverd, dan
     * wordt de welkomspage geladen ('welkom') //TODO, voor mockup lorips!
     * Wordt doorgaans via loadFromHash
     * geactiveerd.
     * TODO: 'welkom'-parameters in appCache
     * zetten, komt vaker voor.
     * @param {String} pageId
     */
    loadPage: function(pageId) {
      $.tips.hideAllTips();
      pageId = pageId.toLowerCase() || 'home';
	  if (pageId.match(/intranet/i)){
	  	top.location.replace('https://intranet.rgoc.nl/');
		return true;
	  }
	  if (pageId.match(/\?/)){
	  	var q = pageId.split(/\?/);
		appCache.querystring = q[1];
		pageId = q[0];
	  }
      var clickId = pageId.replace(/\//g,'_')
                .replace(/\s/g,'%20')
                .replace(/_$/,''),
        clickEl = $(clickId),
        crum,crumEl,call;
      /** URL fout (link bestaat niet), no dice */
      if (!clickEl){
        appCache.content.html('pagina ('+pageId+') niet gevonden!');
        return true;
      }
      /**
       * kruimelspoor maken
       */
      crum = Menu.crumble(clickEl.el.parentNode);
      crumEl = $('kruimel');
      crumEl.html(pageId.toLowerCase() !== 'home' ? crum : '');
      d.title = 'Rob Giel Onderzoekcentrum - '+clickEl.el.rel.replace(/<.*?>/g,'');
      Menu.setSelected(clickEl);
      if (pageId in appCache.pages){
        appCache.content.html(appCache.pages[pageId]);
        w.scrollTo(0,0);
        if (!pageId.match(/publicaties/i)) {
          Main.dynamicHandling(appCache.content);
        } 
		if (appCache.querystring){ 
		   Main.handleQ();
		}
        return true;
      }
      /**
       * wachplaatje als het van de server moet
       * komen (in content)
       */
      $('content').html($('wait').innerHTML());
      /**
       * uitzonderingen check (pages uit database),
       * anders server side -> getPage
       * nb apple: pageid van %20 ontdoen
       */
      call =  pageId.match(/abstracts/i)
          ? 'getAbstrListEn' :
            pageId.replace(/%20/g,' ').match(/samenvattingen onderzoek/i)
          ? 'getAbstrList' :
            'getPage';
      var callback = function(oResp){
        var content = oResp.error
                ? 'De website is in opbouw. Deze informatie is nog niet beschikbaar<br />'+
                   'of er ging iets verkeerd bij de server: <br />'+oResp.error
                : oResp.data;
        appCache.pages[pageId] = content;
        appCache.content.html(appCache.pages[pageId]);
		if (!pageId.match(/publicaties/i)) {
			Main.dynamicHandling(appCache.content);
		}
		if (location.href.match(/\?/)){
			Main.handleQ();
	    }
		
        w.scrollTo(0,0);
      };
      xhr(
         appCache.xUri,
         {
           call: call,
           page: pageId,
           noTipHide: false
          },
          callback
        );
    },
	/**
	 * querystring verwerken
	 */
	handleQ: function(){
		var pageid = location.href,
			query  = (pageid.split(/\?/)[1]).split(/\=/);

		if (query[0].match(/filter/i)){
			var filterinput = $('filterTerm',1);
			if (!filterinput){return true}
			filterinput.el.value = query[1].replace(/%20/g,' ');
			if (pageid.match(/medewerker|abstra|samenvat/i)) {
				setTimeout(function(){Main.filterList(filterinput.el);},50);
			} else if (pageid.match(/publicat/i)) {
				setTimeout(function(){Main.filterList(filterinput.el,1);},50);
			}	
		}
		appCache.querystring = null;
	},
    /**
     * Handler initialiseren voor elementen met fold-class,
     * voor uit/invouwen van extra tekst. Tiptxt werkt niet.
     */
    dynamicHandling: function(el){
      var meerLinks = $.DOM.getElementsByClassNames('fold','div,span',appCache.content),
        tiptxt = 'tt:'+$.util.quot(
                'txt:deze verwijzing opent in een nieuw venster of nieuwe tab,'+
                'width:175,sticky:1,cursor:pointer'
                );
	   
       meerLinks.each(function(item){
        if (!item.classContains('instr') && !item.isHandled) {
          item.handle('click', Main.ToonVerberg);
		  if (!item.classContains('open') && !item.isHandled){
		  	var itemTxt = $(item.id+'Txt');
		  	if (itemTxt) {
				itemTxt.addCssClass('hidden');
			}
		   }
		   item.isHandled = true;
        }
       });

    },
    /**
     * evt externe links in tekstblok van target voorzien
     * voor page met veel links (bv publicaties) is dit in
     * IE weer eens een trage ramp bij veel links.
     */
    setLinksExternal: function(){
      var lnks = el ? $(el).childTags('a') : appCache.content.childTags('a');
      lnks.each(
         function(item){
        var el = item.el;
        if (el.rel && el.rel === 'external') {
          el.target = targetTxt;
          el.title  = tiptxt;
        }
         }
       );
    },
    /**
     * activeert een gebeurtenis zonder fysiek
     * substraat zullen we maar zeggen.
     * TODO: naar util of DOMwrapper
     * @param {Object} el //het element
     * @param {String} etype //de gebeurtenis
     */
    eventFire: function(el, etype){
      if (el.fireEvent) {
        (el.fireEvent('on' + etype));
      } else { // NOG MAAR EENS UITZOEKEN
        var evObj = document.createEvent('Events');
        evObj.initEvent(etype, true, false);
        el.dispatchEvent(evObj);
      }
    }
  }; //_x.main def end
  /**
   * Module RSH (mijn variant op Really Simple History):
   * voor IE < 8. Waarin history via
   * iframe wordt bijgehouden en dat dan
   * weer zonder dat er een roundtrip voor
   * de iframe source nodig is.
   */
  _x.RSH = {
    IEHashInit : function(){
       var hashFrame = document.createElement('iframe');
       document.body.appendChild(hashFrame);
       hashFrame.style.display = 'none';
       appCache.frameContentTemplate =
           ['<html><head><script>top.location.hash = ',
           '\'_hashvalue_\';</scr'+'ipt></head><body>',
           '_hashvalue_</body></html>'].join('');
      hashFrame.src = 'javascript:"'
      +appCache.frameContentTemplate.replace(/_hashvalue_/,location.hash || '#page?'+appCache.defaultPage)+ '"';
      appCache.hashFrame = hashFrame.contentWindow;
    },
    IEHashRenew: function(newHashValue){
       if (appCache.isIe && !appCache.hashFrame){
         RSH.IEHashInit();
       }
       appCache.hashFrame.document.open();
       appCache.hashFrame.document.write(
             appCache.frameContentTemplate.replace(/_hashvalue_/gi,newHashValue)
          );
       appCache.hashFrame.document.close();
       appCache.hashFrame.location.hash = newHashValue;
    },
    setIEHash: function(newHashValue){
       if (appCache.hashFrame && appCache.hashFrame.location.hash !== newHashValue){
         RSH.IEHashRenew(newHashValue||'#'+appCache.defaultPage);
       }
    },
    /**
     * checkt hash en start laden van page
     * tevens wordt de title van het document
     * aangepast. Doorgaans via intervalfunctie
     * die bij het laden van de page is aangezet.
     * @param {String} hash
     */
    loadFromHash: function(hash){
      hash = hash || location.hash || '#'+appCache.defaultPage;
      //document.title = 'Rob Giel Onderzoekcentrum: '+hash.split(/\?/)[1];
      var sHash,aHash;
      if (appCache.isIe) {
        RSH.setIEHash(hash); // hash2history here
      }
      sHash = hash.substr(1);
      if  (sHash.length > 0) {
          Main.loadPage(sHash);
      } else {
          Main.loadPage(appCache.defaultPage);
      }
    }
  }; /** RSH module END */
  /** menu module START */
  _x.menu = {
    /**
     * sitemap uit huidige menu distilleren. Wordt
     * nu bij start page gedaan (en dus in de page-cache gezet),
     * omdat het anders in de knoei komt met het openen/sluiten
     * van submenu's, vooral de wat diepere niveaus.
     * @param {String} sm (i.c. cache label)
     */
    sitemap: function(sm){
      if (sm in appCache.pages){
        appCache.content.html(appCache.pages[sm]);
        return true;
      }
      if (!appCache.pages[sm]){
        /**
         * initialisatie, bij laden nu in pagecache
         */
        var menuClone = $('nav').el.cloneNode(true),
          hrefs,uls,lis, l;
        menuClone.id = 'sitemap';
        hrefs = menuClone.getElementsByTagName('a');
        l = hrefs.length;
        while(--l){
          hrefs[l].id = '';
          hrefs[l].className = '';
        }
        uls = menuClone.getElementsByTagName('ul');
        l = uls.length;
        while(--l){
           uls[l].style.height = 'auto';
           uls[l].style.left = '';
        }
        lis = menuClone.getElementsByTagName('li');
        l = lis.length;
        while(--l>-1){
           if (lis[l].className){
            if (lis[l].className.match(/l1/i)) {
               lis[l].getElementsByTagName('a')[0].className = 'vet';
           }
          lis[l].className = '';
           }
        }
        appCache.pages[sm] = menuClone.innerHTML.replace(/\s+/g,' ');
        return true;
      }
    },
    getRef:function(fromli, single){
       var ref = fromli.getElementsByTagName('a')[0],
         hr = '<'+'a href="_href_">_reftxt_</'+'a>';
       if (ref){
        return single
           ? ref.rel
           : hr.replace(/_href_/i,ref.href)
             .replace(/_reftxt_/i,ref.rel);
      }
     return '';
    },
    crumble: function(el) {
     el=$(el).el;
     if (!el){
      return 'Not Found';
     }
     var parent, cr = [],
       El = el.tagName.match(/^a$/i) ? el.parentNode : el;
     if (El.parentNode){
      parent = El.parentNode;
      do {
        if (parent.tagName && parent.tagName.match(/li/i)){
          cr.push(Menu.getRef(parent));
        }
       } while (parent = parent.parentNode);
      }
      cr.splice(0,0,Menu.getRef(el,1));
      if (!cr[0].match(/#home/i) && !cr[cr.length-1].match(/#home/i)){
        cr[cr.length] = '<'+'a href="#home">Home</'+'a>';
      }
      return cr.reverse().join(' &gt; ');
    },
    checkOpen: function(el){
      var refs = el.getElementsByTagName('a'), i = refs.length;
      while(i--){
        if (refs[i].href.match(location.hash)){
          return true;
        }
      }
      return false;
    },
    shiftupdown: function (el,uTotalTime,force,chain){
     /** menuoptie is al open en moet open blijven: niks te doen
      *  dit ook even uitgezet, om te zien hoe het is als menu
      *  altijd wel wordt gesloten als i open is (blijft dan wel
      *  geselecteerd als het goed is) mmm, maakt niks uit!
      */
     if ((force && !String(force).match(/close|open/i)) &&
        (el.parentNode && el.parentNode.className.match(/open/i))
         || !$(el.parentNode,true).getFirstTag('ul')){
      return true;
     }
     var startY,
       iTargetY,
       up=false,
       elWrap   = $(el),
       parentLi = $(el.parentNode) || el.el.parentNode,
       EL     = $(parentLi.getFirstTag('ul'))
             .css({width:el.offsetWidth+'px',
                 overflow:'hidden',
                 position:'static',
                 display:'block'}),
       ELParent = appCache.menuLinks[el.id].parentLi,
       /**
        * diverse instellingen voor menuitem
        * eerst initialiseren.
        * TODO: opsplitsen, deze method is te lang
        */
       setter   =
        (function(){
            if (force!=='open' && (parentLi.classContains('open') || force=='close')){
            iTargetY = 0;
            startY = EL.offsetHeight();
            uTotalTime = uTotalTime > 400 ? 400 : uTotalTime;
            parentLi.replaceCssClass(/meer/i,'');
           }
             else {
            /** als het level1 is rest eerst sluiten*/
            if (!appCache.menuLinks[el.id].menuRoot){
              Menu.closeOthers(el);
            }
            if (ELParent && ELParent.classContains('open')){
              parentLi.addCssClass('current');
              uTotalTime = 0;
              iTargetY = EL.offsetHeight();
            } else {
               parentLi.replaceCssClass(/meer/i,'open').addCssClass('current');
               iTargetY = EL.offsetHeight();
               EL.css({
               height: '0px',
               top: ($('nav').offsetTop()+
                    el.offsetTop+
                    el.offsetHeight) + 'px'});
               startY = 0;
            }
           }
         })(),
         disp   = Math.abs(iTargetY - startY),
           freq   = Math.PI / (2*uTotalTime),
           startTime = new Date().getTime(),
         negative = iTargetY < startY,
         tmr = setInterval(timed, 1);
         /**
          * timer functie voor interval [tmr]
          */
           function timed() {
             var elapsedTime = new Date().getTime() - startTime;
             if (elapsedTime <= uTotalTime) {
            var  f = Math.round((Math.abs(Math.sin(elapsedTime * freq)))*disp);
            EL.el.style.height = (( negative ? -f : f) + startY) +'px';
          } else {
            clearInterval(tmr);
            EL.css({height:'auto'});
            if (iTargetY>0){
             /**speciaal voor IE7, static had anders inherit kunnen wezen*/
             EL.css({position:'static'});
             parentLi.removeCssClass('current');
            } else {
             parentLi.replaceCssClass(/open/i,'meer');
             EL.css({
               position: appCache.isIe6 ? 'absolute': 'fixed',
               left: '-999px'
             });
            }
            el.blur();
            /**
             * chain is een array van alle ongeopende menuitems,
             * openen van items in chain gaat iets sneller,
             * anders duurt het wel erg lang allemaal.
             * dit alles om het uitvouwen vanuit een menuitem
             * soepeltjes te houden (anders gebeurt het gelijktijdig
             * en gaan sublevels schokkerig naar beneden.
             */
            if (chain){
              var nxtel = $(chain.shift());
              if (nxtel) {
                Menu.shiftupdown(nxtel.el, 400, force, chain);
              }
            }
           }
          }
      return 1;
     },
     /**
    * zoek de LI op waarbinnen el valt
    * (en die dus moet worden geopend voor el)
    * @param {HTMLObject} el
    */
     getParentOpener: function(el){
       var parent = el.parentNode;
       if (parent){
        do {
        if (parent.tagName && parent.tagName.match(/li/i)
          && $(parent).classContains(/meer|open/i)){
          return $(parent);
        }
      } while (parent = parent.parentNode);
      }
      return null;
     },
     /**
    * geeft uniforme id's aan alle menuopties obv href
    * Slaat voor elk item informatie op die nodig is om
    * de menuboom te (her)openen (is onload handler)
    */
     setMenuIds: function(){
       var hrefs = $('nav').childTags('a');
      hrefs.each(
       function(ref){
      var el = ref.el, hash = el.href.split(/#/)[1], thisMenuLink;
      el.id = hash.toLowerCase().replace(/\//g, '_').replace(/\s| /g, '%20');
      appCache.menuLinks[el.id] = {
        el: $(el),
        menuRoot: $(Menu.parentIsLevel1(el)),
        parentLi: $.menu.getParentOpener(el)
      };
      $(el).handle(
          'click',
          function(e){
           e = e || event;
           var evt = $.util.getEvt(e), elR = $.DOM.findPos(this).left+this.offsetWidth;
           if (evt.pos[0] < elR && evt.pos[0] > elR - 15) {
             Menu.setSelected($(this), true);
           } else {
            Menu.setSelected($(this));
           }
          }
           );
      }
       );
     },
     /**
    * zoekt uit waar het rootitem van menuitem [el] zit
    * @param {HTMLObject} el
    */
     parentIsLevel1:function (el){
    var parent = el.parentNode;
    if (parent){
      while ( parent = parent.parentNode ) {
        if ($(parent) && $(parent).classContains(/l1/i)) {
          return parent;
        }
      }
    }
    return null;
     },
     /**
    * sluit alle hoofdmenus, behalve huidige
    */
     closeOthers: function() {
       var lis = $('nav').childTags('li');
       lis.each(
       function(item){
         var cn = item.getClassname(),
        closeIt = cn
              && cn.match(/open/i)
              && !cn.match(/current/i);
         if (closeIt) {
           Menu.shiftupdown(item.getFirstTag('a'), 400, 'close');
         }
       }
     );
    return 1;
     },
     /**
    * haal niet geopende parents
    * [te openen parents dus eigenlijk]
    * @param {HTMLObject} el
    */
     parentsOpen: function (el) {
     var parent=el.parentNode,
       ret = [];
     if (parent){
      do {
        if (parent.id.match(/^nav/i)){break;}
        var mustopen = parent.tagName
                && parent.tagName.match(/li/i)
                && parent.className.match(/meer/i);
        if (mustopen){
          ret.push($($(parent).getFirstTag('a')));
        }
      } while ( parent = parent.parentNode );
      return ret.reverse();
     }
     return null;
     },
     /**
    * stijl de geselecteerde menuoptie,
    * ontstijl de vorige geselecteerde optie
    * en vouw menu uit totaan gekozen optie
    * @param {HTMLObject} klikEl
    * @param {Bool} clicked
    */
     setSelected: function(klikEl,clicked) {
      var selectedMenuLink = appCache.menuLinks[klikEl.id.replace(/_$/,'').trim()],
        parentIsOpen =  (clicked && selectedMenuLink.parentLi &&
                  selectedMenuLink.parentLi.classContains(/open/i)) &&
                  !$(selectedMenuLink.el.el.parentNode).classContains('single')
                ? true : false,
        closedParents = Menu.parentsOpen(klikEl.el);
      if (appCache.menuCurrent.removeCssClass) {
        appCache.menuCurrent.removeCssClass(/linkSelected/gi);
      }
      appCache.menuCurrent = selectedMenuLink.el; /** cache currently selected */
      klikEl.addCssClass('linkSelected');
      if ($(selectedMenuLink.el.el.parentNode).classContains('single') &&
         !selectedMenuLink.menuRoot){
        Menu.closeOthers();
      }
      if (parentIsOpen){
        /** parent sluiten */
        return Menu.shiftupdown(selectedMenuLink.parentLi.getFirstTag('a'),400,'close');
      }
      if (closedParents.length){
        return Menu.shiftupdown(closedParents[0].el,800,'open',(closedParents.slice(1)||null));
      }
    }
  }; /** menu END */
  _x.bericht = {
    /**
     * inschrijven nieuwsbrief. All in 1:
     * start formulier, checkt ingevulde velden, verstuurt,
     * checkt of het op de server goed ging en geeft
     * feedback. Controle op invulling zowel client- als
     * server side.
     * @param {String} naam
     * @param {String} mail
     */
    nlSubscribe: function(naam,mail){
      naam = naam || '',
      mail = mail || '';
      var formtxt =  String($('nbform').innerHTML())
                 .replace(/FRMFLD/g,'')
                 .replace(/_naam_/i,naam)
                 .replace(/_mail_/i,mail)
                 .replace(/_iesucks_/gi,'input type="text"'),
        cb = function() {
        var valName = $('nbnaam').getValue(),
          valEmail = $('nbemail').getValue(),
          errMssg = [],
          nameChk = valName.match($.util.diacritics());
        if (valName.length<3 || (nameChk && nameChk.length !== valName.length)){
          errMssg.push('Uw naam is te kort (minimaal 3 letters), of bevat ongeldige tekens');
        }
        if (!Bericht.chkEmail(valEmail) || valEmail === ''){
          errMssg.push('E-mailadres niet ingevuld of ongeldig');
        }
        if (errMssg.length>0){
          $.DOM.modalMessage(
          {
           kopje: 'Controleer svp',
           content: '<ul><li>'+errMssg.join('</li><li>')+'</li></ul>',
           bttnTxt: ' Ok ',
           callback: function(){$.bericht.nlSubscribe(valName,valEmail);}
          });
          return false;
        }
        else {
          var xhrCallback = function(r){
            if (r.error === 'yes'){
             $.DOM.modalMessage(
              {
               kopje: 'Inschrijving niet geaccepteerd',
               content: 'Controleer svp de invulling van de velden.',
               bttnTxt: 'Ok',
               callback: function(){$.bericht.nlSubscribe(valName,valEmail);}
              });
              return false
            }
            $.DOM.modalMessage(
            {
              kopje: 'Ingeschreven',
              content: 'U ontvangt vanaf nu de nieuwsbrief RGO<i>c</i> per e-mail.<br />'+
                   'Een bevestiging hiervan is naar het door u ingevulde e-mailadres gezonden.'
            });
          };
          xhr(appCache.xUri,
            {
              name:valName,
             email:valEmail,
             call: 'nbSubscribe'
            },
            xhrCallback
          );
          return true;
        }
      };
      $.DOM.modalMessage(
           {
           kopje: 'Inschrijven voor de RGO<i>c</i>-nieuwsbrief',
           content: formtxt,
           bttnTxt: 'versturen',
           callback: cb
           }
        );
    },
     chkEmail: function(val){
      return val !== '' &&
           val.match(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/);
    },
    checkFields: function(velden){
      var cando = {}, foutmelding = [], t = $('detekst');
      velden.each(function(item){
        var matcher = item.classContains(/chkemail|chknaam|chkbericht/i);
        if (matcher) {
          var val = String(item.el.value).trim();
            naamChk = null;
          switch (matcher[0]){
            case 'chkemail': cando.email = [Bericht.chkEmail(val),
                            'niet ingevuld of ongeldig'];
                     break;
            case 'chknaam':  naamChk = val.match($.util.diacritics());
                     cando.naam  =  [val.length>=3
                              && (naamChk
                              && (naamChk.length===val.length)),
                            'minimaal 3 tekens, of ongeldige tekens ingevuld'];
                     break;
            case 'chkphone': val = val.replace(/\-|\s| /g,'');
                     cando.telefoonnummer =  [val.length === 10
                                   && val.match(/\d{10}/g),
                                  '10 cijfers (bv 0593-289777 of 0593 289777)'+val];
                      break;
            case 'chkbericht': cando.berichttekst =  [val.length >= 25,
                                  'minimaal 25 tekens'];
                      break;
            default: break;
          }
        }
      });
      for (var l in cando){
        if (!cando[l][0]) {
          foutmelding.push(l.replace(/email/i,'e-mailadres')+': '+cando[l][1]);
        }
      }
      return foutmelding;
    },
    verstuurBericht: function(){
      var velden = $('ptwo').childTags('input,textarea'),
        cando = Bericht.checkFields(velden);
      if (cando && cando.length>0) {
        $.DOM.modalMessage(
           {
           kopje: 'Controleer svp',
           content: '<ul><li>'+cando.join('</li><li>')+'</li></ul>'
           }
        );
        return false;
      }
      /** verzenden als alles goed is ingevuld */
      var values={ call: 'sendMessage' },
        callback = function(response){
          if (response.error === 'yes'){
          $.DOM.modalMessage(
           {kopje:'Bericht niet verstuurd',
            content:response.data}
          );
          return false;
          }
          $.DOM.modalMessage(
          {kopje:'Verzonden',
           content:'Uw bericht is verzonden.'}
          );
          velden.each(
          function(item){
            item.el.value = '';
          }
          );
        }; /**callback*/
      /** enum fields */
      velden.each(
        function(item){
        values[item.id] = item.el.value;
        }
      );
      xhr (
         appCache.xUri,
         values,
         callback
        );
      return false;
     }
  };
  Main = _x.main;
  RSH = _x.RSH;
  Menu = _x.menu;
  Bericht = _x.bericht;
  Main.onLoad();
}
	/**
	 * laadt benodigd javascript (modules).
	 */
	window.onload = function(){DOMWrapper (
	  {namespace: 'RGOc',
	   includes: [
	   			   UtilWrapper,
				   DragWrapper,
				   TipsWrapper,
				   XHRWrapper,
				   XmlParse,
		 		   MainWrapper
				 ]
	  });
	};
