/*

	TOC:
	* HTTP functions
	* CSS class name functions (add,remove,test)
	* Form handling functions
	* Misc functions
	* Cookie functions
	* Onload events

*/
/* === HTTP functions === */

/* Handles errors in all HTTP requests */
function HTTPBombOut(http, uri) {
	switch (http.status) {
		case 500: // INTERNAL ERROR
			document.getElementsByTagName('body')[0].innerHTML = http.responseText;
			//alert('Server error 500:\n\n' + http.responseText);
			return true;
		case 401: // UNAUTHORIZED
		case 403: // FORBIDDEN
			location = 'logoff.asp';
			return true;
		case 404: // NOT FOUND
			alert('The page you requested could not be found.\n\n' + uri);
			return true;
	}
	try {
		var result = parseInt(http.getResponseHeader('ResultID'), 10);
		if (result) {
			alert(http.responseText);
			if (result < 1000) {
				location = base_url() + 'logoff.asp?result=' + result;
			}
			return true;
		}
	} catch(e) {}
	return false;
}

/* Creates a new socket for HTTP requests */
function CreateHTTP() {
	var xmlhttp=false;
	/*@cc_on @*/
	/*@if (@_jscript_version >= 5)
	try {
		xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
	} catch (e) {
		try {
			xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
		} catch (E) {
			xmlhttp = false;
		}
	}
	@end @*/
	if (!xmlhttp && typeof XMLHttpRequest!='undefined')
		xmlhttp = new XMLHttpRequest();
	return xmlhttp;
}

/* Performs an HTTP GET request */
function HTTPGet(uri, callback_function, callback_parameter) {
	var xmlhttp = CreateHTTP();
	var bAsync = true;
	if (!callback_function)
		bAsync = false;
	xmlhttp.open('GET', uri, bAsync);
	xmlhttp.setRequestHeader('If-Modified-Since', 'Fri, 18 Feb 2005 12:00:00 GMT');
	xmlhttp.send(null);
	if (bAsync) {
		if (callback_function)
			xmlhttp.onreadystatechange = function() {
				if (xmlhttp.readyState == 4)
					if (!HTTPBombOut(xmlhttp, uri))
						callback_function(xmlhttp.responseText, xmlhttp, callback_parameter)
			}
		return xmlhttp;
	} else
		return xmlhttp.responseText;
}

/* Performs an HTTP POST request */
function HTTPPost(uri, object, callback_function, callback_parameter) {
	var xmlhttp = CreateHTTP();
	var bAsync = true;
	if (!callback_function)
		bAsync = false;
	xmlhttp.open('POST', uri, bAsync);
	xmlhttp.setRequestHeader('If-Modified-Since', 'Fri, 18 Feb 2005 12:00:00 GMT');
	xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
	var form_contents = '';
	for (var i in object)
		form_contents += (form_contents ? '&' : '') + i + '=' + escape(object[i]);
	xmlhttp.send(form_contents);
	if (bAsync) {
		if (callback_function)
			xmlhttp.onreadystatechange = function() {
				if (xmlhttp.readyState == 4)
					if (!HTTPBombOut(xmlhttp, uri))
						callback_function(xmlhttp.responseText, xmlhttp, callback_parameter)
			}
		return xmlhttp;
	} else
		return xmlhttp.responseText;
}

/* Used as a callback from HTTP requests, executes JavaScript returns by the server */
function HTTPeval(sText, http, bShowDebug) {
	if (http.status != 200) {
		alert('An HTTP ' + http.status + ' error has occured.' + (bShowDebug ? '\n\nHTTP Response:\n' + sText : ''));
		return false;
	}
	try {
		eval(sText);
	} catch(e) {
		return alert('Error: failed eval().' + (bShowDebug ? '\n\n' + sText : ''));
	}
}

/* Used as a callback from HTTP requests, inserts the server response into a DIV (node) */
function HTTPinto(html, http, node) {
	try {
		node.innerHTML = html;
	} catch(e) {
		// IE readonly table "fix":
		var dummy = document.createElement('div');
		dummy.innerHTML = html;
		node.appendChild(dummy);
	}
	removeNode(busy_icon());
	validate_auto_attach(node);
}

