~azzar1/unity/add-show-desktop-key

307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
1
/* IVLE - Informatics Virtual Learning Environment
2
 * Copyright (C) 2007-2008 The University of Melbourne
3
 *
4
 * This program is free software; you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation; either version 2 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program; if not, write to the Free Software
16
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
 *
18
 * Module: Tutorial system (client)
19
 * Author: Matt Giuca
20
 * Date: 25/1/2008
21
 */
22
325 by mattgiuca
tutorial: Added "run" button which submits the students code to the
23
/** User clicks "Run" button. Do an Ajax call and print the test output.
24
 */
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
25
function runexercise(exerciseid, filename)
325 by mattgiuca
tutorial: Added "run" button which submits the students code to the
26
{
27
    /* Get the source code the student is submitting */
537 by stevenbird
bugfix -- legacy of renaming problems to exercises; caused run button not to work
28
    var exercisediv = document.getElementById(exerciseid);
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
29
    var exercisebox = exercisediv.getElementsByTagName("textarea")[0];
30
    var code = exercisebox.value;
325 by mattgiuca
tutorial: Added "run" button which submits the students code to the
31
333 by mattgiuca
console.js: enter_line now accepts the line as an argument instead of reading
32
    /* Dump the entire file to the console */
663 by drtomc
console: start console lazily.
33
    var callback = function()
34
    {
35
        console_enter_line(code, "block");
36
    }
37
    start_server(callback)
333 by mattgiuca
console.js: enter_line now accepts the line as an argument instead of reading
38
    return;
325 by mattgiuca
tutorial: Added "run" button which submits the students code to the
39
}
40
41
/** Given a response object (JSON-parsed object), displays the result of the
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
42
 * test to the user. This modifies the given exercisediv's children.
325 by mattgiuca
tutorial: Added "run" button which submits the students code to the
43
 */
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
44
function handle_runresponse(exercisediv, runresponse)
325 by mattgiuca
tutorial: Added "run" button which submits the students code to the
45
{
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
46
    var runoutput = exercisediv.getElementsByTagName("textarea")[1];
325 by mattgiuca
tutorial: Added "run" button which submits the students code to the
47
    dom_removechildren(runoutput);
48
    runoutput.appendChild(document.createTextNode(runresponse.stdout));
49
}
50
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
51
/** User clicks "Submit" button. Do an Ajax call and run the test.
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
52
 * exerciseid: "id" of the exercise's div element.
53
 * filename: Filename of the exercise's XML file (used to identify the exercise
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
54
 *     when interacting with the server).
55
 */
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
56
function submitexercise(exerciseid, filename)
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
57
{
719 by mattgiuca
tutorial.js: If the result returned from Submit is a JSON parse error,
58
    var original_saved_status = get_saved_status(exerciseid);
705 by mattgiuca
tutorial (Python & JS)
59
    set_submit_status(exerciseid, filename, "Submitting...");
60
    set_saved_status(exerciseid, filename, "Saving...");
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
61
    /* Get the source code the student is submitting */
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
62
    var exercisediv = document.getElementById(exerciseid);
63
    var exercisebox = exercisediv.getElementsByTagName("textarea")[0];
64
    var code = exercisebox.value;
312 by mattgiuca
Full client-side testing - functional.
65
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
66
    var args = {"code": code, "exercise": filename, "action": "test"};
312 by mattgiuca
Full client-side testing - functional.
67
68
    /* Send the form as multipart/form-data, since we are sending a whole lump
69
     * of Python code, it should be treated like a file upload. */
559 by mattgiuca
Major JavaScript refactor: util.ajax_call is now asynchronous, not
70
    /* AJAX callback function */
71
    var callback = function(xhr)
72
        {
719 by mattgiuca
tutorial.js: If the result returned from Submit is a JSON parse error,
73
            var testresponse;
74
            try
75
            {
76
                testresponse = JSON.parse(xhr.responseText);
77
            }
78
            catch (ex)
79
            {
80
                alert("There was an error submitting or running your code. "
81
                    + "Please notify the administrators of this.");
82
                /* Since it failed, set the Save button back how it was. */
83
                set_saved_status(exerciseid, filename, original_saved_status);
84
                set_submit_status(exerciseid, filename, "Submit");
85
                return;
86
            }
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
87
            handle_testresponse(exercisediv, exerciseid, testresponse);
705 by mattgiuca
tutorial (Python & JS)
88
            set_saved_status(exerciseid, filename, "Saved");
89
            set_submit_status(exerciseid, filename, "Submit");
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
90
            /* Close the "view previous" area (force reload) */
91
            close_previous(exerciseid);
559 by mattgiuca
Major JavaScript refactor: util.ajax_call is now asynchronous, not
92
        }
93
    ajax_call(callback, "tutorialservice", "", args, "POST",
312 by mattgiuca
Full client-side testing - functional.
94
        "multipart/form-data");
95
}
96
698 by mattgiuca
Added Save feature to tutorial system.
97
/** User clicks "Save" button. Do an Ajax call to store it.
98
 * exerciseid: "id" of the exercise's div element.
99
 * filename: Filename of the exercise's XML file (used to identify the exercise
100
 *     when interacting with the server).
101
 */
