2 // PLEASE NOTE that this version is more recent than the incorrectly
3 // numbered v6.1, dated 2003.11.17. From now on, version numbers will
4 // follow those of Hot Potatoes.
5 /* hot-potatoes.js (v6.0.4.0 - 2005.02.18)
6 * =======================================
7 * by Gordon Bateson, February 2003
8 * Copyright (c) 2003 Gordon Bateson. All Rights Reserved.
10 * You are hereby granted a royalty free license to use or modify this
11 * software provided that this copyright notice appears on all copies.
13 * This software is provided "AS IS" without a warranty of any kind.
15 * Documentation and downloads may be available from:
16 * http://www.kanazawa-gu.ac.jp/~gordon/research/hot-potatoes/
18 // This JavaScript library modifies the SendResults and StartUp functions
19 // used by hotpot v5 and v6, so that more (or less!) details about the
20 // student can be input, and more details of a quiz's questions and answers
21 // can be submitted to the server when the quiz is finished
22 // If the arrays below (Login, DB, JBC, ...) are set BEFORE calling this
23 // script, they will NOT be overwritten. Any array that is not set, will
24 // use the defaults below. This is useful if you want to use different
25 // settings for different quizzes.
29 if (window.Login==null) {
31 Login[0] = true; // Show prompt for user name
32 // This can also be a string of user names ...
33 // Login[0] = "Guest,Peter,Paul,Mary,Webmaster";
34 // or an array of user names (and on-screen texts) (and passwords) ...
35 // Login[0] = new Array("Guest", "001,Peter,xxxx", "002,Paul,yyyy", "003,Mary,zzzz", "Webmaster");
36 // and can also be written as ...
37 // Login[0] = new Array(
38 // new Array("Guest"),
39 // new Array("001", "Peter", "xxxx"),
40 // new Array("002", "Paul", "yyyy"),
41 // new Array("003", "Mary", "zzzz"),
42 // new Array("Webmaster")
44 Login[1] = true; // Show prompt for student's UserID
45 // If there is no password prompt (i.e. Logon[3] is false), this value
46 // will be checked against the password information, if any, in Login[0]
47 Login[2] = false; // Show prompt for student's email
48 Login[3] = false; // Show prompt for quiz password, and check this value against
49 // the password information, if any, in Login[0]
50 // This can also be a string required to start the quiz ...
51 // Login[3] = "password";
52 Login[4] = true; // Show prompt for the cookie expiry date
53 // If false, cookies expire at the end of the current session
54 Login[5] = "guest,webmaster"
55 // guest user names (case insensitive) ...
56 // Login[5] = "guest,webmaster";
57 // These users do NOT need to fill in other login fields
58 // and their quiz results are NOT added to the database
59 // the Login prompts and error messages
60 // are defined in the MSG array (see below)
63 // Database (for use with BFormMail)
65 if (window.DB==null) {
67 DB[0] = true; // append form fields to database on server
68 // If you are NOT using BFormMail's database feature,
69 // set DB[0]=false, and you can then safely ignore DB[1 to 5]
70 DB[1] = "/home/gordon/public_html/cgi/hot-potatoes-data";
71 // append_db folder path (no trailing slash)
72 // Can be either an absolute path e.g. "/home/gordon/public_html/cgi/hot-potatoes-data"
73 // or a relative (to CGI bin) path e.g. "hot-potatoes-data"
74 DB[2] = "hot-potatoes";
75 // append_db file name (no extension)
76 // If left blank, the quiz file name, without extension, will be used
77 // i.e. each quiz will have its results stored in a different file.
78 // If filled in, this file will store the results for ALL quizzes.
79 // Database files and folders must be set up BEFORE running the quiz
80 // must have appropriate access privileges (on Unix, use "chmod 666").
81 DB[3] = ""; // append_db extension (if left blank, ".txt" will be used)
82 DB[4] = ""; // db_fields (if left blank, ALL quiz fields will be sent)
83 DB[5] = ""; // db_delimiter (if left blank, tab will be used)
84 DB[6] = "REMOTE_ADDR,HTTP_USER_AGENT";
85 // env_report ('REMOTE_ADDR','HTTP_USER_AGENT' and a few others)
86 // for a complete description of these fields are, see ...
87 // http://www.infosheet.com/stuff/BFormMail.readme
88 // Switches DB[7] and DB[8] force the settings in the ResultForm
89 // In v5 and v6 quizzes, these settings wil be override those in the original quiz
90 // If the quiz results are to be sent to an LMS (via the "store" form)
91 // then switches DB[7] and DB[8] are not used
92 DB[7] = ''; // URL of form processing script
93 // e.g. http://www.kanazawa-gu.ac.jp/~gordon/cgi/bformmail.cgi
94 DB[8] = ''; // email address to which results should be sent
95 // e.g. gordon@kanazawa-gu.ac.jp
97 // By default the quiz's question's scores will be returned.
98 // If you want more detailed information, set the flags below:
102 if (window.JBC==null) {
104 JBC[0] = true; // show separator line between answers on email
105 JBC[1] = true; // show number of attempts to answer question
106 JBC[2] = true; // show question texts
107 JBC[3] = true; // show right answer(s)
108 JBC[4] = true; // show wrong answer(s)
109 JBC[5] = true; // show ignored answer(s)
110 JBC[6] = false; // show answer as text (false) or number (true)
112 // JBC quizzes use the global variables 'I' and 'Status'
113 // I : an array of JBC_QUESTIONs (one for each question)
115 // [0] : question text
116 // [1] : array of JBC_ANSWERs (one for each answer)
117 // [2] : single/multi flag
118 // 0 : single answer (using 'button')
119 // 1 : multiple answers (using 'checkbox')
122 // [1] : answer feedback
123 // [2] : correct answer flag
124 // 0 : this is NOT the correct answer
125 // 1 : this is the correct answer
126 // Status : an array of JBC_QUESTION_STATUSes
127 // JBC_QUESTION_STATUS:
128 // [0] : correctly answered yet flag
129 // 0 : this question has NOT been correctly answered
130 // 1 : this question has been correctly answered
131 // [1] : array of JBC_ANSWER_STATUSes (one for each answer)
132 // '0' : initial value
133 // 'R' : single answer question was answered 'R'ight
134 // 'W' : single answer question was answered 'W'rong
135 // 'C' : multiple answer question's checkbox was 'C'hecked
136 // 'U' : multiple answer question's checkbox was 'U'nchecked
137 // [2] : number of times this question has been wrongly answered
138 // [3] : score (out of 1) for this question (maybe undefined on HP<5.5)
139 // 0 : not correct yet
140 // 0<[3]<1 : correct but only after [2] wrong attempts
141 // 1 : correct first time (bravo!)
142 // N.B. score = (numberOfAnswers - numberofWrongTries) / numberOfAnswers
146 if (window.JCloze==null) {
147 JCloze = new Array();
148 JCloze[0] = true; // show separator line between answers on email
149 JCloze[1] = true; // show student's correct answer
150 JCloze[2] = true; // show other correct answer(s), if any
151 JCloze[3] = true; // show wrong answer(s), if any (NOT available for v5)
152 JCloze[4] = false; // show number of hints + checks (legacy field, replaced by [7]+[9])
153 JCloze[5] = false; // show if clue was asked for or not (legacy field, replaced by [8])
154 JCloze[6] = true; // show clue
155 JCloze[7] = true; // show number of hints (=next letter requests)
156 JCloze[8] = true; // show number of clues
157 JCloze[9] = true; // show number of checks
159 // JCloze quizzes use the global variables 'I' and 'State'
160 // I : array of JCLOZE_ANSWERs
163 // [1] : array of JCLOZE_ANSWER_TEXTs
164 // [2] : clue for this answer
165 // JCLOZE_ANSWER_TEXT :
166 // [0] : array (seems unnecessary, just the text would be enough?)
167 // [0] : text of possible answer
168 // State : array of JCLOZE_ANSWER_STATEs
169 // JCLOZE_ANSWER_STATE (v5) :
170 // [0] : clue asked for or not
171 // [1] : number of hints (show next letter) and penalties ('check' an incorrect answer)
172 // [2] : length of answer matched
173 // [3] : score for this item
174 // [4] : already answered correctly
175 // [5] : answer entered in text box (right or not)
176 // JCLOZE_ANSWER_STATE (v6)
177 // this.ClueGiven = false;
178 // this.HintsAndChecks = 0;
179 // this.MatchedAnswerLength = 0;
180 // this.ItemScore = 0;
181 // this.AnsweredCorrectly = false;
182 // this.Guesses = new Array(); last guess is correct answer
186 if (window.JCross==null) {
187 JCross = new Array();
188 JCross[0] = true; // show separator line between answers on email
189 JCross[1] = true; // show number of penalties (hints or checks before complete)
190 JCross[2] = true; // show number of letters
191 JCross[3] = true; // show correct answers
192 JCross[4] = true; // show clues
193 JCross[5] = true; // show wrong answers
194 JCross[6] = true; // show if clue was asked for or not
195 JCross[7] = true; // show number of hints (=next letter requests)
196 JCross[8] = true; // show number of checks
197 // there are no "ignored" answers for JCross quizzes
199 // JCross quizzes use the following global variables:
200 // L : letters (of correct answers)
201 // C : clue numbers (CL in v6)
203 // 'L', 'C' ('CL') and 'G' are all 2-dimensional arrays (rows x cols)
205 // v5 quizzes additionally use the following single-dimension arrays
206 // A : clues for across (horizontal) words
207 // D : clues for down (vertical) words
208 // N.B. form is only sent when all answers are correct so
209 // you can't find out what 'wrong' answers were entered
213 if (window.JMatch==null) {
214 JMatch = new Array();
215 JMatch[0] = true; // show separator line between answers on email
216 JMatch[1] = false; // show number of penalties (= total number of checks)
217 JMatch[2] = true; // show LHS texts (the question)
218 JMatch[3] = true; // show correct answers
219 JMatch[4] = true; // show wrong answers
220 JMatch[5] = true; // show checks (per match) [empty or unchanged RHS are not counted]
221 // JMatch has no "clue" or "hint" buttons
222 // there cannot be any "ignored" answers
224 // v5 JMatch quizzes use the global variables 'I' and 'Status' (and 'RItems')
225 // v6 JMatch quizzes use only 'Status'
226 // v6+ JMatch quizzes use 'F' and 'D' (see below)
227 // I : an array of JMATCH_PAIRs (one for each pair)
231 // [2] : fixed (=not jumbled) flag
234 // [3] : index in drop down list selection
235 // Status : an array of JMATCH_PAIR_STATUSes
236 // JMATCH_PAIR_STATUS:
237 // [0] : correctly matched yet flag
238 // 0 : this pair has NOT been correctly matched
239 // 1 : this pair has been correctly matched
240 // [1] : number of times this item has been wrongly matched
242 // [2] : id of original SELECT element containing possible matches
243 // Note that after matching, this SELECT is removed, so don't try looking for it :-)
244 // v6+ JMatch quizzes use the global variables 'F' and 'D'
245 // F : array of JMATCH_FIXED_ITEMs
246 // JMATCH_FIXED_ITEM:
249 // D : array of JMATCH_DRAGGABLE_ITEMs
250 // JMATCH_DRAGGABLE_ITEM
252 // [1] : tag of the F item to which it SHOULD be dragged
253 // [2] : tag of the F item to which it was dragged (initally 0)
254 // N.B. form is only sent when all answers are correct so
255 // you can't find out what 'wrong' answers were entered
259 if (window.JMix==null) {
261 JMix[0] = true; // show separator line between answers on email
262 JMix[1] = false; // show number of wrong guesses (replaced by JMix[5])
263 JMix[2] = true; // show right answer
264 JMix[3] = true; // show wrong answer, if any
265 JMix[4] = false; // show answer as text (false) or number (true)
266 JMix[5] = true; // show number of checks
267 JMix[6] = true; // show number of hints (=show next word)
269 // JMix quizzes use the global variables
270 // 'Segments', 'GuessSequence' and 'Penalties'
271 // Segments : array of JMix_QUESTIONs
274 // [1] : order in sequence
276 // GuessSequence : array of 'order in sequence' numbers
277 // Penalties : number of incorrect guesses
281 if (window.JQuiz==null) {
283 JQuiz[0] = true; // show separator line between answers on email
284 JQuiz[1] = true; // show question text
285 JQuiz[2] = true; // show student's correct answer(s)
286 JQuiz[3] = false; // show wrong and ignored answer(s) (legacy field superceded by [8] & [9])
287 JQuiz[4] = true; // show number of hints requested
288 JQuiz[5] = false; // show number of checks of incorrect answers (legacy field superceded by [12])
289 // HP6 v6 quizzes only
290 JQuiz[6] = false; // show answer value (false) or A,B,C... index (true)
291 JQuiz[7] = false; // show all students answers
292 JQuiz[8] = true; // show student's wrong answers
293 JQuiz[9] = true; // show ignored answers (not relevant for multi-select questions)
294 JQuiz[10] = true; // show score weightings
295 JQuiz[11] = true; // show question type
296 JQuiz[12] = true; // show number of checks (if true, then JQuiz[5] of will be ignored)
297 JQuiz[13] = true; // show number of times ShowAnswer button was pressed (usually 0 or 1)
299 // v5 JQuiz quizzes use the global variables 'I' and 'Status'
300 // I : array of JQUIZ_ANSWERs
302 // [0] : question text
303 // [1] : array of JQUIZ_ANSWER_TEXTs (one for each answer)
304 // JQUIZ_ANSWER_TEXT :
305 // [0] : array (seems unnecessary, just the text would be enough?)
306 // [0] : text of possible answer
307 // Status : array of JQUIZ_ANSWER_STATEs
308 // JQUIZ_ANSWER_STATE :
309 // [0] : question done or not
310 // [1] : number of wrong checks
311 // [2] : number of hints asked for
312 // [3] : student's answer
313 // [4] : score for this question
314 // v6 JQuiz quizzes use the global variables 'I' and 'State'
315 // I : array of JQUIZ_QUESTIONs
318 // [1] : ?? (always set to '')
319 // [2] : question type
320 // '0'=multiple-choice, '1'=short-answer, '2'=hybrid, '3'=multi-select
321 // [3] : array of JQUIZ_ANSWERSs (one for each possible answer)
323 // [0] : answer value
324 // [1] : feedback text
325 // [2] : correct answer flag (1=a correct answer, 0=a wrong answer)
326 // [3] : weighted score (as percentage) if correct
327 // [4] : flag (usually set to 1, but for hybrid answers that are not
328 // to be included in multiple choice options, it is set to 0)
329 // State : array of JQUIZ_QUESTION_STATEs
330 // JQUIZ_QUESTION_STATE :
331 // [0] : score (-1 shows not done yet)
332 // [1] : array showing on which number try each JQUIZ_ANSWER was selected
333 // [2] : number of attempts at this question
334 // [3] : total of weighted scores of correct answers that were selected
335 // i.e. each time a correct answer is selected,
336 // its JQUIZ_ANSWER[3] weighting is added to this total
337 // so when all the correct answers have been selected, this will be 100
338 // [4] : penalties incurred for hints (score is set to zero if >= 1)
339 // [5] : - for multiple choice, short-answer and hybrid questions, this is a
340 // comma-delimited list showing order in which answers were chosen
341 // - for multi-select fields, this is bar-delimted ('|') list of settings
342 // showing whether each checkbox was selected ('Y') on not ('N') when the
343 // 'Check' button was clicked. The final item in the list will be the
344 // settings for the correct answer.
345 // N.B. JBC, JMatch(v5) and JQuiz(v5) all use global variables 'I' and 'Status'
346 // JBC : I[0].length==3 && !window.RItems
347 // JQuiz(v5) : I[0].length==2
348 // JMatch(v5) : I[0].length==4 && window.RItems
349 // N.B. JCloze(v5+6) and JQuiz(v6) both use global variables 'I' and 'State'
350 // JCloze (v5) : I[0].length==3 && State[0].Guesses==null
351 // JCloze (v6) : I[0].length==3 && State[0].Guesses!=null
352 // JQuiz (v6) : I[0].length==4
356 if (window.Rhubarb==null) {
357 Rhubarb = new Array();
358 Rhubarb[0] = true; // show correct words (so far)
359 Rhubarb[1] = true; // show correct words as count (true) or list (false)
360 Rhubarb[2] = true; // show wrong words
361 Rhubarb[3] = false; // show wrong words as count (true) or list (false)
362 Rhubarb[4] = false; // show ignored words (not implemented yet)
363 Rhubarb[5] = true; // show hints
368 if (window.Sequitur==null) {
369 Sequitur = new Array();
370 Sequitur[0] = true; // show count of correct button clicks
371 Sequitur[1] = true; // show count of wrong button clicks
376 if (window.MSG==null) {
385 MSG[5] = 'Start the Quiz';
387 // Cookie menu options (only used if Login[4] is true)
388 MSG[7] = 'keep for this session only';
389 MSG[8] = 'keep for one day';
390 MSG[9] = 'keep for one month';
391 MSG[10] = 'do NOT keep cookies';
392 // Login error messages
393 MSG[11] = 'Sorry, you were unable to login. Please try again later.';
394 MSG[12] = 'Please fill in all the information.';
395 MSG[13] = 'Incorrect Password. Please try again.';
396 MSG[14] = 'Incorrect ID. Please try again.';
397 MSG[15] = 'Email address does not appear to be valid.';
398 // day and month names (used in Start_Time and End_Time)
399 MSG[16] = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
400 MSG[17] = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
402 MSG[18] = 'Please enable pop-up windows on your browser.';
403 // browser specific instuctions on how to enable popup windows
405 var s = n.userAgent.toLowerCase();
406 if (n.appName=='Netscape' && s.indexOf('gecko')>=0) {
408 MSG[18] += '\n\n' + 'Edit->Preferences, ' + (s.indexOf('mac')>=0 ? 'Advanced->Scripts & Plugins' : 'Privacy & Security->Popup Window Controls');
409 } else if (s.indexOf('safari')>=0) {
411 MSG[18] += '\n\n' + 'on Safari menu, uncheck "Block Pop-Up Windows"';
412 } else if (s.indexOf('firebird')>=0) {
414 MSG[18] += '\n\n' + 'Preferences->Web Features, uncheck "Block Pop-Up Windows"';
415 } else if (s.indexOf('msie 6')>=0) {
417 MSG[18] += '\n\n' + 'Tools->Pop-up Blocker->Turn Off Pop-up Blocker';
420 //if (window.FEEDBACK==null) {
421 // FEEDBACK = new Array();
422 // FEEDBACK[0] = ''; // url of feedback page/script
423 // FEEDBACK[1] = ''; // array of array('teachername', 'value');
424 // FEEDBACK[2] = ''; // 'student name' [formmail only]
425 // FEEDBACK[3] = ''; // 'email@somewhere.com>' [formmail only]
426 // FEEDBACK[4] = ''; // window width
427 // FEEDBACK[5] = ''; // window height
428 // FEEDBACK[6] = ''; // 'Send a message to teacher' [prompt/button text]
429 // FEEDBACK[7] = ''; // 'Title'
430 // FEEDBACK[8] = ''; // 'Teacher'
431 // FEEDBACK[8] = ''; // 'Message'
432 // FEEDBACK[10] = ''; // 'Close this window' [formmail only]
438 for (var i=0; i<=8; i++) {
441 // indexes for the HP array (makes the code further down easier to read)
454 if (window.ServerFields==null) {
455 ServerFields = new Array();
456 // these fields will be added to the ResultForm and submitted to the CGI script on the server.
457 // 'Sort', 'return_link_title', 'return_link_url' and 'print_blank_fields' are useful for formmail
458 // override the HP setting of sort fields (forces ALL fields to be displayed)
459 ServerFields[0] = new Array('sort', '');
460 // add link to close pop-up results window
461 ServerFields[1] = new Array('return_link_title', 'Close this window');
462 ServerFields[2] = new Array('return_link_url', 'javascript:self.close()');
463 // make sure zero values are printed
464 ServerFields[3] = new Array('print_blank_fields', 'yes');
465 // you can also set other fields for your customized CGI script
466 // e.g. adding a server defined start time (instead of a client defined start time)
467 // ServerFields[4] = new Array('serverStartTime', '<?php echo date("Y-m-d H:i:s") ?>');
469 // *********************
471 // (not required by LMS)
472 // *********************
473 function QuizLogin(LoginPrompt) {
474 if (!is_LMS() && (Login[0] || Login[1] || Login[2] || Login[3])) {
478 + '<body bgColor="#cccccc" onLoad="opener.setFocus(self)">'
481 + 'self.expiry=null;'
483 if (Login[4]) { // cookie expiry
484 html += "opener.checkOK(self,'CookieExpiry');";
486 if (Login[0]) { // user name
487 html += "opener.checkOK(self,'UserName');";
489 if (Login[1]) { // user ID
490 html += "opener.checkOK(self,'UserID');";
492 if (Login[2]) { // user email
493 html += "opener.checkOK(self,'UserEmail');";
495 if (Login[3]) { // quiz password
496 html += "opener.checkOK(self,'Password');";
499 + 'opener.StartQuiz();'
502 + 'if(isNaN(self.tries))self.tries=0;'
504 + 'if(self.tries<3){'
505 + 'opener.setFocus(self);'
507 + "alert(opener.MSG[11]);"
516 + '<caption>' + LoginPrompt + '</caption>';
518 if (Login[0]) { // user name
519 var v = getCookie(self, 'UserName');
521 + '<th align=right nowrap>' + MSG[0] + ' :</th>'
524 if (typeof(Login[0])=='boolean') { // text box
525 html += '<input type=text name=UserName value="' + v + '">';
526 } else { // drop down menu of names
527 // pattern to match commas and white space
528 var comma = (window.RegExp) ? new RegExp('\\s*,\\s*') : ',';
529 // convert list of names to array, if necessary
530 if (typeof(Login[0])=='string') {
531 Login[0] = Login[0].split(comma);
533 html += '<select name=UserName size=1>'
534 + '<option value=""></option>'
536 for(var i=0; i<Login[0].length; i++) {
537 // convert name details to array if nececount_cary
538 if (typeof(Login[0][i])=='string') {
539 Login[0][i] = Login[0][i].split(comma);
541 html += makeOption(Login[0][i][0], v, Login[0][i][1]);
549 if (Login[1]) { // user ID
550 var v = getCookie(self, 'UserID');
551 html += '<tr><th align=right nowrap>' + MSG[1] + ' :</th><td><input type=text name=UserID value="' + v + '"></td></tr>';
553 if (Login[2]) { // user email
554 var v = getCookie(self, 'UserEmail');
555 html += '<tr><th align=right nowrap>' + MSG[2] +' :</th><td><input type=text name=UserEmail value="' + v + '"></td></tr>';
557 if (Login[3]) { // quiz password
558 var v = getCookie(self, 'Password');
559 html += '<tr><th align=right nowrap>' + MSG[3] + ' :</th><td><input type=password name=Password value="' + v + '"></td></tr>';
561 if (Login[4]) { // cookie lifespan
562 var v = getCookie(self, 'CookieExpiry');
564 + '<th align=right nowrap>' + MSG[4] + ' :</th>'
566 + '<select name="CookieExpiry" size=1>'
567 + makeOption('session', v, MSG[7])
568 + makeOption('day', v, MSG[8])
569 + makeOption('month', v, MSG[9])
570 + makeOption('never', v, MSG[10])
579 + '<input type=submit value="' + MSG[5] + '"> '
580 + '<input type=button value="' + MSG[6] + '" onClick="opener.goBack();self.close();">'
583 + '</table></form></body></html>'
585 // set height of Login Window
586 var m = navigator.userAgent.indexOf('Mac')>=0;
587 var h = (m ? 80 : 100);
588 for (var i=0; i<5; i++) h += (Login[i] ? (m ? 20 : 25) : 0);
589 // open up a new window
590 if (!openWindow('', '', (m ? 320 : 300), h, 'RESIZABLE', html)) {
591 alert(MSG[18]); // unable to open popup window
593 } else { // no Login required
594 window.UserName = window.UserID = window.UserEmail = window.Password = '';
599 function makeOption(value, v, txt) {
600 return '<option value="' + value + '"' + (value==v ? ' SELECTED' : '') + '>' + (txt ? txt : value) + '</option>';
602 function setFocus(w) {
603 w.focus(); // bring window to the front
604 var obj = w.document.forms[0].elements;
605 for(var i=0; i<obj.length; i++) {
606 var v = getValue(w, i);
607 if (v=='' || obj[i].type=='submit') {
613 function checkOK(w, n){
614 var v = getValue(w, n, true);
615 if (v || (n!='UserName' && isGuest())) {
616 if (n=='CookieExpiry') setCookieExpiry(w, v);
617 setCookie(self, n, v, w.expiry);
618 if (n!='CookieExpiry') eval('self.' + n + '=v');
620 if (w.ok) alert(MSG[12]);
624 function getValue(w, n, flag) {
625 var obj = w.document.forms[0].elements[n];
626 var TYPE = obj.type.toUpperCase(); // required for ns4 (win)
627 if (obj.options && TYPE.indexOf('SELECT')>=0){
628 var v = obj.options[obj.selectedIndex].value;
634 if (n=='Password' || (n=='UserID' && !Login[3])) {
635 var pwd = getPassword(w);
636 if (pwd && v!=pwd) msg = MSG[n=='Password' ? 13 : 14];
638 if (n=='UserEmail' && window.RegExp) {
640 r = r + '(\\.' + r + ')';
641 r = new RegExp('^(' + r + '*)@(' + r + '+)$');
642 if (v.match(r)==null) msg = MSG[15];
646 if (w.ok) alert(msg);
652 function getPassword(w) {
654 if (Login[3] && typeof(Login[3])=='string') {
656 } else if ((Login[3] || Login[1]) && typeof(Login[0])=='object') {
657 var username = getValue(w, 'UserName');
658 for(var i=0; i<Login[0].length; i++) {
659 if (username==Login[0][i][0]) {
660 pwd = Login[0][i][2];
667 function setCookieExpiry(w, v) {
669 w.expiry = new Date('Thu, 01-Jan-70 00:00:01 GMT');
670 } else if (v=='day' || v=='month') {
671 var ms = (v=='month' ? 31 : 1) * 60 * 60 * 24 * 1000;
672 w.expiry = new Date((new Date()).getTime() + ms);
675 function setCookie(w, name, value, expires, path, domain, secure) {
676 if (name) w.document.cookie = ''
677 + 'HP_' + name + "=" + escape(value)
678 + (expires ? "; expires=" + expires.toGMTString() : "")
679 + (path ? "; path=" + path : "")
680 + (domain ? "; domain=" + domain : "")
681 + (secure ? "; secure" : "")
684 function getCookie(w, n) {
685 var c = w.document.cookie;
686 var i = c.indexOf('HP_' + n + '=');
687 var j = (i<0) ? -1 : c.indexOf(';', (i += n.length + 4));
688 return (i<0) ? '' : unescape(c.substring(i, ((j<0) ? c.length : j)));
691 if (w==null) w = self; // default
692 if (w.history.length) w.history.back();
694 function openWindow(url, name, width, height, attributes, html) {
695 // set height, width and attributes
696 if (window.screen && width && height) {
697 var W = screen.availWidth;
698 var H = screen.availHeight;
699 width = Math.min(width, W);
700 height = Math.min(height, H);
702 + (attributes ? (attributes+',') : '')
703 + 'WIDTH='+width+',HEIGHT='+height
706 // create global hpWindows object, if necessary
707 if (!window.hpWindows) window.hpWindows = new Array();
708 // initialize window object
710 // has a window with this name been opened before?
711 if (name && hpWindows[name]) {
712 // http://www.webreference.com/js/tutorial1/exist.html
713 if (hpWindows[name].open && !hpWindows[name].closed) {
717 hpWindows[name] = null;
720 // check window is not already open
722 // workaround for "Access is denied" errors in IE when offline
723 // based on an idea seen at http://www.devshed.com/Client_Side/JavaScript/Mini_FAQ
724 var ie_offline = (document.all && location.protocol=='file:');
725 // try and open the new window
726 w = window.open((ie_offline ? '' : url), name, attributes);
727 // check window opened OK (user may have prevented popups)
730 if (window.screen && width && height) {
731 w.moveTo((W-width)/2, (H-height)/2);
733 // add content, if required
741 } else if (url && ie_offline) {
744 if (name) hpWindows[name] = w;
749 // *********************
750 // Send results by email
751 // (not required by LMS)
752 // *********************
753 function SendAllResults(Score) {
754 // check this quiz is not generated by a LMS
756 // add flat file database details to the results form
757 AddDatabaseDetailsToResultForm();
758 // add student details to the results form
759 AddStudentDetailsToResultForm();
760 // add question details to the results form
761 AddQuestionDetailsToResultForm();
762 // add server fields, if any, to results form
763 AddServerFieldsToResultForm();
764 // change "method" of form, because "get" only allows 512 byts of data
765 ResultForm = replaceLast('method="get"', 'method="post"', ResultForm);
766 // create results window and form
767 var w = openWindow('', '', 500, 400, 'RESIZABLE,SCROLLBARS,LOCATION', ResultForm);
768 // check window opened OK (user may have prevented popups)
770 // get shortcut to form object
771 var form = w.document.forms[0];
772 // update some important field values
773 form.Score.value = Score + '%';
774 form.realname.value = UserName;
775 form.Start_Time.value = getTime(Start_Time);
776 form.End_Time.value = getTime();
777 // force email subject and Exercise title
778 form.subject.value = document.title;
779 form. Exercise.value = document.title;
780 // update DB fields, if required
781 if (DB[0] && !isGuest()) set_db_fields(form);
782 if (DB[7]) form.action = DB[7];
783 if (DB[8]) form.recipient.value = DB[8];
784 // if this is a Netscape browser, check if the referer will be set OK
785 if (navigator.appName=='Netscape' && (location.protocol=='file:' || navigator.userAgent.indexOf('Netscape6')>=0)) {
786 // ns4 and ns7 set referer to 'file:// ...' when running a quiz offline
787 // ns6.2 (at least) always sets referer to 'about:blank'
788 // Netscape's setting of referer can cause BFormMail
789 // to reject the form, so encode the form data as a URL
790 var url = form.action;
791 var obj = form.elements;
792 for (var i=0; i<obj.length; i++) {
793 var v = escape(obj[i].value);
794 v = v.replace((new RegExp('\\+', 'g')), '%2B');
795 url += (i==0 ? '?' : '&') + obj[i].name + '=' + v;
797 w.location.href = url;
798 } else { // browser can POST form ok
801 } else { // unable to open popup window
807 // check username is not a "guest" user
809 var n = getCookie(self, 'UserName').toLowerCase();
811 // convert list of user names to array, if necessary
812 if(typeof(Login[5])=='string') {
813 Login[5] = Login[5].split(',');
815 for(var i=0; i<Login[5].length; i++) {
816 if (n==Login[5][i].toLowerCase()) {
824 function set_db_fields(form) {
825 // update list of DB fields, if required
826 if (DB[4]=='' && window.RegExp) {
827 // add administration fields
830 + (Login[1] ? ',ID' : '')
831 + (Login[2] ? ',email' : '')
832 + (Login[3] ? ',password' : '')
833 + ',Score,Start_Time,End_Time'
835 // add answer fields (except separators)
836 var r = new RegExp('^[^_]+_q\\d\\d_\\w+$');
837 for(var i=0; i<form.elements.length; i++) {
838 var n = form.elements[i].name;
839 if (r.test(n)) db_fields += ',' + n;
841 form.db_fields.value = db_fields;
843 // make sure delimiter is set (NS6+ requires this be set here, not any earlier)
844 form.db_delimiter.value = (DB[5] ? DB[5] : '\t');
846 function AddStudentDetailsToResultForm() {
848 if (Login[0]) { // user name
849 // use 'realname' instead of a separate 'Name' field
850 // sDetails += hpHiddenField('Name', window.UserName);
852 if (Login[1]) { // user ID
853 sDetails += hpHiddenField('ID', window.UserID);
855 if (Login[2]) { // user email
856 sDetails += hpHiddenField('email', window.UserEmail);
858 if (sDetails && window.RegExp) {
859 // insert sDetails before '<input...Score...></input>'
860 var r = new RegExp('<input[^>]*Score[^>]*><\\/input>', 'i');
861 var m = r.exec(ResultForm);
863 ResultForm = ResultForm.replace(m[0], sDetails + m[0] + makeSeparator('Time_'));
867 if (Login[3]) { // quiz password
868 sDetails += hpHiddenField('Password', window.Password);
869 ResultForm = replaceLast('</form>', sDetails + '</form>', ResultForm);
872 function AddQuestionDetailsToResultForm() {
873 var qDetails = GetQuestionDetails();
875 // insert qDetails before the final </form> tag in the ResultForm
876 ResultForm = replaceLast('</form>', qDetails + '</form>', ResultForm);
879 function AddDatabaseDetailsToResultForm() {
880 if (window.DB && DB[0] && !isGuest()) {
883 if (folder && folder.charAt(folder.length-1)!='/') folder += '/';
886 file = location.href;
887 file= file.substring(file.lastIndexOf('/')+1);
888 var i = file.indexOf('?');
889 if (i >= 0) file = file.substring(0, i);
890 var i = file.lastIndexOf('.');
891 if (i >= 0) file = file.substring(0, i);
893 var ext = (DB[3] ? DB[3] : 'txt');
894 if (ext.charAt(0)!='.') ext = '.' + ext;
895 dbDetails += hpHiddenField('append_db', folder + file + ext);
896 dbDetails += hpHiddenField('db_fields', DB[4]);
897 dbDetails += hpHiddenField('db_delimiter', ''); // NS6+ requires this be set later
898 if (DB[6]) dbDetails += hpHiddenField('env_report', DB[6]);
899 // insert dbDetails before the final </form> tag in the ResultForm
900 ResultForm = replaceLast('</form>', dbDetails + '</form>', ResultForm);
903 function AddServerFieldsToResultForm() {
904 if (window.ServerFields) {
905 var s = ''; // input tags for s(erver fields)
906 for (var i=0; i<ServerFields.length; i++) {
907 if (ServerFields[i][0] && window.RegExp) {
908 // remove previous field value, if any
909 var r = new RegExp('<input[^>]*name\\s*=\\s*["\']\\s*' + ServerFields[i][0] + '[^>]*>(\\s*<\\/input>)?', 'i');
910 if (r.test(ResultForm)) {
911 ResultForm = ResultForm.replace(r, '');
914 if (ServerFields[i][1]) {
915 s += hpHiddenField(ServerFields[i][0], ServerFields[i][1]);
918 if (s) ResultForm = replaceLast('</form>', s + '</form>', ResultForm);
921 function replaceLast(a, b, c) {
922 // replace last occurrence of 'a' in 'c' with 'b'
924 var i = c.lastIndexOf(a);
925 return (i<0 || l==0) ? c : (c.substring(0, i) + b + c.substring(i+l));
927 // *************************
928 // Extract question details
929 // *************************
930 function GetQuestionDetails() {
931 var hp = hpVersion();
932 var t = hpQuizType();
933 var v = hpQuizVersion();
934 return (t==1) ? GetJbcQuestionDetails(hp, v) :
935 (t==2) ? GetJClozeQuestionDetails(hp, v) :
936 (t==3) ? GetJCrossQuestionDetails(hp, v) :
937 (t==4) ? GetJMatchQuestionDetails(hp, v) :
938 (t==5) ? GetJMixQuestionDetails(hp, v) :
939 (t==6) ? GetJQuizQuestionDetails(hp, v) :
940 (t==7) ? GetRhubarbDetails(hp, v) :
941 (t==8) ? GetSequiturDetails(hp, v) : '';
943 function GetJbcQuestionDetails(hp, v) {
945 // check the quiz version
946 if (hp==5 || hp==6) {
947 // get question details
948 for(var q=0; q<I.length; q++) {
949 // initialize strings to hold answer details
950 var aDetails = new Array();
951 aDetails[0] = new Array(); // right
952 aDetails[1] = new Array(); // wrong
953 aDetails[2] = new Array(); // ignored
954 // get answer details
955 for(var a=0; a<I[q][1].length; a++) {
956 var i = (Status[q][1][a]=='R') ? 0 : (Status[q][1][a]=='0') ? 2 : 1;
957 aDetails[i][aDetails[i].length] = (JBC[6] ? a : I[q][1][a][0]);
959 // format 'Q' (a padded, two-digit version of 'q')
960 var Q = getQ('JBC', q);
961 // add separator, if required
962 if (JBC[0]) qDetails += makeSeparator(Q);
963 if (JBC[1]) { // number of attempts to answer question
964 qDetails += hpHiddenField(Q+'attempts', Status[q][2] + (Status[q][0]==1 ? 1 : 0));
966 if (JBC[2]) { // question text
967 qDetails += hpHiddenField(Q+'text', I[q][0]);
969 if (JBC[3] && (DB[0] || aDetails[0].length>0)) { // right
970 qDetails += hpHiddenField(Q+'right', aDetails[0]);
972 if (JBC[4] && (DB[0] || aDetails[1].length>0)) { // wrong
973 qDetails += hpHiddenField(Q+'wrong', aDetails[1]);
975 if (JBC[5] && (DB[0] || aDetails[2].length>0)) { // ignored
976 qDetails += hpHiddenField(Q+'ignored', aDetails[2]);
978 // calculate score for this question, if required (for HP version < 5.5)
979 if (isNaN(Status[q][3])) {
980 var a1 = Status[q][1].length; // answers
981 var a2 = Status[q][2]; // attempts
982 Status[q][3] = (a1<1 || a1<(a2-1)) ? 0 : ((a1 - (a2-1)) / a1);
984 // add 'score' for this question
985 qDetails += hpHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');
990 function GetJClozeQuestionDetails(hp, v) {
992 // check the quiz version
993 if (hp==5 || hp==6) {
994 var r = hpRottmeier();
995 if (parseInt(r)==2) { // Rottmeier Find-It 3a+3b
996 qDetails += hpHiddenField('JCloze_penalties', window.TotWrongChoices);
998 // get details for each question
999 var q_max = (r==0) ? State.length : GapList.length; // could use I.length for both
1000 for (var q=0; q<q_max; q++) {
1001 // format 'Q' (a padded, two-digit version of 'q')
1002 var Q = getQ('JCloze', q);
1003 // add separator, if required
1004 if (JCloze[0]) qDetails += makeSeparator(Q);
1006 var x = (hp==5) ? State[q][3] : (r==0) ? State[q].ItemScore : GapList[q][1].Score;
1007 qDetails += hpHiddenField(Q+'score', Math.floor(x*100)+'%');
1008 var correct = (HP[_correct][q] ? HP[_correct][q] : '');
1009 if (JCloze[1]) { // student's correct answer
1010 qDetails += hpHiddenField(Q+'correct', correct);
1012 if (JCloze[2]) { // ignored answers
1013 var x = new Array();
1014 if (r!=2.1) { // exclude Find-It 3a
1015 for (var i=0, ii=0; i<I[q][1].length; i++) {
1016 var s = I[q][1][i][0];
1017 if (typeof(s)=='string' && s!='') {
1018 if (s.toUpperCase() == correct.toUpperCase()) {
1019 var is_ignored = false;
1022 var is_ignored = true;
1023 var iii_max = HP[_wrong][q] ? HP[_wrong][q].length : 0;
1024 for (var iii=0; iii<iii_max; iii++) {
1025 if (s.toUpperCase() == HP[_wrong][q][iii].toUpperCase()) {
1026 var is_ignored = false;
1036 qDetails += hpHiddenField(Q+'ignored', x);
1039 var x = (HP[_wrong][q] ? HP[_wrong][q] : '');
1040 qDetails += hpHiddenField(Q+'wrong', x);
1042 if (JCloze[4]) { // number of penalties (Hints + Checks)
1043 var x = (hp==5) ? State[q][1] : (r==0) ? State[q].HintsAndChecks : (r==1) ? GapList[q][1].NumOfTrials : (r==2.2) ? GapList[q][1].HintsAndChecks : 0;
1044 qDetails += hpHiddenField(Q+'penalties', x);
1046 if (JCloze[5]) { // clue shown?
1047 var x = (hp==5) ? State[q][0] : (r==0) ? State[q].ClueGiven: (r==1) ? GapList[q][1].ClueAskedFor : false;
1048 qDetails += hpHiddenField(Q+'clue_shown', (x ? 'YES' : 'NO'));
1050 if (JCloze[6]) { // clue text
1051 qDetails += hpHiddenField(Q+'clue_text', I[q][2]);
1053 if (JCloze[7]) { // number of hints
1054 var x = (HP[_hints][q] ? HP[_hints][q] : 0);
1055 qDetails += hpHiddenField(Q+'hints', x);
1057 if (JCloze[8]) { // number of clues
1058 var x = HP[_clues][q] ? HP[_clues][q] : 0;
1059 qDetails += hpHiddenField(Q+'clues', x);
1061 if (JCloze[9]) { // number of checks (including the final one for the correct answer)
1062 var x = (HP[_checks][q] ? HP[_checks][q] : 0);
1063 qDetails += hpHiddenField(Q+'checks', x);
1069 function GetJCrossQuestionDetails(hp, v) {
1071 // check the quiz version
1072 if (hp==5 || hp==6) {
1073 // inialize letter count
1075 // get details for each question
1076 for (var row=0; row<L.length; row++) {
1077 for (var col=0; col<L[row].length; col++) {
1078 // increment letter count, if required
1079 if (L[row][col]) letters++;
1080 // show answers and clues, if required
1081 var q = (hp==5) ? C[row][col] : CL[row][col];
1083 for (var i=0; i<2; i++) { // 0==across, 1==down
1084 var AD = (i==0) ? 'A' : 'D';
1085 var acrossdown = (i==0) ? 'across' : 'down';
1087 var clue = (hp==5) ? eval(AD+'['+q+']') : GetJCrossClue('Clue_'+AD+'_'+q);
1089 // format 'Q' (a padded, two-digit version of 'q')
1090 var Q = getQ('JCross', q) + acrossdown + '_'; // e.g. JCross_01_across_
1093 qDetails += makeSeparator(Q);
1096 var x = (HP[_correct][AD] && HP[_correct][AD][q]) ? HP[_correct][AD][q] : '';
1097 qDetails += hpHiddenField(Q+'correct', x);
1099 if (JCross[4]) qDetails += hpHiddenField(Q+'clue', clue);
1101 var x = (HP[_wrong][AD] && HP[_wrong][AD][q]) ? HP[_wrong][AD][q] : '';
1102 qDetails += hpHiddenField(Q+'wrong', x);
1105 var x = HP[_clues][q] ? HP[_clues][q] : 0;
1106 qDetails += hpHiddenField(Q+'clues', x);
1109 var x = (HP[_hints][AD] && HP[_hints][AD][q]) ? HP[_hints][AD][q] : 0;
1110 qDetails += hpHiddenField(Q+'hints', x);
1113 var x = (HP[_checks][AD] && HP[_checks][AD][q]) ? HP[_checks][AD][q] : '';
1114 qDetails += hpHiddenField(Q+'checks', x);
1121 if (JCross[2]) { // show number of letters
1122 qDetails = hpHiddenField('JCross_letters', letters) + qDetails;
1124 if (JCross[1]) { // show penalties
1125 var x = (window.Penalties) ? Penalties : 0;
1126 qDetails = hpHiddenField('JCross_penalties', x) + qDetails;
1131 function GetJCrossClue(id) {
1132 var obj = (document.getElementById) ? document.getElementById(id) : null;
1133 return (obj) ? GetTextFromNodeN(obj, 'Clue') : '';
1135 function GetJCrossWord(a, r, c, goDown) {
1136 // a is a 2-dimensional array of letters, r is a row number, c is a column number
1138 while (r<a.length && c<a[r].length && a[r][c]) {
1148 function GetJMatchText(q, className) {
1149 var obj = (document.getElementById) ? document.getElementById('Questions') : null;
1150 return (obj) ? GetTextFromNodeN(obj, className, q) : '';
1152 function GetJMatchRHS(v, q, getCorrect) {
1154 if (v==5.1 || v==6.1) { // Drag-and-drop
1155 var max_i = (window.F && window.D) ? D.length : 0;
1156 for (var i=0; i<max_i; i++) {
1157 if (F[q][1]==D[i][getCorrect ? 1 : 2]) break;
1159 if (i<max_i) rhs = D[i][0];
1160 } else if (v==5 || v==6) { // drop-down list of options
1161 var obj=document.getElementById(Status[q][2]);
1162 if (obj) { // not correct yet
1164 var k = GetKeyFromSelect(obj);
1165 var i_max = obj.options.length;
1166 for (var i=0; i<i_max; i++) {
1167 if (obj.options[i].value==k) break;
1169 if (i>=i_max) i = 0; // shouldn't happen
1171 // get current guess, if any
1172 var i = obj.selectedIndex;
1174 if (i) rhs = obj.options[i].innerHTML;
1176 rhs = GetJMatchText(q, 'RightItem');
1181 function GetJMixQuestionDetails(hp, v) {
1183 // check the quiz version
1184 if (hp==5 || hp==6) {
1185 var q = 0; // question number
1186 // format 'Q' (a padded, two-digit version of 'q')
1187 var Q = getQ('JMix', q);
1188 // add separator, if required
1189 if (JMix[0]) qDetails += makeSeparator(Q);
1190 // add 'score' for this question
1191 var score = HP[_correct]==null ? 0 : ((Segments.length-Penalties)/Segments.length);
1192 qDetails += hpHiddenField(Q+'score', Math.floor(score*100)+'%');
1193 if (JMix[1]) { // number of wrong guesses
1194 qDetails += hpHiddenField(Q+'wrongGuesses', Penalties);
1196 if (JMix[2]) { // right answer
1197 var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1198 qDetails += hpHiddenField(Q+'correct', x);
1200 if (JMix[3]) { // wrong answer(s)
1201 var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1202 qDetails += hpHiddenField(Q+'wrong', x);
1204 if (JMix[5]) { // checks
1205 var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
1206 qDetails += hpHiddenField(Q+'checks', x);
1208 if (JMix[6]) { // hints
1209 var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
1210 qDetails += hpHiddenField(Q+'hints', x);
1215 function GetJMixSequence(indexes) {
1216 var s = new Array();
1217 for (var i=0; i<indexes.length; i++) {
1218 s[i] = JMix[4] ? indexes[i] : GetJMixSegmentText(indexes[i]);
1222 function GetJMixSegmentText(index){
1223 var i_max = Segments.length;
1224 for (var i=0; i<i_max; i++) {
1225 if (Segments[i][1] == index) break;
1227 return (i<i_max) ? Segments[i][0] : '';
1229 function GetJQuizQuestionDetails(hp, v) {
1231 // HP5.5 uses "Status" for v5 and v6 JMatch quizzes (HP6 uses "State")
1232 // var hp = (window.Status) ? 5 : (window.State) ? 6 : 0;
1233 // check the quiz version
1234 if (hp==5 || hp==6) {
1235 // get details for each question
1236 var max_q = (hp==5) ? Status.length : State.length;
1237 for (var q=0; q<max_q; q++) {
1238 // skip this question if it was not used (HP6 v6 only)
1239 if (hp==6 && !State[q]) continue;
1240 // format 'Q' (a padded, two-digit version of 'q')
1241 var Q = getQ('JQuiz', q);
1243 if (JQuiz[0]) qDetails += makeSeparator(Q);
1244 if (hp==6 && JQuiz[11]) { // question type
1245 var x = parseInt(I[q][2]);
1246 x = (x==0) ? 'multiple-choice' : (x==1) ? 'short-answer' : (x==2) ? 'hybrid' : (x==3) ? 'multi-select' : 'n/a';
1247 qDetails += hpHiddenField(Q+'type', x);
1250 var x = (hp==5) ? Status[q][4]*10 : I[q][0]*State[q][0];
1252 qDetails += hpHiddenField(Q+'score', Math.floor(x)+'%');
1253 if (hp==6 && JQuiz[10]) { // weighting
1254 qDetails += hpHiddenField(Q+'weighting', I[q][0]);
1256 if (JQuiz[1]) { // question text
1257 var x = (hp==5) ? I[q][0] : (document.getElementById) ? GetTextFromNodeN(document.getElementById('Q_'+q), 'QuestionText') : '';
1258 qDetails += hpHiddenField(Q+'question', x);
1260 if (JQuiz[2]) { // student's correct answers
1261 var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1262 qDetails += hpHiddenField(Q+'correct', x);
1264 if (JQuiz[3]) { // ignored and wrong answers
1265 var x = (hp==5) ? new Array() : GetJQuizAnswerDetails(q, 1);
1267 for (var i=0; i<I[q][1].length; i++) {
1268 var correct = HP[_correct][q] ? HP[_correct][q] : '';
1269 if (I[q][1][i][0] && I[q][1][i][0].toUpperCase()!=correct.toUpperCase()) {
1270 x[x.length] = I[q][1][i][0];
1274 if (DB[0] || x) qDetails += hpHiddenField(Q+'other', x);
1276 if (hp==6 && JQuiz[7]) { // all selected answers
1277 var x = GetJQuizAnswerDetails(q, 0);
1278 qDetails += hpHiddenField(Q+'selected', x);
1280 if (JQuiz[8]) { // wrong answers
1281 var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1282 qDetails += hpHiddenField(Q+'wrong', x);
1284 if (hp==6 && JQuiz[9]) { // ignored answers
1285 var x = GetJQuizAnswerDetails(q, 4);
1286 qDetails += hpHiddenField(Q+'ignored', x);
1288 if (JQuiz[4]) { // number of hints
1289 var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
1290 qDetails += hpHiddenField(Q+'hints', x);
1292 if (JQuiz[5] || JQuiz[12]) { // number of checks
1293 if (JQuiz[12]) { // strictly checks only
1294 var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
1295 } else { // checks (+ hints in HP6)
1296 var x = (hp==5) ? Status[q][1] : (State[q][2]-1);
1298 qDetails += hpHiddenField(Q+'checks', x);
1300 if (JQuiz[13]) { // ShowAnswer button
1301 var x = (HP[_clues][q]) ? HP[_clues][q] : 0;
1302 qDetails += hpHiddenField(Q+'clues', x);
1308 function GetTextFromNodeN(obj, className, n) {
1309 // returns the text under the nth node of obj with the target class name
1311 if (obj && className) {
1312 if (typeof(n)=='undefined') {
1315 var nodes = GetNodesByClassName(obj, className);
1316 if (n<nodes.length) {
1317 txt += GetTextFromNode(nodes[n]);
1322 function GetNodesByClassName(obj, className) {
1323 // returns an array of nodes with the target classname
1324 var nodes = new Array();
1326 if (className && obj.className==className) {
1328 } else if (obj.childNodes) {
1329 for (var i=0; i<obj.childNodes.length; i++) {
1330 nodes = nodes.concat(GetNodesByClassName(obj.childNodes[i], className));
1336 function GetTextFromNode(obj) {
1337 // return text in (and under) a single DOM node
1340 if (obj.nodeType==3) {
1341 txt = obj.nodeValue + ' ';
1343 if (obj.childNodes) {
1344 for (var i=0; i<obj.childNodes.length; i++) {
1345 txt += GetTextFromNode(obj.childNodes[i]);
1351 function GetJQuizAnswerDetails(q, flag) {
1352 // flag : the type of information required about the student's answers
1353 // 0 : all student's answers
1354 // 1 : student's wrong and ignored answers
1355 // 2 : student's correct answers
1356 // 3 : student's wrong answers
1357 // 4 : ignored answers
1358 var x = State[q][5]; //Sequence of answers chosen by number
1359 if (I[q][2]=='3') { // multi-select
1361 var x = new Array();
1363 // get required part of 'x' and convert to array
1364 if (x.charAt(0)=='|') {
1365 // HP 6.0 and 6.1 (always has leading bar)
1366 var i = x.lastIndexOf('|');
1367 var x = x.substring((flag==2 ? (i+1) : 1), ((flag==0 || flag==2) ? x.length : i)).split('|');
1369 // HP 6.2 (no leading delimiter)
1370 var i = x.lastIndexOf(' | ');
1371 var x = x.substring((flag==2 ? (i+3) : 0), ((flag==0 || flag==2) ? x.length : i)).split(' | ');
1374 for (var i=0; i<x.length; i++) {
1375 var a = new Array();
1376 for (var ii=0; ii<x[i].length; ii++) {
1377 if (x[i].charAt(ii)=='Y') {
1378 var s = JQuiz[6] ? String.fromCharCode(97+ii) : I[q][3][ii][0];
1379 if (s && s.replace && window.RegExp) {
1380 s = s.replace(new RegExp('\\+', 'g'), '+');
1387 } else if (x) { // multiple-choice, short-answer and hybrid
1388 if (x.charAt(x.length-1)==',') {
1389 // HP 6.0 and 6.1 (always has trailing comma)
1390 x = x.substring(0, x.length-1).split(',');
1392 // HP 6.2 (short answer also contains student entered text)
1396 var a = new Array();
1397 if (flag==1 || flag==2 || flag==3) {
1398 for (var i=0; i<x.length; i++) {
1399 var is_correct = false;
1400 if (x[i].length==1) { // single letter
1401 var ii = x[i].charCodeAt(0) - 65;
1402 if (I[q][3] && I[q][3][ii] && I[q][3][ii][2]) {
1403 var is_correct = true;
1411 if (flag==1 || flag==3) {
1421 if (flag==1 || flag==4) {
1422 for (var i=0; i<I[q][3].length; i++) {
1423 var s = String.fromCharCode(65+i);
1424 for (var ii=0; ii<x.length; ii++) {
1425 if (x[ii]==s) break;
1427 if (ii==x.length) a.push(s);
1432 // convert answer indexes to values, if required
1433 if (JQuiz[6]==false) {
1434 for (var i=0; i<x.length; i++) {
1435 if (x[i].length==1) { // single letter
1436 var ii = x[i].charCodeAt(0) - 65;
1437 if (I[q][3] && I[q][3][ii]) {
1438 x[i] = I[q][3][ii][0];
1448 function GetRhubarbDetails(v) {
1451 var q = 0; // always zero
1452 var Q = getQ('Rhubarb', q);
1453 if (document.title) { // use quiz title as question name
1454 qDetails += hpHiddenField(Q+'name', document.title);
1456 if (Rhubarb[0]) { // correct words
1457 var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1458 if (Rhubarb[1]) { // count of correct words
1459 for (var i=0,ii=0; i<x.length; i++) {
1464 qDetails += hpHiddenField(Q+'correct', x);
1466 if (Rhubarb[2]) { // wrong words
1467 var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1468 if (Rhubarb[3]) { // count of wrong words
1471 qDetails += hpHiddenField(Q+'wrong', x);
1473 if (Rhubarb[4]) { // ignored
1475 qDetails += hpHiddenField(Q+'ignored', x);
1477 if (Rhubarb[5]) { // hints
1478 var x = (HP[_hints][q]) ? HP[_hints][q] : '';
1479 qDetails += hpHiddenField(Q+'hints', x);
1484 function GetSequiturDetails(v) {
1487 var q = 0; // always zero
1488 var Q = getQ('Sequitur', q);
1489 if (document.title) { // use quiz title as question name
1490 qDetails += hpHiddenField(Q+'name', document.title);
1492 if (Sequitur[0]) { // number of correct buttons chosen
1493 var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1494 qDetails += hpHiddenField(Q+'correct', x);
1496 if (Sequitur[1]) { // number of wrong buttons chosen
1497 var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1498 qDetails += hpHiddenField(Q+'wrong', x);
1503 // *********************
1504 // click event handlers
1505 // *********************
1506 function hpClick(x, args) {
1507 // x is the button type
1508 // args is either empty, a single argument, or an array of arguments
1509 var btn = (x==1) ? 'Hint' : (x==2) ? 'Clue' : (x==3) ? 'Check' : (x==4) ? 'Enter' : '';
1511 // convert args to array, if necessary
1512 var t = typeof(args);
1514 // do nothing (args is already an array)
1515 } else if (t=='undefined') {
1518 args = new Array(''+args);
1520 // call handler for this kind of button
1521 var x = eval('hpClick'+btn+'('+hpVersion()+','+hpQuizType()+','+hpQuizVersion()+',args);');
1524 function hpClickHint(hp, t, v, args) {
1525 if (t==2 || t==5 || t==6 || t==7) { // JCloze, JMix, JQuiz, Rhubarb
1526 var q = args[0]; // clue/question number
1527 if (!HP[_hints][q]) HP[_hints][q] = 0;
1530 if (t==3) { // JCross
1532 var q = args[0]; // clue/question number
1533 var AD = args[1]; // direction ('A' or 'D')
1534 if (!HP[_hints][AD]) HP[_hints][AD] = new Array();
1535 if (!HP[_hints][AD][q]) HP[_hints][AD][q] = 0;
1536 HP[_hints][AD][q]++;
1541 function hpClickClue(hp, t, v, args) {
1542 if (t==2 || t==3 || t==6) { // JCloze or JCross, or JQuiz (ShowAnswer button)
1543 var q = args[0]; // clue/question number
1544 if (!HP[_clues][q]) HP[_clues][q] = 0;
1549 function hpClickCheck(hp, t, v, args) {
1550 if (t==2) { // JCloze
1552 var r = hpRottmeier();
1553 var already_correct = 'true';
1555 already_correct = (hp==5) ? 'State[i][4]==1' : 'State[i].AnsweredCorrectly==true';
1556 } else if (r==1) { // DropDown
1557 already_correct = 'GapList[i][1].GapLocked==true';
1558 } else if (r==2.1) { // Find-It 3a
1559 already_correct = 'GapList[i][1].ErrorFound==true';
1560 } else if (r==2.2) { // Find-It 3b
1561 already_correct = 'GapList[i][1].GapSolved==true';
1563 var i_max = I.length;
1564 for (var i=0; i<i_max; i++) {
1565 if (eval(already_correct)) continue;
1567 if (r==0 || r==2.2) {
1569 } else if (r==1) { // DropDown
1571 g = eval('document.Cloze.Gap'+i+'.value');
1573 var ii = Get_SelectedDropValue(i);
1574 if (isNaN(ii) || ii<0) { // 'null' || -1
1575 g = ''; // no guess yet
1577 if (window.MakeIndividualDropdowns) {
1578 var is_wrong = (ii!=0);
1581 var is_wrong = (ii!=i);
1586 } else if (r==2.1 && i==args[0]) { // Find-It 3a
1590 if (!HP[_checks][i]) HP[_checks][i] = 0;
1592 if (!HP[_guesses][i]) HP[_guesses][i] = new Array();
1593 var ii = HP[_guesses][i].length;
1594 // is this a new guess at this gap?
1595 if (ii==0 || g!=HP[_guesses][i][ii-1]) {
1596 HP[_guesses][i][ii] = g;
1598 // Rottmeier DropDown 2.4
1601 var G = g.toUpperCase();
1602 var ii_max = I[i][1].length;
1603 for (var ii=0; ii<ii_max; ii++) {
1604 if (window.CaseSensitive) {
1605 if (g==I[i][1][ii][0]) break;
1607 if (G==I[i][1][ii][0].toUpperCase()) break;
1610 var is_wrong = (ii==ii_max);
1612 if (is_wrong) { // guess is wrong
1613 if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
1614 var ii_max = HP[_wrong][i].length;
1615 for (var ii=0; ii<ii_max; ii++) {
1616 if (HP[_wrong][i][ii]==g) break;
1619 HP[_wrong][i][ii] = g;
1621 } else { // guess is correct
1622 HP[_correct][i] = g;
1629 if (t==3) { // JCross
1631 var q = args[0]; // clue/question number
1632 for (var row=0; row<L.length; row++) {
1633 for (var col=0; col<L[row].length; col++) {
1634 var q = (v==5) ? C[row][col] : CL[row][col];
1636 hpClickCheckJCrossV5V6(hp, v, 'A', q, row, col);
1637 hpClickCheckJCrossV5V6(hp, v, 'D', q, row, col);
1643 if (t==4) { // JMatch
1644 var a = new Array();
1645 var extra = ''; // extra js code to eval(uate)
1646 var guess = ''; // js code to eval(uate) guess
1647 var correct = ''; // js code to eval(uate) correct answer
1648 if (window.D && window.F) {
1649 // drag-and-drop, i.e. v5+ and v6+ (HP5 and HP6)
1651 guess = 'GetJMatchRHS(v,i)';
1652 correct = 'GetJMatchRHS(v,i,true)';
1653 } else if (window.GetKeyFromSelect) {
1656 guess = 'GetJMatchRHS(v,i)';
1657 correct = 'GetJMatchRHS(v,i,true)';
1658 } else if (window.GetAnswer) {
1661 guess = "(I[i][2]==0||I[i][0]=='')?'':GetAnswer(i)";
1662 correct = 'I[i][3])';
1663 } else if (window.Draggables) {
1666 s = "Draggables[i].correct=='1'";
1667 } else if (window.CorrectAnswers) {
1670 guess = 'document.QuizForm.elements[i*2].selectedIndex';
1671 correct = 'CorrectAnswers[i]';
1673 for (var i=0; i<a.length; i++) {
1674 // check this match has not already been finished
1675 if (!HP[_correct][i]) {
1676 // do extra setup, if necessary
1677 if (extra) eval(extra);
1678 // get the guess, if any
1679 var g = ''+eval(guess);
1681 // is the guess correct?
1682 if (g==eval(correct)) {
1683 HP[_correct][i] = g;
1684 } else { // wrong answer
1685 // initialize wrong guess array if necessary
1686 if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
1687 // check to see if the guess is already in the guess array
1688 var i_max = HP[_wrong][i].length;
1689 for (var ii=0; ii<i_max; ii++) {
1690 if (HP[_wrong][i][ii]==g) break;
1692 // add the guess if it was not found
1694 HP[_wrong][i][ii]=g;
1696 g = null; // this is not a new answer
1699 // increment checks for this question, if necessary
1701 if (!HP[_checks][i]) HP[_checks][i] = 0;
1709 // get question number (always 0)
1711 // check question has not already been answered correctly
1712 if (!HP[_correct][q]) {
1713 // match current guess against possible correct answers
1714 var a_max = Answers.length;
1715 for (var a=0; a<a_max; a++) {
1716 var i_max = Answers[a].length;
1717 for (var i=0; i<i_max; i++) {
1718 if (Answers[a][i] != GuessSequence[i]) break;
1720 if (i==i_max) break; // correct answer was found
1722 // at this point, (a==a_max) means guess is wrong
1723 // get array of segment texts in this g(uess)
1724 var g = GetJMixSequence(GuessSequence);
1725 // convert g(uess) array and to a s(tring)
1727 var i_max = g.length;
1728 for (var i=0; i<i_max; i++) {
1731 s += (s=='' ? '' : '+') + g[i];
1735 if (a==a_max) { // wrong
1736 if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1737 var i = HP[_wrong][q].length;
1738 HP[_wrong][q][i] = s;
1740 HP[_correct][q] = s;
1742 // increment checks for this question
1743 if (!HP[_checks][q]) HP[_checks][q] = 0;
1748 if (t==6) { // JQuiz
1749 if (hp==5 || hp==6) {
1750 var q = args[0]; // clue/question number
1753 var g = TrimString(eval('BottomFrame.document.QForm' + q + '.Guess').value);
1755 var g = TrimString(eval('document.QForm.Guess').value);
1760 // increment check count
1761 if (!HP[_checks][q]) HP[_checks][q] = 0;
1764 var G = g.toUpperCase(); // used for shortanswer only
1765 var correct_answer = ''; // used for multiselect only
1766 // set index of answer array in I (the question array)
1767 var ans = (hp==5) ? 1 : 3;
1768 var i_max = I[q][ans].length;
1769 for (var i=0; i<i_max; i++) {
1770 // is this a (possible) correct answer?
1771 if (hp==5 || (hp==6 && I[q][ans][i][2])) {
1772 if (hp==6 && I[q][2]==3) { // multiselect
1773 correct_answer += (correct_answer ? '+' : '') + I[q][ans][i][0];
1774 } else { // multichoice, shortanswer
1775 if (window.CaseSensitive) {
1776 if (g==I[q][ans][i][0]) break;
1778 if (G==I[q][ans][i][0].toUpperCase()) break;
1783 if (i==i_max && g!=correct_answer) { // wrong
1784 if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1785 var i_max = HP[_wrong][q].length;
1786 for (var i=0; i<i_max; i++) {
1787 if (HP[_wrong][q][i]==g) break;
1789 if (i==i_max) HP[_wrong][q][i] = g;
1791 HP[_correct][q] = g;
1796 if (t==7) { // Rhubarb
1798 var q = 0; // question number (always zero)
1799 var g = args[0]; // InputWord from CheckGuess()
1801 var G = g.toUpperCase();
1802 var i_max = Words.length;
1803 for (var i=0; i<i_max; i++) {
1804 if (G==Words[i].toUpperCase()) break;
1806 if (i<i_max) { // correct
1807 if (!HP[_correct][q]) HP[_correct][q] = new Array();
1808 HP[_correct][q][i] = g;
1810 if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1811 var i_max = HP[_wrong][q].length;
1812 for (var i=0; i<i_max; i++) {
1813 if (G==HP[_wrong][q][i].toUpperCase()) break;
1815 if (i==i_max) HP[_wrong][q][i] = g;
1820 if (t==8) { // Sequitur
1822 var q = 0; // question number (always zero)
1823 if (CurrentCorrect==args[0]) { // correct button chosen
1824 if (!HP[_correct][q]) HP[_correct][q] = 0;
1827 if (!HP[_wrong][q]) HP[_wrong][q] = 0;
1834 function hpClickCheckJCrossV5V6(hp, v, AD, q, row, col) {
1835 // v is the version of Hot Potatoes
1836 // AD is the direction ('A' or 'D')
1837 // make sure HP[_checks] and HP[_correct] are initialized
1838 if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
1839 if (!HP[_correct][AD]) HP[_correct][AD] = new Array();
1841 var clue = (hp==5) ? eval('window.'+AD) : GetJCrossClue('Clue_'+AD+'_' + q);
1842 // is this a question that has not been answered correctly yet?
1843 if (clue && !HP[_correct][AD][q]) {
1845 var guess = GetJCrossWord(G, row, col, (AD=='D'));
1846 var correct = GetJCrossWord(L, row, col, (AD=='D'));
1847 if (guess==correct) {
1848 HP[_correct][AD][q] = correct;
1851 // make sure HP[_wrong] is initialized
1852 if (!HP[_wrong][AD]) HP[_wrong][AD] = new Array();
1853 if (!HP[_wrong][AD][q]) HP[_wrong][AD][q] = new Array();
1854 // check this guess has not been entered before
1855 var i_max = HP[_wrong][AD][q].length;
1856 for (var i=0; i<i_max; i++) {
1857 if (HP[_wrong][AD][q]==guess) break;
1859 // add the guess if it has not been entered before
1861 HP[_wrong][AD][q][i] = guess;
1865 // update HP[_checks], if necessary
1867 if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
1868 if (!HP[_checks][AD][q]) HP[_checks][AD][q] = 0;
1869 HP[_checks][AD][q]++;
1873 function hpClickEnter(hp, t, v, args) {
1874 if (t==3) { // JCross
1875 var q = args[0]; // clue/question number
1876 if (!HP[_enter][q]) HP[_enter][q] = 0;
1881 function GetJMatchQuestionDetails(hp, v) {
1883 // HP5.5 uses "I" for v5 and v6 JMatch quizzes
1884 // var hp5 = (window.I) ? true : false;
1885 // check the quiz version
1886 if (hp==5 || hp==6) {
1887 if (JMatch[1] && v==6.1) { // attempts
1888 qDetails += hpHiddenField('JMatch_attempts', Penalties+1);
1890 // get number of questions
1891 var max_q = (hp==5 || v==6) ? Status.length : F.length;
1892 // get details for each question
1893 for (var q=0; q<max_q; q++) {
1894 // format 'Q' (a padded, two-digit version of 'q')
1895 var Q = getQ('JMatch', q);
1896 // add separator, if required
1897 if (JMatch[0] && (JMatch[1] || JMatch[2] || JMatch[3])) {
1898 qDetails += makeSeparator(Q);
1900 if (JMatch[1] && (hp==5 || v==6)) { // attempts
1901 qDetails += hpHiddenField(Q+'attempts', Status[q][1]);
1903 if (JMatch[2]) { // LHS text (the question)
1904 var x = (v==5) ? I[q][0] : (v==6) ? GetJMatchText(q, 'LeftItem') : F[q][0];
1905 qDetails += hpHiddenField(Q+'lhs', x);
1907 if (JMatch[3]) { // correct answer (if any)
1908 var x = HP[_correct][q] ? HP[_correct][q] : '';
1909 qDetails += hpHiddenField(Q+'correct', x);
1911 if (JMatch[4]) { // wrong answers (if any)
1912 var x = HP[_wrong][q] ? HP[_wrong][q] : '';
1913 qDetails += hpHiddenField(Q+'wrong', x);
1915 if (JMatch[5]) { // checks
1916 var x = HP[_checks][q] ? HP[_checks][q] : 0;
1917 qDetails += hpHiddenField(Q+'checks', x);
1923 // *********************
1924 // library functions
1925 // *********************
1926 function pad(i, l) {
1928 while (s.length<l) s = '0' + s;
1931 function getQ(section, q) {
1932 // Q is a padded, two-digit version of the question number, 'q', prefixed by 'section'
1933 return section + '_q' + (q<9 ? '0' : '') + (q+1) + '_';
1935 function makeSeparator(Q) {
1936 return is_LMS() ? '' : hpHiddenField(Q.substring(0, Q.length-1), '---------------------------------');
1938 function hpHiddenField(name, value, comma, forceHTML) {
1940 var t = typeof(value);
1942 value = encode_entities(value);
1943 } else if (t=='object') { // array
1945 var i_max = values.length;
1947 if (comma==null) comma = ',';
1948 for (var i=0; i<i_max; i++) {
1949 values[i] = trim(values[i]);
1950 if (values[i]!=null && values[i]!='') {
1951 value += (i==0 ? '' : comma) + encode_entities(values[i]);
1955 if (is_LMS() && !forceHTML) {
1956 if (value && value.indexOf && value.indexOf('<')>=0 && value.indexOf('>')>=0) {
1957 value = '<![CDATA[' + value + ']]>';
1959 field = '<field><fieldname>' + name + '</fieldname><fielddata>' + value + '</fielddata></field>';
1961 field = '<input type=hidden name="' + name + '" value="' + value + '">';
1966 if (s==null) s = '';
1969 while (i<ii && s.charAt(i)==' ') {
1972 while (ii>i && s.charAt(ii-1)==' ') {
1975 return s.substring(i, ii);
1977 function encode_entities(s_in) {
1978 var i_max = (s_in) ? s_in.length : 0;
1980 for (var i=0; i<i_max; i++) {
1981 var c = s_in.charCodeAt(i);
1982 // 34 : double quote .......["] &
1983 // 38 : single quote .......['] '
1984 // 43 : plus sign ..........[+]
1985 // 44 : comma ..............[,]
1986 // 60 : left angle bracket .[<] <
1987 // 62 : right angle bracket [>] >
1988 // >=128 multibyte character
1989 s_out += (c==43 || c==44 || c>=128) ? ('&#x' + pad(c.toString(16), 4) + ';') : s_in.charAt(i);
1993 // *********************
1996 // *********************
1997 function getTime(obj) {
1998 obj = obj ? obj : new Date();
1999 // get year, month and day
2000 // for an LMS : yyyy-mm-dd
2001 // for email : DayName MonthName dd yyyy
2003 obj.getFullYear() + '-' + pad(obj.getMonth()+1, 2) + '-' + pad(obj.getDate(), 2) :
2004 MSG[16][obj.getDay()] + ' ' + MSG[17][obj.getMonth()] + ' ' + pad(obj.getDate(), 2) + ' ' + obj.getFullYear()
2006 // get hours, minutes and seconds (hh:mm:ss)
2007 s += ' ' + pad(obj.getHours(), 2) + ':' + pad(obj.getMinutes(), 2) + ':' + pad(obj.getSeconds(), 2);
2008 // get time difference
2009 // for an LMS : +xxxx
2010 // for email : GMT+xxxx
2011 var x = obj.getTimezoneOffset(); // e.g. -540
2013 s += ' ' + (is_LMS() ? '' : 'GMT') + (x<0 ? '+' : '-');
2015 s += pad(parseInt(x/60), 2) + pad(x - (parseInt(x/60)*60), 2);
2019 function getFunc(fn) {
2020 if (typeof(fn)=='string') {
2021 fn = eval('window.' + fn);
2023 return (typeof(fn)=='function') ? fn : null;
2025 function getFuncCode(fn, extraCode, anchorCode, beforeAnchor) {
2027 var obj = getFunc(fn);
2030 var i1 = s.indexOf('{')+1;
2031 var i2 = s.lastIndexOf('}');
2032 if (i1>0 && i1<i2) {
2033 s = s.substring(i1, i2);
2039 s = replaceLast(anchorCode, extraCode + anchorCode, s);
2041 s = replaceLast(anchorCode, anchorCode + extraCode, s);
2053 function getArgsStr(args, addQuotes) {
2054 // make s(tring) version of function args array
2056 var i_max = args.length;
2057 for (var i=0; i<i_max; i++) {
2059 s += '"' + args[i] + '",';
2069 function getFuncArgs(fn, flag) {
2070 // flag==0 : return args as string
2071 // flag==1 ; return args as array
2073 var a = new Array();
2074 var obj = getFunc(fn);
2076 var s = obj.toString();
2077 var i1 = s.indexOf('(') + 1;
2078 var i2 = s.indexOf(')', i1);
2079 // add args to a(rray)
2080 while (i1>0 && i1<i2) {
2081 var i3 = s.indexOf(',', i1); // next comma
2082 if (i3<0 || i3>i2) i3 = i2;
2083 a[i++] = trim(s.substring(i1, i3));
2087 return flag ? a : getArgsStr(a);
2089 function getPrompt(fn) {
2090 // the LoginPrompt is the text string in the first prompt(...) statement
2091 // v5 : in the StartUp function
2092 // v6 : in the GetUserName function
2093 // Note: netscape uses double-quote as delimiter, others use single quote
2094 var s = getFuncCode(fn);
2095 var i1 = s.indexOf('prompt') + 8;
2096 var i2 = s.indexOf(s.charAt(i1-1), i1);
2097 var p = (i1>=8 && i2>i1) ? s.substring(i1, i2) : '';
2098 // make sure browser has decoded the unicode prompt properly
2099 // this check is mainly for ns4, but there may be others
2100 if (window.RegExp) {
2101 var r = new RegExp('u([0-9A-F]{4})');
2104 p = p.replace(m[0], '&#' + parseInt(m[1], 16) + ';');
2110 function getStartUpCode(fn) {
2111 // the main initialization code comes from the StartUp function
2112 // v5 : the code before "UserName", if any,
2113 // and the code after the 2nd subsequent '}'
2114 // v6 : the code before and after 'GetUserName();'
2115 // i.e. all the code except the call to 'GetUserName();'
2116 var s = getFuncCode(fn);
2117 var i1 = s.indexOf('GetUserName();');
2121 var i1 = s.indexOf('UserName');
2122 var i2 = s.indexOf('}', s.indexOf('}', i1+8)+1)+1;
2124 return (0<i1 && i1<i2) ? s.substring(0, i1) + s.substring(i2) : '';
2127 if (!window.hpCheckedForm) {
2128 window.hpCheckedForm = true;
2129 window.hpFoundForm = hpFindForm('store') ? true : false;
2133 function hpFeedback() {
2137 if (FEEDBACK[1] && FEEDBACK[2]) { // formmail
2138 html += '<html><body>'
2139 + '<form action="' + FEEDBACK[0] + '" method="POST">'
2140 + '<table border="0">'
2141 + '<tr><th valign="top" align="right">' + FEEDBACK[7] + ':</th><td>' + document.title + '</td></tr>'
2142 + '<tr><th valign="top" align="right">' + FEEDBACK[8] + ': </th><td>'
2144 if (typeof(FEEDBACK[1])=='string') {
2145 html += FEEDBACK[1] + hpHiddenField('recipient', FEEDBACK[1], ',', true);
2146 } else if (typeof(FEEDBACK[1])=='object') {
2147 var i_max = FEEDBACK[1].length;
2148 if (i_max==1) { // one teacher
2149 html += FEEDBACK[1][0][0] + hpHiddenField('recipient', FEEDBACK[1][0][0]+' <'+FEEDBACK[1][0][1]+'>', ',', true);
2150 } else if (i_max>1) { // several teachers
2151 html += '<select name="recipient">';
2152 for (var i=0; i<i_max; i++) {
2153 html += '<option value="'+FEEDBACK[1][i][1]+'">' + FEEDBACK[1][i][0] + '</option>';
2155 html += '</select>';
2158 html += '</td></tr>'
2159 + '<tr><th valign="top" align="right">' + FEEDBACK[9] + ':</th>'
2160 + '<td><TEXTAREA name="message" rows="10" cols="40"></TEXTAREA></td></tr>'
2161 + '<tr><td> </td><td><input type="submit" value="' + FEEDBACK[6] + '">'
2162 + hpHiddenField('realname', FEEDBACK[2], ',', true)
2163 + hpHiddenField('email', FEEDBACK[3], ',', true)
2164 + hpHiddenField('subject', document.title, ',', true)
2165 + hpHiddenField('title', document.title, ',', true)
2166 + hpHiddenField('return_link_title', FEEDBACK[10], ',', true)
2167 + hpHiddenField('return_link_url', 'javascript:self.close()', ',', true)
2168 + '</td></tr></table></form></body></html>'
2170 } else if (FEEDBACK[1]) { // url only
2171 if (typeof(FEEDBACK[1])=='object') {
2172 var i_max = FEEDBACK[1].length;
2173 if (i_max>1) { // several teachers
2174 html += '<html><body>'
2175 + '<form action="' + FEEDBACK[0] + '" method="POST" onsubmit="this.action+=this.recipient.options[this.recipient.selectedIndex].value">'
2176 + '<table border="0">'
2177 + '<tr><th valign="top" align="right">' + FEEDBACK[7] + ':</th><td>' + document.title + '</td></tr>'
2178 + '<tr><th valign="top" align="right">' + FEEDBACK[8] + ': </th><td>'
2180 html += '<select name="recipient">';
2181 for (var i=0; i<i_max; i++) {
2182 html += '<option value="'+FEEDBACK[1][i][1]+'">' + FEEDBACK[1][i][0] + '</option>';
2184 html += '</select>';
2185 html += '</td></tr>'
2186 + '<tr><td> </td><td><input type="submit" value="' + FEEDBACK[6] + '">'
2187 + '</td></tr></table></form></body></html>'
2189 } else if (i_max==1) { // one teacher
2190 url = FEEDBACK[0] + FEEDBACK[1][0][1];
2192 } else if (typeof(FEEDBACK[1])=='string') {
2193 url = FEEDBACK[0] + FEEDBACK[1];
2199 var w = openWindow(url, 'feedback', FEEDBACK[4], FEEDBACK[5], 'RESIZABLE,SCROLLBARS', html);
2201 // unable to open popup window
2207 // ********************
2209 // ********************
2210 function hpNewFunction(f, a, s) {
2211 if (window.C && C.safari) {
2212 if (f=='CheckAnswers') {
2213 if (s.indexOf('TotalChars-State[i].HintsAndChecks/')>=0) {
2214 // special fix for "CheckAnswers" in JCloze
2215 s = s.replace(/TotalChars-State\[i\]\.HintsAndChecks/g, '(TotalChars-State[i].HintsAndChecks)');
2217 if (s.indexOf('TotalChars-GapList[x][1].HintsAndChecks/')>=0) {
2218 // special fix for "CheckAnswers" in JCloze (Find-It)
2219 s = s.replace(/TotalChars-GapList\[x\]\[1\]\.HintsAndChecks/g, '(TotalChars-GapList[x][1].HintsAndChecks)');
2221 if (s.indexOf('CorrectLetters-Penalties/')>=0) {
2222 // special fix for "CheckAnswers" in JMatch
2223 s = s.replace(/CorrectLetters-Penalties/g, '(CorrectLetters-Penalties)');
2225 if (s.indexOf('TotCorrectChoices-Penalties/')>=0) {
2226 // special fix for "CheckAnswers" in JMix (v6)
2227 s = s.replace(/TotCorrectChoices-Penalties/g, '(TotCorrectChoices-Penalties)');
2229 if (s.indexOf('TotalCorrect-Penalties/')>=0) {
2230 // special fix for "CheckAnswers" in JMix (v6+) Drag-and_Drop
2231 s = s.replace(/TotalCorrect-Penalties/g, '(TotalCorrect-Penalties)');
2234 if (s.indexOf('replace(\\[')>=0) {
2235 s = s.replace(/\\\[/g, '/\\[');
2236 s = s.replace(/\\\]/g, '\\]/g');
2238 if (s.indexOf('for (i')>=0 || s.indexOf('for (x')>=0) {
2239 s = s.replace(/for \(/g, 'for (var ');
2241 eval('window.' + f + '=function(' + getArgsStr(a) + '){' + s + '}');
2243 eval('window.' + f + '=new Function(' + getArgsStr(a, true) + 's);');
2246 function hpInterceptFeedback() {
2247 // modify the function which writes feedback
2248 // v6: ShowMessage(Feedback)
2249 // but Rhubarb prints score in other functions, so use 'CheckFinished'
2250 // v5: WriteFeedback(Feedback)
2251 // v4: WriteFeedback(Stuff)
2252 // v3: WriteFeedback(Feedback) [except JMatch]
2253 // v3: CheckAnswer() [JMatch only]
2255 if (window.CheckWord) { // Rhubarb
2256 f = 'CheckFinished';
2257 window.FEEDBACK = null;
2258 } else if (window.ShowText) { // Sequitur
2260 window.FEEDBACK = null;
2261 } else { // JBC, JCloze, JCross, JMatch, JMix, JQuiz
2262 f = window.ShowMessage ? 'ShowMessage' : window.WriteFeedback ? 'WriteFeedback' : 'CheckAnswer';
2265 var s = getFuncCode(f) + 'Finish();';
2266 var a = getFuncArgs(f, true);
2267 if (a[0] && window.FEEDBACK && FEEDBACK[0]) {
2268 s = a[0] + "+='<br /><br />" + '<a href="javascript:hpFeedback();">' + FEEDBACK[6] + "</A>';" + s;
2270 hpNewFunction(f, a, s);
2273 function hpInterceptHints() {
2274 // modify the function which shows hints
2276 // JCloze v3-v6: ShowHint()
2277 // JCross v3: Cheat(), v4: ShowHint(), v5-v6[HP5]: ShowHint(Across,x,y,BoxName), v6[HP6]: ShowHint(Across,ClueNum,x,y,BoxId)
2279 // JMix v5-v6: CheckAnswer(CheckType=1)
2280 // JQuiz v3: CheckAnswer(ShowHint=true, QNum), v4: CheckAnswer(ShowHint=true), v5-v6[HP5]: CheckAnswer(ShowHint=true,QNum), v6[HP6]: ShowHint(QNum)
2281 var x = ''; // extra code, if any
2284 } else if (window.ShowHint) {
2286 var a = getFuncArgs(f, true);
2288 if (window.FindCurrent) {
2290 x = 'var q=window.Locked?-1:FindCurrent();if(q>=0&&GetHint(q))hpClick(1,q);';
2293 // work out which box would have a hint added
2294 // work out which question that box is part of using GridMap and WinLetters
2296 } else if (a[0]=='Across') {
2297 if (a[1]=='ClueNum') {
2299 x = "var args=new Array(ClueNum,Across?'A':'D');hpClick(1,args);";
2300 } else if (a[1]=='x' && a[2]=='y') {
2301 // JCross v5-v6 [HP5]
2302 x = "var args=new Array(C[x][y],Across?'A':'D');hpClick(1,args);";
2304 } else if (a[0]=='QNum') {
2306 x = 'hpClick(1,QNum);';
2308 } else if (window.Hint) {
2311 var a = getFuncArgs(f, true);
2312 x = 'hpClick(1,0);'; // question number is always zero
2314 } else if (window.CheckAnswer) {
2315 var f = 'CheckAnswer';
2316 var a = getFuncArgs(f, true);
2317 if (a[0]=='ShowHint') {
2319 // JQuiz v3, v5-v6[HP5]
2320 x = 'if(ShowHint)hpClick(1,QNum);';
2323 x = 'if(ShowHint)hpClick(1,QNum-1);'; // QNum is a global variable
2325 } else if (a[0]=='CheckType') {
2327 x = 'if(CheckType==1)hpClick(1,0);'; // question number is always 0;
2330 // add the e(x)tra code, if any, to the start of the hint (f)unction
2332 var s = getFuncCode(f, x, '', true);
2333 hpNewFunction(f, a, s);
2336 function hpInterceptClues() {
2337 // modify the function which shows clues (or ShowAnswers in JQuiz)
2339 // JCloze v3-v6: ShowClue(ItemNum)
2340 // JCross v3-v4: ShowClue(ClueNum), v5-v6: ShowClue(ClueNum,x,y)
2343 // JQuiz ShowAnswers(QNum)
2344 var x = ''; // extra code, if any
2345 if (window.ShowClue) {
2347 var a = getFuncArgs(f, true);
2348 if (a[0]=='ItemNum') {
2350 x = 'if(!window.Locked)hpClick(2,ItemNum);'; // v6 [HP6] uses window.Locked
2351 } else if (a[0]=='ClueNum') {
2352 if (a[1]=='x' && a[2]=='y') {
2353 if (window.A && window.D) {
2354 // JCross v5-v6 [HP5]
2355 x = 'if(A[ClueNum]||D[ClueNum])hpClick(2,ClueNum);';
2356 } else if (document.getElementById) {
2358 x = "if(document.getElementById('clue_' + ClueNum)||document.getElementById('Clue_D_' + ClueNum))hpClick(2,ClueNum);";
2361 if (window.AClues && window.DClues) {
2363 x = 'if(AClues[ClueNum]||DClues[ClueNum])hpClick(2,ClueNum);';
2368 // JQuiz: there is no "ShowClue" function but there is a "ShowAnswer" function
2369 if (window.ShowAnswers) {
2370 var f = 'ShowAnswers';
2371 var a = getFuncArgs(f, true);
2373 if (window.ShowMessage) {
2375 x = 'if(State[QNum][0]<1)hpClick(2,QNum);';
2376 } else if (window.WriteFeedback) {
2378 x = 'if(State[QNum-1][0]<1)hpClick(2,QNum-1);';
2380 } else if (window.Status) {
2381 // JQuiz v5-v6 [HP5]
2382 x = 'if(Status[QNum][0]<0)hpClick(5,QNum);';
2385 // add the e(x)tra code, if any, to the start of the clue (f)unction
2387 var s = getFuncCode(f, x, '', true);
2388 var s = getFuncCode(f, '', '', true);
2389 hpNewFunction(f, a, s);
2392 function hpInterceptChecks() {
2393 // modify the function which handles checks
2395 // JCloze CheckAnswers()
2396 // NB: Rottmeier Find-It 3a: CheckText(GapState,GapId)
2398 // JMatch HP5 v3, v5, v6: CheckAnswer(), HP5 v4: CheckResults(), HP6: CheckAnswers()
2399 // JMix CheckAnswer(CheckType)
2401 // HP5: CheckAnswer(ShowHint, QNum)
2402 // HP6: CheckMCAnswer, CheckMultiSelAnswer, CheckShortAnswer
2403 // Rhubarb CheckWord(InputWord)
2404 // Sequitur CheckAnswer(Chosen, Btn)
2405 // HP6 JQuiz has three "Check Answer" functions
2406 var f = new Array('CheckMCAnswer', 'CheckMultiSelAnswer', 'CheckShortAnswer');
2407 for (var i=0; i<f.length; i++) {
2408 if (eval('window.' + f[i])) {
2409 var a = getFuncArgs(f[i], true);
2411 if (f[i]=='CheckMCAnswer') {
2412 x += "var args=new Array(QNum,I[QNum][3][ANum][0]);";
2413 } else if (f[i]=='CheckShortAnswer') {
2415 + "var obj=document.getElementById('Q_'+QNum+'_Guess');"
2416 + "var args=new Array(QNum,obj.value);"
2418 } else if (f[i]=='CheckMultiSelAnswer') {
2421 + "for (var ANum=0; ANum<I[QNum][3].length; ANum++){"
2422 + "var obj=document.getElementById('Q_'+QNum+'_'+ANum+'_Chk');"
2423 + "if (obj.checked)g+=(g?'+':'')+I[QNum][3][ANum][0];"
2425 + "var args=new Array(QNum,g);"
2429 x = "if(!Finished&&State[QNum].length&&State[QNum][0]<0){" + x + "hpClick(3,args)}";
2430 var s = getFuncCode(f[i], x, '', true);
2431 hpNewFunction(f[i], a, s);
2435 var f = ''; // function name
2436 var x = ''; // extra code, if any
2437 if (window.CheckAnswer) {
2439 var a = getFuncArgs(f, true);
2440 if (a[0]=='ShowHint') {
2442 // JQuiz v3, v5-v6[HP5]
2443 x = 'if(!ShowHint&&Status[QNum][0]<1)hpClick(3,QNum);';
2446 x = 'if(!ShowHint&&State[QNum-1][0]<1)hpClick(3,QNum-1);'; // QNum is a global variable
2448 } else if (a[0]=='CheckType') {
2450 x = 'if(CheckType==0)hpClick(3,0);'; // question number is always 0;
2451 } else if (a[0]=='Chosen') {
2453 x = 'if (!(CurrentNumber==TotalSegments||AllDone||Btn.innerHTML==IncorrectIndicator))hpClick(3,Chosen);';
2455 } else if (window.CheckWord) {
2457 var a = getFuncArgs(f, true);
2458 if (a[0]=='InputWord') {
2460 x = 'if(!window.AllDone)hpClick(3,InputWord);';
2462 } else if (window.CheckText && !window.CheckAnswers) { // Rottmeier Find-It (3a)
2464 var a = getFuncArgs(f, true);
2465 if ((a[0]=='bool' && a[1]=='item') || (a[0]=='GapState' && a[1]=='GapId')) {
2466 x = 'if(!window.Finished&&'+a[0]+')hpClick(3,'+a[1]+');';
2470 var s = getFuncCode(f, x, '', true);
2471 hpNewFunction(f, a, s);
2473 // JMatch has three possible check functions, depending on the version
2474 // (NB: other quiz types also have these functions)
2475 var f = new Array('CheckAnswers', 'CheckAnswer', 'CheckResults');
2476 for (var i=0; i<f.length; i++) {
2477 if (eval('window.' + f[i])) {
2478 var a = getFuncArgs(f[i], true);
2480 var s = getFuncCode(f[i], "hpClick(3);", '', true);
2481 hpNewFunction(f[i], a, s);
2482 break; // out of the loop
2490 // add Array.push if required (allows v6 quizzes to run on ie5win)
2491 if (Array.prototype && Array.prototype.push==null) {
2492 Array.prototype.push = new Function("x", "this[this.length]=x");
2494 // add attachEvent function, if required (allows HP5 v6 quizzes to run on ie5mac)
2495 // NOTE: to allow v6 quizzes on ie5mac, the following code
2496 // needs to be inserted BEFORE the Hot Potatoes javascript
2497 if (window.attachEvent==null) {
2498 window.attachEvent = new Function('evt', 'fn', 'eval("window."+evt+"="+fn)');
2500 if (document.attachEvent==null) {
2501 document.attachEvent = new Function('evt', 'fn', 'eval("document."+evt+"="+fn)');
2503 // fix the ShowMessage function for NS6
2504 // by removing calls to a button's "focus()" method
2505 if (navigator.userAgent.indexOf("Netscape6")>=0 && window.ShowMessage) {
2506 var s = ShowMessage.toString();
2507 var r = new RegExp('document\\.getElementById\\((\'|")FeedbackOKButton(\'|")\\)\\.focus\\(\\);', 'gi');
2508 s = s.substring(s.indexOf('{')+1, s.lastIndexOf('}')).replace(r, '');
2509 window.ShowMessage = new Function('Feedback', s);
2511 // ns6.0 (in JMix at least) has an error in the FocusAButton function too
2512 // this could be fixed as follows ...
2513 //if (window.FocusAButton) {
2514 // window.FocusAButton = new Function('return true');
2516 // however, ns6.0 then crashes completely when the mouse moves over a link, so don't bother
2517 // Hot Potatoes quiz sniffing
2519 // JBC uses "QuizForm", which contains elements called "Q*_**" (* and ** start at 1)
2520 // JCloze uses "Cloze" form
2521 // JCross uses "Crossword" form
2522 // JMatch uses "QuizForm", which contains elements called "1,2,3..x" and "x+1,x+2...",
2523 // and "CheckForm" form, which contains an element called "ScoreBox"
2524 // it is also the only HP quiz type to use an array called "CorrectAnswers"
2525 // JQuiz uses "QForm*" forms (* starts at 1), which each contain an element called "Guess"
2527 // JBC uses "QForm" form in "QuestionDiv", which contains elements called "FB* (* starts at 0)"
2528 // JCloze uses "Cloze" form in "QuestionDiv"
2529 // JCross uses "Crossword" form in "CWDiv"
2530 // JMatch uses "ExCheck" form in "TitleDiv"
2532 // JQuiz uses "QForm" form in "QuestionDiv", which contains an element called "Answer"
2534 // JBC uses "QForm" form, which contains elements called "FB_*_**" (* and ** start at 0)
2535 // JCloze uses "Cloze" form
2536 // JCross writes out "AnswerForm" from a variable called "GetAnswerOpener"
2537 // HP5.3: uses "AnswerForm" in "BottomFrame"
2538 // HP5.5: uses "AnswerForm" in "TopFrame", but it is only there when an answer is being input
2539 // JMatch uses "QForm" form, which contains elements called "sel*" (which disappear by the time the quiz is finished)
2540 // JMix uses "ButtonForm"
2541 // JQuiz uses "QForm*" and "Buttons*" (one for each question)
2543 // JBC uses "QForm" form (elements have no name or id)
2544 // JCloze uses "Cloze" form (elements have no name or id)
2545 // JCross does not use any forms,
2546 // HP5: has "GridDiv" in "MainDiv"
2547 // HP6: has "Clues" table in "MainDiv"
2548 // JMatch has "MatchDiv" in "MainDiv"
2549 // HP5: uses "QForm" form, which contains elements called "sel*"
2550 // HP6: uses "QForm" form, which contains elements called "s*_**"
2551 // JMix does not use any forms, but has "SegmentDiv" in "MainDiv"
2553 // HP5: uses "QForm" form, which contains an element called "Guess"
2554 // HP6: has "Questions" ordered list in "MainDiv"
2556 // JMatch has DIVs called "D*" and "F*" (* starts at 0)
2557 // JMix has DIVs called "D*" and "Drop*" (* starts at 0)
2558 // useful sniffing tools (Cut and Paste to browser address box)
2559 //javascript:var s="";var x=new quiz_obj();for(X in x)s+=","+X+"="+x[X];alert(s.substring(1));
2560 //javascript:var s="";var x=document.layers;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
2561 //javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
2562 //javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
2563 //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))
2564 //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))
2565 function hpDetectQuiz() {
2566 // "sniff" (=detect) the quiz's type and intended browser version
2567 // and cache the values in a global variable called "quiz"
2568 // Hot Potatoes version
2569 // 5 : HP5.3 (mac) or HP5.5 (win)
2570 // 6 : HP6.0 (mac) or HP6.0 (win)
2571 // intended browser version
2572 // 3 : ns3, ie3 (frames)
2573 // 4 : ns4, ie4 (cross browser dhtml)
2574 // 5 : ie5 (frames, send results via CGI)
2575 // 6 : ie6, op7, gecko (w3 standards)
2576 // 6.1 : "drag and drop" versions of JMatch and JMix v6
2585 // 7 : rhubarb (TexToys)
2586 // 8 : Sequitur (TexToys)
2587 // rottmeier quiz type
2588 // 1 : drop-down (JCloze)
2589 // 2 : find-it (JCloze)
2590 // shortcut to window object
2592 // create the global "quiz" object, if necessary
2593 if (!w.quiz) w.quiz = new Object();
2594 // Hot Potatoes version
2596 // HP5 v4-v5: BrowserCheck()
2597 // v3: WinStringToMac() [JCloze, JCross, JQuiz]
2598 // v3: winrightchar [JBC, JMatch]
2599 // v3: DownTime() [JBC, JCloze, JQuiz]
2601 quiz.hp = (w.Client) ? 6 : (w.BrowserCheck) ? 5 : (w.WinStringToMac || w.winrightchar) ? 5 : -1;
2603 // check the version and type are not already set
2604 if (!quiz.v || !quiz.t) {
2605 // initialize version and type
2608 // set shortcuts to DOM objects
2611 if (f.QuizForm && f.CheckForm && w.CorrectAnswers) {
2614 } else if (w.FeedbackFrame && w.CodeFrame) {
2616 f = CodeFrame.document.forms;
2617 t = (f.QuizForm) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.QForm1) ? 6 : 0;
2618 } else if (w.DynLayer) {
2621 // for NS4, adjust "f" to point to a forms object in a layer
2622 var lyr = d.QuestionDiv || d.CWDiv || d.TitleDiv || null;
2623 if (lyr) f = lyr.document.forms;
2625 t = (f.QForm && f.QForm.FB0) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.ExCheck) ? 4 : (f.QForm && f.QForm.Answer) ? 6 : 0;
2626 } else if (w.TopFrame && w.BottomFrame) {
2628 f = BottomFrame.document.forms;
2629 t = (f.QForm && f.QForm.elements[0].name.substring(0,3)=='FB_') ? 1 : (f.Cloze) ? 2 : (w.GetAnswerOpener && GetAnswerOpener.indexOf('AnswerForm')>=0) ? 3 : (f.QForm && w.RItems) ? 4 : (f.ButtonForm) ? 5 : (f.QForm0 && f.Buttons0) ? 6 : 0;
2630 } else if (hpObj(d, 'MainDiv')) {
2632 var obj = (f.QForm) ? f.QForm.elements : null;
2633 t = (obj && obj.length>0 && obj[0].id=='') ? 1 : (f.Cloze) ? 2 : (hpObj(d, 'GridDiv') || hpObj(d, 'Clues')) ? 3 : hpObj(d, 'MatchDiv') ? 4 : hpObj(d, 'SegmentDiv') ? 5 : ((f.QForm && f.QForm.Guess) || hpObj(d, 'Questions')) ? 6 : 0;
2634 } else if (hpObj(d, 'D0')) {
2635 v = 6.1; // drag and drop (HP5 and HP6)
2636 t = (hpObj(d, 'F0')) ? 4 : (hpObj(d, 'Drop0')) ? 5 : 0;
2637 } else if (w.Words && f.Rhubarb) {
2639 t = 7; // rhubarb (TexToys)
2640 } else if (w.Segments && hpObj(d, 'Story')) {
2642 t = 8; // sequitur (TexToys)
2644 quiz.v = v; // intended browser version
2645 quiz.t = t; // quiz type
2648 function hpRottmeier() {
2650 if (typeof(quiz.r)=='undefined') { // first-time only
2652 if (quiz.t==2) { // JCloze
2653 if (quiz.hp==5) { // HP5
2655 } else if (quiz.hp==6) { // HP6
2656 if (window.Create_StateArray) { // Rottmeier
2657 var obj = new Create_StateArray();
2658 if (typeof(obj.GapLocked)=='boolean') {
2659 quiz.r = 1; // drop-down (v2.4)
2660 } else if (typeof(obj.ErrorFound)=='boolean') {
2661 if (typeof(obj.GapSolved)!='boolean') {
2662 quiz.r = 2.1; // find-it (v3.1a)
2664 quiz.r = 2.2; // find-it (v3.1b)
2667 obj = null; // prevents memory leakage on some versions of IE
2674 function hpVersion() {
2678 function hpQuizType() {
2682 function hpQuizVersion() {
2686 function hpScoreEngine(score_i, a, s, aa, ss, count_c, count_i) {
2687 // calculate the score for the quiz so far
2688 // score_i : amount by which to increment "score"
2690 // s : condition, if any, on outer array (=a)
2691 // if true, the score will be incremented by "score_i"
2692 // aa : inner array, if any
2693 // ss : condition, if any, on inner array (=aa)
2694 // count_c : condition, if any, on which "count" is to be incremented
2695 // count_i : amount by which to increment "count"
2696 // "a" and "aa" may be passed as arrays or strings containing the name of an array
2697 // "s" and "ss" are strings containing an expression to be eval(uated)
2698 // "score_i", "count_i" and "count_c" strings containing an expression to be eval(uated)
2701 // set default condition to increment "count", and amount by which to increment the count
2702 if (count_c==null) count_c = "true";
2703 if (count_i==null) count_i = "1";
2704 // set length of outer array. if any
2705 var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
2706 // loop through outer array
2707 for (var i=0; i<l; i++) {
2708 if (s==null && ss==null) {
2709 score += eval(score_i);
2710 if (eval(count_c)) count += eval(count_i);
2712 score += eval(s) ? eval(score_i) : 0;
2713 if (eval(count_c)) count += eval(count_i);
2715 // set length of inner array, if any
2716 var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
2717 // loop through inner array. checking inner condition
2718 for (var ii=0; ii<ll; ii++) {
2719 score += eval(ss) ? eval(score_i) : 0;
2720 if (eval(count_c)) count += eval(count_i);
2725 // get p(enalties) for JCross and JMatch (and JMix ?)
2726 if (window.Penalties) {
2727 score -= (Penalties - (hpFinished() ? 0 : 1));
2729 // adjust count for Find-It 3a and 3b
2730 if (window.TotWrongChoices) {
2731 if (window.CheckText && !window.CheckAnswers) { // Find-It 3a
2732 // this seems a little odd, but will replicate behavior of CalculateScore()
2733 count = score + TotWrongChoices;
2735 count += TotWrongChoices;
2738 score = Math.floor(100*score/count);
2739 if (score<0) { // just in case
2745 function hpScore() {
2746 var x = ''; // score
2747 var hp = hpVersion();
2748 var t = hpQuizType();
2749 var v = hpQuizVersion();
2751 if (v==3) x = hpScoreEngine(1, DoneStatus, "i>0 && a[i]=='0'"); // doesn't work
2752 else if (v==4) x = hpScoreEngine(1, DoneStatus, "a[i]==0"); // doesn't work
2753 else if (v==5 || v==6) x = hpScoreEngine("a[i][3]", Status, "a[i][3]");
2754 } else if (t==2) { // jcloze
2755 if (v==3 || v==4) x = hpScoreEngine("a[i]", Scores);
2756 else if (hp==5) x = hpScoreEngine("a[i][3]", State); // v==5 && v==6
2758 var r = hpRottmeier();
2759 if (r==0) x = x = hpScoreEngine("a[i].ItemScore", State);
2760 else if (r==1) x = hpScoreEngine("a[i][1].Score", GapList, "a[i][1].GapLocked"); // dropdown
2761 else if (r==2.1) x = hpScoreEngine(1, GapList, "a[i][1].ErrorFound"); // Find-It 3a
2762 else if (r==2.2) x = hpScoreEngine("a[i][1].Score", GapList, "a[i][1].GapSolved"); // Find-It 3b
2764 } else if (t==3) { // jcross
2765 if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
2766 else if (v==4) x = hpScoreEngine(1, WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0)==a[i].charAt(0)");
2767 else if (v==5 || v==6) {
2768 if (window.CaseSensitive) { // HP 6.2
2769 x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii]==G[i][ii]", "L[i][ii]");
2771 x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii].toUpperCase()==G[i][ii].toUpperCase()", "L[i][ii]");
2774 } else if (t==4) { // jmatch
2775 if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
2776 else if (v==4) x = hpScoreEngine(1, Draggables, "a[i].correct=='1'");
2777 else if (v==5) x = hpScoreEngine(1, I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]==1");
2778 else if (v==6) x = hpScoreEngine(1, Status, "Status[i][0]==1");
2779 else if (v==5.1 || v==6.1) x = hpScoreEngine(1, D, "D[i][2]==D[i][1] && D[i][2]>0", "", "", "i<F.length");
2780 } else if (t==5) { // jmix
2781 // there was no v3 or v4 of JMix
2782 if (v==5 || v==6 || v==6.1) x = Math.floor(100*(Segments.length-Penalties)/Segments.length);
2783 } else if (t==6) { // jquiz
2785 if (v==3 || v==4) x = hpScoreEngine("a[i][4]/10", State, "a[i][0]==1");
2786 else if (v==5 || v==6) x = hpScoreEngine("a[i][4]/10", Status, "a[i][0]==1", "", "", "true", "1");
2788 if (v==6) x = hpScoreEngine("I[i][0]*a[i][0]", State, "a[i]&&a[i][0]>=0", "", "", "a[i]", "I[i][0]");
2790 } else if (t==7) { // rhubarb
2792 x = Math.floor(100*Correct/TotalWords);
2794 } else if (t==8) { // sequitur
2796 var myTotalPoints = TotalPoints - (hpFinished() ? 0 : (OptionsThisQ-1));
2797 x = Math.floor(100*ScoredPoints/myTotalPoints);
2802 function hpFinishedEngine(a, s, aa, ss) {
2803 // determine whether or not all quistions in a quiz are finished
2805 // s : condition, if any, on outer array
2806 // if true for any element in "a", the quiz is NOT finished
2807 // aa : inner array, if any
2808 // ss : condition, if any, on inner array
2809 // if true for any element in "aa", the quiz is NOT finished
2810 // the arrays "a" and "aa" may be passed as arrays or strings to be eval(uated)
2811 // the conditions "s" and "ss" are specified as strings to be eval(uated)
2812 // assume a positive result
2814 // set length of outer array. if any
2815 var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
2816 // loop through outer array
2817 for (var i=0; i<l; i++) {
2818 // do outer condition, if any
2819 if (s && eval(s)) x = false;
2820 // set length of inner array, if any
2821 var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
2822 // loop through inner array. checking inner condition
2823 for (var ii=0; ii<ll; ii++) {
2824 if (ss && eval(ss)) x = false;
2829 function hpTimedOut() {
2830 // v5 uses "min" and "sec"
2832 return (typeof(self.Seconds)=='number' && Seconds==0) || (typeof(self.min)=='number' && min==0 && typeof(self.sec)=='number' && sec==0);
2834 function hpFinished() {
2835 // assume false result
2837 var hp = hpVersion();
2838 var t = hpQuizType();
2839 var v = hpQuizVersion();
2841 if (v==3) x = hpFinishedEngine(DoneStatus, "i>0 && a[i]=='0'");
2842 else if (v==4) x = hpFinishedEngine(DoneStatus, "a[i]==0");
2843 else if (v==5 || v==6) x = hpFinishedEngine(Status, "a[i][0]==0");
2844 } else if (t==2) { // jcloze
2845 var r = hpRottmeier();
2846 if (r==1) x = hpFinishedEngine(GapList, "a[i][1].GapLocked==false"); // drop-down
2847 else if (r==2.1) x = hpFinishedEngine(GapList, "a[i][1].ErrorFound==false"); // find-it 3a
2848 else if (r==2.2) x = hpFinishedEngine(GapList, "a[i][1].GapSolved==false"); // find-it 3b
2849 else if (v==3 || v==4 || v==5 || v==6) x = hpFinishedEngine(I, "CheckAnswer(i)==-1");
2850 // also: else if (v==5 || v==6) x = hpFinishedEngine(State, "a[i][4]!=1")
2851 } else if (t==3) { // jcross
2852 if (v==3) x = hpFinishedEngine(document.Crossword.elements, "ConvertCase(is.mac?unescape(MacStringToWin(a[i].value)):a[i].value,1)!=Letters[i]");
2853 else if (v==4) x = hpFinishedEngine(WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0) != a[i].charAt(0)");
2854 else if (v==5 || v==6) {
2855 if (window.CaseSensitive) { // 6.2
2856 x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii]!=G[i][ii]");
2858 x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii].toUpperCase()!=G[i][ii].toUpperCase()");
2861 } else if (t==4) { // jmatch
2862 if (v==3) x = hpFinishedEngine(CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex != a[i]");
2863 else if (v==4) x = hpFinishedEngine(Draggables, "a[i].correct!='1'");
2864 else if (v==5) x = hpFinishedEngine(I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]<1 && GetAnswer(i)!=I[i][3]");
2865 else if (v==6) x = hpFinishedEngine(Status, "Status[i][0]<1");
2866 else if (v==5.1 || v==6.1) x = hpFinishedEngine(D, "", F, "F[ii][1]==D[i][1]&&D[i][1]!=D[i][2]");
2867 } else if (t==5) { // jmix
2868 // there was no v3 or v4 of JMix
2869 if (v==5 || v==6 || v==6.1) x = !hpFinishedEngine(Answers, "a[i].join(',')=='" + GuessSequence.join(',') + "'");
2870 } else if (t==6) { // jquiz
2871 if (v==3 || v==4) x = hpFinishedEngine(State, "a[i][0]==0");
2872 else if (v==5 || v==6) {
2873 if (hp==5) x = hpFinishedEngine(Status, "a[i][0]<1");
2874 else if (hp==6) x = hpFinishedEngine(State, "a[i] && a[i][0]<0");
2876 } else if (t==7) { // rhubarb
2877 if (v==6) x = hpFinishedEngine(DoneList, "a[i]==1");
2878 } else if (t==8) { // sequitur
2879 if (v==6) x = (CurrentNumber==TotalSegments || AllDone);
2883 function hpObj(d, id) {
2884 return d.getElementById ? d.getElementById(id) : d.all ? d.all[id] : d[id];
2886 function GetViewportHeight() {
2887 if (window.innerHeight) {
2891 return document.documentElement.clientHeight;
2893 return document.body.clientHeight;
2897 function hpIsStrict() {
2898 if (!window.hpStrictIsSet) {
2899 window.hpStrictIsSet = true;
2900 window.hpStrict = false;
2901 var s = document.compatMode;
2902 if (s && s=="CSS1Compat") { // ie6
2903 window.hpStrict = true;
2905 var obj = document.doctype;
2907 var s = obj.systemId || obj.name; // n6 || ie5mac
2908 if (s && s.indexOf("strict.dtd") >= 0) {
2909 window.hpStrict = true;
2914 return window.hpStrict;
2919 //hpInterceptFeedback();
2920 //hpInterceptHints();
2921 //hpInterceptClues();
2922 hpInterceptChecks();
2923 function hpFindForm(formname, w) {
2924 if (w==null) w = self;
2925 var f = w.document.forms[formname];
2926 if (f==null && w.frames) {
2927 for (var i=0; i<w.frames.length; i++) {
2928 f = hpFindForm(formname, w.frames[i]);
2934 function Finish(quizstatus) {
2935 var mark = hpScore();
2936 window.hpForm = hpFindForm('store');
2937 if (hpForm) { // LMS
2938 hpForm.starttime.value = getTime(Start_Time);
2939 hpForm.endtime.value = getTime();
2940 hpForm.mark.value = mark;
2941 hpForm.detail.value = '<?xml version="1.0"?><hpjsresult><fields>'+GetQuestionDetails()+'</fields></hpjsresult>';
2942 if (hpForm.status) {
2944 // 4=completed, 3=abandoned, 2=timed-out or 1=in-progress
2945 quizstatus = hpFinished() ? 4 : hpTimedOut() ? 2 : 1;
2947 hpForm.status.value = quizstatus;
2949 if (!window.hpQuizResultsSent) {
2950 if (hpForm.status && quizstatus==4) {
2951 window.hpQuizResultsSent = true;
2953 if (quizstatus==4) { // completed
2954 // wait 2 seconds for student to see feedback
2955 setTimeout("hpForm.submit();", 2000);
2960 } else if (hpFinished()) {
2961 SendAllResults(mark);
2964 // create form to send results
2965 if (DB[7] && DB[8] && !is_LMS()) {
2968 + '<form name="Results" action="" method="post" enctype="x-www-form-encoded">'
2969 + hpHiddenField('recipient', '')
2970 + hpHiddenField('subject', '')
2971 + hpHiddenField('Exercise', '')
2972 + hpHiddenField('realname', '')
2973 + hpHiddenField('Score', '')
2974 + hpHiddenField('Start_Time', '')
2975 + hpHiddenField('End_Time', '')
2976 + hpHiddenField('title', 'Thanks!')
2981 // reassign the StartUp function
2982 var p = getPrompt(window.GetUserName || window.StartUp);
2983 var c = getStartUpCode(window.StartUp);
2985 if (window.C && C.safari) {
2986 eval('window.StartUp=function(){QuizLogin("' + p + '")}');
2987 eval('window.StartQuiz=function(){' + c + '}');
2989 window.StartUp = new Function('QuizLogin("' + p + '")');
2990 window.StartQuiz = new Function(c);
2992 // "QuizLogin" finshes by calling "StartQuiz"
2994 // reassign the SendResults function
2995 window.SendResults = SendAllResults;
2997 var Start_Time = new Date();