// Common Javascript functions.

String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,"");
}
String.prototype.ltrim = function() {
	return this.replace(/^\s+/,"");
}
String.prototype.rtrim = function() {
	return this.replace(/\s+$/,"");
}

if(typeof WINGS == "undefined") WINGS = new Object();
if(typeof WINGS.DateFormatOpts == "undefined") WINGS.DateFormatOpts = new Object();
if(typeof WINGS.DateFormatOpts.RawIntl == "undefined") WINGS.DateFormatOpts.RawIntl = false;



// Retrieves which item in a radio button group is selected, by value.
function get_radio_value(radio) {
	for(var idx = 0 ; idx < radio.length ; ++idx) {
		if(radio[idx].checked) return radio[idx].value;
	}
	return null;
}

// Retrives which item in an HTML select group is selected.
function get_select_value(select) {
	return select.options[select.selectedIndex].value;
}

// Selects the radio button matching value.
function set_radio_value(radio, value) {
	for(var idx = 0 ; idx != radio.length ; ++idx) {
		radio[idx].checked = (radio[idx].value == value);
	}
}

// Selects the dropdown item matching value.
function set_select_value(select, value) {
	for(var idx = 0 ; idx < select.options.length ; ++idx) {
		if(select.options[idx].value == value) {
			select.selectedIndex = idx;
			return true;
		}
	}
	return false;
}



// Functions for handling two-part filterable dropdowns.
// Used in places where you can select a family in one dropdown (main), then one of its students in another dropdown (sub).
function subdrop_init(main, sub, map) {
	sub.subdrop_values = new Array();
	
	// Read the current values in the sub and save them.
	for(var idx = 0 ; idx < sub.options.length ; ++idx) {
		sub.subdrop_values[idx] = sub.options[idx];
	}

	// save the map
	sub.subdrop_map = map;
}

function subdrop_update(main, sub) {
	// alert("subdrop_update() called");
	if(!main || !sub || !sub.subdrop_values || !sub.subdrop_map) return;

	var cur_sub = "0";
	if(sub.selectedIndex >= 0) {
		cur_sub = sub.options[sub.selectedIndex].value;
	}

	var cur_main = "0";
	if(main.selectedIndex >= 0) {
		cur_main = main.options[main.selectedIndex].value;
	}

	// Die out of redundant calls to subdrop_update.
	if(typeof main.subdrop_lastval != 'undefined') {
		if(main.subdrop_lastval == cur_main) return;
	}

	main.subdrop_lastval = cur_main;
		
	if(cur_main == "0") {	// Load full list.
		sub.options.length = 0;
		for(var sidx = 0 ; sidx < sub.subdrop_values.length ; ++sidx) {
			sub.options[sidx] = sub.subdrop_values[sidx];
		}
	} else {	// Scan for main and load that list only.
		sub.options.length = 0;
		var fidx;
		for(fidx = sub.subdrop_map.length-1 ; fidx >= 0 ; --fidx) {
			if(sub.subdrop_map[fidx][0] == cur_main) break;
		}

		// Scan through each value in the map  and add the correct option.
		for(var sidx = 0 ; sidx < sub.subdrop_values.length ; ++ sidx) {
			var add = false;
			if(sub.subdrop_values[sidx].value <= 0) {
				add = true;
			} else if(fidx >= 0) {
				for(var lidx = 1 ; lidx < sub.subdrop_map[fidx].length ; ++lidx) {
					if(sub.subdrop_map[fidx][lidx] == sub.subdrop_values[sidx].value) {
						add = true;
						break;
					}
				}
			}
			if(add) {
				sub.options[sub.options.length] = sub.subdrop_values[sidx];
				if(sub.subdrop_values[sidx].value == cur_sub) {
					sub.selectedIndex = sub.options.length - 1;
				}
			}
		}

	}
}