102
function saveexercise(exerciseid, filename)
103
{
703 by mattgiuca
tutorial: (Python + Javascript)
104
    set_saved_status(exerciseid, filename, "Saving...");
698 by mattgiuca
Added Save feature to tutorial system.
105
    /* Get the source code the student is submitting */
106
    var exercisediv = document.getElementById(exerciseid);
107
    var exercisebox = exercisediv.getElementsByTagName("textarea")[0];
108
    var code = exercisebox.value;
109
110
    var args = {"code": code, "exercise": filename, "action": "save"};
111
112
    /* Send the form as multipart/form-data, since we are sending a whole lump
113
     * of Python code, it should be treated like a file upload. */
114
    /* AJAX callback function */
115
    var callback = function(xhr)
116
        {
117
            // XXX Maybe check to see if this worked?
703 by mattgiuca
tutorial: (Python + Javascript)
118
            set_saved_status(exerciseid, filename, "Saved");
698 by mattgiuca
Added Save feature to tutorial system.
119
        }
120
    ajax_call(callback, "tutorialservice", "", args, "POST",
121
        "multipart/form-data");
122
}
123
715 by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the
124
/** User clicks "Reset" button. Replace the contents of the user program text
125
 * box with the hidden "reset button backup" field, containing the original
126
 * partial fragment.
127
 * exerciseid: "id" of the exercise's div element.
718 by mattgiuca
Tutorial: Minor fixes wrt addition of reset button.
128
 * filename: Filename of the exercise's XML file (used to identify the exercise
129
 *     when interacting with the server).
715 by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the
130
 */
718 by mattgiuca
Tutorial: Minor fixes wrt addition of reset button.
131
function resetexercise(exerciseid, filename)
715 by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the
132
{
133
    conf_msg = "This will delete your solution to this exercise, and reset "
134
        + "it back to the default partial solution.\n\n"
135
        + "Are you sure you want to do this?";
136
    if (!confirm(conf_msg))
137
        return;
138
    /* Get the source code the student is submitting */
139
    var exercisediv = document.getElementById(exerciseid);
140
    var exercisebox = exercisediv.getElementsByTagName("textarea")[0];
141
    var resettextbox = document.getElementById("input_resettext_" + exerciseid);
717 by mattgiuca
Tutorial: Bugfix - Reset Text was not escaped, so bad, horribly bad things
142
    var text_urlencoded = resettextbox.value;
143
    /* Need to un-urlencode the value */
144
    exercisebox.value = decodeURIComponent(text_urlencoded);
718 by mattgiuca
Tutorial: Minor fixes wrt addition of reset button.
145
    /* We changed the text, so make Save button available, and autosave after
146
     * 10 seconds. */
147
    set_saved_status(exerciseid, filename, "Save");
715 by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the
148
}
149
703 by mattgiuca
tutorial: (Python + Javascript)
150
/* savetimers is a dict mapping exerciseIDs to timer IDs.
151
 * Its members indicate all exercises that have been modified but not saved.
152
 */