/* Performs an HTTP request to load "preview" HTML into DIVs (node) */
function preview_pane(url, node) {
	node.innerHTML = 'Loading...';
	HTTPGet(url, preview_pane_callback, node);
	return false;
}

/* Callback for the above HTTP request */
function preview_pane_callback(text, http, node) {
	if (http.status != 200)
		alert('An HTTP ' + http.status + ' error has occured.');
	node.innerHTML = text;
}


/* === CSS Classname functions === */

// Check if a node has a certain classname
function hasClass(node, className) {
	if (!node) return false;
	if (node.className == className)
		return true;

	var reg = new RegExp('\\b'+className+'\\b')
	if (reg.test(node.className))
		return true;
	return false;
}

// Add a classname to a node
function addClass (node, className) {
	if (hasClass(node, className))
		return false;
	node.className += ' ' + className;
	return true;
}

// Remove a classname from a node
function removeClass (node, className) {
	if (!hasClass(node, className))
		return false;
	node.className = str_replace(className, '', node.className);
	return true;
}

// Toggle a classname on a node
function toggleClass(node, className) {
	if (!removeClass(node, className))
		if (!addClass(node, className))
			return false;
	return true;
}




/* === Form handling === */

// Called when the user clicks a field name in a validation error box (to highlight the appropriate field)
function form_fix(link) {
	var id = link.href.substr(link.href.indexOf('#') + 1);
	var node = grab(id);
	node.focus();
	node.select();
	return false;
}

// Converts a form node into a data object (suitable for HTTPPost)
function FormToObject(form) {
	if (!form)
		return false;
	var obj = {};
	for ( var i = 0; node = form.elements[i]; i++ )
		if (node.name && !node.disabled)
			if (node.type != 'checkbox' || node.checked)
				obj[node.name] = getValueByNode(node, '');
	return obj;
}

// Initialises validation on a specific form
function form_load_validation(form) {
	if (!form.objValidation) {
		var vNode = form['validation'];
		if (!vNode) return false;
		eval(getValueByNode(vNode));
		form.objValidation = validation;
	}
	return true;
}

// Performs the main validation loop, and creates an error box to describe errors
function IsValid(form) {
	var failures = [];
	if (!form_load_validation(form)) return false;
	for (i=0;inputNode=form.elements[i];i++)
		if (!CheckValidField(inputNode))
			failures[failures.length] = inputNode;
	
	if (failures.length > 0) {
		var errorDiv, prev = form.previousSibling;
		if (prev && hasClass(prev, 'error'))
			errorDiv = prev;
		else {
			errorDiv = document.createElement('div');
			errorDiv.className = 'message error';
			form.parentNode.insertBefore(errorDiv, form);
			errorDiv.style.display = 'none';
		}
		errorDiv.innerHTML = '<p><strong>Error</strong>: Please correct the following form fields:</p>';
		var ul = create_child(errorDiv, 'ul');
		for (i=0;failedNode=failures[i];i++) {
			var li = create_child(ul, 'li');
			var label = getLabelForId(failedNode.id);
			var v = form.objValidation[failedNode.name];
			li.innerHTML = '<a href="#'+ failedNode.id+'" onclick="return form_fix(this)">' + label.innerHTML + '</a> - ' + failedNode.title;
			if (v && v['req'])
				li.innerHTML += ' (Required)';
		}
		var p = create_child(errorDiv, 'p');
		p.innerHTML = 'Tip: You can click on the field names above to quickly move to them in the form below.';
		errorDiv.style.display = 'block';
		ScrollToNode(errorDiv);
		return false;
	}
	return true;
}

// Attaches validation components to all forms when the page loads
function validate_auto_attach(from) {
	if (!from || !from.tagName)
		var forms = document.forms;
	else
		var forms = from.getElementsByTagName('form');
	var f = function () { CheckValidField(this); };
	var c = function () { this.form.changed = true; }
	for (i=0;form=forms[i];i++)
		if (form_load_validation(form)) 
			for (j=0;el=form.elements[j];j++)
				if (el.name && form.objValidation[el.name]) {
					addEvent(el, 'change', c);
					addEvent(el, 'change', f);
					addEvent(el, 'keyup', f);
					addEvent(el, 'click', f);
				}
}