/*
   Uses some code from:
     'Magic' date parsing, by Simon Willison (6th October 2003)
     http://simon.incutio.com/archive/2003/10/06/betterDateInput
   Used with permission

   Adapted to use for WINGS by Daniel J Grace (7th November 2005)



Date formats we want to recognize:

Pattern: yyyymmdd 
- Any 8 digit number where 1300 < yyyy <= 9999

Pattern: mmddyyyy
- Any 8 digit number failing yyyymmdd  (US version)

Pattern: ddmmyyyy
- Any 8 digit number failing yyyymmdd  (international version)

Pattern: yyyy/mm/dd 
- Any 10-digit "1234/12/12" is treated as yyyy/mm/dd.
    Hyphens, spaces, or slashes are all allowed as separator characters, but 
    it  must be the same character in both palces.

- 12/12/1234 is treated as mm/dd/yyyy or dd/mm/yyyy, depending on whether the script is set to US or International dates.
    The first two fields may be one or two characters long -- 1/1/2004 and 01/01/2004 are both valid.
    The year field may be two or four characters long.

- 12-ABC-12 and 12-ABC-1234 are treated as dd-MON-yy and dd-MON-yyyy respectively.





 * yyyymmdd      Detect: 8 char, number, first two digits of "19" or "20"
 * yyyy-mm-dd    Detect: 10 char, 4-2-2
 * yyyy/mm/dd    same
 */

/* Finds the index of the first occurence of item in the array, or -1 if not found */
Array.prototype.indexOf = function(item) {
    for (var i = 0; i < this.length; i++) {
        if (this[i] == item) {
            return i;
        }
    }
    return -1;
};
/* Returns an array of items judged 'true' by the passed in test function */
Array.prototype.filter = function(test) {
    var matches = [];
    for (var i = 0; i < this.length; i++) {
        if (test(this[i])) {
            matches[matches.length] = this[i];
        }
    }
    return matches;
};

/* Debug stuff */
Array.prototype.debug = function() {
	var text = "";
	for(var i = 0; i < this.length; ++i) {
		text += i + ": '" + this[i] + "'\n";
	}
	return text;
}

Object.prototype.debug = function() {
	var text = "";
	for(var i in this) {
		text += i + ": '" + this[i] + "'\n";
	}
	return text;
}

Date.monthNames = "January February March April May June July August September October November December".split(" ");
Date.weekdayNames = "Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" ");

Date.parseInput = function(form, input, output, noreplace) {
	if(document.getElementById) {
		output = document.getElementById(output);
		input  = document.getElementById(input);
	} else {
		input  = form[input];
		if(document.all) {
			output = document.all[output];
		} else if(document.layers) {
			output = document.all[output];
		} else {
			output = null;
		}
	}
	function setOutputText(output, text, critical) {
		if(output) {
			if(output.childNodes) {
				for(var idx = 0 ; idx < output.childNodes.length; ++idx) {
					if(output.childNodes[idx].nodeType == 3) {
						output.childNodes[idx].nodeValue = text;
					}
				}
				return;
			} 
			if(output.innerHTML) {
				output.innerHTML = text;
				return;
			}
		}
		if(critical && !noreplace) alert(text);
	}

	var text = new RegExp("^\\s*(\\S*(.*\\S)?)\\s*$").exec(input.value)[1];
	if(!text) {
		if(input) {
			input.className = remove_class(remove_class(input.className, "dateok"), "dateerror");
		}
		if(output) {
			output.className = remove_class(remove_class(output.className, "dateok"), "dateerror");
			setOutputText(output, WINGS.DateFormatOpts.RawIntl ? "(dd/mm/yyyy)" : "(mm/dd/yyyy)", false);
		}
		return;
	}

	var rv = this.parseText(text);
	if(input) input.parseDateResult = rv;

	if(rv instanceof Date) {
		// el yay! it worked.
		if(!noreplace) {
			var zeroes = "0000";
			var year = rv.getFullYear().toString();
			var month = (rv.getMonth()+1).toString();
			var day = (rv.getDate().toString());

			year  = "" + zeroes.substr(0, (4 - year.length)  > 0 ? (4 - year.length)  : 0) + year;
			month = "" + zeroes.substr(0, (2 - month.length) > 0 ? (2 - month.length) : 0) + month;
			day   = "" + zeroes.substr(0, (2 - day.length)   > 0 ? (2 - day.length  ) : 0) + day;
			
			if(WINGS.DateFormatOpts.RawIntl) {
				input.value = day + "/" + month + "/" + year;
			} else {
				input.value = month + "/" + day + "/" + year;
			}
		}

		if(input) input.className = remove_class(add_class(input.className, "dateok"), "dateerror");
		if(output) output.className = remove_class(add_class(output.className, "dateok"), "dateerror");

		setOutputText(output, this.weekdayNames[rv.getDay()].substr(0, 3) + ", " + this.monthNames[rv.getMonth()].substr(0, 3) + " " + rv.getDate() + ", " + rv.getFullYear(), false);
	} else {
		if(input) input.className = add_class(remove_class(input.className, "dateok"), "dateerror");
		if(output) output.className = add_class(remove_class(output.className, "dateok"), "dateerror");

		setOutputText(output, rv, true);
	}		
}

