Merged from HEAD
[moodle.git] / mod / hotpot / hotpot-full.js
blob9f7ed8a07839257d3d4e73bfb1c3486a8f17eb0d
1 <!--\r
2 // PLEASE NOTE that this version is more recent than the incorrectly \r
3 // numbered v6.1, dated 2003.11.17. From now on, version numbers will \r
4 // follow those of Hot Potatoes.\r
5 \r
6 /* hot-potatoes.js (v6.0.4.0 - 2005.02.18)\r
7  * =======================================\r
8  * by Gordon Bateson, February 2003\r
9  * Copyright (c) 2003 Gordon Bateson. All Rights Reserved.\r
10  *\r
11  * You are hereby granted a royalty free license to use or modify this \r
12  * software provided that this copyright notice appears on all copies. \r
13  *\r
14  * This software is provided "AS IS" without a warranty of any kind.\r
15  * \r
16  * Documentation and downloads may be available from: \r
17  * http://www.kanazawa-gu.ac.jp/~gordon/research/hot-potatoes/\r
18  */\r
20 // This JavaScript library modifies the SendResults and StartUp functions \r
21 // used by hotpot v5 and v6, so that more (or less!) details about the\r
22 // student can be input, and more details of a quiz's questions and answers \r
23 // can be submitted to the server when the quiz is finished\r
25 // If the arrays below (Login, DB, JBC, ...) are set BEFORE calling this \r
26 // script, they will NOT be overwritten. Any array that is not set, will \r
27 // use the defaults below. This is useful if you want to use different \r
28 // settings for different quizzes.\r
30 // ************\r
31 //  Login Screen\r
32 // ************\r
34 if (window.Login==null) {\r
35         Login = new Array();\r
36         Login[0] = true;        // Show prompt for user name\r
37                                 // This can also be a string of user names ... \r
38                                 // Login[0] = "Guest,Peter,Paul,Mary,Webmaster";\r
39                                 // or an array of user names (and on-screen texts) (and passwords) ...\r
40                                 // Login[0] = new Array("Guest", "001,Peter,xxxx", "002,Paul,yyyy", "003,Mary,zzzz", "Webmaster");\r
41                                 // and can also be  written as ...\r
42                                 // Login[0] = new Array(\r
43                                 //      new Array("Guest"),\r
44                                 //      new Array("001", "Peter", "xxxx"),\r
45                                 //      new Array("002", "Paul", "yyyy"),\r
46                                 //      new Array("003", "Mary", "zzzz"),\r
47                                 //      new Array("Webmaster")\r
48                                 // );\r
49         Login[1] = true;        // Show prompt for student's UserID\r
50                                 // If there is no password prompt (i.e. Logon[3] is false), this value \r
51                                 // will be checked against the password information, if any, in Login[0]\r
52         Login[2] = false;       // Show prompt for student's email\r
53         Login[3] = false;       // Show prompt for quiz password, and check this value against \r
54                                 // the password information, if any, in Login[0]\r
55                                 // This can also be a string required to start the quiz ...\r
56                                 // Login[3] = "password";\r
57         Login[4] = true;        // Show prompt for the cookie expiry date\r
58                                 // If false, cookies expire at the end of the current session\r
59         Login[5] = "guest,webmaster"\r
60                                 // guest user names (case insensitive) ...  \r
61                                 // Login[5] = "guest,webmaster"; \r
62                                 // These users do NOT need to fill in other login fields\r
63                                 // and their quiz results are NOT added to the database\r
65         // the Login prompts and error messages \r
66         // are defined in the MSG array (see below)\r
67 }\r
70 // *********\r
71 //  Database (for use with BFormMail)\r
72 // *********\r
74 if (window.DB==null) {\r
75         DB = new Array();\r
76         DB[0] = true; // append form fields to database on server\r
77                         // If you are NOT using BFormMail's database feature, \r
78                         // set DB[0]=false, and you can then safely ignore DB[1 to 5]\r
79         DB[1] = "/home/gordon/public_html/cgi/hot-potatoes-data"; \r
80                         // append_db folder path (no trailing slash)\r
81                         // Can be either an absolute path  e.g. "/home/gordon/public_html/cgi/hot-potatoes-data"\r
82                         // or a relative (to CGI bin) path  e.g. "hot-potatoes-data"\r
83         DB[2] = "hot-potatoes"; \r
84                         // append_db file name (no extension)\r
85                         // If left blank, the quiz file name, without extension, will be used\r
86                         // i.e. each quiz will have its results stored in a different file.\r
87                         // If filled in, this file will store the results for ALL quizzes.\r
88                         // Database files and folders must be set up BEFORE running the quiz \r
89                         // must have appropriate access privileges (on Unix, use "chmod 666").\r
90         DB[3] = ""; // append_db extension (if left blank, ".txt" will be used)\r
91         DB[4] = ""; // db_fields (if left blank, ALL quiz fields will be sent)\r
92         DB[5] = ""; // db_delimiter (if left blank, tab will be used)\r
93         DB[6] = "REMOTE_ADDR,HTTP_USER_AGENT"; \r
94                         // env_report ('REMOTE_ADDR','HTTP_USER_AGENT' and a few others)\r
96         // for a complete description of these fields are, see ... \r
97         // http://www.infosheet.com/stuff/BFormMail.readme\r
99         // Switches DB[7] and DB[8] force the settings in the ResultForm\r
100         // In v5 and v6 quizzes, these settings wil be override those in the original quiz\r
102         // If the quiz results are to be sent to an LMS (via the "store" form)\r
103         // then switches DB[7] and DB[8] are not used\r
105         DB[7] = '';     // URL of form processing script\r
106                         // e.g. http://www.kanazawa-gu.ac.jp/~gordon/cgi/bformmail.cgi\r
107         DB[8] = '';     // email address to which results should be sent\r
108                         // e.g. gordon@kanazawa-gu.ac.jp\r
111 // By default the quiz's question's scores will be returned. \r
112 // If you want more detailed information, set the flags below:\r
114 // ********\r
115 //  JBC\r
116 // ********\r
118 if (window.JBC==null) {\r
119         JBC = new Array();\r
120         JBC[0] = true;  // show separator line between answers on email\r
121         JBC[1] = true;  // show number of attempts to answer question\r
122         JBC[2] = true;  // show question texts\r
123         JBC[3] = true;  // show right answer(s)\r
124         JBC[4] = true;  // show wrong answer(s)\r
125         JBC[5] = true;  // show ignored answer(s)\r
126         JBC[6] = false; // show answer as text (false) or number (true)\r
129 // JBC quizzes use the global variables 'I' and 'Status'\r
131 // I : an array of JBC_QUESTIONs (one for each question)\r
132 // JBC_QUESTION :\r
133 //      [0] : question text\r
134 //      [1] : array of JBC_ANSWERs (one for each answer)\r
135 //      [2] : single/multi flag\r
136 //              0 : single answer (using 'button')\r
137 //              1 : multiple answers (using 'checkbox')\r
138 // JBC_ANSWER :\r
139 //      [0] : answer text\r
140 //      [1] : answer feedback\r
141 //      [2] : correct answer flag\r
142 //              0 : this is NOT the correct answer\r
143 //              1 : this is the correct answer\r
145 // Status : an array of JBC_QUESTION_STATUSes\r
146 // JBC_QUESTION_STATUS:\r
147 //      [0] : correctly answered yet flag\r
148 //              0 : this question has NOT been correctly answered\r
149 //              1 : this question has been correctly answered\r
150 //      [1] : array of JBC_ANSWER_STATUSes (one for each answer)\r
151 //              '0' : initial value\r
152 //              'R' : single answer question was answered 'R'ight\r
153 //              'W' : single answer question was answered 'W'rong\r
154 //              'C' : multiple answer question's checkbox was 'C'hecked\r
155 //              'U' : multiple answer question's checkbox was 'U'nchecked\r
156 //      [2] : number of times this question has been wrongly answered\r
157 //      [3] : score (out of 1) for this question (maybe undefined on HP<5.5)\r
158 //              0 : not correct yet\r
159 //              0<[3]<1 : correct but only after [2] wrong attempts\r
160 //              1 : correct first time (bravo!)\r
161 //      N.B. score = (numberOfAnswers - numberofWrongTries) / numberOfAnswers\r
164 // ********\r
165 //  JCloze\r
166 // ********\r
168 if (window.JCloze==null) {\r
169         JCloze = new Array();\r
170         JCloze[0] = true;       // show separator line between answers on email\r
171         JCloze[1] = true;       // show student's correct answer\r
172         JCloze[2] = true;       // show other correct answer(s), if any\r
173         JCloze[3] = true;       // show wrong answer(s), if any (NOT available for v5)\r
174         JCloze[4] = true;       // show number of hints or penalties\r
175         JCloze[5] = true;       // show if clue was asked for or not\r
176         JCloze[6] = true;       // show clue\r
179 // JCloze quizzes use the global variables 'I' and 'State'\r
181 // I : array of JCLOZE_ANSWERs\r
182 // JCLOZE_ANSWER :\r
183 //      [0] : (unused)\r
184 //      [1] : array of JCLOZE_ANSWER_TEXTs\r
185 //      [2] : clue for this answer\r
186 // JCLOZE_ANSWER_TEXT : \r
187 //      [0] : array (seems unnecessary, just the text would be enough?)\r
188 //              [0] : text of possible answer\r
190 // State : array of JCLOZE_ANSWER_STATEs\r
191 // JCLOZE_ANSWER_STATE (v5) : \r
192 //      [0] : clue asked for or not\r
193 //      [1] : number of hints (show next letter) and penalties ('check' an incorrect answer)\r
194 //      [2] : length of answer matched\r
195 //      [3] : score for this item\r
196 //      [4] : already answered correctly \r
197 //      [5] : answer entered in text box (right or not)\r
199 // JCLOZE_ANSWER_STATE (v6)\r
200 //      this.ClueGiven = false;\r
201 //      this.HintsAndChecks = 0;\r
202 //      this.MatchedAnswerLength = 0;\r
203 //      this.ItemScore = 0;\r
204 //      this.AnsweredCorrectly = false;\r
205 //      this.Guesses = new Array(); last guess is correct answer\r
208 // ********\r
209 //  JCross\r
210 // ********\r
212 if (window.JCross==null) {\r
213         JCross = new Array();\r
214         JCross[0] = true;       // show separator line between answers on email\r
215         JCross[1] = true;       // show number of penalties (hints or checks before complete)\r
216         JCross[2] = true;       // show number of letters\r
217         JCross[3] = true;       // show answers\r
218         JCross[4] = true;       // show clues\r
221 // JCross quizzes use the following global variables: \r
222 //      L : letters (of correct answers)\r
223 //      C : clue numbers (CL in v6)\r
224 //      G : guesses\r
225 // 'L', 'C' ('CL') and 'G' are all 2-dimensional arrays (rows x cols)\r
226 //\r
227 // v5 quizzes additionally use the following single-dimension arrays\r
228 //      A : clues for across (horizontal) words \r
229 //      D : clues for down (vertical) words \r
231 // N.B. form is only sent when all answers are correct so \r
232 // you can't find out what 'wrong' answers were entered\r
235 // ********\r
236 //  JMatch\r
237 // ********\r
239 if (window.JMatch==null) {\r
240         JMatch = new Array();\r
241         JMatch[0] = true;       // show separator line between answers on email\r
242         JMatch[1] = true;       // show number of attempts for each match\r
243         JMatch[2] = true;       // show LHS texts\r
244         JMatch[3] = true;       // show RHS texts\r
247 // v5 JMatch quizzes use the global variables 'I' and 'Status' (and 'RItems')\r
248 // v6 JMatch quizzes use only 'Status'\r
249 // v6+ JMatch quizzes use 'F' and 'D' (see below)\r
251 // I : an array of JMATCH_PAIRs (one for each pair)\r
252 // JMATCH_PAIR :\r
253 //      [0] : LHS text\r
254 //      [1] : RHS text\r
255 //      [2] : fixed (=not jumbled) flag\r
256 //              0 : not fixed\r
257 //              1 : fixed\r
258 //      [3] : index in drop down list selection\r
260 // Status : an array of JMATCH_PAIR_STATUSes\r
261 // JMATCH_PAIR_STATUS:\r
262 //      [0] : correctly matched yet flag\r
263 //              0 : this pair has NOT been correctly matched\r
264 //              1 : this pair has been correctly matched\r
265 //      [1] : number of times this item has been wrongly matched\r
267 //      v6 quizzes only\r
268 //      [2] : id of original SELECT element containing possible matches\r
269 //      Note that after matching, this SELECT is removed, so don't try looking for it :-)\r
271 // v6+ JMatch quizzes use the global variables 'F' and 'D'\r
273 // F : array of JMATCH_FIXED_ITEMs\r
274 // JMATCH_FIXED_ITEM:\r
275 //      [0] : text\r
276 //      [1] : tag\r
278 // D : array of JMATCH_DRAGGABLE_ITEMs\r
279 // JMATCH_DRAGGABLE_ITEM\r
280 //      [0] : text\r
281 //      [1] : tag of the F item to which it SHOULD be dragged\r
282 //      [2] : tag of the F item to which it was dragged (initally 0)\r
284 // N.B. form is only sent when all answers are correct so \r
285 // you can't find out what 'wrong' answers were entered\r
287 // ********\r
288 //  JMix\r
289 // ********\r
291 if (window.JMix==null) {\r
292         JMix = new Array();\r
293         JMix[0] = true;         // show separator line between answers on email\r
294         JMix[1] = true;         // show number of wrong guesses\r
295         JMix[2] = true;         // show right answer\r
296         JMix[3] = true;         // show wrong answer, if any\r
297         JMix[4] = false;        // show answer as text (false) or number (true)\r
300 // JMix quizzes use the global variables \r
301 // 'Segments', 'GuessSequence' and 'Penalties' \r
303 // Segments : array of JMix_QUESTIONs\r
304 // JMix_QUESTION:\r
305 //      [0] : text\r
306 //      [1] : order in sequence\r
307 //      [2] : used flag\r
308 // GuessSequence : array of 'order in sequence' numbers\r
309 // Penalties : number of incorrect guesses\r
311 // ********\r
312 //  JQuiz\r
313 // ********\r
315 if (window.JQuiz==null) {\r
316         JQuiz = new Array();\r
317         JQuiz[0] = true;        // show separator line between answers on email\r
318         JQuiz[1] = true;        // show question text\r
319         JQuiz[2] = true;        // show student's correct answer(s)\r
320         JQuiz[3] = true;        // show wrong and ignored answer(s)\r
321         JQuiz[4] = true;        // show number of hints requested\r
322         JQuiz[5] = true;        // show number of checks of incorrect answers\r
324         // HP6 v6 quizzes only\r
325         JQuiz[6] = false;       // show answer value (false) or A,B,C... index (true)\r
326         JQuiz[7] = true;        // show all students answers\r
327         JQuiz[8] = true;        // show student's wrong answers\r
328         JQuiz[9] = true;        // show ignored answers (not relevant for multi-select questions)\r
329         JQuiz[10] = true;       // show score weightings\r
330         JQuiz[11] = true;       // show question type\r
333 // v5 JQuiz quizzes use the global variables 'I' and 'Status'\r
335 // I : array of JQUIZ_ANSWERs\r
336 // JQUIZ_ANSWER :\r
337 //      [0] : question text\r
338 //      [1] : array of JQUIZ_ANSWER_TEXTs (one for each answer)\r
339 // JQUIZ_ANSWER_TEXT :\r
340 //      [0] : array (seems unnecessary, just the text would be enough?)\r
341 //              [0] : text of possible answer\r
343 // Status : array of JQUIZ_ANSWER_STATEs\r
344 // JQUIZ_ANSWER_STATE : \r
345 //      [0] : question done or not\r
346 //      [1] : number of wrong checks\r
347 //      [2] : number of hints asked for\r
348 //      [3] : student's answer\r
349 //      [4] : score for this question\r
352 // v6 JQuiz quizzes use the global variables 'I' and 'State'\r
354 // I : array of JQUIZ_QUESTIONs\r
355 // JQUIZ_QUESTION :\r
356 //      [0] : weighting\r
357 //      [1] : ?? (always set to '')\r
358 //      [2] : question type\r
359 //              '0'=multiple-choice, '1'=short-answer, '2'=hybrid, '3'=multi-select\r
360 //      [3] : array of JQUIZ_ANSWERSs (one for each possible answer)\r
361 // JQUIZ_ANSWER :\r
362 //      [0] : answer value\r
363 //      [1] : feedback text\r
364 //      [2] : correct answer flag (1=a correct answer, 0=a wrong answer)\r
365 //      [3] : weighted score (as percentage) if correct\r
366 //      [4] : flag (usually set to 1, but for hybrid answers that are not \r
367 //              to be included in multiple choice options, it is set to 0)\r
369 // State : array of JQUIZ_QUESTION_STATEs\r
370 // JQUIZ_QUESTION_STATE : \r
371 //      [0] : score (-1 shows not done yet)\r
372 //      [1] : array showing on which number try each JQUIZ_ANSWER was selected\r
373 //      [2] : number of attempts at this question\r
374 //      [3] : total of weighted scores of correct answers that were selected\r
375 //              i.e. each time a correct answer is selected, \r
376 //              its JQUIZ_ANSWER[3] weighting is added to this total\r
377 //              so when all the correct answers have been selected, this will be 100\r
378 //      [4] : penalties incurred for hints (score is set to zero if >= 1)\r
379 //      [5] :   - for multiple choice, short-answer and hybrid questions, this is a\r
380 //              comma-delimited list showing order in which answers were chosen\r
381 //              - for multi-select fields, this is bar-delimted ('|') list of settings \r
382 //              showing whether each checkbox was selected ('Y') on not ('N') when the \r
383 //              'Check' button was clicked. The final item in the list will be the \r
384 //              settings for the correct answer.\r
386 // N.B. JBC, JMatch(v5) and JQuiz(v5) all use global variables 'I' and 'Status'\r
387 //      JBC : I[0].length==3 && !window.RItems\r
388 //      JQuiz(v5) : I[0].length==2\r
389 //      JMatch(v5) : I[0].length==4 && window.RItems\r
391 // N.B. JCloze(v5+6) and JQuiz(v6) both use global variables 'I' and 'State'\r
392 //      JCloze (v5) : I[0].length==3 && State[0].Guesses==null\r
393 //      JCloze (v6) : I[0].length==3 && State[0].Guesses!=null\r
394 //      JQuiz  (v6) : I[0].length==4\r
396 // **********\r
397 //  Rhubarb\r
398 // **********\r
400 if (window.Rhubarb==null) {\r
401         Rhubarb = new Array();\r
402         Rhubarb[0] = true; // show correct words\r
403         Rhubarb[1] = true; // show incorrect words\r
406 // **********\r
407 //  Sequitur\r
408 // **********\r
410 if (window.Sequitur==null) {\r
411         Sequitur = new Array();\r
414 // **********\r
415 //  Messages\r
416 // **********\r
418 if (window.MSG==null) {\r
419         MSG = new Array();\r
421         // Login prompts\r
422         MSG[0] = 'Name';\r
423         MSG[1] = 'ID';\r
424         MSG[2] = 'Email';\r
425         MSG[3] = 'Password';\r
426         MSG[4] = 'Cookies';\r
428         // Login buttons\r
429         MSG[5] = 'Start the Quiz';\r
430         MSG[6] = 'Cancel';\r
432         // Cookie menu options (only used if Login[4] is true)\r
433         MSG[7] = 'keep for this session only';\r
434         MSG[8] = 'keep for one day';\r
435         MSG[9] = 'keep for one month';\r
436         MSG[10] = 'do NOT keep cookies';\r
438         // Login error messages\r
439         MSG[11] = 'Sorry, you were unable to login. Please try again later.';\r
440         MSG[12] = 'Please fill in all the information.';\r
441         MSG[13] = 'Incorrect Password. Please try again.';\r
442         MSG[14] = 'Incorrect ID. Please try again.';\r
443         MSG[15] = 'Email address does not appear to be valid.';\r
445         // day and month names (used in Start_Time and End_Time)\r
446         MSG[16] = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');\r
447         MSG[17] = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');\r
449         // enable popups\r
450         MSG[18] = 'Please enable pop-up windows on your browser.';\r
452         // browser specific instuctions on how to enable popup windows\r
453         var n = navigator;\r
454         var s = n.userAgent.toLowerCase();\r
455         if (n.appName=='Netscape' && s.indexOf('gecko')>=0) {\r
456                 // Netscape 6 and 7\r
457                 MSG[18] += '\n\n' + 'Edit->Preferences, ' + (s.indexOf('mac')>=0 ? 'Advanced->Scripts & Plugins' : 'Privacy & Security->Popup Window Controls');\r
458         } else if (s.indexOf('safari')>=0) {\r
459                 // Safari\r
460                 MSG[18] += '\n\n' + 'on Safari menu, uncheck "Block Pop-Up Windows"';\r
461         } else if (s.indexOf('firebird')>=0) {\r
462                 // Firebird\r
463                 MSG[18] += '\n\n' + 'Preferences->Web Features, uncheck "Block Pop-Up Windows"';\r
464         } else if (s.indexOf('msie 6')>=0) {\r
465                 // IE 6 (WinXP.SP2)\r
466                 MSG[18] += '\n\n' + 'Tools->Pop-up Blocker->Turn Off Pop-up Blocker';\r
467         }\r
470 // *************\r
471 //  Server Fields\r
472 // *************\r
474 if (window.ServerFields==null) {\r
475         ServerFields = new Array();\r
477         // these fields will be added to the ResultForm and submitted to the CGI script on the server.\r
478         // 'Sort', 'return_link_title', 'return_link_url' and 'print_blank_fields' are useful for formmail\r
480         // override the HP setting of sort fields (forces ALL fields to be displayed)\r
481         ServerFields[0] = new Array('sort', '');\r
483         // add link to close pop-up results window\r
484         ServerFields[1] = new Array('return_link_title', 'Close this window');\r
485         ServerFields[2] = new Array('return_link_url', 'javascript:self.close()');\r
487         // make sure zero values are printed\r
488         ServerFields[3] = new Array('print_blank_fields', 'yes');\r
490         // you can also set other fields for your customized CGI script\r
491         // e.g. adding a server defined start time (instead of a client defined start time)\r
492         // ServerFields[4] = new Array('serverStartTime', '<?php echo date("Y-m-d H:i:s") ?>');\r
495 // *********************\r
496 //      Login screen\r
497 //  (not required by LMS)\r
498 // *********************\r
500 function QuizLogin(LoginPrompt) {\r
501         if ((Login[0] || Login[1] || Login[2] || Login[3]) && !is_LMS()) {\r
502                 var html = ''\r
503                         + '<HTML>'\r
504                         + '<HEAD></HEAD>'\r
505                         + '<BODY bgColor="#cccccc" onLoad="opener.setFocus(self)">'\r
506                         + '<FORM onSubmit="'\r
507                         +       'self.ok=true;'\r
508                         +       'self.expiry=null;'\r
509                 ;\r
510                 if (Login[4]) { // cookie expiry\r
511                         html += "opener.checkOK(self,'CookieExpiry');";\r
512                 }\r
513                 if (Login[0]) { // user name\r
514                         html += "opener.checkOK(self,'UserName');";\r
515                 }\r
516                 if (Login[1]) { // user ID\r
517                         html += "opener.checkOK(self,'UserID');";\r
518                 }\r
519                 if (Login[2]) { // user email\r
520                         html += "opener.checkOK(self,'UserEmail');";\r
521                 }\r
522                 if (Login[3]) { // quiz password\r
523                         html += "opener.checkOK(self,'Password');";\r
524                 }\r
525                 html +=          'if(ok){'\r
526                         +               'opener.StartQuiz();'\r
527                         +               'self.close();'\r
528                         +         '}else{'\r
529                         +               'if(isNaN(self.tries))self.tries=0;'\r
530                         +               'self.tries++;'\r
531                         +               'if(self.tries<3){'\r
532                         +                       'opener.setFocus(self);'\r
533                         +               '}else{'\r
534                         +                       "alert(opener.MSG[11]);"\r
535                         +                       'opener.goBack();'\r
536                         +                       'self.close();'\r
537                         +               '}'\r
538                         +         '}'\r
539                         +         'return false;'\r
540                         + '">'\r
541                 ;\r
542                 html += '<TABLE>'\r
543                         +       '<CAPTION>' + LoginPrompt + '</CAPTION>';\r
544                 ;\r
545                 if (Login[0]) { // user name\r
546                         var v = getCookie(self, 'UserName');\r
547                         html += '<TR>'\r
548                                 +       '<TH align=right nowrap>' + MSG[0] + ' :</TH>'\r
549                                 +       '<TD>'\r
550                         ;\r
552                         if (typeof(Login[0])=='boolean') { // text box\r
553                                 html += '<INPUT type=text name=UserName value="' + v + '">';\r
555                         } else { // drop down menu of names\r
557                                 // pattern to match commas and white space\r
558                                 var comma = (window.RegExp) ? new RegExp('\\s*,\\s*') : ',';\r
560                                 // convert list of names to array, if necessary\r
561                                 if (typeof(Login[0])=='string') {\r
562                                         Login[0] = Login[0].split(comma);\r
563                                 }\r
565                                 html += '<SELECT name=UserName size=1>'\r
566                                         + '<OPTION value=""></OPTION>'\r
567                                 ;\r
568                                 for(var i=0; i<Login[0].length; i++) {\r
569                                         // convert name details to array if necesssary\r
570                                         if (typeof(Login[0][i])=='string') {\r
571                                                 Login[0][i] = Login[0][i].split(comma);\r
572                                         }\r
573                                         html += makeOption(Login[0][i][0], v, Login[0][i][1]);\r
574                                 }\r
575                                 html += '</SELECT>';\r
576                         }\r
577                         html +=         '</TD>'\r
578                                 + '</TR>'\r
579                         ;\r
580                 }\r
581                 if (Login[1]) { // user ID\r
582                         var v = getCookie(self, 'UserID');\r
583                         html += '<TR><TH align=right nowrap>' + MSG[1] + ' :</TH><TD><INPUT type=text name=UserID value="' + v + '"></TD></TR>';\r
584                 }\r
585                 if (Login[2]) { // user email\r
586                         var v = getCookie(self, 'UserEmail');\r
587                         html += '<TR><TH align=right nowrap>' + MSG[2] +' :</TH><TD><INPUT type=text name=UserEmail value="' + v + '"></TD></TR>';\r
588                 }\r
589                 if (Login[3]) { // quiz password\r
590                         var v = getCookie(self, 'Password');\r
591                         html += '<TR><TH align=right nowrap>' + MSG[3] + ' :</TH><TD><INPUT type=password name=Password value="' + v + '"></TD></TR>';\r
592                 }\r
593                 if (Login[4]) { // cookie lifespan\r
594                         var v = getCookie(self, 'CookieExpiry');\r
595                         html += '<TR>'\r
596                                 +       '<TH align=right nowrap>' + MSG[4] + ' :</TH>'\r
597                                 +       '<TD>'\r
598                                 +               '<SELECT name="CookieExpiry" size=1>'\r
599                                 +                       makeOption('session', v, MSG[7])\r
600                                 +                       makeOption('day', v, MSG[8])\r
601                                 +                       makeOption('month', v, MSG[9])\r
602                                 +                       makeOption('never', v, MSG[10])\r
603                                 +               '</SELECT>'\r
604                                 +       '</TD>'\r
605                                 + '</TR>'\r
606                         ;\r
607                 }\r
608                 html +=         '<TR>'\r
609                         +               '<TH>&nbsp;</TH>'\r
610                         +               '<TD nowrap>'\r
611                         +                       '<INPUT type=submit value="' + MSG[5] + '"> '\r
612                         +                       '<INPUT type=button value="' + MSG[6] + '" onClick="opener.goBack();self.close();">'\r
613                         +               '</TD>'\r
614                         +       '</TR>'\r
615                         + '</TABLE></FORM></BODY></HTML>'\r
616                 ;\r
618                 // set height of Login Window\r
619                 var m = navigator.userAgent.indexOf('Mac')>=0;\r
620                 var h = (m ? 80 : 100);\r
621                 for (var i=0; i<5; i++) h += (Login[i] ? (m ? 20 : 25) : 0);\r
623                 // open up a new window\r
624                 if (!openWindow('', '', (m ? 320 : 300), h, 'RESIZABLE', html)) {\r
625                         alert(MSG[18]); // unable to open popup window\r
626                 }\r
628         } else { // no Login required\r
629                 window.UserName = window.UserID = window.UserEmail = window.Password = '';\r
630                 window.StartQuiz();\r
631         }\r
632         return true;\r
634 function makeOption(value, v, txt) {\r
635         return '<OPTION value="' + value + '"' + (value==v ? ' SELECTED' : '') + '>' + (txt ? txt : value) + '</OPTION>';\r
637 function setFocus(w) {\r
638         w.focus(); // bring window to the front\r
639         var obj = w.document.forms[0].elements;\r
640         for(var i=0; i<obj.length; i++) {\r
641                 var v = getValue(w, i);\r
642                 if (v=='' || obj[i].type=='submit') {\r
643                         obj[i].focus();\r
644                         break;\r
645                 }\r
646         }\r
648 function checkOK(w, n){\r
649         var v = getValue(w, n, true);\r
650         if (v || (n!='UserName' && isGuest())) {\r
651                 if (n=='CookieExpiry') setCookieExpiry(w, v);\r
652                 setCookie(self, n, v, w.expiry);\r
653                 if (n!='CookieExpiry') eval('self.' + n + '=v');\r
654         } else {\r
655                 if (w.ok) alert(MSG[12]);\r
656                 w.ok = false;\r
657         }\r
659 function getValue(w, n, flag) {\r
660         var obj = w.document.forms[0].elements[n];\r
661         var TYPE = obj.type.toUpperCase(); // required for ns4 (win)\r
662         if (obj.options && TYPE.indexOf('SELECT')>=0){ \r
663                 var v = obj.options[obj.selectedIndex].value;\r
664         } else {\r
665                 var v = obj.value;\r
666         }\r
667         if (flag) {\r
668                 var msg = '';\r
669                 if (n=='Password' || (n=='UserID' && !Login[3])) {\r
670                         var pwd = getPassword(w);\r
671                         if (pwd && v!=pwd) msg = MSG[n=='Password' ? 13 : 14];\r
672                 } \r
673                 if (n=='Email' && window.RegExp) {\r
674                         var r = '(\\w|-)+';\r
675                         r = r + '(\\.' + r + ')';\r
676                         r = new RegExp('^(' + r + '*)@(' + r + '+)$');\r
677                         if (v.match(r)==null) msg = MSG[15];\r
678                 }\r
679                 if (msg) {\r
680                         obj.value = v = '';\r
681                         if (w.ok) alert(msg);\r
682                         w.ok = false;\r
683                 }\r
684         }\r
685         return v;\r
687 function getPassword(w) {\r
688         var pwd = '';\r
689         if (Login[3] && typeof(Login[3])=='string') {\r
690                 pwd = Login[3];\r
691         } else if ((Login[3] || Login[1]) && typeof(Login[0])=='object') {\r
692                 var username = getValue(w, 'UserName');\r
693                 for(var i=0; i<Login[0].length; i++) {\r
694                         if (username==Login[0][i][0]) {\r
695                                 pwd = Login[0][i][2];\r
696                                 break;\r
697                         }\r
698                 }\r
699         }\r
700         return pwd;\r
702 function setCookieExpiry(w, v) {\r
703         if (v=='never'){\r
704                 w.expiry = new Date('Thu, 01-Jan-70 00:00:01 GMT');\r
705         } else if (v=='day' || v=='month') {\r
706                 var ms = (v=='month' ? 31 : 1) * 60 * 60 * 24 * 1000;\r
707                 w.expiry = new Date((new Date()).getTime() + ms);\r
708         }\r
710 function setCookie(w, name, value, expires, path, domain, secure) {\r
711         if (name) w.document.cookie = ''\r
712                 + 'HP_' + name + "=" + escape(value)\r
713                 + (expires ? "; expires=" + expires.toGMTString() : "")\r
714                 + (path ? "; path=" + path : "")\r
715                 + (domain ? "; domain=" + domain : "")\r
716                 + (secure ? "; secure" : "")\r
717         ;\r
719 function getCookie(w, n) {\r
720         var c = w.document.cookie;\r
721         var i = c.indexOf('HP_' + n + '=');\r
722         var j = (i<0) ? -1 : c.indexOf(';', (i += n.length + 4));\r
723         return (i<0) ? '' : unescape(c.substring(i, ((j<0) ? c.length : j)));\r
725 function goBack(w) {\r
726         if (w==null) w = self; // default\r
727         if (w.history.length) w.history.back();\r
729 function openWindow(url, name, width, height, attributes, html) {\r
730         if (window.screen && width && height) {\r
731                 var W = screen.availWidth;\r
732                 var H = screen.availHeight;\r
733                 width = Math.min(width, W);\r
734                 height = Math.min(height, H);\r
735                 attributes = ''\r
736                         + (attributes ? (attributes+',') : '')\r
737                         + 'WIDTH='+width+',HEIGHT='+height\r
738                 ;\r
739         }\r
741         // workaround for "Access is denied" errors in IE when offline\r
742         // based on an idea seen at http://www.devshed.com/Client_Side/JavaScript/Mini_FAQ\r
743         var ie_offline = (document.all && location.protocol=='file:');\r
745         // open the window\r
746         var w = window.open((ie_offline ? '' : url), name, attributes);\r
748         // check window opened OK (user may have prevented popups)\r
749         if (w) {\r
750                 // center the window\r
751                 if (window.screen && width && height) {\r
752                         w.moveTo((W-width)/2, (H-height)/2);\r
753                 }\r
755                 // add content, if required\r
756                 if (html) {\r
757                         with (w.document) {\r
758                                 clear();\r
759                                 open();\r
760                                 write(html);\r
761                                 close();\r
762                         }\r
763                 } else {\r
764                         if (ie_offline && url) w.location = url;\r
765                 }\r
766         }\r
767         return w;\r
770 // *********************\r
771 //  Send results by email\r
772 //  (not required by LMS)\r
773 // *********************\r
775 function SendAllResults(Score) {\r
776         // check this quiz is not generated by a LMS\r
777         if (!is_LMS()) {\r
779                 // add flat file database details to the results form\r
780                 AddDatabaseDetailsToResultForm();\r
782                 // add student details to the results form\r
783                 AddStudentDetailsToResultForm();\r
785                 // add question details to the results form\r
786                 AddQuestionDetailsToResultForm();\r
788                 // add server fields, if any, to results form\r
789                 AddServerFieldsToResultForm();\r
791                 // change "method" of form, because "get" only allows 512 byts of data\r
792                 ResultForm = replaceLast('method="get"', 'method="post"', ResultForm);\r
794                 // create results window and form\r
795                 var w = openWindow('', '', 500, 400, 'RESIZABLE,SCROLLBARS,HOTPOT_LOCATION', ResultForm);\r
797                 // check window opened OK (user may have prevented popups)\r
798                 if (w) {\r
800                         // get shortcut to form object\r
801                         var form = w.document.forms[0];\r
803                         // update some important field values\r
804                         form.Score.value = Score + '%';\r
805                         form.realname.value = UserName;\r
806                         form.Start_Time.value = getTime(Start_Time);\r
807                         form.End_Time.value = getTime();\r
809                         // force email subject and Exercise title\r
810                         form.subject.value = document.title;\r
811                         form. Exercise.value = document.title;\r
813                         // update DB fields, if required\r
814                         if (DB[0] && !isGuest()) set_db_fields(form);\r
815                         if (DB[7]) form.action = DB[7];\r
816                         if (DB[8]) form.recipient.value = DB[8];\r
818                         // if this is a Netscape browser, check if the referer will be set OK\r
819                         if (navigator.appName=='Netscape' && (location.protocol=='file:' || navigator.userAgent.indexOf('Netscape6')>=0)) {\r
821                                 // ns4 and ns7 set referer to 'file:// ...' when running a quiz offline\r
822                                 // ns6.2 (at least) always sets referer to 'about:blank' \r
824                                 // Netscape's setting of referer can cause BFormMail\r
825                                 // to reject the form, so encode the form data as a URL\r
827                                 var url = form.action;\r
828                                 var obj = form.elements;\r
829                                 for (var i=0; i<obj.length; i++) {\r
830                                         var  v = escape(obj[i].value);\r
831                                         v = v.replace((new RegExp('\\+', 'g')), '%2B');\r
832                                         url += (i==0 ? '?' : '&') + obj[i].name + '=' + v;\r
833                                 }\r
834                                 w.location.href = url;\r
836                         } else { // browser can POST form ok\r
837                                 form.submit();\r
838                         }\r
840                 } else { // unable to open popup window\r
841                         alert(MSG[18]);\r
842                 }\r
844         } // end if LMS\r
846 function isGuest() {\r
847         // check username is not a "guest" user\r
848         var flag = false;\r
849         var n = getCookie(self, 'UserName').toLowerCase();\r
850         if (n) {\r
851                 // convert list of user names to array, if necessary\r
852                 if(typeof(Login[5])=='string') {\r
853                         Login[5] = Login[5].split(',');\r
854                 }\r
855                 for(var i=0; i<Login[5].length; i++) {\r
856                         if (n==Login[5][i].toLowerCase()) {\r
857                                 flag = true;\r
858                                 break;\r
859                         }\r
860                 }\r
861         }\r
862         return flag;\r
864 function set_db_fields(form) {\r
865         // update list of DB fields, if required\r
866         if (DB[4]=='' && window.RegExp) {\r
867                 // add administration fields\r
868                 var db_fields = ''\r
869                         + 'subject,realname'\r
870                         + (Login[1] ? ',ID' : '')\r
871                         + (Login[2] ? ',email' : '')\r
872                         + (Login[3] ? ',password' : '')\r
873                         + ',Score,Start_Time,End_Time'\r
874                 ;\r
875                 // add answer fields (except separators)\r
876                 var r = new RegExp('^[^_]+_q\\d\\d_\\w+$');\r
877                 for(var i=0; i<form.elements.length; i++) {\r
878                         var n = form.elements[i].name;\r
879                         if (r.test(n)) db_fields += ',' + n;\r
880                 }\r
881                 form.db_fields.value = db_fields;\r
882         }\r
883         // make sure delimiter is set (NS6+ requires this be set here, not any earlier)\r
884         form.db_delimiter.value = (DB[5] ? DB[5] : '\t');\r
886 function AddStudentDetailsToResultForm() {\r
887         var sDetails = '';\r
888         if (Login[0]) { // user name\r
889                 // use 'realname' instead of a separate 'Name' field\r
890                 // sDetails += makeHiddenField('Name', window.UserName);\r
891         }\r
892         if (Login[1]) { // user ID\r
893                 sDetails += makeHiddenField('ID', window.UserID);\r
894         }\r
895         if (Login[2]) { // user email\r
896                 sDetails += makeHiddenField('email', window.UserEmail);\r
897         }\r
898         if (sDetails && window.RegExp) {\r
899                 // insert sDetails before '<input...Score...></input>'\r
900                 var r = new RegExp('<input[^>]*Score[^>]*><\\/input>', 'i');\r
901                 var m = r.exec(ResultForm);\r
902                 if (m) {\r
903                         ResultForm = ResultForm.replace(m[0], sDetails + m[0] + makeSeparator('Time_'));\r
904                         sDetails = '';\r
905                 }\r
906         }\r
907         if (Login[3]) { // quiz password\r
908                 sDetails += makeHiddenField('Password', window.Password);\r
909                 ResultForm = replaceLast('</form>', sDetails + '</form>', ResultForm);\r
910         }\r
912 function AddQuestionDetailsToResultForm() {\r
913         var qDetails = GetQuestionDetails();\r
914         if (qDetails) {\r
915                 // insert qDetails before the final </form> tag in the ResultForm\r
916                 ResultForm = replaceLast('</form>', qDetails + '</form>', ResultForm);\r
917         }\r
919 function AddDatabaseDetailsToResultForm() {\r
920         if (window.DB && DB[0] && !isGuest()) {\r
921                 var dbDetails = '';\r
923                 var folder = DB[1];\r
924                 if (folder && folder.charAt(folder.length-1)!='/') folder += '/';\r
926                 var file = DB[2];\r
927                 if (file=='') {\r
928                         file = location.href;\r
929                         file= file.substring(file.lastIndexOf('/')+1);\r
930                         var i = file.indexOf('?');\r
931                         if (i >= 0) file = file.substring(0, i);\r
932                         var i = file.lastIndexOf('.');\r
933                         if (i >= 0) file = file.substring(0, i);\r
934                 }\r
936                 var ext = (DB[3] ? DB[3] : 'txt');\r
937                 if (ext.charAt(0)!='.') ext = '.' + ext;\r
939                 dbDetails += makeHiddenField('append_db', folder + file + ext);\r
940                 dbDetails += makeHiddenField('db_fields', DB[4]);\r
941                 dbDetails += makeHiddenField('db_delimiter', ''); // NS6+ requires this be set later\r
942                 if (DB[6]) dbDetails += makeHiddenField('env_report', DB[6]);\r
944                 // insert dbDetails before the final </form> tag in the ResultForm\r
945                 ResultForm = replaceLast('</form>', dbDetails + '</form>', ResultForm);\r
946         }\r
948 function AddServerFieldsToResultForm() {\r
949         if (window.ServerFields) {\r
950                 var s = ''; // input tags for s(erver fields)\r
951                 for (var i=0; i<ServerFields.length; i++) {\r
952                         if (ServerFields[i][0] && window.RegExp) {\r
953                                 // remove previous field value, if any\r
954                                 var r = new RegExp('<input[^>]*name\\s*=\\s*["\']\\s*' + ServerFields[i][0] + '[^>]*>(\\s*<\\/input>)?', 'i');\r
955                                 if (r.test(ResultForm)) {\r
956                                         ResultForm = ResultForm.replace(r, '');\r
957                                 }\r
958                         }\r
959                         if (ServerFields[i][1]) {\r
960                                 s += makeHiddenField(ServerFields[i][0], ServerFields[i][1]);\r
961                         }\r
962                 } // end for\r
963                 if (s) ResultForm = replaceLast('</form>', s + '</form>', ResultForm);\r
964         }\r
966 function replaceLast(a, b, c) {\r
967         // replace last occurrence of 'a' in 'c' with 'b'\r
968         var l = a.length;\r
969         var i = c.lastIndexOf(a);\r
970         return (i<0 || l==0) ? c : (c.substring(0, i) + b + c.substring(i+l));  \r
973 // *************************\r
974 //  Extract question details\r
975 // *************************\r
977 function GetQuestionDetails() {\r
978         var t = get_quiz_type();\r
979         var v = get_quiz_version();\r
981         return  (t==1) ? GetJbcQuestionDetails(v) : \r
982                 (t==2) ? GetJClozeQuestionDetails(v) : \r
983                 (t==3) ? GetJCrossQuestionDetails(v) : \r
984                 (t==4) ? GetJMatchQuestionDetails(v) : \r
985                 (t==5) ? GetJMixQuestionDetails(v) : \r
986                 (t==6) ? GetJQuizQuestionDetails(v) :\r
987                 (t==7) ? GetRhubarbDetails(v) :\r
988                 (t==8) ? GetSequiturDetails(v) : '';\r
990 function GetJbcQuestionDetails(v) {\r
991         qDetails = '';\r
993         // check the quiz version\r
994         if (v==5 || v==6) {\r
996                 // get question details \r
997                 for(var q=0; q<I.length; q++) {\r
999                         // initialize strings to hold answer details\r
1000                         var aDetails = new Array();\r
1001                         aDetails[0] = new Array(); // right\r
1002                         aDetails[1] = new Array(); // wrong\r
1003                         aDetails[2] = new Array(); // ignored\r
1005                         // get answer details\r
1006                         for(var a=0; a<I[q][1].length; a++) {\r
1007                                 var i = (Status[q][1][a]=='R') ? 0 : (Status[q][1][a]=='0') ? 2 : 1; \r
1008                                 aDetails[i][aDetails[i].length] = (JBC[6] ? a : I[q][1][a][0]);\r
1009                         }\r
1011                         // format 'Q' (a padded, two-digit version of 'q')\r
1012                         var Q = getQ('JBC', q);\r
1014                         // add separator, if required\r
1015                         if (JBC[0]) qDetails += makeSeparator(Q);\r
1017                         if (JBC[1]) { // number of attempts to answer question\r
1018                                 qDetails += makeHiddenField(Q+'attempts', Status[q][2] + (Status[q][0]==1 ? 1 : 0));\r
1019                         }\r
1020                         if (JBC[2]) { // question text\r
1021                                 qDetails += makeHiddenField(Q+'text', I[q][0]);\r
1022                         }\r
1023                         if (JBC[3] && (DB[0] || aDetails[0].length>0)) { // right\r
1024                                 qDetails += makeHiddenField(Q+'right', aDetails[0]);\r
1025                         }\r
1026                         if (JBC[4] && (DB[0] || aDetails[1].length>0)) { // wrong\r
1027                                 qDetails += makeHiddenField(Q+'wrong', aDetails[1]);\r
1028                         }\r
1029                         if (JBC[5] && (DB[0] || aDetails[2].length>0)) { // ignored\r
1030                                 qDetails += makeHiddenField(Q+'ignored', aDetails[2]);\r
1031                         }\r
1032                         // calculate score for this question, if required (for HP version < 5.5)\r
1033                         if (isNaN(Status[q][3])) {\r
1034                                 var a1 = Status[q][1].length; // answers\r
1035                                 var a2 = Status[q][2]; // attempts\r
1036                                 Status[q][3] =  (a1<1 || a1<(a2-1)) ? 0 : ((a1 - (a2-1)) / a1);\r
1037                         }\r
1038                         // add 'score' for this question\r
1039                         qDetails += makeHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');\r
1040                 } // end for\r
1041         }\r
1042         return qDetails;\r
1044 function GetJClozeQuestionDetails(v) {\r
1045         var qDetails = '';\r
1047         // check the quiz version\r
1048         if (v==5 || v==6) {\r
1050                 var hp5 = (State[0].Guesses==null);\r
1052                 // get details for each question\r
1053                 for (var q=0; q<State.length; q++) {\r
1055                         // format 'Q' (a padded, two-digit version of 'q')\r
1056                         var Q = getQ('JCloze', q);\r
1058                         // add separator, if required\r
1059                         if (JCloze[0]) qDetails += makeSeparator(Q);\r
1061                         // score (as %)\r
1062                         var x = (hp5 ? State[q][3] : State[q].ItemScore);\r
1063                         qDetails += makeHiddenField(Q+'score', Math.floor(x*100)+'%');\r
1065                         // shortcut to students correct answer\r
1066                         var correct = (hp5 ? State[q][5] : State[q].Guesses[State[q].Guesses.length-1]);\r
1068                         if (JCloze[1]) { // student's correct answer\r
1069                                 qDetails += makeHiddenField(Q+'correct', correct);\r
1070                         }\r
1071                         if (JCloze[2]) { // other correct answers\r
1072                                 var ignored = new Array();\r
1073                                 for (var i=0, ii=0; i<I[q][1].length; i++) {\r
1074                                         if (I[q][1][i][0] && (I[q][1][i][0].toUpperCase() != correct.toUpperCase())) {\r
1075                                                 ignored[ii++] = I[q][1][i][0];\r
1076                                         }\r
1077                                 }\r
1078                                 if (DB[0] || ignored.length>0) qDetails += makeHiddenField(Q+'ignored', ignored);\r
1079                         }\r
1080                         if (JCloze[3] && State[q].Guesses) {\r
1081                                 var wrong = new Array();\r
1082                                 for (var i=0, ii=0; i<State[q].Guesses.length-1; i++) {\r
1083                                         wrong[ii++] = State[q].Guesses[i];\r
1084                                 }\r
1085                                 if (DB[0] || ii>0) qDetails += makeHiddenField(Q+'wrong', wrong);\r
1086                         }\r
1087                         if (JCloze[4]) { // number of penalties\r
1088                                 var x = (hp5 ? State[q][1] : State[q].HintsAndChecks);\r
1089                                 qDetails += makeHiddenField(Q+'penalties', x);\r
1090                         }\r
1091                         if (JCloze[5]) { // clue shown?\r
1092                                 var x = (hp5 ? State[q][0] : State[q].ClueGiven);\r
1093                                 qDetails += makeHiddenField(Q+'clue_shown', (x ? 'HOTPOT_YES' : 'HOTPOT_NO'));\r
1094                         }\r
1095                         if (JCloze[6]) { // clue text\r
1096                                 qDetails += makeHiddenField(Q+'clue_text', I[q][2]);\r
1097                         }\r
1098                 } // end for\r
1099         }\r
1100         return qDetails;\r
1102 function GetJCrossQuestionDetails(v) {\r
1103         var qDetails = '';\r
1105         // check the quiz version\r
1106         if (v==5 || v==6) {\r
1108                 // inialize letter count\r
1109                 var letters = 0;\r
1111                 // get details for each question\r
1112                 for (var row=0; row<L.length; row++) {\r
1113                         for (var col=0; col<L[row].length; col++) {\r
1115                                 // increment letter count, if required\r
1116                                 if (L[row][col]) letters++; \r
1118                                 // show answers and clues, if required\r
1119                                 var q = (v==5) ? C[row][col] : CL[row][col];\r
1120                                 if (q) {\r
1121                                         // format 'Q' (a padded, two-digit version of 'q')\r
1122                                         var Q = getQ('JCross', q);\r
1124                                         var clue_A = (v==5) ? A[q] : GetJCrossClue('Clue_A_' + q);\r
1125                                         var clue_D = (v==5) ? D[q] : GetJCrossClue('Clue_D_' + q);\r
1127                                         // add separator, if required\r
1128                                         if (JCross[0] && (clue_A || clue_D)) {\r
1129                                                 qDetails += makeSeparator(Q);\r
1130                                         }\r
1132                                         if (clue_A) { // across question\r
1133                                                 if (JCross[3]) qDetails += makeHiddenField(Q+'across', GetJCrossWord(G, row, col));\r
1134                                                 if (JCross[4]) qDetails += makeHiddenField(Q+'across_clue', clue_A);\r
1135                                         }\r
1136                                         if (clue_D) { // down question\r
1137                                                 if (JCross[3]) qDetails += makeHiddenField(Q+'down', GetJCrossWord(G, row, col, true));\r
1138                                                 if (JCross[4]) qDetails += makeHiddenField(Q+'down_clue', clue_D);\r
1139                                         }\r
1140                                 } // end if q\r
1141                         } // end for col\r
1142                 } // end for row\r
1144                 if (JCross[2]) { // show number of letters\r
1145                         qDetails = makeHiddenField('JCross_letters', letters) + qDetails;\r
1146                 }\r
1147                 if (JCross[1]) { // show penalties\r
1148                         qDetails = makeHiddenField('JCross_penalties', window.Penalties) + qDetails;\r
1149                 }\r
1151         }\r
1152         return qDetails;\r
1154 function GetJCrossClue(id) {\r
1155         var obj = (document.getElementById) ? document.getElementById(id) : null;\r
1156         return (obj) ? GetChildNodesText(obj, 'Clue') : '';\r
1158 function GetJCrossWord(a, r, c, goDown) {\r
1159         // a is a 2-dimensional array of letters, r is a row number, c is a column number\r
1160         var s = '';\r
1161         while (r<a.length && c<a[r].length && a[r][c]) {\r
1162                 s += a[r][c];\r
1163                 if (goDown) {\r
1164                         r++;\r
1165                 } else {\r
1166                         c++;\r
1167                 }\r
1168         }\r
1169         return s;\r
1171 function GetJMatchQuestionDetails(v) {\r
1172         var qDetails = '';\r
1174         // HP5.5 uses "I" for v5 and v6 JMatch quizzes\r
1175         var hp5 = (window.I) ? true : false;\r
1177         // check the quiz version\r
1178         if (hp5 || v==6 || v==6.1) {\r
1180                 if (JMatch[1] && v==6.1) { // attempts\r
1181                         qDetails += makeHiddenField('JMatch_attempts', Penalties+1);\r
1182                 }\r
1184                 // get number of questions\r
1185                 var max_q = (hp5 || v==6) ? Status.length : F.length;\r
1187                 // get details for each question\r
1188                 for (var q=0; q<max_q; q++) {\r
1190                         // format 'Q' (a padded, two-digit version of 'q')\r
1191                         var Q = getQ('JMatch', q);\r
1193                         // add separator, if required\r
1194                         if (JMatch[0] && (JMatch[1] || JMatch[2] || JMatch[3])) {\r
1195                                 qDetails += makeSeparator(Q);\r
1196                         }\r
1197                         if (JMatch[1] && (hp5 || v==6)) { // attempts\r
1198                                 qDetails += makeHiddenField(Q+'attempts', Status[q][1]);\r
1199                         }\r
1200                         if (JMatch[2]) { // LHS text\r
1201                                 var x = (hp5) ? I[q][0] : (v==6) ? GetJMatchText(q, 'LeftItem') : F[q][0];\r
1202                                 qDetails += makeHiddenField(Q+'lhs', x);\r
1203                         }\r
1204                         if (JMatch[3]) { // RHS text\r
1205                                 var x = (hp5) ? I[q][1] : (v==6) ? GetJMatchText(q, 'RightItem') : GetJMatchRHS(q);\r
1206                                 qDetails += makeHiddenField(Q+'rhs', x);\r
1207                         }\r
1208                 } // end for\r
1209         }\r
1210         return qDetails;\r
1212 function GetJMatchText(q, className) {\r
1213         var obj = (document.getElementById) ? document.getElementById('Questions') : null;\r
1214         return (obj) ? GetChildNodesText(obj.childNodes[q], className) : '';\r
1216 function GetJMatchRHS(q) { // Drag-and-drop only (v==6.1)\r
1217         var max_i = (window.F && window.D) ? F.length : 0;\r
1218         for (var i=0; i<max_i; i++) {\r
1219                 if (D[i][2]==F[q][1]) break;\r
1220         }\r
1221         return (i<max_i) ? D[i][0] : '';\r
1223 function GetJMixQuestionDetails(v) {\r
1224         qDetails = '';\r
1226         // check the quiz version\r
1227         if (v==5 || v==6 || v==6.1) {\r
1229                 var A = Answers.length;\r
1230                 for (var a=0; a<A; a++) {\r
1231                         var G = Answers[a].length;\r
1232                         for (var g=0; g<G; g++) {\r
1233                                 if (Answers[a][g] != GuessSequence[g]) break;\r
1234                         }\r
1235                         if (g>=G) break; \r
1236                 }\r
1237                 var isWrong = (a>=A);\r
1239                 // format 'Q' (a padded, two-digit version of 'q')\r
1240                 var Q = getQ('JMix', 0);\r
1242                 // add separator, if required\r
1243                 if (JMix[0]) qDetails += makeSeparator(Q);\r
1245                 // add 'score' for this question\r
1246                 var score = isWrong ? 0 : ((Segments.length-Penalties)/Segments.length);\r
1247                 qDetails += makeHiddenField(Q+'score', Math.floor(score*100)+'%');\r
1249                 if (JMix[1]) { // number of wrong guesses\r
1250                         qDetails += makeHiddenField(Q+'wrongGuesses', Penalties);\r
1251                 }\r
1252                 if (JMix[2]) { // right answer\r
1253                         qDetails += makeHiddenField(Q+'right', GetJMixSequence(Answers[isWrong ? 0 : a]));\r
1254                 }\r
1255                 if (JMix[3] && isWrong) { // wrong answer\r
1256                         qDetails += makeHiddenField(Q+'wrong', GetJMixSequence(GuessSequence));\r
1257                 }\r
1258         }\r
1259         return qDetails;\r
1261 function GetJMixSequence(indexes) {\r
1262         var s = new Array();\r
1263         for (var i=0; i<indexes.length; i++) {\r
1264                 s[i] = JMix[4] ? indexes[i] : GetJMixSegmentText(indexes[i]);\r
1265         }\r
1266         return s;\r
1268 function GetJMixSegmentText(index){\r
1269         var i_max = Segments.length;\r
1270         for (var i=0; i<i_max; i++) {\r
1271                 if (Segments[i][1] == index) break;\r
1272         }\r
1273         return (i<i_max) ? Segments[i][0] : '';\r
1275 function GetJQuizQuestionDetails(v) {\r
1276         var qDetails = '';\r
1278         // HP5.5 uses "Status" for v5 and v6 JMatch quizzes (HP6 uses "State")\r
1279         var hp =  (window.Status) ? 5 : (window.State) ? 6 : 0;\r
1281         // check the quiz version\r
1282         if (hp) {\r
1284                 // get details for each question\r
1285                 var max_q = (hp==5) ? Status.length : State.length;\r
1286                 for (var q=0; q<max_q; q++) {\r
1288                         // skip this question if it was not used (HP6 v6 only)\r
1289                         if (hp==6 && !State[q]) continue;\r
1291                         // format 'Q' (a padded, two-digit version of 'q')\r
1292                         var Q = getQ('JQuiz', q);\r
1294                         // add separator\r
1295                         if (JQuiz[0]) qDetails += makeSeparator(Q);\r
1297                         if (hp==6 && JQuiz[11]) { // question type\r
1298                                 var x = parseInt(I[q][2]);\r
1299                                 x = (x==0) ? 'multiple-choice' : (x==1) ? 'short-answer' : (x==2) ? 'hybrid' : (x==3) ? 'multi-select' : 'n/a';\r
1300                                 qDetails += makeHiddenField(Q+'type', x);\r
1301                         }\r
1303                         // score (as %)\r
1304                         var x = (hp==5) ? Status[q][4]*10 : I[q][0]*State[q][0];\r
1305                         qDetails += makeHiddenField(Q+'score', Math.floor(x)+'%');\r
1307                         if (hp==6 && JQuiz[10]) { // weighting\r
1308                                 qDetails += makeHiddenField(Q+'weighting', I[q][0]);\r
1309                         }\r
1310                         if (JQuiz[1]) { // question text\r
1311                                 var x = (hp==5) ? I[q][0] : (document.getElementById) ? GetChildNodesText(document.getElementById('Q_'+q), 'QuestionText') : '';\r
1312                                 qDetails += makeHiddenField(Q+'question', x);\r
1313                         }\r
1314                         if (JQuiz[2]) { // student's correct answers\r
1315                                 var x = (hp==5) ? Status[q][3] : GetJQuizAnswerDetails(q, 2);\r
1316                                 qDetails += makeHiddenField(Q+'correct', x);\r
1317                         }\r
1318                         if (JQuiz[3]) { // ignored and wrong answers\r
1319                                 var x = (hp==5) ? '' : GetJQuizAnswerDetails(q, 1);\r
1320                                 if (hp==5) {\r
1321                                         for (var i=0; i<I[q][1].length; i++) {\r
1322                                                 if (I[q][1][i][0] && (I[q][1][i][0].toUpperCase() != Status[q][3].toUpperCase())) {\r
1323                                                         x += ((x ? ',' : '') + I[q][1][i][0]);\r
1324                                                 }\r
1325                                         }\r
1326                                 }\r
1327                                 if (DB[0] || x) qDetails += makeHiddenField(Q+'other', x);\r
1328                         }\r
1329                         if (hp==6 && JQuiz[7]) { // all selected answers\r
1330                                 var x = GetJQuizAnswerDetails(q, 0);\r
1331                                 qDetails += makeHiddenField(Q+'selected', x);\r
1332                         }\r
1333                         if (hp==6 && JQuiz[8]) { // wrong answers\r
1334                                 var x = GetJQuizAnswerDetails(q, 3);\r
1335                                 qDetails += makeHiddenField(Q+'wrong', x);\r
1336                         }\r
1337                         if (hp==6 && JQuiz[9]) { // ignored answers\r
1338                                 var x = GetJQuizAnswerDetails(q, 4);\r
1339                                 qDetails += makeHiddenField(Q+'ignored', x);\r
1340                         }\r
1341                         if (JQuiz[4]) { // number of hints\r
1342                                 var x = (hp==5) ? Status[q][2] : State[q][4];\r
1343                                 qDetails += makeHiddenField(Q+'hints', x);\r
1344                         }\r
1345                         if (JQuiz[5]) { // number of checks of incorrect answers\r
1346                                 var x = (hp==5) ? Status[q][1] : (State[q][2]-1);\r
1347                                 qDetails += makeHiddenField(Q+'checks', x);\r
1348                         }\r
1349                 } // end for\r
1350         } // end if\r
1352         return qDetails;\r
1354 function GetChildNodesText(obj, className) {\r
1355         // search this node (obj) and its child nodes and \r
1356         // return all text under node with required classname\r
1357         var txt = '';\r
1358         if (obj) {\r
1359                 if (className && obj.className==className) {\r
1360                         className = '';\r
1361                 }\r
1362                 if (className=='' && obj.nodeType==3) { // text node\r
1363                         txt = obj.nodeValue + ' '; // html entities\r
1364                 }\r
1365                 if (obj.childNodes) {\r
1366                         for (var i=0; i<obj.childNodes.length; i++) {\r
1367                                 txt += GetChildNodesText(obj.childNodes[i], className);\r
1368                         }\r
1370                 }\r
1371         }\r
1372         return txt;\r
1374 function GetJQuizAnswerDetails(q, flag) {\r
1375         // flag : the type of information required about the student's answers\r
1376         //      0 : all student's answers\r
1377         //      1 : student's wrong and ignored answers\r
1378         //      2 : student's correct answers\r
1379         //      3 : student's wrong answers\r
1380         //      4 : ignored answers\r
1382         var x = State[q][5];\r
1384         if (I[q][2]=='3') { // multi-select\r
1386                 if (flag==4) {\r
1387                         var x = new Array();\r
1388                 } else {\r
1389                         // get required part of 'x' and convert to array\r
1390                         var i = x.lastIndexOf('|');\r
1391                         var x = x.substring((flag==2 ? (i+1) : 1), ((flag==0 || flag==2) ? x.length : i)).split('|');\r
1392                 }\r
1393                 for (var i=0; i<x.length; i++) {\r
1394                         var a = new Array();\r
1395                         for (var ii=0; ii<x[i].length; ii++) {\r
1396                                 if (x[i].charAt(ii)=='Y') {\r
1397                                         var s = JQuiz[6] ? String.fromCharCode(97+ii) : I[q][3][ii][0];\r
1398                                         if (s && s.replace && window.RegExp) {\r
1399                                                 s = s.replace(new RegExp('\\+', 'g'), '&#43;');\r
1400                                         }\r
1401                                         a.push(s);\r
1402                                 }\r
1403                         }\r
1404                         x[i] = a.join('+');\r
1405                 }\r
1407         } else if (x) { // multiple-choice, short-answer and hybrid \r
1409                 // remove trailing comma and convert to array\r
1410                 x = x.substring(0, x.length-1).split(',');\r
1412                 if (flag) {\r
1413                         var a = new Array();\r
1414                         if (flag==1 || flag==2 || flag==3) {\r
1415                                 for (var i=0; i<x.length; i++) {\r
1416                                         var ii = I[q][3][(x[i].charCodeAt(0)-65)][2];\r
1417                                         if(((flag==1 || flag==2) && ii==1) || (flag==3 && ii==0)) a.push(x[i]);\r
1418                                 }\r
1419                         }\r
1420                         if (flag==1) {\r
1421                                 x = a;\r
1422                                 a = new Array();\r
1423                         }\r
1424                         if (flag==1 || flag==4) {\r
1425                                 for (var i=0; i<I[q][3].length; i++) {\r
1426                                         var s = String.fromCharCode(65+i);\r
1427                                         for (var ii=0; ii<x.length; ii++) {\r
1428                                                 if (x[ii]==s) break;\r
1429                                         }\r
1430                                         if (ii==x.length) a.push(s);\r
1431                                 }\r
1432                         }\r
1433                         x = a;\r
1434                 }\r
1436                 // convert answer indexes to values, if required\r
1437                 if (JQuiz[6]==false) {\r
1438                         for (var i=0; i<x.length; i++) {\r
1439                                 var ii = x[i].charCodeAt(0) - 65;\r
1440                                 x[i] = I[q][3][ii][0];\r
1441                         }\r
1442                 }\r
1443         }\r
1444         return x;\r
1446 function GetRhubarbDetails(v) {\r
1447         qDetails = '';\r
1448         if (v==6) {\r
1449                 var Q = getQ('Rhubarb', 0);\r
1450                 if (document.title) { // use quiz title as question name\r
1451                         qDetails += makeHiddenField(Q+'name', document.title);\r
1452                 }\r
1453                 if (Rhubarb[0]) { // correct words\r
1454                         qDetails += makeHiddenField(Q+'correct', Words.length+' words');\r
1455                 }\r
1456                 if (Rhubarb[1]) { // incorrect words\r
1457                         // remove leading 'Wrong guesses: ' from Detail\r
1458                         var x = Detail.substring(15).split(' ');\r
1459                         qDetails += makeHiddenField(Q+'wrong', x);\r
1460                 }\r
1461         }\r
1462         return qDetails;\r
1464 function GetSequiturDetails(v) {\r
1465         qDetails = '';\r
1466         // there is no information available ... at the moment\r
1467         return qDetails;\r
1470 // *********************\r
1471 //   library functions\r
1472 // *********************\r
1474 function pad(i, l) {\r
1475         var s = (i+'');\r
1476         while (s.length<l) s = '0' + s;\r
1477         return s;\r
1479 function getQ(section, q) {\r
1480         // Q is a padded, two-digit version of the question number, 'q', prefixed by 'section'\r
1481         return section + '_q' + (q<9 ? '0' : '') + (q+1) + '_';\r
1483 function makeSeparator(Q) {\r
1484         return  is_LMS() ? '' : makeHiddenField(Q.substring(0, Q.length-1), '---------------------------------');\r
1486 function makeHiddenField(name, value) {\r
1487         var field = '';\r
1488         var t = typeof(value);\r
1489         if (t=='string') {\r
1490                 value = encode_entities(value);\r
1491         } else if (t=='object') {\r
1492                 var values = value;\r
1493                 var i_max = values.length;\r
1494                 value = '';\r
1495                 for (var i=0; i<i_max; i++) {\r
1496                         values[i] = trim(values[i]);\r
1497                         if (values[i]!='') {\r
1498                                 value += (i==0 ? '' : ',') +  encode_entities(values[i]);\r
1499                         }\r
1500                 }\r
1501         }\r
1502         if (is_LMS()) {\r
1503                 if (value && value.indexOf && value.indexOf('<')>=0 && value.indexOf('>')>=0) {\r
1504                         value = '<![CDATA[' + value + ']]>';\r
1505                 }\r
1506                 field = '<field><fieldname>' + name + '</fieldname><fielddata>' + value + '</fielddata></field>';\r
1507         } else {\r
1508                 field = '<INPUT type=hidden name="' + name + '" value="' + value + '">';\r
1509         }\r
1510         return field;\r
1512 function trim(s) {\r
1513         var i = 0;\r
1514         var ii = s.length;\r
1515         while (i<ii && s.charAt(i)==' ') {\r
1516                 i++;\r
1517         }\r
1518         while (ii>i && s.charAt(ii-1)==' ') {\r
1519                 ii--;\r
1520         }\r
1521         return s.substring(i, ii);\r
1523 function encode_entities(s_in) {\r
1524         var i_max = (s_in) ? s_in.length : 0;\r
1525         var s_out = '';\r
1526         for (var i=0; i<i_max; i++) {\r
1527                 var c = s_in.charCodeAt(i);\r
1528                 // 34 : double quote .......["] &amp;\r
1529                 // 38 : single quote .......['] &apos;\r
1530                 // 44 : comma ..............[,]\r
1531                 // 60 : left angle bracket .[<] &lt;\r
1532                 // 62 : right angle bracket [>] &gt;\r
1533                 // >=128 multibyte character\r
1534                 s_out += (c<128) ?  s_in.charAt(i) : ('&#x' + pad(c.toString(16), 4) + ';');\r
1535         }\r
1536         return s_out;\r
1539 // *********************\r
1540 //      initialization\r
1541 //        functions\r
1542 // *********************\r
1544 function getTime(obj) {\r
1545         obj = obj ? obj : new Date();\r
1547         // get year, month and day\r
1548         //      for an LMS : yyyy-mm-dd\r
1549         //      for email  : DayName MonthName dd yyyy\r
1550         var s = is_LMS() ? \r
1551                 obj.getFullYear() + '-' + pad(obj.getMonth()+1, 2) + '-' + pad(obj.getDate(), 2) : \r
1552                 MSG[16][obj.getDay()] + ' ' + MSG[17][obj.getMonth()] + ' ' + pad(obj.getDate(), 2) + ' ' + obj.getFullYear()\r
1553         ;\r
1555         // get hours, minutes and seconds (hh:mm:ss)\r
1556         s += ' ' + pad(obj.getHours(), 2) + ':' + pad(obj.getMinutes(), 2) + ':' + pad(obj.getSeconds(), 2);\r
1558         // get time difference\r
1559         //      for an LMS : +xxxx\r
1560         //      for email  : GMT+xxxx\r
1561         var x = obj.getTimezoneOffset(); // e.g. -540\r
1562         if (!isNaN(x)) {\r
1563                 s += ' ' + (is_LMS() ? '' : 'GMT') + (x<0 ? '+' : '-');\r
1564                 x = Math.abs(x);\r
1565                 s += pad(parseInt(x/60), 2) + pad(x - (parseInt(x/60)*60), 2);\r
1566         }\r
1568         return s;\r
1571 function getFunction(fn) {\r
1572         if (typeof(fn)=='string') {\r
1573                 fn = eval('window.' + fn);\r
1574         }\r
1575         return (typeof(fn)=='function') ? fn : null;\r
1577 function getFunctionCode(fn, extra) {\r
1578         var s = '';\r
1579         var obj = getFunction(fn);\r
1580         if (obj) {\r
1581                 s = obj.toString();\r
1582                 var i1 = s.indexOf('{')+1;\r
1583                 var i2 = s.lastIndexOf('}');\r
1584                 if (i1>0 && i1<i2) {\r
1585                         s = s.substring(i1, i2);\r
1586                 }\r
1587         }\r
1588         return s + (extra ? extra : '');\r
1590 function getFunctionArgs(fn) {\r
1591         var a = new Array();\r
1592         var obj = getFunction(fn);\r
1593         if (obj) {\r
1594                 var s = obj.toString();\r
1595                 var i1 = s.indexOf('(')+1;\r
1596                 var i2 = s.indexOf(')');\r
1597                 if (i1>0 && i1<i2) {\r
1598                         a = s.substring(i1, i2).split(',');\r
1599                 }\r
1600         }\r
1601         return (a.length) ? ('"'+ a.join('","') + '",') : '';\r
1603 function getPrompt(fn) {\r
1604         // the LoginPrompt is the text string in the first prompt(...) statement\r
1605         //      v5 : in the StartUp function\r
1606         //      v6 : in the GetUserName function\r
1607         // Note: netscape uses double-quote as delimiter, others use single quote\r
1608         var s = getFunctionCode(fn);\r
1609         var i1 = s.indexOf('prompt') + 8;\r
1610         var i2 = s.indexOf(s.charAt(i1-1), i1); \r
1612         var p = (i1>=8 && i2>i1) ? s.substring(i1, i2) : '';\r
1614         // make sure browser has decoded the unicode prompt properly\r
1615         // this check is mainly for ns4, but there may be others\r
1616         if (window.RegExp) {\r
1617                 var r = new RegExp('u([0-9A-F]{4})');\r
1618                 var m = r.exec(p);\r
1619                 while (m) {\r
1620                         p = p.replace(m[0], '&#' + parseInt(m[1], 16) + ';');\r
1621                         m = r.exec(p);\r
1622                 }\r
1623         }\r
1624         return p;\r
1626 function getStartUpCode(fn) {\r
1627         // the main initialization code comes from the StartUp function\r
1628         //      v5 : the code before "UserName", if any, \r
1629         //           and the code after the 2nd subsequent '}'\r
1630         //      v6 : the code before and after 'GetUserName();' \r
1631         //           i.e. all the code except the call to 'GetUserName();'\r
1632         var s = getFunctionCode(fn);\r
1633         var i1 = s.indexOf('GetUserName();');\r
1634         if (i1>=0) { // v6 \r
1635                 var i2 = i1 + 14;\r
1636         } else { // v5\r
1637                 var i1 = s.indexOf('UserName');\r
1638                 var i2 = s.indexOf('}', s.indexOf('}', i1+8)+1)+1;\r
1639         }\r
1640         return (0<i1 && i1<i2) ? s.substring(0, i1) + s.substring(i2) : '';\r
1642 function is_LMS() {\r
1643         return document.forms['store'] ? true : false;\r
1647 // ***************\r
1648 //  fix IE5 and NS6\r
1649 // ***************\r
1651 // add Array.push if required (allows v6 quizzes to run on ie5win)\r
1652 if (Array.prototype && Array.prototype.push==null) {\r
1653         Array.prototype.push = new Function("x", "this[this.length]=x");\r
1656 // add attachEvent function, if required (allows HP5 v6 quizzes to run on ie5mac)\r
1657 //      NOTE: to allow v6 quizzes on ie5mac, the following code \r
1658 //      needs to be inserted BEFORE the Hot Potatoes javascript\r
1659 if (window.attachEvent==null) {\r
1660         window.attachEvent = new Function('evt', 'fn', 'eval("window."+evt+"="+fn)');\r
1662 if (document.attachEvent==null) {\r
1663         document.attachEvent = new Function('evt', 'fn', 'eval("document."+evt+"="+fn)');\r
1666 // fix the ShowMessage function for NS6\r
1667 // by removing calls to a button's "focus()" method\r
1668 if (navigator.userAgent.indexOf("Netscape6")>=0 && window.ShowMessage) {\r
1669         var s = ShowMessage.toString();\r
1670         var r = new RegExp('document\\.getElementById\\((\'|")FeedbackOKButton(\'|")\\)\\.focus\\(\\);', 'gi');\r
1671         s = s.substring(s.indexOf('{')+1, s.lastIndexOf('}')).replace(r, '');\r
1672         window.ShowMessage = new Function('Feedback', s);\r
1675 // ns6.0 (in JMix at least) has an error in the FocusAButton function too\r
1676 // this could be fixed as follows ...\r
1677 //if (window.FocusAButton) {\r
1678 //      window.FocusAButton = new Function('return true');\r
1679 //}\r
1680 // however, ns6.0 then crashes completely when the mouse moves over a link, so don't bother\r
1682 // Hot Potatoes quiz sniffing\r
1684 // === v3 ===\r
1685 // JBC uses "QuizForm", which contains elements called "Q*_**" (* and ** start at 1)\r
1686 // JCloze uses "Cloze" form\r
1687 // JCross uses "Crossword" form\r
1688 // JMatch uses "QuizForm", which contains elements called "1,2,3..x" and "x+1,x+2...",\r
1689 //      and "CheckForm" form, which contains an element called "ScoreBox"\r
1690 //      it is also the only HP quiz type to use an array called "CorrectAnswers"\r
1691 // JQuiz uses "QForm*" forms (* starts at 1), which each contain an element called "Guess"\r
1693 // === v4 ===\r
1694 // JBC uses "QForm" form in "QuestionDiv", which contains elements called "FB* (* starts at 0)"\r
1695 // JCloze uses "Cloze" form in "QuestionDiv"\r
1696 // JCross uses "Crossword" form in "CWDiv"\r
1697 // JMatch uses "ExCheck" form in "TitleDiv"\r
1698 // (no JMix in hp4)\r
1699 // JQuiz uses "QForm" form in "QuestionDiv", which contains an element called "Answer"\r
1701 // === v5 ===\r
1702 // JBC uses "QForm" form, which contains elements called "FB_*_**" (* and ** start at 0)\r
1703 // JCloze uses "Cloze" form\r
1704 // JCross writes out "AnswerForm" from a variable called "GetAnswerOpener"\r
1705 //      HP5.3: uses "AnswerForm" in "BottomFrame" \r
1706 //      HP5.5: uses "AnswerForm" in "TopFrame", but it is only there when an answer is being input\r
1707 // JMatch uses "QForm" form, which contains elements called "sel*" (which disappear by the time the quiz is finished)\r
1708 // JMix uses "ButtonForm"\r
1709 // JQuiz uses "QForm*" and "Buttons*" (one for each question)\r
1711 // === v6 ===\r
1712 // JBC uses "QForm" form (elements have no name or id)\r
1713 // JCloze uses "Cloze" form (elements have no name or id)\r
1714 // JCross does not use any forms, \r
1715 //      HP5: has "GridDiv" in "MainDiv"\r
1716 //      HP6: has "Clues" table in "MainDiv"\r
1717 // JMatch has "MatchDiv" in "MainDiv"\r
1718 //      HP5: uses "QForm" form, which contains elements called "sel*"\r
1719 //      HP6: uses "QForm" form, which contains elements called "s*_**"\r
1720 // JMix does not use any forms, but has "SegmentDiv" in "MainDiv"\r
1721 // JQuiz \r
1722 //      HP5: uses "QForm" form, which contains an element called "Guess"\r
1723 //      HP6: has "Questions" ordered list in "MainDiv"\r
1725 // === v6+ ===\r
1726 // JMatch has DIVs called "D*" and  "F*" (* starts at 0)\r
1727 // JMix has DIVs called "D*" and  "Drop*" (* starts at 0)\r
1729 // useful sniffing tools (Cut and Paste to browser address box)\r
1730 //javascript:var s="";var x=new quiz_obj();for(X in x)s+=","+X+"="+x[X];alert(s.substring(1));\r
1731 //javascript:var s="";var x=document.layers;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))\r
1732 //javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))\r
1733 //javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))\r
1734 //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))\r
1735 //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))\r
1737 function sniff_quiz() {\r
1738         // "sniff" (=detect) the quiz's type and intended browser version\r
1739         // and cache the values in a global variable caleld "quiz"\r
1741         // quiz type\r
1742         //      0 : unknown\r
1743         //      1 : jbc\r
1744         //      2 : jcloze\r
1745         //      3 : jcross\r
1746         //      4 : jmatch\r
1747         //      5 : jmix\r
1748         //      6 : jquiz\r
1749         //      7 : rhubarb (TexToys)\r
1750         //      8 : Sequitur (TexToys)\r
1752         // intended browser version\r
1753         //      3   : ns3, ie3 (frames)\r
1754         //      4   : ns4, ie4 (cross browser dhtml)\r
1755         //      5   : ie5 (frames, send results via CGI)\r
1756         //      6   : ie6, op7, gecko (w3 standards)\r
1757         //      6.1 : "drag and drop" versions of JMatch and JMix v6\r
1759         // create the global "quiz" object, if necessary\r
1760         if (!window.quiz) window.quiz = new Object();\r
1762         // check the version and type are not already set\r
1763         if (!quiz.v || !quiz.t) {\r
1765                 // initialize version and type\r
1766                 var v = 0;\r
1767                 var t = 0; \r
1769                 // set shortcuts to DOM objects\r
1770                 var d = document;\r
1771                 var f = d.forms;\r
1773                 if (f.QuizForm && f.CheckForm && self.CorrectAnswers) {\r
1774                         v = 3;\r
1775                         t = 4; // jmatch\r
1777                 } else if (self.FeedbackFrame && self.CodeFrame) {\r
1778                         v = 3;\r
1779                         f = CodeFrame.document.forms;\r
1780                         t = (f.QuizForm) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.QForm1) ? 6 : 0;\r
1782                 } else if (self.DynLayer) {\r
1783                         v = 4;\r
1784                         if (d.layers) {\r
1785                                 // for NS4, adjust "f" to point to a forms object in a layer\r
1786                                 var lyr = d.QuestionDiv || d.CWDiv || d.TitleDiv || null;\r
1787                                 if (lyr) f = lyr.document.forms;\r
1788                         }\r
1789                         t = (f.QForm && f.QForm.FB0) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.ExCheck) ? 4 : (f.QForm && f.QForm.Answer) ? 6 : 0;\r
1791                 } else if (self.TopFrame && self.BottomFrame) {\r
1792                         v = 5;\r
1793                         f = BottomFrame.document.forms;\r
1794                         t = (f.QForm && f.QForm.elements[0].name.substring(0,3)=='FB_') ? 1 : (f.Cloze) ? 2 : (self.GetAnswerOpener && GetAnswerOpener.indexOf('AnswerForm')>=0) ? 3 : (f.QForm && self.RItems) ? 4 : (f.ButtonForm) ? 5 : (f.QForm0 && f.Buttons0) ? 6 : 0;\r
1796                 } else if (GetObj(d, 'MainDiv')) {\r
1797                         v = 6;\r
1798                         var obj = (f.QForm) ? f.QForm.elements : null;\r
1799                         t = (obj && obj.length>0 && obj[0].id=='') ? 1 : (f.Cloze) ? 2 : (GetObj(d, 'GridDiv') || GetObj(d, 'Clues')) ? 3 : GetObj(d, 'MatchDiv') ? 4 : GetObj(d, 'SegmentDiv') ? 5 : ((f.QForm && f.QForm.Guess) || GetObj(d, 'Questions')) ? 6 : 0;\r
1801                 } else if (GetObj(d, 'D0')) {\r
1802                         v = 6.1; // drag and drop (HP5 and HP6)\r
1803                         t = (GetObj(d, 'F0')) ? 4 : (GetObj(d, 'Drop0')) ? 5 : 0;\r
1805                 } else if (window.Words && f.Rhubarb) {\r
1806                         v = 6;\r
1807                         t = 7; // rhubarb (TexToys)\r
1809                 } else if (window.Segments && GetObj(d, 'Story')) {\r
1810                         v = 6;\r
1811                         t = 8; // sequitur (TexToys)\r
1813                 }\r
1815                 if (v) quiz.v = v; // intended browser version\r
1816                 if (t) quiz.t = t; // quiz type\r
1817         }\r
1819 function get_quiz_type() {\r
1820         sniff_quiz();\r
1821         return quiz.t;\r
1823 function get_quiz_version() {\r
1824         sniff_quiz();\r
1825         return quiz.v;\r
1828 function all_finished(a, s, aa, ss) {\r
1829         // determine whether or not all quistions in a quiz are finished\r
1831         // a  : outer array\r
1832         // s  : condition, if any, on outer array\r
1833         // aa : inner array, if any\r
1834         // ss : condition, if any, on inner array\r
1836         // the arrays "a" and "aa" may be passed as arrays or strings to be eval(uated)\r
1837         // the conditions "s" and "ss" are specified as strings to be eval(uated)\r
1839         // assume a positive result\r
1840         var r = true;\r
1842         // set length of outer array. if any\r
1843         var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;\r
1845         // loop through outer array\r
1846         for (var i=0; i<l; i++) {\r
1848                 // do outer condition, if any\r
1849                 if (s && eval(s)) r = false;\r
1851                 // set length of inner array, if any\r
1852                 var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;\r
1854                 // loop through inner array. checking inner condition\r
1855                 for (var ii=0; ii<ll; ii++) {\r
1856                         if (ss && eval(ss)) r = false;\r
1857                 }\r
1858         }\r
1859         return r;\r
1862 function is_finished() {\r
1864         // assume false result\r
1865         var r = false; \r
1867         var t = get_quiz_type();\r
1868         var v = get_quiz_version();\r
1870         if (t==1) { // jbc\r
1872                 if (v==3) r = all_finished(DoneStatus, "i>0 && a[i]=='0'");\r
1873                 else if (v==4) r = all_finished(DoneStatus, "a[i]==0");\r
1874                 else if (v==5 || v==6) r = all_finished(Status, "a[i][0]==0");\r
1877         } else if (t==2) { // jcloze\r
1879                 if (v==3 || v==4 || v==5 || v==6) r = all_finished(I, "CheckAnswer(i)==-1");\r
1880                 // also:   else if (v==5 || v==6) r = all_finished(State, "a[i][4]!=1")\r
1882         } else if (t==3) { // jcross\r
1884                 if (v==3) r = all_finished(document.Crossword.elements, "ConvertCase(is.mac?unescape(MacStringToWin(a[i].value)):a[i].value,1)!=Letters[i]");\r
1885                 else if (v==4) r = all_finished(WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0) != a[i].charAt(0)");\r
1886                 else if (v==5) r = all_finished(L, "", "L[i]", "L[i][ii] && L[i][ii]!=G[i][ii]");\r
1888         } else if (t==4) { // jmatch\r
1890                 if (v==3) r = all_finished(CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex != a[i]");\r
1891                 else if (v==4) r = all_finished(Draggables, "a[i].correct!='1'");\r
1892                 else if (v==5) r = all_finished(I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]<1 && GetAnswer(i)!=I[i][3]");\r
1893                 else if (v==6) r = all_finished(D, "D[i][2]==0 || D[i][2]!=D[i][1]");\r
1895         } else if (t==5) { // jmix\r
1897                 // there was no v3 or v4 of JMix\r
1898                 if (v==5 || v==6) r = !all_finished(Answers, "a[i].join(',')=='" + GuessSequence.join(',') + "'");\r
1900         } else if (t==6) { // jquiz\r
1902                 if (v==3 || v==4) r = all_finished(State, "a[i][0]==0");\r
1903                 else if (v==5 || v==6) r = all_finished(State, "a[i] && a[i][0]<0");\r
1905         } else if (t==7) { // rhubarb\r
1906                 if (v==6) r = all_finished(DoneList, "a[i]==1");\r
1908         } else if (t==8) { // sequitur\r
1909                 if (v==6) r = (CurrentNumber==TotalSegments || AllDone);\r
1910         }\r
1912         return r; // result\r
1915 function GetObj(d, id) {\r
1916         return d.getElementById ? d.getElementById(id) : d.all ? d.all[id] : d[id];\r
1919 // **************\r
1920 //  initialization\r
1921 // **************\r
1923 if (window.Finish==null) { // v3, v4 and v5\r
1924         // modify the function which writes feedback to call Finish() if the quiz is finished\r
1925         //      usually this is the WriteFeedback()\r
1926         //      but v3 of JMatch uses CheckAnswer()\r
1927         var f = window.WriteFeedback ? 'WriteFeedback' : 'CheckAnswer';\r
1928         var s = getFunctionCode(f, 'if(is_finished())Finish();');\r
1929         var a = getFunctionArgs(f);\r
1930         eval('window.' + f + '=new Function(' + a + 's)');\r
1933 // the standard Finish() function\r
1934 // for v6, this overwrites the original function\r
1935 function Finish(){\r
1936         var f = document.store;\r
1937         if (f) {\r
1938                 // hotpot use "Score", TexToys use "FinalScore"\r
1939                 var mark = (window.Score ? Score : window.FinalScore ? FinalScore : 0);\r
1940                 f.starttime.value = getTime(Start_Time);\r
1941                 f.endtime.value = getTime();\r
1942                 f.mark.value = mark;\r
1943                 f.detail.value = '<?xml version="1.0"?><hpjsresult><fields>'+GetQuestionDetails()+'</fields></hpjsresult>';\r
1944                 f.submit();\r
1945         }\r
1948 // create form to send results\r
1949 if (DB[7] && DB[8] && !is_LMS()) { \r
1950         ResultForm = ''\r
1951                 + '<html><body>'\r
1952                 + '<form name="Results" action="" method="post" enctype="x-www-form-encoded">'\r
1953                 +       makeHiddenField('recipient', '')\r
1954                 +       makeHiddenField('subject', '')\r
1955                 +       makeHiddenField('Exercise', '')\r
1956                 +       makeHiddenField('realname', '')\r
1957                 +       makeHiddenField('Score', '')\r
1958                 +       makeHiddenField('Start_Time', '')\r
1959                 +       makeHiddenField('End_Time', '')\r
1960                 +       makeHiddenField('title', 'Thanks!')\r
1961                 + '</form>'\r
1962                 + '</body></html>'\r
1963         ;\r
1965 // reassign the StartUp function\r
1966 var p = getPrompt(window.GetUserName || window.StartUp);\r
1967 var c = getStartUpCode(window.StartUp);\r
1968 if (p && c) {\r
1969         window.StartUp = new Function('QuizLogin("' + p + '")');\r
1970         window.StartQuiz = new Function('if(!is_LMS()){' + c + '}');\r
1973 // reassign the SendResults function\r
1974 window.SendResults = SendAllResults;\r
1976 // set start time\r
1977 var Start_Time = new Date();\r
1979 //-->\r