/*  dlbExCheck.js

Basic interactive exercise checking script.

Written by Daryl L. Beres (bere0046@umn.edu).
Last updated 8/17/04

*/

// set handlers on a check and erase button in each form on the page
// set first button found with the word "check" in the name as the Check button
// set second button (or by name?) as erase button (not necessary if not using pictorial feedback)
// NOTE: ERASE BUTTON NOT PROGRAMMED YET
if (document.forms.length == 0)
	window.alert('There are no forms to check on this page. Try putting the dlbExCheck script at the end of the document.');
else
	{
	for (var i=0; i<document.forms.length; i++)
		{
		foundCheckButton = false;
		
		// loop through all elements in form to find the Check button
		for (j=0; j<document.forms[i].elements.length; j++)
			{
			if (document.forms[i].elements[j].type == "button")
				{
				var theButtonName = document.forms[i].elements[j].name; // shorthand
				if (theButtonName.search(/check/i) != -1)
					{
					// *** NEED TO SET ALL TYPES OF HANDLERS HERE TO AVOID RETURN KEY BUGS ***
					document.forms[i].elements[j].onclick = function () {check(this.form); return false;};
					document.forms[i].onsubmit = function () {check(this); return false;};
					foundCheckButton = true;
					break;
					}
				} 
			}
		if (foundCheckButton == false)
			window.alert ("No Check button found in Form #" + (i+1) + " on this page.  User will not be able to check the form.");
		
		// checks optional hidden elements at beginning of form to set options
		setPreferences(document.forms[i]);
		}
	}