153
savetimers = {}
154
155
/** Changes whether an exercise is considered "saved" or not.
156
 * stat is a string which specifies the status, and also the button text.
157
 * If stat == "Save", then it indicates it is NOT saved. This will
158
 * enable the "Save" button, and set a timer going which will auto-save
159
 * after a set period of time (eg. 30 seconds).
160
 * Any other value will disable the "Save" button and disable the timer.
161
 * stat should be "Saving..." when the save request is issued, and "Saved"
162
 * when the response comes back.
163
 */
164
function set_saved_status(exerciseid, filename, stat)
165
{
166
    var timername = "savetimer_" + exerciseid;
167
    var button = document.getElementById("savebutton_" + exerciseid);
168
    var is_saved = stat != "Save";
169
    button.value = stat;
170
171
    /* Disable the timer, if it exists */
172
    if (typeof(savetimers[timername]) != "undefined")
173
    {
174
        clearTimeout(savetimers[timername]);
175
        savetimers[timername] = undefined;
176
    }
177
178
    if (is_saved)
179
    {
180
        /* Disable the button */
181
        button.disabled = true;
182
    }
183
    else
184
    {
185
        /* Enable the button */
186
        button.disabled = false;
187
        /* Create a timer which will auto-save when it expires */
188
        var save_string = "saveexercise(" + repr(exerciseid) + ", "
189
            + repr(filename) + ")"
190
        savetimers[timername] = setTimeout(save_string, 10000);
191
    }
192
}
193
719 by mattgiuca
tutorial.js: If the result returned from Submit is a JSON parse error,
194
/** Retrieves the saved status of a given exercise.
195
 * Returns "Save" if the exercise is modified and needs to be saved.
196
 * Returns "Saved" if the exercise has been saved and is unmodified.
197
 * Returns "Saving..." if the exercise is in the process of being saved
198
 *  (an ajax request is on its way).
199
 */
200
function get_saved_status(exerciseid)
201
{
202
    var button = document.getElementById("savebutton_" + exerciseid);
203
    return button.value;
204
}
205
705 by mattgiuca
tutorial (Python & JS)
206
/** Changes the state of the submit button, so it can appear disabled during
207
 * the submission process (to avoid multiple clicks).
208
 * stat is a string which specifies the status, and also the button text.
209
 * If stat == "Submit", then it indicates it is available for submission.
210
 * This will enable the "Submit" button.
211
 * Any other value (recommended: "Submitting...") will disable the "Submit"
212
 * button.
213
 */