Date.parseText = function(text) {
	var sep = "[-\/.,\\s]+"

	text = text.toLowerCase();

	// Returns a valid date object if it was able to parse
	// Otherwise returns a string describing the error.
	// We would use exceptions, but not all older browser support them.

	var today = new Date(), rv = new Date();
	var day = month = year = 0;
	var swap = false;
	var result;

	// Test for 4, 6 and 8-digit raw numbers
	result = new RegExp("^(\\d{2})(\\d{2})((\\d{2})(\\d{2})?)?$", "g").exec(text);

	if(result) {
		var p1 = result[1];
		var p2 = result[2];
		// result[3] is dummy junk to ensure that the results are in a consistent order.
		var p3 = result[4];	
		var p4 = result[5];

		if(p3 == undefined || !p3.length) {	// 4-digit date, ew.
			if(WINGS.DateFormatOpts.RawIntl) {
				day = parseInt(p1, 10);
				month = parseInt(p2, 10);
			} else {
				month = parseInt(p1, 10);
				day = parseInt(p2, 10);
			}
			year = today.getFullYear();
		} else if(p4 == undefined || !p4.length) {	// 6 digit date (2 digit year)
			if(WINGS.DateFormatOpts.RawIntl) {
				day = parseInt(p1, 10);
				month = parseInt(p2, 10);
			} else {
				month = parseInt(p1, 10);
				day = parseInt(p2, 10);
			}
			year = parseInt(p3, 10) + 1900;
			if(year < 1930) year += 100;
		} else {	// 8 digit date (4 digit year)
			// See if the year is the first component.
			year = (parseInt(p1, 10)*100) + parseInt(p2, 10);
			month = parseInt(p3, 10);
			day = parseInt(p4, 10);
		}

		return this.check(year, month, day);
	}

	// Test for 3-component numeric dates
	result = new RegExp("^(\\d{1,4})" + sep + "(\\d{1,4})" + sep + "(\\d{1,4})$", "g").exec(text);
	if(result) {
		var p1 = result[1];
		var p2 = result[2];
		var p3 = result[3];
		
		if(p1.length == 4) {	// Year is first component.  yyyy-mm-dd
			year = parseInt(p1, 10);
			month = parseInt(p2, 10);
			day = parseInt(p3, 10);

			return this.check(year, month, day);
		}

		// Otherwise, it's mm-dd-yy or dd-mm-yy
		if(WINGS.DateFormatOpts.RawIntl) {
			day = parseInt(p1, 10);
			month = parseInt(p2, 10);
		} else {
			month = parseInt(p1, 10);
			day = parseInt(p2, 10);
		}
		year = parseInt(p3, 10);
		if(year < 100) {
			year += 1900;
			if(year < 1930) year += 100;
		}

		return this.check(year, month, day);
	}

	// Test for dates containing the actual month name.

	// DD-MONTH-YY, DDth MONTH YYYY, YYYY MONTH DD, etc.
	result = new RegExp("^((\\d{1,4})(st|nd|rd|th)?)" + sep + "(of" + sep + ")?([a-zA-Z]+)" + sep + "((\\d{1,4})(st|nd|rd|th)?)?$", "ig").exec(text);
	if(result) {
		month = result[5];
		if(result[2].length == 4 && result[7]) {
			year = parseInt(result[2], 10);
			day  = parseInt(result[7], 10);
		} else {
			day  = parseInt(result[2], 10);
			if(result[7]) {
				year = parseInt(result[7], 10);
				if(year < 100) {
					year += 1900;
					if(year < 1930) year += 100;
				}
			} else {
				year = today.getFullYear();
			}
		}
	} else {
		// Test for MONTH DD YYYY
		result = new RegExp("^([a-zA-Z]+)" + sep + "((\\d{1,4})(st|nd|rd|th)?)(" + sep + "(\\d{1,4}))?$", "ig").exec(text);
		if(result) {
			month = result[1];
			day   = parseInt(result[3], 10);
			if(result[6]) {
				year = parseInt(result[6], 10);
				if(year < 100) {
					year += 1900;
					if(year < 1930) year += 100;
				}
			} else {
				year = today.getFullYear();
			}
		}
	}
	if(result) {	// If one of the above matched.
		var matches = this.monthNames.filter(function(item) { 
			return new RegExp("^" + month, "i").test(item);
		});

		switch(matches.length) {
			case 0: return "Invalid month.";
			case 1:	return this.check(year, this.monthNames.indexOf(matches[0])+1, day);
			default: return "Ambiguous month."; break;
		}
	}

	// Today, Yesterday, Tomorrow
	if(text.length > 2 && "today".substr(0, text.length) == text) {
		return today;
	}

	if(text.length > 0 && "yesterday".substr(0, text.length) == text) {
		rv = today;
		rv.setDate(rv.getDate()-1);
		return rv;
	}

	// we allow the incorrect spelling of 'tommorow' as well.
	if(text.length > 2 && ("tomorrow".substr(0, text.length) == text || "tommorow".substr(0, text.length) == text)) {
		rv = today;
		rv.setDate(rv.getDate()+1);
		return rv;
	}


	// A weekday.
	result = new RegExp("^((last|this|next)" + sep + ")?([a-zA-Z]+)(" + sep + "of)?(" + sep + "(.*))?").exec(text);
	if(result) {
		var weekday;
		// First of all, see if it's a valid weekday.
		var matches = this.weekdayNames.filter(function(item) { 
			return new RegExp("^" + result[3], "i").test(item);
		});

		switch(matches.length) {
			case 0: return "Invalid date.";
			case 1: weekday = this.weekdayNames.indexOf(matches[0]); break;
			default: return "Ambiguous day of the week."; break;
		}

		// If we have a date component, use it.
		if(result[6]) {
			base = this.parseText(result[6]);
			if(!(base instanceof Date)) return base;
		} else {	// otherwise, use today's date.
			base = today;
		}

		// Set the date to Sunday.
		base.setDate(base.getDate() - base.getDay());

		// Handle last/next
		if(result[2] == 'last') { base.setDate(base.getDate() - 7); }
		else if(result[2] == 'next') { base.setDate(base.getDate() + 7); }

		// Add weekday to the date to get the correct day.
		base.setDate(base.getDate() + weekday);
		
		return base;
	}
	return "Invalid date format";
}

