~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
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
2
 * Copyright (C) 2007-2009 The University of Melbourne
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
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
66
    /* Send the form as multipart/form-data, since we are sending a whole lump
67
     * of Python code, it should be treated like a file upload. */
559 by mattgiuca
Major JavaScript refactor: util.ajax_call is now asynchronous, not
68
    /* AJAX callback function */
69
    var callback = function(xhr)
70
        {
719 by mattgiuca
tutorial.js: If the result returned from Submit is a JSON parse error,
71
            var testresponse;
72
            try
73
            {
74
                testresponse = JSON.parse(xhr.responseText);
75
            }
76
            catch (ex)
77
            {
78
                alert("There was an error submitting or running your code. "
79
                    + "Please notify the administrators of this.");
80
                /* Since it failed, set the Save button back how it was. */
81
                set_saved_status(exerciseid, filename, original_saved_status);
82
                set_submit_status(exerciseid, filename, "Submit");
83
                return;
84
            }
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
85
            handle_testresponse(exercisediv, exerciseid, testresponse);
705 by mattgiuca
tutorial (Python & JS)
86
            set_saved_status(exerciseid, filename, "Saved");
87
            set_submit_status(exerciseid, filename, "Submit");
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
88
            /* Close the "view previous" area (force reload) */
1394.2.5 by William Grant
Don't deal with previous attempts if not running in a worksheet -- there are none.
89
            if (worksheet)
90
                close_previous(exerciseid);
559 by mattgiuca
Major JavaScript refactor: util.ajax_call is now asynchronous, not
91
        }
1394.2.4 by William Grant
Make exercise JS behave if there is no worksheet set.
92
    if (worksheet)
1394.2.7 by William Grant
Hook up a new ExerciseRESTView.test into the JS.
93
    {
94
        var args = {'code': code};
95
        var attempts_path = (
1394.2.4 by William Grant
Make exercise JS behave if there is no worksheet set.
96
            "api/subjects/" + subject + "/" + year + "/" +
97
            semester + "/+worksheets/" + worksheet + "/" + filename +
98
            '/+attempts/' + username);
1394.2.7 by William Grant
Hook up a new ExerciseRESTView.test into the JS.
99
        ajax_call(callback, attempts_path, "", args, "PUT", "application/json");
100
    }
1394.2.4 by William Grant
Make exercise JS behave if there is no worksheet set.
101
    else
1394.2.7 by William Grant
Hook up a new ExerciseRESTView.test into the JS.
102
    {
103
        var args = {'ivle.op': 'test', 'code': code};
104
        var attempts_path = "api/+exercises/" + filename
105
        ajax_call(callback, attempts_path, "", args, "POST");
106
    }
312 by mattgiuca
Full client-side testing - functional.
107
}
108
698 by mattgiuca
Added Save feature to tutorial system.
109
/** User clicks "Save" button. Do an Ajax call to store it.
110
 * exerciseid: "id" of the exercise's div element.
111
 * filename: Filename of the exercise's XML file (used to identify the exercise
112
 *     when interacting with the server).
113
 */
114
function saveexercise(exerciseid, filename)
115
{
703 by mattgiuca
tutorial: (Python + Javascript)
116
    set_saved_status(exerciseid, filename, "Saving...");
698 by mattgiuca
Added Save feature to tutorial system.
117
    /* Get the source code the student is submitting */
118
    var exercisediv = document.getElementById(exerciseid);
119
    var exercisebox = exercisediv.getElementsByTagName("textarea")[0];
120
    var code = exercisebox.value;
121
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
122
    var args = {"text": code, "ivle.op": "save"};
698 by mattgiuca
Added Save feature to tutorial system.
123
124
    /* Send the form as multipart/form-data, since we are sending a whole lump
125
     * of Python code, it should be treated like a file upload. */
126
    /* AJAX callback function */
127
    var callback = function(xhr)
128
        {
129
            // XXX Maybe check to see if this worked?
703 by mattgiuca
tutorial: (Python + Javascript)
130
            set_saved_status(exerciseid, filename, "Saved");
698 by mattgiuca
Added Save feature to tutorial system.
131
        }
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
132
        
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
133
    call_path = 'api/subjects/' + subject + "/" + year + "/" + semester + 
134
                                '/+worksheets/' + worksheet + '/' + filename;
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
135
    ajax_call(callback, call_path, "", args, "POST");
698 by mattgiuca
Added Save feature to tutorial system.
136
}
137
715 by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the
138
/** User clicks "Reset" button. Replace the contents of the user program text
139
 * box with the hidden "reset button backup" field, containing the original
140
 * partial fragment.
141
 * exerciseid: "id" of the exercise's div element.
718 by mattgiuca
Tutorial: Minor fixes wrt addition of reset button.
142
 * filename: Filename of the exercise's XML file (used to identify the exercise
143
 *     when interacting with the server).
715 by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the
144
 */