// wrapper for IsValidField, gives invalid fields a red border ("invalid" classname)
function CheckValidField(node) {
	var label = getLabelForId(node.id);
	var className = 'invalid';
	if (IsValidField(node)) {
		removeClass(node, className);
		removeClass(label, className);
		return true;
	} else {
		addClass(node, className);
		addClass(label, className);
		return false;
	}
}

// Checks if a single form field is valid
function IsValidField(node) {
	var form = node.form;
	if (!form_load_validation(form)) return false;
	
	var valid = true;
	var v = form.objValidation[node.name];
	if (!v) return true;
	var value = getValueByNode(node);
	if (v['req'] || value.length > 0) {
		if (v['reg'] != '') {
			var reg = new RegExp(v['reg']);
			if (!reg.test(value))
				return false;
		}
		if (value.length == 0) {
			return false;
		}
	}
	return true;
}

// Returns the HTML <label> element for a given form input id
function getLabelForId(id) {
	var label, labels = document.getElementsByTagName('label');
	for (var i = 0; (label = labels[i]); i++)
		if (label.htmlFor == id)
			return label;
	return false;
}

// Converts an object into a querystring (with no ? at front)
function ObjectToQueryString(obj) {
	var qs = '';
	for (var i in obj)
		if (obj[i])
			qs += '&' + i + '=' + escape(obj[i]);
	return qs;
}

// Converts an object into HTML inputs to be placed in a form
function ObjectToHTML(obj) {
	var sHTML = '';
	for (var i in obj) {
		if (!obj[i]) obj[i] = '';
		sHTML += '<input type="hidden" name="' + i + '" value="' + obj[i] + '" />\n';
	}
	return sHTML;
}

// Converts a form (by id) to a querystring
function FormToQueryString(formID) {
	var obj = FormToObject(formID);
	var qs = ObjectToQueryString(obj);
	return qs;
}

// Returns the value of an input
function getValueById(id, defaultValue) {
	var node = document.getElementById(id);
	return getValueByNode(node, defaultValue);
}

// Called from the page's onbeforeunload() event. Prevents user's losing form information
function check_forms_changed() {
	var forms = document.forms;
	for (i=0;forms[i];i++)
		if (forms[i] && forms[i].changed && !forms[i].submitted)
			return "Any changes you have made will not be saved."
}

// Inserts a "Cancel" button for CTRL_CANCEL 
function cancel_auto_attach() {
	var forms = document.forms;
	for (i=0;forms[i];i++)
		if (forms[i] && (spans = forms[i].getElementsByTagName('span')))
			for (var j in spans)
				if (j && spans[j] && hasClass(spans[j], 'cancel'))
					spans[j].innerHTML = '<button onclick="history.go(-1); return false;">Cancel</button>';
}

// Returns the value of a form input field
function getValueByNode(inputNode, defaultValue) {
	if (typeof defaultValue == 'undefined')
		defaultValue = false;
	if (!inputNode) return defaultValue;
	if(inputNode.type == 'radio' && inputNode.name) {
		if (inputNode.form)
			group = inputNode.form[inputNode.name];
		else
			group = document.getElementsByName(inputNode.name);
		for(var i = 0; i < group.length; i++)
			if(group[i].checked)
				return group[i].value;
		return defaultValue;
	} else if (inputNode.type == 'checkbox') {
		if (inputNode.checked)
			return inputNode.value;
		else
			return defaultValue;
	} else if (inputNode.tagName && inputNode.tagName.toLowerCase() == 'select') {
		if (inputNode.multiple) {
			var s = '';
			for(var i = 0; option = inputNode.options[i]; i++) {
				if (option.selected && option.value)
					s += option.value + ',';
			}
			if (s)
				return s.substr(0, s.length - 1);
			else
				return defaultValue;
		} else {
			if (inputNode.options.length > 0 && inputNode.selectedIndex >= 0)
				return inputNode.options[inputNode.selectedIndex].value;
			else
				return defaultValue;
		}
	} else if (typeof inputNode.value != 'undefined') {
		return inputNode.value;
	} else if (inputNode.innerHTML && inputNode.innerHTML.length > 0) {
		return inputNode.innerHTML;
	} else {
		return defaultValue;
	}
}

