//Global variables
var allowable_inactivity_length;
var inactivity_warning_timer;
var inactivity_timer;
var form_id;
var selected_control;

//Define an object to hold the collapsesd/expanded status of all collapse bars.  This object will function as an associative array,
//	but it is important not to define it as an Array object, because it will then automatically be given a 'length' attribute 
//	(initially set to zero) that will never be incremented if you merely add associative-type attributes.  This creates problems for the JSON
//	'stringify' function b/c it sees the presence of the length attribute and decides to treat the object as an array, but then it tries to 
//	iterate (zero times) through the object's numeric-array-style attributes (which are nonexistent), leading to an empty result. (ND - 2008-03-07)

var collapseNodeTree = new Object();
collapseNodeTree.numNodes = 0;

/**
 *
 * @access public
 * @return void
 **/
function setTimeoutLength(timeout_length) {
	allowable_inactivity_length = timeout_length;  //this is modifying the global var above
};

/**
 *
 * @access public
 * @return void
 **/
function displayTimeoutWarning(){
	// Display the previously-written warning
	document.getElementById('session_timeout_warning_layer').style.visibility = "visible";
	// Bring this window to the forefront of the user's screen, so they see the warning
	//	This doesn't seem to be working in Firefox 2 or IE 7
	window.focus();
}

/**
 *
 * @access public
 * @return void
 **/
function terminate_session(){
	//Tell the session that we've timed out
	ajax_session_handler.update('action=terminate&reason=session_timed_out', 'POST');
	//Send the browser to the session_terminated.php page, AFTER we've set the necessary session vars
	//  Do this by overwriting ajax_session_handler's "callback" function (which is always called after the XMLHTTPRequest finishes)
	ajax_session_handler.callback = function() {window.location = "session_terminated.php";};
}

/**
 * This function is called whenever the user exhibits activity.  Its job is to start/restart the countdown
 *	toward timeout (and near-timeout), as well as update the PHP session to reflect the most recent activity.
 * @access public
 * @return void
 **/
function activityHandler(is_page_load) {
	// Start counting down the seconds until we've reached 90% of the allowable inactivity length,
	//	at which point we'll call another function to display the warning.
	// Also, start counting down until 100%, at which point we'll redirect to the logout page
	// Whenever this function is called, we should reset those countdowns.
	clearTimeout(inactivity_warning_timer);
	clearTimeout(inactivity_timer);
	var warning_time = allowable_inactivity_length * 0.9;
	// Warn the user after the appropriate amount of milliseconds
	inactivity_warning_timer = setTimeout("displayTimeoutWarning()",(warning_time * 1000));
	inactivity_timer = setTimeout("terminate_session()",(allowable_inactivity_length * 1000));
	// Furthermore, we should AJAXically update the PHP session to reflect this most recent activity,
	//	unless this function is being called right after a page load (see the parameter), in which case the session has just been updated.
	if(is_page_load == "false") {
		//AJAXically update $_session['last_access']
		ajax_session_handler.update('action=maintain', 'POST');
	}
}

function setupGlobalEvents(){
	//Setup duplication events (where the user is given the option of duplicating a particular element, such as in a form context)
	setupDuplicationEvents(document);
}

function setupHelptext(root_el){
	if (typeof root_el == "undefined") {
		root_el = document;
	}
	//Setup events to display helptext elements
	var help_zones = getElementsByClassName(root_el, "DIV", "help_zone");
	var num_hz = help_zones.length;
	for (var i = 0; i < num_hz; i++) {
		var help_zone = help_zones[i];
		var help_zone_id = help_zone.id;
		//Get the helptext element with nearly the same id as this help_zone
		helptext_id = help_zone_id.replace(/help_zone$/, "helptext");
		//I use this YUI method instead of document.getElementById(helptext_id) because setupHelptext is often called on an element that has
		//	not yet been placed in the DOM
		var helptext = YAHOO.util.Dom.getElementsBy(
				function(one_el){
					return one_el.id == helptext_id;
				},
				"DIV" , root_el)[0]; 

		if (helptext) {
			YAHOO.util.Event.addListener(help_zone, "mouseover", function(){
				show(this.id.replace(/help_zone$/, "helptext"));
			});
			YAHOO.util.Event.addListener(help_zone, "mouseout", function(){
				hide(this.id.replace(/help_zone$/, "helptext"));
			});
		}
	}
}