718 by mattgiuca
Tutorial: Minor fixes wrt addition of reset button.
145
function resetexercise(exerciseid, filename)
715 by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the
146
{
147
    conf_msg = "This will delete your solution to this exercise, and reset "
148
        + "it back to the default partial solution.\n\n"
149
        + "Are you sure you want to do this?";
150
    if (!confirm(conf_msg))
151
        return;
152
    /* Get the source code the student is submitting */
153
    var exercisediv = document.getElementById(exerciseid);
154
    var exercisebox = exercisediv.getElementsByTagName("textarea")[0];
155
    var resettextbox = document.getElementById("input_resettext_" + exerciseid);
717 by mattgiuca
Tutorial: Bugfix - Reset Text was not escaped, so bad, horribly bad things
156
    var text_urlencoded = resettextbox.value;
157
    /* Need to un-urlencode the value */
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
158
    exercisebox.value = text_urlencoded;//decodeURIComponent(text_urlencoded);
718 by mattgiuca
Tutorial: Minor fixes wrt addition of reset button.
159
    /* We changed the text, so make Save button available, and autosave after
160
     * 10 seconds. */
161
    set_saved_status(exerciseid, filename, "Save");
715 by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the
162
}
163
703 by mattgiuca
tutorial: (Python + Javascript)
164
/* savetimers is a dict mapping exerciseIDs to timer IDs.
165
 * Its members indicate all exercises that have been modified but not saved.
166
 */
167
savetimers = {}
168
169
/** Changes whether an exercise is considered "saved" or not.
170
 * stat is a string which specifies the status, and also the button text.
171
 * If stat == "Save", then it indicates it is NOT saved. This will
172
 * enable the "Save" button, and set a timer going which will auto-save
173
 * after a set period of time (eg. 30 seconds).
174
 * Any other value will disable the "Save" button and disable the timer.
175
 * stat should be "Saving..." when the save request is issued, and "Saved"
176
 * when the response comes back.
177
 */
178
function set_saved_status(exerciseid, filename, stat)
179
{
1394.2.4 by William Grant
Make exercise JS behave if there is no worksheet set.
180
    /* If the current worksheet is undefined, we cannot save state.
181
     * This button probably doesn't even exist. */
182
    if (!worksheet)
183
        return;
184
703 by mattgiuca
tutorial: (Python + Javascript)
185
    var timername = "savetimer_" + exerciseid;
186
    var button = document.getElementById("savebutton_" + exerciseid);
187
    var is_saved = stat != "Save";
188
    button.value = stat;
189
190
    /* Disable the timer, if it exists */
191
    if (typeof(savetimers[timername]) != "undefined")
192
    {
193
        clearTimeout(savetimers[timername]);
194
        savetimers[timername] = undefined;
195
    }
196
197
    if (is_saved)
198
    {
199
        /* Disable the button */
200
        button.disabled = true;
201
    }
202
    else
203
    {
204
        /* Enable the button */
205
        button.disabled = false;
206
        /* Create a timer which will auto-save when it expires */
1060 by wagrant
Make IVLE work fine in Firefox 3.1 (ie. Gecko/XULRunner 1.9.1). Gecko 1.9.1 has
207
208
        /* XXX: This is bad, but it's better than using repr and I want this
209
         * fixed quickly (wgrant). */
210
        var save_string = document.getElementById("savebutton_" + exerciseid).onclick;
703 by mattgiuca
tutorial: (Python + Javascript)
211
        savetimers[timername] = setTimeout(save_string, 10000);
212
    }
213
}
214
719 by mattgiuca
tutorial.js: If the result returned from Submit is a JSON parse error,
215
/** Retrieves the saved status of a given exercise.
216
 * Returns "Save" if the exercise is modified and needs to be saved.
217
 * Returns "Saved" if the exercise has been saved and is unmodified.
218
 * Returns "Saving..." if the exercise is in the process of being saved
219
 *  (an ajax request is on its way).
220
 */