// function check() is passed a form, and 
// "checks" the form by comparing user-inputted values in the input
// fields with creator-inputted values in the hidden fields.  The hidden
// field(s) directly after an input field list possible correct answers
// for that field.  check() can handle input fields of type text, radio,
// checkbox and list.
function check(theForm) {

var theFeedback = "";
var numOfQuestions = 0;
var numWrong = 0;
var theElem = theForm.elements;

// loop through all the form elements passed to check()
var x = 0;

while (x<theElem.length)
	{
	var theResponse = "";
	var theAnswers = new Array();

	// see what type of element it is--skip the unsupported ones
	// if it's a text box or textarea
	if (theElem[x].type == "text" || theElem[x].type == "textarea")
		{
		// get the text
		theResponse = theElem[x].value;
		}		
		
	// if it's a radio
	// get selected value
	else if (theElem[x].type == "radio")
		{
		/*
		theProps = "";
		for (a in theElem[x])
			theProps += a + "\n";
		alert(theProps);
		*/
		while ((x<theElem.length) && (theElem[x].type == "radio"))
			{
			if (theElem[x].checked)
				theResponse = theElem[x].value;
			x++;
			}
		// need to step back one because we've incremented too far
		x--;
		}
		
	// if it's a checkbox
	else if (theElem[x].type == "checkbox")
		{
		// get checked or not
		if(theElem[x].checked)
			theResponse = "yes";
		else
			theResponse = "no";
		}

	// if it's a drop-down list
	else if (theElem[x].type == "select-one")
		// get selected value
		theResponse = theElem[x].options[theElem[x].selectedIndex].value;
	
	// not a readable field, go onto the next one
	else
		{
		x++;
		continue;
		}
	
	// check to make sure there's at least one hidden element with an answer
	// if no answer defined, alert with error, and go onto next readable field
	QuesElementIndex = x;
	x++;
	if (theElem[x].type != "hidden")
		{
		alert("No answer defined for input #" + x + ". Cannot check this answer.");
		continue;
		}
	// if there's an answer defined, then read in list of answers
	else
		{
		// increment number of questions in exercise
		numOfQuestions++;
		var i = 0;
		while ((x < theElem.length) && (theElem[x].type == "hidden"))
			{
			theAnswers[i] = theElem[x].value;
			i++;
			x++;
			}

		// check answer
		if(!(compare (theResponse, theAnswers, theForm.dlbExCheck.caseSensitive, theForm.dlbExCheck.checkPunctuation)))
			{
			// increment number of questions wrong
			numWrong++;
			
			// add feedback for incorrect answers if desired
			if (theForm.dlbExCheck.detailedFeedback == true)
				theFeedback += "\"" + theResponse + "\" is not correct.\n";
			// toggle graphic feedback image if desired
			if(theForm.dlbExCheck.useGraphicFeedback == true)
				{
				if(theElem[QuesElementIndex].feedbackImage)
					theElem[QuesElementIndex].feedbackImage.src = document.wrongGraphic.src;
				else
					alert('Use graphic feedback preference is chosen, but this question does not have an associated image.  Add the image and script code after each question.');
				}
			}
		else
			{
			// toggle graphic feedback image if desired
			if(theForm.dlbExCheck.useGraphicFeedback == true)
				{
				if(theElem[QuesElementIndex].feedbackImage)
					theElem[QuesElementIndex].feedbackImage.src = document.correctGraphic.src;
				else
					alert('Use graphic feedback preference is chosen, but this question does not have an associated image.  Add the image and script code after each question.');
				}
			}
		}
		
	// go on to next readable field (skip all the hidden fields just read)
	}
	
	// give feedback
	var theScore = Math.round((numOfQuestions - numWrong)/numOfQuestions*100);
	if (numWrong > 0)
		var theMessage = "Please try again.  Your score is:  "+theScore+
			"%.\n("+numWrong+" wrong out of "+numOfQuestions+
			").\n";
	else
		var theMessage = "Great job!  You've got them all correct!\nYour score is 100%.\n";

	// increment number of tries, if tracking
	// and add it to the feedback message
	if (theForm.dlbExCheck.trackTries == true)
		{
		theForm.dlbExCheck.numTries++;		
		theMessage += "This was your ";
		if (theForm.dlbExCheck.numTries == 1)
			theMessage += "1st try.\n\n";
		else if (theForm.dlbExCheck.numTries == 2)
			theMessage += "2nd try.\n\n";
		else if (theForm.dlbExCheck.numTries == 3)
			theMessage += "3rd try.\n\n";
		else
			theMessage += theForm.dlbExCheck.numTries + "th try.\n\n"
		}
	else
		theMessage += "\n"
	
	theFeedback = theMessage + theFeedback;
	if (theForm.dlbExCheck.useAlertFeedback == true)
		{
		// this is a hack to fix an IE5 MacOSX bug where
		// the last 2 lines of an alert message are not viewable
		theFeedback += "\n";
		window.alert(theFeedback);
		}
}