function setupDuplicationEvents(root_el) {
	var duplicators = getElementsByClassName(root_el, "div", "add_another");
	var length = duplicators.length;
	for(var i = 0; i < length; i++) {
		YAHOO.util.Event.addListener(duplicators[i], "click", function(){
			addAnotherControl(this);	
		});
	}
}

function collapseBarsCommInit() {
	collapse_bars_sync = new ajaxObject('', 'collapse_bars_manager.php');
	//response_text should contain the (simplified) collapseNode tree structure in JSON form
//	collapse_bars_sync.callback = 	function(response_text) { 
										setupCollapseBars("true");
//									};
	//ask the server side manager whether the user has just logged in									
//	collapse_bars_sync.update('action=get_just_logged_in', 'POST');

}

/* This function performs the most generic setup procedure(s) for collapsable elements.  It may be called by more customized
 * 	setup functions, but is designed to be useful to as many pages in as many contexts as possible
 * Created: 2008-08-29
 * Author: Nathanael Dewhurst
 * Parameters: parent_element - this string is the id of a DIV element possessing one or more "collapse_bar" descendant nodes 
 */
function basicCollapseBarSetup(parent_element, collapse_bars_tag_name) {
	if (collapse_bars_tag_name) {
		collapse_bars = getElementsByClassName(document.getElementById(parent_element), collapse_bars_tag_name, "collapse_bar");
	}
	else {
		collapse_bars = getElementsByClassName(document.getElementById(parent_element), "*", "collapse_bar");
	}
		for(var i = 0; i < collapse_bars.length ; i++) {
			var collapse_bar = collapse_bars[i];
			//Give each "collapse bar" element an event to call the collapse_expand toggle function whenever the element is clicked
			collapse_bar.onclick = function(){collapse_expand(this.id);};
			//If this bar is supposed to be collapsed, collapse it
			if(collapse_bar.className.match(/\bcollapsed\b/)) {
				collapse(collapse_bar.id, true);
			}
		}
}

function setupCollapseBars(just_logged_in) {
	if(just_logged_in.match(/true/i)) {
		basicCollapseBarSetup('projects_list', 'div');
	
		setupExpandAlls();
		collapse_bars_sync.callback = 	function(response_text) {
											//Response should be (simplified) collapseNode tree in JSON form
											setupNodeTree(response_text);
										};
		collapse_bars_sync.update('action=init', 'POST');
	}
	else if(just_logged_in.match(/false/i)){
		//If this is not the first time the user has loaded this page since logging in...
		collapse_bars_sync.callback = 	function(response_text) {
			//Response should be (simplified) collapseNode tree in JSON form
			//TODO: watch out for junk charachters getting into the response_text, like \r\n <-- not sure where these are coming from, or how they
			//	are affecting operations... (ND - 2008-03-19)
			setupNodeTree(response_text);
			//After the tree is properly setup, we will walk the tree and set collapse_bar properties accordingly
			for (node_id in collapseNodeTree) {
				//TODO: check to make sure the attribute with this name is an object of type collapseNode (it should be, normally, but if
				//	we extend the Object protptype, collapseNodeTree will have some additional attributes that we might be drudging up
				//	with this iteration)
				var node = collapseNodeTree[node_id];
				var node_element = document.getElementById(node_id);
				node_element.className.replace("/fresh|bell_air|stale/", node.getFreshness());
				node_element.className.replace("/collapsed|expanded/", node.getCollapsedStatus());
				//Give the "collapse bar" (a.k.a. node) element an event to call the collapse_expand toggle function whenever the element is clicked
				node_element.onclick = function(){
					collapse_expand(this.id);
				};
				//If this bar is supposed to be collapsed, collapse it
				if (node_element.className.match(/\bcollapsed\b/)) {
					collapse(node_element.id);
				}
			}
			setupExpandAlls();
		};
		collapse_bars_sync.update('action=get_tree', 'POST');
	}
}	