Date.check = function(year, month, day) {
	if(year < 1910 || year > 2037) return "Invalid year (1910-2037)";
	if(month < 1 || month > 12) return "Invalid month";
	if(day < 1) return "Invalid day";

	// If we made it this far, it LOOKS like a valid date.
	// The only error possible is too many days in a month.

	rv = new Date(year, month-1, day);

	// Check to see if things match up.
	if(rv.getMonth() != month-1 || rv.getFullYear() != year) {	// They don't.
		// Figure out what the last day of the month WOULD be.
		lastday = new Date(year, month, 0).getDate();
		return this.monthNames[month-1] + " " + year + " has " + lastday + " days.";
	}

	return rv;
}





Date.parseInputTime = function(form, input, output, noreplace, useSeconds) {
	if(document.getElementById) {
		output = document.getElementById(output);
		input  = document.getElementById(input);
	} else {
		input  = form[input];
		if(document.all) {
			output = document.all[output];
		} else if(document.layers) {
			output = document.all[output];
		} else {
			output = null;
		}
	}
	function setOutputText(output, text, critical) {
		if(output) {
			if(output.childNodes) {
				for(var idx = 0 ; idx < output.childNodes.length; ++idx) {
					if(output.childNodes[idx].nodeType == 3) {
						output.childNodes[idx].nodeValue = text;
					}
				}
				return;
			} 
			if(output.innerHTML) {
				output.innerHTML = text;
				return;
			}
		}
		if(critical && !noreplace) alert(text);
	}

	var text = new RegExp("^\\s*(\\S*(.*\\S)?)\\s*$").exec(input.value)[1];
	if(!text) {
		if(input) {
			input.className = remove_class(remove_class(input.className, "dateok"), "dateerror");
		}
		if(output) {
			output.className = remove_class(remove_class(output.className, "dateok"), "dateerror");
			setOutputText(output, useSeconds ? "(hh:mm:ss AM/PM)" : "(hh:mm AM/PM)", false);
		}
		return;
	}


	var rv = this.parseTextTime(text);

	if(rv instanceof Date) {
		// el yay! it worked.
		var minute = rv.getMinutes().toString();
		var second = rv.getSeconds().toString();
		if(minute.length == 1) minute = "0" + minute;
		if(second.length == 1) second = "0" + second;

		var hour = rv.getHours() % 12;
		if(hour == 0) hour = 12;
		hour = hour.toString();

		var reformatted = hour + ":" + minute + (useSeconds ? (":" + second) : "") + (rv.getHours()<12 ? " AM" : " PM");
		if(!noreplace && input) input.value = reformatted;
		setOutputText(output, reformatted);

		if(input) input.className = remove_class(add_class(input.className, "dateok"), "dateerror");
		if(output) output.className = remove_class(add_class(output.className, "dateok"), "dateerror");
	} else {
		if(input) input.className = add_class(remove_class(input.className, "dateok"), "dateerror");
		if(output) output.className = add_class(remove_class(output.className, "dateok"), "dateerror");

		setOutputText(output, rv, true);
	}		
}

