/*
dropdown menus based on son of suckerfish.

Beefed up a little by Grae to allow for multiple suckerfish drop downs within the one page, from one linked javascript.
Checks to ensure presence of element by id before attaching function.
*/

/* #nav dropdown */
function dropDownNav()
{
	var sfEls = document.getElementById("nav").getElementsByTagName("LI");
	for (var i=0; i<sfEls.length; i++)
	{
		sfEls[i].onmouseover=function()
		{
			this.className+=" sfhover";
			this.style.zIndex=200;
		}
		sfEls[i].onmouseout=function()
		{
			this.className=this.className.replace(new RegExp(" sfhover\\b"), "");
		}
	}
}

/* Check for elements to attach drop down functions to */
function elementChecker()
{
	/* check for main nav */
	if (document.getElementById('nav') != null)
	{
		dropDownNav();
	}

}

/* onload run element checker function */
if (window.attachEvent)
{
	window.attachEvent("onload", elementChecker);
}

/* ***********************************************************************

List Filter script
By Tibor Grubits, ABCB

Based on the concept by:
  Author: Justin Whitford
  Source: www.evolt.org
  Node: (55035) Dynamically Filtering Dropdown Lists in Javascript


Structure:
	Form (with a name defined) containing:
	~ Criteria as drop-down SELECT elements
	~ Helpers as SPAN elements
	~ A file list SELECT element (with an ID defined).
	~ A submit button: the form's submit action will incorporate the download function.
	~ A reset button: the form's reset action will incorporate the reset criteria function.
	~ A SPAN element with ID="calcVersion" to show the version of the currently selected calculator.
	
Usage:
	1. Set up the form in accordance with the structure above.
	2. Change the parameters in the call to initialiseFilter on the first line of code.
	3. The addOnloadListener function is called automatically and configures all the actions, events and data structures.

Configuration:
	Define custom attributes on the drop-down criteria SELECT elements:
	(In the form ~ <attribute> = <value / description>
	~ critera = true	MUST have this for the filter to consider the drop-down as filter criteria.

	Define custom attributes on the drop-down criteria OPTION elements:
	(In the form ~ <attribute> = <value / description>
	~ requires = <other criteria id>/<other option value>
	~ prevents = <other criteria>
	~ allows = <other criteria
	
	Separate multiple criteria with a ";" eg:
		requires="BCAYear/2009;BCAVolume/Vol_One"
	The requires directive means that if the current option is selected the other criteria/option will be automatically selected.
	The prevents directive means that if the current option is selected the the other criteria is disabled.
	The allows directive works to undo a disable based on the prevents directive.
	
	
	Define custom attributes on the helper SPAN elements:
	(In the form ~ <attribute> = <value / description>
	~ showif = <other criteria id>/<other option value>
	
	The showif directive lists the criteria for which the SPANned text will be displayed.
	Separate multiple criteria with a ";" for AND matching mode, or with a "|" (pipe character) for OR matching mode.
	
	


********************************************************************** */

//	Load the filter
//	Change the parameters to initialiseFilter to be the form name, and the id of the file list.
addListener("load", window, function startFilter() {
		initialiseFilter("selector", "fileList", startFilter);
	});

function addListener(eventName, theElement, theFunction) { 
/*
	Cross-browser function to add event handlers.
*/
	if (theElement.addEventListener) {
		theElement.addEventListener(eventName, theFunction, false);
	} else if (theElement.attachEvent) {
		theElement.attachEvent("on" + eventName, theFunction);
	} else {
		switch (eventName) {
			case "load":	chainEvent(theElement.onload, theFunction);		break;
			case "change":	chainEvent(theElement.onchange, theFunction);	break;
			case "submit":	chainEvent(theElement.onsubmit, theFunction);	break;
			case "reset":	chainEvent(theElement.onreset, theFunction);	break;
			default:		break;
		}
	}
}

function chainEvent(theEvent, newFunction) {
/*
	Fall-back function to chain events.
*/
	if (typeof theEvent != "function") {
		theEvent = newFunction;
	} else {
		var oldEvent = theEvent;
		theEvent = function () {
				if (oldEvent) oldEvent();
				newFunction();
			};
	}
}