//Accept a JSON string containing a tree of nodes corresponding to collapse bars.  Reconstruct the tree in terms of special javascript objects.
function setupNodeTree(response_text) {
	var tree = JSON.parse(response_text);
//	var print_element = document.getElementById("message_container"); 
//	print_element.innerHTML += "\n<br/>";
	for (node in tree) {
//		print_element.innerHTML += node + "\n<br/>";
		var node_data = tree[node];
		//Create a new collapseNode object with the appropriate DOM ID and parent node ID
		var one_collapse_node = new collapseNode(node, node_data['parent_node'], node_data['last_update'], node_data['freshness'], node_data['collapsed_status']);
//		for(attribute in node_data) {
//			print_element.innerHTML += "\t -->&nbsp;" + attribute + "\n<br/>";
//		}
		collapseNodeTree[node] = one_collapse_node;
		collapseNodeTree.numNodes++;
	}
	//collapseNodeTree now contains all the node objects, but each one has an empty childNodes array 
	//	and a parent attribute that is a string rather than a reference to an object.
	//So, we walk through the collection of nodes and sew these familial bonds
	for (node_id in collapseNodeTree) {
		if (node_id != "numNodes") {
			var node = collapseNodeTree[node_id];
			var parent_id = node.getParent();
			if (parent_id in collapseNodeTree) {
				var parent_node = collapseNodeTree[parent_id];
				parent_node.addChild(node);
				node.setParent(parent_node);
			}
		}
	}
}

function setupExpandAlls() {
	var expand_alls = getElementsByClassName(document, "div", "expand_all");
	for(var i = 0; i < expand_alls.length ; i++) {
		//Display each "expand_all" element
		expand_alls[i].style.display = "block";
		//Give each "expand_all" element an event to call the expand_all function whenever the element is clicked
		expand_alls[i].onclick = function(){expand_all(this.id);};
	}
	var collapse_alls = getElementsByClassName(document, "div", "collapse_all");
	for(var i = 0; i < collapse_alls.length ; i++) {
		//Display each "collapse_all" element
		collapse_alls[i].style.display = "block";
		//Give each "collapse_all" element an event to call the collapse_all function whenever the element is clicked
		collapse_alls[i].onclick = function(){collapse_all(this.id);};
	}
}

/*
function setCollapseBarsStatus(whitelist) {
	//Set the collapse_bars_status array in the session AJAXically
	//We either send a subset of the collapse_bars_status array (i.e. only one at a time)...
	if(whitelist != null) {
		var cbars_status_JSON_text = JSON.stringify(collapse_bars_status, whitelist);
	}
	//Or, we send the whole thing, such as when the user first logs in
	else {
		var cbars_status_JSON_text = JSON.stringify(collapse_bars_status);
	}
	var passData = "action=set&cbars_status=" + cbars_status_JSON_text;
	collapse_bars_sync.update(passData, 'POST');
}
*/
	
function collapse_expand(id) {
	if (collapseNodeTree.numNodes > 0) {
		//If this element's little siblings are hidden, then show them.
		if (collapseNodeTree[id].getCollapsedStatus() == "collapsed") {
			expand(id);
		}
		//Otherwise, this element's little siblings are currently displayed; in this case, "hide" them.
		else {
			collapse(id);
		}
	}
	else {
		//If this element is collapsed, then expand it
		if (document.getElementById(id).className.match(/collapsed/)) {
			expand(id);
		}
		//Otherwise, this element's little siblings are currently displayed; in this case, "hide" them.
		else {
			collapse(id);
		}
	}
}