// Moves the focus to the first form element on the page (that matches certain criteria)
function focus_first_form_element() {
	var forms = document.forms;
	for (i=0;form=forms[i];i++)
		for (j=0;el=form.elements[j];j++)
			// Input must be visible and enabled. Can't be a button or a dropdown
			if (el.type && el.type != 'hidden' && !el.disabled && !el.readOnly && el.type.substr(0,6) != 'select' && el.type != 'submit')
				try {
					if (el.focus) el.focus();
					if (el.select) el.select();
					return true;
				} catch (e)	{ continue; }
	return false;
}




/* === Misc functions === */

// Returns the Base Href (from header.asp)
function base_url() {
	return document.getElementsByTagName('base')[0].href;
}

// Increments an automatic ID
var static_auto_id = 1;
function auto_id() {
	return 'static-' + static_auto_id++;
}

// 
function busy_icon(id) {
	var id = 'busy-indicator' + (id ? id : 'default');
	var img = grab(id);
	if (!img) {
		img = document.createElement('img');
		img.src = 'images/wait.gif';
		img.id = id;
	}
	return img;
}

// Shorthand way of performing document.getElementById
function grab(id) {
	return document.getElementById(id);
}

// Alternative way of calling a string replace
function str_replace(search, replace, subject) {
	return subject.replace(new RegExp(search,'g'), replace);
}

// Alerts the contents of a javascript variable (recursive on objects)
function Debug(mixed, bReturn) {
	var s = '';
	switch (typeof mixed) {
		case 'array':
		case 'object':
			for( var i in mixed )
				s += i + ': ' + mixed[i] + '\n';
			break;
		default:
			s = mixed;
	}
	if ( bReturn )
		return s;
	alert(s);
}

// Scroll the user down to a specific page element
function ScrollToNode(node){
	var x = 0;
	var y = 0;
	while( node != null ) {
		x += node.offsetLeft;
		y += node.offsetTop;
		node = node.offsetParent;
	}
	window.scrollTo(x, y);
}

// Return the coordinates of a an element on the page (in pixels)
function AbsolutePosition(el) {
	var SL = 0, ST = 0;
	var is_div = /^div$/i.test(el.tagName);
	if (is_div && el.scrollLeft)
		SL = el.scrollLeft;
	if (is_div && el.scrollTop)
		ST = el.scrollTop;
	var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
	if (el.offsetParent) {
		var tmp = AbsolutePosition(el.offsetParent);
		r.x += tmp.x;
		r.y += tmp.y;
	}
	return r;
};

// Removes an HTML element from the page
function removeNode(node) {
	if (typeof node == 'string') node = grab(node);
	if (node && node.parentNode) {
		return node.parentNode.removeChild(node);
	} else
		return false;
}

// Shorthand for creating an HTML element inside another
function create_child(node, type) {
	var new_node = document.createElement(type);
	node.appendChild(new_node);
	return new_node;
}

// Insert a DOM node after an existing node (normally can only insert before an existing node)
function insertAfter(newNode, node) {
	var parent = node.parentNode;
	var next = node.nextSibling;

	if (next)
		parent.insertBefore(newNode, next);
	else
		parent.appendChild(newNode);
}

// Gives alternating odd and even classnames to table rows
function stripe(table) {
	if (! table) { return; }
	var even = false;
	var tbodies = table.getElementsByTagName("tbody");
	for (var h = 0; tbody = tbodies[h]; h++) {
		var trs = tbody.getElementsByTagName("tr");
		for (var i = 0; tr = trs[i]; i++) {
			addClass(tr, even ? 'even' : 'odd');
			even = !even;
		}
	}
}

// Calls the above table stripe function for all tables
function auto_stripe() {
	var tables = document.getElementsByTagName('table');
	for (i=0;table=tables[i];i++)
		if (!hasClass(table, 'structural'))
			stripe(table);
}   

/* === Collapse Functions === */