221
function get_saved_status(exerciseid)
222
{
1394.2.4 by William Grant
Make exercise JS behave if there is no worksheet set.
223
    /* No worksheet => no state => no save button. */
224
    if (!worksheet)
225
        return;
719 by mattgiuca
tutorial.js: If the result returned from Submit is a JSON parse error,
226
    var button = document.getElementById("savebutton_" + exerciseid);
227
    return button.value;
228
}
229
705 by mattgiuca
tutorial (Python & JS)
230
/** Changes the state of the submit button, so it can appear disabled during
231
 * the submission process (to avoid multiple clicks).
232
 * stat is a string which specifies the status, and also the button text.
233
 * If stat == "Submit", then it indicates it is available for submission.
234
 * This will enable the "Submit" button.
235
 * Any other value (recommended: "Submitting...") will disable the "Submit"
236
 * button.
237
 */
238
function set_submit_status(exerciseid, filename, stat)
239
{
240
    var button = document.getElementById("submitbutton_" + exerciseid);
241
    button.disabled = stat != "Submit";
242
    button.value = stat;
243
}
244
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
245
/** Given a exercise div, return the testoutput div which is its child.
312 by mattgiuca
Full client-side testing - functional.
246
 * (The div which is its child whose class is "testoutput".
247
 */
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
248
function get_testoutput(exercisediv)
312 by mattgiuca
Full client-side testing - functional.
249
{
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
250
    var childs = exercisediv.childNodes;
312 by mattgiuca
Full client-side testing - functional.
251
    var i;
252
    var testoutput;
253
    for (i=0; i<childs.length; i++)
1170 by William Grant
Don't use ELEMENT_NODE, but hardcode 1, as IE8 is broken.
254
        if (childs[i].nodeType == 1 && /* 1 = ELEMENT_NODE */
312 by mattgiuca
Full client-side testing - functional.
255
            childs[i].getAttribute("class") == "testoutput")
256
            return childs[i];
257
    return null;
258
}
259
260
/** Given a response object (JSON-parsed object), displays the result of the
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
261
 * test to the user. This modifies the given exercisediv's children.
312 by mattgiuca
Full client-side testing - functional.
262
 */
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
263
function handle_testresponse(exercisediv, exerciseid, testresponse)
312 by mattgiuca
Full client-side testing - functional.
264
{
515 by stevenbird
Propagated "problem" -> "exercise" nomenclature change.
265
    var testoutput = get_testoutput(exercisediv);
312 by mattgiuca
Full client-side testing - functional.
266
    var i, j;
267
    var ul;
268
    var case_ul;
269
    if (testoutput == null) return;     /* should not happen */
270
    dom_removechildren(testoutput);
271
272
    ul = document.createElement("ul");
273
    testoutput.appendChild(ul);
274
275
    if ("critical_error" in testresponse)
276
    {
277
        /* Only one error - and it's bad.
278
         * Just print and stop */
518 by stevenbird
Cleaned up display of test results:
279
        ul.appendChild(create_response_item("critical", 0,
312 by mattgiuca
Full client-side testing - functional.
280
            testresponse.critical_error.name,
281
            testresponse.critical_error.detail));
282
        return;
283
    }
284
285
    for (i=0; i<testresponse.cases.length; i++)
286
    {
287
        var testcase = testresponse.cases[i];
288
        if ("exception" in testcase)
289
        {
290
            /* User's code threw an exception */
518 by stevenbird
Cleaned up display of test results:
291
            fail_li = create_response_item("fail", 0, testcase.name);
312 by mattgiuca
Full client-side testing - functional.
292
            ul.appendChild(fail_li);
293
            /* Create a sub-ul to display the failing cases. */
294
            case_ul = document.createElement("ul");
295
            fail_li.appendChild(case_ul);
518 by stevenbird
Cleaned up display of test results:
296
            case_ul.appendChild(create_response_item("exception", 0,
312 by mattgiuca
Full client-side testing - functional.
297
                testcase.exception.name, testcase.exception.detail));
298
        }
299
        else if (testcase.passed)
300
        {
301
            /* All parts of the test case passed. Just report the overall case
302
             * passing. */
518 by stevenbird
Cleaned up display of test results:
303
	    ul.appendChild(create_response_item("pass", 0, testcase.name));
312 by mattgiuca
Full client-side testing - functional.
304
        }
305
        else
306
        {
518 by stevenbird
Cleaned up display of test results:
307
            var fail_li = create_response_item("fail", 0, testcase.name);
312 by mattgiuca
Full client-side testing - functional.
308
            ul.appendChild(fail_li);
309
            /* Create a sub-ul to display the failing cases. */
310
            case_ul = document.createElement("ul");
311
            fail_li.appendChild(case_ul);
312
            
313
            for (j=0; j<testcase.parts.length; j++)
314
            {
315
                var part = testcase.parts[j];
316
                if (part.passed)
317
                {
518 by stevenbird
Cleaned up display of test results:
318
                    case_ul.appendChild(create_response_item("pass", 1,
312 by mattgiuca
Full client-side testing - functional.
319
                        part.description));
320
                }
321
                else
322
                {
518 by stevenbird
Cleaned up display of test results:
323
                    case_ul.appendChild(create_response_item("fail", 1,
324
                        part.description /*, part.error_message */));
312 by mattgiuca
Full client-side testing - functional.
325
                }
326
            }
327
        }
328
    }
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
329
1394.2.10 by William Grant
Don't present the problem summary (completed and attempt count) when we are outside a worksheet.
330
    /* If we have a worksheet, we will get back stats:
331
     *  - is the exercise complete (ie. passed in any previous attempt)
332
     *  - how many attempts did it take to complete the exercise?
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
333
    /* Update the summary box (completed, attempts) with the new values we got
334
     * back from the tutorialservice.
710 by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top.
335
     * (Also update the balls in the table-of-contents).
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
336
     */
1394.2.10 by William Grant
Don't present the problem summary (completed and attempt count) when we are outside a worksheet.
337
    if (worksheet)
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
338
    {
1394.2.10 by William Grant
Don't present the problem summary (completed and attempt count) when we are outside a worksheet.
339
        var toc_li = document.getElementById("toc_li_" + exerciseid);
340
        var summaryli = document.getElementById("summaryli_" + exerciseid);
341
        var summarycomplete = document.getElementById("summarycomplete_"
342
            + exerciseid);
343
        var summaryattempts = document.getElementById("summaryattempts_"
344
            + exerciseid);
345
        toc_li.setAttribute("class",
346
            (testresponse.completed ? "complete" : "incomplete"));
347
        summaryli.setAttribute("class",
348
            (testresponse.completed ? "complete" : "incomplete"));
349
        summarycomplete.removeChild(summarycomplete.lastChild);
350
        summarycomplete.appendChild(document.createTextNode(testresponse.completed
351
            ? "Complete" : "Incomplete"));
352
        var old_attempts_value = summaryattempts.lastChild.data;
353
        summaryattempts.removeChild(summaryattempts.lastChild);
354
        summaryattempts.appendChild(document.createTextNode(
355
            testresponse.attempts));
356
        if (testresponse.completed && testresponse.attempts == 1 &&
357
            old_attempts_value == "0")
358
        {
359
            /* Add "Well done" for extra congratulations */
360
            summaryli.appendChild(document.createTextNode(
361
                " Well done!"));
362
        }
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
363
    }
312 by mattgiuca
Full client-side testing - functional.
364
}
365
366
/* DOM creators for test case response elements */
367
368
/** Create a <li> element for the result of a test case.
369
 * type: "pass", "fail", "exception" or "critical"
518 by stevenbird
Cleaned up display of test results:
370
 * level is 0 for outer, and 1 for inner
312 by mattgiuca
Full client-side testing - functional.
371
 * For exceptions and crits, "desc" is the exception name,
518 by stevenbird
Cleaned up display of test results:
372
 * detail is the message; detail should be null for passing cases.
312 by mattgiuca
Full client-side testing - functional.
373
 */