// setPreferences() is passed the elements of a form and looks
// within the element array using associative names to find
// if hidden elements governing the preferences have been set
function setPreferences(theForm) {

// initialize variables to the defaults
theForm.dlbExCheck = new Object;
theForm.dlbExCheck.caseSensitive = true;
theForm.dlbExCheck.checkPunctuation = true;
theForm.dlbExCheck.useAlertFeedback = true;
theForm.dlbExCheck.useFeedbackFrame = false;
theForm.dlbExCheck.detailedFeedback = false;
theForm.dlbExCheck.useGraphicFeedback = false;
theForm.dlbExCheck.trackTries = false;
theElem = theForm.elements; // shorthand

// check to see if hidden elements governing activity preferences have been set

// dlbExCheck.caseSensitive determines whether or not
// the possible responses and correct answers are compared
// with case sensitivity or without
if (theElem['caseSensitive'])
	if (theElem['caseSensitive'].value == 'false')
		theForm.dlbExCheck.caseSensitive = false;

// dlbExCheck.checkPunctuation determines whether or not
// a JavaScript alert message is used to give feedback
if (theElem['checkPunctuation'])
	if (theElem['checkPunctuation'].value == 'false')
		theForm.dlbExCheck.checkPunctuation = false;

// dlbExCheck.useAlertFeedback determines whether or not
// a JavaScript alert message is used to give feedback
if (theElem['useAlertFeedback'])
	if (theElem['useAlertFeedback'].value == 'false')
		theForm.dlbExCheck.useAlertFeedback = false;

// dlbExCheck.detailedFeedback determines whether the feedback
// given to the user (in either a JavaScript alert message or a 
// feedback frame) gives a detailed list of each wrong answer
if (theElem['detailedFeedback'])
	if (theElem['detailedFeedback'].value == 'true')
		theForm.dlbExCheck.detailedFeedback = true;
		
// dlbExCheck.useFeedbackFrame determines whether feedback should be 
// presented in a specified frame (or popup window)
if (theElem['useFeedbackFrame'])
	{
	if (theElem['useFeedbackFrame'].value == 'true')
		{
		theForm.dlbExCheck.useFeedbackFrame = true;
		/*
		// check if there is a frame to use
		for (var i=0; i<top.frames.length; i++)
			{
			var str = "";
			str += top.frames[i].name + "\n";
			}
		alert(str);
		if (top.frames["dlbFeedbackFrame"])
			{
			alert("frame exists");
			theFrame = top.dlbFeedbackFrame;
			}
		else
			alert("no frame"); */
		}
	}

// dlbExCheck.useGraphicFeedback determines whether or not
// checking function should try to toggle graphics to show 
// right/wrong answers (default is no graphics)
if (theElem['useGraphicFeedback'])
	{
	if (theElem['useGraphicFeedback'].value == 'true')
		{
		theForm.dlbExCheck.useGraphicFeedback = true;
		
		// check to make sure browser implements image objects
		if (document.images)
			{
			// check to make sure URLs for all graphics are set, otherwise don't use
			if (!theElem['blankGraphicURL'])
				theForm.dlbExCheck.useGraphicFeedback = false;
			else
				{	
				theForm.dlbExCheck.blankGraphicURL = theElem['blankGraphicURL'].value;
				if (!theElem['correctGraphicURL'])
					theForm.dlbExCheck.useGraphicFeedback = false;
				else
					{	
					theForm.dlbExCheck.correctGraphicURL = theElem['correctGraphicURL'].value;
					if (!theElem['wrongGraphicURL'])
						theForm.dlbExCheck.useGraphicFeedback = false;
					else
						{
						theForm.dlbExCheck.wrongGraphicURL = theElem['wrongGraphicURL'].value;
						// preload the images to save time later
						document.blankGraphic = new Image;
						document.blankGraphic.src = theForm.dlbExCheck.blankGraphicURL;
						document.correctGraphic = new Image;
						document.correctGraphic.src = theForm.dlbExCheck.correctGraphicURL;
						document.wrongGraphic = new Image;
						document.wrongGraphic.src = theForm.dlbExCheck.wrongGraphicURL;
						}
					}
				}
			}
		}
	}

// dlbExCheck.trackTries determines whether or not
// the number of times a user checks a form is tracked.
if (theElem['trackTries'])
	if (theElem['trackTries'].value == 'true')
		{
		theForm.dlbExCheck.trackTries = true;
		theForm.dlbExCheck.numTries = 0;
		}
	

} // end function setPreferences

function compare(resp, ans, caseSensitive, checkPunctuation) {

	if (caseSensitive == false)
		resp = resp.toLowerCase();
	if (checkPunctuation == false)
		resp = stripPunc(resp);
	
	// loop through possible answers and compare with student's response
	for (var i=0; i<ans.length; i++)
		{
		if (caseSensitive == false)
			ans[i] = ans[i].toLowerCase();
		if (checkPunctuation == false)
			ans[i] = stripPunc(ans[i]);
		if (resp == ans[i])
			return true;
		}
	return false;

}

// removes all none-alphanumeric characters
function stripPunc(str) {

	var tempStr = '';
	for (var i=0; i<str.length; i++)
		if ((str.charAt(i) >= 'A' && str.charAt(i) <= 'Z') ||
			(str.charAt(i) >= 'a' && str.charAt(i) <= 'z') ||
			(str.charAt(i) >= '0' && str.charAt(i) <= '9'))
			tempStr += str.charAt(i);
	return tempStr;		
}