214
function set_submit_status(exerciseid, filename, stat)
215
{
216
    var button = document.getElementById("submitbutton_" + exerciseid);
217
    button.disabled = stat != "Submit";
218
    button.value = stat;
219
}
220
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
221
/** Given a exercise div, return the testoutput div which is its child.
312 by mattgiuca
Full client-side testing - functional.
222
 * (The div which is its child whose class is "testoutput".
223
 */
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
224
function get_testoutput(exercisediv)
312 by mattgiuca
Full client-side testing - functional.
225
{
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
226
    var childs = exercisediv.childNodes;
312 by mattgiuca
Full client-side testing - functional.
227
    var i;
228
    var testoutput;
229
    for (i=0; i<childs.length; i++)
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
230
        if (childs[i].nodeType == exercisediv.ELEMENT_NODE &&
312 by mattgiuca
Full client-side testing - functional.
231
            childs[i].getAttribute("class") == "testoutput")
232
            return childs[i];
233
    return null;
234
}
235
236
/** Given a response object (JSON-parsed object), displays the result of the
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
237
 * test to the user. This modifies the given exercisediv's children.
312 by mattgiuca
Full client-side testing - functional.
238
 */
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
239
function handle_testresponse(exercisediv, exerciseid, testresponse)
312 by mattgiuca
Full client-side testing - functional.
240
{
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
241
    var testoutput = get_testoutput(exercisediv);
312 by mattgiuca
Full client-side testing - functional.
242
    var i, j;
243
    var ul;
244
    var case_ul;
245
    if (testoutput == null) return;     /* should not happen */
246
    dom_removechildren(testoutput);
247
248
    ul = document.createElement("ul");
249
    testoutput.appendChild(ul);
250
251
    if ("critical_error" in testresponse)
252
    {
253
        /* Only one error - and it's bad.
254
         * Just print and stop */
518 by stevenbird
Cleaned up display of test results:
255
        ul.appendChild(create_response_item("critical", 0,
312 by mattgiuca
Full client-side testing - functional.
256
            testresponse.critical_error.name,
257
            testresponse.critical_error.detail));
258
        return;
259
    }
260
261
    for (i=0; i<testresponse.cases.length; i++)
262
    {
263
        var testcase = testresponse.cases[i];
264
        if ("exception" in testcase)
265
        {
266
            /* User's code threw an exception */
518 by stevenbird
Cleaned up display of test results:
267
            fail_li = create_response_item("fail", 0, testcase.name);
312 by mattgiuca
Full client-side testing - functional.
268
            ul.appendChild(fail_li);
269
            /* Create a sub-ul to display the failing cases. */
270
            case_ul = document.createElement("ul");
271
            fail_li.appendChild(case_ul);
518 by stevenbird
Cleaned up display of test results:
272
            case_ul.appendChild(create_response_item("exception", 0,
312 by mattgiuca
Full client-side testing - functional.
273
                testcase.exception.name, testcase.exception.detail));
274
        }
275
        else if (testcase.passed)
276
        {
277
            /* All parts of the test case passed. Just report the overall case
278
             * passing. */
518 by stevenbird
Cleaned up display of test results:
279
	    ul.appendChild(create_response_item("pass", 0, testcase.name));
312 by mattgiuca
Full client-side testing - functional.
280
        }
281
        else
282
        {
518 by stevenbird
Cleaned up display of test results:
283
            var fail_li = create_response_item("fail", 0, testcase.name);
312 by mattgiuca
Full client-side testing - functional.
284
            ul.appendChild(fail_li);
285
            /* Create a sub-ul to display the failing cases. */
286
            case_ul = document.createElement("ul");
287
            fail_li.appendChild(case_ul);
288
            
289
            for (j=0; j<testcase.parts.length; j++)
290
            {
291
                var part = testcase.parts[j];
292
                if (part.passed)
293
                {
518 by stevenbird
Cleaned up display of test results:
294
                    case_ul.appendChild(create_response_item("pass", 1,
312 by mattgiuca
Full client-side testing - functional.
295
                        part.description));
296
                }
297
                else
298
                {
518 by stevenbird
Cleaned up display of test results:
299
                    case_ul.appendChild(create_response_item("fail", 1,
300
                        part.description /*, part.error_message */));
312 by mattgiuca
Full client-side testing - functional.
301
                }
302
            }
303
        }
304
    }
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
305
306
    /* Update the summary box (completed, attempts) with the new values we got
307
     * back from the tutorialservice.
710 by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top.
308
     * (Also update the balls in the table-of-contents).
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
309
     */
710 by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top.
310
    var toc_li = document.getElementById("toc_li_" + exerciseid);
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
311
    var summaryli = document.getElementById("summaryli_" + exerciseid);
312
    var summarycomplete = document.getElementById("summarycomplete_"
313
        + exerciseid);
314
    var summaryattempts = document.getElementById("summaryattempts_"
315
        + exerciseid);
710 by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top.
316
    toc_li.setAttribute("class",
317
        (testresponse.completed ? "complete" : "incomplete"));
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
318
    summaryli.setAttribute("class",
319
        (testresponse.completed ? "complete" : "incomplete"));
320
    summarycomplete.removeChild(summarycomplete.lastChild);
321
    summarycomplete.appendChild(document.createTextNode(testresponse.completed
322
        ? "Complete" : "Incomplete"));
323
    var old_attempts_value = summaryattempts.lastChild.data;
324
    summaryattempts.removeChild(summaryattempts.lastChild);
325
    summaryattempts.appendChild(document.createTextNode(
326
        testresponse.attempts));
327
    if (testresponse.completed && testresponse.attempts == 1 &&
328
        old_attempts_value == "0")
329
    {
330
        /* Add "Well done" for extra congratulations */
331
        summaryli.appendChild(document.createTextNode(
332
            " Well done!"));
333
    }
312 by mattgiuca
Full client-side testing - functional.
334
}
335
336
/* DOM creators for test case response elements */
337
338
/** Create a <li> element for the result of a test case.
339
 * type: "pass", "fail", "exception" or "critical"
518 by stevenbird
Cleaned up display of test results:
340
 * level is 0 for outer, and 1 for inner
312 by mattgiuca
Full client-side testing - functional.
341
 * For exceptions and crits, "desc" is the exception name,
518 by stevenbird
Cleaned up display of test results:
342
 * detail is the message; detail should be null for passing cases.
312 by mattgiuca
Full client-side testing - functional.
343
 */