function collapse_all(id) {
	var siblings = document.getElementById(id).parentNode.childNodes;
	//For each sibling...
	for(var i = 0; i < siblings.length ; i++) {
		var sib = siblings[i];
		if(sib.childNodes != null) {
			//...look at its children...
			for(var j = 0; j < sib.childNodes.length ; j++) {
				var nephew = sib.childNodes[j];
				if(nephew.className != null) {
					//...and if this nephew is a collapse bar...
					if(nephew.className.indexOf("collapse_bar") > -1) {
						collapse(nephew.id); //...collapse it
					}
				}
			}
		}
	}
}

function expand_all(id) {
	var siblings = document.getElementById(id).parentNode.childNodes;
	//For each sibling...
	for(var i = 0; i < siblings.length ; i++) {
		if(siblings[i].childNodes != null) {
			//...look at its children...
			for(var j = 0; j < siblings[i].childNodes.length ; j++) {
				if(siblings[i].childNodes[j].className != null) {
					//...and if this nephew is a collapse bar...
					if(siblings[i].childNodes[j].className.indexOf("collapse_bar") > -1) {
						expand(siblings[i].childNodes[j].id); //...expand it
					}
				}
			}
		}
	}
}

function collapse(id, init) {
	if (id) {
		var element = document.getElementById(id);
		if (element) {
			var siblings = element.parentNode.childNodes;
			//For each element in the same family as the bar that was clicked...
			for (var i = 0; i < siblings.length; i++) {
				//...hide that element
				if (siblings[i].style) {
					siblings[i].style.display = "none";
				}
			}
			//After hiding all siblings, bring back the one representative element
			element.style.display = "block";
			if (!init) {
				//Switch the collapsed/expanded class of the element, if necessary (these classes govern styling that indicates collapsed/expanded status to the user)
				var new_class = element.className.replace(/expanded/, "collapsed");
				element.className = new_class;
				//If this collaps_bar corresponds to a collapseNode in a grand collapseNodeTree...
				if (collapseNodeTree.numNodes > 0) {
					//Update the status of this family's bar in the collapseNode tree and in the session/db 
					collapseNodeTree[id].setCollapsedStatus("collapsed");
				}
			}
		}
	}
}

function expand(id) {
	var element = document.getElementById(id);
	if(element) {
		var siblings = element.parentNode.childNodes;
		for(var i = 0; i < siblings.length; i++) {
			var sib = siblings[i]; 
			//Restore this element to the block display mode (we assume that all elements were originally displayed this way)  
			if(sib.style) {
				sib.style.display = "block";
			}
		}
		//Switch the collapsed/expanded class of the element, if necessary (these classes govern styling that indicates collapsed/expanded status to the user)
		var new_class = element.className.replace(/collapsed/, "expanded");
		element.className = new_class;
		//If this collaps_bar corresponds to a collapseNode in a grand collapseNodeTree...
		if (collapseNodeTree.numNodes > 0) {
			//Update the status of this family's bar in the collapseNode tree and in the session/db 
			collapseNodeTree[id].setCollapsedStatus("expanded");
		}
	}
}

function updateFreshnessInDOM(DOMID, freshness) {
	var node_element = document.getElementById(DOMID);
	var new_class_list = node_element.className.replace(/fresh|bell_air|stale/, freshness);
	node_element.className = new_class_list;
	return this;
}

loop_log = new Object();