Date.parseTextTime = function(text, useSeconds) {
	var sep = "[-\/.,\\s]+"

	text = text.toLowerCase();

	// Returns a valid date object if it was able to parse
	// Otherwise returns a string describing the error.
	// We would use exceptions, but not all older browser support them.
	//
	// The date object is only relevant for day, hour and minute.
	// It is seeded on 2001-01-01.
	// This is to avoid issues with DST and such.


	var hour = minute = second = null;
	var ampm = null;	// If not present, 24hr time.  Else, 'a' = am, 'p' = pm
	var swap = false;
	var result;

	// If the text is 'now', we can simply return the current time.
	if(text == 'now') return new Date();

	// First, we scan for a, am, a.m, a.m., p, pm, p.m.  either at the end or beginning of the field.
	// If we find one, we set ampm to 'am' or 'pm'
	// If we find more than one and they conflict, we report an error.
	// If we don't find any, it's a 24hr time.

	result = new RegExp("^([ap]\\.m\\.?|[ap]m?)?\\s*(\\S*?)\\s*([ap]\\.m\\.?|[ap]m?)?$").exec(text);
	if(!result) return "Invalid time format.";

	if(!result[1] && !result[3]) {
		ampm = null;
	} else if(result[1]) {
		ampm = result[1].charAt(0);
		if(result[3] && result[3].charAt(0) != ampm) {
			return "AM and PM cannot both be specified.";
		}
	} else if(result[3]) {
		ampm = result[3].charAt(0);
	}

	// get remaining text
	text = result[2];
	
	// We'll accept times in one of four formats:
	// HHMM or HHMMSS without any delineation, or:
	// HH*MM or HH*MM*SS  where "*" is either a colon or dot.  (Hour may be a one-digit number in this case only)

	// Test for 4 and 6-digit raw numbers (HHMM and HHMMSS)
	if(result = new RegExp("^(\\d{2})(\\d{2})(\\d{2})?$", "g").exec(text)) {
		hour = parseInt(result[1], 10);
		minute = parseInt(result[2], 10);
		second = (result[3] == undefined) ? 0 : parseInt(result[3], 10);
	} else if(result = new RegExp("^(\\d{1,2})[.:](\\d{2})([.:](\\d{2}))?$", "g").exec(text)) {
		hour = parseInt(result[1], 10);
		minute = parseInt(result[2], 10);
		second = (result[4] == undefined) ? 0 : parseInt(result[4], 10);
	} else {
		return "Invalid time format.";
	}

	// Check hour, minute, second to see if they are valid.
	if(!useSeconds) second = 0;

	if(ampm == null) { // Military time
		if(hour < 0 || hour > 23) return "Hour must be between 0 and 23";
	} else {
		if(hour < 1 || hour > 12) return "Hour must be between 1 and 12";
	}
	if(minute < 0 || minute > 59) return "Minute must be between 0 and 59";
	if(second < 0 || second > 59) return "Second must be between 0 and 59";

	// Figure out what hour it REALLY is.
	if(ampm == "p") {	// P.M
		if(hour != 12) hour += 12;	//12 PM is really noon, but 1 PM is 1300, 2 PM is 1400...
	} else if(ampm == "a") {	// A.M.
		if(hour == 12) hour = 0;	//12 AM is really midnight.
	} // Military time is always correct.

	// Return correct date value
	return new Date(2000, 0, 1, hour, minute, second)
}