// Attaches the collapsing behaviour to all fieldsets
function collapse_auto_attach() {
	var fieldsets = document.getElementsByTagName('fieldset');
	for (var i = 0; fieldset = fieldsets[i]; i++)
		if (hasClass(fieldset, 'jscollapse'))
			collapse_attach(fieldset, hasClass(fieldset, 'startcollapsed'));
}

// Collapses or uncollapses a fieldset
function collapse(node, bForceOpen) {
	var className = 'collapsed';
	if (bForceOpen)
		removeClass(node, className);
	else
		toggleClass(node, className);
	var img = node.getElementsByTagName('img')[0];
	if (hasClass(node, className)) {
		img.src = 'images/icons/closed.gif';
		img.title = 'Open this section';
	} else {
		img.src = 'images/icons/open.gif';
		img.title = 'Close this section';
	}
}

// Attaches the collapsing behaviour to a fieldset
function collapse_attach(id, bCollapsed) {
	if (!id.tagName)
		var node = grab(id);
	else
		var node = id;
	if (!node)
		return false;
	switch (node.tagName.toLowerCase()) {
		case 'legend':
			node = node.parentNode;
			if (node.tagName.toLowerCase() != 'fieldset')
				return false;
		case 'fieldset':
			var legend = node.getElementsByTagName('legend');
			addClass(node, 'collapser');
			if (!legend)
				return false;
			legend[0].innerHTML = '<a href="#" onclick="collapse(this.parentNode.parentNode); return false;"><img src="images/open.gif" alt="[&plusmn;]" title="Close this section"> <span>' + legend[0].innerHTML + '</span></a>';
			break;
		default:
			return false;
	}
	collapse(node, !bCollapsed);
	return true;
}

/* === Cookies === */

function createCookie(name,value,days) {
	if (days) {
		var date = new Date();
		date.setTime(date.getTime()+(days*24*60*60*1000));
		var expires = "; expires="+date.toGMTString();
	}
	else var expires = "";
	document.cookie = name+"="+value+expires+"; path=/";
}

function readCookie(name) {
	var nameEQ = name + "=";
	var ca = document.cookie.split(';');
	for(var i=0;i < ca.length;i++) {
		var c = ca[i];
		while (c.charAt(0)==' ') c = c.substring(1,c.length);
		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
	}
	return null;
}

function eraseCookie(name) {
	createCookie(name,"",-1);
}

function hasCookies() {
	var n = 'TestingTesting123';
	createCookie(n, '123', 0);
	var r = (readCookie(n) == '123');
	eraseCookie(n);
	return r;
}

/* === Tracking Notes === */

// Adds the tracking note form to the page (creates the HTTP request)
function tracking_note_add(node){
	var div = grab('AddNoteDiv');
	if (!div) {
		div = document.createElement('div');
		div.id = 'AddNoteDiv';
		var ul = grab('casenotes_grid').nextSibling;
		ul.parentNode.insertBefore(div, ul);
	}
	HTTPGet(node.href + "&noheader=true", tracking_note_add_callback, div);
	return false;
}

// Inserts the tracking note form onto the page
function tracking_note_add_callback(html, http, div) {
	var temp = document.createElement('div');
	temp.innerHTML = html;
	div.innerHTML = temp.getElementsByTagName('form')[0].innerHTML;
	var ul = div.getElementsByTagName('input')[0];
	var button = create_child(ul.parentNode, 'button');
	button.innerHTML = 'Cancel';
	button.onclick = function () { removeNode(div); };
	focus_first_form_element();
	calendar_auto_attach();
}

// Saves a tracking note
function tracking_note_save(node) {
	var form = node.form;
	var data = FormToObject(form);
	data.frmAction = "Save";
	var uri = base_url() + 'TNotes/notes_save.asp?noheader=true&js=true';
	HTTPPost(uri, data, tracking_note_save_callback, node);
	return false;
}

// Displays errors from saving a tracking note
function tracking_note_save_callback(html, http, node) {
	grab('fieldsetNotes').innerHTML = html;
}

