if (!self[/*{*/"_loadhandlers"/*}*/])
{
	// Try to fix IE image caching issues. (IE7 doesn't need it but doesn't mind. Anything else before IE6SP1 doesn't understand it.)
	try { document.execCommand("BackgroundImageCache",false,true); } catch(e){};
	
	document.write("<style>.hideforjzs"+(document.createElement?",.hidefordom":"")+"{position:absolute;left:-249em;width:1em;}</style>");
	
	
	function GetTimer(name)
	{
		$log("Starting timer for "+name);
		var start = new Date();
		return {end:function()
		{
			var end = new Date();
			$log("Ending timer for "+name+" at "+(end-start)+"ms");
		}};
	}
	
	function ie6togglebodyhack(){
		var b = $(document.body);
		b.css('zoom',b.css('zoom')=='100%'?'normal':'100%');
	};

	///////////////////////////////////////////////////////////////////////
	// Obfuscation and utility functions
	///////////////////////////////////////////////////////////////////////
	function $_(){} // $_ is defined for backwards-compatibility; it used to be part of a page-loading trick.
	var _isIE=false // Are we using IE?
	var _sizelocked=false; // Is the page size locked at some previous size (because we're performing a drag that shouldn't alter anything)
	var keyAfterHB=false; // Has a key been pressed since the last heartbeat? (might affect session timeout)
	var _bwsnavig=null; // The button or such that was clicked recently (used by navigation warnings)
	var _clickhandlers={};
	var _overhandlers={};
	var _outhandlers={};
	var _bwswatchnavig=[];
	if (!self.hbloc) hbloc = null;
	var controldest={};
	var controlsrc={};
	var ignorehbfail=false;
	
	function parLoc(url,par)
	{
		var pos = url.indexOf("?")+1;
		if (!pos) return;
		
		url = url.substr(pos);
		url = url.split('&');
		for (var i=0;i<url.length;++i)
		{
			var bits = url[i].split("=");
			if (bits[0]==par)
				return pos;
			pos=pos+url[i].length+1;
		}
	}
	
	function arraypush(a,o)
	{
		var r = a.length;
		a[r]=o;
		return r;
	}
	
	
	///////////////////////////////////////////////////////////////////////
	// DHTML Helper functions
	///////////////////////////////////////////////////////////////////////
	
	function ge(o,n)
	{
		return o.getAttribute?o.getAttribute(n):o[n];
	}
	// Get an element, by name
	function getel(n)
	{
		var o=document.getElementById?document.getElementById(n):(document.all||document.layers)[n];
		if (!o) o=document.getElementsByName(n)[0];
		return o;
	}
	// Get the parent element/node of an element/node
	function getParent(o)
	{
		return o.parentNode||o.parentElement;
	}
	// Returns a closure (that merely calls the given closure) that can be
	// assigned to DOM objects without IE leaking memory.
	// Using the undoer correctly will prevent significant memory leaks.
	_closures = {};
	function closure2(closure)
	{
		var name = "bws"+Math.random();
		_closures[name] = closure;
		closure = null;
		return {
		maybefn:function()
		{
			if (_closures[name])
				return _closures[name].apply(this, arguments);
		},
		fn:function()
		{
			return _closures[name].apply(this, arguments);
		},
		undo:function()
		{
			delete _closures[name];
		}};
	}
	// Please use closure2 and propagate/use undo functions correctly, instead.
	function closure(fn) { return closure2(fn).fn; }
	// Get information about the document and its visible area
	// _x, _y are the scroll left-top coords
	// _w, _h are the full document width/height
	// _cw, _ch are the visible area's width/height
	// _x2, _y2 are the scroll bottom-right coords
	function getScroll()
	{
		if (!document.all||document.compatMode=='CSS1Compat') var o=document.documentElement;
		if (!o||o.scrollWidth==null) o=document.body||{};
		var r={_o:o, _x:o.scrollLeft||0, _y:o.scrollTop||0, _w:o.scrollWidth||0, _h:o.scrollHeight||0, _cw:o.clientWidth||window.innerWidth||0, _ch:o.clientHeight||window.innerHeight||0};
		if (!_sizelocked) rightx=r._w;bottomy=r._h;
		r._x2=r._x+r._cw;
		r._y2=r._y+r._ch;
		return r;
	}
	// To allow the clicking of multiple submits...
	var eventCount = 0;
	function eventStarted(r)
	{
		++eventCount;
		if (logevents) $log('eS('+r+','+eventCount+')');
	}
	var clickingButtons = false;
	function eventFinished(r)
	{
		if (logevents) $log('eF('+r+','+eventCount+')');
		if (eventCount>1) return --eventCount;
		if (rescanNames) FullNameRescan();
		--eventCount;
		
		if (buttonsclicked.length>0)
		{
			eventStarted('$c');
			clickingButtons=true;
			setTimeout(function(){
				if (eventCount!=1) {eventFinished('$c');return;}
				var b = buttonsclicked;
				buttonsclicked=[];
				for (var i=0;i<b.length;++i)
				{
					var e = b[i];
					if (e.reset)
					{
						var f = e;
						continue;
					}
					if (!e.click||!e.form)
						continue;
					var f=e.form;
					var name = e.name;
					var olde = e;
					e = document.createElement("input");
					e.type = "text";
					e.name = name;
					e.value = "1";
					e.style.display = "none";
					e._fakeSubmit = true;
					olde.parentNode.insertBefore(e,olde);
					_bwsnavig = olde;
				}
				if (f)
				{
					// Browsers try to prevent us from clicking buttons, for some reason, but
					// we can submit the form...
					if (f.onsubmit) f.onsubmit();
					if (!window.SubmitPartial) f.submit();
				}
				clickingButtons=false;
				eventFinished('$c');
			}, 1);
		}
		
		if (eventCount!=0) return;
		
		var runon=_loadhandlers.length>0;
		if (runon) var t = GetTimer("Running OnLoads");
		// If we end up terminating early, due to exception, we don't want to stop it but we do want to continue running onloads.
		var thingsbreak = setTimeout(function(){eventStarted('unbreak');eventFinished('unbreak');},1);
		while (_loadhandlers.length>0)
		{
			var lh = _loadhandlers[0];
			_loadhandlers=_loadhandlers.slice(1);
			lh();
			if (eventCount) {clearTimeout(thingsbreak);$log("Paused OnLoads");t.end();return;}
		}
		clearTimeout(thingsbreak);
		if (runon) t.end();
	}
	// Click a submit or submit a form (returning true on success)
	// Also avoids nav nags.
	var buttonsclicked=[];
	function $c(o, isTargetTop)
	{
		$log("$c("+(o.id||o.name||o)+")");
		if (typeof o=='string') o = getel(o);
		if (!o||!o.click&&!o.submit) return false;
		if (eventCount>0||buttonsclicked.length>0)
			arraypush(buttonsclicked, o);
		else
		{
			_bwsnavig = o;
			if (o.click)
			{
				if (window.SubmitPartial) SubmitPartial(null,o, isTargetTop);
				else
				{
					if (o.onclick) o.onclick();
					// TODO: Find a better way of doing this. The browser inhibits o.click() but not form.submit(), so we call the submit and then the click. If the click works, it should override the submit. Maybe we should do the fakesubmit trickery we've done elsewhere.
					o.form.submit();
					o.click();
				}
			}
			else if (o.onsubmit)
			{
				o.onsubmit();
				if (!window.SubmitPartial) o.submit();
			}
		}
		return true;
	}
	
	///////////////////////////////////////////////////////////////////////
	// Page-load handling - tries to run delayed JS functions on page-load,
	//                      but before in-page objects necessarily have.
	//                      (now just uses jQuery)
	///////////////////////////////////////////////////////////////////////
	_loadhandlers = [];
	_LoadDone = _LoadTimer = 0;
	eventStarted('init');
	_LoadObject = document;
	function init()
	{
		if (_LoadDone++) return;
		document.body.className=(document.body.className||'')+' bwsjsisactive';
		getScroll()._o.onkeypress=ddkeypress;
		
		hb();
		
		eventFinished('init');
		
		OnLoad(function(){
			var o={};
			for (var i=0;i<pending.length;++i)
				o[pending[i][0]]=1;
			for (i in o) loadjs(i);
			nowloaded("$");
		});
	}
	jQuery(init);
	
	/*!*/
	/*@cc_on @if (@_win32||@_win64||@_win128||@_win256)
	_isIE=true;
	document.documentElement.className+=" isie";
	/*@end @*/
	/*!*/
	
	// Runs the provided function after the page has finished loading.
	function OnLoad(fn)
	{
		if (!eventCount) fn();
		else arraypush(_loadhandlers, fn);
	}
	
	///////////////////////////////////////////////////////////////////////
	// Logging functionality - find out what's happening on the client
	///////////////////////////////////////////////////////////////////////
	
	function ISOWithMS(d)
	{
		return d.getFullYear()+'-'+twodig(d.getMonth()+1)+'-'+twodig(d.getDate())+' '+twodig(d.getHours())+':'+twodig(d.getMinutes())+':'+twodig(d.getSeconds())+'.'+threedig(d.getMilliseconds());
	}
	
	function threedig(n)
	{
		return (n<10&&n>=-10?'00':n<100&&n>=-100?'0':'')+n;
	}
	
	function twodig(n)
	{
		return (n<10&&n>-10?'0':'')+n;
	}
	
	function $logevents(onoff)
	{
		document.cookie="fs2logevents="+(onoff||1);
	}
	var logevents = document.cookie.indexOf("fs2logevents=1")!=-1;
	
	function $log(s)
	{
		try{if (parent!=self) {parent.$log("Ch: "+s);return;}}catch(e){}
		$log2(s);
	}
	
	var loglines=[];
	function $log2(s)
	{
		loglines.push([new Date(), s+'']);
	}
	
	var $diagline;
	function $addtodiagline(button)
	{
		if (!$diagline)
		{
			$diagline = $("<div style='position:absolute;top:0px;left:0px;width:100%;border:0;padding:0;margin:0;color:#FFFF00;background-color:#0000A0'>Diag: </div>");
			OnLoad(function(){
				$diagline.appendTo(document.body);
				document.body.style.marginTop="1.5em";
			});
		}
		if (typeof button == 'string')
			var button = $("<button>a</button>").text(button);
		$diagline.append(button);
		return button;
	}
	
	var logarraybutton;
	function $AddSubmitLogButton()
	{
		if (logarraybutton) return;
		logarraybutton = $addtodiagline("Submit Log");
		logarraybutton.click(function()
		{
			var lf=$("<form method='POST' target='logbuttondest' action='/default.aspx/JSLog/' style='width:200px;height:200px;position:absolute;left:-2000px;top:-2000px'><textarea name='jslogjslog'></textarea></form>");
			$('textarea',lf).val(loglines.join("\n"));
			lf.appendTo(document.body);
			var ifr=$("<iframe name='logbuttondest'></iframe>");
			ifr.appendTo(document.body);
			lf[0].submit();
			$logdone = function(){ifr.remove();lf.remove();alert("Done");};
		});
	}
	
	// This is kept for backwards-compatibility with instructions given to Lincs and Corby
	function $logtoarray()
	{
		document.cookie='fs2debug=array';
		$AddSubmitLogButton();
		$logevents();
	}
	if (document.cookie.indexOf('fs2debug=array')!=-1) $logtoarray();
	
	//onerror = function (msg, url, line) {
	//	$log("Big error: Line "+line+" of "+url+": "+msg);
	//}
	
	var logwindowbutton;
	function $AddWindowLogButton()
	{
		if (logwindowbutton) return;
		logwindowbutton = $addtodiagline("Log Window");
		logwindowbutton.click($logwindowgo);
	}
	
	var logwindow;
	var $windowloggedcount = 0;
	function $logwindowgo()
	{
		if (!logwindow)
		{
			var m=/^fs2(main|debug)(\d+)$/.exec(window.name||'');
			if (!m)
			{
				var num = Math.floor(Math.random()*100000);
				window.name='fs2main'+num;
				m=[0,'main',num];
			}
			if (m[1]=='main')
				logwindow = window.open('','fs2debug'+m[2]);
			if (!logwindow.init)
			{
				logwindow.document.open();
				logwindow.document.write("<html><head><title></title></head><body></body></html>");
				logwindow.document.close();
				logwindow.init=true;
			}
			logwindow.document.title='FS2 Debug for '+location;
		}
		for (;$windowloggedcount<loglines.length;++$windowloggedcount)
			$logwindowline(loglines[$windowloggedcount][0],loglines[$windowloggedcount][1]);
	}
	
	function $logwindowline(t,s)
	{
		try{
			var div = logwindow.document.createElement("div");
			var time = logwindow.document.createElement("tt");
			time.appendChild(logwindow.document.createTextNode(ISOWithMS(t)));
			div.appendChild(time);
			div.appendChild(logwindow.document.createTextNode(' - '+s));
			logwindow.document.body.appendChild(div);
		}catch(e){}
	}
	
	function $diag()
	{
		document.cookie = 'fs2diag=1';
		$AddSubmitLogButton();
		$AddWindowLogButton();
	}
	var $diagon = document.cookie.indexOf('fs2diag=1')!=-1;
	if ($diagon) $diag();
	
	///////////////////////////////////////////////////////////////////////
	// Heartbeat functionality - Lets the server know we're still here.
	///////////////////////////////////////////////////////////////////////
	function genhbloc(force)
	{
		if (!hbloc||force||_fs2s) genfirsthbloc();
		if (!_fs2s) return;
		var loc = parLoc(hbloc,"fs2hb");
		if (!loc) return;
		loc+=6;
		var endloc = hbloc.indexOf("&",loc);
		if (endloc==-1) endloc = hbloc.length;
		hbloc = hbloc.substr(0,loc)+_fs2s+hbloc.substr(endloc);
	}
	function genfirsthbloc()
	{
		var f = document.forms;
		for (var i=f.length;i-->0;)
		{
			var pos;
			if (pos=parLoc(f[i].action,"fs2s"))
				hbloc=f[i].action.substr(0,pos)+"fs2hb="+f[i].action.substr(pos+5);
		}
	}
	_hbfailed = false;
	var _timeoutat;
	var _to;
	function hb()
	{
		genhbloc();
		if (hbloc&&hbloc.indexOf("?")>-1)
		{
			var _started = +new Date();
			getXML(hbloc+(keyAfterHB?"&keypressed=1":"")+"&random="+Math.random(),function(xml)
			{
				if(xml&&xml.lastChild&&xml.lastChild.nodeName=="redirect")
					location=xml.lastChild.lastChild.nodeValue;
				else if (xml&&xml.firstChild&&xml.firstChild.nodeName=="failure")
					hbfailed();
				else if (xml&&xml.firstChild&&xml.firstChild.nodeName=="success")
				{
					if (_to) {clearTimeout(_to);_to=null;}
					var delay = _started+60*1000-new Date();
					_to = setTimeout(hb, Math.max(0,delay));
					_timeoutat = _started + 120*1000;
				}
				else if (!_timeoutat&&!ignorehbfail)
					if (!_isIE6) alert("Javascript failure");
			});
			keyAfterHB=false;
			if (_timeoutat && +new Date()>_timeoutat)
				setTimeout(function(){if (_timeoutat && +new Date()>_timeoutat) hbfailed();}, 1000);
			else if (!_hbfailed)
				_to = setTimeout(hb,10000);
		}
	}
	
	function hbfailed()
	{
		if (!_hbfailed)
		{
			_hbfailed = true;
			if (window._hbredirect) 
				window.location=_hbredirect; 
			else
				alert("Your session has expired. Choose a toolbar item to start again.");
		}
	}
	
	///////////////////////////////////////////////////////////////////////
	// Easy part of Request-based dynamic HTML
	///////////////////////////////////////////////////////////////////////
	
	// Gets an XMLHTTPRequest object (can reuse old ones)
	_prevXHR=[];
	function geXHR()
	{
		if (_prevXHR.length>0) return _prevXHR.pop();
		try{ return new XMLHttpRequest(); }catch(e){}
		try{ return new ActiveXObject("Msxml2.XMLHTTP"); }catch(e){}
		try{ return new ActiveXObject("Microsoft.XMLHTTP"); }catch(e){}
	}
	// Accepts an XMLHTTPRequest object for reuse
	function puXHR(o){arraypush(_prevXHR,o);}
	
	
	// Do a GET, using XMLHTTPRequest, calling fn(resp, code) on
	// completion or failure, where resp is the response XML (or
	// null, on failure) and code is the status-code received
	// from the server.
	function getText(URL, fn, data, ctype){return getData(URL, fn, data, ctype, "responseText");}
	function getXML(URL, fn, data, ctype){return getData(URL, fn, data, ctype, "responseXML");}
	function getData(URL, fn, data, ctype, propname)
	{
		var XHR = geXHR();
		XHR.open(data?"POST":"GET", URL, true);
		XHR.setRequestHeader('X-FS2JS', '1.0');
		if (data||ctype)
			XHR.setRequestHeader('Content-Type', ctype||'application/x-www-form-urlencoded');
		var ch = closure2(function()
		{
			if (XHR.readyState!=4) return;
			ch.undo(); // This stops the event handler being run again. Simply reassigning onreadystatechange would make IE crash.
			fn&&fn(XHR.status==200?XHR[propname]:null,XHR.status);
			puXHR(XHR);
		});
		XHR.onreadystatechange=ch.maybefn;
		XHR.send(data);
	}
	_timedthings={};
	function runfunctionafter(fn,o,t)
	{
		setTimeout(function(){fn(o);}, t||1);
	}
	_loaded=0;
	_basefns={'0':setaction00, '1':setaction01, '1a':JS01aAction, '2':setaction02, '3':null, '4':JS04setaction, '5':JS05Action, '6':setaction06, '7':setaction07, '9':JS09setaction, 'a':setaction0a, 'b':setaction0b, 'd':setaction0d, 'e':setaction0e, 'f':setaction0f, 'g':setaction0g, 'h':setaction0h, 's':setaction0s, 'v':setaction0v};
	_loadcount=1;
	var iframeid;
	function nowloaded(a,b)
	{
		if (js[a]==1)
		{
			_loadcount--;
			js[a]=2;
			_basefns[a]=b;
		}
		if (_loadcount==0)
		{
			if (!iframeid&&parent!=window)
			{
				try
				{
					parent[/*{*/"JS07Refresh"/*}*/]();
					checkSize();
					setInterval(/*{*/"checkSize()"/*}*/,55);
				}catch(e){}
				if (iframeid) parent[/*{*/"JS07LoadComplete"/*}*/](iframeid);
			}
			var t = GetTimer("Run $$");
			// If we end up terminating early, due to exception, we don't want to stop it but we do want to continue running onloads.
			var thingsbreak = setTimeout(function(){eventStarted('unbreak2');eventFinished('unbreak2');},1);
			for (var i=0;i<pending.length;++i)
			{
				var s = pending[i];
				if (!undoers[s.s]) undoers[s.s]=[];
				arraypush(undoers[s.s], _basefns[s[0]](s));
			}
			pending=[];
			clearTimeout(thingsbreak);
			t.end();
		}
	}

	var js={$:1, '0':2, '1':2, '1a':2, '2':2, '3':2, '4':2, '5':2, '6':2, '7':2, '9':2, 'a':2, 'b':2, 'd':2, 'e':2, 'f':2, 'g':2, 'h':2, 's':2, 'v':2};
	OnLoad(function(){
		if (_isIE6)
			document.body.onclick = IE6LabelHandler;
	});
	
	var _isIE6 = !!(window.VBArray&&!window.XMLHttpRequest)
	if (_isIE6) document.documentElement.className+=" isie6";
	document.documentElement.className+=" hasjs";
	if (document.createElement) document.documentElement.className+=" hasdom";
		
	function IE6LabelHandler()
	{
		if (window.lastwashandled) return false;
		var ev = window.event;
		var el = ev.targetElement||ev.srcElement;
		while (el&&!(el.tagName&&el.tagName.toLowerCase()=="label")) el=el.parentElement;
		el=el&&getel(el.htmlFor);
		if (!el||el.tagName.toLowerCase()!="select") return true;
		el.focus();
		return false;
	}
	function ddkeypress(ev)
	{
		$log("keypress!");
		keyAfterHB=true;
		var evv=ev||event;
		var el=evv.srcElement||evv.target;
		
		if ((evv.charCode||evv.keyCode||evv.which)==13&&(el.type=="text"||el.type=="password"))
		{
			if (el.ignoreEnter) return false;
			var f=document.all;
			var i=el.sourceIndex;
			var dest = controldest[el.id]||controldest[el.name];
			if (dest)
			{
				var del = getel(dest);
				if (del.type=='text'||del.type=='password'||del.tagName.toLowerCase()=='textarea') {del.focus();return false;}
			if (del.type=='submit'||del.type=='image') {return !$c(del);}
			}
			if (!f||!i)
			{
				f=el.form.elements;
				for (i=0;i<f.length;++i)
					if (f[i]==el) break;
			}
			while (++i<f.length)
			{
				if (controldest[f[i].id||f[i].name]||controlsrc[f[i].id||f[i].name]||jQuery(f[i]).is(":hidden")||jQuery(f[i]).css("display")=="none") continue;
				if (f[i].type=='text'||f[i].type=='password'||f[i].tagName.toLowerCase()=='textarea') {f[i].focus();return false;}
				if ((f[i].type=='submit'||f[i].type=='image')&&!ge(f[i],'ignore')) {return !$c(f[i]);}
			}
		}
		else if (window[/*{*/"ddkeypress2"/*}*/]) return ddkeypress2(ev);
	}
	// This function adds pending events
	var pending=[];
	var nowloadedwaiting=false;
	function nowloadedload()
	{
		nowloadedwaiting=false;
		nowloaded();
	}
	function $$(s)
	{
		// Allow people to call with as many instructions as they like
		for (var i=1;i<arguments.length;++i) $$(arguments[i]);
		var p = s.split("~");p.s=s;
		pending[pending.length]=p;
		if (!js[p[0]]) loadjs(p[0]);
		if (_loadcount==0&&!nowloadedwaiting)
		{
			nowloadedwaiting=true;
			OnLoad(nowloadedload);
		}
	}
	//$$=function(){}
	var undoers={};
	function $$undo(s)
	{
		// Allow people to call with as many instructions as they like
		for (var i=1;i<arguments.length;++i) $$undo(arguments[i]);
		
		var o = undoers[s];
		if (o==null||!o.length) return;
		o = o.pop();
		if (o) o();
	}
	function loadjs(s)
	{
		if (js[s]) return;
		var o=getel("bwsjs");
		if (!o) return;
		_loadcount+=1;
		js[s]=1;
		var s2=o.src.replace("0.js","0"+s+".js");
		if (document.createElement)
		{
			if (_LoadDone)
			{
				var el=document.createElement('script');
				if (!_isIE)
				{
					el.defer=true;
					el.src=s2;
				}
				el.bws=1;
				document.body.appendChild(el);
				// If we insert a deferred script, here, IE will lose the history
				// when we navigate away from this page (if we're using dynamic
				// updates). Instead, we use a hack.
				if (_isIE) el.src=s2;
			}
			else
				document.write("<script bws=bws src='"+s2+"' type='text/javascript'></script>");
		}
		else o.src=s2;
	}
	var lastsize;
	function checkSize()
	{
		var thissize={};
		var o=getScroll()._o;
		thissize._w=o.scrollWidth;
		thissize._h=Math.min(o.scrollHeight, document.body.offsetHeight+15);
		if (!lastsize||lastsize._w!=thissize._w||lastsize._h!=thissize._h)
			parent["JS07SetSize"](iframeid,thissize._w,thissize._h);
		lastsize=thissize;
	}
	
	function ElsByName(name)
	{
		var a=document.getElementsByName(name);
		var ret = {};
		for (var i=0;i<a.length;++i)
		{
			if (!a[i]._nw)
				a[i]._nw = "nw"+Math.random();
			ret[a[i]._nw] = a[i];
		}
		return ret;
	}
	
	function ElByID(id)
	{
		var o = document.getElementById(id);
		if (o&&!o._nw) o._nw="nw"+Math.random();
		return o;
	}
	
	function getels(n, norecurse)
	{
		var all = {};
		if (norecurse)
			all[n] = 1;
		else
			all = AddDests(n);
		var ret = {};
		for (var i in all)
		{
			var o = ElByID(i);
			if (o)
				ret[o._nw] = o;
			var a = ElsByName(i);
			for (var j in a)
				ret[a[j]._nw] = a[j];
		}
		return ret;
	}
	
	var namemap={};
	function AddNameMap(from, to)
	{
		if (!namemap[from]) namemap[from]={};
		if (namemap[from][to])
			namemap[from][to]++;
		else
			namemap[from][to]=1;
	}

	function RemNameMap(from, to)
	{
		if (!--namemap[from][to])
			delete namemap[from][to];
	}
	
	function AddDests(name, currentset)
	{
		currentset = currentset||{};
		currentset[name]=1;
		if (namemap[name])
			for (var i in namemap[name])
			{
				if (currentset[i]) continue;
				AddDests(i, currentset);
			}
		return currentset;
	}

	var namewatchers = {};
	function AddNameWatch(name, addfn)
	{
		var thisname = "bws"+Math.random();
		if (!namewatchers[name]) namewatchers[name]={};
		
		var rem={};
		
		namewatchers[name][thisname] = function()
		{
			var cur = getels(name);
			
			for (var i in rem)
				if (!cur[rem[i]._nw])
				{
					rem[i].undo();
					delete rem[i];
				}
			for (var i in cur)
				if (!rem[cur[i]._nw])
					rem[cur[i]._nw] = {undo: addfn(cur[i]), _nw:cur[i]._nw};
		};
		
		namewatchers[name][thisname]();
		
		return function()
		{
			for (var i in rem)
				rem[i].undo();
			rem={};
			delete namewatchers[name][thisname];
		};
	}
	
	var rescanNames = false;
	function ElementsUpdated()
	{
		rescanNames = true;
		if (eventCount==0)
		{
			eventStarted('trickupdate');
			eventFinished('trickupdate');
		}
	}
	
	function FullNameRescan()
	{
		rescanNames = false;
		$log("Watchers Begin");
		for (var i in namewatchers)
		{
			var oi = namewatchers[i];
			for (var j in oi) oi[j]();
		}
		$log("Watchers End");
	}
	
	function undo2(fn1, fn2){return function(){fn1();fn2();};}
	
	function $bwsajat()
	{
		return;
		genhbloc();
		if (window._secrettoken||!_isIE||(document.cookie+"").indexOf("NoAJAT")>=0||!hbloc) return;
		_fsurl = hbloc+"/FJ/";
		_fsurlhash = "/Panel/";
		_forceloaded=1;
		//document.write('<div id="fs2jsdiv" style="display:none">'+"<script src='"+getel('bwsjs').src.replace("/0.js","/F.js")+"' type='text/javascript'></script></div>");
	}
	
	function AddToObject(obj, thing)
	{
		var id = "ato"+Math.random();
		obj[id]=thing;
		return function(){delete obj[id]};
	}
}












































// This is the base JS that all of the dynamic JS files use
// It adds custom event-handlers and provides other useful
// cross-platform functionality.

//TODO: Replace all underscore variables
//TODO: thingsavoidme->_thingsavoidme