//This function receives orders for a particular node's info to be updated in the server-side Stored Status Collection
//Currently this function is not prepared to accept multiple nodes as arguments, but we may do that in the future,
//	or we may create another function (probably the former)
//	As it is, the server-side script accepts an array of updated nodes, so even here we format the single node as a 
//	multi-dimensional array with one top-level member.
function updateNodeInSSC(DOMID) {
//	alert("looks like you want to tell the server about some action on the " + DOMID + " element.");
	//Get appropriate node from node tree, in simple array format
	var node_mini_array = Object();
	node_mini_array[DOMID] = collapseNodeTree[DOMID].getSimpleArray();
	//Convert node attribute array into JSON string for transmission
	var node_json = JSON.stringify(node_mini_array);
	//Set the ajax object's callback function to an empty function, 
	//	so whatever function was previously associated with it will not be called this time
	//	(in the future, we may be getting a response and acting upon it)
	collapse_bars_sync.callback = function() {}
	//Pass JSON string as POST parameter to handler script on server
	//The ajax object may be busy at the moment with another request, so continue trying every so often until the object is ready for our update action
	i = 0;
	var ajax_update = function() {
								i++;
								if(collapse_bars_sync.update('action=set_nodes&node_tree=' + node_json, 'POST') == true) {
									window.clearInterval(ajax_update_loop);
									loop_log[DOMID] = i;
								}
							 };
	var ajax_update_loop = window.setInterval(ajax_update, 100);
	return this;
}

/*
 * @param is_intro - If true, then the existing_progeny nodes corresponding to prog_id have already been disappeared 
 */
function disappearProgeny(gen_id, prog_id, is_intro) {
	if(typeof(is_intro) == "undefined") is_intro = false;
	//First, disappear any sub-progeny this progeny element may have
	var my_progeny = gen_prog_map[prog_id];
	if (my_progeny) {
		for (var sub_prog_id in my_progeny) {
			if(my_progeny[sub_prog_id]["existing_progeny"]) {
				disappearProgeny(prog_id, sub_prog_id, false);
			}
		}
	}
	
	//If it hasn't been done already...
	if (is_intro === false) {
		//...disappear all existing copies of this progeny element
		var existing_progeny = gen_prog_map[gen_id][prog_id]["existing_progeny"];
		var num_prog = existing_progeny.length;
		for (var i = 0; i < num_prog; i++) {
			disappear(existing_progeny[i]);
		}
	}
}

/*
 * @param is_intro - If true, then the existing_progeny nodes corresponding to prog_id have already been disappeared 
 */
function displayProgeny(gen_id, prog_id, is_intro) {
	if(typeof(is_intro) == "undefined") is_intro = false;
	//First, display any sub-progeny this progeny element may have
	var my_progeny = gen_prog_map[prog_id];
	if (my_progeny) {
		for (var sub_prog_id in my_progeny) {
			if(my_progeny[sub_prog_id]["existing_progeny"]) {
				displayProgeny(prog_id, sub_prog_id, false);
			}
		}
	}
	
	//If it hasn't been done already...
	if (is_intro === false) {
		//...display all existing copies of this progeny element
		var existing_progeny = gen_prog_map[gen_id][prog_id]["existing_progeny"];
		var num_prog = existing_progeny.length;
		for (var i = 0; i < num_prog; i++) {
			display(existing_progeny[i]);
		}
	}
}

//This function receives orders for a particular element-attribute-status combo to be updated in the db
//The function accepts an array containing one or more rows, each containing element-attribute-status data along with the user's uid.
function updateElementStatusInDB(status_array) {
//	alert("looks like you want to tell the server about some action on the " + DOMID + " element.");
	//Convert element status array into JSON string for transmission
	var status_json = JSON.stringify(status_array);
	//Set the ajax object's callback function to an empty function, 
	//	so whatever function was previously associated with it will not be called this time
	//	(in the future, we may be getting a response and acting upon it)
	collapse_bars_sync.callback = function() {}
	//Pass JSON string as POST parameter to handler script on server
	//The ajax object may be busy at the moment with another request, so continue trying every so often until the object is ready for our update action
	var ajax_update = function() {
								if(element_status_ajax.update('action=set_status&status_array=' + status_json, 'POST') == true) {
									window.clearInterval(ajax_update_loop);
								}
							 };
	var ajax_update_loop = window.setInterval(ajax_update, 100);
	return this;
}

function hide(id) {
	document.getElementById(id).style.visibility = "hidden";
}