function initialiseFilter(formID, listID, theFunction) {
/*
	Runs the filter initialisation.
	Arguments:
	~ formID (String): ID of Form that contains the drop-down boxes with the rules.
	~ listID (String): ID of List Object that contains the file list and to which data will be attached.
	~ theFunction (Function): Function attached to window load event.
*/
	// Convert form ID and list ID to object references
	if (!formID) var formID = "selector";
	if (!listID) var listID = "fileList";
	
	var theForm = document.getElementById(formID);
	var theList = document.getElementById(listID);

	if (theForm && theList) {	
		attachHandler(theForm, theList);	// Attach filter function to criteria drop-down boxes, and show version function to file list.
		attachFormAction(theForm, theList);	// Attach form submit/reset functions
		buildArray(theList);	// Make copy of original file list
		buildRules(theForm, theList);	// Store Requires / Allows / Prevents directives
	}

	// Clean up window onload handler
	if (theFunction) {
		if (window.removeEventListener) {
			window.removeEventListener("load", theFunction, false);
		} else if (window.detachEvent) {
			window.detachEvent("onload", theFunction);
		}
	}
}

function attachHandler(theForm, theList) {
/*
	Attaches the filterList function to the onchange event of criteria drop-downs.
	Attaches to the form an array of pointers to the criteria.
	Arguments:
	~ theForm (FORM): Form Object that contains the drop-down boxes to attach to.
	~ theList (SELECT): List Object that contains the file list.
*/
	//	Array for pointers to criteria
	theForm.allCriteria = new Array();
	var allSelects = theForm.getElementsByTagName("select");
	if (allSelects) {
		for (var i=0; i<allSelects.length; i++) {
			if (!allSelects[i].getAttribute("criteria")) continue;
			//	Find form SELECT elements that are marked as criteria
			addListener("change", allSelects[i], function() {
					filterList(theForm.id, theList.id);
				}
			);
			//	Store pointer to the criteria in the array
			theForm.allCriteria[theForm.allCriteria.length] = allSelects[i];
		}
	}
	//	Attach function to file list to show current calculator version 
	addListener("change", theList, function() {
			showFileInfo(theList.id);
		}
	);
}

function attachFormAction(theForm, theList) {
/*
	Attaches the download and reset functionality to the form actions.
	Arguments:
	~ theForm (FORM): Form Object that should be updated.
	~ theList (SELECT): List Object that contains the file list.
*/
	addListener("submit", theForm, function() {
			doDownload(theList.id); 
			return false;
		}
	);
	addListener("reset", theForm, function() {
			resetFilter(theForm.id, theList.id);
		}
	);
}

function buildArray(theList) {
/*
	Attaches a copy of the initial list items to the file list.
	Arguments:
	~ theList (SELECT): List Object that will have copy attached.
*/
	theList.allOptions = new Array();
	for (var i=0; i<theList.length; i++) {
		theList.allOptions[theList.allOptions.length] = new Array(theList[i].value, theList[i].text, theList[i].getAttribute("filename"), theList[i].getAttribute("calcversion"));
	}
}

function buildRules(theForm, theList) {
/*
	Attaches the Requires / Allows / Prevents / Helpers directives to the file list.
	Arguments:
	~ theForm (FORM): Form that contains the drop-down boxes with rules.
	~ theList (SELECT): List Object that will have the rules attached.
*/
	theList.arrayRequires = new Array();
	theList.arrayPrevents = new Array();
	theList.arrayAllows = new Array();
	theList.arrayHelpers = new Array();

	for (var i=0; i<theForm.allCriteria.length; i++) {
		// Loop through each SELECT element that is marked as filter "criteria"
		for (var j=0; j<theForm.allCriteria[i].options.length; j++) {
			var option = theForm.allCriteria[i].options[j];
			if (option.getAttribute("requires")) {
				// Read "requires" directive for each option and store in array attached to theList
				var aRequires = option.getAttribute("requires").split(";");
				for (var k=0; k<aRequires.length; k++) {
					theList.arrayRequires[theList.arrayRequires.length] = Array(Array(theForm.allCriteria[i].id, option.value), aRequires[k].split("/"));
				}
			}
			if (option.getAttribute("allows")) {
				// Read "allows" directive for each option and store in array attached to theList
				var aAllows = option.getAttribute("allows").split(";");
				for (var k=0; k<aAllows.length; k++) {
					theList.arrayAllows[theList.arrayAllows.length] = Array(Array(theForm.allCriteria[i].id, option.value), aAllows[k]);
				}
			}
			if (option.getAttribute("prevents")) {
				// Read "prevents" directive for each option and store in array attached to theList
				var aPrevents = option.getAttribute("prevents").split(";");
				for (var k=0; k<aPrevents.length; k++) {
					theList.arrayPrevents[theList.arrayPrevents.length] = Array(Array(theForm.allCriteria[i].id, option.value), aPrevents[k]);
				}
			}
		}
	}
	var helpers = theForm.getElementsByTagName("span");
	if (helpers) {
		for (var i=0; i<helpers.length; i++) {
			if(!helpers[i].getAttribute("showif")) continue;
			// Loop through each SPAN element that has showif criteria
			var bOrMode = helpers[i].getAttribute("showif").indexOf("|") >= 0;
			var sSplitToken = (bOrMode) ? "|" : ";";
			var sRegExpToken = (bOrMode) ? "|" : "+.*";
			var conditions = new Array();
			var aHelpers = helpers[i].getAttribute("showif").split(sSplitToken);
			for (var j=0; j<aHelpers.length; j++) {
				var aCondition = aHelpers[j].split("/");
				conditions[conditions.length] = "(_" + aCondition[1] + ")";
			}
			// Store the Helper details in array attached to theList
			//		[0] (String)	Helper ID
			//		[1] (Boolean)	Or Matching Mode
			//		[2] (Array)		Conditions
			//		[3] (Object)	RegExp object with matching pattern
			theList.arrayHelpers[theList.arrayHelpers.length] = Array(helpers[i].id, bOrMode, aHelpers, new RegExp(conditions.join(sRegExpToken), "i"));
		}
	}
}