function setaction00(s)
{
	if (!start00done) start00();
	
	if (s[1]!="")
	{
		// 0~parentname~childname~~childname~~childname~~...
		for (var i=2;s[i];i+=2)
			AddNameMap(s[1],s[i]);
		return function(){
			for (var i=2;s[i];i+=2)
				RemNameMap(s[1],s[i]);
		};
	}
	
	if (s[2]!="")
	{
		controlsrc[s[3]]=s[2];
		controldest[s[2]]=s[3];
		return;
	}
	
	return fns00[s[3]](s);
}

var fns00 =
{
	// A field that contains the current/expected scroll position (and names/ids of fields to scroll into view)
	"0":function(s)
	{
		maybeforcescroll();
		return AddToObject(forcescrolls,s[4]);
	},
	// The id of something to scroll into view
	"Vis":function(s)
	{
		maybeforcescroll();
		return AddToObject(scrollobjs,s[4]);
	},
	// selshate
	"SH":function(s)
	{
		if (!_isIE) return function(){};
		//TODO: Make maybehidesels get called more often. Whenever any change happens that might make the size/position of a hidesel change. It should be fairly cheap, at least, though obviously never as cheap as not doing it.
		return AddNameWatch(s[4],function(el)
		{
			selshate(el);
			return function(){selsunhate(el);}
		});
	},
	// Something that tries to scroll with the page when forcescrolls happen, but not otherwise.
	"SW":function(s)
	{
		return AddNameWatch(s[4],function(el)
		{
			var setx = parseFloat(s[5])||0;
			var sety = parseFloat(s[6])||0;
			var $el=jQuery(el);
			var origtop = el.style.top;
			var origleft = el.style.left;
			var offset=setx||sety?{left:setx,top:sety}:$el.offset();
			var posat = function(x,y)
			{
				x = offset.left+x;
				y = offset.top+y;
				objpos(el, x, y);
			};
			var r = getScroll();
			posat(r._x,r._y);
			var undo = ForceScrollWatch(function(ev)
			{
				posat(ev._new._x,ev._new._y);
			});
			return function(){undo();el.style.top=origtop;el.style.left=origleft;};
		});
	},
	// Helptext - prevent clicks and make focus display it.
	"h":function(s)
	{
		var el = getel(s[4]);
		if (el)
		{
			var img = getel(s[5]);
			img.tabIndex = 0;
			var o = {el:el, img:img};
			addobjref(o);
			fsAddEvent(img, "_onfocusin", function(){el.style.display="";});
			fsAddEvent(img, "_onfocusout", function(){el.style.display="none";});
			fsAddEvent(img, "_onmouseclick", function(){return true;});
			el.parentNode.insertBefore(img, el);
			el.style.display="none";
			el.className = el.className.replace("hidefordom","helptext");
		}
	},
	// One. Focus a field and click a button if it gets unfocused?! Seems insane.
	"1":function(s)
	{
		if (s[4]=getel(s[4])) s[4].focus();
		if (s[5])
		{
			var sub = getel(s[5]);
			sub.style.position="absolute";
			sub.style.top="-1000em";
			s[4].onblur = closure(function(){sub.click()});
		}
	},
	// L. Fake-links. That is, buttons (styled like a link) that become a link for those who have JS.
	"l":function(s)
	{
		var link = document.createElement("a");
		link.sub = getel(s[4]);
		link.href="javascript:void $c(getel('"+s[4].replace(/(['\\])/g,"\\$1")+"'))";
		link.onclick=function(){ _bwsnavig = getel(s[4]); }
		link.className = link.sub.className.split("fakelink").join("").split("genericsubmit").join("");
		link.appendChild(document.createTextNode(link.sub.value));
		link.sub.parentNode.insertBefore(link, link.sub);
		link.sub.style.position="absolute";
		link.sub.style.left="-10000em";
		link.sub.style.top="-20em";
		link.sub.tabIndex=-1;
	},
	// Character counts for textareas. TODO: Fix the detect-welsh-by-Dileu hack
	"5":function(s)
	{
		var ta = getel(s[4]);
		var len = parseInt(s[5]);
		
		// IE does some awful relayout whenever we change the text. The only way to avoid it seems to be to absolutely position the text into place from elsewhere and change that.
		if (_isIE)
		{
			// A gap for the count to be positioned into
			var gap = document.createElement("div");
			gap.innerHTML='&#160;';
			if (ta.nextSibling) ta.parentNode.insertBefore(gap, ta.nextSibling);
			else ta.parentNode.appendChild(gap);
			
			var text = document.createElement("div");
			text.style.position="absolute";
			document.body.appendChild(text);
			var sizeandpos = function(){
				moveobj(text,objleft(gap),objtop(gap));
				gap.style.height = text.offsetHeight+"px";
			};
			var timer = setInterval(sizeandpos,300);
		}
		else
		{
			var text = document.createElement("div");
			if (ta.nextSibling) ta.parentNode.insertBefore(text, ta.nextSibling);
			else ta.parentNode.appendChild(text);
		}
		
		var text = document.createElement("div");
		if (ta.nextSibling) ta.parentNode.insertBefore(text, ta.nextSibling);
		else ta.parentNode.appendChild(text);
		var isWelsh = false;
		for (var i=ta.form.elements.length;i-->0;)
		{
			var te=ta.form.elements[i];
			if (te.type=="submit"&&te.value.substr(0,5)=="Dileu")
				isWelsh=true;
		}
		ta.onchange = ta.onpropertychange = ta.onkeyup = ta.onkeypress = closure(function()
		{
			var tatext = ta.value;
			tatext=tatext.replace(/\r\n|\r|\n/g, "\r\n");
			var left = len-tatext.length;
			if (len>=tatext.length)
			{
				text.className = "textarealength";
				text.innerHTML = isWelsh?
					(left?left+' cymeriad':'Dim cymeriadau')+" (o "+len+") ar &#244;l.":
					((left||'No')+" character"+(left==1?"":"s")+" (of "+len+") left.");
			}
			else
			{
				text.className = "textarealength warning";
				text.innerHTML = isWelsh?
					len+" o gymeriadau a ganiateir. Tynnwch "+-left+" cymeriad"+(left!=-1?"au":"")+" os gwelwch yn dda.":
					(len+" characters allowed. Please remove "+-left+" character"+(left==-1?"":"s")+".");
			}
			if (_isIE) sizeandpos();
		});
		ta.onchange();
		return function(){text.parentNode.removeChild(text);if (gap) gap.parentNode.removeChild(gap);if (timer) clearInterval(timer);};
	},
	// Warns the user when they're navigating away. All parameters optional. First is message. Second is the div within which changes to fields have to occur in order for us to be interested. Third is the div within which pressing a button is entirely uninteresting to us.
	"W":function(s)
	{
		var msg = s[4]||"If you navigate away, your changes will be lost.";
		var div = s[5];
		var par = s[6];
		var rnd = Math.random();
		_bwswatchnavig[_bwswatchnavig.length] = {msg:msg,rnd:rnd,div:div,par:par};
		return function()
		{
			var newnavig = [];
			for (var i=0;i<_bwswatchnavig.length;++i)
				if (_bwswatchnavig[i].rnd!=rnd) newnavig[newnavig.length]=_bwswatchnavig[i];
			_bwswatchnavig = newnavig;
		};
	},
	// Watch a control. If it changes, submit the form. For textboxes and textareas, 1.5s of no typing is required. (0.5s if you unfocus the field)
	"T":function(s)
	{
		// If we're not AJAXy, we're not interested.
		if (!window.SubmitPartial) return function(){};
		return AddNameWatch(s[4], function(el)
		{
			var oldval = ControlValue(el);
			var to;
			var undof;
			var go = function()
			{
				if (to) {clearTimeout(to); to=null;}
				if (oldval==ControlValue(el)) return;
				oldval=ControlValue(el);
				$log("T caused refresh" + ControlValue(el));
				if (undof) {undof(); undof=null;}
				if (el.form) $c(el.form);
			};
			var undoch = OnChange(el, function()
			{
				if (el.tagName=="TEXTAREA"||el.tagName=="textarea"||el.type=="text"||el.type=="secret")
				{
					if (to) clearTimeout(to);
					if (!undof) undof = fsAddEvent(el, "_onfocusout", function(){if (to) clearTimeout(to);to=setTimeout(go,500);});
					to = setTimeout(go, 1500);
				}
				else
					go();
			});
			return function(){
				if (to) clearTimeout(to);
				if (undof) undof();
				undoch();
			};
		});
},
	"Tm":function(s)
	{
		return function(){}; // this function is  broken - so we don't use it
	    if (!window.SubmitPartial) return function() { };
	    return AddNameWatch(s[4], function(el) {
	        var go = function() {
	            $c(el.form);
	        };
	        fsAddEvent(el, "_onfocusout", go);
	        return function() {
	            //fsAddEvent(el, "_onfocusout", go);
	        };
	    });
	},
	// Watching for time fields - only causes update when the apparent value has changed.
	"Tt":function(s){
		// If we're not AJAXy, we're not interested.
		if (!window.SubmitPartial) return function(){};
		return AddNameWatch(s[4], function(elh)
		{
			return AddNameWatch(s[5], function(elm)
			{
				var getVal = function()
				{
					var h = parseInt(ControlValue(elh));
					var m = parseInt(ControlValue(elm));
					if (isNaN(h)||isNaN(m)) return null;
					return h+","+m; // Doesn't have to be human-readable, but has to be identifying.
				};
				var lastVal = getVal();
				var change = function()
				{
					var newVal = getVal();
					if (newVal==lastVal) return;
					$log("Auto-submit because time field changed from "+lastVal+" to "+newVal);
					lastVal=newVal;
					$c(elh.form);
				};
				var undo1 = OnChange(elh, change);
				var undo2 = OnChange(elm, change);
				return function(){undo1();undo2();};
			});
		});
	},
	// Watching for date fields - only causes update when the apparent value has changed.
	"Td":function(s){
		// If we're not AJAXy, we're not interested.
		if (!window.SubmitPartial) return function(){};
		return AddNameWatch(s[4], function(eld)
		{
			return AddNameWatch(s[5], function(elm)
			{
				return AddNameWatch(s[6], function(ely)
				{
					var timer;
					var getVal = function()
					{
						var d = parseInt(ControlValue(eld));
						var m = ControlValue(elm);
						var y = parseInt(ControlValue(ely));
						
						if (isNaN(d)||isNaN(y)||m=='MM') return null;
						return d + "-" + m + "-" + y;
					};
					var lastVal = getVal();
					var change = function()
					{
						if (timer!=null) return;
						var newVal = getVal();
						if (newVal==lastVal) return;
						$log("Auto-submit because date field changed from "+lastVal+" to "+newVal);
						lastVal=newVal;
						$c(eld.form);
					};
					var undo1 = OnChange(eld, change);
					var undo2 = OnChange(elm, change);
					var ychange = function(){
						timer = null;
						change();
					};
					var undo3 = OnChange(ely, function(){
						if (timer!=null) {clearTimeout(timer);timer=null;}
						if (ely._hasFocus) timer = setTimeout(ychange, 1500);
						else change();
					});
					var undo4 = fsAddEvent(ely, "_onfocusout", function(){
						if (timer) {clearTimeout(timer);timer=null;}
						change();
					});
					return function(){undo1();undo2();undo3();undo4();};
				});
			});
		});
	},
	// Double-click on the first thing to click the second thing (a submit, really).
	"D":function(s)
	{
		var sub = getel(s[5]);
		getel(s[4]).ondblclick=closure(function(){sub.click();return false;});
		sub.style.position = "absolute";
		sub.style.top = "-1000em";
	},
	// Hide the first thing if the second thing has a value or has focus.
	"in":function(s)
	{
		var lbl = getel(s[4]);
		var ctrl = getel(s[5]);
		var chk = closure(function()
		{
			lbl.style.display=(ControlValue(ctrl)||ctrl._hasFocus)?"none":"";
		});

		fsAddEvent(ctrl, "_onfocusin", chk);
		fsAddEvent(ctrl, "_onfocusout", chk);
		OnChange(ctrl, chk);
		chk();
	},
	"tinyMCE":function(s)
	{
		if (!window.tinyMCEInited)
		{
			// TODO: TinyMCE still currently disables AJAXy updates, because we need to call the undoer before the page elements are removed. The change required for that may affect other things, so it's postponed for now. (Maybe if the name-watching stuff gets that feature, we can use name-watches, instead).
			tinyMCEInited=true;
			tinyMCE.init({
				mode : "none",
				plugins : "advimage",
				theme : "advanced", 
				theme_advanced_buttons1 :"bold,italic,underline,numlist,bullist,outdent,indent,separator,link,unlink,image,code",
				theme_advanced_buttons2 : "",
				theme_advanced_buttons3 : "",
				theme_advanced_toolbar_location : "top",
				theme_advanced_toolbar_align : "left",
				theme_advanced_path_location : "bottom",
				content_css : "<%{jsfolder}%>/tiny_mce/css/example_full.css",
				plugin_insertdate_dateFormat : "%Y-%m-%d",
				plugin_insertdate_timeFormat : "%H:%M:%S",
				extended_valid_elements : "hr[class|width|size|noshade],font[face|size|color|style],span[class|align|style]",
				external_link_list_url : "example_link_list.js",
				external_image_list_url : "example_image_list.js",
				flash_external_list_url : "example_flash_list.js",
				media_external_list_url : "example_media_list.js",
				template_external_list_url : "example_template_list.js",
				theme_advanced_resize_horizontal : false,
				theme_advanced_resizing : true,
				nonbreaking_force_tab : true,
				apply_source_formatting : true,
				relative_urls : false,
				convert_urls : false,
				remove_script_host : true,
				strict_loading_mode : true
			});
		}
		
		tinyMCE.execCommand('mceAddControl',true,s[4]);
		// This isn't yet used, since tinyMCE still inhibits AJAXiness, but hey.
		return function(){tinyMCE.execCommand('mceRemoveControl',true,s[4])};
	},
	// Xoomer. Click on the first thing to set the second thing's value to the third parameter. Moves currentxoom class accordingly and alters the fourth and fifth things classes to xoomed+third parameter, to alter page accordingly.
	"X":function(s)
	{
		return AddNameWatch(s[4], function(el)
		{
			return fsAddEvent(el, "_onmouseclick", function(ev)
			{
				getel(s[5]).value=s[6];
				for (var o=el.parentNode.firstChild;o;o=o.nextSibling) o.className=o.className.replace("currentxoom","");
				el.className+=" currentxoom";
				getel(s[8]).className=getel(s[7]).className="xoomed"+s[6];
				return true;
			});
		});
	}
};

function NavigateAway(ev)
{
	// If someone's trying to navigate away, we want to ignore heartbeat failures
	ignorehbfail = true;
	if (_hbfailed) {$log("Navigating away (hb had failed)");return;}
	for (var i=_bwswatchnavig.length;i-->0;)
	{
		var o = _bwswatchnavig[i];
		var isDiff=true;
		var div;
		if (div=getel(o.div))
		{
			var valsbefore = FormValsFrom(div,null,true,true);
			var valsafter = FormValsFrom(div,null,false,true);
			var diff=DiffFromFormVals(valsafter, valsbefore);
			var scrollers = {};
			for (var j in forcescrolls) {if(!forcescrolls.hasOwnProperty(j)) continue;scrollers[forcescrolls[j]]=1;}
			for (var j in diff) {if (!diff.hasOwnProperty(j)||scrollers[j]) delete diff[j];}
			$log("NavCheck: "+ValsAsPostData(diff));
			isDiff=false;
			for (var j in diff) {isDiff=true;break;}
		}
		var p = o.par?getel(o.par):document.body;
		var found = false;
		for (var el=_bwsnavig;el;el=el.parentNode)
			if (el==p)
				found=true;
		if (isDiff&&!found) {$log("Navigating away, maybe");return (ev||event).returnValue=o.msg;}
	}
	$log("Navigating away");
}

function CheckNavig(ev, evo, o)
{
	var el = ev._el;
	if (el.tagName.toLowerCase()=="a" && el.sub)
	{
		el = el.sub;
	}
	_bwsnavig=(el.tagName.toLowerCase()=="input"||el.tagName.toLowerCase()=="submit")?el:null;
}

var scrollobjs = {};
var forcescrolls = {};
var scrollnewx;
var scrollnewy;
var scrolldo = true;
function wantedscrollpos()
{
	for (var i in forcescrolls)
	{
		if (!forcescrolls.hasOwnProperty(i)) continue;
		var o = getel(forcescrolls[i]);
		if (!o) continue;
		var s = o.value.split("~");
		if (s.length>2)
		{
			for (var j=0;j<s.length-2;++j)
				AddToObject(scrollobjs,s[j]);
			o.value=s[s.length-2]+"~"+s[s.length-1];
		}
	}
	var x = null;
	var y = null;
	for (var i in forcescrolls)
	{
		if (!forcescrolls.hasOwnProperty(i)) continue;
		var o = getel(forcescrolls[i]);
		if (!o) continue;
		var s = o.value.split("~");
		x = +s[0]||x;
		y = +s[1]||y;
	}
	
	var r=getScroll();
	x=x||r._x;y=y||r._y;
	// We've got a candidate x,y - now let's go through all of the
	// referenced objects and find their current position and try
	// to make sure they're on-screen.
	for (var i in scrollobjs)
	{
		if (!scrollobjs.hasOwnProperty(i)) continue;
		var o = getel(scrollobjs[i]);
		if (!o) continue;
		var ox = objleft(o);
		var oy = objtop(o);
		var ox2 = ox + (o.offsetWidth||0);
		var oy2 = oy + (o.offsetHeight||100);
		$log("Scrolling "+scrollobjs[i]+" into view, at ("+ox+","+oy+"), from ("+x+","+y+") ");
		
		if (x+r._cw<ox2) x=ox2-r._cw;
		if (x>ox) x=ox;
		if (y+r._ch<oy2) y=oy2-r._ch;
		if (y>oy) y=oy;
	}
	
	$log("Final estimated scrollpos: "+x+","+y);
	return {_x:x,_y:y,_r:r};
}

var forcing=null;
function maybeforcescroll()
{
	if (forcing) return;
	$log("Forcing scroll in 100ms");
	if (!_isIE6) forcing = setTimeout("forcescroll()",100);
}

function forcescroll()
{
	if (eventCount>0)
	{
		$log("Forced scrolling delayed for 100ms");
		if (!_isIE6) setTimeout("forcescroll()",100);
		return;
	}
	forcing=null;
	if (!scrolldo) {$log("Forced scrolling cancelled");return;}
	
	var pos = wantedscrollpos();
	if (pos._x!=pos._r._x||pos._y!=pos._r._y)
	{
		scrollnewx=pos._x;scrollnewy=pos._y;
		scrollTo(pos._x,pos._y);
	}
}

_cancelotherdrag=null;

function replaceEv(el, name, fn)
{
	if (el[name]==fn) return;
	if (el[name])
	{
		if (!el["bwsold"+name]) el["bwsold"+name]=[el[name]];
		else arraypush(el["bwsold"+name], el[name]);
	}
	el[name]=fn;
}

function ddchange(el, force)
{
	if (!el._ddchange) return;
	var newval =  ControlValue(el);
	if (newval==el._lastval&&!force) return;
	var oldval = el._lastval;
	el._lastval=newval;
	
	for (var i in el._ddchange)
		el._ddchange[i](el, oldval, el._lastval, force);
}

function ddglobalchange(ev)
{
	ev = ev || event;
	var targ = ev.srcElement||ev.target;
	if (!targ._ddchange) return;
	
	if (targ.type!="radio")
		return ddchange(targ);
	
	if (targ.form)
	{
		var els = targ.form.elements[targ.name];
		for (var i=0;i<els.length;++i)
			ddchange(els[i]);
	}
	else
	{
		var t = document.getElementsByName(targ.name);
		for (var i=0;i<t.length;++i)
			if (t[i].form==null)
				ddchange(t[i]);
	}
}

function Changed(el)
{
	ddchange(el, true);
}

function iechange()
{
	var ths=this;
	eventStarted('iechange');
	setTimeout(function(){ddchange(ths);eventFinished('iechange');},1);
}

function InitOnChange(el)
{
	if (el._ddchange) return;
	el._ddchange={};
	el._lastval = ControlValue(el);
	// IE doesn't bubble onchange events but does support onpropertychange
	replaceEv(el,"onpropertychange",iechange);
}

function OnChange(el, fn)
{
	InitOnChange(el);
	
	var name = "bws"+Math.random();
	var clos = closure2(fn);
	el._ddchange[name] = clos.fn;
	return function(){clos.undo();delete el._ddchange[name];};
}

//////////////////////////////////////////////////////////////////////////////////
//Initialisation and behaviour stuff
//////////////////////////////////////////////////////////////////////////////////
var start00done = false;
if (window.addEventListener)
	window.addEventListener("beforeunload", NavigateAway, true);
else
	window.onbeforeunload = NavigateAway;
function start00()
{
	if (start00done) return;
	start00done = true;
	var dbs=getScroll();

	dbs._o.onmousedown=ddmousedown;
	dbs._o.onmouseup=ddmouseup;
	dbs._o.onmousemove=ddmousemove;
	dbs._o.onmouseover=ddmouseover;
	dbs._o.onscroll=ddscroll;
	if (dbs._o.addEventListener)
	{
		dbs._o.addEventListener("focus", ddfocus, true);
		dbs._o.addEventListener("blur", ddblur, true);
	}
	else
	{
		dbs._o.onfocusin = ddfocus;
		dbs._o.onfocusout = ddblur;
	}
//	dbs._o.onresize=ddscroll;
	document.onselectionchange=ddselchange;
	dbs._o.oncontextmenu=ddcancelifhandled;
	dbs._o.onclick=ddcancelifhandled;
	dbs._o.onchange=ddglobalchange;
	fsAddEvent(dbs._o, "_onmouseclick", CheckNavig, 0);
	ddscrolllastx=dbs._x;
	ddscrolllasty=dbs._y;
}

var menus={};
var menusrc={};
var richeds=[];
var sels=[];
var nextactive=null;
var lastactive=null;
var mustval=0;

// attachmenu -> onmouseover, onmouseout

//////////////////////////////////////////////////////////////////////////////////
//Base DHTML stuff
//
//Custom event-handling model (to get around some issues with the various
//browsers), style access and cross-platform object access and movement
//////////////////////////////////////////////////////////////////////////////////

var lastmousestate={};
var lastev={};
var dragdrop=null;
var dragmove=null;

// Remove an object from an array
function arrayremove(a,o)
{
	var ret=[];
	for (var i=0;i<a.length;++i)
		if (a[i]!=o)
			arraypush(ret,a[i]);
	return ret;
}

// Add a class to an object
function addclass(o,c){remclass(o,c,c);}
// Remove a class from an object
function remclass(o,c,c2)
{
	var a=o.className.split(" ");
	a=arrayremove(a,c);
	if (c2) arraypush(a,c2);
	o.className=a.join(" ");
}

var eventobjs=[{}];
// This is used to provide an indirection to get around the IE GC issues.
function addobjref(o)
{
	o._id=eventobjs.length;
	arraypush(eventobjs,o);
}
// Add an event handler to an object
function addEvent(oo,t,fn,o)
{
	if (!oo[t]) oo[t]={};
	var id = "ev"+Math.random();
	oo[t][id]={fn:fn,o:o};
	fn=o=null;
	return function(){delete oo[t][id];}
}
var fsAddEvent=addEvent;
var mmc=0;
function addEventMouseMove(oo,fn,o)
{
	++mmc;
	return undo2(function(){--mmc},fsAddEvent(oo,/*{*/"_onmousemove"/*}*/,fn,o));
}
// Fix the position of an element, after a style change (when display:none, position doesn't get updated properly)
function fixpos(o){
	if (o._x!=null) {(o.style||o).left=o._x-objleft(o.offsetParent)+"px";(o.style||o).top=o._y-objtop(o.offsetParent)+"px";}}
// Move an object
function moveobj(o,x,y) { if (o._x==x&&o._y==y) return; o._x=x; o._y=y; fixpos(o); }
// Move an object, but keep it in the visible area of the window
function objpos(o,x,y)
{
	var r=getScroll();
	if (x+o.offsetWidth>r._x2) x=r._x2-o.offsetWidth;
	if (y+o.offsetHeight>r._y2) y=r._y2-o.offsetHeight;
	if (x<r._x) x=r._x;
	if (y<r._y) y=r._y;
	moveobj(o,x,y);
	if (o._selid) hidesels();
}
// Get or set a style
function setstyle(o,st,s){if (o._propertyCache&&o._propertyCache.style[st]===void 0) o._propertyCache.style[st]=(o.style||o)[st]||"";(o.style||o)[st]=s;fixpos(o);if (st=='visibility') if (s=='hidden') hidden(o); else visible(o);}
// Calculate the position of the left of an object
function objleft(o){if (!o) return 0;var x=0;if (o.offsetLeft==null) return o.x||0;while (o){x+=o._buggyLeft||o.offsetLeft;o=o.offsetParent;}return x;}
// Calculate the position of the top of an object
function objtop(o){if (!o) return 0;var y=0;if (o.offsetTop==null) return o.y||0;while (o){y+=o._buggyTop||o.offsetTop;o=o.offsetParent;}return y;}

//TODO:
function hidden(){}function visible(){}

// Special IE select-box handling (to make them wide enough to use)
function IESelIn(el)
{
	return;
	if (!el||!el.tagName||el.tagName.toLowerCase()!='select') return;
	var w = el.offsetWidth;
	// Using a class for all this does not work in IE6.
	el.style.width='auto';
	// Absolute positioning to avoid extra layout issues.
    el.style.position='absolute';
	// If we didn't usefully change the width, undo it.
	if (w>=el.offsetWidth)
		IESelOut(el,true);
}
function IESelOut(el,force)
{
	return;
	if (!el||!el.tagName||el.tagName.toLowerCase()!='select') return;
	if (el._hasFocus&&!force) return;
    el.style.width='';
    el.style.position='';
}

function doEvent(ev,els,evname,others)
{
	eventStarted('ev'+evname);
	for (var i=0;i<els.length;++i)
	{
		var ia=els[i];
		var o = others&&(others[ia.id]||others[ia.name]);
		if (o)
			for (var j in o)
				if (o[j]._fn(ev, ia, o[j]._o))
				{
					eventFinished('ev'+evname);
					return false;
				}
		ia=ia[evname];
		if (ia)
			for (var j in ia)
				if (ia[j].fn(ev,els[i],eventobjs[ia[j].o]))
				{
					eventFinished('ev'+evname);
					return false;
				}
	}
	eventFinished('ev'+evname);
	return true;
}


function StartCache(o)
{
	o._propertycache={_propertycache:o._propertycache,_style:{}};
	for (var i in {width:0,height:0,visibility:0,position:0,top:0,left:0}) o._propertycache._style[i]=o.style[i]||"";
	o._propertycache._x=o._x||null;
	o._propertycache._y=o._y||null;
	return function(){EndCache(o);};
}

function EndCache(o)
{
	var pc=o._propertycache;
	o._propertycache=null;
	for (var i in pc) if (i!="_style"&&i!="_propertycache") o[i]=pc[i];
	for (var i in pc._style) setstyle(o,i,pc._style[i]);
	o._propertycache=pc._propertycache;
}

// Sets a fake parent
var fakePars=[];
function setFakeParent(o,par)
{
	var r=fakePars.length;
	fakePars[r]=par;
	if (o._propertyCache&&o._propertyCache._fakeParent===void 0) o._propertyCache._fakeParent=o._fakeParent||null;
	o._fakeParent=r;
	return r;
}

// Fill the object chain in an event (only done when the event is being passed to a handler)
function getparels(ev,disallowfake)
{
	if (ev._els) return;
	ev._els=[];
	var o=ev._el;
	while (o)
	{
		arraypush(ev._els,o);
		if (o._fakeParent&&disallowfake) return;
		o=o._fakeParent!=null?fakePars[o._fakeParent]:(o.parentNode||o.parentElement);
	}
}
// Try to create a consistent event object
function myevent(ev,ismup)
{
	ev=ev||event;
	var modifiers=ev.modifiers;
	var r={};
	r._el=ev.srcElement||ev.target;
	r._kctrl=modifiers&1==1||ev.ctrlKey;
	r._kalt=modifiers&2==2||ev.altKey;
	r._kshift=modifiers&4==4||ev.shiftKey;
	r._kmeta=modifiers&8==8||ev.metaKey;
	var mx=ev.pageX;
	var my=ev.pageY;
	if (mx==null)
	{
		mx=ev.clientX;my=ev.clientY;
		if (window.navigator.userAgent.indexOf('Opera')==-1&&(!window.ScriptEngine||ScriptEngine().indexOf('InScript')==-1)&&window.navigator.vendor!='KDE')
		{
			var o=getScroll();
			mx+=o._x;my+=o._y;
		}
	}
	r._mx=mx;
	r._my=my;
	var which=ev.which;
	var button=ev.button;
	var keyCode=ev.charCode||ev.keyCode;
	if (which==null)
		if (ismup&&!button) r._button=lastmousestate._button; // Workaround for MacIE bug (mouseup never has a button)
		else r._button=button==1?1:button==2?3:2;
	else if (which==65536) // Workaround for Safari bug (mouseup on submits gives 65536)
		r,_button=lastmousestate._button;
	else if (which-button==1)
		r._button=which;
	else
		r._button=which>2?2:which<2?1:3;
	r._key=keyCode||which;
	return r;
}
function ddmousedown(ev)
{
	// Ignore scrollbar clicks in Firefox
	if (ev&&ev.originalTarget&&window.XULElement&&ev.originalTarget instanceof XULElement) return;
	
	ev=myevent(ev);
	// If the browser supports componentFromPoint (probably IE), this might be a scrollbar event
	// We actually assume there's not going to be a mouseup, which is true for IE, at least.
	if (ev._el.componentFromPoint&&window.event)
	{
		var loc = ev._el.componentFromPoint(event.clientX,event.clientY);
		if (loc!=""&&loc!="outside") return;
	}
	
	if (_isIE) IESelIn(ev._el);
	
	var o=getScroll();
	if (ev._mx>o._x2||ev._my>o._y2) return;
	// TODO: Fix this for IE's multi-button-combination
	if (lastmousestate._button) {lastmousestate._button=ev._button;return;}
	ddrealmousedown(ev);
}
function arrcontains(arr,el)
{
	for (var i=arr.length-1;i>=0;--i)
		if (arr[i]==el) return true;
}
function ddrealmousedown(ev)
{
	eventStarted('ddMouse');
	lastmousestate._button=ev._button;
	lastmousestate._downevent=ev;
	lastmousestate._mx=ev._mx;
	lastmousestate._my=ev._my;
	lastev=ev;
	getparels(ev,true);
	addjssmouseclick(ev);
	var al=captures.length;
	var arr=[];
	while (captured&&!arrcontains(ev._els,captured))
		endovercapture(ev);
	if (al!=captures.length)
	{
		lastmousestate._button=0;
		fakemouseover(ev);
		getparels(ev,true);
		lastmousestate._button=ev._button;
	}

	return doEvent(ev,ev._els,/*{*/"_onmousedown"/*}*/);
}
function ddmouseup(ev)
{
	// Ignore scrollbar clicks in Firefox
	if (ev&&ev.originalTarget&&window.XULElement&&ev.originalTarget instanceof XULElement) return;
	
	lastwashandled=false;
	ev=myevent(ev,true);
	getparels(ev,true);
	addjssmouseclick(ev);
	// We need this because IE has a very strange sequence of events for double-clicks
	// for which the only work around is to turn the second mouseup into a mouseclick.
	if (!lastmousestate._button) ddrealmousedown(ev);

	// This fixes some bug, but it causes another. We need to correctly handle IE's multi-button-combination behaviour. (TODO:!)
	if (ev._button&&lastmousestate._button!=ev._button) {lastwashandled=true;return;}
	if (lastmousestate._dragging)
	{
		doEvent(ev,[lastmousestate._dragging],/*{*/"_ondragdrop"/*}*/);
		if (scrollinterval) scrollinterval=clearInterval(scrollinterval)&&null;
		lastmousestate._button=null;
		lastmousestate._dragging=false;
		lastwashandled=true;
		eventFinished('ddMouse');
		return false;
	}
	else
	{
		lastmousestate._button=null;
		var ret = !(lastwashandled=!doEvent(ev,ev._els,/*{*/"_onmouseup"/*}*/));
		if (ret) ret = !(lastwashandled=!doEvent(ev,ev._els,/*{*/"_onmouseclick"/*}*/,_clickhandlers));
		if (jQuery(ev._el).closest('.mceToolbar').length>0) ret=!(lastwashandled=true);
		eventFinished('ddMouse');
		return ret;
	}
}
var lastwashandled=false;
function ddcancelifhandled(ev)
{
	ev=ev||event;
	if (lastwashandled) return false;
	return ddmouseup(ev);
}
var lastoverlist=[];
var nextoverlist=[];
function ddmouseover(ev)
{
	ev=myevent(ev);
	ev._button=null;
	getparels(ev);
	addjssmouseover(ev);
	fakemouseover(ev);
}
function fakemouseover(ev)
{
	var inside=!captured;
	if (captured)
		for (var i=ev._els.length;i>=0;--i)
			if (ev._els[i]==captured) inside=true;
	if (inside)
		if (!lastmousestate._dragging&&!lastmousestate._button)
			changeover(ev);
	else
	{
		for (var i=lastoverlist.length-2;i>=0;--i)
		{
			if (lastoverlist[i]==captured)
			{
				loseover(ev,i+1);
				return;
			}
		}
	}
}
function loseover(ev,n)
{
	var to=[];
	for (var i=n;i<lastoverlist.length;++i)
		arraypush(to,lastoverlist[i]);
	changeover(ev,to);
}
function changeover(ev,to)
{
	var from=lastoverlist;
	to=to||ev._els;

	var d=to.length-from.length;
	var els=[];
	for (var i=0;from[i]!=to[d+i];++i)
		arraypush(els,from[i]);
	if (_isIE) IESelOut(els[0]);
	doEvent(ev,els,/*{*/"_onmouseout"/*}*/,_outhandlers);
	els=[];
	for (i=i+d-1;i>=0;--i)
		arraypush(els,to[i]);
	if (_isIE) IESelIn(els[0]);
	doEvent(ev,els,/*{*/"_onmouseover"/*}*/,_overhandlers);

	lastoverlist=to;
}

var ohcount = 0;
function AddHandlerByName(o, n, fn, p)
{
	var id = "oh"+ohcount++;
	if (!o[n]) o[n] = {};
	o[n][id] = {_fn:fn, _o:p};
	return function(){delete o[n][id];};
}

// TODO: Should 'out' be the undo for 'over'?
function AddOverHandlerByName(n, fn, p)
{
	return AddHandlerByName(_overhandlers, n, fn, p);
}

function AddOutHandlerByName(n, fn, p)
{
	return AddHandlerByName(_outhandlers, n, fn, p);
}

function AddClickHandlerByName(n, fn, p)
{
	return AddHandlerByName(_clickhandlers, n, fn, p);
}

var captures=[];
var captured;
function overcapture(ev,par,el,fn)
{
	for (var i=0;i<lastoverlist.length;++i)
		if (lastoverlist[i]==par)
			loseover(ev,i);
	setFakeParent(el,par);
	arraypush(captures,{_o:el,_fn:fn,_d:lastoverlist.length});
	var to=[el];
	for (var i=0;i<lastoverlist.length;++i)
		arraypush(to,lastoverlist[i]);
	changeover(ev,to);
	captured=el;
}
function endovercapture(ev,skipfn)
{
	var capt = captures[captures.length-1];
	var fn=capt._fn;
	var d=capt._d;
	var o=capt._o;
	captures.length--;
	captured=captures[captures.length-1];
	if (captured) captured=captured._o;
	if (fn&&!skipfn) fn(o);
	// TODO: Work out how d is ever greater, as that's a bug. Grant hid a symptom.
	if (d<lastoverlist.length)
		loseover(ev,lastoverlist.length-d);
}
var scrollinterval=null;
var drageltx=0;
var dragelty=0;
function scrollagain(dx,dy)
{
	var tx=Math.round(drageltx+=dx);
	var ty=Math.round(dragelty+=dy);
	if (tx||ty)
	{
		var ddss=getScroll();
		var ddx=ddss._x;
		var ddy=ddss._y;
		if (_sizelocked&&ddss._x+ddss._cw+tx>rightx) tx=rightx-ddss._cw-ddss._x;
		if (_sizelocked&&ddss._y+ddss._ch+ty>bottomy) ty=bottomy-ddss._ch-ddss._y;
		window.scrollBy(tx,ty);
		drageltx-=tx;
		dragelty-=ty;
		if (ddx==ddss._x&&ddy==ddss._y) scrollinterval=clearInterval(scrollinterval)&&null;
	}
}
var dragscrolls;
function ddmousemove(ev)
{
	ev=myevent(ev);
	getparels(ev);
	if (lastmousestate._dragging&&lastmousestate._dragging._ondragmove)
	{
		if (doEvent(ev,[lastmousestate._dragging],/*{*/"_ondragmove"/*}*/)) return;
		var cx=ev._mx;
		var cy=ev._my;
		var dx=0;
		var dy=0;
		var r=getScroll();
		if (cx>r._x2-150)
		{
			var ddx=r._x2-cx;
			if (ddx<1) ddx=1;
			dx+=(1/ddx)*75;
		}
		if (cx<r._x+150)
		{
			var ddx=cx-r._x;
			if (ddx<1) ddx=1;
			dx-=(1/ddx)*75;
		}
		if (cy>r._y2-150)
		{
			var ddy=r._y2-cy;
			if (ddy<1) ddy=1;
			dy+=(1/ddy)*75;
		}
		if (cy<r._y+150)
		{
			var ddy=cy-r._y;
			if (ddy<1) ddy=1;
			dy-=(1/ddy)*75;
		}
		if (scrollinterval) scrollinterval=clearInterval(scrollinterval)&&null;
		if (dx||dy) {scrollinterval=setInterval("scrollagain("+dx+","+dy+")",1);}
		return false;
	}
	else if (lastmousestate._button&&lastmousestate._downevent)
	{
		var dmx=lastmousestate._mx-ev._mx;
		var dmy=lastmousestate._my-ev._my;
		if (dmx<-2||dmx>2||dmy<-2||dmy>2)
		{
			var lev=lastmousestate._downevent;
			lastmousestate._dragging=lev;

			var handled=false;
			if (lev._el.type!="text"&&lev._el.type!="password")
			{
				doEvent(lev,lev._els,/*{*/"_ondragstart"/*}*/);
			}
			if (!lev._ondragdrop) lastmousestate._downevent=lastmousestate._dragging=null;
		}
		return false;
	}
	else if (mmc)
		doEvent(ev, ev._els, /*{*/"_onmousemove"/*}*/);
}
function ddkeypress2(ev)
{
	ev=myevent(ev);
	getparels(ev);
	return doEvent(ev,ev._els,/*{*/"_onkeypress"/*}*/);
}
function ddselchange(ev)
{
	ev=myevent(ev);
	ev._sel=document.selection.createRange();
	ev._el=ev._sel.parentElement();
	getparels(ev);
	return doEvent(ev,ev._els,/*{*/"_onselchange"/*}*/);
}
var ddscrolllastx;
var ddscrolllasty;
var scrollinputs;
var lastnotedscroll={_x:0,_y:0};
var forcescrollwatch={};
function ForceScrollWatch(fn)
{
	return fsAddEvent(forcescrollwatch,/*{*/"_onforcescroll"/*}*/, fn);
}
function ddscroll()
{
	var r=getScroll();
	var mvx=r._x-ddscrolllastx;
	var mvy=r._y-ddscrolllasty;
	ddscrolllastx=r._x;
	ddscrolllasty=r._y;
	if (scrollnewx!=null)
	{
		var lx = scrollnewx;
		var ly = scrollnewy;
		scrollnewx=scrollnewy=null;
		var oldlns=lastnotedscroll;
		lastnotedscroll={_x:r._x,_y:r._y};
		doEvent({_old:oldlns,_new:lastnotedscroll},[forcescrollwatch],/*{*/"_onforcescroll"/*}*/);
		if (r._x!=lx||r._y!=ly) {if (!_isIE6) setTimeout("forcescroll()",100);return;}
	}
	
	if (eventCount==0)
	{
		scrolldo = false;
		for (var i in forcescrolls)
			getel(forcescrolls[i]).value = r._x+"~"+r._y;
	}
}

_blurry = null;
// Focus events give us most of the information we need. Most browsers allow capturing
// of onfocus; IE allows bubbling of onfocusin. These are nearly equivalent and are
// handled by this function.
function ddfocus(ev)
{
	_blurev = null;
	ev = myevent(ev);
	getparels(ev);
	SetFocus(ev, ev._els);
}
// Most browsers won't give us any way of detecting whether we're losing focus entirely
// or whether it's just moving to another element (IE will). However, we can detect a
// blur without a corresponding focus by using this hack:
function ddblur(ev)
{
	ev = myevent(ev);
	_blurev = ev;
	/*!*/setTimeout("ddlostfocus()",1);/*!*/
}
function ddlostfocus()
{
	if (_blurev) SetFocus(_blurev, []);
}

_lastfocus=[];
_infocus = null;
function SetFocus(ev, els)
{
	// If the focus changes while we're handling focus-changes, we just
	// store the newer focus-chain and the code below will start working
	// towards that, instead.
	_newfocus=els;
	if (_infocus) return;
	_infocus = true;
	
	
	// Handle focus-changes on an element-by-element basis, making sure
	// that focusouts are checked for, first.
	while (_lastfocus[0]!=_newfocus[0])
	{
		var d = _newfocus.length-_lastfocus.length;
		if (_lastfocus[0]!=_newfocus[d])
		{
			_lastfocus[0]._hasFocus = false;
			if (_isIE) IESelOut(_lastfocus[0]);
			doEvent(ev, [_lastfocus[0]], "_onfocusout");
			_lastfocus = _lastfocus.slice(1);
		}
		else if (d>0)
		{
			_lastfocus = [_newfocus[d-1]].concat(_lastfocus);
			_lastfocus[0]._hasFocus = true;
			if (_isIE) IESelIn(_lastfocus[0]);
			doEvent(ev, [_lastfocus[0]], "_onfocusin");
		}
	}
	
	_infocus = false;
}
function GetFocusIDs(upto)
{
	var ret = [];
	for (var i=0;i<_lastfocus.length;++i)
	{
		if (_lastfocus[i].id)
			ret[ret.length]=_lastfocus[i].id;
		if (_lastfocus[i]==upto) return;
	}
	return ret;
}
function TrySetFocus(ids)
{
	var el;
	for (var i=0;i<ids.length;++i)
	{
		var el = getel(ids[i]);
		if (!el) continue;
		try { el.focus(); return el; } catch(e) {}
	}
}

var hackhiders=[];
function hidesels()
{
	for (var i=0;i<hackhiders.length;++i)
	{	
		var o = hackhiders[i];
		o._div.style.width=o.offsetWidth;
		o._div.style.height=o.offsetHeight;
		o._div.style.top=o.offsetTop;
		o._div.style.left=o.offsetLeft;
	}
}
function maybehidesels(){}
var maybehideselswillrun;
if (_isIE) maybehidesels = function()
{
	if (maybehideselswillrun) return;
	maybehideselswillrun=1;
	OnLoad(function(){hidesels();maybehideselswillrun=0;});
};

function selshate(o)
{
	o._selshate=(o._selshate||0)+1;
	if (o._selshate!=1) return;
	if (o._ifr||!_isIE) return;
	o._div = document.createElement("div");
	o._ifr = document.createElement("iframe");
	o._div.style.position="absolute";
	o._div.style.left="0";
	o._div.style.top="0";
	o._div.style.width="1px";
	o._div.style.height="1px";
	o._div.style.filter="alpha(opacity=0)";
	o._ifr.style.width="100%";
	o._ifr.style.height="100%";
	o._ifr.src="javascript:0";
	o._div.appendChild(o._ifr);
	o.parentNode.insertBefore(o._div,o);
	arraypush(hackhiders,o);
	hidesels();
}

function selsunhate(o)
{
	if (--o._selshate) return;
	if (!o._ifr||!_isIE) return;
	o._ifr.parentNode.removeChild(o._ifr);
	o._div.parentNode.removeChild(o._div);
	o._div=o._ifr=null;
	hackhiders=arrayremove(hackhiders,o);
	hidesels()
}

function JS00StandardHide()
{
	if (!this._hidecount++)
	{
		this.style.display="none";
		this._hidecount = 1;
	}
}

function JS00StandardShow()
{
	if (!--this._hidecount)
		this.style.display="";
}

function JS00StandardFocus()
{
	this.focus();
	if (this.type=="text") this.select();
}

function IsControl(el)
{
	return el._getValue&&1||el.tagName=="SELECT"||el.tagName=="TEXTAREA"||el.tagName=="INPUT";
}

function ControlValue(el)
{
	if (el._getValue) return el._getValue();
	if (el.type=="checkbox"||el.type=="radio") return el.checked?el.value:'';
	if (el.tagName!="SELECT"&&el.tagName!="select") return el.value;
	var val = el.selectedIndex==-1?null:el.options[el.selectedIndex].value;
	if (val=="-1"&&el.selectedIndex==0) return '';
	return val;
}

function ControlText(el)
{
	return el._getValue?el._getText():
		el.tagName=="SELECT"?el.options[el.selectedIndex].text:
		el.type=="checkbox"||el.type=="radio"?el.checked?el.value:'':
		el.value;
}

function HideControl(el)
{
	if (!el._hide) el._hide = JS00StandardHide;
	el._hide();
}

function ShowControl(el)
{
	if (!el._show) el._show = JS00StandardShow;
	el._show();
}

function FocusControl(el, backwards)
{
	if (!el._focus) el._focus = JS00StandardFocus;
	el._focus(backwards);
}

function FindControls(el, aout)
{
	aout=aout||[];
	if (!IsControl(el))
		for (var i=0;i<el.childNodes.length;++i)
			FindControls(el.childNodes[i], aout);
	else
		arraypush(aout, el);
	return aout;
}

OnLoad(start00);















































//<!--// Stick something like the following in 0.js:
// if (browser definitely supports AJAT) location.replace(aspxpart+"/FJ/#"+rest);
// (Hopefully with an fs2, so the server doesn't need to think)-->

// Also TODO: these
// Upload fields missing
// FieldPopup return seems to fail.
// Page Timeouts need handling.
// Response timeouts and other errors need handling.

_historyiframe=null;
_historytext=null;
_secrettoken="FS2S"+Math.floor(Math.random()*100000000000.0)+"FS2S";
_watchint=null;
_forcerefresh=false;
_Uploads=[];
_UploadCount=0;
_UploadTokens='';
__UploadDoneFn=null;
_action=null;
_actionel=null;

function StartAJAT(mode)
{
	if (mode=="None") return;
	_forceloaded=1;
	_ignorelocation=1;
	eventStarted('startAJAT');
	if (mode=="OnePageNoBack")
	{
		var mainform;
	
		for (var i=0;i<document.forms.length;++i)
		{
			var f = document.forms[i];
			var els = f.elements;
			if (parLoc(f.action,"fs2s"))
			{
				_action = f.action;
				_actionel = f;
				FixNode(f, true);
				f.onsubmit=SubmitPartial2;
				servervals=lastvals=FormValsFrom(f,null,true);
				mainform = f;
				break;
			}
		}
		if (!_action)
		{
			eventFinished('startAJAT');
			return;
		}
		_action=_action.split("&amp;").join("&");
		var fs2sloc = parLoc(_action,"fs2s")+5;
		_fs2s = _action.substr(fs2sloc,11);
		_action = _action.substr(0, fs2sloc)+_secrettoken+_action.substr(fs2sloc+11);
	}
	SubmitPartial = SubmitPartial2;
	AddNextState("<FS2P fs2s='"+_fs2s+"' page='"+(window.fs2pageid||'initial')+"' responseid='AAAAAAAAAAA'><html><head><title>No title</title><script type='text/javascript' src='blah/0.js' id='bwsjs'></script></head><body></body></html></FS2P>");
	GoToHistory(histpos+1, null, true);
	_abortonpagechange=1;
	eventFinished('startAJAT');
}

var ajaxdone;
if (!ajaxdone)
{
	ajaxdone=true;
	OnLoad(function()
	{
		if (window._ajaxmode) return StartAJAT(_ajaxmode);
		SubmitPartial=SubmitPartial2;
		eventStarted('AJAXLoad');
		_historyiframe = getel('fs2jsifr');
		_historytext = getel('fs2jsta');
		if (_historytext&&_historytext.value&&!window._firstpage)
		{
			var o = RestoreState();
			if (self._forceloaded) {if (InitialiseExistingHTML()==false) return false;}
			var url = getAJAXURL();
			if (!o._url&&urlhist[url.num-1]==url.rel)
			{
				GoToHistory(url.num,{});
				eventFinished('AJAXLoad');
				_watchint=setInterval(CheckPageURL, 55);
				return;
			}
			if (self._forceloaded) return;
			history.go(-1);
			GoToHistory(url.num,{},true);
			eventFinished('AJAXLoad');
			_watchint=setInterval(CheckPageURL, 55);
			LinkHit(o._url);
			return;
		}
		else if (self._forceloaded)
		{
			if (InitialiseExistingHTML()==false) return false;
			ExtractInitialState();
			return;
		}
		var url = getAJAXURL();
		if (url&&url.abs)
		getText(url.abs, function(txt,code)
		{
			if (txt==null)
			{
				document.cookie = "NoAJAT=1";
				location.refresh();
				//TODO: Support non-forced mode for this
				//location.replace((location+"").split("#").join(""));
				return;
			}
			AddNextState(txt);
			GoToHistory(histpos+1);
			AddToHistory(url.rel, url.preFJ);
			StoreAllState();
			eventFinished('AJAXLoad');
			if (_historyiframe)
				_watchint=setInterval(CheckPageURL, 55);
		});
	});
}

_fs2s=null;
//TODO: Test this or remove it. It's for the AJAX mode for rendering a form, in which it can handle in-render page transitions but no other kind.
function InitialiseExistingHTML()
{
	var cause = "This is normally caused by invalid HTML in a branding, a raw HTML field or the page otherwise surrounding the form.";
	var el = getel('bwsjs');
	while (el&&(el.nodeType!=8||el.nodeValue!="FS2PS: Begin head"))
	{
		el._donoterase = true;
		el = el.previousSibling;
	}
	if (!el)
	{
		alert("AJAX cannot find start of head. "+cause);
		return false;
	}
	el.parentNode.insertBefore(getel('bwsjs'),el);
	
	var el = getel('bwsjs');
	while (el&&(el.nodeType!=8||el.nodeValue!="FS2PS: End head"))
	{
		el._donoterase = true;
		el = el.nextSibling;
	}
	if (!el)
	{
		alert("AJAX cannot find end of head. "+cause);
		return false;
	}
	
	var beginb = [];
	forEveryNode(document.documentElement, function(el){if (el.nodeValue=='FS2PS: Begin body') beginb.push(el);});
	var endb = beginb[0];
	while (endb&&endb.nodeValue!='FS2PS: End body') endb=endb.nextSibling;
	if (beginb.length!=1||!endb)
	{
		alert("AJAX cannot initialise because the DOM is invalid. "+cause);
		return false;
	}

	for (var i=0;i<document.forms.length;++i)
	{
		var f = document.forms[i];
		var els = f.elements;
		if (parLoc(f.action,"fs2s"))
		{
			_action = f.action;
			_actionel = f;
		}
		else for (var j=0;j<els.length;++j)
		{
			if (!els[j].name) continue;
			var n=els[j].name.indexOf("FS2ActionUrl");
			if (n==-1||n!=els[j].name.length-"FS2ActionUrl".length) continue;
			_actionel = els[j];
			_action = _actionel.value;
			break;
		}
		if (_action)
		{
			for (var k=0;k<els.length;++k)
				FixNode(els[k], true);
			f.onsubmit=SubmitPartial2;
			servervals=lastvals=FormValsFrom(f);
			break;
		}
	}
	_action=_action.split("&amp;").join("&");
	var fs2sloc = parLoc(_action,"fs2s")+5;
	_fs2s = _action.substr(fs2sloc,11);
	_action = _action.substr(0, fs2sloc)+_secrettoken+_action.substr(fs2sloc+11);
}
	
function ExtractInitialState()
{
	// NOTE: This method has to run eventFinished('AJAXLoad') before returning.
	var head = "";
	var body = "";
	var begin = /FS2PS: Begin (.+)/;
	forEveryNode(document.documentElement, function(el)
	{
		if (el.nodeType!=8) return;
		var m = el.nodeValue.match(begin);
		if (!m||m[1]!='head'&&m[1]!='body') return;
		
		for (var end=el.nextSibling; end&&end.nodeValue!='FS2PS: End '+m[1]; end=end.nextSibling)
		{
			FixNode(end, true);
			if (!end.bws&&end.id!="fs2jsdiv"&&end.id!='bwsjs'&&end.id!="bwsload"&&(!end.text||end.text.indexOf("bws_oldonload")==-1))
				if (m[1]=='head')
					head+=end.outerHTML;
				else
					body+=end.outerHTML;
		}
	});
	
	AddNextState("<FS2P fs2s='"+_fs2s+"' page='initial'><html><head><title>"+document.title+"</title><script type='text/javascript' src='blah/0.js' id='bwsjs'></script>"+head+"</head><body>"+body+"</body></html></FS2P>");
	GoToHistory(histpos+1, null, true);
	_forcerefresh=false;
	var url = getAJAXURL();
	AddToHistory(url.rel);
	StoreAllState();
	eventFinished('AJAXLoad');
	if (_historyiframe)
		_watchint=setInterval(CheckPageURL, 55);
}

function UploadError(type, text, ifr)
{
	alert("Upload error of type "+type+":\n"+text);
	UploadDone(null,ifr);
}

function UploadDone(id, ifr)
{
	if (!_Uploads[ifr._upid]) return;
	if (id)
		_UploadTokens=(_UploadTokens?_UploadTokens+","+id:id);
	_UploadCount--;
	delete _Uploads[ifr._upid];
	//if (_isIE) history.go(-1);
	if (_UploadCount!=0) return;
	eventFinished('Upload');
	_UploadDoneFn();
}

function MaybeClickButton(frm, ths)
{
	if (ths.disabled) return;
	return !SubmitPartial(frm,ths);
}

function KeySubmitPartial(ev, evo)
{
	if (ev._key!=13&&ev._key!=32) return;
	return !SubmitPartial(null, evo);
}

function ForceIERefresh()
{
	var myfr = document.createElement('iframe');
	myfr.src='javascript:""';
	myfr.style.position='absolute';
	myfr.style.left=myfr.style.top='0';
	myfr.style.width=myfr.style.height='100%';
	document.body.appendChild(myfr);
	document.body.removeChild(myfr);
}

var AJAXCallbacks={done:{}};

var SubmitPartial;
// Submit a form, AJAT-style (only send changed items)
function SubmitPartial2(frm, ths, isTargetTop)
{
	$log("AJAX starting");
	if (window.tinyMCE&&tinyMCE.triggerSave) try{tinyMCE.triggerSave();}catch(e){}
	// If we're called by Sharepoint, we don't want to handle it.
	if ((arguments.callee.caller+'').indexOf('__doPostBack')!=-1) return;
	ths=ths||this;
	if (eventCount>(clickingButtons?1:0))
	{
		$log("AJAX postponed, JS busy");
		$c(ths);
		return false;
	}
	eventStarted('AJAXSubmit');
	// What form changes are there?
	frm = frm||ths.form||ths;
	var origurl = ((_action||(frm.getAttribute?frm.getAttribute("action"):frm.action))+"").split(_secrettoken).join(getContent('fs2s'));
	var url = getAJAXURL(origurl)||{abs:origurl,rel:origurl};
	
	document.body.className+=" ajaxbusy";
	if (_isIE) ForceIERefresh();
	
	_UploadCount=0;
	for (var i in _Uploads)
	{
		if (!_Uploads.hasOwnProperty(i)) continue;
		if (url.abs.indexOf('#')!=-1) url.abs=url.abs.substring(0,url.abs.indexOf('#'));
		++_UploadCount;
		_Uploads[i].contentWindow.document.forms[0].action=url.abs+"&FS2AJAXPartialForm";
		try{_Uploads[i].contentWindow.document.forms[0].submit();}catch(e){--_UploadCount;delete _Uploads[i];}
	}
	if (_UploadCount>0)
	{
		$log('AJAX handling upload fields');
		eventStarted('Upload');
		eventFinished('AJAXSubmit');
		_UploadTokens='';
		_UploadDoneFn=function(){$log('AJAX finished handling upload fields');SubmitPartial2(frm, ths)};
		return false;
	}
	
	var tt = GetTimer("AJAX Request");
	$log("AJAX getting form values");
	var subs = [];
	var vals = FormValsFrom(frm, subs);
	var diff=DiffFromFormVals(vals, servervals);
	
	AddFormVals(lastvals, diff);
	AddDateTimeFields(diff, lastvals);
	
	if (ths.type=="submit"||ths.type=="image"||ths.type=="button")
		arraypush(subs, ths);
		
	for (var i=0;i<subs.length;++i)
		AddFormVal(diff, subs[i].name, subs[i].value);
		
	var append=_UploadTokens?("&FS2AJAXPartialForm="+_UploadTokens):"";
	_UploadTokens='';
	
	var postdata = ValsAsPostData(diff)+append;
	
	var requestt = GetTimer("AJAX request");
	
	getText(url.abs, function(txt,status)
	{
		requestt.end();
		if (txt.indexOf("<!--AJAXForceMainPageSubmit-->")!=-1)
		{
			$log("AJAX forcing main page submission");
			var newact = _actionel.action||_actionel.value;
			var actfs2sp = parLoc(newact,"fs2s")+5;
			var respfs2sp = txt.indexOf("fs2s='")+6;
			newact=newact.substr(0,actfs2sp)+txt.substr(respfs2sp,11)+newact.substr(actfs2sp+11);
			if (_actionel.action)
			{
				for (var i=0;i<document.forms.length;++i)
				{
					var f = document.forms[i];
					if (parLoc(f.action,"fs2s"))
						_actionel = f;
				}
				_actionel.action = newact;
			}
			else _actionel.value = newact;
			
			if (isTargetTop) {
				_actionel.target = "_top";
			}
			
			if (_actionel.action)
				_actionel.submit();
			else
				_actionel.form.submit();
			return;
		}
		var t = GetTimer("AJAX parsing HTML");
		if (AddNextState(txt, diff, isTargetTop)==1) {t.end();tt.end();return;}
		t.end();
		
		t = GetTimer("AJAX altering page");
		var r = getScroll();
		if (GoToHistory(histpos+1)==1) {t.end();tt.end();return;}
		scrollnewx=-1;
		scrollTo(r._x,r._y);
		scrolldo=true;
		AddToHistory(url.rel);
		t.end();
		
		$log("AJAX storing extra state");
		StoreAllState();
		var t = GetTimer("AJAX calling callbacks");
		for (var i in AJAXCallbacks.done) AJAXCallbacks.done[i](ths);
		t.end();
		$log("AJAX removing busy cursor");
		document.body.className=(document.body.className+'').replace(/\s*ajaxbusy/g,"");
		// IE doesn't update the cursor. Sticking an iframe under the cursor briefly seems to work.
		if (_isIE) ForceIERefresh();
		$log("AJAX Finishing");
		eventFinished('AJAXSubmit');
		_bwsnavig = null;
		tt.end();
	}, postdata);
	
	return false;
}

// Handles the default action for all links
function LinkHit(url)
{
	if (_forceloaded) return location=url;
	eventStarted('LinkHit');
	url = getAJAXURL(url);
	getText(url.abs, function(txt,status)
	{
		AddNextState(txt);
		GoToHistory(histpos+1);
		AddToHistory(url.rel);
		StoreAllState();
		eventFinished('LinkHit');
	});
}

function GetCurrentTitle()
{
	return getContent(getContent('page')+"-title");
}

// Updates the location and the page.
function AddToHistory(url)
{
	if (_historyiframe)
	{
		_historyiframe.contentWindow.document.open();
		_historyiframe.contentWindow.document.write("<html><head><title>"+GetCurrentTitle()+"</title><script type='text/javascript'>"+(window._fsurl?"parent._fsurlhash=":"parent.location.hash=")+_serialise(histpos+url)+"</script></head><body><h1>"+histpos+url+"</h1></body></html>");
		_historyiframe.contentWindow.document.close();
	}
	else
	{
		if (window._fsurlhash)
			_fsurlhash = histpos+(url||'/FJ/');
		else if (!window._ignorelocation)
			location.hash = histpos+url;
	}
	
	urlhist[histpos-1]=url;
}

// Monitoring of the address bar - tells us when the user has modified it, so we can take over the world.
function CheckPageURL()
{
	if (eventCount>0) return;
	var url = getAJAXURL();
	if (urlhist[histpos-1]==url.rel&&histpos==url.num) return;
	if (urlhist[url.num-1]==url.rel) return GoToHistory(url.num);
	
	StoreAllState({_url:url.abs});
	document.location=url.preFJ+"/FJ/?GoBack="+2;
	eventStarted('CheckPageURL');
}

function StoreAllState(extras)
{
	if (!_historytext) return;
	var o = {_secrettoken:_secrettoken, _content:content, _hist:hist, _urlhist:urlhist, _valhist:valhist, _lastvals:lastvals, _servervals:servervals, _histpos:histpos};
	for (var i in extras)
		o[i]=extras[i];
	_historytext.value="("+_serialise(o)+")";
}

function RestoreState()
{
	var o = eval(_historytext.value);
	_secrettoken = o._secrettoken;
	content = o._content;
	hist = o._hist;
	urlhist = o._urlhist;
	valhist = o._valhist;
	lastvals = o._lastvals;
	servervals = o._servervals;
	histpos = o._histpos;
	return o;
}

var content={page:{arr:[],pos:0}};
var hist=[];
var histpos=0;
var urlhist=[];
var valhist=[];
var lastvals={};
var servervals={};

var fs2sre = /^<FS2P fs2s='(.*?)'/;
var fullpagere = /^<FS2P fs2s='(.*?)' page='(.*?)'(?: responseid='(.*?)')?>[\s\S]*?<html[\s\S]*?<head[\s\S]*?<title>([\s\S]*?)<\/title>([\s\S]*?)(?:<script type='text\/javascript' src='[^']*\/0.js' id='bwsjs'><\/script>([\s\S]*?))?<\/head>[\s\S]*?<body[\s\S]*?>([\s\S]*?)<\/body[\s\S]*?<\/FS2P>$/;
var partpagere = /^<FS2P fs2s='(.*?)' page='(.*?)'(?: responseid='(.*?)')?>([\s\S]*?)<\/FS2P>$/;
// This is used for aborting a changing in GoToHistory (when a mismatch is detected)
var lastresponseid;
function AddNextState(txt, sentvals, isTargetTop)
{
	var sects = hist[histpos] = {};
	
	var vals = {};
	
	if (!txt) {alert("Error 75.");return;}
	
	var fs2sm = fs2sre.exec(txt);
	if (!fs2sm)
	{
		var div = document.createElement("div");
		var h1 = document.createElement("h1");
		h1.innerHTML="Unrecognised response from server";
		div.appendChild(h1);
		var inner = document.createElement("div");
		inner.innerHTML=txt.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\n/g,"<br/>");
		div.appendChild(inner);
		div.style.position="absolute";
		div.style.width="90%";
		div.style.height="90%";
		div.style.left="1em";
		div.style.right="1em";
		div.style.top="1em";
		div.style.bottom="1em";
		div.style.backgroundColor="white";
		div.style.border="2px solid red";
		div.style.overflow='scroll';
		var odiv = document.createElement('div');
		odiv.appendChild(div);
		document.body.innerHTML=odiv.innerHTML;
		return;
	}
	txt = txt.split(fs2sm[1]).join(_secrettoken);
	_fs2s=fs2sm[1];
	AddNextPart('fs2s', fs2sm[1], sects);
	
	var page = fullpagere.exec(txt);
	if (!page) page = partpagere.exec(txt);
	if (!page) {alert("Error 77.");return;}
	lastresponseid=page[3];
	
	if (page.length<=5&&window._forceloaded)
	{
		for (var i=content['page'].arr.length;i-->0;)
			if (content['page'].arr[i]==page[2])
				break;
		if (i<0)
			page[2]='initial';
	}
	
	if (page[2]!=content.page.arr[content.page.pos-1])
	{
		AddNextPart('page', page[2], sects);
	}
	
	if (page.length>5&&window._abortonpagechange||/tinyMCE|gmaps/.exec(txt)||window.tinyMCEInited||page[2]=='initial'&&window.fs2pageid)
	{
		$log("AJAX ABORT");
		setTimeout(function() {
		    if (isTargetTop
		        || (document.forms.length > 0 && document.forms[0].target == "_top")) {
		        window.top.location = ((_action || (frm.getAttribute ? frm.getAttribute("action") : frm.action)) + "").split(_secrettoken).join(page[3]);
		    } else {
		        document.location = ((_action || (frm.getAttribute ? frm.getAttribute("action") : frm.action)) + "").split(_secrettoken).join(page[3]);
		    }
		},1000);
		return 1;
	}
	
	if (page.length>5)
	{
		AddNextPart(page[2]+'-title', page[4], sects);
		AddNextPart(page[2]+'-head', page[5]+page[6], sects);
		AddNextPart(page[2], page[7], sects, vals);
	}
	else
	{
		var repre = /<!--FS2PS: Replacing (.*?)-->/g;
		
		var str = page[4];
		
		var replacing = null;
		var lastend = 0;
		var m=repre.exec(str);
		while (m)
		{
			var m2=repre.exec(str);
			var endpos = m2?m2.index:str.length;
			var startpos = m.index+m[0].length;
			AddNextPart(m[1], str.substring(startpos,endpos), sects, vals);
			m=m2;
		}
	}
	
	AddFormVals(lastvals, vals);
	if (sentvals) {AddFormVals(sentvals,vals);vals=sentvals;}
	// TODO: This needs to correctly handle incomplete sets of radio buttons.
	valhist[histpos] = vals;
}
function NewArrayFor(name)
{
	var arr=[];
	if (!content[name]) content[name]={arr:[], pos:0};
	content[name].arr.length = content[name].pos;
	content[name].arr[content[name].pos] = arr;
	return arr;
}
function AddNextPart(name,str,dest,vals)
{
	if (vals)
	{
		// If the caller is interested in form values, there might also be links to take care of.
		if (!_forceloaded) str = InstrumentLinks(str);
		GetValues(str, vals);
	}
	
	dest[name]=1;
	
	var splitter=/<!--FS2PS: (Begin|End) (.*?)-->/g;
	var lastpos = 0;
	var arr=NewArrayFor(name);
	var stack=[];
	var m;
	while (m = splitter.exec(str))
	{
		arraypush(arr, str.substring(lastpos, m.index));
		lastpos = m.index+m[0].length;
		
		if (m[1]=="Begin")
		{
			arraypush(arr,m[2])
			arraypush(stack,arr);
			arr=NewArrayFor(m[2]);
			if (dest[m[2]]) alert("Error 198");
			dest[m[2]] = 1;
		}
		else
			arr = stack.pop();
	}
	arraypush(arr,str.substr(lastpos));
	
	if (stack.length>0) alert("Error 682.");
	
	return dest;
}

