moodle/mod/hotpot/hotpot-full.js

1980 lines
63 KiB
JavaScript

<!--
// PLEASE NOTE that this version is more recent than the incorrectly
// numbered v6.1, dated 2003.11.17. From now on, version numbers will
// follow those of Hot Potatoes.
/* hot-potatoes.js (v6.0.4.0 - 2005.02.18)
* =======================================
* by Gordon Bateson, February 2003
* Copyright (c) 2003 Gordon Bateson. All Rights Reserved.
*
* You are hereby granted a royalty free license to use or modify this
* software provided that this copyright notice appears on all copies.
*
* This software is provided "AS IS" without a warranty of any kind.
*
* Documentation and downloads may be available from:
* http://www.kanazawa-gu.ac.jp/~gordon/research/hot-potatoes/
*/
// This JavaScript library modifies the SendResults and StartUp functions
// used by hotpot v5 and v6, so that more (or less!) details about the
// student can be input, and more details of a quiz's questions and answers
// can be submitted to the server when the quiz is finished
// If the arrays below (Login, DB, JBC, ...) are set BEFORE calling this
// script, they will NOT be overwritten. Any array that is not set, will
// use the defaults below. This is useful if you want to use different
// settings for different quizzes.
// ************
// Login Screen
// ************
if (window.Login==null) {
Login = new Array();
Login[0] = true; // Show prompt for user name
// This can also be a string of user names ...
// Login[0] = "Guest,Peter,Paul,Mary,Webmaster";
// or an array of user names (and on-screen texts) (and passwords) ...
// Login[0] = new Array("Guest", "001,Peter,xxxx", "002,Paul,yyyy", "003,Mary,zzzz", "Webmaster");
// and can also be written as ...
// Login[0] = new Array(
// new Array("Guest"),
// new Array("001", "Peter", "xxxx"),
// new Array("002", "Paul", "yyyy"),
// new Array("003", "Mary", "zzzz"),
// new Array("Webmaster")
// );
Login[1] = true; // Show prompt for student's UserID
// If there is no password prompt (i.e. Logon[3] is false), this value
// will be checked against the password information, if any, in Login[0]
Login[2] = false; // Show prompt for student's email
Login[3] = false; // Show prompt for quiz password, and check this value against
// the password information, if any, in Login[0]
// This can also be a string required to start the quiz ...
// Login[3] = "password";
Login[4] = true; // Show prompt for the cookie expiry date
// If false, cookies expire at the end of the current session
Login[5] = "guest,webmaster"
// guest user names (case insensitive) ...
// Login[5] = "guest,webmaster";
// These users do NOT need to fill in other login fields
// and their quiz results are NOT added to the database
// the Login prompts and error messages
// are defined in the MSG array (see below)
}
// *********
// Database (for use with BFormMail)
// *********
if (window.DB==null) {
DB = new Array();
DB[0] = true; // append form fields to database on server
// If you are NOT using BFormMail's database feature,
// set DB[0]=false, and you can then safely ignore DB[1 to 5]
DB[1] = "/home/gordon/public_html/cgi/hot-potatoes-data";
// append_db folder path (no trailing slash)
// Can be either an absolute path e.g. "/home/gordon/public_html/cgi/hot-potatoes-data"
// or a relative (to CGI bin) path e.g. "hot-potatoes-data"
DB[2] = "hot-potatoes";
// append_db file name (no extension)
// If left blank, the quiz file name, without extension, will be used
// i.e. each quiz will have its results stored in a different file.
// If filled in, this file will store the results for ALL quizzes.
// Database files and folders must be set up BEFORE running the quiz
// must have appropriate access privileges (on Unix, use "chmod 666").
DB[3] = ""; // append_db extension (if left blank, ".txt" will be used)
DB[4] = ""; // db_fields (if left blank, ALL quiz fields will be sent)
DB[5] = ""; // db_delimiter (if left blank, tab will be used)
DB[6] = "REMOTE_ADDR,HTTP_USER_AGENT";
// env_report ('REMOTE_ADDR','HTTP_USER_AGENT' and a few others)
// for a complete description of these fields are, see ...
// http://www.infosheet.com/stuff/BFormMail.readme
// Switches DB[7] and DB[8] force the settings in the ResultForm
// In v5 and v6 quizzes, these settings wil be override those in the original quiz
// If the quiz results are to be sent to an LMS (via the "store" form)
// then switches DB[7] and DB[8] are not used
DB[7] = ''; // URL of form processing script
// e.g. http://www.kanazawa-gu.ac.jp/~gordon/cgi/bformmail.cgi
DB[8] = ''; // email address to which results should be sent
// e.g. gordon@kanazawa-gu.ac.jp
}
// By default the quiz's question's scores will be returned.
// If you want more detailed information, set the flags below:
// ********
// JBC
// ********
if (window.JBC==null) {
JBC = new Array();
JBC[0] = true; // show separator line between answers on email
JBC[1] = true; // show number of attempts to answer question
JBC[2] = true; // show question texts
JBC[3] = true; // show right answer(s)
JBC[4] = true; // show wrong answer(s)
JBC[5] = true; // show ignored answer(s)
JBC[6] = false; // show answer as text (false) or number (true)
}
// JBC quizzes use the global variables 'I' and 'Status'
// I : an array of JBC_QUESTIONs (one for each question)
// JBC_QUESTION :
// [0] : question text
// [1] : array of JBC_ANSWERs (one for each answer)
// [2] : single/multi flag
// 0 : single answer (using 'button')
// 1 : multiple answers (using 'checkbox')
// JBC_ANSWER :
// [0] : answer text
// [1] : answer feedback
// [2] : correct answer flag
// 0 : this is NOT the correct answer
// 1 : this is the correct answer
// Status : an array of JBC_QUESTION_STATUSes
// JBC_QUESTION_STATUS:
// [0] : correctly answered yet flag
// 0 : this question has NOT been correctly answered
// 1 : this question has been correctly answered
// [1] : array of JBC_ANSWER_STATUSes (one for each answer)
// '0' : initial value
// 'R' : single answer question was answered 'R'ight
// 'W' : single answer question was answered 'W'rong
// 'C' : multiple answer question's checkbox was 'C'hecked
// 'U' : multiple answer question's checkbox was 'U'nchecked
// [2] : number of times this question has been wrongly answered
// [3] : score (out of 1) for this question (maybe undefined on HP<5.5)
// 0 : not correct yet
// 0<[3]<1 : correct but only after [2] wrong attempts
// 1 : correct first time (bravo!)
// N.B. score = (numberOfAnswers - numberofWrongTries) / numberOfAnswers
// ********
// JCloze
// ********
if (window.JCloze==null) {
JCloze = new Array();
JCloze[0] = true; // show separator line between answers on email
JCloze[1] = true; // show student's correct answer
JCloze[2] = true; // show other correct answer(s), if any
JCloze[3] = true; // show wrong answer(s), if any (NOT available for v5)
JCloze[4] = true; // show number of hints or penalties
JCloze[5] = true; // show if clue was asked for or not
JCloze[6] = true; // show clue
}
// JCloze quizzes use the global variables 'I' and 'State'
// I : array of JCLOZE_ANSWERs
// JCLOZE_ANSWER :
// [0] : (unused)
// [1] : array of JCLOZE_ANSWER_TEXTs
// [2] : clue for this answer
// JCLOZE_ANSWER_TEXT :
// [0] : array (seems unnecessary, just the text would be enough?)
// [0] : text of possible answer
// State : array of JCLOZE_ANSWER_STATEs
// JCLOZE_ANSWER_STATE (v5) :
// [0] : clue asked for or not
// [1] : number of hints (show next letter) and penalties ('check' an incorrect answer)
// [2] : length of answer matched
// [3] : score for this item
// [4] : already answered correctly
// [5] : answer entered in text box (right or not)
// JCLOZE_ANSWER_STATE (v6)
// this.ClueGiven = false;
// this.HintsAndChecks = 0;
// this.MatchedAnswerLength = 0;
// this.ItemScore = 0;
// this.AnsweredCorrectly = false;
// this.Guesses = new Array(); last guess is correct answer
// ********
// JCross
// ********
if (window.JCross==null) {
JCross = new Array();
JCross[0] = true; // show separator line between answers on email
JCross[1] = true; // show number of penalties (hints or checks before complete)
JCross[2] = true; // show number of letters
JCross[3] = true; // show answers
JCross[4] = true; // show clues
}
// JCross quizzes use the following global variables:
// L : letters (of correct answers)
// C : clue numbers (CL in v6)
// G : guesses
// 'L', 'C' ('CL') and 'G' are all 2-dimensional arrays (rows x cols)
//
// v5 quizzes additionally use the following single-dimension arrays
// A : clues for across (horizontal) words
// D : clues for down (vertical) words
// N.B. form is only sent when all answers are correct so
// you can't find out what 'wrong' answers were entered
// ********
// JMatch
// ********
if (window.JMatch==null) {
JMatch = new Array();
JMatch[0] = true; // show separator line between answers on email
JMatch[1] = true; // show number of attempts for each match
JMatch[2] = true; // show LHS texts
JMatch[3] = true; // show RHS texts
}
// v5 JMatch quizzes use the global variables 'I' and 'Status' (and 'RItems')
// v6 JMatch quizzes use only 'Status'
// v6+ JMatch quizzes use 'F' and 'D' (see below)
// I : an array of JMATCH_PAIRs (one for each pair)
// JMATCH_PAIR :
// [0] : LHS text
// [1] : RHS text
// [2] : fixed (=not jumbled) flag
// 0 : not fixed
// 1 : fixed
// [3] : index in drop down list selection
// Status : an array of JMATCH_PAIR_STATUSes
// JMATCH_PAIR_STATUS:
// [0] : correctly matched yet flag
// 0 : this pair has NOT been correctly matched
// 1 : this pair has been correctly matched
// [1] : number of times this item has been wrongly matched
// v6 quizzes only
// [2] : id of original SELECT element containing possible matches
// Note that after matching, this SELECT is removed, so don't try looking for it :-)
// v6+ JMatch quizzes use the global variables 'F' and 'D'
// F : array of JMATCH_FIXED_ITEMs
// JMATCH_FIXED_ITEM:
// [0] : text
// [1] : tag
// D : array of JMATCH_DRAGGABLE_ITEMs
// JMATCH_DRAGGABLE_ITEM
// [0] : text
// [1] : tag of the F item to which it SHOULD be dragged
// [2] : tag of the F item to which it was dragged (initally 0)
// N.B. form is only sent when all answers are correct so
// you can't find out what 'wrong' answers were entered
// ********
// JMix
// ********
if (window.JMix==null) {
JMix = new Array();
JMix[0] = true; // show separator line between answers on email
JMix[1] = true; // show number of wrong guesses
JMix[2] = true; // show right answer
JMix[3] = true; // show wrong answer, if any
JMix[4] = false; // show answer as text (false) or number (true)
}
// JMix quizzes use the global variables
// 'Segments', 'GuessSequence' and 'Penalties'
// Segments : array of JMix_QUESTIONs
// JMix_QUESTION:
// [0] : text
// [1] : order in sequence
// [2] : used flag
// GuessSequence : array of 'order in sequence' numbers
// Penalties : number of incorrect guesses
// ********
// JQuiz
// ********
if (window.JQuiz==null) {
JQuiz = new Array();
JQuiz[0] = true; // show separator line between answers on email
JQuiz[1] = true; // show question text
JQuiz[2] = true; // show student's correct answer(s)
JQuiz[3] = true; // show wrong and ignored answer(s)
JQuiz[4] = true; // show number of hints requested
JQuiz[5] = true; // show number of checks of incorrect answers
// HP6 v6 quizzes only
JQuiz[6] = false; // show answer value (false) or A,B,C... index (true)
JQuiz[7] = true; // show all students answers
JQuiz[8] = true; // show student's wrong answers
JQuiz[9] = true; // show ignored answers (not relevant for multi-select questions)
JQuiz[10] = true; // show score weightings
JQuiz[11] = true; // show question type
}
// v5 JQuiz quizzes use the global variables 'I' and 'Status'
// I : array of JQUIZ_ANSWERs
// JQUIZ_ANSWER :
// [0] : question text
// [1] : array of JQUIZ_ANSWER_TEXTs (one for each answer)
// JQUIZ_ANSWER_TEXT :
// [0] : array (seems unnecessary, just the text would be enough?)
// [0] : text of possible answer
// Status : array of JQUIZ_ANSWER_STATEs
// JQUIZ_ANSWER_STATE :
// [0] : question done or not
// [1] : number of wrong checks
// [2] : number of hints asked for
// [3] : student's answer
// [4] : score for this question
// v6 JQuiz quizzes use the global variables 'I' and 'State'
// I : array of JQUIZ_QUESTIONs
// JQUIZ_QUESTION :
// [0] : weighting
// [1] : ?? (always set to '')
// [2] : question type
// '0'=multiple-choice, '1'=short-answer, '2'=hybrid, '3'=multi-select
// [3] : array of JQUIZ_ANSWERSs (one for each possible answer)
// JQUIZ_ANSWER :
// [0] : answer value
// [1] : feedback text
// [2] : correct answer flag (1=a correct answer, 0=a wrong answer)
// [3] : weighted score (as percentage) if correct
// [4] : flag (usually set to 1, but for hybrid answers that are not
// to be included in multiple choice options, it is set to 0)
// State : array of JQUIZ_QUESTION_STATEs
// JQUIZ_QUESTION_STATE :
// [0] : score (-1 shows not done yet)
// [1] : array showing on which number try each JQUIZ_ANSWER was selected
// [2] : number of attempts at this question
// [3] : total of weighted scores of correct answers that were selected
// i.e. each time a correct answer is selected,
// its JQUIZ_ANSWER[3] weighting is added to this total
// so when all the correct answers have been selected, this will be 100
// [4] : penalties incurred for hints (score is set to zero if >= 1)
// [5] : - for multiple choice, short-answer and hybrid questions, this is a
// comma-delimited list showing order in which answers were chosen
// - for multi-select fields, this is bar-delimted ('|') list of settings
// showing whether each checkbox was selected ('Y') on not ('N') when the
// 'Check' button was clicked. The final item in the list will be the
// settings for the correct answer.
// N.B. JBC, JMatch(v5) and JQuiz(v5) all use global variables 'I' and 'Status'
// JBC : I[0].length==3 && !window.RItems
// JQuiz(v5) : I[0].length==2
// JMatch(v5) : I[0].length==4 && window.RItems
// N.B. JCloze(v5+6) and JQuiz(v6) both use global variables 'I' and 'State'
// JCloze (v5) : I[0].length==3 && State[0].Guesses==null
// JCloze (v6) : I[0].length==3 && State[0].Guesses!=null
// JQuiz (v6) : I[0].length==4
// **********
// Rhubarb
// **********
if (window.Rhubarb==null) {
Rhubarb = new Array();
Rhubarb[0] = true; // show correct words
Rhubarb[1] = true; // show incorrect words
}
// **********
// Sequitur
// **********
if (window.Sequitur==null) {
Sequitur = new Array();
}
// **********
// Messages
// **********
if (window.MSG==null) {
MSG = new Array();
// Login prompts
MSG[0] = 'Name';
MSG[1] = 'ID';
MSG[2] = 'Email';
MSG[3] = 'Password';
MSG[4] = 'Cookies';
// Login buttons
MSG[5] = 'Start the Quiz';
MSG[6] = 'Cancel';
// Cookie menu options (only used if Login[4] is true)
MSG[7] = 'keep for this session only';
MSG[8] = 'keep for one day';
MSG[9] = 'keep for one month';
MSG[10] = 'do NOT keep cookies';
// Login error messages
MSG[11] = 'Sorry, you were unable to login. Please try again later.';
MSG[12] = 'Please fill in all the information.';
MSG[13] = 'Incorrect Password. Please try again.';
MSG[14] = 'Incorrect ID. Please try again.';
MSG[15] = 'Email address does not appear to be valid.';
// day and month names (used in Start_Time and End_Time)
MSG[16] = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
MSG[17] = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
// enable popups
MSG[18] = 'Please enable pop-up windows on your browser.';
// browser specific instuctions on how to enable popup windows
var n = navigator;
var s = n.userAgent.toLowerCase();
if (n.appName=='Netscape' && s.indexOf('gecko')>=0) {
// Netscape 6 and 7
MSG[18] += '\n\n' + 'Edit->Preferences, ' + (s.indexOf('mac')>=0 ? 'Advanced->Scripts & Plugins' : 'Privacy & Security->Popup Window Controls');
} else if (s.indexOf('safari')>=0) {
// Safari
MSG[18] += '\n\n' + 'on Safari menu, uncheck "Block Pop-Up Windows"';
} else if (s.indexOf('firebird')>=0) {
// Firebird
MSG[18] += '\n\n' + 'Preferences->Web Features, uncheck "Block Pop-Up Windows"';
} else if (s.indexOf('msie 6')>=0) {
// IE 6 (WinXP.SP2)
MSG[18] += '\n\n' + 'Tools->Pop-up Blocker->Turn Off Pop-up Blocker';
}
}
// *************
// Server Fields
// *************
if (window.ServerFields==null) {
ServerFields = new Array();
// these fields will be added to the ResultForm and submitted to the CGI script on the server.
// 'Sort', 'return_link_title', 'return_link_url' and 'print_blank_fields' are useful for formmail
// override the HP setting of sort fields (forces ALL fields to be displayed)
ServerFields[0] = new Array('sort', '');
// add link to close pop-up results window
ServerFields[1] = new Array('return_link_title', 'Close this window');
ServerFields[2] = new Array('return_link_url', 'javascript:self.close()');
// make sure zero values are printed
ServerFields[3] = new Array('print_blank_fields', 'yes');
// you can also set other fields for your customized CGI script
// e.g. adding a server defined start time (instead of a client defined start time)
// ServerFields[4] = new Array('serverStartTime', '<?php echo date("Y-m-d H:i:s") ?>');
}
// *********************
// Login screen
// (not required by LMS)
// *********************
function QuizLogin(LoginPrompt) {
if ((Login[0] || Login[1] || Login[2] || Login[3]) && !is_LMS()) {
var html = ''
+ '<HTML>'
+ '<HEAD></HEAD>'
+ '<BODY bgColor="#cccccc" onLoad="opener.setFocus(self)">'
+ '<FORM onSubmit="'
+ 'self.ok=true;'
+ 'self.expiry=null;'
;
if (Login[4]) { // cookie expiry
html += "opener.checkOK(self,'CookieExpiry');";
}
if (Login[0]) { // user name
html += "opener.checkOK(self,'UserName');";
}
if (Login[1]) { // user ID
html += "opener.checkOK(self,'UserID');";
}
if (Login[2]) { // user email
html += "opener.checkOK(self,'UserEmail');";
}
if (Login[3]) { // quiz password
html += "opener.checkOK(self,'Password');";
}
html += 'if(ok){'
+ 'opener.StartQuiz();'
+ 'self.close();'
+ '}else{'
+ 'if(isNaN(self.tries))self.tries=0;'
+ 'self.tries++;'
+ 'if(self.tries<3){'
+ 'opener.setFocus(self);'
+ '}else{'
+ "alert(opener.MSG[11]);"
+ 'opener.goBack();'
+ 'self.close();'
+ '}'
+ '}'
+ 'return false;'
+ '">'
;
html += '<TABLE>'
+ '<CAPTION>' + LoginPrompt + '</CAPTION>';
;
if (Login[0]) { // user name
var v = getCookie(self, 'UserName');
html += '<TR>'
+ '<TH align=right nowrap>' + MSG[0] + ' :</TH>'
+ '<TD>'
;
if (typeof(Login[0])=='boolean') { // text box
html += '<INPUT type=text name=UserName value="' + v + '">';
} else { // drop down menu of names
// pattern to match commas and white space
var comma = (window.RegExp) ? new RegExp('\\s*,\\s*') : ',';
// convert list of names to array, if necessary
if (typeof(Login[0])=='string') {
Login[0] = Login[0].split(comma);
}
html += '<SELECT name=UserName size=1>'
+ '<OPTION value=""></OPTION>'
;
for(var i=0; i<Login[0].length; i++) {
// convert name details to array if necesssary
if (typeof(Login[0][i])=='string') {
Login[0][i] = Login[0][i].split(comma);
}
html += makeOption(Login[0][i][0], v, Login[0][i][1]);
}
html += '</SELECT>';
}
html += '</TD>'
+ '</TR>'
;
}
if (Login[1]) { // user ID
var v = getCookie(self, 'UserID');
html += '<TR><TH align=right nowrap>' + MSG[1] + ' :</TH><TD><INPUT type=text name=UserID value="' + v + '"></TD></TR>';
}
if (Login[2]) { // user email
var v = getCookie(self, 'UserEmail');
html += '<TR><TH align=right nowrap>' + MSG[2] +' :</TH><TD><INPUT type=text name=UserEmail value="' + v + '"></TD></TR>';
}
if (Login[3]) { // quiz password
var v = getCookie(self, 'Password');
html += '<TR><TH align=right nowrap>' + MSG[3] + ' :</TH><TD><INPUT type=password name=Password value="' + v + '"></TD></TR>';
}
if (Login[4]) { // cookie lifespan
var v = getCookie(self, 'CookieExpiry');
html += '<TR>'
+ '<TH align=right nowrap>' + MSG[4] + ' :</TH>'
+ '<TD>'
+ '<SELECT name="CookieExpiry" size=1>'
+ makeOption('session', v, MSG[7])
+ makeOption('day', v, MSG[8])
+ makeOption('month', v, MSG[9])
+ makeOption('never', v, MSG[10])
+ '</SELECT>'
+ '</TD>'
+ '</TR>'
;
}
html += '<TR>'
+ '<TH>&nbsp;</TH>'
+ '<TD nowrap>'
+ '<INPUT type=submit value="' + MSG[5] + '"> '
+ '<INPUT type=button value="' + MSG[6] + '" onClick="opener.goBack();self.close();">'
+ '</TD>'
+ '</TR>'
+ '</TABLE></FORM></BODY></HTML>'
;
// set height of Login Window
var m = navigator.userAgent.indexOf('Mac')>=0;
var h = (m ? 80 : 100);
for (var i=0; i<5; i++) h += (Login[i] ? (m ? 20 : 25) : 0);
// open up a new window
if (!openWindow('', '', (m ? 320 : 300), h, 'RESIZABLE', html)) {
alert(MSG[18]); // unable to open popup window
}
} else { // no Login required
window.UserName = window.UserID = window.UserEmail = window.Password = '';
window.StartQuiz();
}
return true;
}
function makeOption(value, v, txt) {
return '<OPTION value="' + value + '"' + (value==v ? ' SELECTED' : '') + '>' + (txt ? txt : value) + '</OPTION>';
}
function setFocus(w) {
w.focus(); // bring window to the front
var obj = w.document.forms[0].elements;
for(var i=0; i<obj.length; i++) {
var v = getValue(w, i);
if (v=='' || obj[i].type=='submit') {
obj[i].focus();
break;
}
}
}
function checkOK(w, n){
var v = getValue(w, n, true);
if (v || (n!='UserName' && isGuest())) {
if (n=='CookieExpiry') setCookieExpiry(w, v);
setCookie(self, n, v, w.expiry);
if (n!='CookieExpiry') eval('self.' + n + '=v');
} else {
if (w.ok) alert(MSG[12]);
w.ok = false;
}
}
function getValue(w, n, flag) {
var obj = w.document.forms[0].elements[n];
var TYPE = obj.type.toUpperCase(); // required for ns4 (win)
if (obj.options && TYPE.indexOf('SELECT')>=0){
var v = obj.options[obj.selectedIndex].value;
} else {
var v = obj.value;
}
if (flag) {
var msg = '';
if (n=='Password' || (n=='UserID' && !Login[3])) {
var pwd = getPassword(w);
if (pwd && v!=pwd) msg = MSG[n=='Password' ? 13 : 14];
}
if (n=='Email' && window.RegExp) {
var r = '(\\w|-)+';
r = r + '(\\.' + r + ')';
r = new RegExp('^(' + r + '*)@(' + r + '+)$');
if (v.match(r)==null) msg = MSG[15];
}
if (msg) {
obj.value = v = '';
if (w.ok) alert(msg);
w.ok = false;
}
}
return v;
}
function getPassword(w) {
var pwd = '';
if (Login[3] && typeof(Login[3])=='string') {
pwd = Login[3];
} else if ((Login[3] || Login[1]) && typeof(Login[0])=='object') {
var username = getValue(w, 'UserName');
for(var i=0; i<Login[0].length; i++) {
if (username==Login[0][i][0]) {
pwd = Login[0][i][2];
break;
}
}
}
return pwd;
}
function setCookieExpiry(w, v) {
if (v=='never'){
w.expiry = new Date('Thu, 01-Jan-70 00:00:01 GMT');
} else if (v=='day' || v=='month') {
var ms = (v=='month' ? 31 : 1) * 60 * 60 * 24 * 1000;
w.expiry = new Date((new Date()).getTime() + ms);
}
}
function setCookie(w, name, value, expires, path, domain, secure) {
if (name) w.document.cookie = ''
+ 'HP_' + name + "=" + escape(value)
+ (expires ? "; expires=" + expires.toGMTString() : "")
+ (path ? "; path=" + path : "")
+ (domain ? "; domain=" + domain : "")
+ (secure ? "; secure" : "")
;
}
function getCookie(w, n) {
var c = w.document.cookie;
var i = c.indexOf('HP_' + n + '=');
var j = (i<0) ? -1 : c.indexOf(';', (i += n.length + 4));
return (i<0) ? '' : unescape(c.substring(i, ((j<0) ? c.length : j)));
}
function goBack(w) {
if (w==null) w = self; // default
if (w.history.length) w.history.back();
}
function openWindow(url, name, width, height, attributes, html) {
if (window.screen && width && height) {
var W = screen.availWidth;
var H = screen.availHeight;
width = Math.min(width, W);
height = Math.min(height, H);
attributes = ''
+ (attributes ? (attributes+',') : '')
+ 'WIDTH='+width+',HEIGHT='+height
;
}
// workaround for "Access is denied" errors in IE when offline
// based on an idea seen at http://www.devshed.com/Client_Side/JavaScript/Mini_FAQ
var ie_offline = (document.all && location.protocol=='file:');
// open the window
var w = window.open((ie_offline ? '' : url), name, attributes);
// check window opened OK (user may have prevented popups)
if (w) {
// center the window
if (window.screen && width && height) {
w.moveTo((W-width)/2, (H-height)/2);
}
// add content, if required
if (html) {
with (w.document) {
clear();
open();
write(html);
close();
}
} else {
if (ie_offline && url) w.location = url;
}
}
return w;
}
// *********************
// Send results by email
// (not required by LMS)
// *********************
function SendAllResults(Score) {
// check this quiz is not generated by a LMS
if (!is_LMS()) {
// add flat file database details to the results form
AddDatabaseDetailsToResultForm();
// add student details to the results form
AddStudentDetailsToResultForm();
// add question details to the results form
AddQuestionDetailsToResultForm();
// add server fields, if any, to results form
AddServerFieldsToResultForm();
// change "method" of form, because "get" only allows 512 byts of data
ResultForm = replaceLast('method="get"', 'method="post"', ResultForm);
// create results window and form
var w = openWindow('', '', 500, 400, 'RESIZABLE,SCROLLBARS,HOTPOT_LOCATION', ResultForm);
// check window opened OK (user may have prevented popups)
if (w) {
// get shortcut to form object
var form = w.document.forms[0];
// update some important field values
form.Score.value = Score + '%';
form.realname.value = UserName;
form.Start_Time.value = getTime(Start_Time);
form.End_Time.value = getTime();
// force email subject and Exercise title
form.subject.value = document.title;
form. Exercise.value = document.title;
// update DB fields, if required
if (DB[0] && !isGuest()) set_db_fields(form);
if (DB[7]) form.action = DB[7];
if (DB[8]) form.recipient.value = DB[8];
// if this is a Netscape browser, check if the referer will be set OK
if (navigator.appName=='Netscape' && (location.protocol=='file:' || navigator.userAgent.indexOf('Netscape6')>=0)) {
// ns4 and ns7 set referer to 'file:// ...' when running a quiz offline
// ns6.2 (at least) always sets referer to 'about:blank'
// Netscape's setting of referer can cause BFormMail
// to reject the form, so encode the form data as a URL
var url = form.action;
var obj = form.elements;
for (var i=0; i<obj.length; i++) {
var v = escape(obj[i].value);
v = v.replace((new RegExp('\\+', 'g')), '%2B');
url += (i==0 ? '?' : '&') + obj[i].name + '=' + v;
}
w.location.href = url;
} else { // browser can POST form ok
form.submit();
}
} else { // unable to open popup window
alert(MSG[18]);
}
} // end if LMS
}
function isGuest() {
// check username is not a "guest" user
var flag = false;
var n = getCookie(self, 'UserName').toLowerCase();
if (n) {
// convert list of user names to array, if necessary
if(typeof(Login[5])=='string') {
Login[5] = Login[5].split(',');
}
for(var i=0; i<Login[5].length; i++) {
if (n==Login[5][i].toLowerCase()) {
flag = true;
break;
}
}
}
return flag;
}
function set_db_fields(form) {
// update list of DB fields, if required
if (DB[4]=='' && window.RegExp) {
// add administration fields
var db_fields = ''
+ 'subject,realname'
+ (Login[1] ? ',ID' : '')
+ (Login[2] ? ',email' : '')
+ (Login[3] ? ',password' : '')
+ ',Score,Start_Time,End_Time'
;
// add answer fields (except separators)
var r = new RegExp('^[^_]+_q\\d\\d_\\w+$');
for(var i=0; i<form.elements.length; i++) {
var n = form.elements[i].name;
if (r.test(n)) db_fields += ',' + n;
}
form.db_fields.value = db_fields;
}
// make sure delimiter is set (NS6+ requires this be set here, not any earlier)
form.db_delimiter.value = (DB[5] ? DB[5] : '\t');
}
function AddStudentDetailsToResultForm() {
var sDetails = '';
if (Login[0]) { // user name
// use 'realname' instead of a separate 'Name' field
// sDetails += makeHiddenField('Name', window.UserName);
}
if (Login[1]) { // user ID
sDetails += makeHiddenField('ID', window.UserID);
}
if (Login[2]) { // user email
sDetails += makeHiddenField('email', window.UserEmail);
}
if (sDetails && window.RegExp) {
// insert sDetails before '<input...Score...></input>'
var r = new RegExp('<input[^>]*Score[^>]*><\\/input>', 'i');
var m = r.exec(ResultForm);
if (m) {
ResultForm = ResultForm.replace(m[0], sDetails + m[0] + makeSeparator('Time_'));
sDetails = '';
}
}
if (Login[3]) { // quiz password
sDetails += makeHiddenField('Password', window.Password);
ResultForm = replaceLast('</form>', sDetails + '</form>', ResultForm);
}
}
function AddQuestionDetailsToResultForm() {
var qDetails = GetQuestionDetails();
if (qDetails) {
// insert qDetails before the final </form> tag in the ResultForm
ResultForm = replaceLast('</form>', qDetails + '</form>', ResultForm);
}
}
function AddDatabaseDetailsToResultForm() {
if (window.DB && DB[0] && !isGuest()) {
var dbDetails = '';
var folder = DB[1];
if (folder && folder.charAt(folder.length-1)!='/') folder += '/';
var file = DB[2];
if (file=='') {
file = location.href;
file= file.substring(file.lastIndexOf('/')+1);
var i = file.indexOf('?');
if (i >= 0) file = file.substring(0, i);
var i = file.lastIndexOf('.');
if (i >= 0) file = file.substring(0, i);
}
var ext = (DB[3] ? DB[3] : 'txt');
if (ext.charAt(0)!='.') ext = '.' + ext;
dbDetails += makeHiddenField('append_db', folder + file + ext);
dbDetails += makeHiddenField('db_fields', DB[4]);
dbDetails += makeHiddenField('db_delimiter', ''); // NS6+ requires this be set later
if (DB[6]) dbDetails += makeHiddenField('env_report', DB[6]);
// insert dbDetails before the final </form> tag in the ResultForm
ResultForm = replaceLast('</form>', dbDetails + '</form>', ResultForm);
}
}
function AddServerFieldsToResultForm() {
if (window.ServerFields) {
var s = ''; // input tags for s(erver fields)
for (var i=0; i<ServerFields.length; i++) {
if (ServerFields[i][0] && window.RegExp) {
// remove previous field value, if any
var r = new RegExp('<input[^>]*name\\s*=\\s*["\']\\s*' + ServerFields[i][0] + '[^>]*>(\\s*<\\/input>)?', 'i');
if (r.test(ResultForm)) {
ResultForm = ResultForm.replace(r, '');
}
}
if (ServerFields[i][1]) {
s += makeHiddenField(ServerFields[i][0], ServerFields[i][1]);
}
} // end for
if (s) ResultForm = replaceLast('</form>', s + '</form>', ResultForm);
}
}
function replaceLast(a, b, c) {
// replace last occurrence of 'a' in 'c' with 'b'
var l = a.length;
var i = c.lastIndexOf(a);
return (i<0 || l==0) ? c : (c.substring(0, i) + b + c.substring(i+l));
}
// *************************
// Extract question details
// *************************
function GetQuestionDetails() {
var t = get_quiz_type();
var v = get_quiz_version();
return (t==1) ? GetJbcQuestionDetails(v) :
(t==2) ? GetJClozeQuestionDetails(v) :
(t==3) ? GetJCrossQuestionDetails(v) :
(t==4) ? GetJMatchQuestionDetails(v) :
(t==5) ? GetJMixQuestionDetails(v) :
(t==6) ? GetJQuizQuestionDetails(v) :
(t==7) ? GetRhubarbDetails(v) :
(t==8) ? GetSequiturDetails(v) : '';
}
function GetJbcQuestionDetails(v) {
qDetails = '';
// check the quiz version
if (v==5 || v==6) {
// get question details
for(var q=0; q<I.length; q++) {
// initialize strings to hold answer details
var aDetails = new Array();
aDetails[0] = new Array(); // right
aDetails[1] = new Array(); // wrong
aDetails[2] = new Array(); // ignored
// get answer details
for(var a=0; a<I[q][1].length; a++) {
var i = (Status[q][1][a]=='R') ? 0 : (Status[q][1][a]=='0') ? 2 : 1;
aDetails[i][aDetails[i].length] = (JBC[6] ? a : I[q][1][a][0]);
}
// format 'Q' (a padded, two-digit version of 'q')
var Q = getQ('JBC', q);
// add separator, if required
if (JBC[0]) qDetails += makeSeparator(Q);
if (JBC[1]) { // number of attempts to answer question
qDetails += makeHiddenField(Q+'attempts', Status[q][2] + (Status[q][0]==1 ? 1 : 0));
}
if (JBC[2]) { // question text
qDetails += makeHiddenField(Q+'text', I[q][0]);
}
if (JBC[3] && (DB[0] || aDetails[0].length>0)) { // right
qDetails += makeHiddenField(Q+'right', aDetails[0]);
}
if (JBC[4] && (DB[0] || aDetails[1].length>0)) { // wrong
qDetails += makeHiddenField(Q+'wrong', aDetails[1]);
}
if (JBC[5] && (DB[0] || aDetails[2].length>0)) { // ignored
qDetails += makeHiddenField(Q+'ignored', aDetails[2]);
}
// calculate score for this question, if required (for HP version < 5.5)
if (isNaN(Status[q][3])) {
var a1 = Status[q][1].length; // answers
var a2 = Status[q][2]; // attempts
Status[q][3] = (a1<1 || a1<(a2-1)) ? 0 : ((a1 - (a2-1)) / a1);
}
// add 'score' for this question
qDetails += makeHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');
} // end for
}
return qDetails;
}
function GetJClozeQuestionDetails(v) {
var qDetails = '';
// check the quiz version
if (v==5 || v==6) {
var hp5 = (State[0].Guesses==null);
// get details for each question
for (var q=0; q<State.length; q++) {
// format 'Q' (a padded, two-digit version of 'q')
var Q = getQ('JCloze', q);
// add separator, if required
if (JCloze[0]) qDetails += makeSeparator(Q);
// score (as %)
var x = (hp5 ? State[q][3] : State[q].ItemScore);
qDetails += makeHiddenField(Q+'score', Math.floor(x*100)+'%');
// shortcut to students correct answer
var correct = (hp5 ? State[q][5] : State[q].Guesses[State[q].Guesses.length-1]);
if (JCloze[1]) { // student's correct answer
qDetails += makeHiddenField(Q+'correct', correct);
}
if (JCloze[2]) { // other correct answers
var ignored = new Array();
for (var i=0, ii=0; i<I[q][1].length; i++) {
if (I[q][1][i][0] && (I[q][1][i][0].toUpperCase() != correct.toUpperCase())) {
ignored[ii++] = I[q][1][i][0];
}
}
if (DB[0] || ignored.length>0) qDetails += makeHiddenField(Q+'ignored', ignored);
}
if (JCloze[3] && State[q].Guesses) {
var wrong = new Array();
for (var i=0, ii=0; i<State[q].Guesses.length-1; i++) {
wrong[ii++] = State[q].Guesses[i];
}
if (DB[0] || ii>0) qDetails += makeHiddenField(Q+'wrong', wrong);
}
if (JCloze[4]) { // number of penalties
var x = (hp5 ? State[q][1] : State[q].HintsAndChecks);
qDetails += makeHiddenField(Q+'penalties', x);
}
if (JCloze[5]) { // clue shown?
var x = (hp5 ? State[q][0] : State[q].ClueGiven);
qDetails += makeHiddenField(Q+'clue_shown', (x ? 'HOTPOT_YES' : 'HOTPOT_NO'));
}
if (JCloze[6]) { // clue text
qDetails += makeHiddenField(Q+'clue_text', I[q][2]);
}
} // end for
}
return qDetails;
}
function GetJCrossQuestionDetails(v) {
var qDetails = '';
// check the quiz version
if (v==5 || v==6) {
// inialize letter count
var letters = 0;
// get details for each question
for (var row=0; row<L.length; row++) {
for (var col=0; col<L[row].length; col++) {
// increment letter count, if required
if (L[row][col]) letters++;
// show answers and clues, if required
var q = (v==5) ? C[row][col] : CL[row][col];
if (q) {
// format 'Q' (a padded, two-digit version of 'q')
var Q = getQ('JCross', q);
var clue_A = (v==5) ? A[q] : GetJCrossClue('Clue_A_' + q);
var clue_D = (v==5) ? D[q] : GetJCrossClue('Clue_D_' + q);
// add separator, if required
if (JCross[0] && (clue_A || clue_D)) {
qDetails += makeSeparator(Q);
}
if (clue_A) { // across question
if (JCross[3]) qDetails += makeHiddenField(Q+'across', GetJCrossWord(G, row, col));
if (JCross[4]) qDetails += makeHiddenField(Q+'across_clue', clue_A);
}
if (clue_D) { // down question
if (JCross[3]) qDetails += makeHiddenField(Q+'down', GetJCrossWord(G, row, col, true));
if (JCross[4]) qDetails += makeHiddenField(Q+'down_clue', clue_D);
}
} // end if q
} // end for col
} // end for row
if (JCross[2]) { // show number of letters
qDetails = makeHiddenField('JCross_letters', letters) + qDetails;
}
if (JCross[1]) { // show penalties
qDetails = makeHiddenField('JCross_penalties', window.Penalties) + qDetails;
}
}
return qDetails;
}
function GetJCrossClue(id) {
var obj = (document.getElementById) ? document.getElementById(id) : null;
return (obj) ? GetChildNodesText(obj, 'Clue') : '';
}
function GetJCrossWord(a, r, c, goDown) {
// a is a 2-dimensional array of letters, r is a row number, c is a column number
var s = '';
while (r<a.length && c<a[r].length && a[r][c]) {
s += a[r][c];
if (goDown) {
r++;
} else {
c++;
}
}
return s;
}
function GetJMatchQuestionDetails(v) {
var qDetails = '';
// HP5.5 uses "I" for v5 and v6 JMatch quizzes
var hp5 = (window.I) ? true : false;
// check the quiz version
if (hp5 || v==6 || v==6.1) {
if (JMatch[1] && v==6.1) { // attempts
qDetails += makeHiddenField('JMatch_attempts', Penalties+1);
}
// get number of questions
var max_q = (hp5 || v==6) ? Status.length : F.length;
// get details for each question
for (var q=0; q<max_q; q++) {
// format 'Q' (a padded, two-digit version of 'q')
var Q = getQ('JMatch', q);
// add separator, if required
if (JMatch[0] && (JMatch[1] || JMatch[2] || JMatch[3])) {
qDetails += makeSeparator(Q);
}
if (JMatch[1] && (hp5 || v==6)) { // attempts
qDetails += makeHiddenField(Q+'attempts', Status[q][1]);
}
if (JMatch[2]) { // LHS text
var x = (hp5) ? I[q][0] : (v==6) ? GetJMatchText(q, 'LeftItem') : F[q][0];
qDetails += makeHiddenField(Q+'lhs', x);
}
if (JMatch[3]) { // RHS text
var x = (hp5) ? I[q][1] : (v==6) ? GetJMatchText(q, 'RightItem') : GetJMatchRHS(q);
qDetails += makeHiddenField(Q+'rhs', x);
}
} // end for
}
return qDetails;
}
function GetJMatchText(q, className) {
var obj = (document.getElementById) ? document.getElementById('Questions') : null;
return (obj) ? GetChildNodesText(obj.childNodes[q], className) : '';
}
function GetJMatchRHS(q) { // Drag-and-drop only (v==6.1)
var max_i = (window.F && window.D) ? F.length : 0;
for (var i=0; i<max_i; i++) {
if (D[i][2]==F[q][1]) break;
}
return (i<max_i) ? D[i][0] : '';
}
function GetJMixQuestionDetails(v) {
qDetails = '';
// check the quiz version
if (v==5 || v==6 || v==6.1) {
var A = Answers.length;
for (var a=0; a<A; a++) {
var G = Answers[a].length;
for (var g=0; g<G; g++) {
if (Answers[a][g] != GuessSequence[g]) break;
}
if (g>=G) break;
}
var isWrong = (a>=A);
// format 'Q' (a padded, two-digit version of 'q')
var Q = getQ('JMix', 0);
// add separator, if required
if (JMix[0]) qDetails += makeSeparator(Q);
// add 'score' for this question
var score = isWrong ? 0 : ((Segments.length-Penalties)/Segments.length);
qDetails += makeHiddenField(Q+'score', Math.floor(score*100)+'%');
if (JMix[1]) { // number of wrong guesses
qDetails += makeHiddenField(Q+'wrongGuesses', Penalties);
}
if (JMix[2]) { // right answer
qDetails += makeHiddenField(Q+'right', GetJMixSequence(Answers[isWrong ? 0 : a]));
}
if (JMix[3] && isWrong) { // wrong answer
qDetails += makeHiddenField(Q+'wrong', GetJMixSequence(GuessSequence));
}
}
return qDetails;
}
function GetJMixSequence(indexes) {
var s = new Array();
for (var i=0; i<indexes.length; i++) {
s[i] = JMix[4] ? indexes[i] : GetJMixSegmentText(indexes[i]);
}
return s;
}
function GetJMixSegmentText(index){
var i_max = Segments.length;
for (var i=0; i<i_max; i++) {
if (Segments[i][1] == index) break;
}
return (i<i_max) ? Segments[i][0] : '';
}
function GetJQuizQuestionDetails(v) {
var qDetails = '';
// HP5.5 uses "Status" for v5 and v6 JMatch quizzes (HP6 uses "State")
var hp = (window.Status) ? 5 : (window.State) ? 6 : 0;
// check the quiz version
if (hp) {
// get details for each question
var max_q = (hp==5) ? Status.length : State.length;
for (var q=0; q<max_q; q++) {
// skip this question if it was not used (HP6 v6 only)
if (hp==6 && !State[q]) continue;
// format 'Q' (a padded, two-digit version of 'q')
var Q = getQ('JQuiz', q);
// add separator
if (JQuiz[0]) qDetails += makeSeparator(Q);
if (hp==6 && JQuiz[11]) { // question type
var x = parseInt(I[q][2]);
x = (x==0) ? 'multiple-choice' : (x==1) ? 'short-answer' : (x==2) ? 'hybrid' : (x==3) ? 'multi-select' : 'n/a';
qDetails += makeHiddenField(Q+'type', x);
}
// score (as %)
var x = (hp==5) ? Status[q][4]*10 : I[q][0]*State[q][0];
qDetails += makeHiddenField(Q+'score', Math.floor(x)+'%');
if (hp==6 && JQuiz[10]) { // weighting
qDetails += makeHiddenField(Q+'weighting', I[q][0]);
}
if (JQuiz[1]) { // question text
var x = (hp==5) ? I[q][0] : (document.getElementById) ? GetChildNodesText(document.getElementById('Q_'+q), 'QuestionText') : '';
qDetails += makeHiddenField(Q+'question', x);
}
if (JQuiz[2]) { // student's correct answers
var x = (hp==5) ? Status[q][3] : GetJQuizAnswerDetails(q, 2);
qDetails += makeHiddenField(Q+'correct', x);
}
if (JQuiz[3]) { // ignored and wrong answers
var x = (hp==5) ? '' : GetJQuizAnswerDetails(q, 1);
if (hp==5) {
for (var i=0; i<I[q][1].length; i++) {
if (I[q][1][i][0] && (I[q][1][i][0].toUpperCase() != Status[q][3].toUpperCase())) {
x += ((x ? ',' : '') + I[q][1][i][0]);
}
}
}
if (DB[0] || x) qDetails += makeHiddenField(Q+'other', x);
}
if (hp==6 && JQuiz[7]) { // all selected answers
var x = GetJQuizAnswerDetails(q, 0);
qDetails += makeHiddenField(Q+'selected', x);
}
if (hp==6 && JQuiz[8]) { // wrong answers
var x = GetJQuizAnswerDetails(q, 3);
qDetails += makeHiddenField(Q+'wrong', x);
}
if (hp==6 && JQuiz[9]) { // ignored answers
var x = GetJQuizAnswerDetails(q, 4);
qDetails += makeHiddenField(Q+'ignored', x);
}
if (JQuiz[4]) { // number of hints
var x = (hp==5) ? Status[q][2] : State[q][4];
qDetails += makeHiddenField(Q+'hints', x);
}
if (JQuiz[5]) { // number of checks of incorrect answers
var x = (hp==5) ? Status[q][1] : (State[q][2]-1);
qDetails += makeHiddenField(Q+'checks', x);
}
} // end for
} // end if
return qDetails;
}
function GetChildNodesText(obj, className) {
// search this node (obj) and its child nodes and
// return all text under node with required classname
var txt = '';
if (obj) {
if (className && obj.className==className) {
className = '';
}
if (className=='' && obj.nodeType==3) { // text node
txt = obj.nodeValue + ' '; // html entities
}
if (obj.childNodes) {
for (var i=0; i<obj.childNodes.length; i++) {
txt += GetChildNodesText(obj.childNodes[i], className);
}
}
}
return txt;
}
function GetJQuizAnswerDetails(q, flag) {
// flag : the type of information required about the student's answers
// 0 : all student's answers
// 1 : student's wrong and ignored answers
// 2 : student's correct answers
// 3 : student's wrong answers
// 4 : ignored answers
var x = State[q][5];
if (I[q][2]=='3') { // multi-select
if (flag==4) {
var x = new Array();
} else {
// get required part of 'x' and convert to array
var i = x.lastIndexOf('|');
var x = x.substring((flag==2 ? (i+1) : 1), ((flag==0 || flag==2) ? x.length : i)).split('|');
}
for (var i=0; i<x.length; i++) {
var a = new Array();
for (var ii=0; ii<x[i].length; ii++) {
if (x[i].charAt(ii)=='Y') {
var s = JQuiz[6] ? String.fromCharCode(97+ii) : I[q][3][ii][0];
if (s && s.replace && window.RegExp) {
s = s.replace(new RegExp('\\+', 'g'), '&#43;');
}
a.push(s);
}
}
x[i] = a.join('+');
}
} else if (x) { // multiple-choice, short-answer and hybrid
// remove trailing comma and convert to array
x = x.substring(0, x.length-1).split(',');
if (flag) {
var a = new Array();
if (flag==1 || flag==2 || flag==3) {
for (var i=0; i<x.length; i++) {
var ii = I[q][3][(x[i].charCodeAt(0)-65)][2];
if(((flag==1 || flag==2) && ii==1) || (flag==3 && ii==0)) a.push(x[i]);
}
}
if (flag==1) {
x = a;
a = new Array();
}
if (flag==1 || flag==4) {
for (var i=0; i<I[q][3].length; i++) {
var s = String.fromCharCode(65+i);
for (var ii=0; ii<x.length; ii++) {
if (x[ii]==s) break;
}
if (ii==x.length) a.push(s);
}
}
x = a;
}
// convert answer indexes to values, if required
if (JQuiz[6]==false) {
for (var i=0; i<x.length; i++) {
var ii = x[i].charCodeAt(0) - 65;
x[i] = I[q][3][ii][0];
}
}
}
return x;
}
function GetRhubarbDetails(v) {
qDetails = '';
if (v==6) {
var Q = getQ('Rhubarb', 0);
if (document.title) { // use quiz title as question name
qDetails += makeHiddenField(Q+'name', document.title);
}
if (Rhubarb[0]) { // correct words
qDetails += makeHiddenField(Q+'correct', Words.length+' words');
}
if (Rhubarb[1]) { // incorrect words
// remove leading 'Wrong guesses: ' from Detail
var x = Detail.substring(15).split(' ');
qDetails += makeHiddenField(Q+'wrong', x);
}
}
return qDetails;
}
function GetSequiturDetails(v) {
qDetails = '';
// there is no information available ... at the moment
return qDetails;
}
// *********************
// library functions
// *********************
function pad(i, l) {
var s = (i+'');
while (s.length<l) s = '0' + s;
return s;
}
function getQ(section, q) {
// Q is a padded, two-digit version of the question number, 'q', prefixed by 'section'
return section + '_q' + (q<9 ? '0' : '') + (q+1) + '_';
}
function makeSeparator(Q) {
return is_LMS() ? '' : makeHiddenField(Q.substring(0, Q.length-1), '---------------------------------');
}
function makeHiddenField(name, value) {
var field = '';
var t = typeof(value);
if (t=='string') {
value = encode_entities(value);
} else if (t=='object') {
var values = value;
var i_max = values.length;
value = '';
for (var i=0; i<i_max; i++) {
values[i] = trim(values[i]);
if (values[i]!='') {
value += (i==0 ? '' : ',') + encode_entities(values[i]);
}
}
}
if (is_LMS()) {
if (value && value.indexOf && value.indexOf('<')>=0 && value.indexOf('>')>=0) {
value = '<![CDATA[' + value + ']]>';
}
field = '<field><fieldname>' + name + '</fieldname><fielddata>' + value + '</fielddata></field>';
} else {
field = '<INPUT type=hidden name="' + name + '" value="' + value + '">';
}
return field;
}
function trim(s) {
var i = 0;
var ii = s.length;
while (i<ii && s.charAt(i)==' ') {
i++;
}
while (ii>i && s.charAt(ii-1)==' ') {
ii--;
}
return s.substring(i, ii);
}
function encode_entities(s_in) {
var i_max = (s_in) ? s_in.length : 0;
var s_out = '';
for (var i=0; i<i_max; i++) {
var c = s_in.charCodeAt(i);
// 34 : double quote .......["] &amp;
// 38 : single quote .......['] &apos;
// 44 : comma ..............[,]
// 60 : left angle bracket .[<] &lt;
// 62 : right angle bracket [>] &gt;
// >=128 multibyte character
s_out += (c<128) ? s_in.charAt(i) : ('&#x' + pad(c.toString(16), 4) + ';');
}
return s_out;
}
// *********************
// initialization
// functions
// *********************
function getTime(obj) {
obj = obj ? obj : new Date();
// get year, month and day
// for an LMS : yyyy-mm-dd
// for email : DayName MonthName dd yyyy
var s = is_LMS() ?
obj.getFullYear() + '-' + pad(obj.getMonth()+1, 2) + '-' + pad(obj.getDate(), 2) :
MSG[16][obj.getDay()] + ' ' + MSG[17][obj.getMonth()] + ' ' + pad(obj.getDate(), 2) + ' ' + obj.getFullYear()
;
// get hours, minutes and seconds (hh:mm:ss)
s += ' ' + pad(obj.getHours(), 2) + ':' + pad(obj.getMinutes(), 2) + ':' + pad(obj.getSeconds(), 2);
// get time difference
// for an LMS : +xxxx
// for email : GMT+xxxx
var x = obj.getTimezoneOffset(); // e.g. -540
if (!isNaN(x)) {
s += ' ' + (is_LMS() ? '' : 'GMT') + (x<0 ? '+' : '-');
x = Math.abs(x);
s += pad(parseInt(x/60), 2) + pad(x - (parseInt(x/60)*60), 2);
}
return s;
}
function getFunction(fn) {
if (typeof(fn)=='string') {
fn = eval('window.' + fn);
}
return (typeof(fn)=='function') ? fn : null;
}
function getFunctionCode(fn, extra) {
var s = '';
var obj = getFunction(fn);
if (obj) {
s = obj.toString();
var i1 = s.indexOf('{')+1;
var i2 = s.lastIndexOf('}');
if (i1>0 && i1<i2) {
s = s.substring(i1, i2);
}
}
return s + (extra ? extra : '');
}
function getFunctionArgs(fn) {
var a = new Array();
var obj = getFunction(fn);
if (obj) {
var s = obj.toString();
var i1 = s.indexOf('(')+1;
var i2 = s.indexOf(')');
if (i1>0 && i1<i2) {
a = s.substring(i1, i2).split(',');
}
}
return (a.length) ? ('"'+ a.join('","') + '",') : '';
}
function getPrompt(fn) {
// the LoginPrompt is the text string in the first prompt(...) statement
// v5 : in the StartUp function
// v6 : in the GetUserName function
// Note: netscape uses double-quote as delimiter, others use single quote
var s = getFunctionCode(fn);
var i1 = s.indexOf('prompt') + 8;
var i2 = s.indexOf(s.charAt(i1-1), i1);
var p = (i1>=8 && i2>i1) ? s.substring(i1, i2) : '';
// make sure browser has decoded the unicode prompt properly
// this check is mainly for ns4, but there may be others
if (window.RegExp) {
var r = new RegExp('u([0-9A-F]{4})');
var m = r.exec(p);
while (m) {
p = p.replace(m[0], '&#' + parseInt(m[1], 16) + ';');
m = r.exec(p);
}
}
return p;
}
function getStartUpCode(fn) {
// the main initialization code comes from the StartUp function
// v5 : the code before "UserName", if any,
// and the code after the 2nd subsequent '}'
// v6 : the code before and after 'GetUserName();'
// i.e. all the code except the call to 'GetUserName();'
var s = getFunctionCode(fn);
var i1 = s.indexOf('GetUserName();');
if (i1>=0) { // v6
var i2 = i1 + 14;
} else { // v5
var i1 = s.indexOf('UserName');
var i2 = s.indexOf('}', s.indexOf('}', i1+8)+1)+1;
}
return (0<i1 && i1<i2) ? s.substring(0, i1) + s.substring(i2) : '';
}
function is_LMS() {
return document.forms['store'] ? true : false;
}
// ***************
// fix IE5 and NS6
// ***************
// add Array.push if required (allows v6 quizzes to run on ie5win)
if (Array.prototype && Array.prototype.push==null) {
Array.prototype.push = new Function("x", "this[this.length]=x");
}
// add attachEvent function, if required (allows HP5 v6 quizzes to run on ie5mac)
// NOTE: to allow v6 quizzes on ie5mac, the following code
// needs to be inserted BEFORE the Hot Potatoes javascript
if (window.attachEvent==null) {
window.attachEvent = new Function('evt', 'fn', 'eval("window."+evt+"="+fn)');
}
if (document.attachEvent==null) {
document.attachEvent = new Function('evt', 'fn', 'eval("document."+evt+"="+fn)');
}
// fix the ShowMessage function for NS6
// by removing calls to a button's "focus()" method
if (navigator.userAgent.indexOf("Netscape6")>=0 && window.ShowMessage) {
var s = ShowMessage.toString();
var r = new RegExp('document\\.getElementById\\((\'|")FeedbackOKButton(\'|")\\)\\.focus\\(\\);', 'gi');
s = s.substring(s.indexOf('{')+1, s.lastIndexOf('}')).replace(r, '');
window.ShowMessage = new Function('Feedback', s);
}
// ns6.0 (in JMix at least) has an error in the FocusAButton function too
// this could be fixed as follows ...
//if (window.FocusAButton) {
// window.FocusAButton = new Function('return true');
//}
// however, ns6.0 then crashes completely when the mouse moves over a link, so don't bother
// Hot Potatoes quiz sniffing
// === v3 ===
// JBC uses "QuizForm", which contains elements called "Q*_**" (* and ** start at 1)
// JCloze uses "Cloze" form
// JCross uses "Crossword" form
// JMatch uses "QuizForm", which contains elements called "1,2,3..x" and "x+1,x+2...",
// and "CheckForm" form, which contains an element called "ScoreBox"
// it is also the only HP quiz type to use an array called "CorrectAnswers"
// JQuiz uses "QForm*" forms (* starts at 1), which each contain an element called "Guess"
// === v4 ===
// JBC uses "QForm" form in "QuestionDiv", which contains elements called "FB* (* starts at 0)"
// JCloze uses "Cloze" form in "QuestionDiv"
// JCross uses "Crossword" form in "CWDiv"
// JMatch uses "ExCheck" form in "TitleDiv"
// (no JMix in hp4)
// JQuiz uses "QForm" form in "QuestionDiv", which contains an element called "Answer"
// === v5 ===
// JBC uses "QForm" form, which contains elements called "FB_*_**" (* and ** start at 0)
// JCloze uses "Cloze" form
// JCross writes out "AnswerForm" from a variable called "GetAnswerOpener"
// HP5.3: uses "AnswerForm" in "BottomFrame"
// HP5.5: uses "AnswerForm" in "TopFrame", but it is only there when an answer is being input
// JMatch uses "QForm" form, which contains elements called "sel*" (which disappear by the time the quiz is finished)
// JMix uses "ButtonForm"
// JQuiz uses "QForm*" and "Buttons*" (one for each question)
// === v6 ===
// JBC uses "QForm" form (elements have no name or id)
// JCloze uses "Cloze" form (elements have no name or id)
// JCross does not use any forms,
// HP5: has "GridDiv" in "MainDiv"
// HP6: has "Clues" table in "MainDiv"
// JMatch has "MatchDiv" in "MainDiv"
// HP5: uses "QForm" form, which contains elements called "sel*"
// HP6: uses "QForm" form, which contains elements called "s*_**"
// JMix does not use any forms, but has "SegmentDiv" in "MainDiv"
// JQuiz
// HP5: uses "QForm" form, which contains an element called "Guess"
// HP6: has "Questions" ordered list in "MainDiv"
// === v6+ ===
// JMatch has DIVs called "D*" and "F*" (* starts at 0)
// JMix has DIVs called "D*" and "Drop*" (* starts at 0)
// useful sniffing tools (Cut and Paste to browser address box)
//javascript:var s="";var x=new quiz_obj();for(X in x)s+=","+X+"="+x[X];alert(s.substring(1));
//javascript:var s="";var x=document.layers;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
//javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
//javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
//javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
//javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
function sniff_quiz() {
// "sniff" (=detect) the quiz's type and intended browser version
// and cache the values in a global variable caleld "quiz"
// quiz type
// 0 : unknown
// 1 : jbc
// 2 : jcloze
// 3 : jcross
// 4 : jmatch
// 5 : jmix
// 6 : jquiz
// 7 : rhubarb (TexToys)
// 8 : Sequitur (TexToys)
// intended browser version
// 3 : ns3, ie3 (frames)
// 4 : ns4, ie4 (cross browser dhtml)
// 5 : ie5 (frames, send results via CGI)
// 6 : ie6, op7, gecko (w3 standards)
// 6.1 : "drag and drop" versions of JMatch and JMix v6
// create the global "quiz" object, if necessary
if (!window.quiz) window.quiz = new Object();
// check the version and type are not already set
if (!quiz.v || !quiz.t) {
// initialize version and type
var v = 0;
var t = 0;
// set shortcuts to DOM objects
var d = document;
var f = d.forms;
if (f.QuizForm && f.CheckForm && self.CorrectAnswers) {
v = 3;
t = 4; // jmatch
} else if (self.FeedbackFrame && self.CodeFrame) {
v = 3;
f = CodeFrame.document.forms;
t = (f.QuizForm) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.QForm1) ? 6 : 0;
} else if (self.DynLayer) {
v = 4;
if (d.layers) {
// for NS4, adjust "f" to point to a forms object in a layer
var lyr = d.QuestionDiv || d.CWDiv || d.TitleDiv || null;
if (lyr) f = lyr.document.forms;
}
t = (f.QForm && f.QForm.FB0) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.ExCheck) ? 4 : (f.QForm && f.QForm.Answer) ? 6 : 0;
} else if (self.TopFrame && self.BottomFrame) {
v = 5;
f = BottomFrame.document.forms;
t = (f.QForm && f.QForm.elements[0].name.substring(0,3)=='FB_') ? 1 : (f.Cloze) ? 2 : (self.GetAnswerOpener && GetAnswerOpener.indexOf('AnswerForm')>=0) ? 3 : (f.QForm && self.RItems) ? 4 : (f.ButtonForm) ? 5 : (f.QForm0 && f.Buttons0) ? 6 : 0;
} else if (GetObj(d, 'MainDiv')) {
v = 6;
var obj = (f.QForm) ? f.QForm.elements : null;
t = (obj && obj.length>0 && obj[0].id=='') ? 1 : (f.Cloze) ? 2 : (GetObj(d, 'GridDiv') || GetObj(d, 'Clues')) ? 3 : GetObj(d, 'MatchDiv') ? 4 : GetObj(d, 'SegmentDiv') ? 5 : ((f.QForm && f.QForm.Guess) || GetObj(d, 'Questions')) ? 6 : 0;
} else if (GetObj(d, 'D0')) {
v = 6.1; // drag and drop (HP5 and HP6)
t = (GetObj(d, 'F0')) ? 4 : (GetObj(d, 'Drop0')) ? 5 : 0;
} else if (window.Words && f.Rhubarb) {
v = 6;
t = 7; // rhubarb (TexToys)
} else if (window.Segments && GetObj(d, 'Story')) {
v = 6;
t = 8; // sequitur (TexToys)
}
if (v) quiz.v = v; // intended browser version
if (t) quiz.t = t; // quiz type
}
}
function get_quiz_type() {
sniff_quiz();
return quiz.t;
}
function get_quiz_version() {
sniff_quiz();
return quiz.v;
}
function all_finished(a, s, aa, ss) {
// determine whether or not all quistions in a quiz are finished
// a : outer array
// s : condition, if any, on outer array
// aa : inner array, if any
// ss : condition, if any, on inner array
// the arrays "a" and "aa" may be passed as arrays or strings to be eval(uated)
// the conditions "s" and "ss" are specified as strings to be eval(uated)
// assume a positive result
var r = true;
// set length of outer array. if any
var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
// loop through outer array
for (var i=0; i<l; i++) {
// do outer condition, if any
if (s && eval(s)) r = false;
// set length of inner array, if any
var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
// loop through inner array. checking inner condition
for (var ii=0; ii<ll; ii++) {
if (ss && eval(ss)) r = false;
}
}
return r;
}
function is_finished() {
// assume false result
var r = false;
var t = get_quiz_type();
var v = get_quiz_version();
if (t==1) { // jbc
if (v==3) r = all_finished(DoneStatus, "i>0 && a[i]=='0'");
else if (v==4) r = all_finished(DoneStatus, "a[i]==0");
else if (v==5 || v==6) r = all_finished(Status, "a[i][0]==0");
} else if (t==2) { // jcloze
if (v==3 || v==4 || v==5 || v==6) r = all_finished(I, "CheckAnswer(i)==-1");
// also: else if (v==5 || v==6) r = all_finished(State, "a[i][4]!=1")
} else if (t==3) { // jcross
if (v==3) r = all_finished(document.Crossword.elements, "ConvertCase(is.mac?unescape(MacStringToWin(a[i].value)):a[i].value,1)!=Letters[i]");
else if (v==4) r = all_finished(WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0) != a[i].charAt(0)");
else if (v==5) r = all_finished(L, "", "L[i]", "L[i][ii] && L[i][ii]!=G[i][ii]");
} else if (t==4) { // jmatch
if (v==3) r = all_finished(CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex != a[i]");
else if (v==4) r = all_finished(Draggables, "a[i].correct!='1'");
else if (v==5) r = all_finished(I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]<1 && GetAnswer(i)!=I[i][3]");
else if (v==6) r = all_finished(D, "D[i][2]==0 || D[i][2]!=D[i][1]");
} else if (t==5) { // jmix
// there was no v3 or v4 of JMix
if (v==5 || v==6) r = !all_finished(Answers, "a[i].join(',')=='" + GuessSequence.join(',') + "'");
} else if (t==6) { // jquiz
if (v==3 || v==4) r = all_finished(State, "a[i][0]==0");
else if (v==5 || v==6) r = all_finished(State, "a[i] && a[i][0]<0");
} else if (t==7) { // rhubarb
if (v==6) r = all_finished(DoneList, "a[i]==1");
} else if (t==8) { // sequitur
if (v==6) r = (CurrentNumber==TotalSegments || AllDone);
}
return r; // result
}
function GetObj(d, id) {
return d.getElementById ? d.getElementById(id) : d.all ? d.all[id] : d[id];
}
// **************
// initialization
// **************
if (window.Finish==null) { // v3, v4 and v5
// modify the function which writes feedback to call Finish() if the quiz is finished
// usually this is the WriteFeedback()
// but v3 of JMatch uses CheckAnswer()
var f = window.WriteFeedback ? 'WriteFeedback' : 'CheckAnswer';
var s = getFunctionCode(f, 'if(is_finished())Finish();');
var a = getFunctionArgs(f);
eval('window.' + f + '=new Function(' + a + 's)');
}
// the standard Finish() function
// for v6, this overwrites the original function
function Finish(){
var f = document.store;
if (f) {
// hotpot use "Score", TexToys use "FinalScore"
var mark = (window.Score ? Score : window.FinalScore ? FinalScore : 0);
f.starttime.value = getTime(Start_Time);
f.endtime.value = getTime();
f.mark.value = mark;
f.detail.value = '<?xml version="1.0"?><hpjsresult><fields>'+GetQuestionDetails()+'</fields></hpjsresult>';
f.submit();
}
}
// create form to send results
if (DB[7] && DB[8] && !is_LMS()) {
ResultForm = ''
+ '<html><body>'
+ '<form name="Results" action="" method="post" enctype="x-www-form-encoded">'
+ makeHiddenField('recipient', '')
+ makeHiddenField('subject', '')
+ makeHiddenField('Exercise', '')
+ makeHiddenField('realname', '')
+ makeHiddenField('Score', '')
+ makeHiddenField('Start_Time', '')
+ makeHiddenField('End_Time', '')
+ makeHiddenField('title', 'Thanks!')
+ '</form>'
+ '</body></html>'
;
}
// reassign the StartUp function
var p = getPrompt(window.GetUserName || window.StartUp);
var c = getStartUpCode(window.StartUp);
if (p && c) {
window.StartUp = new Function('QuizLogin("' + p + '")');
window.StartQuiz = new Function('if(!is_LMS()){' + c + '}');
}
// reassign the SendResults function
window.SendResults = SendAllResults;
// set start time
var Start_Time = new Date();
//-->