// Removes a tracking note, with confirmation
function tracking_note_delete(node) {
	if (confirm('Are you sure you want to delete this note?')) {
		var data = RowContents(node);
		var id = data['Note ID'];
		node.deleteid = id;
		var uri = base_url() + 'TNotes/notes_delete.asp?noheader=true&id=' + id;
		node.parentNode.appendChild(busy_icon(node.deleteid));
		HTTPGet(uri, tracking_note_delete_callback, node);
	}
	return false;
}

// Rremoves a tracking note from the notes grid
function tracking_note_delete_callback(html, http, node) {
	var row = node.parentNode;
	while (row && row.tagName.toLowerCase() != 'tr') row = row.parentNode;
	removeNode(busy_icon(node.deleteid));
	if (!row.id) row.id = auto_id();
	Fat.fade_element(row.id, 30, 2000, 'ff0000', '666666', removeNode);
}

/* === Calendar === */

var calendars = 0;

// Attaches calendars to CTRL_DATE fields
function calendar_auto_attach() {
	var inputs = document.getElementsByTagName('input');
	for (i=0;input=inputs[i];i++)
		if (input && (hasClass(input, 'date') || hasClass(input, 'datetime')))
			calendar_attach(input);
	var allday = grab('chkAllDay');
	if (allday) {
		addEvent(allday, 'change', calendar_all_day_change);
		calendar_all_day_change();
	}
}

// Enables and disables the time dropdowns when "All day" is clicked
function calendar_all_day_change() {
	var allday = grab('chkAllDay');
	if (!allday) return;
	var selects = document.forms[0].getElementsByTagName('select');
	for (i=0;select=selects[i];i++)
		if (hasClass(select, 'datetime'))
			select.disabled = allday.checked;
}

// Attaches a calendar to a CTRL_DATE field
function calendar_attach(input) {
	var trigger = document.createElement('img');
	trigger.id = 'cmdCalendar-' + input.id;
	trigger.alt = 'Calendar';
	trigger.title = 'Show popup calendar';
	trigger.className = 'calendar-icon';
	trigger.src = 'images/calendar-icon.gif';
	insertAfter(trigger, input);
	var settings = {
		inputField	: input.id,
		ifFormat	: '%e/%m/%Y',
		button		: trigger.id,
		singleClick	: true,
		weekNumbers	: false
	};
	if (typeof ourDateStatusFunc == 'function') {
		settings.dateStatusFunc = ourDateStatusFunc;
	}
	if (typeof dateSelect == 'function') {
		settings.onSelect = dateSelect;
	}
	Calendar.setup(settings);
}

// Handler for clicking dates in the toolbar (i.e. for using the selected day's date when clicking the Add Event button)
function calendar_click_toolbar(a) {
	if (!selected_date) return true;
	var url = a.href.split('&date=')[0];
	if (!url) url = a.href;
	url += '&date=' + selected_date.href.split('&date=')[1];
	location = url;
	return false;
}

// Shows the white calendar popup when hovering over the dates in the month view calendar
function calendar_show_popup(nLink) {
	var sLinkDate = nLink.href.split("&date=")[1];
	var aDate = sLinkDate.split('/');
	var d = new Date(aDate[0], aDate[1] - 1, aDate[2]);
	var weekday = new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday");
	var monthname = new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec");
	sDate = weekday[d.getDay()] + " " + d.getDate() + " " + monthname[d.getMonth()] + " " + d.getFullYear();
	
	removeNode('day-popup');
	var div = document.createElement('div');
	div.id = 'day-popup';
	div.className = 'selector';
	addEvent(div, 'mouseout', calendar_mouseout);
	var sContent = sDate + '<ul>';
	var base = base_url();
	if (dayPopupExtra) {
		for (var i in dayPopupExtra) {
			link = dayPopupExtra[i];
			sContent += '<li><a href="' + base + link.url + (link.appendDate ? sLinkDate : '')+'">' + link.name + '</a></li>';
		}
	}
	sContent += '</ul>';
	div.innerHTML = sContent;
	
	var pos= AbsolutePosition(nLink);
	div.style.top = pos.y + 'px';
	div.style.left = pos.x + 'px';
	document.getElementsByTagName('body')[0].appendChild(div);
	while (pos.x > document.documentElement.scrollWidth - 150) {
		pos.x -= 10;
		div.style.left = pos.x + 'px';
	}
	return false;
}