function ElementsByLowerTagName(el,tagName)
{
	var ret = [];
	// For some reason, this is _much_ faster than jQuery.
	if (el.getElementsByTagName)
	{
		
		if (el.tagName.toLowerCase()==tagName)
			ret.push(el);
		var els = el.getElementsByTagName(tagName);
		for (var i=0;i<els.length;++i)
			ret.push(els[i]);
		
	}
	else
	{
		var $el = $(el);
		$el.find(tagName).add($el.filter('tagName')).each(function(el){ret.push(el)});
	}
	return ret;
}

function ForEachElementByLowerTagName(el, tagName, fn)
{
	var els = ElementsByLowerTagName(el,tagName);
	for (var i=0;i<els.length;++i)
		fn(els[i]);
}

var focusTxt = null;
function GoToHistory(n, touched, delayDraw)
{
	var origScroll = getScroll();
	
	if (!touched)
	{
		touched = {};
		var pagethen = getContent('page');
		while (histpos<n||histpos>n)
		{
			if (histpos>n) --histpos;
			valhist[histpos] = AddFormVals(servervals,valhist[histpos]);
			for (var i in hist[histpos])
			{
				touched[i]=1;
				content[i].pos+=(histpos<n?1:-1);
			}
			if (histpos<n) ++histpos;
		}
	}
	if (delayDraw) {_forcerefresh=true;return;}
	var pagenow = getContent('page');
	if (pagethen!=pagenow||_forcerefresh) touched.title=touched.head=touched.body=1;
	_forcerefresh=false;
	
	if (touched.page&&!window._forceloaded)
		document.title = getContent(pagenow+"-title");
	
	var t = GetTimer("Find page-bits")
	var ends = getTouchedEnds(touched);
	t.end();
	
	var lostFocus = false;
	
	// This element is created at the limits of the document size to make sure the scroll area doesn't ever become smaller while we're playing with the page. It should make random scrolling happen less, but some scrolling still happens, and I've had to put a workaround into SubmitPartial2
	var scrpos = getScroll();
	var noscr = document.createElement("div");
	noscr.style.position="absolute";
	noscr.style.left=scrpos._w-1+"px";
	noscr.style.top=scrpos._h-1+"px";
	noscr.style.width="1px";
	noscr.style.height="1px";
	noscr.innerHTML="#160;";
	document.body.appendChild(noscr);
	
	var undot = GetTimer("Undoing");
	for (var i=0;i<ends.length;++i)
	{
		var end = ends[i];
		var els = end.els = [];
		for (var el = end.start.nextSibling; el&&el!=end.end; el=el.nextSibling)
		{
			els.push(el);
			var objs = [];
			ForEachElementByLowerTagName(el, 'script', function(el){
				if (!el.text) return;
				var eltext = el.text;
				var m = /\s*<!--\s*([\s\S]*)/.exec(eltext);
				eltext = m&&m[1]||eltext;
				eltext = eltext.split("$$(");
				if (eltext.length>1)
				{
					eltext = eltext.join("$$undo(");
					eval(eltext);
				}
			});
			
			// We need to hide objects directly, because otherwise they might be visibly left behind. This was certainly the case for IE6, IE7, IE8. It was sporadic, though. Also, it may have been related to the bug whereby we were accidentally using the non-recursive version of removeChild.
			ForEachElementByLowerTagName(el, 'object', function(el){
				el.style.display="none";
			});
			
			// If a select-box has focus when it is destroyed, IE is likely to eventually crash.
			// Elements with focus would get loss-of-focus events after they've been removed, anyway.
			if (el._hasFocus)
			{
				lostFocus = GetFocusIDs(el);
				if (!focusTxt)
				{
					if (_isIE)
						focusTxt = document.createElement("<input type='text'>");
					else
					{
						focusTxt = document.createElement('input');
						focusTxt.type='text';
					}
					focusTxt.style.position="absolute";
					document.body.appendChild(focusTxt);
					focusTxt.style.zIndex="-200";
					focusTxt.style.filter="alpha(opacity=1)";
					focusTxt.tabIndex=-1;
					try {
						focusTxt.style.clip="rect(0 0 1 1)";
					} catch (e) {
						focusTxt.style.clip="rect(0, 0, 1px, 1px)";
					}
				}
				moveobj(focusTxt,origScroll._x,origScroll._y);
				focusTxt.focus();
			}
		}
		
		if (!end.end)
		{
			//alert("Unbalanced node: "+end.name+"\nI'll compensate, but please show Grant how you made this message appear.");
			// TODO: Rather than redrawing the whole page, this could try just
			//       redrawing the first containing PageSection that fully
			//       encloses el and end.
			// (Passing in touched causes a page redraw - dodgy but true)
			document.body.removeChild(noscr);
			if (window._abortonpagechange)
			{
				alert("AJAX forced refresh for "+end.start.nodeValue);
				setTimeout(function(){document.location=((_action||(frm.getAttribute?frm.getAttribute("action"):frm.action))+"").split(_secrettoken).join(lastresponseid);},1000);
				$log('AJAX ABORT for '+end.start.nodeValue);
				return 1;
			}
			return GoToHistory(n,touched);
		}
	}
	undot.end();
	
	var removet = GetTimer("Removing");
	for (var i=0;i<ends.length;++i)
	{
		var els = ends[i].els;
		for (var j=0;j<els.length;++j)
		{
			var el = els[j];
			if (el.id!="fs2jsdiv")
				if (el._donoterase)
				{
					el.disabled = true;
					end.start = el;
				}
				else
					el.parentNode.removeChild(el);
			else
				end.start = el;
		}
	}
	removet.end();
	
	var allnodes = [];
	var addt = GetTimer("Adding");
	for (var i=0;i<ends.length;++i)
	{
		var end = ends[i];
		var html = getContent(end.name=='head'?pagenow+'-head':end.name=='body'?pagenow:end.name);
		var nodes = end.nodes = importHTML(html);
		var df = document.createDocumentFragment();
		for (var j=0;j<nodes.length;++j)
			df.appendChild(nodes[j]);
		end.end.parentNode.insertBefore(df, end.end);
	}
	addt.end();
	
	var forAllNodes = function(fn)
	{
		for (var i=0;i<ends.length;++i)
		{
			var nodes = ends[i].nodes;
			for (var j=0;j<nodes.length;++j)
				fn(nodes[j]);
		}
	};
	var fixt = GetTimer("Toggling stylesheets")
	forAllNodes(ToggleStylesheet);
	fixt.end();
	fixt = GetTimer("Fixing field values");
	forAllNodes(function(el){UseFormVals(lastvals,el);});
	fixt.end();
	
	fixt = GetTimer("Fixing form tags");
	forAllNodes(FixForms);
	fixt.end();
	fixt = GetTimer("Fixing form fields");
	forAllNodes(FixFieldEvents);
	fixt.end();
	fixt = GetTimer("Fixing scripts");
	forAllNodes(FixScripts);
	fixt.end();
	fixt = GetTimer("Fixing name-assocations");
	ElementsUpdated();
	fixt.end();
	
	if (lostFocus)
	{
		var rr = getScroll();
		moveobj(focusTxt,rr._x,rr._y);
		TrySetFocus(lostFocus);
	}
	else if (focusTxt)
	{
		document.body.removeChild(focusTxt);
		focusTxt = null;
	}
	document.body.removeChild(noscr);
}