518 by stevenbird
Cleaned up display of test results:
374
function create_response_item(type, level, desc, detail)
312 by mattgiuca
Full client-side testing - functional.
375
{
376
    var crit = false;
377
    if (type == "critical")
378
    {
379
        /* Crits look like exceptions, but are slightly different */
380
        crit = true;
381
        type = "exception";
382
    }
383
    var li = document.createElement("li");
518 by stevenbird
Cleaned up display of test results:
384
    if (level == 0)
385
    {
386
        li.setAttribute("class", type);
387
    }
388
    else
389
    {
390
        if (type == "pass") { li.setAttribute("class", "check") }
391
        else { li.setAttribute("class", "cross") }
392
    }
393
394
    if (level == 0) /* print Pass/Fail tag at outer level only */
395
    {
396
        var b = document.createElement("b");
771 by mattgiuca
tutorial/tutorial.js: Replaced string [] indexing with charAt, for
397
        var text = type.charAt(0).toUpperCase() + type.substr(1) + ":";
518 by stevenbird
Cleaned up display of test results:
398
        b.appendChild(document.createTextNode(text));
399
        li.appendChild(b);
400
    }
401
312 by mattgiuca
Full client-side testing - functional.
402
    if (type == "pass")
403
        text = desc;
404
    else if (type == "fail")
405
        text = desc + (detail == null ? "" : ":");
406
    else if (type == "exception")
407
    {
408
        if (crit)
409
            text = "Your code could not be executed, "
410
                + "due to the following error:";
411
        else
412
            text = "The following exception occured "
413
                + "while running your code:";
414
    }
415
    li.appendChild(document.createTextNode(" " + text));
416
    if (type == "pass" || (type == "fail" && detail == null))
417
        return li;
418
419
    /* Non-passes, display the error message */
420
    li.appendChild(document.createElement("br"));
421
    if (type == "exception")
422
    {
423
        b = document.createElement("b");
424
        b.appendChild(document.createTextNode(desc + ":"));
425
        li.appendChild(b);
426
    }
427
    li.appendChild(document.createTextNode(detail));
428
    return li;
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
429
}
711 by mattgiuca
Tutorial: Tabs now correctly indent code in exercise boxes.
430
431
/** Special key handlers for exercise text boxes */
432
function catch_textbox_input(exerciseid, filename, key)
433
{
434
    /* NOTE: Copied and modified from console/console.js:catch_input. */
435
    /* Always update the saved status, so it will enable the save button and
436
     * auto-save timer. */
437
    set_saved_status(exerciseid, filename, "Save");
438
    var inp = document.getElementById('textarea_' + exerciseid);
439
    switch (key)
440
    {
441
    case 9:                 /* Tab key */
442
        var selstart = inp.selectionStart;
443
        var selend = inp.selectionEnd;
444
        var chars_added;
445
        if (selstart == selend)
446
        {
447
            /* No selection, just a carat. Insert a tab here. */
448
            inp.value = inp.value.substr(0, selstart)
449
                + TAB_STRING + inp.value.substr(selstart);
450
            chars_added = TAB_STRING.length;
451
        }
452
        else
453
        {
454
            /* Text is selected.
455
             * Indent each line that is selected.
456
             */
457
            var pre_sel = inp.value.substr(0, selstart);
458
            var in_sel = inp.value.substr(selstart, selend-selstart);
459
            var post_sel = inp.value.substr(selend);
460
            /* Move everything after the last newline in pre_sel to in_sel,
461
             * so it will be indented too (ie. the first line
462
             * partially-selected). */
463
            var pre_sel_newline = pre_sel.lastIndexOf('\n')+1;
464
            in_sel = pre_sel.substr(pre_sel_newline) + in_sel;
465
            pre_sel = pre_sel.substr(0, pre_sel_newline);
466
            /* Now insert TAB_STRING before each line of in_sel */
467
            in_sel = in_sel.split('\n');
468
            var new_in_sel = TAB_STRING + in_sel[0]
469
            for (var i=1; i<in_sel.length; i++)
470
                new_in_sel += '\n' + TAB_STRING + in_sel[i];
471
            chars_added = TAB_STRING.length * in_sel.length;
472
473
            inp.value = pre_sel + new_in_sel + post_sel;
474
        }
475
        /* Update the selection so the same characters as before are selected
476
         */
477
        inp.selectionStart = selstart + chars_added;
478
        inp.selectionEnd = inp.selectionStart + (selend - selstart);
479
        /* Cancel the event, so the TAB key doesn't move focus away from this
480
         * box */
481
        return false;
482
        /* Note: If it happens that some browsers don't support event
483
         * cancelling properly, this hack might work instead:
484
        setTimeout(
485
            "document.getElementById('console_inputText').focus()",
486
            0);
487
         */
488
        break;
489
    }
490
}
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
491
492
/** User clicks "view previous attempts" button. Do an Ajax call and populate.
493
 * exerciseid: "id" of the exercise's div element.
494
 * filename: Filename of the exercise's XML file (used to identify the
495
 *     exercise when interacting with the server).
496
 */