function showFileInfo(listID) {
/*
	Shows the current version of the selected calculator and any relevant Helpers.
	Arguments:
	~ listID (String): ID of the List Object that has file list.
*/
	var theList = document.getElementById(listID);
	var infoSpan = document.getElementById("calcVersion");
	if (theList.selectedIndex < 0) {
		infoSpan.innerHTML = "";
	} else {
		var theOption = theList.options[theList.selectedIndex];
		infoSpan.innerHTML = "current version is " + theOption.getAttribute("calcversion");
		showHelpers(theList, "_" + theOption.value);
	}
}

function cleanseForm(theList) {
/*
	Validates the drop-down selections based on the Requires / Allows / Prevents directives.
	Arguments:
	~ theList (SELECT): List Object that has the pre-built rules to apply.
*/
	for (var i=0; i<theList.arrayRequires.length; i++) {
		// Apply each "requires" directive
		if (document.getElementById(theList.arrayRequires[i][0][0]).value == theList.arrayRequires[i][0][1]) {
			selectItem(document.getElementById(theList.arrayRequires[i][1][0]), theList.arrayRequires[i][1][1]);
		}
	}
	for (var i=0; i<theList.arrayAllows.length; i++) {
		// Apply each "allows" directive
		if (document.getElementById(theList.arrayAllows[i][0][0]).value == theList.arrayAllows[i][0][1]) {
			document.getElementById(theList.arrayAllows[i][1]).disabled = false;
		}
	}
	// Apply "prevents" after "allow"
	for (var i=0; i<theList.arrayPrevents.length; i++) {
		// Apply each "prevents" directive
		if (document.getElementById(theList.arrayPrevents[i][0][0]).value == theList.arrayPrevents[i][0][1]) {
			document.getElementById(theList.arrayPrevents[i][1]).disabled = true;
		}
	}
}

function selectItem(theList, theValue) {
/*
	Selects an item in a list based on value, not index.
	Arguments:
	~ theList (SELECT): List object to search and select.
	~ theValue (String): Value to match.
*/
	for (var i=0; i<theList.options.length; i++) {
		// Find the item in the list and select it.
		if (theList.options[i].value == theValue) {
			theList.selectedIndex = i;
			return;
		}
	}
}

function buildPattern(theForm, theList) {
/*
	Creates the RegExp object to be used for matching and shows relevant Helpers.
	Arguments:
	~ theForm (FORM): Form that contains the drop-down boxes with the matching criteria.
	~ theList (SELECT): List Object that contains the Requires / Allows / Prevents / Helpers directives.
	Returns: RegExp object.
*/
	var conditions = new Array();	// For RegExp string fragments representing filter criteria
	var condCount = 0;	// Internal counter
	
	// Apply Requires / Allows / Prevents directives	
	cleanseForm(theList);
	
	for (var i=0;i<theForm.allCriteria.length;i++) {
		if (theForm.allCriteria[i].getAttribute("ignorevalue")) continue;
		// Loop through each SELECT element that is marked as filter "criteria"
		var condition = theForm.allCriteria[i].options[theForm.allCriteria[i].selectedIndex].value;
		if (condition.length > 0) {
			// If condition is not blank then add RegExp string fragment to array
			conditions[condCount] = "(_" + condition + ")";
			condCount++;
		}
	}
	showHelpers(theList, conditions.join("", "i"));
	// Create RegExp object based on concatenation of RegExp string fragments
	return new RegExp(conditions.join("+.*"), "i");
}