function show(id) {
	document.getElementById(id).style.visibility = "visible";
}

function disappear(id) {
	document.getElementById(id).style.display = "none";
}

function display(id){
	document.getElementById(id).style.display = "block";
}

function incrementNumSuffix(old_str) {	
	var id_num = parseInt(old_str.match(/\d+$/));
	id_num++;
	return old_str.replace(/\d+$/, id_num);
}

function swapBaseIds(el, base_ids) {
	if (el.id) {
		el.id = el.id.replace(base_ids[0], base_ids[1]);
		if(el.name) {
			el.name = el.name.replace(base_ids[0], base_ids[1]);
		}
		if(el.hasAttribute("for")) {
			var old_for = el.getAttribute("for");
			var new_for = old_for.replace(base_ids[0], base_ids[1]);
			el.setAttribute("for", new_for);
		}
		return true;
	}
	else return false;
}

function acclimateCopiedEl(el, base_ids){
	swapBaseIds(el, base_ids);

	var old_id = base_ids[0];
	var new_id = base_ids[1];
	
	//Setup necessary events for the new node
	//TODO: copy all events where target equals old_id
	
	//Gen-prog events
	//If gen-prog array has key old_id, then create gen-prog event(s) for new element,
	//	where progeny is found in the gen-prog array (old_id => prog)
	
	
	
	//Validation events
	
	
}	

//TODO: monitor whether this remains in disuse
function getBaseIdViaAddAnother(parent_el){
	var replicator_element = getElementsByClassName(target_el, "div", "add_another")[0];
	var base_id_old = replicator_element.id.replace(/^add_another_(.+)$/, "$1");
	return base_id_old;
}	
	
//function acclimateCopiedRootEl(target_el, base_id_old, is_inc, is_init) {	
function acclimateCopiedRootEl(target_el, is_inc, is_init) {
	if(typeof(is_inc) == "undefined") is_inc = true;
	if(typeof(is_init) == "undefined") is_init = false;
	
	var base_id_old = target_el.id;
	var id_num_match = base_id_old.match(/\d+$/);
	//If the element's id string does not end in a number, assign a default id num of zero, 
	//	which will be incremented momentarily (probably never decremented, according to current usage patterns (ND -- 2008-11-14))
//	var id_num = id_num_match ? parseInt(id_num_match) : 0;

	var id_num, base_id_new;

	if (id_num_match) {
		id_num = parseInt(id_num_match);
		if (is_inc) 
			id_num++;
		else 
			id_num--;
			
		base_id_new = base_id_old.replace(/\d+$/, id_num);
	}
	else {
		id_num = 1;
		base_id_new = base_id_old + "_1";
	}
	
	var base_ids = new Array(base_id_old, base_id_new);
	
	var kids_and_me = collectionToArray(target_el.getElementsByTagName("*"));
	kids_and_me.push(target_el); //this array has only now earned its name
	
	//Walk through the target element and its descendants and update their ids and names to use an incremented integer suffix (e.g. "bank_2" vs. "bank_1")
	//If the element is newly created, then setup its special events (e.g. gen-prog & validation) as well
	if(is_init) {
		//Update the ids/names and certain events that may apply to each descendant of this root el 
		YAHOO.util.Dom.batch(kids_and_me, acclimateCopiedEl, base_ids);
		//Setup all helptext events within this root el
		setupHelptext(target_el);
		//Setup all duplication events within this root el
		setupDuplicationEvents(target_el);
		
		//Purge any form field data, unless the copies_retain_values flag is extant and positive
		var copy_vals = target_el.getAttribute("copies_retain_values");
		if(!target_el.hasAttribute("copies_retain_values") || target_el.getAttribute("copies_retain_values") == "false") {
			resetFormControls(target_el);		
		}	
	}
	else {
		YAHOO.util.Dom.batch(kids_and_me, swapBaseIds, base_ids);
	}
	
	//Update the label of the target element to use an incremented integer suffix (e.g. "Bank #2" vs. "Bank #1")
	var label_el;
	
	if (target_el.className.match(/fieldset/)) {
		//Obtain the legend element
		label_el = target_el.getElementsByTagName("LEGEND")[0];
		//There should be (under the current design technique) a span immediately nested within the legend
		//	If so, that is the real "label element" for the fieldset.  Accomodate an absence of said span for greater flexibility.
		var spans = label_el.getElementsByTagName("SPAN"); 
		if(spans.length > 0)
			label_el = spans[0];
	}
	else {
		//Obtain the label element
		label_el = target_el.getElementsByTagName("LABEL")[0];
	}
	
	var old_content = label_el.innerHTML;
	var numeric_suffix = /\d+\s*$/;
	if (old_content.match(numeric_suffix)) 
		label_el.innerHTML = old_content.replace(numeric_suffix, id_num);
	else if(is_inc) 
		label_el.innerHTML = old_content + " " + id_num;
		
	return id_num;	
}

