////////////////////////////////////////////////////////////////////////////////
//  HANG WIRE HTML HELPERS

function HW_HTML_Helpers()
{
	this.unique_number_value = 0;
	this.onload_tasks = new Object();
	this.load_complete = false;
	this.max_zIndex = 0;
	this.min_zIndex = 0;
	this.draggers = new Array();

	this.nbsp = "\u00a0";				// nonbreaking space character

	function unique_number()
	{
		this.unique_number_value++;
		return this.unique_number_value;
	}
	this.unique_number = unique_number;

	function add_onload_task(func, cut_in_line, win)
	{
		win = win || window;
		cut_in_line = cut_in_line || false;		// if true, puts this function first in the queue
		var name = win.name || 'main';
		if(!this.onload_tasks[name])
		{
			this.onload_tasks[name] = new Array();
		}

		if(cut_in_line)
		{
			this.onload_tasks[name].unshift(func);
		}
		else
		{
			this.onload_tasks[name].push(func);
		}

		var t = this;
		win.onload = function () {
			for(var i = 0; i < t.onload_tasks[name].length; i++)		// Perform onload tasks in order
			{
				t.onload_tasks[name][i]();										// Do the task
			}
			t.load_complete = true;										// Mark indicator that window has finished loading
		}
	}
	this.add_onload_task = add_onload_task;
	
	function remove_all_children(obj)
	{
		// Removes all children, recursively
		for(var i=obj.children.length - 1; i>=0; i--)
		{
			this.remove_all_children(obj.children[i]);
			obj.removeChild(obj.children[i]);
		}
	}
	this.remove_all_children = remove_all_children;

	function bring_to_front(obj)
	{
		obj.style.zIndex = ++this.max_zIndex;
	}
	this.bring_to_front = bring_to_front;

	function send_to_back(obj)
	{
		obj.style.zIndex = --this.min_zIndex;
	}
	this.send_to_back = send_to_back;

	function create_dragger(dragbar, dragelem)
	{
		var lgth = this.draggers.length;
		this.draggers[lgth] = new HW_Dragger(dragbar, dragelem);
		return lgth;
	}
	this.create_dragger = create_dragger;

	function get_absolute_screen_position(elem)
	{
		var el = elem;
		var ret = new Object();
		ret.left = 0;
		ret.top = 0;
		while(el != document)
		{
			if(el.offsetTop && el.offsetLeft)
			{
				ret.left += el.offsetLeft;
				ret.top += el.offsetTop;
			}
			el = el.parentNode;
		}
		return ret;
	}
	this.get_absolute_screen_position = get_absolute_screen_position;

	function get_screen_scrolling_position()
	{
		var ret = new Object();
		if(window.pageYOffset)
		{
			ret.left = self.pageXOffset;
			ret.top = self.pageYOffset;
		}
		else if(document.documentElement && document.documentElement.scrollTop)
		{
			ret.left = document.documentElement.scrollLeft;
			ret.top = document.documentElement.scrollTop;
		}
		else if(document.body)
		{
			ret.left = document.body.scrollLeft;
			ret.top = document.body.scrollTop;
		}
		return ret;
	}
	this.get_screen_scrolling_position = get_screen_scrolling_position;

	function urlencode(str)
	{
		var s = str.toString();
		var ret = '';
		var hx = null;
		var c = null;
		for(var i=0; i<s.length; i++)
		{
			c = s.charAt(i);
			if((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '.' || c == '-' || c == '_') 
			{
				// Character OK
				ret = ret + c;
			}
//			else if(s.charCodeAt(i)==32)		// for some reason, this doesn't work, so just use %20 for space
//			{
//				// Space becomes +
//				ret = ret + '+';
//			}
			else
			{
				// Other character requires a hex code
				hx = s.charCodeAt(i).toString(16);
				ret = ret + '%' + (hx.length==1 ? '0' : '') + hx.toUpperCase();
			}
		}
		return ret;
	}
	this.urlencode = urlencode;

	function alert(html, title, cssclass, onclick, w)
	{
		var popup = new HW_PopUp(title || 'System Message', null, w || window);
		popup.set_dimensions(200, 100);
		var m = w.document.createElement('p');
		m.innerHTML = html;
		popup.contentdiv.appendChild(m);
		var p = w.document.createElement('p');
		p.style.textAlign = 'center';
		popup.contentdiv.appendChild(p);
		var b = new Button();
		b.onclick = function () { popup.hide(); }
		b.value = 'OK';
		p.appendChild(b);
		popup.contentdiv.className = cssclass || 'HW_alert_div_class';
		if(onclick)
		{
			popup.onhide = onclick;
		}
		popup.show();
	}
	this.alert = function (h, t, c, o, w) { this.alert(h, t || 'Alert', c, o, w); }
	this.message = function (h, t, c, o, w) { this.alert(h, t || 'Message', c, o, w); }
}

var HangWire = new HW_HTML_Helpers();



////////////////////////////////////////////////////////////////////////////////
//  HTTP REQUEST OBJECT


function HW_http_state_change(hwhttp)
{
	if(hwhttp.xmlhttp.readyState == 4)
	{
		if(hwhttp.xmlhttp.status != 200)
		{
			hwhttp.error = 'Error: ' + hwhttp.xmlhttp.status + ' ' + hwhttp.xmlhttp.statusText + '; status ' + hwhttp.xmlhttp.status;
		}
		else
		{
			hwhttp.error = false;
		}
		hwhttp.on_response(hwhttp.use_xml_response ? hwhttp.xmlhttp.responseXML : hwhttp.xmlhttp.responseText);
	}
}

function HW_http_request(url, uname, pwd)
{
	var cont = '';
	for(nm in this.content)
	{
		cont = cont + '&' + nm + '=' + this.content[nm];
	}
	this.xmlhttp.open(this.method, url + '?' + cont.substring(1), this.async, uname || null, pwd || null);
	for(nm in this.headers)
	{
		this.xmlhttp.setRequestHeader(nm, this.headers[nm]);
	}
	this.xmlhttp.send(null); //cont.length > 0 ? cont.substring(1) : null);
}

function HW_http_add_header_value(name, val)
{
	this.headers[name] = val;
}

function HW_http_add_value(name, val)
{
	this.content[name] = val;
}

function HW_HTTP()
{
	this.error = false;
	if(window.XMLHttpRequest)		// Mozilla, etc.
	{
		this.xmlhttp = new XMLHttpRequest();
	}
	else if(window.ActiveXObject)	// IE
	{
		this.xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	}
	if(this.xmlhttp)
	{
		var t = this;
		this.xmlhttp.onreadystatechange = function () { HW_http_state_change(t); }
	}
	else
	{
		this.error = "Browser doesn't support XML HTTP requests.";
	}

	this.request = HW_http_request;
	this.add_value = HW_http_add_value;
	this.add_header_value = HW_http_add_header_value;
	this.on_response = function (x) {}

	this.method = 'GET';
	this.async = true;
	this.use_xml_response = false;

	this.headers = new Object();
	this.content = new Object();
}

	

////////////////////////////////////////////////////////////////////////////////
//  HW SELECTION RANGE
//  An attempt to iron out the various incompatible implementations of selections and ranges


	
window.HW_Range = function (doc, anchorelem)
{
	this.doc = doc || window.document;
	
	if(!anchorelem)
	{
		// Use current user selection
		if(window.getSelection) {				// W3C-compliant
			
			var sel = window.getSelection();
			
			if(sel.getRangeAt) {
				this.range = sel.getRangeAt(0);
			}
			else { // Safari
				var range = this.doc.createRange();
				range.setStart(selectionObject.anchorNode,selectionObject.anchorOffset);
				range.setEnd(selectionObject.focusNode,selectionObject.focusOffset);
				this.range = range;
			}
		}
		else if(this.doc.selection) {			// IE, maybe Opera
			
			this.range = this.doc.selection.createRange(); 
			//alert(this.doc.selection);
			
			this.ms_bookmark = this.range.getBookmark();
			
			this.select = function () {			//select() function for IE
				
				this.doc.selection.moveToBookmark(this.ms_bookmark);
			}
		}
	}

}



////////////////////////////////////////////////////////////////////////////////
//  DRAGGER OBJECT
//  Makes any element into a drag-and-drop dragger


function HW_drag_Drop_Record(dropelem, onmouseover, onmouseout, ondrop)
{
	// Drop element record
	this.elem = dropelem;
	this.onmouseover = onmouseover || function (el) {};
	this.onmouseout = onmouseout || function (el) {};
	this.ondrop = ondrop || function (el) {};
	this.currently_in = false;
	this.top = 0;
	this.bottom = 0;
	this.left = 0;
	this.right = 0;

	// Methods
	this.get_position = function () {
		var pos = HangWire.get_absolute_screen_position(this.elem);
		this.left = pos.left;
		this.top = pos.top;
		this.right = x + el.offsetWidth;
		this.bottom = y + el.offsetHeight;
	}
}

function HW_drag_is_in_drop(dropid, dragx, dragy)
{
	// Sees whether the dragger is currently within the drop element with id #dropid in the dropelements array
	
	// Get absolute screen positions x and y for comparison
	var x = dragx + this.drag_screen_offset.left;
	var y = dragy + this.drag_screen_offset.top;

	return (x >= this.dropelements[dropid].left && x <= this.dropelements[dropid].right && y >= this.dropelements[dropid].top && y <= this.dropelements[dropid].bottom);
}

function HW_drag_find_top_mouseover()
{
	// Finds the top z-index element that the dragger is currently moused over
	// Returns the array index of the drop-element record in the dragger object, or null
	var maxzix = 'N';
	var ret = null;
	for(var i=0; i<this.dropelements.length; i++)
	{
		if(this.is_in_drop(i))
		{
			var zix = this.dropelements[i].elem.style.zIndex || 0;
			if(isNaN(maxzix) || (zix > maxzix))
			{
				maxzix = zix;
				ret = i;
			}
		}
	}
	return ret;
}

function HW_drag_add_drop_element(dropelem, onmouseover, onmouseout, ondrop)
{
	// Adds a drop element to the dragger, and causes the dragger to watch for when the mouse is over that element
	this.dropelements[this.dropelements.length] = new HW_drag_Drop_Record(dropelem, onmouseover, onmouseout, ondrop);
}

function HW_drag_drag(ev)
{
	var e = window.event || ev;			// IE || Netscape

	// Move the element
	var x = this.drag_orig_x + e.clientX - this.drag_offset_x;
	var y = this.drag_orig_y + e.clientY - this.drag_offset_y;
	this.dragelem.style.left = x + 'px';
	this.dragelem.style.top = y + 'px';

	// Check whether it's in a drop element
	var is_in = false;
	for(var i = 0; i < this.dropelements.length; i++)
	{
		// See if it's in the drop element
		is_in = this.is_in_drop(i, x, y);
		if(!this.dropelements[i].currently_in && is_in)
		{
			// Mouseover for this drop
			this.dropelements[i].currently_in = true;
			this.dropelements[i].onmouseover(this.dropelements[i].elem);
		}
		else if(this.dropelements[i].currently_in && !is_in)
		{
			// Mouseout for this drop
			this.dropelements[i].currently_in = false;
			this.dropelements[i].onmouseout(this.dropelements[i].elem);
		}
	}

	// Return false, so mouse can actually move
	return false;
}

function HW_drag_drag_begin(ev)
{
	var e = window.event || ev;			// IE || Netscape
	var t = this;
	
	// Bring drag element to front
	HangWire.bring_to_front(this.dragelem);

	// Set drag offsets
	this.drag_offset_x = e.clientX;
	this.drag_offset_y = e.clientY;

	// Assign defaults to top and left style elements
	this.dragelem.style.left = this.dragelem.style.left || '0px';
	this.dragelem.style.top = this.dragelem.style.top || '0px';

	// Set drag original values
	this.drag_orig_x = parseInt(this.dragelem.style.left);
	this.drag_orig_y = parseInt(this.dragelem.style.top);

	// Get absolute screen offset - what to add to the numbers we're passing to get the absolute offset from the screen origin
	this.drag_screen_offset = HangWire.get_absolute_screen_position(this.dragelem);
	this.drag_screen_offset.left -= this.drag_orig_x;
	this.drag_screen_offset.top -= this.drag_orig_y;

	// Set initial positions of drop elements
	for(var i=0; i<this.dropelements.length; i++)
	{
		this.dropelements[i].get_position();
	}

	// Set document.onmousemove and document.onmouseup
	// Note that only one dragger can be dragged at the same time: this stands to reason
	this.w.document.onmousemove = function (e) { t.drag(e); }
	this.w.document.onmouseup = function () {
		t.w.document.onmousemove = null;
		t.w.document.onselectstart = null;
		t.dragelem.style.zIndex = t.normal_z_index;
		var targ = t.find_top_mouseover();
		if(targ && t.dropelements[targ])
		{
			t.dropelements[targ].ondrop(t.dropelements[targ].elem);
		}
	}

	// Prevent selection of text during dragging
	document.body.focus();
	document.onselectstart = function () { return false; }		// IE
	return false;												// non-IE
}

function HW_Dragger(dragbar, dragelem, w)
{
	// Causes a drag-and-drop operation on dragbar to drag and drop dragelem; if dragelem not given, assume they're the same

	// Parameters
	this.dragbar = dragbar;
	this.dragelem = dragelem || dragbar;
	this.normal_z_index = dragelem.style.zIndex;
	this.w = w || window;

	// Properties
	var t = this;				// for function closures within the constructor
	this.drag_offset_x = 0;
	this.drag_offset_y = 0;
	this.drag_orig_x = 0;
	this.drag_orig_y = 0;
	this.dropelements = new Array();
	this.drag_screen_offset = new Object();
	this.drag_screen_offset.top = 0;
	this.drag_screen_offset.left = 0;

	// Methods
	this.drag_begin = HW_drag_drag_begin;
	this.drag = HW_drag_drag;
	this.add_drop_element = HW_drag_add_drop_element;
	this.is_in_drop = HW_drag_is_in_drop;
	this.find_top_mouseover = HW_drag_find_top_mouseover;
	
	// Set drag begin
	this.dragbar.onmousedown = function (e) { t.drag_begin(e); }
}



////////////////////////////////////////////////////////////////////////////////
//  AUTOBUTTON OBJECT
//


function HW_ab_reset_src(urloff, urlpress, urlhover)
{
	this.offimg.src = urloff;
	this.pressimg.src = urlpress || urlhover || urloff;
	this.hoverimg.src = urlhover || urloff;
}

function HW_AutoButton(img, urloff, urlpress, urlhover)
{
	// Internal image objects
	var t = this;								// for function closures within the constructor
	this.img = img;
	this.img.src = urloff;
	this.offimg = new Image();
	this.pressimg = new Image();
	this.hoverimg = new Image();

	// Event handlers
	this.img.onmouseover = function () { t.img.src = t.hoverimg.src; }
	this.img.onmouseout = function () { t.img.src = t.offimg.src; }
	this.img.onmousedown = function () { t.img.src = t.pressimg.src; }
	this.img.onmouseup = function () { t.img.src = t.hoverimg.src; }

	// Methods
	this.reset_src = HW_ab_reset_src;

	// Load src
	this.reset_src(urloff, urlpress || null, urlhover || null);
}



////////////////////////////////////////////////////////////////////////////////
//  POP-UP "WINDOW" OBJECT
//  it's actually a div that gets put on top of the other stuff on the page


function HW_pu_show()
{
	// Show the pop-up
//	window.HangWire.bring_to_front(this.bigdiv);
	var spos = HangWire.get_screen_scrolling_position();
	this.bigdiv.style.top = (this.initial_coordinates.top + (this.initial_coordinates.use_scrollpos ? spos.top : 0)) + 'px';
	this.bigdiv.style.left = (this.initial_coordinates.left + (this.initial_coordinates.use_scrollpos ? spos.left : 0)) + 'px';
	this.bigdiv.style.display = 'block';
	this.actually_set_dimensions(this.width, this.height, this.scrcss);
	this.onshow();
}

function HW_pu_set_title_text(txt)
{
	this.titletextnode.replaceData(txt);
}

function HW_pu_actually_set_dimensions(x, y, scrcss)		// scrcss = css scrolling property of the contentdiv
{
	// actually sets the dimensions -- because it must be called when display:block in order for offsetWidth to be correct
	this.bigdiv.style.position = 'absolute';
	this.bigdiv.style.width = x + 'px';
	if(y)
	{
		this.contentdiv.style.height = y + 'px';
	}
	this.closerimg.style.top = Math.round((parseInt(this.titlediv.offsetHeight) - parseInt(this.closerimg.offsetHeight))/2 - 2) + 'px';
	this.closerimg.style.left = (parseInt(this.titlediv.offsetWidth) - parseInt(this.closerimg.offsetWidth) - 3) + 'px';
	if(scrcss)
	{
		this.contentdiv.overflow = scrcss;
	}
}

function HW_pu_set_dimensions(x, y, scrcss)
{
	this.width = x;
	this.height = y;
	this.scrcss = scrcss;
}

function HW_pu_apply_styles(content_class, title_class, closer_class, bigdiv_class)
{
	if(content_class)
	{
		this.contentdiv.className = content_class;
	}
	if(title_class)
	{
		this.titlediv.className = title_class;
	}
	if(closer_class)
	{
		this.closerimg.className = closer_class;
	}
	if(bigdiv_class)
	{
		this.bigdiv.className = bigdiv_class;
	}
}

function HW_pu_initialize()
{
	var t = this;

	// Create "big div": the div that holds the whole "window" including title bar, etc.
	this.bigdiv = this.w.document.createElement('div');
	this.bigdiv.style.display = 'none';
	this.w.document.body.appendChild(this.bigdiv);				// append to the document body AFTER page load

	// Add the title bar and make it a dragger
	this.titlediv = this.w.document.createElement('div');
	this.bigdiv.appendChild(this.titlediv);
//	this.titlediv.style.width = '100%';
	this.titletextnode = this.w.document.createTextNode(this.title || '');
	this.titlediv.appendChild(this.titletextnode);
	this.dragger = new HW_Dragger(this.titlediv, this.bigdiv);

	// Add the closer image to the title bar
	this.closerimg = this.w.document.createElement('img');
	this.titlediv.appendChild(this.closerimg);
	this.closerimg.style.position = 'absolute';
	this.closerimg.onclick = function () { t.hide(); }
	this.closerab = new HW_AutoButton(this.closerimg, 'http://www.hang-wire.com/images/x.png');

	// Add the content div element
	if(this.divelemid && (this.contentdiv = this.w.document.getElementById(this.divelemid)))
	{
		// Use the given div element
		this.contentdiv.parentNode.removeChild(this.contentdiv);
		this.contentdiv.style.display = 'block';
	}
	else
	{
		// Create a new div element
		this.contentdiv = this.w.document.createElement('div');
	}
	this.bigdiv.appendChild(this.contentdiv);
//	this.contentdiv.style.width = '100%';

	// Apply default styles
	this.apply_styles(this.contentdiv.className || 'HW_pu_default_content_class', 'HW_pu_default_title_class', 'HW_pu_default_closer_class', 'HW_pu_default_bigdiv_class');
}

function HW_PopUp(title, divelem, w)
{
	// Parameters
	this.w = w || window;

	// Properties
	var t = this;									// for function closures within the constructor
	this.initial_coordinates = new Object();
	this.width = 400;
	this.height = 300;
	this.scrcss = 'scroll';
	this.title = title;
	this.divelemid = divelem;

	// Methods
	this.show = HW_pu_show;
	this.hide = function () { this.bigdiv.style.display = 'none'; }
	this.set_title_text = HW_pu_set_title_text;
	this.actually_set_dimensions = HW_pu_actually_set_dimensions;
	this.set_dimensions = HW_pu_set_dimensions;
	this.set_initial_coordinates = function (x, y, absolute) {
		this.initial_coordinates.left = x || this.initial_coordinates.left;
		this.initial_coordinates.top = y || this.initial_coordinates.top;
		if(!absolute) {		// absolute=true prevents the window from popping up relative to the scroll position
			this.initial_coordinates.use_scrollpos = true;
		} else {
			this.initial_coordinates.use_scrollpos = false;
		}
	}
	this.apply_styles = HW_pu_apply_styles;
	this.initialize = HW_pu_initialize;
	this.onshow = function () {}

	// Set initialization function to execute after page load
	window.HangWire.add_onload_task(function () { t.initialize(); }, false, t.w);

	// Set initial position
	this.set_initial_coordinates(100, 100);
}



////////////////////////////////////////////////////////////////////////////////
//  TABBED DIV OBJECT
//


function HW_td_Tab(tabbeddiv, divobj, lblobj, sellblobj)
{
	this.tdobj = tabbeddiv.w.document.createElement('td');
	this.lblobj = lblobj;										// Label object (text node or image)
	this.sellblobj = sellblobj;									// Label object for selected (optional)
	this.divobj = divobj;
	this.tdobj.className = tabbeddiv.label_td_unselected_class_name;
	this.tdobj.appendChild(this.lblobj);
	if(this.sellblobj)
	{
		this.sellblobj.style.display = 'none';
		this.tdobj.appendChild(this.sellblobj);
	}
	if(this.lblobj.style) { this.lblobj.style.display = 'block'; }
	this.divobj.style.display = 'none';
	if(this.divobj.parentNode) { this.divobj.parentNode.removeChild(this.divobj); }
	this.divobj.className = this.divobj.className || tabbeddiv.content_class_name;
	tabbeddiv.div.appendChild(this.divobj);
	tabbeddiv.labelrow.appendChild(this.tdobj);
}

function HW_td_add_tab(t, divobj, lblobj, sellblobj)
{
	HangWire.add_onload_task(function () {
		var newid = t.tabs.length;
		t.tabs[newid] = new HW_td_Tab(t, divobj, lblobj, sellblobj);
		t.tabs[newid].tdobj.onclick = function () { t.select(newid); }
	}, false, t.w);
}

function HW_td_select(tabid)
{
	// Clear currently selected div if it exists
	if(this.selection > -1 && this.tabs[this.selection] && this.tabs[this.selection].divobj)
	{
		this.tabs[this.selection].divobj.style.display = 'none';
		this.tabs[this.selection].tdobj.className = this.label_td_unselected_class_name;
		if(this.tabs[this.selection].sellblobj)
		{
			this.tabs[this.selection].sellblobj.style.display = 'none';
			this.tabs[this.selection].lblobj.style.display = 'block';
		}
	}

	// Check that selection is valid
	if(!this.tabs[tabid])
	{
		window.alert('Error selecting tab');
		return false;
	}

	// Change internal selection indicator and display the selected div
	this.selection = tabid;
	this.tabs[this.selection].divobj.style.display = 'block';
	this.tabs[this.selection].tdobj.className = this.label_td_selected_class_name;
	if(this.tabs[this.selection].sellblobj)
	{
		this.tabs[this.selection].lblobj.style.display = 'none';
		this.tabs[this.selection].sellblobj.style.display = 'block';
	}

	this.onselect(tabid);
}

function HW_td_initialize()
{
	// Create the label table
	var tbl = this.w.document.createElement('table');
	var tby = this.w.document.createElement('tbody');
	this.labelrow = this.w.document.createElement('tr');
	tby.appendChild(this.labelrow);
	tbl.appendChild(tby);
	tbl.className = this.label_table_class_name;
	this.div.appendChild(tbl);
}

function HW_Tabbed_Div(div, w)
{
	// Parameters
	this.w = w || window;
	this.div = div || this.w.document.createElement('div');

	// Properties
	var t = this;
	this.label_table_class_name = 'HW_td_label_table_class';
	this.label_td_selected_class_name = 'HW_td_label_td_selected_class';
	this.label_td_unselected_class_name = 'HW_td_tabel_td_unselected_class';
	this.content_class_name = 'HW_td_content_class';
	this.tabs = new Array();
	this.labelrow = null;			// table row will contain all the labels
	this.selection = -1;			// current tab selected; -1 is none (because 0 is the first)

	// Methods
	this.add_tab_text = function (div, text) { HW_td_add_tab(t, div, this.w.document.createTextNode(text)); }
	this.add_tab_image = function (div, img, selimg) { HW_td_add_tab(t, div, img, selimg); }
	this.initialize = HW_td_initialize;
	this.select = HW_td_select;
	this.onselect = function (tabid) {}

	// Onload, build the basic structure
	window.HangWire.add_onload_task(function () { t.initialize(); }, false, t.w);
}



////////////////////////////////////////////////////////////////////////////////
//  ENHANCED INPUT OBJECT
//  Enhances an input box with some cool features


function HW_ei_prohibit_characters(prohibited_string)
{
	// Prohibits all characters in prohibited_string from this input
	function returnfalse (input_obj) { return false; }

	for(i=0; i<prohibited_string.length; i++)
	{
		this.onkeypress_array[prohibited_string.charCodeAt(i)] = returnfalse;
	}
}

function HW_Enhanced_Input(input_object)
{
	// Syntax:
	// var hwei = HW_Enhanced_Input(document.getElementById('my_input_object_id'));

	// Optional:
	// hwei.onmouseover = function (input_obj) { do what you want with the input obj; default is below }
	// hwei.onenterpress = function () { alert('enter has been pressed'); }
	// hwei.onkeypress[13] = function () { alert('enter has been pressed'); }

	var t = this;
	this.input_object = input_object;

	// Methods
	this.prohibit_characters = HW_ei_prohibit_characters;
	
	// onmouseover function
	this.onmouseover = function (input_obj) { input_obj.focus(); input_obj.select(); }
	this.input_object.onmouseover = function () { t.onmouseover(t.input_object); }

	// onanykeypress function
	this.onanykeypress = function (input_obj, keycode) {}

	// onkeypress associative array of functions
	this.onkeypress_array = new Object();
	this.onkeypress = function (keycode) {
		if(this.onkeypress_array[keycode]) {
			this.onkeypress_array[keycode](this.input_object);			// call the function
		}
		this.onanykeypress(this.input_object, keycode);
	}

	// syntactic sugar: default key 13 (enter) to 'onenterpress' function so the programmer can just reassign that
	this.onenterpress = function (inputobj) {}
	this.onkeypress_array[13] = function (inputobj) { t.onenterpress(inputobj); }

	// finally, assign onkeypress to the input box
	this.input_object.onkeypress = function (e) {
		var key = window.event.keyCode || e.which;			// IE || Netscape
		if(key)
		{
			t.onkeypress(key);								// call the appropriate function if defined
		}
	}

}



////////////////////////////////////////////////////////////////////////////////
//  FLOATING BOX
//  Sort of like an "alt" tag for images, except it's for anything, and appearance is controlled by CSS


function HW_fb_show(e)
{
	// Get the event object
	var ev = e || window.event;

	// Position the box
	this.box.style.position = 'relative';
	HangWire.bring_to_front(this.box);
	this.box.style.display = 'block';
	var pos = new Object();

//var scrollpos = HangWire.get_screen_scrolling_position();
//alert(pos.left + ' ' + pos.top + ' | ' + ev.clientX + ' ' + ev.clientY + ' | ' + ev.pageX + ' ' + ev.pageY + ' | ' + scrollpos.left + ' ' + scrollpos.top);

	// Assign the coordinates with respect to the trigger element alignment point, or mouse cursor
	if(this.positionbase.indexOf('mouse') > -1)
	{
		// Use the mouse cursor as the alignment point
		pos.left = ev.clientX - refpos.left;
		pos.top = ev.clientY - refpos.top;
	}
	else
	{
		// Align with respect to the selected alignment point on the trigger element
		pos.left = 0;
		pos.top = 0;
		if(this.positionbase.indexOf('right') > 0) {
			pos.left += this.trigger_element.offsetWidth; }						// right alignment to trigger element
		if(this.positionbase.indexOf('center') > 0) {
			pos.left += Math.round(this.trigger_element.offsetWidth / 2); }		// center alignment to trigger element
		if(this.positionbase.indexOf('bottom') > 0) {
			pos.top += this.trigger_element.offsetHeight; }						// bottom alignment to trigger element
		if(this.positionbase.indexOf('middle') > 0) {
			pos.top += Math.round(this.trigger_element.offsetHeight / 2); }		// middle alignment to trigger element
	}

	// Show the box (which is required for aligning with respect to the box)
	this.box.style.left = pos.left + this.offset.left;
	this.box.style.top = pos.top + this.offset.top;

	// Assign the coordinates with respect to the alignment point on the pop-up box or mouse cursor
	if(this.position.indexOf('right') > 0) {
		this.box.style.left -= this.box.offsetWidth; }						// right alignment of box
	if(this.position.indexOf('center') > 0) {
		this.box.style.left -= Math.round(this.box.offsetWidth / 2); }		// center alignment of box
	if(this.position.indexOf('bottom') > 0) {
		this.box.style.top -= this.box.offsetHeight; }						// bottom alignment of box
	if(this.position.indexOf('middle') > 0) {
		this.box.style.top -= Math.round(this.box.offsetHeight / 2); }		// middle alignment of box
}

function HW_Floating_Box(trigger_element, trigger_type, offset, positionbase, position, box_div, box_css_class, text, onshow, onhide, win)
{
	// trigger_type can be:  click | mouseover
	// positionbase is the reference point for positioning the popup; position is the point on the popup that gets positioned
	// positionbase can be:  mouse | top left | top center | top right | middle left | ... | bottom right
	// position can be:  top left | top center | top right | middle left | ... | bottom right

	// Parameters & Properties
	this.trigger_element = trigger_element;
	this.w = win || window;
	this.box = box_div || this.w.document.createElement('div');
	if(this.box.parentNode)
	{
		this.box.parentNode.removeChild(this.box);
	}

	// Box positioning and layout
	this.box.className = box_css_class || 'HW_fb_box_class';
	HangWire.bring_to_front(this.box);							// set z-index
	this.box.style.display = 'none';
	this.trigger_element.parentNode.appendChild(this.box);
	if(text) { this.box.appendChild(this.w.document.createTextNode(text)); }
	this.positionbase = positionbase || 'bottom left';			// default to positioning the top-left corner of the...
	this.position = position || 'top left';					// ...popup box at the position of the mouse cursor
	this.onshow = onshow || function () {}
	this.onhide = onhide || function () {}
	this.offset = new Object();
	this.offset.top = 0;
	this.offset.left = 0;
	if(offset && offset.length==2)
	{
		this.offset.left = offset[0];
		this.offset.top = offset[1];
	}
	var t = this;

	// Popup methods
	this.show = HW_fb_show;
	this.hide = function () { this.box.style.display = 'none'; }
	if(trigger_type == 'click')
	{
		this.trigger_element.onclick = function (e) { t.show(e); t.onshow(); }
	}
	else if(trigger_type == 'mouseover')
	{
		this.trigger_element.onmouseover = function (e) { t.show(e); t.onshow(); }
		this.trigger_element.onmouseout = function (e) { t.hide(); t.onhide(); }
	}
}



////////////////////////////////////////////////////////////////////////////////
//  TREE OPTIONS OBJECT


function HW_to_node_toggle_select()
{
	// If not multiple, need to unselect other option
	if(!this.to.multiple && this.select_status==-1 && this.to.nodes[this.to.input_element.value] && this.to.nodes[this.to.input_element.value].select_status==1)
	{
		this.to.nodes[this.to.input_element.value].toggle_select();		// unselect the former option
	}

	// Toggle select this node
	this.select_status = -this.select_status;			// 1 <==> -1; 0 <==> 0
	if(this.select_status == 1)
	{
		// Select it
		this.a.className = this.selected_css_class_name;
		this.to.input_element.value = this.to.input_element.value + (this.to.input_element.value ? this.to.delimiter : '') + this.id;
		this.to.onselect(this);
	}
	if(this.select_status == -1)
	{
		// Unselect it
		this.a.className = this.unselected_css_class_name;
		this.to.input_element.value = this.to.input_element.value.replace(new RegExp(this.id + '(?:\\' + this.to.delimiter + ')?'), '');
		if(this.to.input_element.value.substring(this.to.input_element.value.length - this.to.delimiter.length, this.to.input_element.value.length) == this.to.delimiter)
		{
			this.to.input_element.value = this.to.input_element.value.substring(0, this.to.input_element.value.length - this.to.delimiter.length);
		}
		this.to.onunselect(this);
	}
	return this;
}

function HW_to_node_toggle_expand()
{
	this.expand_status = -this.expand_status;			// 1 <==> -1; 0 <==> 0
	if(this.expand_status == 1)
	{
		// Expand it
		this.img.src = this.to.minus_image.src;
		for(childid in this.children)
		{
			this.children[childid].div.style.display = 'block';
		}
		this.to.onexpand(this);
	}
	if(this.expand_status == -1)
	{
		// Collapse it
		this.img.src = this.to.plus_image.src;
		for(childid in this.children)
		{
			this.children[childid].div.style.display = 'none';
		}
		this.to.oncollapse(this);
	}
	return this;
}

function HW_to_node_add_text(elem, text)
{
	var tn = this.to.w.document.createTextNode(text);
	elem.appendChild(tn);
}

function HW_to_node_get_level()
{
	var level = 1;
	var test_id = this.id;
	while(test_id = this.to.get_parent(test_id))
	{
		level++;
	}
	return level;
}

function HW_to_node_add_child(child_node)
{
	this.to.how_to_add_child(this.div, child_node);
	child_node.parent = this;
	this.children[child_node.id] = child_node;
	if(this.expand_status == 0)
	{
		// Prepare for children
		this.expand_status = -1;
		this.img.src = this.to.plus_image.src;
		this.img.style.cursor = 'hand';
	}
	if(this.expand_status == 1)
	{
		// Display the new child node
		child_node.div.style.display = 'block';
	}
	return this;
}

function HW_to_node_remove_child(child_node)
{
	this.to.how_to_remove_child(this.div, child_node);
	child_node.parent = null;
	this.children[child_node.id] = null;
	if(!this.has_children())
	{
		// Revert to no-children state
		this.expand_status = 0;
		this.img.src = this.to.spacer_image.src;
		this.img.style.cursor = 'default';
	}
	return this;
}

function HW_to_node_make_selectable()
{
	this.select_status = -1;
	this.a.className = this.unselected_css_class_name;
	return this;
}

function HW_to_node_make_unselectable()
{
	this.select_status = 0;
	this.a.className = this.standard_css_class_name;
	return this;
}

function HW_to_node_has_children()
{
	var child_count = 0;
	for(id in this.children)
	{
		if(this.children[id])
		{
			child_count++;
		}
	}
	return (child_count > 0);
}

function HW_to_node_is_selectable()
{
	return (this.select_status != 0);
}

function HW_to_Node(id, treeoptions)
{
	// Creates a new calendar node
	this.id = id;
	this.to = treeoptions;
	this.parent = null;
	this.children = new Object();
	this.standard_css_class_name = this.to.standard_css_class_name;
	this.selected_css_class_name = this.to.selected_css_class_name;
	this.unselected_css_class_name = this.to.unselected_css_class_name;
	this.onselect = this.to.onselect;
	this.onunselect = this.to.onunselect;
	this.onexpand = this.to.onexpand;
	this.oncollapse = this.to.oncollapse;
	this.select_status = 0;
	this.expand_status = 0;
	this.make_selectable = HW_to_node_make_selectable;
	this.make_unselectable = HW_to_node_make_unselectable;
	this.add_text = HW_to_node_add_text;
	this.get_level = HW_to_node_get_level;
	this.is_selectable = HW_to_node_is_selectable;
	this.has_children = HW_to_node_has_children;
	this.toggle_expand = HW_to_node_toggle_expand;
	this.toggle_select = HW_to_node_toggle_select;
	this.add_child = HW_to_node_add_child;
	this.remove_child = HW_to_node_remove_child;

	var t = this;
	this.div = this.to.w.document.createElement('div');
	this.div.setAttribute('id', this.to.jsidbase + '_HW_to_div_' + this.id);
	this.div.style.display = 'none';
	this.a = this.to.w.document.createElement('a');
	this.a.setAttribute('id', this.to.jsidbase + '_HW_to_a_' + this.id);
	this.a.setAttribute('href', 'javascript://');								// null anchor
	this.a.onclick = function () { t.toggle_select(); }
	this.a.className = this.to.standard_css_class_name;
	this.img = this.to.w.document.createElement('img');
	this.img.setAttribute('id', this.to.jsidbase + '_HW_to_img_' + this.id);
	this.img.src = this.to.spacer_image.src;
	this.img.onclick = function () { t.toggle_expand(); }
	
	this.to.how_to_arrange_new_element(this);
}

function HW_to_arrange_new_element(newnode)
{
	newnode.div.appendChild(newnode.img);
	newnode.div.style.paddingLeft = this.indent_pixels + 'px';
	newnode.add_text(newnode.div, ' ');
	newnode.add_text(newnode.a, this.get_label(newnode.id));
	newnode.div.appendChild(newnode.a);
	return newnode;
}

function HW_to_create_input_element()
{
	this.input_element = this.w.document.createElement('textarea');
	this.input_element.style.display = 'none';
	this.element_to_append.appendChild(this.input_element);
	this.input_element.value = '';
	return this;
}

function HW_to_set_post_name(varname)
{
	this.input_element.setAttribute('name', varname);
	return this;
}

function HW_to_set_window(custom_window)
{
	// Get current input string
	var inputstring = this.element_to_append.value;
	var postname = this.input_element.getAttribute('name');
	postname = postname ? postname : '';
	this.input_element = null;
	this.w = custom_window;
	this.create_input_element();
	this.input_element.value = inputstring;
	if(postname)
	{
		this.set_post_name(postname);
	}
	return this;
}

function HW_to_set_jsidbase(jsidbase)
{
	this.jsidbase = jsidbase;
	return this;
}

function HW_to_set_class_names(standard, selected, unselected, parent_div)
{
	if(standard)
	{
		this.standard_css_class_name = standard;
	}
	if(selected)
	{
		this.selected_css_class_name = selected;
	}
	if(unselected)
	{
		this.unselected_css_class_name = unselected;
	}
	if(parent_div)
	{
		this.parent_css_class_name = parent_div;
		this.element_to_append.className = parent_div;
	}
	return this;
}

function HW_to_set_images(plus_src, minus_src, spacer_src)
{
	if(plus_src)
	{
		this.plus_image.src = plus_src;
	}
	else
	{
		this.plus_image = null;
	}
	if(minus_src)
	{
		this.minus_image.src = minus_src;
	}
	else
	{
		this.minus_image = null;
	}
	if(spacer_src)
	{
		this.spacer_image.src = spacer_src;
	}
	else
	{
		this.spacer_image = null;
	}
	return this;
}

function HW_to_set_layout_methods(how_to_arrange_new_element, how_to_add_child, how_to_remove_child)
{
	if(how_to_arrange_new_element)
	{
		this.how_to_arrange_new_element = how_to_arrange_new_element;
	}
	if(how_to_add_child)
	{
		this.how_to_add_child = how_to_add_child;
	}
	if(how_to_remove_child)
	{
		this.how_to_remove_child = how_to_remove_child;
	}
	return this;
}

function HW_to_toggle_select(id)
{
	if(this.nodes[id])
	{
		this.nodes[id].toggle_select();
		return this;
	}
	else
	{
		this.error = "Node ID#" + id + " not found.";
		return false;
	}
	return this;
}

function HW_to_clear_selections()
{
	for(var id in this.nodes)
	{
		if(this.nodes[id] && (this.nodes[id].select_status == 1))
		{
			this.nodes[id].toggle_select();
		}
	}
	return this;
}

function HW_to_select_all()
{
	for(var id in this.nodes)
	{
		if(this.nodes[id] && (this.nodes[id].select_status == -1))
		{
			this.nodes[id].toggle_select();
		}

	}
	return this;
}

function HW_to_invert_selections()
{
	for(var id in this.nodes)
	{
		if(this.nodes[id] && (this.nodes[id].select_status != 0))
		{
			this.nodes[id].toggle_select();
		}
	}
}

function HW_to_clear()
{
	for(var id in this.nodes)
	{
		if(this.nodes[id] && (this.nodes[id].select_status != 0))
		{
			this.remove_option(id);
		}
	}
}

function HW_to_toggle_expand(id)
{
	if(this.nodes[id])
	{
		this.nodes[id].toggle_expand();
		return this;
	}
	else
	{
		this.error = "Node ID#" + id + " not found.";
		return false;
	}
	return this;
}

function HW_to_expand_all()
{
	for(id in this.nodes)
	{
		if(this.nodes[id] && (this.nodes[id].expand_status == -1))
		{
			this.nodes[id].toggle_expand();
		}
	}
}

function HW_to_collapse_all()
{
	for(id in this.nodes)
	{
		if(this.nodes[id] && (this.nodes[id].expand_status == 1))
		{
			this.nodes[id].toggle_expand();
		}
	}
}

function HW_to_remove_option(id)
{
	var test_id = id;
	var parent_id = null;
	var parent_div = null;
	var div = null;

	// Make sure the div exists
	if(!this.nodes[id] || !(div = this.w.document.getElementById(this.jsidbase + '_HW_to_div_' + test_id)))
	{
		this.error = 'Element not found for option ID#' + test_id;
		return false;
	}

	// Unselect the option if it's selected
	if(this.nodes[id].select_status == 1)
	{
		this.nodes[id].toggle_select();
	}

	// Make it unselectable
	this.nodes[id].make_unselectable();

	// Custom onremove function
	this.onremove(this.nodes[id]);

	// Now cycle through the parents, if this node doesn't have children
	if(!this.nodes[id].has_children())
	{
		var continue_loop = true;
		var remove_parent = true;
		while(continue_loop && (parent_node = this.nodes[test_id].parent))
		{
			// Remove the child node
			parent_node.remove_child(this.nodes[test_id]);
			this.nodes[test_id] = null;

			// See if parent has any other children
			if(parent_node.has_children())
			{
				// End loop and do not remove parent
				remove_parent = false;
				continue_loop = false;
			}
			else
			{
				// No more children; continue loop
				test_id = parent_node.id;
			}
		}
	}

	// No more parents
	if(remove_parent)
	{
		// First parent has no other children; remove it
		this.how_to_remove_child(this.element_to_append, this.nodes[test_id]);
		this.nodes[test_id] = null;
	}
	return true;
}

function HW_to_add_option(id)
{
	var newnode = null;
	var test_id = id;

	// See if the element is already in there
	if(this.nodes[id])
	{
		// It's already present; see if it's already selectable
		if(this.nodes[id].is_selectable())
		{
			// Already done!
			this.error = 'Option ID#' + id + ' already entered.';
			return false;
		}
		else
		{
			// Make it selectable and exit
			this.nodes[id].make_selectable();
			this.onadd(this.nodes[id]);
			return true;
		}
	}
	else
	{
		// Create the line objects and continue
		newnode = new HW_to_Node(id, this);
		this.nodes[id] = newnode;
		div = newnode.div;
	}

	// See if the object has parents; if so, place it in the hierarchy correctly
	while((parent_id = this.get_parent(test_id)) && (parent_id != '0'))
	{
		// See if the parent exists
		if(this.nodes[parent_id])
		{
			// Parent already exists; append child and exit
			this.nodes[parent_id].add_child(newnode);
			this.nodes[id].make_selectable();
			this.onadd(newnode);
			return true;
		}
		else
		{
			// Parent does not exist; continue loop
			parentnode = new HW_to_Node(parent_id, this);
			this.nodes[parent_id] = parentnode;
			parentnode.add_child(newnode);
			newnode = parentnode;
			test_id = parent_id;
		}
	}

	// No more parents; append and display
	this.how_to_add_child(this.element_to_append, this.nodes[test_id]);		// add directly to this.element_to_append; no parent node
	this.nodes[test_id].div.style.display = 'block';
	this.nodes[id].make_selectable();
	this.onadd(this.nodes[id]);
	return true;
}

function HW_Tree_Options(element_to_append, how_to_get_parent, how_to_get_label)
{
	// Parameters
	this.element_to_append = element_to_append;
	this.get_parent = how_to_get_parent;				// function (id) { return parent of id }
	this.get_label = how_to_get_label;					// function (id) { return label of id }

	// Methods
	this.toggle_select = HW_to_toggle_select;
	this.toggle_expand = HW_to_toggle_expand;
	this.add_option = HW_to_add_option;
	this.remove_option = HW_to_remove_option;
	this.clear_selections = HW_to_clear_selections;
	this.select_all = HW_to_select_all;
	this.invert_selections = HW_to_invert_selections;
	this.clear = HW_to_clear;
	this.expand_all = HW_to_expand_all;
	this.collapse_all = HW_to_collapse_all;
	this.set_window = HW_to_set_window;
	this.set_jsidbase = HW_to_set_jsidbase;
	this.set_class_names = HW_to_set_class_names;
	this.set_post_name = HW_to_set_post_name;
	this.set_images = HW_to_set_images;
	this.set_layout_methods = HW_to_set_layout_methods;
	this.create_input_element = HW_to_create_input_element;
	this.get_selections = function () { return this.input_element.value.split(this.delimiter ? this.delimiter : null); }

	// Default properties
	this.w = window;
	this.error = false;
	this.jsidbase = 'HW_to_' + window.HangWire.unique_number();
	this.delimiter = ',';
	this.unselected_css_class_name = 'HW_to_unselected_class_name';
	this.selected_css_class_name = 'HW_to_selected_class_name';
	this.standard_css_class_name = 'HW_to_standard_class_name';
	this.parent_css_class_name = 'HW_to_parent_class_name';
	this.indent_pixels = 14;
	this.plus_image = new Image();
	this.minus_image = new Image();
	this.spacer_image = new Image();
	this.plus_image.src = 'http://www.hang-wire.com/images/tree_plus.png';
	this.minus_image.src = 'http://www.hang-wire.com/images/tree_minus.png';
	this.spacer_image.src = 'http://www.hang-wire.com/images/tree_spacer.png';
	this.nodes = new Object();
	this.multiple = true;					// allow multiple selects

	// Default methods
	this.onselect = function (node) {}
	this.onunselect = function (node) {}
	this.onexpand = function (node) {}
	this.oncollapse = function (node) {}
	this.onadd = function (node) {}
	this.onremove = function (node) {}
	this.how_to_arrange_new_element = HW_to_arrange_new_element;
	this.how_to_add_child = function (parent_div, child_node) { parent_div.appendChild(child_node.div); }
	this.how_to_remove_child = function (parent_div, child_node) { parent_div.removeChild(child_node.div); }

	// Create input element and set parent class name
	this.create_input_element();
	this.element_to_append.className = this.parent_css_class_name;
}



///////////////////////////////////////////////////////////////////////////////
//  ADD/REMOVE BUTTON


function HW_arb_click()
{
	// Get selections and remove if remove method set
	for(var id in this.objects)
	{
		this.objects[id].selections = this.objects[id].get_selections();
		if(this.objects[id].selections.length == 1 && this.objects[id].selections[0] == '')
		{
			this.objects[id].selections.pop();		// get rid of blank selection
		}
		if(this.objects[id].remove)
		{
			for(var remid = 0; remid < this.objects[id].selections.length; remid++)
			{
				this.objects[id].remove(this.objects[id].selections[remid]);
			}
		}
	}

	// Add selections if add method set, and trigger onclick
	for(var id in this.objects)
	{
		if(this.objects[id].add)
		{
			for(var i = 0; i < this.objects[id].add_from_id.length; i++)
			{
				for(var addid = 0; addid < this.objects[this.objects[id].add_from_id[i]].selections.length; addid++)
				{
					this.objects[id].add(this.objects[this.objects[id].add_from_id[i]].selections[addid]);
				}
			}
		}
		if(this.objects[id].selections.length > 0)
		{
			this.onclick(this.objects[id].selections, id);
		}
	}
}

function HW_arb_add_object(id, get_selections, how_to_remove, how_to_add, add_from_id)
{
	this.objects[id] = new Object();
	this.objects[id].selections = new Array();
	if(get_selections)
	{
		this.objects[id].get_selections = get_selections;
	}
	else
	{
		this.objects[id].get_selections = function () { return new Array(); }
	}
	if(how_to_remove)
	{
		this.objects[id].remove = how_to_remove;
	}
	else
	{
		this.objects[id].remove = null;
	}
	if(how_to_add)
	{
		this.objects[id].add = how_to_add;
	}
	else
	{
		this.objects[id].add = null;
	}

	// add_from_id can be comma-delimited
	this.objects[id].add_from_id = add_from_id ? add_from_id.split(',') : new Array();
}

function HW_Add_Remove_Button(img, off_src, press_src, hover_src, onclick)
{
	// Parameters
	this.img = img;
	this.onclick = function (selarr, id) {}
	if(onclick)
	{
		this.onclick = onclick;
	}

	// Images
	this.off_img = new Image();
	this.press_img = new Image();
	this.hover_img = new Image();
	this.off_img.src = off_src;
	this.press_img.src = press_src || off_src;
	this.hover_img.src = hover_src || off_src;
	var t = this;
	this.img.src = this.off_img.src;
	this.img.onmouseover = function () { t.img.src = t.hover_img.src; }
	this.img.onmouseout = function () { t.img.src = t.off_img.src; }
	this.img.onmousedown = function () { t.img.src = t.press_img.src; }
	this.img.onmouseup = function () { t.img.src = t.hover_img.src; }
	this.img.onclick = function () { t.click(); onclick(); }

	// Properties
	this.error = false;
	this.objects = new Object();

	// Methods
	this.add_object = HW_arb_add_object;
	this.click = HW_arb_click;
}



///////////////////////////////////////////////////////////////////////////////
//  CALENDAR OBJECT


function HW_cal_day_click()
{
	if(this.has_event)
	{
		this.cal.ondayclick(this.cal.year, this.cal.month, this.day);
	}
}

function HW_cal_day_set_event_status(whether)
{
	if(this.day)
	{
		// There's a day number in this cell
		this.td.className = whether ? this.cal.day_event_css_class_name : this.cal.day_standard_css_class_name;
	}
	else
	{
		// The cell is empty
		this.td.className = this.cal.day_empty_css_class_name;
	}
	this.has_event = whether ? true : false;
	return this;
}

function HW_cal_day_add_event(what)
{
	this.set_event_status(true);
	this.cal.how_to_do_event_layout(this.day, this.eventsdiv, what);
}

function HW_cal_day_clear()
{
	this.events = new Array();
	this.set_event_status(false);
	for(var i=this.eventsdiv.childNodes.length-1; i>=0; i--)
	{
		this.eventsdiv.removeChild(this.eventsdiv.childNodes[i]);
	}
	this.eventsdiv.appendChild(this.a);
}

function HW_cal_day_set_day(day)
{
	// Displays a day number or clears the display
	if(!day || day=='')
	{
		day = window.HangWire.nbsp;
		this.day = false;
	}
	else
	{
		this.day = day;
	}
	this.text.data = day;
	this.set_event_status(this.has_event);			// don't change this.has_event; this is just to make sure the correct CSS class is applied
	return this;
}

function HW_cal_Day(wk, dy, cal)
{
	// Parameters & Properties
	this.wk = wk;
	this.dy = dy;
	this.cal = cal;
	this.has_event = false;
	this.events = new Array();
	this.day = false;

	// Methods
	this.set_day = HW_cal_day_set_day;
	this.set_event_status = HW_cal_day_set_event_status;
	this.day_click = HW_cal_day_click;
	this.clear = HW_cal_day_clear;
	this.add_event = HW_cal_day_add_event;

	// Layout
	var t = this;
	this.td = this.cal.w.document.createElement('td');
	this.td.className = this.cal.day_standard_css_class_name;
	this.td.setAttribute('id', this.cal.jsidbase + '_HW_cal_daycell_w' + this.wk + 'd' + this.dy);
	this.td.style.overflow = 'hidden';
	this.a = this.cal.w.document.createElement('a');
	this.a.setAttribute('id', this.cal.jsidbase + '_HW_cal_day_w' + this.wk + 'd' + this.dy);
	this.a.onclick = function () { t.day_click(); }
	this.text = this.cal.w.document.createTextNode(window.HangWire.nbsp);
	this.a.appendChild(this.text);
	this.td.appendChild(this.a);
	this.eventsdiv = this.cal.w.document.createElement('div');		// event info will go here
	this.td.appendChild(this.eventsdiv);
}

function HW_cal_do_layout()
{
	var tr = null;
	var td = null;
	var tn = null;
	var tb = null;

	// Start title row
	this.element_to_append.style.textAlign = 'center';
	var tt = this.w.document.createElement('table');
	tt.width = '100%';
	var ttb = this.w.document.createElement('tbody');
	var ttr = this.w.document.createElement('tr');

	// Left nav button
	td = this.w.document.createElement('td');
	td.style.textAlign = 'left';
	td.appendChild(this.left_month_nav);
	ttr.appendChild(td);

	// Title cell
	this.title_td.style.textAlign = 'center';
	ttr.appendChild(this.title_td);

	// Right nav button
	td = this.w.document.createElement('td');
	td.style.textAlign = 'right';
	td.appendChild(this.right_month_nav);
	ttr.appendChild(td);

	// Finish title row
	ttb.appendChild(ttr);
	tt.appendChild(ttb);
	this.element_to_append.appendChild(tt);

	// Start table
	this.table.style.width = '100%';
	tb = this.w.document.createElement('tbody');

	// Week headings
	tr = this.w.document.createElement('tr');
	for(var i=0; i<7; i++)
	{
		this.day_headings[i].appendChild(this.w.document.createTextNode(this.day_names.abbreviated[i]));
		tr.appendChild(this.day_headings[i]);
	}
	tb.appendChild(tr);

	// Day grid
	for(var wk in this.days)
	{
		tr = this.w.document.createElement('tr');
		for(var dy in this.days[wk])
		{
			tr.appendChild(this.days[wk][dy].td);
		}
		tb.appendChild(tr);
	}

	// Finish
	this.table.appendChild(tb);
	this.element_to_append.appendChild(this.table);
}

function HW_cal_change_layout(how_to_do_layout)
{
	if(how_to_do_layout)
	{
		this.how_to_do_layout = how_to_do_layout;
	}
	for(i=0; i<this.element_to_append.children.length; i++)
	{
		HangWire.remove_all_children(this.element_to_append);
	}
	this.how_to_do_layout();
	return this;
}

function HW_cal_set_css(tbl, mnav, title, dayhead, day, dayev, today, daymouse, dayempty)
{
	var t = this;
	if(tbl)
	{
		this.table_css_class_name = tbl;
		this.table.className = tbl;
	}
	if(mnav)
	{
		this.month_nav_css_class_name = mnav;
		this.left_month_nav.className = mnav;
		this.right_month_nav.className = mnav;
	}
	if(title)
	{
		this.title_css_class_name = title;
		this.title_td.className = title;
	}
	if(dayhead)
	{
		this.day_heading_css_class_name = dayhead;
		for(var i in this.day_headings)
		{
			this.day_headings[i].className = dayhead;
		}
	}
	if(day || dayev || today || daymouse)
	{
		if(day)
		{
			this.day_standard_css_class_name = day;
		}
		if(dayev)
		{
			this.day_event_css_class_name = dayev;
		}
		if(today)
		{
			this.today_css_class_name = today;
		}
		if(daymouse)
		{
			this.day_mouseover_css_class_name = daymouse;
		}
		for(var i in this.days) for(var j in this.days[i])
		{
			this.days[i][j].tdclass = ((this.days[i][j].day == this.day) && today ? today : (this.days[i][j].has_event ? dayev : day));
			this.days[i][j].td.className = this.days[i][j].tdclass;
			if(daymouse)
			{
				this.days[i][j].td.onmouseover = function () { this.className = daymouse; }
				this.days[i][j].td.onmouseout = function () { this.className = t.days[i][j].tdclass; }
			}
		}
		if(dayempty)
		{
			this.day_empty_css_class_name = dayempty;
		}
		else
		{
			this.day_empty_css_class_name = day;				// default empty days to standard day style if dayempty not given
		}
	}
}

function HW_cal_calsys_add_leap_alternative(leap_id, is_leap_year, fullmonths, leapmonthlengths, abbmonths, maptoregular)
{
	// Default is no abbreviations
	if(!abbmonths)
	{
		abbmonths = fullmonths;
	}

	// Default is identity map
	if(!maptoregular)
	{
		maptoregular = function (m) { return m; }
	}

	this.leap[leap_id] = new Object();
	this.leap[leap_id].isleap = is_leap_year;
	this.leap[leap_id].months = new Array();
	for(i in fullmonths)
	{
		this.leap[leap_id].months[i] = new Object();
		this.leap[leap_id].months[i].full = fullmonths[i];
		this.leap[leap_id].months[i].abbreviated = abbmonths[i];
		this.leap[leap_id].months[i].month_length = leapmonthlengths[i];
		this.leap[leap_id].months[i].maptoregular = maptoregular;
	}
	return this;
}

function HW_cal_calsys_months_in_year(year)
{
	var leapid = this.isleap(year);
	if(leapid)
	{
		return this.leap[leapid].months;
	}
	else
	{
		return this.months;
	}
}

function HW_cal_Calendar_System(fullmonths, monthlengths, abbmonths)
{
	// Default is no abbreviations
	if(!abbmonths)
	{
		abbmonths = fullmonths;
	}

	this.leap = new Object();

	this.months = new Array();
	for(i in fullmonths)
	{
		this.months[i] = new Object();
		this.months[i].full = fullmonths[i];
		this.months[i].abbreviated = abbmonths[i];
		this.months[i].month_length = monthlengths[i];
	}

	this.months_in_year = HW_cal_calsys_months_in_year;
	this.add_leap_alternative = HW_cal_calsys_add_leap_alternative;
	
	this.isleap = function (year)
	{
		var result = false;
		for(var lid in this.leap)
		{
			if(this.leap[lid].isleap(year))
			{
				result = lid;
				break;
			}
		}
		return result;
	}
}

function HW_cal_set_calendar_system(id)
{
	this.calendar_system = id;
}

function HW_cal_add_calendar_system(id, fullmonths, monthlengths, abbmonths)
{
	this.calendar_systems[id] = new HW_cal_Calendar_System(fullmonths, monthlengths, abbmonths);
	return this;
}

function HW_cal_current_month_system(year)
{
	year = year || this.year;
	var leapid = this.calendar_systems[this.calendar_system].isleap(this.year);
	return leapid ? this.calendar_systems[this.calendar_system].leap[leapid].months : this.calendar_systems[this.calendar_system].months;
}

function HW_cal_display_month_title(abbrev, month, year)
{
	var abbr = abbrev ? 'abbreviated' : 'full';
	month = month || this.month;
	year = year || this.year;
	this.title_text.data = this.current_month_system()[month - 1][abbr] + ' ' + year;
}

function HW_cal_clear_events()
{
	for(i in this.days) for(j in this.days[i])
	{
		this.days[i][j].clear();
	}
}

function HW_cal_add_event(day, what)
{
	this.monthdays[day].add_event(what);
}

function HW_cal_set_month(year, month)
{
	this.year = year;
	this.month = month;
	this.monthdays = new Array(' ');

	// Clear old events
	this.clear_events();

	// Month title
	this.display_month_title(this.month_abbreviation);

	// Get day of week of first day of selected month
	this.d.setFullYear(this.year, this.month - 1, 1);
	var day = this.d.getDay();

	// Display days of this month
	var dt = 0;
	var ml = this.current_month_system(year)[month - 1].month_length;
	var daylabel = 0;
	for(var wk = 0; wk < 6; wk++)
	{
		for(var dy = 0; dy < 7; dy++)
		{
			this.days[wk][dy].set_event_status(false);
			daylabel = (wk==0 && dy < day) || (++dt <= ml) ? dt : false;
			this.days[wk][dy].set_day(daylabel);
			if(daylabel)
			{
				this.monthdays[daylabel] = this.days[wk][dy];
			}
		}
	}

	// Add events for this month
	this.how_to_get_events(this.year, this.month);
}

function HW_cal_move_month(offset)
{
	this.month = this.month + offset;
	while(this.month <= 0)
	{
		this.year -= 1;
		this.month += this.calendar_systems[this.calendar_system].months_in_year(this.year).length;
	}
	while(this.month > this.calendar_systems[this.calendar_system].months_in_year(this.year).length)
	{
		this.month -= this.calendar_systems[this.calendar_system].months_in_year(this.year).length;
		this.year += 1;
	}
	this.set_month(this.year, this.month);
	return this;
}

function HW_cal_move_year(offset)
{
	var leap_formerly = this.calendar_systems[this.calendar_system].isleap(this.year);
	this.year += offset;
	if(leap_formerly && !this.calendar_systems[this.calendar_system].isleap(this.year))
	{
		this.month = this.calendar_systems[this.calendar_system].leap[leap_formerly].maptoregular(this.month);
	}
	this.set_month(this.year, this.month);
	return this;
}

function HW_cal_refresh()
{
//	this.change_layout();
	this.set_month(this.year, this.month);
}

function HW_Calendar(element_to_append, ondayclick, css)
{
	// Parameters
	var t = this;
	this.element_to_append = element_to_append;
	this.ondayclick = function (year, month, day) {}
	if(ondayclick)
	{
		this.ondayclick = ondayclick;						// function (year, month, day) {...}
	}
	css = css || ['HW_cal_table_class_name', 'HW_cal_month_nav_class_name', 'HW_cal_title_class_name', 'HW_cal_day_heading_class_name', 'HW_cal_day_standard_class_name', 'HW_cal_day_event_class_name', 'HW_cal_day_today_class_name', 'HW_cal_day_mouseover_class_name', 'HW_cal_day_empty_class_name'];

	// Properties
	this.table = null;
	this.days = new Array();
	this.left_td = null;
	this.title_td = null;
	this.right_td = null;
	this.day_headings = new Array();
	this.error = false;
	this.month_abbreviation = false;
	this.table_css_class_name = null;
	this.month_nav_css_class_name = null;
	this.title_css_class_name = null;
	this.day_heading_css_class_name = null;
	this.day_standard_css_class_name = null;
	this.day_event_css_class_name = null;
	this.day_empty_css_class_name = null;

	// Default properties
	this.w = window;
	this.jsidbase = 'HW_cal_' + window.HangWire.unique_number();
	this.d = new Date();
	this.year = this.d.getFullYear();
	this.month = this.d.getMonth() + 1;
	this.day = this.d.getDate();
	this.calendar_system = null;
	this.calendar_systems = new Object();
	this.numbering_system = false;				// default to using numbers
	this.day_names = new Object();
	this.day_names.full = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday');
	this.day_names.abbreviated = new Array('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');
	this.monthdays = new Array();

	// Methods
	this.add_calendar_system = HW_cal_add_calendar_system;
	this.add_leap_alternative = function (id, lid, ily, fm, lml, am) { this.calendar_systems[id].add_leap_alternative(lid, ily, fm, lml, am); }
	this.set_css = HW_cal_set_css;
	this.set_calendar_system = HW_cal_set_calendar_system;
	this.display_month_title = HW_cal_display_month_title;
	this.current_month_system = HW_cal_current_month_system;
	this.how_to_do_layout = HW_cal_do_layout;
	this.change_layout = HW_cal_change_layout;
	this.set_month = HW_cal_set_month;
	this.move_month = HW_cal_move_month;
	this.move_year = HW_cal_move_year;
	this.clear_events = HW_cal_clear_events;
	this.add_event = HW_cal_add_event;
	this.how_to_get_events = function (year, month) {}
	this.how_to_do_event_layout = function (day, eventsdiv, what) {		// what is an object (associative array) of data
	}
	this.refresh = HW_cal_refresh;

	// Set Gregorian calendar system
	var fullmonths = new Array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December');
	var abbmonths = new Array('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
	var mlengths = new Array(31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	var lmlengths = new Array(31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
	this.add_calendar_system('gregorian:en', fullmonths, mlengths, abbmonths);
	this.add_leap_alternative('gregorian:en', 'leap', function (year) { return ((year % 4 == 0) && (year % 400 != 0)); }, fullmonths, lmlengths, abbmonths);
	this.set_calendar_system('gregorian:en');

	// Create day objects
	for(var i=0; i<6; i++)
	{
		this.days[i] = new Array();
		for(var j=0; j<7; j++)
		{
			this.days[i][j] = new HW_cal_Day(i, j, this);
		}
	}

	// Create left month nav object: defaults to anchor with '<<'
	this.left_month_nav = this.w.document.createElement('a');
	this.left_month_nav.appendChild(this.w.document.createTextNode('<<'));
	this.left_month_nav.setAttribute('href', 'javascript://');
	this.left_month_nav.setAttribute('id', this.jsidbase + '_HW_cal_left');
	this.left_month_nav.onclick = function () { t.move_month(-1); }

	// Create title cell and title text objects
	this.title_td = this.w.document.createElement('td');
	this.title_td.setAttribute('id', this.jsidbase + '_HW_cal_title');
	this.title_text = this.w.document.createTextNode(window.HangWire.nbsp);
	this.title_td.appendChild(this.title_text);

	// Create right month nav object: defaults to anchor with '>>'
	this.right_month_nav = this.w.document.createElement('a');
	this.right_month_nav.appendChild(this.w.document.createTextNode('>>'));
	this.right_month_nav.setAttribute('href', 'javascript://');
	this.right_month_nav.setAttribute('id', this.jsidbase + '_HW_cal_right');
	this.right_month_nav.onclick = function () { t.move_month(1); }

	// Table for calendar
	this.table = this.w.document.createElement('table');
	this.table.setAttribute('id', this.jsidbase + '_HW_cal_table');

	// Week headings
	this.day_headings = new Array();
	for(var i=0; i<7; i++)
	{
		this.day_headings[i] = this.w.document.createElement('td');
		this.day_headings[i].setAttribute('id', this.jsidbase + '_HW_cal_day_heading_' + i);
	}

	// Do layout, set CSS, set month and finish
	this.how_to_do_layout();
	this.set_css(css[0], css[1], css[2], css[3], css[4], css[5], css[6], css[7], css[8]);
	this.set_month(this.year, this.month); /**/
}