497
function open_previous(exerciseid, filename)
498
{
499
    var exercisediv = document.getElementById(exerciseid);
500
    var divs = exercisediv.getElementsByTagName("div");
501
    var attempthistory;
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
502
    var attempts_path;
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
503
    for (var i=0; i<divs.length; i++)
504
        if (divs[i].getAttribute("class") == "attempthistory")
505
            attempthistory = divs[i];
506
507
    /* Get handles on the four existing elements of the history box */
508
    var openbutton = attempthistory.getElementsByTagName("p")[0];
509
    var openarea = attempthistory.getElementsByTagName("div")[0];
510
    var dropdown = attempthistory.getElementsByTagName("select")[0];
511
    var textarea = attempthistory.getElementsByTagName("textarea")[0];
1099.1.98 by Matt Giuca
Tutorial: Added a message, "no attempts have been made to this exercise",
512
    /* Further handles on the paragraphs for showing/hiding */
513
    var attemptslist = openarea.getElementsByTagName("p")[1];
514
    var noattempts = openarea.getElementsByTagName("p")[2];
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
515
1099.1.97 by Matt Giuca
tutorial/media/tutorial.js: Now sets the shown/hidden properties of the
516
    /* Clear the dropdown box */
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
517
    dom_removechildren(dropdown);
518
    var pleasewait = document.createElement("option");
519
    pleasewait.appendChild(document.createTextNode("Retrieving past attempts..."));
520
    dropdown.appendChild(pleasewait);
521
522
    /* Send the form as multipart/form-data, since we are sending a whole lump
523
     * of Python code, it should be treated like a file upload. */
524
    /* AJAX callback function */
525
    var callback = function(xhr)
526
        {
527
            var attempts;
528
            var attempt;
529
            var opt;
530
            try
531
            {
532
                attempts = JSON.parse(xhr.responseText);
533
            }
534
            catch (ex)
535
            {
536
                alert("There was an error fetching your attempt history. "
537
                    + "Please notify the administrators of this.");
538
                return;
539
            }
540
            /* Populate the attempt history div */
541
            dom_removechildren(dropdown);
542
            for (var i=0; i<attempts.length; i++)
543
            {
544
                /* An object with a date and complete */
545
                attempt = attempts[i];
546
                opt = document.createElement("option");
547
                opt.setAttribute("value", attempt.date);
548
                opt.appendChild(document.createTextNode(attempt.date));
549
                if (attempt.complete)
550
                {
551
                    /* Add a little green ball to this option
552
                     * This is probably hideously illegal, but looks nice :)
553
                     */
554
                    opt.appendChild(document.createTextNode(" "));
555
                    var img = document.createElement("img");
556
                    img.setAttribute("src",
1099.1.68 by William Grant
Move the remaining images to the new framework, in the new ivle.webapp.core
557
                        make_path("+media/ivle.webapp.tutorial/images/tiny/complete.png"));
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
558
                    img.setAttribute("alt", "Complete");
559
                    opt.appendChild(img);
560
                }
561
                dropdown.appendChild(opt);
562
            }
1099.1.97 by Matt Giuca
tutorial/media/tutorial.js: Now sets the shown/hidden properties of the
563
564
            /* Display the page elements */
565
            openbutton.setAttribute("style", "display: none");
566
            openarea.setAttribute("style", "display: auto");
567
            textarea.setAttribute("style", "display: none");
1099.1.98 by Matt Giuca
Tutorial: Added a message, "no attempts have been made to this exercise",
568
            attemptslist.setAttribute("style", "display: none");
569
            noattempts.setAttribute("style", "display: none");
570
            // NOTE: This must go after setting openarea to visible. For some
571
            // reason, Firefox will not display these elements otherwise.
572
            if (attempts.length > 0)
573
                attemptslist.setAttribute("style", "display: auto");
574
            else
575
                noattempts.setAttribute("style", "display: auto");
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
576
        }
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
577
    attempts_path = "api/subjects/" + subject + "/" + year + "/" + semester + 
578
        "/+worksheets/" + worksheet + "/" + filename + '/+attempts/' + username;
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
579
    ajax_call(callback, attempts_path, "", {}, "GET");
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
580
}
581
582
function close_previous(exerciseid)
583
{
584
    var exercisediv = document.getElementById(exerciseid);
585
    var divs = exercisediv.getElementsByTagName("div");
586
    var attempthistory;
587
    for (var i=0; i<divs.length; i++)
588
        if (divs[i].getAttribute("class") == "attempthistory")
589
            attempthistory = divs[i];
590
591
    /* Get handles on the four existing elements of the history box */
592
    var openbutton = attempthistory.getElementsByTagName("p")[0];
593
    var openarea = attempthistory.getElementsByTagName("div")[0];
594
595
    /* Deactivate the "open" state */
596
    openbutton.setAttribute("style", "display: auto");
597
    openarea.setAttribute("style", "display: none");
598
}
599
600
/** User selects an attempt in the dropdown. Do an Ajax call and populate.
601
 * exerciseid: "id" of the exercise's div element.
602
 * filename: Filename of the exercise's XML file (used to identify the
603
 *     exercise when interacting with the server).
604
 */
