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
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
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
14 * This software is provided "AS IS" without a warranty of any kind.
\r
16 * Documentation and downloads may be available from:
\r
17 * http://www.kanazawa-gu.ac.jp/~gordon/research/hot-potatoes/
\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
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
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
71 // Database (for use with BFormMail)
\r
74 if (window.DB==null) {
\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
118 if (window.JBC==null) {
\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
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
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
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
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
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
225 // 'L', 'C' ('CL') and 'G' are all 2-dimensional arrays (rows x cols)
\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
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
255 // [2] : fixed (=not jumbled) flag
\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
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
278 // D : array of JMATCH_DRAGGABLE_ITEMs
\r
279 // JMATCH_DRAGGABLE_ITEM
\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
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
306 // [1] : order in sequence
\r
308 // GuessSequence : array of 'order in sequence' numbers
\r
309 // Penalties : number of incorrect guesses
\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
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
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
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
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
410 if (window.Sequitur==null) {
\r
411 Sequitur = new Array();
\r
418 if (window.MSG==null) {
\r
425 MSG[3] = 'Password';
\r
426 MSG[4] = 'Cookies';
\r
429 MSG[5] = 'Start the Quiz';
\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
450 MSG[18] = 'Please enable pop-up windows on your browser.';
\r
452 // browser specific instuctions on how to enable popup windows
\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
460 MSG[18] += '\n\n' + 'on Safari menu, uncheck "Block Pop-Up Windows"';
\r
461 } else if (s.indexOf('firebird')>=0) {
\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
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
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
505 + '<BODY bgColor="#cccccc" onLoad="opener.setFocus(self)">'
\r
506 + '<FORM onSubmit="'
\r
508 + 'self.expiry=null;'
\r
510 if (Login[4]) { // cookie expiry
\r
511 html += "opener.checkOK(self,'CookieExpiry');";
\r
513 if (Login[0]) { // user name
\r
514 html += "opener.checkOK(self,'UserName');";
\r
516 if (Login[1]) { // user ID
\r
517 html += "opener.checkOK(self,'UserID');";
\r
519 if (Login[2]) { // user email
\r
520 html += "opener.checkOK(self,'UserEmail');";
\r
522 if (Login[3]) { // quiz password
\r
523 html += "opener.checkOK(self,'Password');";
\r
526 + 'opener.StartQuiz();'
\r
529 + 'if(isNaN(self.tries))self.tries=0;'
\r
531 + 'if(self.tries<3){'
\r
532 + 'opener.setFocus(self);'
\r
534 + "alert(opener.MSG[11]);"
\r
535 + 'opener.goBack();'
\r
543 + '<CAPTION>' + LoginPrompt + '</CAPTION>';
\r
545 if (Login[0]) { // user name
\r
546 var v = getCookie(self, 'UserName');
\r
548 + '<TH align=right nowrap>' + MSG[0] + ' :</TH>'
\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
565 html += '<SELECT name=UserName size=1>'
\r
566 + '<OPTION value=""></OPTION>'
\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
573 html += makeOption(Login[0][i][0], v, Login[0][i][1]);
\r
575 html += '</SELECT>';
\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
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
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
593 if (Login[4]) { // cookie lifespan
\r
594 var v = getCookie(self, 'CookieExpiry');
\r
596 + '<TH align=right nowrap>' + MSG[4] + ' :</TH>'
\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
609 + '<TH> </TH>'
\r
611 + '<INPUT type=submit value="' + MSG[5] + '"> '
\r
612 + '<INPUT type=button value="' + MSG[6] + '" onClick="opener.goBack();self.close();">'
\r
615 + '</TABLE></FORM></BODY></HTML>'
\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
628 } else { // no Login required
\r
629 window.UserName = window.UserID = window.UserEmail = window.Password = '';
\r
630 window.StartQuiz();
\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
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
655 if (w.ok) alert(MSG[12]);
\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
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
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
680 obj.value = v = '';
\r
681 if (w.ok) alert(msg);
\r
687 function getPassword(w) {
\r
689 if (Login[3] && typeof(Login[3])=='string') {
\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
702 function setCookieExpiry(w, v) {
\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
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
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
736 + (attributes ? (attributes+',') : '')
\r
737 + 'WIDTH='+width+',HEIGHT='+height
\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
746 var w = window.open((ie_offline ? '' : url), name, attributes);
\r
748 // check window opened OK (user may have prevented popups)
\r
750 // center the window
\r
751 if (window.screen && width && height) {
\r
752 w.moveTo((W-width)/2, (H-height)/2);
\r
755 // add content, if required
\r
757 with (w.document) {
\r
764 if (ie_offline && url) w.location = url;
\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
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
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
834 w.location.href = url;
\r
836 } else { // browser can POST form ok
\r
840 } else { // unable to open popup window
\r
846 function isGuest() {
\r
847 // check username is not a "guest" user
\r
849 var n = getCookie(self, 'UserName').toLowerCase();
\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
855 for(var i=0; i<Login[5].length; i++) {
\r
856 if (n==Login[5][i].toLowerCase()) {
\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
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
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
881 form.db_fields.value = db_fields;
\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
888 if (Login[0]) { // user name
\r
889 // use 'realname' instead of a separate 'Name' field
\r
890 // sDetails += makeHiddenField('Name', window.UserName);
\r
892 if (Login[1]) { // user ID
\r
893 sDetails += makeHiddenField('ID', window.UserID);
\r
895 if (Login[2]) { // user email
\r
896 sDetails += makeHiddenField('email', window.UserEmail);
\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
903 ResultForm = ResultForm.replace(m[0], sDetails + m[0] + makeSeparator('Time_'));
\r
907 if (Login[3]) { // quiz password
\r
908 sDetails += makeHiddenField('Password', window.Password);
\r
909 ResultForm = replaceLast('</form>', sDetails + '</form>', ResultForm);
\r
912 function AddQuestionDetailsToResultForm() {
\r
913 var qDetails = GetQuestionDetails();
\r
915 // insert qDetails before the final </form> tag in the ResultForm
\r
916 ResultForm = replaceLast('</form>', qDetails + '</form>', ResultForm);
\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
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
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
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
959 if (ServerFields[i][1]) {
\r
960 s += makeHiddenField(ServerFields[i][0], ServerFields[i][1]);
\r
963 if (s) ResultForm = replaceLast('</form>', s + '</form>', ResultForm);
\r
966 function replaceLast(a, b, c) {
\r
967 // replace last occurrence of 'a' in 'c' with 'b'
\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
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
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
1020 if (JBC[2]) { // question text
\r
1021 qDetails += makeHiddenField(Q+'text', I[q][0]);
\r
1023 if (JBC[3] && (DB[0] || aDetails[0].length>0)) { // right
\r
1024 qDetails += makeHiddenField(Q+'right', aDetails[0]);
\r
1026 if (JBC[4] && (DB[0] || aDetails[1].length>0)) { // wrong
\r
1027 qDetails += makeHiddenField(Q+'wrong', aDetails[1]);
\r
1029 if (JBC[5] && (DB[0] || aDetails[2].length>0)) { // ignored
\r
1030 qDetails += makeHiddenField(Q+'ignored', aDetails[2]);
\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
1038 // add 'score' for this question
\r
1039 qDetails += makeHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');
\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
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
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
1078 if (DB[0] || ignored.length>0) qDetails += makeHiddenField(Q+'ignored', ignored);
\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
1085 if (DB[0] || ii>0) qDetails += makeHiddenField(Q+'wrong', wrong);
\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
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
1095 if (JCloze[6]) { // clue text
\r
1096 qDetails += makeHiddenField(Q+'clue_text', I[q][2]);
\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
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
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
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
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
1144 if (JCross[2]) { // show number of letters
\r
1145 qDetails = makeHiddenField('JCross_letters', letters) + qDetails;
\r
1147 if (JCross[1]) { // show penalties
\r
1148 qDetails = makeHiddenField('JCross_penalties', window.Penalties) + 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
1161 while (r<a.length && c<a[r].length && a[r][c]) {
\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
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
1197 if (JMatch[1] && (hp5 || v==6)) { // attempts
\r
1198 qDetails += makeHiddenField(Q+'attempts', Status[q][1]);
\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
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
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
1221 return (i<max_i) ? D[i][0] : '';
\r
1223 function GetJMixQuestionDetails(v) {
\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
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
1252 if (JMix[2]) { // right answer
\r
1253 qDetails += makeHiddenField(Q+'right', GetJMixSequence(Answers[isWrong ? 0 : a]));
\r
1255 if (JMix[3] && isWrong) { // wrong answer
\r
1256 qDetails += makeHiddenField(Q+'wrong', GetJMixSequence(GuessSequence));
\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
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
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
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
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
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
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
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
1318 if (JQuiz[3]) { // ignored and wrong answers
\r
1319 var x = (hp==5) ? '' : GetJQuizAnswerDetails(q, 1);
\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
1327 if (DB[0] || x) qDetails += makeHiddenField(Q+'other', x);
\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
1333 if (hp==6 && JQuiz[8]) { // wrong answers
\r
1334 var x = GetJQuizAnswerDetails(q, 3);
\r
1335 qDetails += makeHiddenField(Q+'wrong', x);
\r
1337 if (hp==6 && JQuiz[9]) { // ignored answers
\r
1338 var x = GetJQuizAnswerDetails(q, 4);
\r
1339 qDetails += makeHiddenField(Q+'ignored', x);
\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
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
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
1359 if (className && obj.className==className) {
\r
1362 if (className=='' && obj.nodeType==3) { // text node
\r
1363 txt = obj.nodeValue + ' '; // html entities
\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
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
1387 var x = new Array();
\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
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'), '+');
\r
1404 x[i] = a.join('+');
\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
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
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
1430 if (ii==x.length) a.push(s);
\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
1446 function GetRhubarbDetails(v) {
\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
1453 if (Rhubarb[0]) { // correct words
\r
1454 qDetails += makeHiddenField(Q+'correct', Words.length+' words');
\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
1464 function GetSequiturDetails(v) {
\r
1466 // there is no information available ... at the moment
\r
1470 // *********************
\r
1471 // library functions
\r
1472 // *********************
\r
1474 function pad(i, l) {
\r
1476 while (s.length<l) s = '0' + 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
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
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
1503 if (value && value.indexOf && value.indexOf('<')>=0 && value.indexOf('>')>=0) {
\r
1504 value = '<![CDATA[' + value + ']]>';
\r
1506 field = '<field><fieldname>' + name + '</fieldname><fielddata>' + value + '</fielddata></field>';
\r
1508 field = '<INPUT type=hidden name="' + name + '" value="' + value + '">';
\r
1512 function trim(s) {
\r
1514 var ii = s.length;
\r
1515 while (i<ii && s.charAt(i)==' ') {
\r
1518 while (ii>i && s.charAt(ii-1)==' ') {
\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
1526 for (var i=0; i<i_max; i++) {
\r
1527 var c = s_in.charCodeAt(i);
\r
1528 // 34 : double quote .......["] &
\r
1529 // 38 : single quote .......['] '
\r
1530 // 44 : comma ..............[,]
\r
1531 // 60 : left angle bracket .[<] <
\r
1532 // 62 : right angle bracket [>] >
\r
1533 // >=128 multibyte character
\r
1534 s_out += (c<128) ? s_in.charAt(i) : ('&#x' + pad(c.toString(16), 4) + ';');
\r
1539 // *********************
\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
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
1563 s += ' ' + (is_LMS() ? '' : 'GMT') + (x<0 ? '+' : '-');
\r
1565 s += pad(parseInt(x/60), 2) + pad(x - (parseInt(x/60)*60), 2);
\r
1571 function getFunction(fn) {
\r
1572 if (typeof(fn)=='string') {
\r
1573 fn = eval('window.' + fn);
\r
1575 return (typeof(fn)=='function') ? fn : null;
\r
1577 function getFunctionCode(fn, extra) {
\r
1579 var obj = getFunction(fn);
\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
1588 return s + (extra ? extra : '');
\r
1590 function getFunctionArgs(fn) {
\r
1591 var a = new Array();
\r
1592 var obj = getFunction(fn);
\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
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
1620 p = p.replace(m[0], '&#' + parseInt(m[1], 16) + ';');
\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
1637 var i1 = s.indexOf('UserName');
\r
1638 var i2 = s.indexOf('}', s.indexOf('}', i1+8)+1)+1;
\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
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
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
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
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
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
1722 // HP5: uses "QForm" form, which contains an element called "Guess"
\r
1723 // HP6: has "Questions" ordered list in "MainDiv"
\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
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
1769 // set shortcuts to DOM objects
\r
1773 if (f.QuizForm && f.CheckForm && self.CorrectAnswers) {
\r
1777 } else if (self.FeedbackFrame && self.CodeFrame) {
\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
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
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
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
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
1807 t = 7; // rhubarb (TexToys)
\r
1809 } else if (window.Segments && GetObj(d, 'Story')) {
\r
1811 t = 8; // sequitur (TexToys)
\r
1815 if (v) quiz.v = v; // intended browser version
\r
1816 if (t) quiz.t = t; // quiz type
\r
1819 function get_quiz_type() {
\r
1823 function get_quiz_version() {
\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
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
1862 function is_finished() {
\r
1864 // assume false result
\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
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
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
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
1948 // create form to send results
\r
1949 if (DB[7] && DB[8] && !is_LMS()) {
\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
1962 + '</body></html>'
\r
1965 // reassign the StartUp function
\r
1966 var p = getPrompt(window.GetUserName || window.StartUp);
\r
1967 var c = getStartUpCode(window.StartUp);
\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
1977 var Start_Time = new Date();
\r