function get_classes(classlist) {
	return classlist.split(new RegExp("\\s+"));
}

function has_class(classlist, checkclass) {
	classlist = get_classes(classlist);
	// Process the array backwards to get around array key renumbering with splice()
	for(var pos = classlist.length - 1 ; pos >= 0 ; --pos) {
		if(classlist[pos] == checkclass) return true;
	}
	return false;
}

function add_class(classlist, newclass) {
	if(has_class(classlist, newclass)) return classlist;
	return classlist + " " + newclass;
}

function remove_class(classlist, delclass) {
	classlist = get_classes(classlist);
	// Process the array backwards to get around array key renumbering with splice()
	for(var pos = classlist.length - 1 ; pos >= 0 ; --pos) {
		if(classlist[pos] == delclass) classlist.splice(pos, 1);
	}
	return classlist.join(" ");
}

function toggle_element_class(element, className, toggle) {
	if(!element) return false;

	if(toggle) {
		element.className = remove_class(element.className, className);
	} else {
		element.className = add_class(element.className, className);
	}

	return true;
}

function find_element_by_id(id) {
	if(document.getElementById) {
		return document.getElementById(id);
	} else if(document.all) {
		return document.all[id];
	} else if(document.layers) {
		return document.layers[id];
	}
	return null;
}

function enable_element(element, enable) { return toggle_element_class(element, 'hidden', enable); }
function show_element(element, show) { return toggle_element_class(element, 'invisible', show); }
function enable_id(id, enable) { return enable_element(find_element_by_id(id), enable); }
function show_id(id, show) { return show_element(find_element_by_id(id), show); }

// AJAX
function create_request() {
	var rv, e;
	/*@cc_on @*/
	/*@if (@_jscript_version >= 5)
	// JScript gives us Conditional compilation, we can cope with old IE versions.
	// and security blocked creation of the objects.
	try {
		rv = new ActiveXObject("Msxml2.XMLHTTP");
	} catch (e) {
		try {
			rv = new ActiveXObject("Microsoft.XMLHTTP");
		} catch (e) {
			return false;
		}
	}
	@end @*/
	if(rv) return rv;

	if(typeof XMLHttpRequest!='undefined') {
		try {
			rv = new XMLHttpRequest();
			return rv;
		} catch (e) {
			return false;
		}
	}

	if(window.createRequest) {
		try {
			rv = window.createRequest();
			return rv;
		} catch (e) {
			return false;
		}
	}
	return false;
}

