/**
* @class
*
* @classdesc
* <p>
* specController() is called via jQueries document.ready function which appears in spectroscopy.html. It is called each time the
* page is loaded or the browser is refreshed. <p>specController is the javascript class responsible for running the
* interface for the spectroscopy JSOCLO.
*
* <p>
* Category is not used in Spectroscopy so we must set the category cookie to the empty string when we initialize.
*
* @see {@link searcher}
* @tutorial ArchitectureOverview
*/
function specController() {
/**
* load the other javascripts and the CSS
*/
loadJS("../js/jquery/jquery.cookie.js");
loadJS("../js/jsoclo/chemUtils.js");
loadJS("../js/jsoclo/analyzerBase.js");
loadJS("../js/jsoclo/spectroAnalyzer.js");
loadJS("../js/jsparser/smidge.js");
loadCSS("../css/spectroscopy.css");
/** the number of attempts made on this quesiton */
var attempts = 0;
/**
* used to give unique div ID's to jsme instances in the response div. Used by createJsmeDiv()
*/
var jsmeDivCount = 2;
/**
* The number of questions in the series. Set by getNumberOfQuestions() used by next and prev quesiton
*/
var questionMax = 0;
/**
* after 3 attempts hints are allowed this controls showing them all at once.
*/
var toggleHints = true; // default since hint button is hidden till needed
/**
* we keep track of all previous answers in submitAnswer()
*/
var previousAnwers = new Array();
/**
* Keeps track of the the completed questions so that scoring works properly. Is reset when the category changes. The array is
* stringified with Json and stored as a cookie.
*/
var completedQuestions = new Array();
try {
//alert("hello");
$.cookie("category", "", {
path : '/'
});
$.cookie("fClass", config.SPEC_FCLASS, {
path : '/'
});
if ($.cookie("question") == null) {
$.cookie("question", 1, {
path : '/'
});
}
$.cookie("questionLock", "unlocked", {
path : '/'
});
$.cookie("completedQuestions", JSON.stringify(completedQuestions), {
path : '/'
});
$.cookie("numQuestionsTried", 0, {
path : '/'
});
$.cookie("score", 0, {
path : '/'
});
questionMax = getNumberOfQuestions();
var analyzer = new spectroAnalyzer();
// set the OCLO Functional Class and page titlE
$("#title").html(config.SPEC_TITLE);
$("#questionNum").html(config.SPEC_TITLE + ": " + config.SPEC_QNAME + $.cookie("question"));
$("#difficultyImg").attr("src", analyzer.getDifficulty());
// handlers for the buttons.
$("#massButton").click(function() {
display(config.SPEC_MASS_IMG);
});
$("#irButton").click(function() {
display(config.SPEC_IR_IMG);
});
$("#cnmrButton").click(function() {
display(config.SPEC_CARBON_IMG);
});
$("#hnmrButton").click(function() {
display(config.SPEC_HYDROGEN_IMG);
});
$("#viewAllButton").click(function() {
display(config.SPEC_ALLSPEC_PAGE);
});
$("#submitAnswerButton").click(function() {
submitAnswer();
});
$("#nextButton").click(function() {
nextQuestion();
});
$("#prevButton").click(function() {
prevQuestion();
});
$("#hintButton").click(function() {
showHint();
});
$("#solutionButton").click(function() {
unlockQuestion();
display(config.SPEC_ANSWER_PAGE, false);
});
// prevent return key on the buttons
$("#solutionButton").keydown(function(e) {
preventReturn(e);
});
$("#hintButton").keydown(function(e) {
preventReturn(e);
});
$("#nextButton").keydown(function(e) {
preventReturn(e);
});
$("#prevButton").keydown(function(e) {
preventReturn(e);
});
$("#massButton").keydown(function(e) {
preventReturn(e);
});
$("#irButton").keydown(function(e) {
preventReturn(e);
});
$("#hnmrButton").keydown(function(e) {
preventReturn(e);
});
$("#cnmrButton").keydown(function(e) {
preventReturn(e);
});
$("#viewAllButton").keydown(function(e) {
preventReturn(e);
});
// $("#helpButton").keydown(function(e) {preventReturn(e);});
// hide the solution button
$("#solutionButton").hide();
$("#hintButton").hide();
if (config.SPEC_UNLOCK_ALL_QUESTIONS) {
unlockQuestion();
}
} catch (e) {
alert("initializeSpectroscopy: " + e);
}
/**
* callback handler for when a new question number has been selected from the next and previous buttons
*/
function loadNewQuestion(num) {
var qnum = num;
// set the questionNumber cookie for reload.
$.cookie("question", qnum, {
path : '/'
});
try {
analyzer.loadQuestionFile(null, null, qnum);
// set the OCLO Functional Class and page titlE
$("#questionNum").html(config.SPEC_TITLE + ": " + config.SPEC_QNAME + $.cookie("question"));
$("#difficultyImg").attr("src", analyzer.getDifficulty());
jsmeApplet.reset();
if (!config.SPEC_UNLOCK_ALL_QUESTIONS) {
$("#response").val("");
$("#solutionButton").hide();
}
attempts = 0;
analyzer.questionScore = config.SPEC_MAX_QUESTION_SCORE;
// if the question has been completed already
// then unlock it.
if (checkComplete()) {
unlockQuestion();
}
} catch (e) {
// an unexpected error happened, probably loading or parsing the
// question file
// so we're going to reset the category and put them back on question
// 1, or a complete reset back to functional groups if needed,
alert(e + " Question Number: " + $.cookie("question"));
$.cookie("score", 0, {
path : '/'
});
$.cookie("question", 1, {
path : '/'
});
$.cookie("numQuestionsTried", 0, {
path : '/'
});
// unlock the question
unlockQuestion();
location.reload();
}
previousAnwers = new Array();
}
/**
* Moves to the next question.
*
* End of questions behaviour is configured using CYCLE_NEXT_PREV value.
*
*/
function nextQuestion() {
if (config.SPEC_UNLOCK_ALL_QUESTIONS) {
unlockQuestion();
}
var locked = $.cookie("questionLock") == "locked";
if (locked) {
alert(config.NO_CATEGORY_CHANGE);
return;
}
var nextQnum = new Number($.cookie("question")) + 1;
if (nextQnum.valueOf() > questionMax) {
if (config.CYCLE_NEXT_PREV) {
nextQnum = new Number(1);
} else {
alert(config.NO_NEXT_QUESTION);
return;
}
}
if (!config.SPEC_UNLOCK_ALL_QUESTIONS) {
// hide the solution button
$("#solutionButton").hide();
$("#hintButton").hide();
}
$("#response").html("");
loadNewQuestion(nextQnum);
}
/**
* Moves to the previous question in the category.
*
* Beginning of questions behaviour is configured using config CYCLE_NEXT_PREV value.
*
*/
function prevQuestion() {
if (config.SPEC_UNLOCK_ALL_QUESTIONS) {
unlockQuestion();
}
var locked = $.cookie("questionLock") == "locked";
if (locked) {
alert(config.NO_CATEGORY_CHANGE);
return;
}
var prevQnum = new Number($.cookie("question")) - 1;
if (prevQnum.valueOf() == 0) {
if (config.CYCLE_NEXT_PREV) {
prevQnum = new Number(questionMax);
} else {
alert(config.NO_PREV_QUESTION);
return;
}
}
if (!config.SPEC_UNLOCK_ALL_QUESTIONS) {
// hide the solution button
$("#solutionButton").hide();
$("#hintButton").hide();
}
$("#response").html("");
loadNewQuestion(prevQnum);
}
/**
* Submits to the analyzer and updates the page attributes and values based on the analyzer responses.
*/
function submitAnswer() {
var answer = jsmeApplet.smiles();
var feedback = "";
try {
if (answer == "") {
throw config.NO_ANSWER;
}
// If this is the first non-empty submitted
// answer we disable navigation
if (previousAnwers.length == 0) {
// lock for page
$.cookie("questionLock", "locked", {
path : '/'
});
}
// if the question has been completed already
// then unlock it.
if (checkComplete()) {
unlockQuestion();
}
if (checkForDuplicates(answer)) {
throw config.DUPLICATE_ANSWER;
}
attempts++;
toggleHints = true;
//
// default a message if the analyzer produces no
// feedback
var tmpMsg = analyzer.getFeedback(answer);
if (tmpMsg.match(/^\s*$/i)) {
feedback += config.UNRECOGNIZED_ANWER;
} else {
feedback += tmpMsg;
}
if (attempts == config.MAXATTEMPTS && !analyzer.isCorrect(answer)) {
unlockQuestion();
if (config.ALERT_PERSIST_MSGS) {
alert(config.getPersistMessage());
} else {
feedback += "<b>" + config.getPersistMessage() + "</b><br>";
feedback += "Solution button is available<br>";
}
attempts = 0;
}
if (config.ALLOW_HINTS_AT == attempts) {
if (config.ALERT_HINTS_MSGS) {
alert(config.HINTS_ALERT_MSG);
} else {
feedback += "<b>" + config.HINTS_ALERT_MSG + "</b><br>";
}
$("#hintButton").show();
}
// if this is the first time the answer is correct
//
if (!checkComplete() && analyzer.getCorrect(answer)) {
completedQuestions.push($.cookie("question"));
$.cookie("completedQuestions", JSON.stringify(completedQuestions), {
path : '/'
});
$("#scoreDiv").html(createScoreString());
unlockQuestion();
}
// create the jsmeApplet to display the answer in,
// first create the div string and a unique ID.
var jsme = createJsmeDiv();
// insert into the DOM
var response = "Your Answer <br>" + jsme.div + "FeedBack:<br>" + feedback + "<br>-----------------<br>";
var previousResponses = $("#response").html();
$("#response").html(response + previousResponses);
// create the applet inside the div and feed it the answer as a JME stirng.
var myApplet = new JSApplet.JSME(jsme.id, "250px", "120px", {
"options" : "depict"
});
myApplet.readMolecule(jsmeApplet.jmeFile());
/*
* $("#"+ jsme.id).css("margin-left", "300"); $("#"+ jsme.id).css("clear", "none");
*/
} catch (e) {
alert(e);
}
}
/**
* Creates the string for a div to be populated with a JSME applet.
*
* Returns an object with the div string and its unique id.
*
*/
function createJsmeDiv() {
var id = "JSME" + jsmeDivCount++;
var response = {
"div" : String,
"id" : String
};
response.div = '<div id="' + id + '"></div>';
response.id = id;
return response;
}
/**
* Uses the values in the cookies to create a string showing the average (as a %) over x number of questions.
*
* @returns {String}
*/
function createScoreString() {
var me = "Score: " + Math.round(analyzer.getScore()) + "% on " + $.cookie("numQuestionsTried") + " question";
var numcompletedQs = numberOfCompleteQuetions();
// create the string
if (numcompletedQs == 0) {
return "";
}
if (numcompletedQs > 1) {
me += "s";
}
return me;
}
/**
* unlocks the question so that the solution can be viewed and the question or category changed. Also allows duplicate answers to be
* submitted.
*
*/
function unlockQuestion() {
// $("#numberList").removeAttr("disabled");
$("#nextButton").removeAttr("disabled");
$("#prevButton").removeAttr("disabled");
$("#solutionButton").show();
$("#hintButton").show();
$.cookie("questionLock", "unlocked", {
path : '/'
});
previousAnwers = new Array();
}
/**
* Called from submitAnswer() to check if the answer was previously given.
*
* @param answer
* The answer that was submitted.
* @returns {Boolean} Can you guess what true means?
*/
function checkForDuplicates(answer) {
if (config.SPEC_UNLOCK_ALL_QUESTIONS) {
return false;
}
var regex = new RegExp(RegExp.escape(answer));
// alert(previousAnwers);
for (var int = 0; int < previousAnwers.length; int++) {
var pastAnswer = previousAnwers[int];
if (regex.test(pastAnswer) == true) {
if (pastAnswer == answer) {
return true;
}
}
}
previousAnwers.push(answer);
return false;
}
/**
* Alerts or displays a hint. Handler for the hint button.
* Behaviour is configured via config SPEC_UNLOCK_ALL_QUESTIONS and
* config ALERT_HINTS_MSGS
*/
function showHint() {
if (config.SPEC_UNLOCK_ALL_QUESTIONS) {
toggleHints = true;
}
if (config.ALERT_HINTS_MSGS) {
if (toggleHints) {
alert(analyzer.getHint());
} else {
alert(config.TRY_FIRST);
}
} else {
if (toggleHints) {
$("#response").html("<b>" + analyzer.getHint() + "</b><br>" + $("#response").html());
} else {
$("#response").html("<b>" + config.TRY_FIRST + "</b><br>" + $("#response").html());
}
}
toggleHints = false; // show one hint at a time.
}
/**
*
* @returns {Number} number of complete questions in the category since the category was selected.
*/
function numberOfCompleteQuetions() {
var completedQs = JSON.parse($.cookie("completedQuestions"));
return completedQs.length;
}
/**
* checks to see if the question number in the cookie 'queston' has been completed.
*
* @returns {Boolean} true if the question is complete, false otherwise.
*/
function checkComplete() {
var completedQs = JSON.parse($.cookie("completedQuestions"));
var questionNum = $.cookie("question");
for (var int = 0; int < completedQs.length; int++) {
var compl = completedQs[int];
if (questionNum == compl) {
return true;
}
}
return false;
}
/**
* Displays a popup window or new tab
*
* @param {String}
* what what is the rleative URL to display
* @param {Boolean}
* popup true and the window will be a popup false and it will be in a new tab.
*/
function display(what, popup) {
try {
var newWindow = null;
var specString = config.getBaseUrl() + analyzer.fclass + config.STN + config.QDIR_PREFIX
+ $.cookie("question") + "/" + what;
if (popup == true || popup == null) {
newWindow = window.open(specString, "Pup", "toolbar=no,menubar=no,status=yes,scrollbars=yes,width=750,height=460");
newWindow.focus();
} else {
newWindow = window.open(specString, "Ptab");
newWindow.focus();
}
// alert(newWindow.document.title);
} catch (e) {
alert(e);
}
}
/**
* Uses Ajax (in a synchronous mode) to retrieve /json/spectroscopy.json and uses it determine the number
* of questions for spectroscopy.
*
* If you add a question you must update /json/spectroscopy.json accordingly
*
* @returns the number of questions
*/
function getNumberOfQuestions() {
var maxQuestions = 0;
// make it synch
$.ajaxSetup({
async : false
});
try {
var url = config.getBaseUrl() + config.SPEC_CONF;
$.get(url, function(data, status) {
maxQuestions = new Number(JSON.parse(JSON.stringify(data))).valueOf();
// alert(maxQuestions);
});
} catch (e) {
alert("Fatal Error: Unable to load json" + e);
throw e;
}
return maxQuestions;
}
/**
*
* Used by initializeSpectroscopy to load javascript files it depends on. JSME and JQuery must already
* be loaded (from the html page).
*
* @param src
*/
function loadJS(src) {
var jsLink = $("<script type='text/javascript' src='"+src+"'>");
$("head").append(jsLink);
}
/**
* Used by initializeSpectroscopy to load the CSS file.
*
* @param href
*/
function loadCSS(href) {
var cssLink = $("<link rel='stylesheet' type='text/css' href='"+href+"'>");
$("head").append(cssLink);
}
}
/**
* IMPORTANT: this is the function JSME calls when it initializes itself. This function and the document.ready function don't play
* nice together.
*
* This function should not be removed or called from the document ready function. It can be modified if needed. This function and
* how it works is why we need to do Synchronous calls to the server for question files.
*
*/
function jsmeOnLoad() {
try {
jsmeApplet = new JSApplet.JSME("appletContainer", "323px", "350px", {
"options" : "autoez,removehs"
});
// jsmeApplet has the same API as the original Java applet
document.JME = jsmeApplet;
jsmeApplet.showInfo(config.SPEC_TITLE + ": " + config.SPEC_QNAME + $.cookie("question"));
adjustCSS();
} catch (e) {
alert("error in jsmeOnLoad: " + e);
}
}
/**
* Adjusts the css on the page for the middle and right column. is called from jsmeOnload so the height includes the jsme App.
*
*/
function adjustCSS() {
var colHeight = $("#leftColumn").height();
$("#centerColumn").height(colHeight);
$("#rightColumn").height(colHeight);
var responseHeight = colHeight - $("#prevButton").height() - 60; // for padding
// response div
$("#response").height(responseHeight);
};