518 by stevenbird
Cleaned up display of test results:
344
function create_response_item(type, level, desc, detail)
312 by mattgiuca
Full client-side testing - functional.
345
{
346
    var crit = false;
347
    if (type == "critical")
348
    {
349
        /* Crits look like exceptions, but are slightly different */
350
        crit = true;
351
        type = "exception";
352
    }
353
    var li = document.createElement("li");
518 by stevenbird
Cleaned up display of test results:
354
    if (level == 0)
355
    {
356
        li.setAttribute("class", type);
357
    }
358
    else
359
    {
360
        if (type == "pass") { li.setAttribute("class", "check") }
361
        else { li.setAttribute("class", "cross") }
362
    }
363
364
    if (level == 0) /* print Pass/Fail tag at outer level only */
365
    {
366
        var b = document.createElement("b");
771 by mattgiuca
tutorial/tutorial.js: Replaced string [] indexing with charAt, for
367
        var text = type.charAt(0).toUpperCase() + type.substr(1) + ":";
518 by stevenbird
Cleaned up display of test results:
368
        b.appendChild(document.createTextNode(text));
369
        li.appendChild(b);
370
    }
371
312 by mattgiuca
Full client-side testing - functional.
372
    if (type == "pass")
373
        text = desc;
374
    else if (type == "fail")
375
        text = desc + (detail == null ? "" : ":");
376
    else if (type == "exception")
377
    {
378
        if (crit)
379
            text = "Your code could not be executed, "
380
                + "due to the following error:";
381
        else
382
            text = "The following exception occured "
383
                + "while running your code:";
384
    }
385
    li.appendChild(document.createTextNode(" " + text));
386
    if (type == "pass" || (type == "fail" && detail == null))
387
        return li;
388
389
    /* Non-passes, display the error message */
390
    li.appendChild(document.createElement("br"));
391
    if (type == "exception")
392
    {
393
        b = document.createElement("b");
394
        b.appendChild(document.createTextNode(desc + ":"));
395
        li.appendChild(b);
396
    }
397
    li.appendChild(document.createTextNode(detail));
398
    return li;
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
399
}
711 by mattgiuca
Tutorial: Tabs now correctly indent code in exercise boxes.
400
401
/** Special key handlers for exercise text boxes */
402
function catch_textbox_input(exerciseid, filename, key)
403
{
404
    /* NOTE: Copied and modified from console/console.js:catch_input. */
405
    /* Always update the saved status, so it will enable the save button and
406
     * auto-save timer. */
407
    set_saved_status(exerciseid, filename, "Save");
408
    var inp = document.getElementById('textarea_' + exerciseid);
409
    switch (key)
410
    {
411
    case 9:                 /* Tab key */
412
        var selstart = inp.selectionStart;
413
        var selend = inp.selectionEnd;
414
        var chars_added;
415
        if (selstart == selend)
416
        {
417
            /* No selection, just a carat. Insert a tab here. */
418
            inp.value = inp.value.substr(0, selstart)
419
                + TAB_STRING + inp.value.substr(selstart);
420
            chars_added = TAB_STRING.length;
421
        }
422
        else
423
        {
424
            /* Text is selected.
425
             * Indent each line that is selected.
426
             */
427
            var pre_sel = inp.value.substr(0, selstart);
428
            var in_sel = inp.value.substr(selstart, selend-selstart);
429
            var post_sel = inp.value.substr(selend);
430
            console.log("pre_sel = " + repr(pre_sel));
431
            console.log("in_sel = " + repr(in_sel));
432
            console.log("post_sel = " + repr(post_sel));
433
            /* Move everything after the last newline in pre_sel to in_sel,
434
             * so it will be indented too (ie. the first line
435
             * partially-selected). */
436
            var pre_sel_newline = pre_sel.lastIndexOf('\n')+1;
437
            in_sel = pre_sel.substr(pre_sel_newline) + in_sel;
438
            pre_sel = pre_sel.substr(0, pre_sel_newline);
439
            /* Now insert TAB_STRING before each line of in_sel */
440
            in_sel = in_sel.split('\n');
441
            var new_in_sel = TAB_STRING + in_sel[0]
442
            for (var i=1; i<in_sel.length; i++)
443
                new_in_sel += '\n' + TAB_STRING + in_sel[i];
444
            chars_added = TAB_STRING.length * in_sel.length;
445
446
            inp.value = pre_sel + new_in_sel + post_sel;
447
        }
448
        /* Update the selection so the same characters as before are selected
449
         */
450
        inp.selectionStart = selstart + chars_added;
451
        inp.selectionEnd = inp.selectionStart + (selend - selstart);
452
        /* Cancel the event, so the TAB key doesn't move focus away from this
453
         * box */
454
        return false;
455
        /* Note: If it happens that some browsers don't support event
456
         * cancelling properly, this hack might work instead:
457
        setTimeout(
458
            "document.getElementById('console_inputText').focus()",
459
            0);
460
         */
461
        break;
462
    }
463
}
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
464
465
/** User clicks "view previous attempts" button. Do an Ajax call and populate.
466
 * exerciseid: "id" of the exercise's div element.
467
 * filename: Filename of the exercise's XML file (used to identify the
468
 *     exercise when interacting with the server).
469
 */