// For some ridiculous reason, neither importNode and cloneNode properly copy elements from one document to another.
// While text nodes copy, element nodes do not (even though their contained text nodes do)
// This function is a quick hack that deep-copies elements using a special non-recursive algorithm.
// doc = document receiving the copy.  The nodes won't be appended anywhere
// root = root node of the source of the copy.
// includeRoot = TRUE if root should be copied, FALSE if only its descendants should be copied.
function dom_deep_copy(doc, root, includeRoot) {
	var rv = doc.createDocumentFragment();

	var scur;	// Current source node
	var dcur = null;	// Current destination node
	var dpar;	// Current destination node's parent.

	scur = root;
	dpar = rv;
	
	do {
		if(scur != root || includeRoot) {
			switch(scur.nodeType) {
				case 1:	// ELEMENT_NODE
					dcur = doc.createElement(scur.nodeName);
					dpar.appendChild(dcur);
					for(var i = 0 ; i < scur.attributes.length ; ++i) {
						dcur.setAttribute(scur.attributes[i].name, scur.attributes[i].value);
					}
				break;

				case 2:	// ATRIBUTE_NODE
				break;	// Theoretically, this was already handled.

				case 3: // TEXT_NODE
					dpar.appendChild(doc.createTextNode(scur.nodeValue));
				break;

				case 4:	// CDATA_SECTION_NODE
					dpar.appendChild(doc.createCDATASection(scur.nodeValue));
				break;

				case 5: // ENTITY_REFERENCE_NODE
					dpar.appendChild(doc.createEntityReference(scur.nodeName));
				break;
				// Other node types need not be supported.
			}
		}

		// Recurse, derecurse, or circulate.
		if(scur.firstChild) {	// We have children...
			if(dcur) dpar = dcur;
			scur = scur.firstChild;
			continue;
		}

		while(scur != root) {
			if(scur.nextSibling) {
				scur = scur.nextSibling;
				break;
			}
			if(!scur.parentNode) {
				scur = root;	// force the outer loop to die.
				break;
			}
			scur = scur.parentNode;
			dpar = dpar.parentNode;
		}
	} while(scur != root);

	return rv;
}



// Code for intelligently linking dropdowns that have an 'other' option, and the 'other' option.
// Takes four arguments: 'ctlDropdown' (link to the dropdown), 'ctlTextbox' (link to the textbox), 
// 'otherId' (what value in the dropdown corresponds to the 'other' option), and 'wasText' (boolean value
// that is TRUE if the textbox was updated, FALSE if the dropdown was)
// Works on the following logic:
// 1) If text is entered in the 'other' box that corresponds to an option in the dropdown, the text is
//    cleared and the dropdown is altered to that setting.
// 2) If text is entered in the 'other' box that does not correspond to an option in the dropdown, the
//    dropdown is automatically altered to the 'other' option
// 3) If any selection other than 'other' is made in the dropdown, the text is cleared.
function dropdown_other_update(ctlDropdown, ctlTextbox, otherId, textWasUpdated) {
	if(textWasUpdated) {
		if(ctlTextbox.value.length) {
			if(set_select_value(ctlDropdown, this.value)) {
				ctlTextbox.value='';
			} else {
				set_select_value(ctlDropdown, otherId);
			}
		}
	} else {
		if(get_select_value(ctlDropdown) != otherId) {
			ctlTextbox.value='';
		}
	}
}

function popup_event(id) {
	var eventWindow = window.open("event_view.php?close=2&id=" + id, "eventPopupWindow", "width=450,height=500,toolbar=no,status=no,location=no,resizable=1,scrollbars=1", false);
	if(!eventWindow) {
		return true;	// Popup blockers.
	}
	if(window.focus) eventWindow.focus();
	return false;
}

function popup_news(id) {
	var newsWindow = window.open("news_view.php?close=2&id=" + id, "newsPopupWindow", "width=450,height=500,toolbar=no,status=no,location=no,resizable=1,scrollbars=1", false);
	if(!newsWindow) {
		return true;	// Popup blockers.  Bleh.
	}
	if(window.focus) newsWindow.focus();
	return false;
}