// Given a set of pagesection identifiers (for which content needs replacing), find all the start and end comment markers, but not the ones that are entirely surrounded by another of interest.
function getTouchedEnds(touched)
{
	var ret = [];
	var within = null;
	var begin = /FS2PS: Begin (.+)/;
	forEveryNodeMatching(document.documentElement, function(el)
	{
		if (el.nodeType!=8) return !within;
		if (!within)
		{
			var m = el.nodeValue.match(begin);
			if (m&&touched[m[1]]) {within = m[1];ret[ret.length]={name:within,start:el};}
		}
		else if (el.nodeValue=='FS2PS: End '+within) {within = null;ret[ret.length-1].end=el;}
		
		return !within;
	});
	return ret;
}

// IE and Firefox occasionally mess up stylesheets unless you do this after adding them.
function ToggleStylesheet(el)
{
	if ((el.tagName=="link"||el.tagName=="LINK")&&!el.disabled) el.disabled=!(el.disabled=true);
}

function FixForms(el)
{
	ForEachElementByLowerTagName(el, 'form', function(el){
		el.onsubmit=SubmitPartial2;
	});
}

function FixFieldEvents(el, initial)
{
	ForEachElementByLowerTagName(el, 'button', function(el){
		fsAddEvent(el,"_onmouseclick", MaybeClickButton);
		fsAddEvent(el,"_onkeypress", KeySubmitPartial);
	});
	ForEachElementByLowerTagName(el, 'input', function(el){
		if (el.type=="submit"||el.type=="image"||el.type=="button")
		{
			fsAddEvent(el,"_onmouseclick", MaybeClickButton);
			fsAddEvent(el,"_onkeypress", KeySubmitPartial);
		}
		if (!initial)
		{
			el._defaultChecked=el.checked;
			el._defaultValue=el.value;
		}
	});
	if (!initial)
	{
		ForEachElementByLowerTagName(el, 'option', function(el){
			el._defaultSelected=el.selected;
		});
		ForEachElementByLowerTagName(el, 'textarea', function(el){
			el._defaultValue=el.value;
		});
	}
}

function FixScripts(el)
{
	// Firefox will already have run scripts - IE and Chrome need some prodding
	if (DoInnerHtmlScriptsRun()) return;
	
	ForEachElementByLowerTagName(el, 'script', function(el){
		if (el.id=="bwsjs"||el.text=="$bwsajat()") return;
		var par = el.parentNode;
		var next = el.nextSibling;
		par.removeChild(el);
		InsertScript(par,next,Script(el.text,el.src,el.id,el.name));
	});
}

function FixNode(el, initial)
{
	// IE and Firefox occasionally mess up stylesheets unless you do this.
	if (!initial) ToggleStylesheet(el);
	
	if (!initial) UseFormVals(lastvals,el);
	
	FixForms(el);
	FixFieldEvents(el, initial);
	// TODO: Find out whether we can safely set default* rather than _default*
	if (!initial) FixScripts(el);
	
	ElementsUpdated();
}

function Script(text,src,id,name)
{
	var scr = document.createElement('script');
	if (text) scr.text = text;
	else scr.src = src;
	if (name) scr.name = name;
	if (id) scr.id = id;
	return scr;
}

function DoNothing(){}
function EventFinishedWhenLoaded()
{
	if (this.alldone) return;
	if (this.readyState=="complete"||this.readyState=="loaded"||!this.readyState)
	{
		this.alldone=true;
		this.onreadystatechange=DoNothing;
		this.onload=DoNothing;
		eventFinished('ScriptLoad '+(this.src||this.text.substr(0,30)));
	}
}

function InsertScript(par, next, el)
{
	eventStarted('ScriptLoad '+(el.src||el.text.substr(0,30)));
	// If the script has a source, that means the browser is going to delay the running. We need to register the event handler _before_ we add it to the document
	if (el.src)
	{
		el.onreadystatechange=EventFinishedWhenLoaded;
		el.onload=EventFinishedWhenLoaded;
	}
	try
	{
		if (next)
			par.insertBefore(el,next);
		else
			par.appendChild(el);
	}
	catch (e) {}
	if (!el.src) eventFinished('ScriptLoad '+(el.src||el.text.substr(0,30)));
}

// Well, do they? Caches the result, since it's a per-browser thing
var doTheyTested;
var doTheyRun;
function DoInnerHtmlScriptsRun()
{
	if (doTheyTested) return doTheyRun;
	var div = document.createElement('div');
	div.innerHTML="<pre></pre><script type='text/javascript'>doTheyRun=true;<\/script>";
	document.body.appendChild(div);
	document.body.removeChild(div);
	doTheyTested=true;
	return doTheyRun;
}

function getContent(name)
{
	var ret = '';
	var arr = content[name].arr[content[name].pos-1]||[];
	for (var i=0;i<arr.length;i+=2)
	{
		ret+=arr[i];
		if (arr[i+1]) ret+="<!--FS2PS: Begin "+arr[i+1]+"-->"+getContent(arr[i+1])+"<!--FS2PS: End "+arr[i+1]+"-->";
	}
	return ret;
}

/////////////////////////////////////////////////////////////////////////////
// Supporting functions
//---------------------------------------------------------------------------
// These simply help with the dynamic goodness.
/////////////////////////////////////////////////////////////////////////////