// Handles moving the cursor out of the calendar popups
function calendar_mouseout(e) {
	if (!e) var e = window.event;
	var tg = (window.event) ? e.srcElement : e.target;
	if (tg.nodeName != 'DIV') return;
	var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
	while (reltg != tg && reltg.nodeName != 'BODY')
		reltg= reltg.parentNode;
	if (reltg== tg) return;
	removeNode('day-popup');
}

var old_day = false;
var selected_date = false;
// Highlights a calendar day, used to add events to specific days
function calendar_highlight_day(mode, node) {
	switch (mode) {
		case 'month':
		case 'list':
		case 'week':
			addClass(node, 'selected');
			if (old_day) removeClass(old_day, 'selected');
			old_day = node;
			selected_date = node.getElementsByTagName('a')[0];
			return;
	}
}

/* === Tab Switching === */

// Initialises the use of tabs within a certain container
function tabs_init(container, first) {
	if (!container) return;
	container.className = 'active-tabs';
	container.lasttab = false;
	fieldsets = container.getElementsByTagName('fieldset');
	var ul = document.createElement('ul');
	ul.className = 'tabs';
	var myfirst = false;
	for (i=0;panel = fieldsets[i];i++) {
		if (panel && hasClass(panel, 'panel')) {
			d = document.createElement('div');
			while (panel.hasChildNodes()) d.appendChild(panel.firstChild);
			d.className = 'panel-inner';
			li = document.createElement('li');
			ul.appendChild(li);
			a = document.createElement('a');
			li.appendChild(a);
			li.container = container;
			legend = d.getElementsByTagName('legend')[0];
			li.index = legend.innerHTML.replace(' ', '');
			li.id = 'tab-' + li.index;
			if (!myfirst) myfirst = li.index;
			a.innerHTML = legend.innerHTML;
			a.onmousedown = function () { tab_swap(this.parentNode); return false; };
			a.onclick = function () { tab_swap(this.parentNode); return false; };
			a.href = '#' + li.index;
			panel.innerHTML = '';
			panel.appendChild(legend);
			panel.appendChild(d);
			addClass(legend, 'hidden');
			fieldsets[i].id = 'panel-' + li.index;
			addClass(panel, 'hidden');
		}
	}
	container.insertBefore(ul, container.firstChild);
	tab_swap(first || myfirst);
}

// Swaps to a different tab
function tab_swap(tab) {
	if (typeof tab == 'string') {
		tab = grab('tab-' +  tab.replace(' ', ''));
	}
	var div = grab('panel-' + tab.index).getElementsByTagName('div')[0];
	if (div && hasClass(div, 'message')) removeNode(div);
	container = tab.container;
	if (container.lasttab !== false) {
		removeClass(grab('tab-' + container.lasttab), 'active');
		addClass(grab('panel-' + container.lasttab), 'hidden');
	}
	addClass(tab, 'active');
	removeClass(grab('panel-' + tab.index), 'hidden');
	container.lasttab = tab.index;
}

/* === Onload events === */

function addLoadEvent(func) {
	addEvent(window, 'load', func);
}

function addEvent(element, type, handler) {
    if (!handler.$$guid) handler.$$guid = addEvent.guid++;
    if (!element.events) element.events = {};
    var handlers = element.events[type];
    if (!handlers) {
        handlers = element.events[type] = {};
        if (element["on" + type]) {
            handlers[0] = element["on" + type];
        }
    }
    handlers[handler.$$guid] = handler;
    element["on" + type] = handleEvent;
};
addEvent.guid = 1;

function removeEvent(element, type, handler) {
    if (element.events && element.events[type]) {
        delete element.events[type][handler.$$guid];
    }
};

function handleEvent(event) {
    event = event || window.event;
    var handlers = this.events[event.type];
    for (var i in handlers) {
        this.$$handleEvent = handlers[i];
        this.$$handleEvent(event);
    }
};


// Add's behaviours described above to each page
addLoadEvent(collapse_auto_attach);
addLoadEvent(cancel_auto_attach);
addLoadEvent(calendar_auto_attach);
addLoadEvent(focus_first_form_element);
addLoadEvent(validate_auto_attach);