470
function open_previous(exerciseid, filename)
471
{
472
    var exercisediv = document.getElementById(exerciseid);
473
    var divs = exercisediv.getElementsByTagName("div");
474
    var attempthistory;
475
    for (var i=0; i<divs.length; i++)
476
        if (divs[i].getAttribute("class") == "attempthistory")
477
            attempthistory = divs[i];
478
479
    /* Get handles on the four existing elements of the history box */
480
    var openbutton = attempthistory.getElementsByTagName("p")[0];
481
    var openarea = attempthistory.getElementsByTagName("div")[0];
482
    var dropdown = attempthistory.getElementsByTagName("select")[0];
483
    var textarea = attempthistory.getElementsByTagName("textarea")[0];
484
485
    /* Activate the "open" state, and clear the dropdown box */
486
    openbutton.setAttribute("style", "display: none");
487
    openarea.setAttribute("style", "display: auto");
488
    textarea.setAttribute("style", "display: none");
489
    dom_removechildren(dropdown);
490
    var pleasewait = document.createElement("option");
491
    pleasewait.appendChild(document.createTextNode("Retrieving past attempts..."));
492
    dropdown.appendChild(pleasewait);
493
494
    var args = {"exercise": filename, "action": "getattempts"};
495
496
    /* Send the form as multipart/form-data, since we are sending a whole lump
497
     * of Python code, it should be treated like a file upload. */
498
    /* AJAX callback function */
499
    var callback = function(xhr)
500
        {
501
            var attempts;
502
            var attempt;
503
            var opt;
504
            try
505
            {
506
                attempts = JSON.parse(xhr.responseText);
507
            }
508
            catch (ex)
509
            {
510
                alert("There was an error fetching your attempt history. "
511
                    + "Please notify the administrators of this.");
512
                return;
513
            }
514
            /* Populate the attempt history div */
515
            dom_removechildren(dropdown);
516
            for (var i=0; i<attempts.length; i++)
517
            {
518
                /* An object with a date and complete */
519
                attempt = attempts[i];
520
                opt = document.createElement("option");
521
                opt.setAttribute("value", attempt.date);
522
                opt.appendChild(document.createTextNode(attempt.date));
523
                if (attempt.complete)
524
                {
525
                    /* Add a little green ball to this option
526
                     * This is probably hideously illegal, but looks nice :)
527
                     */
528
                    opt.appendChild(document.createTextNode(" "));
529
                    var img = document.createElement("img");
530
                    img.setAttribute("src",
531
                        make_path("media/images/tutorial/tiny/complete.png"));
532
                    img.setAttribute("alt", "Complete");
533
                    opt.appendChild(img);
534
                }
535
                dropdown.appendChild(opt);
536
            }
537
        }
538
    ajax_call(callback, "tutorialservice", "", args, "GET");
539
}
540
541
function close_previous(exerciseid)
542
{
543
    var exercisediv = document.getElementById(exerciseid);
544
    var divs = exercisediv.getElementsByTagName("div");
545
    var attempthistory;
546
    for (var i=0; i<divs.length; i++)
547
        if (divs[i].getAttribute("class") == "attempthistory")
548
            attempthistory = divs[i];
549
550
    /* Get handles on the four existing elements of the history box */
551
    var openbutton = attempthistory.getElementsByTagName("p")[0];
552
    var openarea = attempthistory.getElementsByTagName("div")[0];
553
554
    /* Deactivate the "open" state */
555
    openbutton.setAttribute("style", "display: auto");
556
    openarea.setAttribute("style", "display: none");
557
}
558
559
/** User selects an attempt in the dropdown. Do an Ajax call and populate.
560
 * exerciseid: "id" of the exercise's div element.
561
 * filename: Filename of the exercise's XML file (used to identify the
562
 *     exercise when interacting with the server).
563
 */