// Gets the values from all the fields within el. el should be a node from
// either an XML Document or an HTML one.
function FormValsFrom(el, subs, doDefs, forceSelects)
{
	var vals = this.vals = {};
	if (el)
		forEveryNodeMatching(el, function(el)
		{
			if (el.nodeType!=1) return false;
			var elName = el.tagName.toLowerCase();
			var type = el.type;
			var valName = el.name;
			var val = null;
			if (elName=="input"&&(type=="text"||type=="hidden"||type=="password"))
			{
				if (el._fakeSubmit) {arraypush(subs,el);el.parentNode.removeChild(el);return false;}
				val = doDefs?el._defaultValue||el.defaultValue:el.value;
				if (val==null) val = el.getAttribute('value');
				if (val==null) val = el.textContent;
				if (val==null) val = XMLsToStr(el.childNodes);
			}
			else if (type=="checkbox"||type=="radio")
			{
				val = (doDefs?el._defaultChecked||el.defaultChecked:el.checked)?el.value:'';
			}
			else if (elName=="select")
			{
				var v=vals[valName];
				if (!v) vals[valName]=v=[];
				var firstopt;
				var sel=false;
				forEveryNodeMatching(el, function(el)
				{
					if (!el.tagName||el.tagName.toLowerCase()!="option") return true;
					if (!firstopt) firstopt=el;
					if (doDefs?el._defaultSelected||el.defaultSelected:el.selected)
					{
						sel=true;
						v[v.length]=doDefs?el.defaultValue||el.value:el.value;
					}
				});
				if (!sel&&forceSelects&&firstopt)
					v[v.length]=doDefs?firstopt.defaultValue||firstopt.value:firstopt.value;
				return true;
			}
			else if (elName=="textarea")
			{
				if (el==_historytext) return;
				val = doDefs?el._defaultValue||el.defaultValue:el.value;
				if (val==null)
				{
					val="";
					for (var chel = el.firstChild;chel;chel=chel.nextSibling)
						if (chel.nodeType=="3")
							val+=chel.nodeValue;
						else if (chel.nodeType=="1"&&chel.nodeName=="br")
							val+="\n";
				}
				val = val.replace(/\r\n|\r/g,"\n");
			}
			else return true;
			if (!vals[valName]) vals[valName] = val?[val]:[];
			else if (val) arraypush(vals[valName], val);
		});
	return vals;
}
function nullToEmpty(s)
{
	if (s==null) return '';
	else return s;
}
function UseFormVals(vals, el)
{
	if (el)
		forEveryNodeMatching(el, function(el)
		{
			if (el.nodeType!=1) return false;
			var elName = el.Name||el.tagName.toLowerCase();
			var type = el.type;
			var valName = el.name;
			var val = null;
			if (elName=="input"&&(type=="text"||type=="hidden"||type=="password")||elName=="textarea")
			{
				if (vals[valName]) el.value = vals[valName][0]||'';
			}
			else if (elName=="input"&&(type=="checkbox"||type=="radio")||elName=="option")
			{
				if (elName=="option") valName = el.parentNode.name;
				var val = el.value;
				var checked = false;
				if (vals[valName])
					for (var i=0;i<vals[valName].length;++i)
						if (vals[valName][i]==val)
							checked = true;
				el.checked=el.selected=checked;
			}
			else return true;
		});
}
function AddFormVals(all, vals)
{
	var ret = {};
	for (var i in vals)
	{
		ret[i] = all[i];
		if (vals[i])
			all[i]=vals[i];
		else
			delete all[i];
	}
	return ret;
}
function AddFormVal(all, valName, val)
{
	if (!all[valName]) all[valName] = [val];
	else arraypush(all[valName], val);
}
function IsSameValue(v1, v2)
{
	// All values are arrays or nulls
	if ((v1&&v1.length)!=(v2&&v2.length)) return false;
	
	// But order is not important. The presence of the same number of
	// each value is, so we build a count dictionary.
	var v1o = {};
	var v2o = {};
	for (var i=0;i<v1.length;++i)
	{
		v1o[v1[i]]=(v1o[v1[i]]||0)+1;
		v2o[v2[i]]=(v2o[v2[i]]||0)+1;
	}
	// We've already confirmed a length match, so this loop is sufficient.
	for (var i in v1o)
		if (v1o[i]!=v2o[i]) return false;
	return true;
}
function DiffFromFormVals(all, lastvals)
{
	var diff={};
	for (var i in all)
		if (!lastvals[i]||!IsSameValue(lastvals[i],all[i]))
			diff[i]=all[i];
	return diff;
}
// TODO: Get rid of this hack and handle date/time fields properly.
var interesting={m:1,y:1,d:1,h:1};
function AddDateTimeFields(all, other)
{
	for (var i in all)
	{
		var c = i.charAt(i.length-1);
		if (interesting[c])
		{
			c = i.substr(0,i.length-1);
			for (var j in interesting)
				if (other[c+j])
					all[c+j] = other[c+j];
		}
	}
}
function ValsAsPostData(all)
{
	var post = '';
	for (var i in all)
		post+="&"+encodeURIComponent(i)+"="+encodeURIComponent(all[i]);
	return post.substr(1);
}