function addAnotherControl(duplicator_element){
	//Make a direct copy of this element's parent li element and place it after that parent element
	var parent = duplicator_element.parentNode;
	var copy = parent.cloneNode(true);

	//Place the copy in the DOM
	YAHOO.util.Dom.insertAfter(copy, parent);
	
	//Walk through the copy's descendants and update their ids and names to use an incremented integer suffix (e.g. "bank_2" vs. "bank_1")
	//Also update the label of the copy element to use an incremented integer suffix (e.g. "Bank #2" vs. "Bank #1")
	//Function returns the calculated integer "id" number of the main element (i.e. copy)
	//TODO: while iterating, setup for the copied elements any events present in original element, such as gen-prog and validation
	var id_num = acclimateCopiedRootEl(copy, true, true);
	
	//Setup helptext display for new element
//	setupHelptext(copy);
	
	//Delete/hide the original duplicator element 
	duplicator_element.style.display = "none";
	
	var duplicator_els = getElementsByClassName(copy, "div", "add_another");
	var new_master_duplicator = duplicator_els[duplicator_els.length - 1];
	
	//If this is the first copy (i.e. 2nd, not including original), 
	//	add a "remove this ______" element after the newly generated copy (alongside/before the "Add another" element)
	//Otherwise, the removal element should already be present
	//I believe the easiest way to do this is to copy the duplicator and then modify it slightly
	var remove_el_array = getElementsByClassName(copy, "div", "remove_this");
	var remove_el;
	if(remove_el_array.length > 0) {
		remove_el = remove_el_array[0]; 
	}	
	else {
		remove_el = new_master_duplicator.cloneNode(true);
		YAHOO.util.Dom.insertBefore(remove_el, new_master_duplicator);
		remove_el.id = remove_el.id.replace(/add_another/, "remove_this");
		remove_el.className = remove_el.className.replace(/add_another/, "remove_this");
		remove_el.innerHTML = "Remove";
		
		//Now that we're adding a second copy, go ahead and add a numerical suffix to the original
		
	}
	YAHOO.util.Event.addListener(remove_el, "click", function(){
			removeAddedControl(this.parentNode);
		});
	
	//Bring the duplicator element of the latest copy to life by assigning it the necessary events
	//This vitalization is now performed in the acclimateCopiedRootEl function.  I believe it is safe to remove this section. (ND - 2008-11-28)
	//TODO: remove this section after confirming that everything works flawlessly 
/*
	YAHOO.util.Event.addListener(new_master_duplicator, "click", function(){
		addAnotherControl(this);
	});
*/	
	//If additional copies are not allowed, then hide the latest duplicator element 
	//	(though hidden, its potency is necessary because it may be revealed in the event that one of this copy's older siblings is deleted) 
	if (id_num >= parseInt(duplicator_element.getAttribute("max_instances"))) {
		new_master_duplicator.style.display = "none";
	}
}