function showHelpers(theList, matchString) {
/*
	Shows relevant helpers.
	Arguments:
	~ theList (SELECT): List Object that contains the array of Helpers.
	~ matchString (String): String of current criteria or file value to match against.
*/

	for (var i=0; i<theList.arrayHelpers.length; i++) {
		document.getElementById(theList.arrayHelpers[i][0]).style.display = (theList.arrayHelpers[i][3].test(matchString)) ? "" : "none";
	}
}

function addOption(theList, theOption) {
/*
	Adds an item to a list.
	Arguments:
	~ theList (SELECT): List Object to which item will be added.
	~ theOption (Array): Array defining the new list item:
		[0] Item value attribute
		[1] Item text
		[2] OPTIONAL Item filename attribute
		[3] OPTIONAL Item calcversion attribute
*/
	// Define new option
	var newOption = document.createElement("option");
	newOption.value = theOption[0];
	newOption.text = theOption[1];
	if (theOption.length = 4) {
		newOption.setAttribute("filename", theOption[2]);
		newOption.setAttribute("calcversion", theOption[3]);
	}
	// Add new option to list
	theList.options.add(newOption);
}

function filterList(formID, listID) {
/*
	Applies the filter to the form.
	Arguments:
	~ formID (String): Name of form that contains the drop-down boxes with criteria.
	~ listID (String): ID of list Object that has attached data and will display results.
	
*/
	// Convert form ID and list ID to object references
	var theForm = document.getElementById(formID);
	var theList = document.getElementById(listID);

	// Run the initialisation if it did not run from page onLoad
	if (!theList.allOptions) initialiseFilter(formID, listID);
	
	// Update filter RegExp
	var filterPattern = buildPattern(theForm, theList);
	
	// Clear list
	theList.length = 0;

	for (i=0;i<theList.allOptions.length;i++) {
		// Loop through list of available files and add them back to the list if they match the filter criteria (RegExp)
		if (filterPattern.test("_" + theList.allOptions[i][0]))
			addOption(theList, theList.allOptions[i]);
	}
	// Be helpful if there are no results
	if (theList.options.length==0) {
		addOption(theList, Array("", "No calculators match your criteria.", "#"));
		theList.disabled = true;
	} else {
		theList.disabled = false;
	}
//	When filterList runs there is no file selected so the following line is redundant.
//	showFileInfo(theList.id);
}

function doDownload(listID) {
/*
	Downloads the selected file based on filename attribute of list item.
	Arguments:
	~ listID (String): ID of list Object that has the file names.
*/
	var theList = document.getElementById(listID);
	if (theList.selectedIndex < 0) {
		alert ("Please select one of the available calculators first.");
	} else {
		var theOption = theList.options[theList.selectedIndex];
		
		var newURL = "/~/media/" + theOption.getAttribute("filename");
		
		if (window.addEventListener) 
			window.open(newURL);
		else
			window.location.replace(newURL);
	}
}

function resetFilter(formID, listID) {
/*
	Resets all criteria to first item on the list.
	Then calls filterList to update the file list based on the new (empty) criteria.
	Arguments:
	~ formID (String): Name of form that contains the drop-down boxes with criteria.
	~ listID (String): ID of list Object that has attached data and will display results.
	
*/
	var theForm = document.getElementById(formID);
	for (var i=0; i<theForm.allCriteria.length; i++) {
		theForm.allCriteria[i].selectedIndex = 0;
	}
	filterList(formID, listID);
}

/****  HELPER FUNCTIONS FOR INTERNAL TESTING ONLY  ****/

function fullURL(newTail) {
	var currentURL = String(window.location);
	var baseURL = Left(currentURL, currentURL.lastIndexOf("/"));
	while (Left(newTail, 3) == "../") {
		baseURL = Left(baseURL, baseURL.lastIndexOf("/"));
		newTail = Right(newTail, -3);
	}
	return baseURL + "/" + newTail;
}

function Left(str, n) {
	var theString = String(str);
	if (n <= 0)
	    return theString.substring(0, theString.length + n);
	else if (n > theString.length)
	    return theString;
	else
	    return theString.substring(0,n);
}

function Right(str, n) {
	var theString = String(str);
	if (n <= 0)
	    return theString.substring(- n);
	else if (n > theString.length)
	    return str;
	else
	    return theString.substring(theString.length - n);
}