605
function select_attempt(exerciseid, filename)
606
{
607
    var exercisediv = document.getElementById(exerciseid);
608
    var divs = exercisediv.getElementsByTagName("div");
609
    var attempthistory;
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
610
    var call_path
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
611
    for (var i=0; i<divs.length; i++)
612
        if (divs[i].getAttribute("class") == "attempthistory")
613
            attempthistory = divs[i];
614
615
    /* Get handles on the four existing elements of the history box */
616
    var dropdown = attempthistory.getElementsByTagName("select")[0];
617
    var textarea = attempthistory.getElementsByTagName("textarea")[0];
618
619
    /* Get the "value" of the selected option */
1099.1.98 by Matt Giuca
Tutorial: Added a message, "no attempts have been made to this exercise",
620
    if (dropdown.selectedIndex < 0)
621
        // Nothing is selected. Fail silently (should not occur in practice).
622
        return;
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
623
    var date = dropdown.options[dropdown.selectedIndex].getAttribute("value");
624
625
    /* Send the form as multipart/form-data, since we are sending a whole lump
626
     * of Python code, it should be treated like a file upload. */
627
    /* AJAX callback function */
628
    var callback = function(xhr)
629
        {
630
            var attempt;
631
            try
632
            {
1060 by wagrant
Make IVLE work fine in Firefox 3.1 (ie. Gecko/XULRunner 1.9.1). Gecko 1.9.1 has
633
                attempt = JSON.parse(xhr.responseText).code;
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
634
            }
635
            catch (ex)
636
            {
637
                alert("There was an error fetching your attempt history. "
638
                    + "Please notify the administrators of this.");
639
                return;
640
            }
641
            if (attempt == null)
642
            {
643
                /* There was no data for this date - that's odd */
644
                alert("There was no attempt made before that date.");
645
                return;
646
            }
647
            /* Populate the attempt text field */
648
            dom_removechildren(textarea);
649
            textarea.appendChild(document.createTextNode(attempt));
650
            textarea.setAttribute("style", "display: auto");
651
        }
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
652
        
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
653
    call_path = "api/subjects/" + subject + "/" + year + "/" + semester + 
654
            '/+worksheets/' + worksheet + '/' + filename + '/+attempts/' 
655
            + username + '/' + date;
1099.1.71 by William Grant
Remove an unneeded query string from a now-RESTful tutorial service call.
656
    ajax_call(callback, call_path, "", {}, "GET");
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
657
}