function popup_date_help() {
	var dateHelpWindow = window.open("help_dates.php", "datePopupWindow", "width=450,height=500,toolbar=no,status=no,location=no,resizable=1,scrollbars=1", false);
	if(!dateHelpWindow) {
		return true;	// Popup blockers.  Bleh.
	}
	if(window.focus) dateHelpWindow.focus();
	return false;
}

function write_close_button() {
	// in a function due to HTML characters...
	document.write("<p>[ <a href=\"javascript:window.close();\">Close</a> ]</p>");
}

// mode 0 = select none
// mode 1 = select all
// mode 2 = invert
// mode -1 = count how many are selected
function select_all(ctl, mode) {
	if(!ctl) return;
	if(!ctl.length) {
		ctl.length = 1;
		ctl[0] = ctl;
	}

	var numselected = 0;

	for(var idx = 0 ; idx < ctl.length ; ++idx) {
		switch(mode) {
			case 0: ctl[idx].checked = false; break;
			case 1: ctl[idx].checked = true; break;
			case 2: ctl[idx].checked = !ctl[idx].checked; break;
			case -1: break;
		}
		if(ctl[idx].onclick) ctl[idx].onclick();
		if(ctl[idx].checked) ++numselected;
	}

	return ctl.numSelected = numselected;
}

// Count how many checkboxes in a group.
// Code readability version of select_all(ctl, -1)
function count_selected(ctl) { return select_all(ctl, -1); }

// Update the count of how many checkboxes in a group are selected.
// add onclick="update_checkbox_count(ctl, this.checked);" to checkboxes being tracekd.
function update_checkbox_count(ctl, isChecked) {
	if(!ctl) return;
	if(isChecked) {
		if(!ctl.numSelected) ctl.numSelected = 1;	// Ensure that some sort of initialization is done.
		else ++ctl.numSelected;
	} else {
		if(!ctl.numSelected | ctl.numSelected < 1) ctl.numSelected = 0;	// Initialize, and prevent negative values.
		else --ctl.numSelected;
	}
	return true;
}

function write_select_all(ctlname, includeInvert) {
	document.write('<p>');
	document.write('[ <a href="javascript:void(select_all(' + ctlname + ', 1));">Select All</a> ]');
	document.write(' [ <a href="javascript:void(select_all(' + ctlname + ', 0));">Select None</a> ]');

	if(includeInvert) document.write(' [ <a href="javascript:void(select_all(' + ctlname + ', 2));">Invert Selection</a> ]');
	document.write('</p>');
}


//
//*jpl 112808
//
// This function added to provide consistancy across
// browsers for where the carret is set when 
// putting the focus on text fields.
//
function setFocusAtEndOfText( field )
{
    if ( null == field )
        return;
        
        
    if ( null != field.createTextRange )
    {
        var oFieldRange = field.createTextRange();
        
        oFieldRange.moveStart( 'character', field.value.length );
        oFieldRange.collapse();
        oFieldRange.select();
    }
    else
    {
        field.focus();
    }
}

// djg addendum derived from above:
// Finds the first visible and enabled 'input' element with the 'focus' class and calls setFocusAtEndOfText() on it
function autoSetFocus() {
	if(!document) return false;
	if(!document.getElementsByTagName) return false;
	
	var textboxes = document.getElementsByTagName('input');
	var first_textbox, e, cn;
	for(var idx = 0 ; idx < textboxes.length; ++idx) {
		e = textboxes[idx];
		cn = e.className;
		if(!cn) continue;
		
		if(
			!has_class(cn, 'invisible')
			&& !e.disabled
			&& e.getAttribute('type')=='text'
		) {
			if(!first_textbox) first_textbox = e;
		
			if(has_class(cn, 'focus')) {
				setFocusAtEndOfText(e);
			}
			return;
		}
	}
	// If we're stil lhere, nothing had the 'focus' attribute so take the first textbox we found
	if(first_textbox) setFocusAtEndOfText(first_textbox);
}