// Gets all form values from the page fragment.
function GetValues(str, vals)
{
	vals=vals||{};
	var lastsel;
	var tagre = /<(\w+)((?:[^\/>'"]+"[^"]*"|[^\/>'"]+'[^']*')*)[^\/>'"]*\/?>(?:([^<]*)<\/\1>)?/g;
	var m;
	while (m = tagre.exec(str))
	{
		var attr = {};
		var attrre = /\s*(\w+)=(?:"([^"]*)"|'([^']*)')/g;
		var am;
		while (am = attrre.exec(m[2]))
			attr[am[1]] = am[2]||am[3]||'';
		
		if (m[1]=='select')
		{
			lastsel = unescapeHTML(attr.name);
			continue;
		}
		else if (m[1]=='textarea')
		{
			AddFormVal(vals, unescapeHTML(attr.name), unescapeHTML(m[3]));
			continue;
		}
		
		if (m[1]=='option')
		{
			if (m[2].indexOf("selected=")!=-1)
				AddFormVal(vals, lastsel, unescapeHTML(attr.value));
		}
		else if (attr.name&&(
		attr.type=='secret'||
				attr.type=='text'||
				attr.type=='hidden'||
				attr.checked&&
					(attr.type=='radio'||
					attr.type=='checkbox')))
		{
			AddFormVal(vals, unescapeHTML(attr.name), unescapeHTML(attr.value||''));
		}
	}
}

var knownchars = {'amp':'&', 'lt':'<', 'gt':'>', 'apos':"'", 'quot':'"'};
function unescapeHTML(html)
{
	return html.replace(/&(?:#x([0-9a-fA-F]+)|#(\d+)|(\w+));?/g,
		function(m, g1, g2, g3, pos)
		{
			if (g3) return knownchars[g3]||'';
			return String.fromCharCode(parseInt(g1||g2,g2?10:16));
		});
}

// We'll rewrite all the link destinations and hope nothing depends
// on the values being unaltered. (There's no other way to override
// only the default action for following a link.)
function InstrumentLinks(content)
{
	var hrefre = /<a ([^>]*)href=(['"])([\s\S]*?)\2([^>]*)/g;
	var m;
	while (m = hrefre.exec(content))
		if (!/\btarget=['"](?!_self|_parent).*?['"]/i.exec(' '+m[1]+' '+m[4]))
			content = content.substr(0,m.index)+"<a "+m[1]+"href="+m[2]+"javascript:LinkHit("+(m[2]=='"'?"'":'"')+m[3].replace(/([\\'"])/g,"\\$1")+(m[2]=='"'?"'":'"')+")"+m[2]+content.substr(m.index+m[0].length);
	return content;
}

// Imports an (assumedly balanced) html fragment.
var namere=/<([a-z]+)/i;
function importHTML(html)
{
	var depth = 0;
	var name = html.match(namere);
	name = name&&name[1]&&name[1].toLowerCase();
	
	// IE has real trouble with td, tr, tbody and option, so we need to
	// surround them with appropriate elements. To avoid arbitrarily
	// losing script and link elements, we surround everything else
	// with table-tbody-tr-td. There are probably simpler elements to
	// use, but div didn't work and table is the first one that did.
	// Other browsers might behave better, but they don't mind these
	// hacks.
	switch (name)
	{
	default:
		html = ++depth&&"<td>"+html+"</td>"
	case "td":
		html = ++depth&&"<tr>"+html+"</tr>";
	case "tr":
		html = ++depth&&"<tbody>"+html+"</tbody>";
	case "tbody":
	case "thead":
	case "tfoot":
		html = ++depth&&"<table>"+html+"</table>";
		break;
	case "option":
		html = ++depth&&"<select>"+html+"</select>";
		break;
	}
	var node = document.createElement("div");
	node.innerHTML = html;
	for (;depth;--depth) node = node.firstChild;
	return ToArray(node.childNodes);
}

function ToArray(arr)
{
	var ret = [];
	var len = arr.length;
	for (var i=0;i<len;++i)
		ret[i]=arr[i];
	return ret;
}

// Runs fn for el. If it returns true, recurses for every subelement.
// Typically, fn will both do something with nodes and decide whether
// or not the subelements might be interesting.
function forEveryNodeMatching(el, fn)
{
	if (fn(el))
	{
		var ch = el.firstChild;
		for (;ch;ch=next)
		{
			var next = ch.nextSibling;
			forEveryNodeMatching(ch, fn);
		}
	}
}

function forEveryNode(el, fn)
{
	fn(el);
	var ch = el.firstChild;
	for (;ch;ch=next)
	{
		var next = ch.nextSibling;
		forEveryNode(ch, fn);
	}
}

// Regex for URLs taken from RFC 2396 (some brackets removed)
var urlre = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?$/;
//            1           2           3       4         5
//            scheme      authority  path     query     fragment
function SplitURL(surl)
{
	surl=(surl||'')+'';
	var url = surl.match(urlre);
	return {_scheme:url[1], _authority:url[2], _path:url[3], _query:url[4], _fragment:url[5], _orig:surl};
}
function JoinURL(url)
{
	return (url._scheme||"")+(url._authority||"")+(url._path||"")+(url._query||"")+(url._fragment||"");
}

var singledots = /(?:^|\/)\.(\/|$)/g;
var doubledot = /(?:^|\/)(?:.*\/)?\.\.(\/|$)/;
function CombineURLs(base, other)
{
	base = SplitURL(base);
	other = SplitURL(other);
	if (!other._scheme&&!other._authority&&!other._path&&!other._query&&!other._fragment)
		return JoinURL(base);
	if (other._scheme)
		return JoinURL(other);
	other._scheme=base._scheme;
	if (!other._authority)
	{
		other._authority=base._authority;
		if ((other._path||"").substr(0,1)!='/')
		{
			var newpath = base._path||'';
			var slpos = newpath.lastIndexOf('/');
			if (slpos!=-1) newpath = newpath.substr(0,slpos+1);
			newpath+=(other._path||'');
			do
			{
				var pathlen = newpath.length;
				newpath = newpath.replace(singledots, '$1');
			} while (pathlen>newpath.length);
			do
			{
				var pathlen = newpath.length;
				newpath = newpath.replace(doubledot, '$1');
			} while (pathlen>newpath.length);
			other._path=newpath;
		}
	}
	return JoinURL(other);
}

// Turns any URL into either an AJAT-usable one or null.
// (If it returns no .rel, it's not an AJAT URL.)
var FJre = /^(.*?)\/FJ\/#?(\d*)(\/.*)$/;
var absurlre = /[\w]+:/;
function getAJAXURL(url)
{
	var loc = window._fsurl?_fsurl+"#"+_fsurlhash:location+"";
	var m = FJre.exec(loc);
	
	if (!m) return null;
	loc = m[1]+m[3];
	
	if (!url) return {abs:loc, rel:m[3], num:+m[2], preFJ:m[1]};
	
	url = url.replace(/(#.*)?\s*$/g,"");
	
	var abs = CombineURLs(loc, url);
	
	var ret = {num:+m[2], abs:abs, preFJ:m[1]};
	if (ret.abs.substr(0,m[1].length+1)==m[1]+'/') ret.rel = ret.abs.substr(m[1].length);
	return ret;
}

// Turns objects into expressions that generate something like them.
// Objects that expose _serialise will get that called.
_serialise=function(o)
{
	if (typeof(o)=='number'||o==null) return ""+o;
	if (o._serialise) return o._serialise();
	if (typeof(o)=='string') return "'"+o.split("\\").join("\\\\").split("'").join("\\'").split("\r").join("\\r").split("\n").join("\\n")+"'";
	if (o.constructor==Array)
	{
		var ret = "[";
		for (var i=0;i<o.length;++i)
			ret+=(i>0?', ':'')+(o[i]===void 0?'':_serialise(o[i]));
		return ret+"]";
	}
	var ret = "{";
	for (var i in o)
	{
		ret+=(ret=="{"?'':', ')+_serialise(i)+":"+_serialise(o[i]);
	}
	return ret+"}";
}
















if (!window._ajaxmode) _ajaxmode = 'OnePageNoBack';




// Textareas get auto-sized to match their content
OnEveryLoad(function FixTextareas(force)
{
	// For ~15 textareas, IE takes 3s to run this, which is way too long. Until we have a faster solution, IE doesn't get this feature.
	if (_isIE) return;
	var t = GetTimer("FixTextareas");
	jQuery('textarea').each(function() {
        var ta = this;
        var $ta = jQuery(ta);
        if ($ta.data('fs2fixta')) return;
        $ta.data('fs2fixta', 1);

        ta.style.overflow = 'hidden';
        if ((ta.clientHeight && ta.rows) || ta.style.height != 0) {
            // Don't go smaller than the initial height.            
            var minHeight = ta.clientHeight;

            // Compensate for any issues in the first getting/setting of the height
            $ta.height($ta.height());

            // Then, calculate the difference between our two height measures for later height changes
            var diff = $ta.height() - ta.clientHeight;

            var size = function() {
                ta.scrollHeight; // IE bug compensation
                // We're aiming to have the clientHeight always one less than the scrollHeight (for easy detection of scrollheight changes), give or take the minimum height
                if (ta.clientHeight >= minHeight && (ta.scrollHeight == ta.clientHeight + 1 || ta.scrollHeight <= ta.clientHeight)) return;

                while (ta.scrollHeight == ta.clientHeight) {
                    $ta.height($ta.height() - 10);
                    ta.scrollHeight; // IE bug compensation (though IE shouldn't really hit this line, as it doesn't force the minimum scrollHeight to be the clientHeight)
                }

                $ta.height(Math.max(ta.scrollHeight - 1, minHeight) + diff);
            }
            $ta.keyup(size).keydown(size).change(size);
            ta.onpropertychange = size;
            ta.onpropertychange();
        } else {
            if (ta.rows) {
                try {
                    //Code run calculate of parent window
                    function getParentWindow(windowParent) {
                        return windowParent.parent;
                    }
                    var frameElementW = window;                    
                    while (frameElementW.frameElement) {
                        frameElementW = getParentWindow(frameElementW);
                    }
                    var elementClone = jQuery(this).clone();
                    var idElement = "temporary_clone_element" + elementClone[0].id;
                    elementClone[0].id = idElement;
                    elementClone[0].style.position = "absolute"
                    elementClone[0].style.top = "-1000px";
                    elementClone[0].style.left = "-1000px";
                    elementClone.appendTo(frameElementW.document.body);

                    var clientHeight = elementClone[0].clientHeight;
                    var scrollHeight = elementClone[0].scrollHeight;
                    // Compensate for any issues in the first getting/setting of the height
                    elementClone.height(elementClone.height());

                    // Then, calculate the difference between our two height measures for later height changes
                    var diff = elementClone.height() - clientHeight;
                    elementClone.remove();
                    
                    // Don't go smaller than the initial height.
                    var minHeight = clientHeight;
                    var size = function() {
                        ta.scrollHeight; // IE bug compensation
                        // We're aiming to have the clientHeight always one less than the scrollHeight (for easy detection of scrollheight changes), give or take the minimum height
                        if (ta.clientHeight >= minHeight && (ta.scrollHeight == ta.clientHeight + 1 || ta.scrollHeight <= ta.clientHeight)) return;

                        while (ta.scrollHeight == ta.clientHeight && ta.clientHeight>0) {
                            $ta.height($ta.height() - 10);
                            ta.scrollHeight; // IE bug compensation (though IE shouldn't really hit this line, as it doesn't force the minimum scrollHeight to be the clientHeight)
                        }
                       $ta.height(Math.max(ta.scrollHeight - 1, minHeight) + diff);
                    }
                    $ta.keyup(size).keydown(size).change(size);
                    ta.onpropertychange = size;
                    ta.onpropertychange();
                }
                catch (err) {}
            }

        }
    });
	t.end();
});

OnEveryLoad(function ReworkTargetTopEls(obj) {
	var t = GetTimer("ReworkTargetTopEls");
    var context;
    
    if (obj) {
        try {
            context = obj.ownerDocument.body;
	    } catch (err) {
	        return;
	    }

	    if (context.childNodes.length > 0) {
	        jQuery(".idTargetTopAction * a", context).each(function() {
	            var regex = /^(.+\$c\(getel\('[^']+'\))(\))$/;

	            if (regex.test(jQuery(this).attr("href"))) {
	                jQuery(this).attr("href", jQuery(this).attr("href").replace(regex, "$1, true$2"));
	            }
	        });

	        jQuery("input.idTargetTopAction", context).each(function() {
	            jQuery(this).click(function() {
	                this.form.target = "_top";
	            });
	        });
	    }
    }
	t.end();
});

function OnEveryLoad(fn)
{
	OnLoad(fn);
	return OnAJAXDone(fn);
}

function OnAJAXDone(fn)
{
	return AddToObject(AJAXCallbacks.done, fn);
}

// Included for compatibility with some JS Giles wrote.
var AJAXDoneCallback=[];
AddToObject(AJAXCallbacks.done, function(){for (var i=0;i<AJAXDoneCallback.length;++i) AJAXDoneCallback[i]();});


var bigcover;
var bigcovertimer;
var BigCoverCount=0;
function OpenBigCover()
{
	if (BigCoverCount++) return;
	if (!bigcover)
	{
		bigcover = document.createElement("div");
		bigcover.style.backgroundColor='black';
		bigcover.style.opacity='0.5';
		bigcover.style.MozOpacity='0.5';
		bigcover.style.filter='alpha(opacity=50)';
		bigcover.style.position='absolute';
		bigcover.style.left='0';
		bigcover.style.top='0';
		bigcover.style.zIndex='100';
		FixBigCover();
		document.body.insertBefore(bigcover,document.body.firstChild);
		//document.body.appendChild(bigcover);
	}
	bigcover.style.display='block';
	bigcovertimer = setInterval(FixBigCover,55);
}

function FixBigCover()
{
	var r = getScroll();
	var newheight = Math.max(Math.max(r._h,document.body.offsetHeight),r._ch)+"px";
	var newwidth = Math.max(Math.max(r._w,document.body.offsetWidth),r._cw)+"px";
	if (bigcover.style.height!=newheight)
		bigcover.style.height=newheight;
	if (bigcover.style.width!=newwidth)
		bigcover.style.width=newwidth;
}

function CloseBigCover()
{
	if (--BigCoverCount) return;
	clearInterval(bigcovertimer);
	bigcovertimer=null;
	bigcover.style.display='none';
}











// Pop-up menus
//
// Parameters:
//   * Object Identifier
//   * Menu Identifier
//   * When menu should appear
//     0 = on mouseover
//     1 = on left mouse click
//     2 = on right mouse click
//   * posx (0=0,1=1,h=0.5)
//     resultant x-position=posx[0]*mousex + posx[1]*objleft + posx[2]*objwidth - posx[3]*menuwidth + posx[4]*scrollLeft + posx[5]*clientWidth
//   * posy (0=0,1=1,h=0.5)
//     resultant y-position=posy[0]*mousey + posy[1]*objtop + posy[2]*objheight - posy[3]*menuheight + posy[4]*scrollTop + posy[5]*clientHeight
//   * menuwidth
//     0 = as small as possible
//     1 = same width as object
//   * menuheight
//     0 = as small as possible
//     1 = same height as object
//
// Examples:
// "1~obj~menu~0~01~011~1" makes menu a menu for obj; the menu appears (on mouseover) below the object and matches the width of the object.
// "1~obj~menu~2~1~1" makes menu a menu for obj; the menu appears (on right-click) at the current mouse position and is as small as possible.
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("1~obj~menu~0~01~011~1")//--></script>

var intmap={'0':0,'1':1,'2':2,'h':0.5};
function parseInts(s)
{
	var ret=[];s=s||'';
	for (var i=0;i<s.length;++i)
		arraypush(ret,intmap[s.charAt(i)]||0);
	return ret;
}

// This wants to be as fast as possible, which involves touching as little
// of the DOM as possible. We add event handlers to the event-handling
// replacement structures, which are later checked when events happen.
// TODO: Check for speed issues in bookings, now that this has been replaced.
function setaction01(s)
{
	s=s.slice(1);
	s[2]=parseInt(s[2])||0;
	s._when = s[2];
	if (s[2]==0)
		return undo2(
			AddOverHandlerByName(s[0], JS01MouseOverOrClick, s),
			AddOutHandlerByName(s[0], JS01MouseOut, s));
	else
		return AddClickHandlerByName(s[0], JS01MouseOverOrClick, s);
}

function JS01Prepare(o)
{
	o._objn = o[0];
	o._mn = o[1];
	o._obj = getel(o[0]);
	o._m = getel(o[1]);
	o._cached = [];
	o._posx = parseInts(o[3]);
	o._posy = parseInts(o[4]);
	o._menuwidth = parseInt(o[5]);
	o._menuheight = parseInt(o[6]);
	o._fixedwidth = o[10];
	addobjref(o);
	o._m._id=o._id;
	o._obj._id=o._id;
}

function domenu(obj,m,when,posx,posy,menuwidth,menuheight,fixedwidth,prep)
{
	o={_objn:obj,_mn:m,_obj:getel(obj),_m:getel(m),_cached:[],_when:when,_posx:posx,_posy:posy,_menuwidth:menuwidth,_menuheight:menuheight,_fixedwidth:fixedwidth,_prepare:prep};
	addobjref(o);
	// o._mousex, o._mousey, o._istext are null and so don't need setting
	// o._captured starts off false, and so doesn't need setting
	if (o._when==0)
		undo=undo2(
			AddOverHandlerByName(obj, JS01MouseOverOrClick, o),
			AddOutHandlerByName(obj, JS01MouseOut, o));
	else
		var undo = AddClickHandlerByName(obj, JS01MouseOverOrClick, o); //TODO: Handle undo
	return {_menu:o, _undo:undo};
}

function JS01Restore(ev,o)
{
	EndCache(o._m);
	o._obj._attachedmenu=null;
	if (o._when!=0&&o._captured) endovercapture(ev);
	o._selshated=0;
	selsunhate(o._m);
	doEvent(ev,[o],/*{*/"_onmenuclose"/*{*/);
	o._vis=false;
}

function JS01FixMenu(o)
{
	if (!o._obj._attachedmenu) return;
	if (!o._fixedwidth&&o._m.offsetWidth>getScroll()._w*0.8&&o._m.tagName.toLowerCase()!="iframe")
		o._m.style.width="20em";
	JS01FixMenu2(o)
}

function JS01FixMenu2(o)
{
	if (!o._obj._attachedmenu) return;
	var r = getScroll();
	var x=(o._posx[0]||0)*(o._mousex||0) + (o._posx[1]||0)*objleft(o._obj) + (o._posx[2]||0)*o._obj.offsetWidth  - (o._posx[3]||0)*o._m.offsetWidth + (o._posx[4]||0)*r._x + (o._posx[5]||0)*r._cw;
	var y=(o._posy[0]||0)*(o._mousey||0) + (o._posy[1]||0)*objtop(o._obj)  + (o._posy[2]||0)*o._obj.offsetHeight - (o._posy[3]||0)*o._m.offsetHeight + (o._posy[4]||0)*r._y + (o._posy[5]||0)*r._ch;
	//TODO: Use istext for something useful
	objpos(o._m,x,y);
	if (!o._selshated)
	{
		o._selshated=1;
		selshate(o._m);
	}
}

function JS01MouseClick(ev)
{
	ev._nomenuaction=true;
}

function JS01EndCapture(o)
{
	JS01MouseOut(null,null,eventobjs[o._id]);
}

function JS01MouseOut(ev,evo,o)
{
	o._captured=false;
	if (o._obj && o._obj._attachedmenu)
		JS01Restore(ev,o);
}

function JS01MouseOverOrClick(ev,evo,o)
{
	if (o._vis) return false;
	if (ev._nomenuaction) return;
	for (var i=0;ev._els[i]!=o._obj;++i)
		if (ev._els[i].tagName=="TEXTAREA"||ev._els[i].tagName=="INPUT"||ev._els[i]._isRichEdit)
			o._istext=true;
	if ((o._when==2?1:0)!=((ev._button>1||ev._kctrl||ev._kalt||ev._kshift||ev._kmeta)?1:0)) return;
	if (!o._cached||o._prepare) {(o._prepare||JS01Prepare)(o);o._prepare=0;}
	if (o._when!=0&&!o._mclickdone)
	{
		//TODO: Handle undo
		AddClickHandlerByName(o._mn, JS01MouseClick, o);
		o._mclickdone = true;
	}
	//Calculate position into x and y and xs and ys (may need recalculating)
	//Compensate for istext (make sure no overlap)
	StartCache(o._m);
	setFakeParent(o._m,o._obj);
	o._obj._attachedmenu=1;
	if (o._when!=0) {overcapture(ev,o._obj,o._m,JS01EndCapture);o._captured=true;}
	doEvent(ev,[o],/*{*/"_onmenuopen"/*{*/);
	setstyle(o._m,'position','absolute');
	moveobj(o._m,-1000,-1000);
	o._m._id=o._id;
	setstyle(o._m,'visibility','visible');
	setstyle(o._m,'display','block');
	o._vis=true;
	o._mousex=ev._mx;
	o._mousey=ev._my;
	setTimeout(function(){if (o._nofix1) JS01FixMenu2(o); else JS01FixMenu(o);},1);
	return true;
}

function JS01ReplaceObject(o,newm,nofix1)
{
	if (!o._obj._attachedmenu)
	{
		o._m=newm;
		o._m._id=o._id;
		return;
	}
	EndCache(o._m);
	o._selshated=0;
	selsunhate(o._m);
	o._m=newm;
	o._m._id=o._id;
	StartCache(o._m);
	setFakeParent(o._m,o._obj);
	if (o._when!=0)
	{
		endovercapture(null,true);
		overcapture(null,o._obj,o._m,JS01EndCapture);
	}
	setstyle(o._m,'position','absolute');
	moveobj(o._m,-1000,-1000);
	setstyle(o._m,'visibility','visible');
	setstyle(o._m,'display','block');
	setTimeout(function(){JS01FixMenu2(o);},1);
}








var richeds;
var lastactive;
function JS01aAction(s)
{
	if (s[1]=="0")
	{
		// s[2] = attachedto   s[3] = mousebutton   s[4] = hiddenid   s[5] = submitid   s[6] = estimatedwidth   s[7] = estimatedheight
		return AddClickHandlerByName(s[2], JS01aMouseClick, s);
	}
	else
	{
		// s[2] = attachedto   s[3] = popupel   s[4] = x%   s[5] = y%   s[6] = pagex   s[7] = pagey   s[8] = submit
		var attachedto = getel(s[2])
		var el = getel(s[3]);
		var capt = false;
		var ie6scroll;
		setTimeout(function() {
		    var r = getScroll();
		    if (s[1] == "2") {
		        el.style.position = "fixed";
				OpenBigCover();
		        //center window
		        var heightWindow = el.clientHeight;
		        var widthWindow = el.clientWidth;
		        var x =(r._cw - widthWindow) / 2>0?(r._cw - widthWindow)/2: 1;
		        var y = (r._ch - heightWindow) / 2 > 0 ? (r._ch - heightWindow) / 2 : 1;
		        moveobj(el, x, y);
				if (_isIE6||1)
				{
					el.style.position="absolute";
					el.style.zIndex='999';
					ie6scroll = "0~~~SW~"+s[3]+"~"+x+"~"+y;
					$$(ie6scroll);
				}
		    } else {
		        if (objleft(el) + el.offsetWidth > r._x2 || objtop(el) + el.offsetHeight > r._y2)
		            objpos(el, parseFloat(s[4]) * attachedto.offsetWidth + objleft(attachedto), parseFloat(s[5]) * attachedto.offsetHeight + objtop(attachedto));
		    }
		    capt = true;
		    overcapture(null, el.parentNode || el.parentElement, el, closure(function() { capt = false; $c(getel(s[8])) }));
		    selshate(el);

		}, 200);
		return function()
		{
			if (s[1]=="2")
			{
				if (_isIE6||1) $$undo(ie6scroll);
				CloseBigCover();
			}
			if (capt) endovercapture(null,true);
		};
	}
}

function JS01aMouseClick(ev, evo, s)
{
	var x = (ev._mx-objleft(evo))/evo.offsetWidth;
	var y = (ev._my-objtop(evo))/evo.offsetHeight;
	
	if ((s[3]=='2'?1:0)!=((ev._button>1||ev._kctrl||ev._kalt||ev._kshift||ev._kmeta)?1:0)) return;
	
	var qq = document.createElement('div');
	qq.style.position='absolute';
	var pel = getel(s[5]);
	(pel.parentNode||pel.parentElement).appendChild(qq);
	pel = qq.offsetParent;
	(qq.parentNode||qq.parentElement).removeChild(qq);
	
	var w = s[6]||0;
	var h = s[7]||0;
	var r = getScroll();
	var mx = Math.min(ev._mx, r._x2-w);
	var my = Math.min(ev._my, r._y2-h);
	
	// IE likes to claim td and table are positioning/offset parents, even when they're clearly not.
	while (_isIE&&pel&&(pel.tagName=="TD"||pel.tagName=="TABLE")) pel=pel.offsetParent;
	getel(s[4]).value = x+"~"+y+"~"+(mx-objleft(pel))+"~"+(my-objtop(pel));
	$c(getel(s[5]));
	
	
	if (document.getElementById('wthreecolleft')) {
	    (document.getElementById('wthreecolleft').style.zIndex = 100).z;
	}
	
	return true;
}






















// Date controls
//
// Parameters:
//   * Outer object identifier
//   * Mandatoriness
//     Mandatory if present - non-mandatory otherwise
//   * Style
//     0 - Replace controls with a date-control link
//     1 - Adds a link after the last control with a date-control link
//     2 - Adds no way to bring up a menu (only performs error checking)
//   * Minimum date
//   * Maximum date
//   * ID of button to be pressed when date is selected
//   * Static text for the link
//
// Example:
// "2~myouter" Adds a date with myyear for year, mymonth=month and myday=day.
// "2~myouter~1" does the same as the last one, but makes it a mandatory date.
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("2~myouter")//--></script>

var monthnames;
var daynames=['M','Tu','W','T','F','Sa','Su'];

var JS02LastBlur;

function setaction02(s)
{
	var o={};
	addobjref(o);
	o._outer = getel(s[1]);
	
	// All this, just to guess which control is which.
	var a = o._outer.getElementsByTagName("SELECT");
	o._objd = o._objm = a[0];
	if (a[0].options.length<=13) o._objd = a[1];
	else o._objm = a[1];
	a = o._outer.getElementsByTagName("INPUT");
	o._outer._id = o._id;
	o._objy = a[0];
	if (o._objy.maxLength==2)
	{
		o._objy = a[1];
		o._objd = a[0];
	}
	else if (a.length==2)
		o._objd = a[1];

	o._m=s[2];
	o._datestyle=s[3]||0;
	o._mindate=s[4]?new Date(s[4]):null;
	o._maxdate=s[5]?new Date(s[5]):null;
	o._subname=s[6];
	o._linktext=s[7];
	o._lasterror=72;
	// TODO: The _getValue check means that if two bits of date JS reference the same controls, only one of them will have any effect. If that one is then removed, there's overall no effect, whereas really the other should then have the right effect.
	if (!o._objy||!o._objm||!o._objd||o._outer._getValue) return function(){};
	
	o._outer._getValue = JS02GetValue;
	o._outer._getText = JS02GetText;
	o._outer._focus = JS02FocusOuter;
	
	// o._restore starts off null and so doesn't need setting
	// o._lastdate starts off null and so doesn't need setting
	// o._laststartmonth starts off null

	// If we don't yet have the month names, and the month object is a
	// select box, we should grab the month names from that.
	if (!monthnames&&o._objm.tagName=="SELECT")
	{
		monthnames=[];
		for (var i=0;i<12;i++)
			monthnames[i]=o._objm.options[i+o._objm.options.length-12].text;
	}
	if (o._datestyle==0||o._datestyle==1)
	{
		o._txt=document.createElement("A");
		o._txt.href="date:picker";
		o._txt.id=s[1]+"Txt";
		if (o._datestyle==1) {o._txt.innerHTML="&#160;";o._txt.className="datecombo";o._txt.style.textDecoration="none";}
		if (o._objy.nextSibling)
			(o._objy.parentElement||o._objy.parentNode).insertBefore(o._txt,o._objy.nextSibling);
		else
			(o._objy.parentElement||o._objy.parentNode).appendChild(o._txt);
		if (o._datestyle==0)
		{
			setstyle(o._objy,'display','none');
			setstyle(o._objm,'display','none');
			setstyle(o._objd,'display','none');
			// TODO: Make this less destructive.
			o._objy.onfocus=o._objm.onfocus=o._objd.onfocus=JS02OnFocusField;
			o._objy.onblur=o._objm.onblur=o._objd.onblur=JS02OnBlurField;
			o._txt.onfocus=JS02OnFocus;
			o._txt.o=o._objy.o=o._objm.o=o._objd.o=o._id;
		}
		else
			o._txt.tabIndex=-1;
		var blah = domenu(o._txt.id,null,1,[1],[1],0,0,null,JS02CreateCal);
		o._menu=blah._menu;
		o._menu._cal = o;
		fsAddEvent(o._menu,/*{*/"_onmenuopen",JS02ShowingMenu,o._id);
	}
	JS02CheckDate(o);
	o._objd.onchange=o._objd.onpropertychange=o._objm.onchange=o._objm.onpropertychange=o._objy.onchange=o._objy.onpropertychange=JS02CheckDateEvent;
	o._objd._id=o._objm._id=o._objy._id=o._id;
	
	return function()
	{
		o._outer._getValue = null;
		o._outer._getText = null;
		o._outer._focus = null;
		// TODO: Make the following match the init, when the init is fixed.
		o._objy.onfocus=o._objm.onfocus=o._objd.onfocus=null;
		o._objy.onblur=o._objm.onblur=o._objd.onblur=null;
		if (blah) blah._undo();
		if (o._txt)
		{
			o._txt.onfocus=null;
			(o._txt.parentElement||o._txt.parentNode).removeChild(o._txt, true);
		}
	}
}

function JS02CreateCal(o)
{
	o=o._cal;
	o._cal=document.createElement('DIV');
	setstyle(o._cal,'position','absolute');
	setstyle(o._cal,'visibility','hidden');
	document.body.appendChild(o._cal);
	moveobj(o._cal,-1000,-1000);
	setstyle(o._cal,'zIndex',1000000);
	o._cal._thingsavoidme='yep';
	o._cal.id="cal"+Math.floor(Math.random()*1000000000);
	o._menu._m = o._cal;
	o._menu._mn = o._cal.id;
	//o._cal.dragme=true;
	//ddscrollobs[ddscrollobs.length]=o._cal;
	fsAddEvent(o._cal,/*{*/"_onmouseclick"/*}*/,JS02MouseClick,o._id);
}

function JS02ShowingMenu(ev,evo,o)
{
	JS02ShowDate(null,o);
}

function JS02SetError(obj,err)
{
	if (err) addclass(obj,"dateerror"); else remclass(obj,"dateerror");
	obj.title=err;
}

function JS02CheckDateEvent()
{
	if (this._id) JS02CheckDate(eventobjs[this._id]);
}

function JS02FixYear(yval)
{
	if (yval==''||yval==null) yval=new Date().getFullYear();
	if (yval<100)
	{
		var cutoffyear=new Date().getFullYear()+30;
		yval=cutoffyear-((cutoffyear-yval)%100);
	}
	return yval;
}

function JS02CheckDate(o)
{
	var lastlast = o._lastdate;
	o._lastdate=null;
	var err="";
	if (o._objd.value) 
	{
		var yval = JS02FixYear(o._objy.value);
		var d=new Date(yval,o._objm.value-1,o._objd.value);
		if (isNaN(d-0)) err="Date is invalid";
		else if (d.getDate()!=o._objd.value) err="Day is out of range for month";
		else
		{
			if (o._mindate&&d<o._mindate) err="Day is before minimum allowed date";
			else if (o._maxdate&&d>o._maxdate) err="Day is after maximum allowed date";
			o._lastdate=d;
		}
	}
	if (err!=o._lasterror)
	{
		o._lasterror=err;
		JS02SetError(o._objd,err);
		JS02SetError(o._objm,err);
		JS02SetError(o._objy,err);
	}
	
	var s=o._linktext||(o._lastdate?o._lastdate.getDate()+' '+monthnames[o._lastdate.getMonth()]+' '+o._lastdate.getFullYear():'Select a date');
	if (o._datestyle==0)
	{
		while (o._txt.firstChild) o._txt.removeChild(o._txt.firstChild);
		o._txt.appendChild(document.createTextNode(s));
	}

    if (lastlast&&+lastlast!=+o._lastdate&&o._subname && o._subname != "#form#")
		$c(o._subname);
	
	return s;
}

function JS02GetText()
{
	return JS02CheckDate(eventobjs[this._id]);
}

function JS02GetValue()
{
	var o = eventobjs[this._id];
	JS02CheckDate(o);
	return o._lastdate.getFullYear()+"-"+(o._lastdate.getMonth()<9?"0":"")+(o._lastdate.getMonth()+1)+"-"+(o._lastdate.getDate()<10?"0":"")+o._lastdate.getDate();
}

function JS02SetDate(d,ev,o)
{
	if (o._mindate&&d&&d<o._mindate) d=o._mindate;
	else if (o._maxdate&&d&&d>o._maxdate) d=o._maxdate;
	o._objy.value=d?d.getFullYear():'';
	o._objm.selectedIndex=d?d.getMonth()+o._objm.options.length-12:0;
	if (o._objd.selectedIndex!=null)
		o._objd.selectedIndex=d?d.getDate()+o._objd.options.length-32:0;
	else
		o._objd.value=d?d.getDate():'';
	JS02CheckDate(o);
    if (o._subname && o._subname == "#form#")
    {
	    var els = jQuery(o._outer).parents("form");
	    
	    if (els.length > 0)
	    {
	        $c(jQuery(o._outer).parents("form")[0]);
	    }
	}
	JS01Restore(ev,o._menu);
	jQuery(o._objy).change();
	jQuery(o._objm).change();
	jQuery(o._objd).change();
}

function JS02MouseClick(ev,evo,o)
{
	var d=new Date(o._laststartmonth);
	var set=ge(ev._els[0],'set');
	var show=ge(ev._els[0],'show');
	if (set)
	{
		if (set=='t') JS02SetDate(new Date(),ev,o);
		else if (set=='n') JS02SetDate(null,ev,o);
		else {d.setDate(set);JS02SetDate(d,ev,o);}
	}
	else if (show)
	{
		d.setDate(show);
		JS02ShowDate(d,o);
	}
	return true;
}

function JS02ShowDate(d,o)
{
	var cl='cal';
	var cledge=' class="'+cl+' '+cl+'edge"';
	var clwday=' class="'+cl+' '+cl+'wday"';
	var cldate=' class="'+cl+' '+cl+'date';
	var clother=cldate+' '+cl+'dateother"';
	var clerror=cldate+' '+cl+'error"';
	var cldatesel=cldate+' '+cl+'datesel"';
	cldate+='"';
	cl=' class="'+cl+'"';
	if (document.compatMode=="BackCompat")
	{
		cledge+=" style='font-size:0.7em'";
		clwday+=" style='font-size:0.7em'";
		cldate+=" style='font-size:0.7em'";
		clother+=" style='font-size:0.7em'";
		clerror+=" style='font-size:0.7em'";
	}
	
	if (!d)
	{
		var dtdate=o._objd.value;
		var dtmonth=o._objm.value;
		var dtyear=JS02FixYear(o._objy.value);
		var newdate=new Date();
		var invaliddate=!dtyear||!dtmonth||!dtdate;
		dtdate=dtdate||newdate.getDate();
		dtmonth=dtmonth||(newdate.getMonth()+1);
		dtyear=dtyear||newdate.getFullYear();
		d=new Date(dtyear,dtmonth-1);
		if (isNaN(d-0)) {d=new Date(newdate.getFullYear(),newdate.getMonth());invaliddate=true;}
		if (o._mindate&&d<o._mindate) d=new Date(o._mindate);
		else if (o._maxdate&&d>o._maxdate) d=new Date(o._maxdate);
	}
	d.setDate(1);
	o._laststartmonth=d;
	
	var days20=1000*60*60*24*20;
	var days365=days20*365/20;
	var s="";
	// Clicking "Today" does setdate(new Date())
	// Clicking < for month does showdate(new Date(laststartmonth-days5))
	// Clicking > for month does showdate(new Date(laststartmonth+days35))
	// Clicking "None" does setdate()
	s+='<table cellpadding=0 cellspacing=0 border=0 class="cal"><tr>';
	s+='<td'+cledge+'>&#160;</td>';
	s+='<td'+cledge+'><table style="width:100%" cellpadding=0 cellspacing=0 border=0><tr><td width=1'+cledge+'><a href="" set="t">Today</a></td>';
	s+='<td'+cledge+' align="center"><A href="" show="-20">&lt;</A> <SPAN>'+monthnames[d.getMonth()]+'</SPAN> <A href="" show="40">&gt;</A></td>';
	if (!o._m)
		s+='<td width=1'+cledge+'><a href="" set="n">None</a></td>';
	else
		s+='<td width=1'+cledge+'>&#160;&#160;&#160;&#160;</td>';
	s+='</tr></table></td><td'+cledge+'>&#160;</td>';
	s+='</tr><tr>';
	s+='<td'+cledge+'>&#160;</td>';
	s+='<td'+cl+'>';

	var dmonth=d.getMonth();
	s+='<table cellpadding=2 cellspacing=0 border=0><tr>';
	for (var i=0;i<7;i++)
		s+='<td'+clwday+'>'+daynames[i]+'</td>'
	var dow=d.getDay();
	dow=(dow+6)%7;
	d=new Date(d);
	var dyear=d.getFullYear();
	d.setDate(1-dow);
	var minday=1-dow;

	for (var i=0;i<6;i++)
	{
		if(!(d.getMonth()!=dmonth && (i==4 || i==5)))
		{
			s+='<tr>';
			for (var j=0;j<7;j++)
			{
				if ((o._mindate&&d<o._mindate)||(o._maxdate&&d>o._maxdate))
					s+='<td'+clerror+'><a href="">'+d.getDate()+'</a></td>';
				else
					s+='<td'+(d.getMonth()!=dmonth?clother:(d-0==o._lastdate-0&&!invaliddate)?cldatesel:cldate)+'><a href="" set="'+(minday+i*7+j)+'">'+d.getDate()+'</a></td>';
				
				d.setDate(d.getDate()+1);
			}
			s+='</tr>';
		}
	}
	s+='</table>';
	s+='</td>';
	s+='<td'+cledge+'>&#160;</td></tr>'
	s+='<tr><td'+cledge+'>&#160;</td>';
	s+='<td'+cledge+' align="center"><A href="" show="-3637">&lt;&lt;</A> <A href="" show="-350">&lt;</A> <SPAN>'+dyear+'</SPAN> <A href="" show="370">&gt;</A> <A href="" show="3667">&gt;&gt;</A></td>';
	s+='<td'+cledge+'>&#160;</td>';
	s+='</tr></table>';
	// This is delayed because altering the innerHTML while the user's clicking the next/prev month/year buttons was otherwise causing a page refresh (probably following of a null link) in Chrome. Don't ask me why. The event was cancelled, either way...
	setTimeout(function(){o._cal.innerHTML=s;},1);
}

function JS02OnFocus(ev)
{
	// We're not interested in focus-changes caused by mouse-clicks
	if (lastmousestate._button) return;
	ev=myevent(ev);
	JS02Focus(ev._el.o,ev._kshift);
}

function JS02OnBlurField(ev)
{
	ev=myevent(ev);
	if (JS02LastBlur&&JS02LastBlur!=ev._el.o)
		JS02Hide(JS02LastBlur);
	JS02LastBlur=ev._el.o;
	setTimeout("JS02OnBlurField2("+ev._el.o+")",1);
}

function JS02OnFocusField(ev)
{
	ev=myevent(ev);
	if (JS02LastBlur&&JS02LastBlur!=ev._el.o)
		JS02Hide(JS02LastBlur);
	JS02LastBlur=null;
}

function JS02OnBlurField2(ev)
{
	if (JS02LastBlur)
		JS02Hide(JS02LastBlur);
	JS02LastBlur=null;
}

function JS02FocusOuter(backwards)
{
	JS02Focus(this._id, backwards);
}

function JS02Focus(id, backwards)
{
	var o = eventobjs[id];
	setstyle(o._objy,'display','inline');
	setstyle(o._objm,'display','inline');
	setstyle(o._objd,'display','inline');
	if (backwards)
	{
		o._objy.focus();
		o._objy.select();
	}
	else
		o._objd.focus();
	setstyle(o._txt,'display','none');
}

function JS02Hide(oid)
{
	var o=eventobjs[oid];
	setstyle(o._objy,'display','none');
	setstyle(o._objm,'display','none');
	setstyle(o._objd,'display','none');
	setstyle(o._txt,'display','inline');
}














// Showing/hiding
// Hides the object when the control does not have the given value.
//
// Parameters:
//   * Control name/id
//   * Values
//   * Object name/id
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("3~mycontrol~myvalue~myidofthingtohide")//--></script>
//  or with more than one value
//   <script type="text/javascript"><!--
//   $$("3~mycontrol~myvalue1,myvalue2,myvalue3~myidofthingtohide")//--></script>

(function()
{

    function ShowObjectOnControlVal(controlname, value, objectname)
    {
        var controls = {};
        var correctcount = 0;
        var hidden = true;
        var objects = {};

        var HasCorrectValue = function(el)
        {
			var valArray = value.split(",");
			var showElement = false;
			var val = el._currentlyHidden ? null : ControlValue(el);
			
			for(var i = 0;i<valArray.length;i++)
			{
				if(valArray[i]==val)
				{
					showElement=true;
					break;
				}
			}
            return showElement || (value == "" && !val);
        }

        var Update = function(el, hidden)
        {
            if (el.tagName == "TD")
            {
                //el.style.visibility = hidden ? "hidden" : "";
                if (el.id = "advmenu")
                {
                    el.style.display = hidden ? "none" : "block";
                }
                else
                {
                    el.style.visibility = hidden ? "hidden" : "";
                }
            }
            else
                el.style.display = hidden ? "none" : "";

            var elsa = [[el], el.getElementsByTagName("INPUT"), el.getElementsByTagName("SELECT")];
            for (var j = 0; j < elsa.length; ++j)
            {
                var els = elsa[j];
                for (var k = 0; k < els.length; ++k)
                    if (!els[k]._currentlyHidden != !hidden)
                {
                    //TODO: This needs to somehow handle new stuff getting added inside a hidden page-part.
                    els[k]._currentlyHidden = hidden;
                    Changed(els[k]);
                }
            }
            maybehidesels();
        }

        var Hide = function(el)
        {
            if (! --el._viscount)
                Update(el, true);
        }

        var Show = function(el)
        {
            if (!el._viscount++)
                Update(el, false);
        }

        var ShowOrHide = function()
        {
            if (hidden && correctcount > 0)
            {
                hidden = false;
                for (var i in objects)
                    Show(objects[i]);
            }
            else if ((!hidden) && correctcount == 0)
            {
                hidden = true;
                for (var i in objects)
                    Hide(objects[i]);
            }
        }

        var AddControl = function(el)
        {
            var hasval = HasCorrectValue(el);
            correctcount += hasval ? 1 : 0;
            var o = { el: el, correct: hasval };
            var id = Math.random() + 3 + "";
            controls[id] = o;
            ShowOrHide();

            o.undo = OnChange(el, function()
            {
                var newhasval = HasCorrectValue(el);
                if (newhasval == hasval) return;
                hasval = newhasval;
                correctcount += hasval ? 1 : -1;
                ShowOrHide();
            });

            return function()
            {
                delete controls[id];
                correctcount -= o.correct ? 1 : 0;
                o.undo();
                ShowOrHide();
            };
        }

        var AddObject = function(el)
        {
            el._3count = (el._3count || 0) + 1;
            el._viscount = (el._viscount || 0);
            if (!hidden)
            {
                el._viscount++;
                if (el._viscount == 1 && el._3count > 1) Update(el, false);
            }
            else if (el._3count == 1) Update(el, true);

            // TODO: Clean up
            jQuery(el).removeClass('hidefordom');

            var id = Math.random() + 3 + "";
            objects[id] = el;

            return function()
            {
                delete objects[id];

                el._3count--;
                if (!hidden)
                {
                    if (--el._viscount == 0 && el._3count > 0) Update(el, true);
                }
                else if (el._3count == 0) Update(el, false);
            }
        }

        // These will call RemoveObject and RemoveControl as necessary, so no other removal is necessary
        return undo2(AddNameWatch(controlname, AddControl), AddNameWatch(objectname, AddObject));
    }

    function Handler(s)
    {
        if (s[1])
            return ShowObjectOnControlVal(s[1], s[2], s[3]);
        else if (s[2] == 1)
        {
            var undos = [];
            var ctrl = s[3];
            for (var i = 4; i < s.length; ++i)
            {
                var obj = s[i];
                while (s[++i])
                    undos[undos.length] = ShowObjectOnControlVal(ctrl, s[i], obj);
            }
            return function() { for (var i = 0; i < undos.length; ++i) undos[i]() };
        }
    }

    _basefns['3'] = Handler;

})();















// Drag and drop
// Allows dragging of objects and dropping of them in specific places
//
// Parameters:
//   * draggroup
//   * control identifier for text (only required once for any draggroup)
//   * control identifier for submit (only required once for any draggroup)
//   Then, repeat the following three any number of times:
//   * Some subset of blort, for a target, or empty, for a draggable
//   * item identifier or control name
//   * identifier that's sent to the server (can be empty, if you want the same id as the control)

var dropgroups={};
var droptargetbar;
function JS04setaction(s)
{
	var dg=dropgroups[s[1]]=dropgroups[s[1]]||{};
	if (s[2]) dg._txtt=s[2];
	if (s[3]) dg._subt=s[3];
	for (var i=4;i<s.length;i+=4)
	{
		s[i+3]=s[i+3]||s[i+1];
		
		
		if (s[i])
		{ // Is a target
			var drop_ids = (s[i+1]||'').split(/,/g);
			for (var j=0;j<drop_ids.length;++j)
			{
				var el=getel(drop_ids[j]);
				if (!el) continue;
				
				el._droptarget=el._droptarget||{_t:{},_b:{},_l:{},_r:{},_o:{},_linked_drop_els:{}};
				if (s[i].indexOf('t')+1) el._droptarget._t[s[1]]=s[i+3];
				if (s[i].indexOf('r')+1) el._droptarget._r[s[1]]=s[i+3];
				if (s[i].indexOf('b')+1) el._droptarget._b[s[1]]=s[i+3];
				if (s[i].indexOf('l')+1) el._droptarget._l[s[1]]=s[i+3];
				if (s[i].indexOf('o')+1) el._droptarget._o[s[1]]=s[i+3];
				el._droptarget._linked_drop_els[s[1]]=drop_ids.join(",");
			}
			
		}
		else
		{ // Is draggable
			var el =  getel(s[i+1]);
			if (!el) continue;
			var de=getel(s[i+2]||s[i+1]);
			if (!el._dropgroups)
			{
				el._dropgroups={};
				var o={_det:s[i+1]};
				addobjref(o);
				fsAddEvent(de,/*{*/"_ondragstart"/*}*/,JS04OnDragStart,o._id);
			}
			el._dropgroups[s[1]]=s[i+3];
		}
	}
}

function JS04OnDragStart(ev,evo,o)
{
	if (!droptargetbar)
	{
		droptargetbar=document.createElement('DIV');
		droptargetbar.className="droptargetbar";
		document.body.appendChild(droptargetbar);
	}
	if (_cancelotherdrag) return;
	JS04DisallowSelect();
	var de = getel(o._det);
	StartCache(de);
	setstyle(de,'position','relative');
	o._hasSelect = de.getElementsByTagName("select").length>0;
	getScroll();
	_sizelocked=true;
	fsAddEvent(ev,/*{*/"_ondragmove"/*}*/,JS04OnDragMove,o._id);
	fsAddEvent(ev,/*{*/"_ondragdrop"/*}*/,JS04OnDragDrop,o._id);
	fsAddEvent(ev,/*{*/"_ondragcancel"/*}*/,JS04OnDragCancel,o._id);
	return true;
}

function JS04OnDragMove(ev,evo,o)
{
	o._mx = ev._mx;
	o._my = ev._my;
	o._omx = evo._mx;
	o._omy = evo._my;
	if (o._to) return true;
	ev=evo=null;
	o._to = setTimeout(function(){
		JS04OnDragInner(o);
	}, o._hasSelect?100:1);

	return true;
}

function JS04OnDragInner(o)
{
	setstyle(getel(o._det),'left','-1000em');
	setstyle(getel(o._det),'top','-1000em');
	
	if (navigator.product != "Gecko") return JS04OnDragMove2(o);
	setTimeout(function(){JS04OnDragMove2(o);},1);
	return true;
}

function JS04OnDragMove2(o)
{
	o._to = null;
	var de=getel(o._det);
	var x=o._mx;
	var y=o._my;
	var ox = o._omx;
	var oy = o._omy;
	var myo=o;
	
	var dbs=getScroll();
	var o=document.elementFromPoint(x-dbs._x,y-dbs._y);
	if (o==droptargetbar&&droptargetbar.lastwasover) {setstyle(droptargetbar,'visibility','hidden');o=document.elementFromPoint(x-dbs._x,y-dbs._y);}
	setstyle(de,'left',x-ox+"px");
	setstyle(de,'top',y-oy+"px");
	if (o==droptargetbar) return myo._dropped&&JS04OnDragDrop(null, null, myo)||true;

	var best={_score:-1, linked_drop_els:{}};
	while (o)
	{
		if (o._droptarget)
		{
			var $o=jQuery(o);
			var elx=$o.offset().left;
			var ely=$o.offset().top;
			x-=elx;y-=ely;
			var elw=$o.width();
			var elh=$o.height();
			var oo=o._droptarget;
			for (var i in de._dropgroups)
			{
				if (dropgroups[i]&&dropgroups[i]._txtt&&dropgroups[i]._subt&&getel(dropgroups[i]._txtt)&&getel(dropgroups[i]._subt))
				{
					var dei=de._dropgroups[i];
					best = JS04FieldStrength(best,x-elw/2,y,elw/2,elh,dei,"t",i,oo._t[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25),oo);
					best = JS04FieldStrength(best,x-elw/2,elh-y,elw/2,elh,dei,"b",i,oo._b[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25),oo);
					best = JS04FieldStrength(best,x,y-elh/2,elw,elh/2,dei,"l",i,oo._l[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25),oo);
					best = JS04FieldStrength(best,elw-x,y-elh/2,elw,elh/2,dei,"r",i,oo._r[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25),oo);
					best = JS04FieldStrength(best,x-elw/2,y-elh/2,elw/2,elh/2,dei,"o",i,oo._o[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25),oo);
					best._linked_drop_els = best._oo._linked_drop_els[i].split(/,/g);
				}
			}
			x+=elx;y+=ely;
		}
		o=o.parentElement;
	}
	
	if (best._score<0) {de.targ=null;setstyle(droptargetbar,'visibility','hidden');return myo._dropped&&JS04OnDragDrop(null, null, myo)||true;}
	
	
	if (best._linked_drop_els && best._linked_drop_els.length>1)
	{
		best._elx2 = best._elx + best._elw;
		best._ely2 = best._ely + best._elh;
		
		for (var j=0;j<best._linked_drop_els.length;++j)
		{
			var l = getel(best._linked_drop_els[j]);
			if (!l) continue;
			var $l = jQuery(l);
			best._elx = Math.min(best._elx, $l.offset().left);
			best._ely = Math.min(best._ely, $l.offset().top);
			best._elx2 = Math.max(best._elx2, $l.offset().left + $l.width());
			best._ely2 = Math.max(best._ely2, $l.offset().top + $l.height());
		}
		best._elh = best._ely2 - best._ely;
		best._elw = best._elx2 - best._elx;
	}
	
	de.targ=best;
	if (best._l=="o")
	{
		droptargetbar.style.width=best._elw+"px";
		droptargetbar.style.height=best._elh+"px";
		droptargetbar.style.filter="Alpha(enabled=1,opacity=50)";
		droptargetbar.lastwasover=true;
	}
	else
	{
		droptargetbar.style.width=droptargetbar.style.height=
		droptargetbar.style.filter="";
		droptargetbar.lastwasover=false;
		if (best._l=="t"||best._l=="b")
		{
			droptargetbar.style.width=best._elw+"px";
			if (best._l=="b") best._ely+=best._elh;
			else best._ely-=droptargetbar.offsetHeight;
		}
		else if (best._l=="l"||best._l=="r")
		{
			droptargetbar.style.height=best._elh+"px";
			if (best._l=="r") best._elx+=best._elw;
			else best._elx-=droptargetbar.offsetWidth;
		}
	}
	moveobj(droptargetbar,best._elx+1,best._ely+1);
	setstyle(droptargetbar,'visibility','visible');
	
	return myo._dropped&&JS04OnDragDrop(null, null, myo)||true;
}

_abs=Math.abs;
function JS04FieldStrength(best,x1,y1,x2,y2,dei,l,i,id,o,elx,ely,elw,elh,scale,oo)
{
	if (!id) return best;
	x1=_abs(x1);x2=_abs(x2);y1=_abs(y1);y2=_abs(y2);
	var fs = scale*(1-((y1/y2<x1/x2)?x1/x2:y1/y2));
	if (fs<=best._score) return best;
	return {_score:fs,_ret:id+"~"+dei+"~"+l,_dg:i,_l:l,_o:o,_elx:elx,_ely:ely,_elw:elw,_elh:elh,_x1:x1,_y1:y1,_x2:x2,_y2:y2,_oo:oo};
}

function JS04OnDragDrop(ev,evo,o)
{
	if (o._to) {clearTimeout(o._to);JS04OnDragInner(o);}
	o._dropped = false;
	var a=getel(o._det).targ;
	if (a)
	{
		var b=dropgroups[a._dg];
		getel(b._txtt).value=a._ret;
		// The bar wants to vanish when the whole set of current operations is done. Typically _after_ the resulting AJAXy request.
		JS04RestoreSelect();
		OnLoad(function(){setstyle(droptargetbar,'visibility','hidden');});
		return $c(b._subt);
	}
	return JS04OnDragCancel(ev,evo,o);
}

function JS04OnDragCancel(ev,evo,o)
{
	if (o._to) clearTimeout(o._to);
	setstyle(droptargetbar,'visibility','hidden');
	JS04RestoreSelect();
	EndCache(getel(o._det));
	return true;
}

function JS04DisallowSelect()
{
	jQuery(document.documentElement).css({'-moz-user-select':'none','-webkit-user-select':'none', 'user-select':'none'});
}

function JS04RestoreSelect()
{
	jQuery(document.documentElement).css({'-moz-user-select':'', '-webkit-user-select':'', 'user-select':''});
}








// Rich Edit
// Allows rich editing of specific objects
//
// Parameters:
//   * Rich Edit ID
//   * Control ID
//
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("5~myricheditbox~myhiddencontrol")//--></script>

var lastactive;
function JS05Action(s)
{
	if (!document.selection) return function(){}; // No richedit available
	
	var so = [{c:'Bold',s:'B'},{c:'Italic',s:'<I>I</I>'},{c:'Underline',s:'<U>U</U>'},{c:'InsertUnorderedList',s:'&#8226;'},{c:'InsertOrderedList',s:'1.'}];

	return AddNameWatch(s[1], function(o)
	{
		o.innerHTML='<div class=richeditinner contenteditable></div>';
		var inner=o.firstChild;
		addclass(o,'richeditouter');
		var dragrange;
		
		var JS05GetRich = function()
		{
			var z=document.selection.createRange();
			var o=z.parentElement();
			while (o&&o!=inner) o=o.parentElement;
			return o?z:null;
		}
		
		var srces = {};
		
		var SetValue=function(s)
		{
			for (var i in srces)
				if (srces[i].value!=s)
					srces[i].value = s;
			if (inner.innerHTML!=s) inner.innerHTML=s;
		};
		
		var blockchange = false;
		var JS05Timeout;
		var lastactive;
		var JS05DocSelChange = function()
		{
			if (JS05Timeout) JS05Timeout=clearTimeout(JS05Timeout)&&null;
			
			var z = JS05GetRich();
			if (!lastactive!=!z)
			{
				if (z)
				{
					setstyle(richeditbar,'left',objleft(o)+'px');
					if (!JS05NeedsScroll()) setstyle(richeditbar,'top',(objtop(o)-richeditbar.offsetHeight+1)+'px');
					//selshate(richeditbar);
				}
				else
				{
					setstyle(richeditbar,'left','-1000em');
					//selsunhate(richeditbar);
				}
				
				lastactive = z;
				
				if (!z) SetValue(inner.innerHTML);
			}
			if (!z) return;
			JS05Timeout=setTimeout(function(){SetValue(inner.innerHTML);JS05Timeout=null;},200);
			for (var i=0;i<so.length;i++)
			{
				so[i]._sub.className="richeditbutton"+(z.queryCommandState(so[i].c)?" richedithighlight":"");
			}
			blockchange = true;
			var l=iricheditblocks[z.queryCommandValue("FormatBlock")];
			block._el.selectedIndex=l;
			var l=iricheditfonts[z.queryCommandValue("FontName")];
			font._el.selectedIndex=l;
			var l=iricheditsizes[z.queryCommandValue("FontSize")||3];
			size._el.selectedIndex=l;
			blockchange = false;
			return true;
		}
		
		inner.onpaste=function()
		{
			var z = JS05GetRich();
			z.text = clipboardData.getData('Text');
			return event.returnValue=false;
		};
		inner.ondrop=function()
		{
			var r=document.body.createTextRange();
			r.moveToPoint(event.x,event.y);
			r.select();
			var z = JS05GetRich();
			if (z) z.text = event.dataTransfer.getData('Text');
			event.returnValue=false;
			
			if (!event.ctrlKey&&dragrange) dragrange.text='';
			dragrange=null;
		};
		inner.ondragstart=function()
		{
			dragrange=document.selection.createRange();
		};
		inner.ondragend=function()
		{
			dragrange=null;
		};
		
		
		var richeditbar = document.createElement("div");
		richeditbar.unselectable=true;
		richeditbar.className="richeditbar";
		richeditbar.style.cssText="";
		
		richeditbar.appendChild(document.createTextNode("Style: "));
		for (var i=0;i<so.length;++i)
		{
			so[i]._sub = document.createElement("button");
			so[i]._sub.unselectable=true;
			so[i]._sub.tabIndex=-1;
			so[i]._sub.title=so[i].c;
			so[i]._sub.innerHTML="<b>"+so[i].s+"</b>";
			so[i]._sub.className="richeditbutton";
			so[i]._undo=fsAddEvent(so[i]._sub, /*{*/"_onmouseclick"/*}*/, function(ev, evo){
				var z = JS05GetRich();
				if (z) z.execCommand(evo.title);
				setTimeout(JS05DocSelChange,1);
				return false;
			});
			richeditbar.appendChild(so[i]._sub);
		}
		
		var JS05Sel = function(a, cmd, parent)
		{
			var el = document.createElement("select");
			for (var i=0;i<a.length;i++)
			{
				var op = document.createElement("option");
				op.innerText=a[i];
				op.value=a[i];
				el.appendChild(op);
			}
			el.unselectable = true;
			el.tabIndex = "-1";
			//el.selectedIndex = 0; // Otherwise defaults to -1, which is invalid...
			parent.appendChild(el);
			return {_el:el, _undo:OnChange(el, function(){
				if (blockchange) return;
				var z = JS05GetRich();
				if (!z) return;
				z.execCommand(cmd, false, ControlValue(el));
				setTimeout(JS05DocSelChange,1);
			})};
		}
		
		var block = JS05Sel(richeditblocks, "FormatBlock", richeditbar);
		richeditbar.appendChild(document.createTextNode("  Font: "));
		var font = JS05Sel(richeditfonts, "FontName", richeditbar);
		var size = JS05Sel(richeditsizes, "FontSize", richeditbar);
		
		if(JS05NeedsScroll())
		{
			var scrollClosure = function() {
				richeditbar.style.top = getScroll()._y+'px';
			}
			setInterval(scrollClosure, 55);
		}
		setstyle(richeditbar,'left','-1000em');
		
		document.body.appendChild(richeditbar);
		if (!JS05NeedsScroll()) setstyle(richeditbar,'top',(objtop(o)-richeditbar.offsetHeight+1)+'px');
		
		setFakeParent(richeditbar,inner);
		
		var scr = getScroll();
		var undo1 = fsAddEvent(scr._o,/*{*/"_onselchange"/*}*/,function(){setTimeout(JS05DocSelChange,1);});
		var undo2 = fsAddEvent(scr._o,/*{*/"_onkeypress"/*}*/,function(){setTimeout(JS05DocSelChange,1);});
		var undo3 = AddNameWatch(s[2], function(r)
		{
			SetValue(ControlValue(r));
			srces[r._nw] = r;
			var undo = OnChange(r, function()
			{
				SetValue(ControlValue(r));
			});
			return function()
			{
				delete srces[r._nw];
				undo();
			};
		});
		
		return function()
		{
			undo1();
			undo2();
			undo3();
			for (var i=0;i<so.length;++i)
				so[i]._undo();
			block._undo();
			font._undo();
			size._undo();
			richeditbar.removeNode(true);
		}
	});
}

//RichEdit stuff
//ForeColor, indent, outdent, createLink
//Other commands: fontsize, fontface, cut, copy, paste, justifyright, justifyleft, justifycenter, , InsertImage
function JS05Fill(a){var o={};for(var i=0;i<a.length;i++)o[a[i]]=i;return o;}
function JS05Option(a){var s="";for (var i=0;i<a.length;i++)s+='<option>'+a[i]+'</option>';return s;}
function JS05NeedsScroll(){return (window.VBArray&&!window.XMLHttpRequest)||document.compatMode=='BackCompat';}

var richeditblocks=["Normal","Formatted","Address","Heading 1","Heading 2","Heading 3","Heading 4","Heading 5","Heading 6"];
var iricheditblocks=JS05Fill(richeditblocks);
var richeditfonts=["Arial","Courier New","Times New Roman","Verdana"];
var iricheditfonts=JS05Fill(richeditfonts);
var richeditsizes=[1,2,3,4,5,6,7];
var iricheditsizes=JS05Fill(richeditsizes);


















// Column-resize
//
// Parameters:
//   * Object Identifier
//   * Textbox identifier
//   * Submit identifier
//
// Object must be a TABLE. TDs in first row must have names or ids. Must also have sizes in the same units as each other.
// On successful resize, textbox contains columnname1~columnname2~size1~size2 and submit is pressed.
//
// Examples:
// "6~obj~textbox~submit" enables column resizing for obj, using textbox and submit to give the results to the server.
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("6~obj~textbox~submit")//--></script>

var resizesetup=false;
function setaction06(s)
{
	if (!_isIE) return;
	var obj = getel(s[1]);
	var txt = getel(s[2]);
	var sub = getel(s[3]);
	if (!obj||!txt||!sub) return function(){};
	
	var l;
	var r;
	var lw;
	var rw;
	var diff;
	var lastlw;
	var lastrw;
	var rcss;
	var lcss;
	
	var lasthit=false;
	var lastcursor=null;
	var droplastcursor=function()
	{
		_cancelotherdrag=false;
		if (lasthit) {obj.style.cursor=lastcursor;lasthit=null;}
	}
	
	var JS06GetDetails = function(ev,evo)
	{
		var tro=evo;
		while (tro&&tro.tagName!="TR"&&tro.children)
			tro=tro.children[0];
		if (!tro||tro.tagName!="TR") return droplastcursor();
		
		var i=tro.children.length-1;
		l=tro.children[i];
		while (i-->0)
		{
			r=l;
			l=tro.children[i];
			var ex=objleft(r);
			if (ev._mx>ex-6&&ev._mx<ex+10) break;
		}
		if (i<0||!r.id&&!r.name||!l.id&&!l.name) return droplastcursor();
		
		var cursorelement=tro;
		lcss=JS06GetBits(l);
		rcss=JS06GetBits(r);
		if (rcss._u!=lcss._u) return droplastcursor();
		if (!lasthit)
		{
			lastcursor=obj.style.cursor;
			lasthit=true;
			obj.style.cursor="e-resize";
		}
		_cancelotherdrag=true;
		return true;
	}
	
	// JS06GetDetails sets up the cursor correctly.
	addEventMouseMove(obj,JS06GetDetails);
	
	fsAddEvent(obj,/*{*/"_ondragstart"/*}*/,function(ev, evo)
	{
		if (!JS06GetDetails(ev,evo)) return;
		lw=l.offsetWidth;
		rw=r.offsetWidth;
		
		fsAddEvent(ev,/*{*/"_ondragmove"/*}*/,function(ev, evo)
		{
			diff=ev._mx-evo._mx;
			if (diff>rw||diff<-lw) return l.runtimeStyle.width=r.runtimeStyle.width="";
			r.runtimeStyle.width=lastrw=Math.round(100*rcss._s*(rw-diff)/rw)/100+rcss._u;
			l.runtimeStyle.width=lastlw=Math.round(100*lcss._s*(lw+diff)/lw)/100+lcss._u;
		});
		fsAddEvent(ev,/*{*/"_ondragdrop"/*}*/,function(ev, evo)
		{
			if (diff<=rw&&diff>=-lw)
			{
				txt.value=(l.id||l.name)+"~"+(r.id||r.name)+"~"+lastlw+"~"+lastrw;
				return $c(sub);
			}
			return JS06OnDragCancel(ev,evo);
		});
		var JS06OnDragCancel = function(ev, evo)
		{
			droplastcursor();
			l.runtimeStyle.width="";
			r.runtimeStyle.width="";
			return true;
		};
		fsAddEvent(ev,/*{*/"_ondragcancel"/*}*/, JS06OnDragCancel);
		
		return true;
	});
}

function JS06GetBits(o)
{
	var m = /(\d*\.?\d*)(.*)/.exec((o.currentStyle.width||'')+'');
	return {_s:m[1]-0, _u:m[2]};
}

















// Pop-up menus using iframes
//
// Parameters for return:
//   * [empty]
//   * Return value (can include '~' characters)
//
// Parameters for pop-up:
//   * Object Identifier
//   * Menu Identifier
//   * When menu should appear
//     0 = on mouseover
//     1 = on left mouse click
//     2 = on right mouse click
//   * posx (0=0,1=1,h=0.5)
//     resultant x-position=posx[0]*mousex + posx[1]*objleft + posx[2]*objwidth - posx[3]*menuwidth + posx[4]*scrollLeft + posx[5]*clientWidth
//   * posy (0=0,1=1,h=0.5)
//     resultant y-position=posy[0]*mousey + posy[1]*objtop + posy[2]*objheight - posy[3]*menuheight + posy[4]*scrollTop + posy[5]*clientHeight
//   * menuwidth
//     0 = as small as possible
//     1 = same width as object
//   * menuheight
//     0 = as small as possible
//     1 = same height as object
//   * URL for content
//   * Object Identifier for failure
//   * ID of textbox field for result
//   * ID of submit button for result

var JS07Loading={};
var JS07CheckInterval;
var JS07Press;
function setaction07(s)
{
	if (s[1]==''&&window.iframeid!=null)
		return parent[/*{*/"JS07Return"/*}*/](iframeid,s.slice(1).join("~"));
	var o={};
	addobjref(o);
	var menuish = domenu(s[1],s[2],parseInt(s[3])||0,parseInts(s[4]),parseInts(s[5]),parseInt(s[6]),parseInt(s[7]),null,JS07Prepare);
	o._menu=menuish._menu;
	o._menu._7=o;
	o._width=s[6];
	o._height=s[7];
	o._url=s[8];
	o._class=s[12]||'iframepopup';
	o._errobj=getel(s[9]);
	o._rettxt=s[10];
	o._retsubname=s[11];
	o._prehtml=s[13]||'#C#';
	if (o._prehtml=="#C#") {o._prehtml='<div class="iframecloser"><div class="iframecloseinner"><a href="#" onclick="JS07CloseByID('+o._menu._id+');return false;">Close</a></div></div>';}
	var undo1=fsAddEvent(o._menu,/*{*/"_onmenuopen"/*}*/,JS07MenuOpen,o._id);
	var undo2=fsAddEvent(o._menu,/*{*/"_onmenuclose"/*}*/,JS07MenuClose,o._id);
	var ret = function(){undo1();undo2();menuish._undo();};
	ret._id = o._id;
	return ret;
}

function JS07Close(el)
{
	while (el&&el._id==null) el=el.parentElement;
	return JS07CloseByID(el._id);
}

function JS07CloseByID(id)
{
	var o = eventobjs[id];
	JS01EndCapture(o);
}

function JS07Prepare(o)
{
	o=o._7;
	o._iframe = document.createElement('iframe');
	o._iframe._id=o._id;
	o._iframe.allowTransparency=true;
	o._load=1;
	JS07Loading[o._id]=1;
	setTimeout("JS07OnTimeout("+o._id+")",5000);
	o._iframe.frameBorder=0;
	o._iframe.src=o._url;
	
	o._ifrwrap = document.createElement('div');
	o._ifrwrap._id=o._id;
	//o._ifrwrap.className = o._class;
	o._ifrwrap.style.zIndex=200;
	o._ifrwrap.innerHTML = o._prehtml;
	o._ifrwrap.style.position = 'absolute';
	o._ifrwrap.style.top = '-1000em';
	document.body.appendChild(o._ifrwrap);
	o._ifrwrap.appendChild(o._iframe);
	
	if (!JS07CheckInterval)
		JS07CheckInterval=setInterval("JS07Check()",55);
}

function JS07MenuOpen(ev,evo,o)
{
	if (o._7) return JS07MenuOpen(ev,evo,o._7);
	if (o._vis) return;
	o._vis=true;
	OpenBigCover();
}

function JS07Refresh()
{
	for (var i in JS07Loading)
	{
		try
		{
			eventobjs[i]._iframe.contentWindow.iframeid=i;
		}catch(e){}
	}
}

function JS07LoadComplete(oid)
{
	var o=eventobjs[oid];
	if (!o) return;
	if (o._load!=2)
	{
		o._load=2;
		
		JS01ReplaceObject(o._menu,o._ifrwrap,true);
	}
}

function JS07MenuClose(ev,evo,o)
{
	if (o._7) return JS07MenuClose(ev,evo,o._7);
	if (!o._vis) return;
	o._vis=false;
	//TODO: Cancel all captures and run mouseover out of inner document.body
	CloseBigCover();
}

function JS07OnTimeout(oid)
{
	var o=JS07Loading[oid];
	if (!o) return;
	if (o._vis)
	{
		delete JS07Loading[oid];
		JS01ReplaceObject(o._menu,o._errobj,false);
	}
}

function JS07SetSize(oid,w,h)
{
	var o=eventobjs[oid];
	var r=getScroll();
	if (o._width=='2')
		w = Math.max(w, h * r._w/r._h);
	if (o._height=='2')
		h = Math.max(h, w * r._h/r._w);
	w = Math.min(w, 0.9*r._cw);
	h = Math.min(h, 0.9*r._ch);
	o._iframe.style.width=w+"px";
	o._iframe.style.height=h+"px";
	if (o._iframe._propertycache)
	{
		o._iframe._propertycache._style.width=w+"px";
		o._iframe._propertycache._style.height=h+"px";
	}
	if (o._vis&&o._load==2)
		JS01FixMenu2(o._menu);
}

function JS07Return(oid,val)
{
	var o=eventobjs[oid];
	JS01EndCapture(o._menu);
	if (o._rettxt) getel(o._rettxt).value=val;
	if (o._retfn) return o._retfn(val);
	JS07Press=o._retsubname;
	return o._retsubname;
}

function JS07Check()
{
	if (JS07Press)
	{
		var e=getel(JS07Press);
		// TODO: Find out why e.click() doesn't submit the form
		// As it is, this code renames the submit and creates
		// a text field in its place, in order to make sure
		// the server thinks the button's been pressed.
		var f=e.form;
		e.name="rand"+Math.random();
		e = document.createElement("input");
		e.type = "text";
		e.name = JS07Press;
		e.value = "1";
		e.style.display = "none";
		e._fakeSubmit = true;
		f.appendChild(e);
		if (f.onsubmit) f.onsubmit();
		_bwsnavig=true;
		if (!window.SubmitPartial) f.submit();
		JS07Press=null;
	}
}






// Draggables
// $$("9~mynowdraggablething")
function JS09setaction(s)
{
	return AddNameWatch(s[1], function(de)
	{
		if (!de._dragenabled++)
		{
			de._dragenabled=1;
			de._undodragen = fsAddEvent(de,/*{*/"_ondragstart"/*}*/, function(ev)
			{
				if (_cancelotherdrag) return;
				getScroll();
				_sizelocked=true;
				fsAddEvent(ev,/*{*/"_ondragmove"/*}*/,function(ev, evo)
				{
					moveobj(de,x+ev._mx-evo._mx,y+ev._my-evo._my);
					return true;
				});
				fsAddEvent(ev,/*{*/"_ondragdrop"/*}*/,hidesels);
				fsAddEvent(ev,/*{*/"_ondragcancel"/*}*/,hidesels);
				var x=objleft(de);
				var y=objtop(de);
				return true;
			});
		}
		return function(){
		if (!--de._dragenabled)
			de._undodragen();
		};
	});
}



// Click-to-insert identifiers
// $$("a~thingwithtokensinit")
function setaction0a(s)
{
	if (!document.selection) return function(){};
	return AddNameWatch(s[1], function(el)
	{
		var txt=el.innerHTML+'';
		var newtxt=txt.split(/[,\s]+/);
		for (var i=0;i<newtxt.length;++i)
			newtxt[i]="<span unselectable=on>"+newtxt[i]+"</span>";
		el.innerHTML=newtxt.join(" ");
		var undo = fsAddEvent(el, "_onmouseclick", function(ev)
		{
			var z=document.selection.createRange();
			var oo=z.parentElement();
			if (oo.type!='text'&&oo.tagName!='TEXTAREA') return;
			z.text=ev._el.innerHTML;
			return true;
		});
		return function(){ undo(); el.innerHTML = txt; };
	});
}



// Set values on click
// $$("b~clickable~controls~valuetoset~setalltotrue")
function setaction0b(s)
{
	var name = s[2];
	var val = s[3];
	var alltrue = s[4];
	
	return AddNameWatch(s[1], function(el)
	{
		return fsAddEvent(el, "_onmouseclick", function()
		{
			var els = getels(name);
			for (var i in els)
			{
				var el=els[i];
				if (el.type=="checkbox"||el.type=="radio")
					el.checked=!!alltrue||val==el.value;
				else if (el.tagName.toLowercase()=="select")
					el.selectedIndex=val;
				else
					el.value=val;
			}
			return true;
		});
	});
}



// Sketch field
// $$("d~domnodeinwhichtoputit~hiddenfieldwithvalue")
// TODO: Make this and 03 use a new AddNamePairWatch.
function setaction0d(s)
{
	var src = getel("bwsjs").src.replace("0.js","WebSketch.dll");
	if (src.substring(0,5)!='http:'&&src.substring(0,6)!='https:')
	src='http:'+src;
	
	var val="";
	var controls={};
	var objects={};
	
	var SetVal = function(s)
	{
		if (val==s) return;
		val = s;
		for (var i in controls)
			if (controls[i].value!=val)
				controls[i].value=val;
		for (var i in objects)
			if (objects[i].picture!=val)
				objects[i].picture=val;
	};
	
	var undo1 = AddNameWatch(s[1], function(el)
	{
		var object = document.createElement('object');
		el.appendChild(object);
		object.classid=src+'#BWS.WebSketch';
		// Giving a dynamic size too early would make the control only appear on mouseover
		object.style.width=object.style.height="1px";
		var init = false;
		var check = function()
		{
			if (!init&&object.ready)
			{
				init=true;
				object.style.width=object.style.height="100%";
				if(val)object.picture = val;
				objects[el._nw] = object;
			}
			else if (init&&object.changed)
				SetVal(object.picture);
		};
		var interval = setInterval(check, 55);
		
		return function()
		{
			delete objects[el._nw];
			clearInterval(interval);
			object.parentNode.removeChild(object);
		}
	});
	
	var undo2 = AddNameWatch(s[2], function(el)
	{
		controls[el._nw] = el;
		SetVal(el.value);
		var undo=OnChange(el,function()
		{
			SetVal(el.value);
		});
		return function(){undo();delete controls[el._nw];};
	});

	return function(){undo1();undo2();};
}



// Press button immediately on value change
// $$("e~mycontrol~myidofthingtopress")
function setaction0e(s)
{
	return AddNameWatch(s[1], function(el)
	{
		var oldval = ControlValue(el);
		return OnChange(el, function(el, blah, balh, force)
		{
			var val = ControlValue(el);
			if (!force&&val!=oldval)
			{
				oldval=val;
				$log('E caused refresh '+val);
				$c(getel(s[2]));
			}
		});
	});
} 










// Dynamic server update
//
// Parameters:
//   * Action type:
//     1: Turn into editable collection - call given URL when data changes and focus is lost.
//       * Outer element for the set
//       * URL to call when data changes
//       Repeatedly:
//         * Value name
//         * Object identifier for value or any containing element thereof (useful for dates)

function setaction0f(s)
{
	if (s[1]=='1')
	{
		var o={_url:s[3], _outer:getel(s[2])||getel(s[5]).parentNode,_els:[]};

		var JS0FFocusOut = function()
		{
			var url = o._url;
			if (url&&url.indexOf("?")==-1) url+="?";
			for (var i=0;i<o._els.length;++i)
			{
				var s="";
				for (var j=0;j<o._els[i]._els.length;++j)
				{
					var el = o._els[i]._els[j];
					var ls = ControlText(el);
					s = combine(s, ControlValue(el));
					el._otherelement.innerHTML=ControlText(el).split("&").join("&amp;").split("<").join("&lt;");
					HideControl(el);
					ShowControl(el._otherelement);
				}
				if (url)
					url+="&"+o._els[i]._name+"="+escape(s);
			}
			
			if (url&&o._lastURL&&o._lastURL!=url)
				getXML(url);
			o._lastURL = url;
		}
		
		var JS0FFocusIn = function(ev)
		{
			var firstel = null;
			var lastel = null;
			for (var i=0;i<o._els.length;++i)
				for (var j=0;j<o._els[i]._els.length;++j)
				{
					lastel = o._els[i]._els[j];
					firstel = firstel||lastel;
					ShowControl(lastel);
				}
			
			FocusControl(ev._kshift?lastel:firstel, ev._kshift);
			
			// We can only hide the spans once we've put focus on something else
			for (var i=0;i<o._els.length;++i)
				for (var j=0;j<o._els[i]._els.length;++j)
					HideControl(o._els[i]._els[j]._otherelement);
		}
		
		fsAddEvent(o._outer,"_onfocusout",closure(JS0FFocusOut),0);
		fsAddEvent(o._outer,"_onfocusin",closure(JS0FFocusIn),0);
		for (var i=4;i<s.length;i+=2)
		{
			var eln = s[i];
			var el = getel(s[i+1]);
			el._fid = o._fid;
			var lo = {_name:eln, _el:el,_els:FindControls(el)};
			arraypush(o._els, lo);
			for (var j=0;j<lo._els.length;++j)
			{
				var lel = lo._els[j];
				var span = document.createElement("SPAN");
				span.tabIndex = 0;
				lel._otherelement = span;
				lel.parentElement.insertBefore(span, lel);
				span.id = ".."+lel.id+"..";
				HideControl(span);
			}
		}
		JS0FFocusOut();
	}
}

function combine(s1,s2)
{
	return !s2?s1||'':!s1?s2:s1+' '+s2;
}










// Upload controls
// 
// Paramters:
//    * id of upload box
//    * id of delete button
//    * id of filename span

function setaction0g(s)
{
	var boxid = s[1];
	var delid = s[2];
	var textid = s[3];
	var delbtntxt = s[4];
	
	var box = document.getElementById(boxid);
	var delbtn = document.getElementById(delid);
		
	var text = document.getElementById(textid);
	var par = (box||delbtn);
	par = par.parentNode||par.parentElement;
	var ifr;
	
	var somethingchanged = closure(function (ev)
	{
		var o = ifr?ifr.contentWindow.document.getElementById(boxid):box;
		var f = o?o.value.split('\\'):[''];
		f = f[f.length - 1].split('/');
		if (f[f.length-1]&&!text.innerHTML)
		{
			var o=ifr||box;
			// There's a filename, so we'll hide the upload box and show the delete button
			o.style.position = "absolute";
			o.style.left = "-1000em";
			par.insertBefore(delbtn,ifr||box);
			if (fakebutton)
				par.removeChild(fakebutton);
			
			text.appendChild(document.createTextNode(f[f.length - 1]));
			
			if (ifr) _Uploads[boxid]=ifr;
		}
	});
	
	var btnclick = closure(function (ev)
	{
		if (CreateIfr())
		{
			delete _Uploads[boxid];
		}
		else
		{
			var newbox = document.createElement("input");
			newbox.type = "file";
			newbox.id = boxid;
			newbox.name = boxid;
			newbox.onchange = somethingchanged;
			newbox.onkeydown = function() { return false; };
			newbox.oncontextmenu = function() { return false; };
			
			if (ifr||box)
				par.replaceChild(newbox, ifr||box);
			else
				par.insertBefore(newbox, delbtn.nextSibling);
			box = newbox;
		}
		
		if (fakebutton)
		{
			par.insertBefore(fakebutton, delbtn);
		}
		par.removeChild(delbtn);
		
		text.innerHTML = "";
		return false;
	});
	
	var CreateIfr=function()
	{
		if (window.SubmitPartial)
		{
			var oldifr = ifr;
			ifr = document.createElement('iframe');
			ifr.src="javascript:''";
			ifr.style.width="1px";
			ifr.style.height="1px";
			ifr.frameBorder="0";
			ifr.style.border="0";
			ifr._upid=boxid;
			if (oldifr)
				par.replaceChild(ifr, oldifr);
			else
				par.insertBefore(ifr, box||delbtn.nextSibling);
			ifr.contentWindow.document.open();
			ifr.contentWindow.document.write("<html><head><title>Upload</title><style>body {margin:0;padding:0} table {border-collapse:collapse;border:0;} td {border:0}</style><script>function go(){var tab=document.getElementById('thetable');if (tab.offsetWidth==0) return setTimeout(go,1);frameElement.style.width=tab.offsetWidth+'px';frameElement.style.height=tab.offsetHeight+'px';document.body.style.overflow='hidden';}</script></head><body onload='setTimeout(go,1)'><form class='uploadform' method='post' enctype='multipart/form-data' action=''><table id='thetable'><tr><td id='thetd'><input type='file' id='"+boxid+"' name='"+boxid+"' onchange='frameElement.onchange()'/></td></tr></table></form></body></html>");
			ifr.contentWindow.document.close();
			ifr.onchange = somethingchanged;
			return true;
		}
	};
	if (box&&CreateIfr())
	{
		par.removeChild(box);
		box = null;
	}
	
	if (!delbtn)
	{
		delbtn = document.createElement("input");
		delbtn.type = "submit";
		delbtn.id = delid;
		delbtn.className = "genericsubmit uploaddelete";
		delbtn.name = delid;
		delbtn.onclick = btnclick;
		delbtn.value = delbtntxt;
		delbtn.title = delbtntxt;
		
		// No delbtn means no text.
		text = document.createElement("span");
		text.id = textid;
		par.insertBefore(text,box||ifr);
	}
	else
	{
		// We only need a fakebutton if there is initially a delbtn.
		var fakebutton = document.createElement("input");
		fakebutton.type = "hidden";
		fakebutton.name = delid;
		fakebutton.value = "yes";
		fakebutton._fakeSubmit = true;
	}

	if (box) box.onchange = somethingchanged;
	if (delbtn) delbtn.onclick = btnclick;
}





// Forwardslash-based hierarchy
// Splits a single select control into a dynamic series of controls, based on forward slashes in the original set of options.
//
// Parameters:
//   * Select control id
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("h~myselectid")//--></script>

function clearChildSelect(select) {
	if (select.childSelect) {
		clearChildSelect(select.childSelect);
		select.childSelect.parentNode.removeChild(select.childSelect);
		select.childSelect = null;
	}
}
function cascadePopulate(select, level, finalList, isRoot) {
	if (!isRoot) {
		var rootOption = document.createElement("OPTION");
		rootOption.text = "";
		rootOption.value = level.id;
		try {
			select.add(rootOption, null);
		}
		catch(ex) {
			select.add(rootOption);
		}
	}
	for (var kid in level.children) {
		var kidOption = document.createElement("OPTION");
		kidOption.parentSelect = select;
		kidOption.text = "/" + kid;
		kidOption.value = level.children[kid].id;
		kidOption.level = level.children[kid];
		try {
			select.add(kidOption, null);
		}
		catch(ex) {
			select.add(kidOption);
		}
	}
	select.onchange = function() {
		var newSelect = document.createElement("SELECT");
		newSelect.className = "genericselect";
		var selOption = this.options[this.selectedIndex];
		var anyKids = false;
		for (var _ in selOption.level.children) {
			anyKids = true;
			break;
		}
		if (anyKids) {
			cascadePopulate(newSelect, selOption.level, finalList, false);
			clearChildSelect(this);
			this.parentNode.appendChild(newSelect);
			this.childSelect = newSelect;
		}
		else {
			clearChildSelect(this);
		}
		finalList.value = selOption.level.id;
	}
	select.onblur = select.onchange
}
function setaction0h(s) {
	var fieldID = s[1];
	var fullList = document.getElementById(fieldID);
	var hierarchy = {};
	
	for (var i=0, imax=fullList.options.length; i<imax; i++) {
		var option = fullList.options[i];
		var segments = (" " + option.text).split(/\//g);
		var level = hierarchy;
		if (segments.length == 1) {
			if (!level.children)
				level.children = {};
			level.children[''] = {id: option.value, path: option.text};
		}
		for (var j=1, jmax=segments.length-1; j<=jmax; j++) {
			if (!level.children)
				level.children = {};
			var newLevel = level.children[segments[j]];
			if (!newLevel) {
				newLevel = {};
				level.children[segments[j]] = newLevel;
			}
			level = newLevel;
			if (j == jmax) {
				level.id = option.value;
				level.path = option.text;
			}
		}
	}
	
	var topLevelSelect = document.createElement("SELECT");
	topLevelSelect.className = "genericselect";
	cascadePopulate(topLevelSelect, hierarchy, fullList, true);
	
	fullList.parentNode.appendChild(topLevelSelect);
	fullList.style.display = 'none';
}







var activesig;
var activestart;
var sigswaiting = self.sigswaiting||[];

function setaction0s(s)
{
	var divid = s[1];
	// If fieldid is present, we're editable. Otherwise, val is present.
	var fieldid = s[2];
	var val = s[3];
	
	return AddNameWatch(divid, function(div)
	{
		var sigid='sig'+Math.floor(Math.random()*1000000000);
		var startid='sigstart'+Math.floor(Math.random()*1000000000);
		var finishid='sigfinish'+Math.floor(Math.random()*1000000000);
		
		jQuery(div).html('<object classid="clsid:69A40DA3-4D42-11D0-86B0-0000C025864A" height="75" id="'+sigid+'" name="SigPlus1" style="height: 120px; width: 360px" viewastext="yes"><param name="_Version" value="131095" /><param name="_ExtentX" value="4842" /><param name="_ExtentY" value="1323" /><param name="_StockProps" value="0" />You need to enable the SigPlus ActiveX control in order to use this field.</object>'+(fieldid?'<br /><input type="button" value="Start Sign" class="genericsubmit" id="'+startid+'"/><input type="button" value="Clear" class="genericsubmit" /><input type="button" value="Finish Sign" class="genericsubmit" id="'+finishid+'"/>':''));
		
		if (fieldid)
		{
			jQuery(div).find('input')[0].onclick=function(){StartSig(sigid, startid);return false;}
			jQuery(div).find('input')[1].onclick=function(){ClearSig(sigid, startid, finishid, fieldid);return false;}
			jQuery(div).find('input')[2].onclick=function(){FinishSig(sigid, fieldid, finishid);return false;}
		}
		
		var sig = getel(sigid);
		
		if (!sig) return function(){};
		
		sig.SigCompressionMode=1;
		sig.DisplayPenWidth=10;
		sig.JustifyMode=0;
		sig.EncryptionMode=0;
		if (val) sig.SigString=val;
		
		var undo1 = function()
		{
			RemoveSigFromWaiting(sigid,fieldid);
		}
		
		var undo2 = fieldid?AddNameWatch(fieldid, function(field)
		{
			sig.SigCompressionMode=1;
			sig.DisplayPenWidth=10;
			sig.JustifyMode=0;
			sig.EncryptionMode=0;
			
			if (field.value)
			{
				sig.SigString=field.value;
				getel(startid).disabled=true;
				getel(finishid).disabled=true;
			}
			else
			{
				AddSigToWaiting(sigid,startid);
			}
			return function(){};
		}):function(){};
		return function(){undo1();undo2();};
	});
}

function RemoveSigFromWaiting(sigid, startid)
{
	while (activesig==sigid) ActivateNextSig();
	var oldsw = sigswaiting;
	sigswaiting=[];
	for (var i=0;i<sigswaiting.length;++i)
		if (oldsw[i][0]!=sigid)
			sigswaiting[i]=oldsw[i];
}

function AddSigToWaiting(sigid, startid)
{
	if (!activesig) StartSig(sigid, startid);
	else arraypush(sigswaiting, [sigid, startid]);
}

function StartSig(sigid, startid)
{
	RemoveSigFromWaiting(sigid, startid);
	
	if (activesig)
	{
		getel(activestart).disabled=false;
		getel(activesig).TabletState=0;
		activesig=null;
		activestart=null;
	}
	if (getel(sigid)==null) return ActivateNextSig();
	getel(sigid).TabletState=1;
	getel(startid).disabled=true;
	activesig = sigid;
	activestart = startid;
}

function ActivateNextSig()
{
	//TODO: Find out why sometimes !getel(activesig)
	if (activesig&&getel(activesig)) getel(activesig).TabletState=0;
	activesig = null;
	activestart = null;
	if (sigswaiting.length>0)
	{
		var sigid = sigswaiting[0][0];
		var startid = sigswaiting[0][1];
		sigswaiting=sigswaiting.slice(1);
		StartSig(sigid, startid);
	}
}

function FinishSig(sigid, fieldid, finishid) {
	var sigplus = getel(sigid);
	if (activesig==sigid)
	{
		sigplus.TabletState=0;
		ActivateNextSig();
	}
	sigplus.EncryptionMode=0;
	sigplus.SigCompressionMode=1;
	getel(fieldid).value=sigplus.NumberOfTabletPoints==0?'':sigplus.SigString;
	getel(finishid).disabled=true;
}

function ClearSig(sigid, startid, finishid, fieldid)
{
	var sigplus = getel(sigid);
	sigplus.ClearTablet();
	getel(fieldid).value="";
	getel(finishid).disabled=false;
	if (activesig==sigid)
		sigplus.TabletState = 1;
	else
		getel(startid).disabled=false;
	if (!activesig)
		StartSig(sigid, startid);
	else
		arraypush(sigswaiting, [sigid, startid]);
}

// expects 2010-06-14 09:30:14.613000+0100 
function PrettyDate(dateString) {
	try {
		var date=parsedate(dateString);
	}
	catch (e) {
		return dateString; // TODO: expose the error somehow
	}
	
	// this logic is duplicated in Util/Date. If you change this, you should probably change that -->
	
	var now = window.fs_servertime?fs_servertime:new Date();
	var diff = now - date; // milliseconds
	
	var s = 1000;
	var m = s * 60;
	var h = m * 60;
	var d = h * 24;
	
	var endword = 'ago';
	
	if (diff < 0) 
	{
		diff = -diff;
		endword = 'from now';
	}
	
	if (diff < m) return Math.floor(diff/s) + " seconds " + endword;
	if (diff < 2*m) return " a minute " + endword;
	if (diff < h) return Math.floor(diff / m) + " minutes " + endword;
	if (diff < 2*h) return " an hour " + endword;
	if (diff < 24*h) return Math.floor(diff / h) + " hours " + endword;
	if (diff < 2*d) return " a day " + endword;
	if (diff < 14*d) return Math.floor(diff / d) + " days " + endword;
	if (diff < 35*d) return Math.floor(diff / (d*7)) + " weeks " + endword;
	return date.toLocaleDateString();
}	


// Ruler
// It allows the dragging of marks in a horizontal ruler
//
// Parameters for the ruler
//   * 0
//   * id for the whole ruler
//   * id of ruler element
//   * id of form control that receives the values for a drag operation  "ID~poschange" (poschange is a possibly negative non-integer number of ruler-widths by which the element has moved)
//   * id of form control that gets clicked for a drag operation

// Parameters for a ruler mark
//   * 1
//   * id for the whole ruler
//   * Opaque ID of an alineation position
//   * id of a corresponding page element (assumed to be positioned)

_rulers = {};
_rulermarks = {};
_aligned = {};
function setaction0v(s)
{
	if (s[1]=="0")
	{
		return AddNameWatch(s[3], function(ruler)
		{
			return AddNameWatch(s[4],function(txt)
			{
				return AddNameWatch(s[5],function(sub)
				{
					return AddObject(/*{*/"_rulers"/*}*/, s[2], function(o)
					{
						o._ruler = ruler;
						o._txt = txt;
						o._sub = sub;
						o._right = s[6];
					});
				});
			});
		});
	}
	else if (s[1]=="1")
	{
		return AddNameWatch(s[4], function(el)
		{
			return AddObject(/*{*/"_rulermarks"/*}*/, s[2], function(o)
			{
				o._el = el;
				o._name = s[3];
				return fsAddEvent(el, /*{*/"_ondragstart"/*}*/, function(ev)
				{
					_sizelocked=true;
					var line = document.createElement("div");
					var scr = getScroll();
					line.className="rulerguideline";
					line.style.height=scr._h+"px";
					var undobodyadd = BodyAdd(line);
					var undocache = StartCache(el);
					var offset = 0;
					var basepos = objleft(el)+el.offsetWidth/2;
					var undom = fsAddEvent(ev, /*{*/"_ondragmove"/*}*/, function(evm)
					{
						offset = evm._mx-ev._mx;
						
						setstyle(el,"left",offset-el.offsetWidth/2+"px");
						line.style.left=objleft(el)+el.offsetWidth/2+1+"px";
						return true;
					});
					var undod = fsAddEvent(ev, /*{*/"_ondragdrop"/*}*/, function(evd)
					{
						if (!_rulers[s[2]]) return lundo();
						for (var i in _rulers[s[2]])
						{
							var r = _rulers[s[2]][i];
							r._txt.value = s[3]+"~"+(basepos+offset-objleft(r._ruler))/r._ruler.offsetWidth;
							$c(r._sub);
						}
						lundo();
						return true;
					});
					
					var lundo = function()
					{
						undobodyadd(line);
						undocache();
						undom();
						undod();
						_sizelocked=false;
					};
					return true;
				});
			});
		});
	}
	else if (s[1]=="2")
	{
		var currentlyover = false;
		var undo = AddOverHandlerByName(s[3], function(el){
			undo();
			currentlyover = true;
			undo = AddNameWatch(s[3], gogogo);
			currentlyover = false;
		});
		var gogogo = function(el)
		{
			return AddNameWatch(s[4], function(txt)
			{
				return AddNameWatch(s[5], function(sub)
				{
					var side = s[6]; // l or r
					
					var handleouter = document.createElement("div");
					var handleinner = document.createElement("div");
					var handleimg = document.createElement("img");
					handleimg.src=s[7]+(side=='l'?"rulerleft.gif":"rulerright.gif");
					handleouter.className=(side=='l'?"edgehandleleft":"edgehandleright");
					handleouter.appendChild(handleinner);
					handleouter.appendChild(handleimg);
					el.style.position="relative";
					el.appendChild(handleouter);
					if (currentlyover) handleouter.style.visibility="visible";
					
					var undo1 = fsAddEvent(el, /*{*/"_onmouseover"/*}*/, function()
					{
						handleouter.style.visibility="visible";
					});
					var undo2 = fsAddEvent(el, /*{*/"_onmouseout"/*}*/, function()
					{
						handleouter.style.visibility="hidden";
					});
					
					var interest = {};
					var lmark = s[8];
					var lmarkinc = s[9];
					var rmark = s[10];
					var rmarkinc = s[11];
					var undo3 = fsAddEvent(handleouter, /*{*/"_ondragstart"/*}*/, function(ev)
					{
						if (!_rulermarks[s[2]]) return false;
						_sizelocked=true;
						
						var undo = [];
						var sizer = document.createElement("div");
						sizer.className="sizer";
						arraypush(undo, BodyAdd(sizer));
						setstyle(sizer, "width", el.offsetWidth+"px");
						setstyle(sizer, "height", el.offsetHeight+"px");
						moveobj(sizer, objleft(el), objtop(el));
						var sizerright = objleft(sizer)+sizer.offsetWidth;
						
						var marks = [];
						var interesting = false;
						var leftlimit;
						var rightlimit;
						// NOTE: This is dependent on objects iterating in the same order that entries were inserted. This is guaranteed, according to recent JS standards and every browser that isn't Safari/KHTML.
						var scr = getScroll();
						for (var i in _rulermarks[s[2]])
						{
							var o = _rulermarks[s[2]][i];
							var n = o._name;
							var p =objleft(o._el)+o._el.offsetWidth/2+1;
							if (n==lmark)
							{
								leftlimit = p;
								interesting = true;
								if (!lmarkinc) continue;
							}
							if (n==rmark)
							{
								rightlimit = p;
								if (!rmarkinc) interesting = false;
							}
							if (!interesting) continue;
							var line = document.createElement("div");
							line.className="rulerguideline";
							line.style.height=scr._h+"px";
							arraypush(undo, BodyAdd(line));
							arraypush(marks, {pos:p, name: n});
							line.style.left=p;
							if (n==rmark) interesting=false;
						}
						
						var closest = null;
						arraypush(undo,fsAddEvent(ev, /*{*/"_ondragmove"/*}*/, function(evm)
						{
							var thisclosest = null;
							var dist = 1000000;
							for (var i=0;i<marks.length;++i)
							{
								var o = marks[i];
								var p = marks[i].pos;
								if (s[6]=="l"&&p+10>=objleft(sizer)+sizer.offsetWidth) continue;
								if (s[6]=="r"&&p-10<=objleft(sizer)) continue;
								var el2dist = Math.abs(p-evm._mx);
								if (el2dist<dist&&el2dist<20) {dist = el2dist; thisclosest = marks[i];}
							}
							if (!thisclosest)
								thisclosest = {pos:Math.max(leftlimit+20, Math.min(rightlimit-20, evm._mx))};
							closest = thisclosest;
							if (s[6]=="l")
							{
								setstyle(sizer, "width", sizerright-closest.pos);
								moveobj(sizer, closest.pos, objtop(el));
							}
							else
								setstyle(sizer, "width", closest.pos-objleft(sizer));
							return true;
						}));
						arraypush(undo,fsAddEvent(ev, /*{*/"_ondragdrop"/*}*/, function(evd)
						{
							if (closest)
							{
								if (!closest.name)
								{
									for (var i in _rulers[s[2]])
										var r = _rulers[s[2]][i];
									txt.value = (closest.pos - objleft(r._ruler))/r._ruler.offsetWidth;
								}
								else
									txt.value=closest.name;
								$c(sub);
							}
							lundo();
							return true;
						}));
						
						var lundo = function()
						{
							for (var i=0;i<undo.length;++i)
								undo[i]();
						}
						return true;
					});
					
					return function(){undo1();undo2();undo3();_sizelocked=false;};
				});
			});
		};
		// undo can be reassigned, so don't mess with this.
		return function(){undo();};
	}
}

function AddObject(name, sub, fn)
{
	if (!window[name]) window[name]={};
	var o = window[name];
	if (!o[sub]) o[sub]={};
	o=o[sub];
	var key = Math.random()+"";
	o[key] = {};
	var undo = fn(o[key]);
	return function()
	{
		if (undo) undo();
		delete o[key];
	}
}

function BodyAdd(el)
{
	return AppendChild(document.body, el);
}

function AppendChild(p, ch)
{
	p.appendChild(ch);
	return function()
	{
		p.removeChild(ch);
	}
}

function emptydivinbody()
{
	var emptydiv = document.createElement("div");
	emptydiv.id="fsemptydiv"+Math.floor(Math.random()*100000);
	emptydiv.style.display="none";
	document.body.appendChild(emptydiv);
	return emptydiv;
}

function dofsiframe(href)
{
	if (BigCoverCount) return;
	if (parent!=self)
	{
		try
		{
			var blah = parent.dofsiframe;
		}
		catch (e){}
		if (blah) return parent.dofsiframe(href);
	}
	var el = emptydivinbody();
	el.style.display="block";
	el.style.position="absolute";
	el.style.top="0";
	var emptydiv = emptydivinbody();
	var action = "7~"+el.id+"~"+emptydiv.id+"~1~000h1h~000h1h~2~0~"+href+"~"+emptydiv.id;
	var undo = setaction07([7,el.id,emptydiv.id,'1','000h1h','000h1h','2','0',href,emptydiv.id]);
	var o = eventobjs[undo._id];
	var ev = {_el:el,_els:[el]};
	JS01MouseOverOrClick(ev,null,o._menu)
}


var jssclickhandlers = {};
jssclickhandlers.jssfsiframe = function(el)
{
	if (!el.id) el.id="fsiframe"+Math.floor(Math.random()*100000);
	var emptydiv = emptydivinbody();
	var action = "7~"+el.id+"~"+emptydiv.id+"~1~000h1h~000h1h~2~0~"+el.href+"~"+emptydiv.id
	$$(action);
	return function(){$$undo(action)}
};
jssclickhandlers.jssexpandsnext = function (el)
{
	$(el).click(function(){ var $this = $(this);$this.next('.expands').slideToggle(300, function(){$this.toggleClass('toggled')}); });
};
function jsslogin(el, parm) {
	if (window.firmstepuser!="AnonymousUs"||window.firmstepuser==null) return function(){}
	if (!el.id) el.id="fsiframe"+Math.floor(Math.random()*100000);
	var emptydiv = document.createElement("div");
	emptydiv.id="fsemptydiv"+Math.floor(Math.random()*100000);
	emptydiv.display="none";
	document.body.appendChild(emptydiv);
	var ret = setaction07([7,el.id,emptydiv.id,'1','000h1h','000h1h','2','0','/LogIn/LoginPopup.aspx?'+parm+'=1', emptydiv.id]);
	eventobjs[ret._id]._retfn = function(){if (el.href) location=el.href; else $c(el);return true;};
	return ret;
}
jssclickhandlers.jssforcelogin = function (el) {
	return jsslogin(el, "Forced");
};
jssclickhandlers.jsscanskiplogin = function (el) {
	return jsslogin(el, "CanSkip");
};
function addjssmouseclick(ev)
{
	for (var i=0;i<ev._els.length;++i)
	{
		var el = ev._els[i];
		if ((el.className+'').indexOf("jssmouseclick")==-1) continue;
		$(el).removeClass('jssmouseclick');
		var classes = (el.className+'').split(/\s+/g);
		for (var j=0;j<classes.length;++j)
		{
			var fn = jssclickhandlers[classes[j]];
			if (!fn) continue;
			$(el).removeClass(classes[j]);
			el.className+=" jssundo";
			el._jssundo=el._jssundo||[];
			el._jssundo.push(fn(el));
		}
	}
}

var jssoverhandlers = {};
jssoverhandlers.titleisutciso = function(el)
{
	
	var utc = el.title;
	el.title = parsedateUTC(utc) + '';
	return function(){el.title=utc;};
}

function parsedateUTC(str)
{
	var m = /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})/.exec(str);
	if (!m) return;
	var d = new Date(0);
	d.setUTCFullYear(+m[1]);
	d.setUTCMonth(+m[2]-1);
	d.setUTCDate(+m[3]);
	d.setUTCHours(+m[4]);
	d.setUTCMinutes(+m[5]);
	d.setUTCSeconds(+m[6]);
	return d;
}

function parsedate(str)
{
	var m = /(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})\.(\d{6})(\+|-)(\d{2})(\d{2})/.exec(str); // feel free to support other formats if you wish to;
	if (!m) return;
	
	var offsetms = (+m[9] * 60 + +m[10]) * 1000 * 60;
	if (m[8] = '-') offsetms = -offsetms;
	
	return new Date(Date.UTC(+m[1],+m[2]-1,+m[3],+m[4],+m[5],+m[6]) + offsetms);
}

function addjssmouseover(ev)
{
	for (var i=0;i<ev._els.length;++i)
	{
		var el = ev._els[i];
		if ((el.className+'').indexOf("jssmouseover")==-1||(el.className+'').indexOf("jssoverundo")!=-1) continue;
		var classes = (el.className+'').split(/\s+/g);
		for (var j=0;j<classes.length;++j)
		{
			var fn = jssoverhandlers[classes[j]];
			if (!fn) continue;
			el.className+=" jssoverundo";
			el._jssoverundo=el._jssoverundo||[];
			el._jssoverundo.push(fn(el));
		}
	}
}






$log("0.js loaded");