function removeAddedControl(copy_element) {
	//Instantly hide the element
	copy_element.style.display = "none";
	
	//Determine whether this element is the last in its family
	var is_last = true;
	var next_li = YAHOO.util.Dom.getNextSibling(copy_element);
	var base_id = copy_element.id;
	var numless_base_id = base_id.replace(/(.+)\d+$/, "$1");
	if(next_li) {
		var tmp_solo_array = getElementsByClassName(next_li, "div", "add_another");
		var next_duplicator_el;
		if(tmp_solo_array.length > 0) {
			next_duplicator_el = tmp_solo_array[0];
		}
		else next_duplicator_el = "null";
		if (next_duplicator_el.id) {
			if (next_duplicator_el.id.match(numless_base_id)) {
				is_last = false;
			}
		}
	}

	//If this element is the last in its family, then it contains the only visible "Add another" element
	//	Therefore, we must display the "Add another" element of the preceding sibling upon deleting this one
	if(is_last === true) {
		var sibs_duplicators = getElementsByClassName(YAHOO.util.Dom.getPreviousSibling(copy_element), "div", "add_another");
		sibs_duplicators[sibs_duplicators.length - 1].style.display = "block";
	}
	//If this element is not the last in its family, decrement the int suffix of all its younger siblings
	else {
		var current_li = next_li;
		while(is_last === false) {
//			var base_id = getBaseIdViaAddAnother(current_li);
			id_num = acclimateCopiedRootEl(current_li, false, false);
			next_li = YAHOO.util.Dom.getNextSibling(current_li);
			is_last = true;
			if(next_li) {
				var sibs_duplicators = getElementsByClassName(next_li, "div", "add_another");
				var next_duplicator_el;
				if(sibs_duplicators.length > 0) {
					next_duplicator_el = sibs_duplicators[sibs_duplicators.length - 1];
				}
				else next_duplicator_el = "null";
				if (next_duplicator_el.id) {
					if (next_duplicator_el.id.match(numless_base_id)) {
						is_last = false;
						current_li = next_li;
					}
				}
			}
		}
		//Make sure the duplicator element of the last sibling is displayed (it may have been hidden if num copies was formerly equal to max_instances)
		getElementsByClassName(current_li, "div", "add_another")[0].style.display = "block";
	}

	//Delete this element
	copy_element.parentNode.removeChild(copy_element);
}

								
/*
 * Description: This function will assess whether the target element is completely or partially out of view,
 *   and will shift the viewport as necessary so that the target element becomes visible,
 *   not only vertically, but also horizontally.
 * Params:	target_el - the element that should be brought into view
 *  		margin - a pixel value defining the space between the edges of the target element and the new viewport 
 *  				(the same value is used for horizontal and vertical margin)
 * Notes: This function relies heavily on YUI methods from the DOM class
 * Author: Nathanael Dewhurst
 * Date: 2009-02-27
 */
function scrollIntoViewXY(target_el, margin) {
	if(YAHOO.util.Dom.getY(target_el) < YAHOO.util.Dom.getDocumentScrollTop())
		target_el.scrollIntoView(true);
	else if(YAHOO.util.Dom.getY(target_el) > YAHOO.util.Dom.getDocumentScrollTop() + YAHOO.util.Dom.getViewportHeight())
		target_el.scrollIntoView(false);
	
	var target_x = YAHOO.util.Dom.getX(target_el);
	if (target_x < YAHOO.util.Dom.getDocumentScrollLeft()) 
		window.scrollTo(target_x - margin, YAHOO.util.Dom.getDocumentScrollTop());
	else {
		var right_edge = YAHOO.util.Dom.getDocumentScrollLeft() + YAHOO.util.Dom.getViewportWidth();
		var target_region = YAHOO.util.Region.getRegion(target_el);
		var target_width = target_region.right - target_region.left;
		if (target_x + target_width > right_edge) 
			window.scrollBy(target_x + target_width - right_edge + margin, 0);
	}
}