564
function select_attempt(exerciseid, filename)
565
{
566
    var exercisediv = document.getElementById(exerciseid);
567
    var divs = exercisediv.getElementsByTagName("div");
568
    var attempthistory;
569
    for (var i=0; i<divs.length; i++)
570
        if (divs[i].getAttribute("class") == "attempthistory")
571
            attempthistory = divs[i];
572
573
    /* Get handles on the four existing elements of the history box */
574
    var dropdown = attempthistory.getElementsByTagName("select")[0];
575
    var textarea = attempthistory.getElementsByTagName("textarea")[0];
576
577
    /* Get the "value" of the selected option */
578
    var date = dropdown.options[dropdown.selectedIndex].getAttribute("value");
579
580
    var args = {"exercise": filename, "action": "getattempt", "date": date};
581
582
    /* Send the form as multipart/form-data, since we are sending a whole lump
583
     * of Python code, it should be treated like a file upload. */
584
    /* AJAX callback function */
585
    var callback = function(xhr)
586
        {
587
            var attempt;
588
            try
589
            {
590
                attempt = JSON.parse(xhr.responseText);
591
            }
592
            catch (ex)
593
            {
594
                alert("There was an error fetching your attempt history. "
595
                    + "Please notify the administrators of this.");
596
                return;
597
            }
598
            if (attempt == null)
599
            {
600
                /* There was no data for this date - that's odd */
601
                alert("There was no attempt made before that date.");
602
                return;
603
            }
604
            /* Populate the attempt text field */
605
            dom_removechildren(textarea);
606
            textarea.appendChild(document.createTextNode(attempt));
607
            textarea.setAttribute("style", "display: auto");
608
        }
609
    ajax_call(callback, "tutorialservice", "", args, "GET");
610
}