~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"));
1689.1.16 by Matt Giuca
worksheet page: If the viewer has permission to edit, the list of exercises in the contents is replaced by a table showing the statistics on each exercise, including the number of students who have attempted and completed the exercise. Fixed up JavaScript so the dynamic change to a green ball still works on this modified view.
347
        /* If it is a table row, also set the image */
348
        if (toc_li.tagName.toLowerCase() == "tr")
349
        {
350
            toc_li_img = toc_li.getElementsByTagName("img")[0];
351
            toc_li_img.setAttribute("src", "/+media/ivle.webapp.tutorial/"
352
                + "images/tiny/"
353
                + (testresponse.completed ? "complete" : "incomplete")
354
                + ".png");
355
        }
1394.2.10 by William Grant
Don't present the problem summary (completed and attempt count) when we are outside a worksheet.
356
        summaryli.setAttribute("class",
357
            (testresponse.completed ? "complete" : "incomplete"));
358
        summarycomplete.removeChild(summarycomplete.lastChild);
359
        summarycomplete.appendChild(document.createTextNode(testresponse.completed
360
            ? "Complete" : "Incomplete"));
361
        var old_attempts_value = summaryattempts.lastChild.data;
362
        summaryattempts.removeChild(summaryattempts.lastChild);
363
        summaryattempts.appendChild(document.createTextNode(
364
            testresponse.attempts));
365
        if (testresponse.completed && testresponse.attempts == 1 &&
366
            old_attempts_value == "0")
367
        {
368
            /* Add "Well done" for extra congratulations */
369
            summaryli.appendChild(document.createTextNode(
370
                " Well done!"));
371
        }
707 by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts"
372
    }
312 by mattgiuca
Full client-side testing - functional.
373
}
374
375
/* DOM creators for test case response elements */
376
377
/** Create a <li> element for the result of a test case.
378
 * type: "pass", "fail", "exception" or "critical"
518 by stevenbird
Cleaned up display of test results:
379
 * level is 0 for outer, and 1 for inner
312 by mattgiuca
Full client-side testing - functional.
380
 * For exceptions and crits, "desc" is the exception name,
518 by stevenbird
Cleaned up display of test results:
381
 * detail is the message; detail should be null for passing cases.
312 by mattgiuca
Full client-side testing - functional.
382
 */
518 by stevenbird
Cleaned up display of test results:
383
function create_response_item(type, level, desc, detail)
312 by mattgiuca
Full client-side testing - functional.
384
{
385
    var crit = false;
386
    if (type == "critical")
387
    {
388
        /* Crits look like exceptions, but are slightly different */
389
        crit = true;
390
        type = "exception";
391
    }
392
    var li = document.createElement("li");
518 by stevenbird
Cleaned up display of test results:
393
    if (level == 0)
394
    {
395
        li.setAttribute("class", type);
396
    }
397
    else
398
    {
399
        if (type == "pass") { li.setAttribute("class", "check") }
400
        else { li.setAttribute("class", "cross") }
401
    }
402
403
    if (level == 0) /* print Pass/Fail tag at outer level only */
404
    {
405
        var b = document.createElement("b");
771 by mattgiuca
tutorial/tutorial.js: Replaced string [] indexing with charAt, for
406
        var text = type.charAt(0).toUpperCase() + type.substr(1) + ":";
518 by stevenbird
Cleaned up display of test results:
407
        b.appendChild(document.createTextNode(text));
408
        li.appendChild(b);
409
    }
410
312 by mattgiuca
Full client-side testing - functional.
411
    if (type == "pass")
412
        text = desc;
413
    else if (type == "fail")
414
        text = desc + (detail == null ? "" : ":");
415
    else if (type == "exception")
416
    {
417
        if (crit)
418
            text = "Your code could not be executed, "
419
                + "due to the following error:";
420
        else
421
            text = "The following exception occured "
422
                + "while running your code:";
423
    }
424
    li.appendChild(document.createTextNode(" " + text));
425
    if (type == "pass" || (type == "fail" && detail == null))
426
        return li;
427
428
    /* Non-passes, display the error message */
429
    li.appendChild(document.createElement("br"));
430
    if (type == "exception")
431
    {
432
        b = document.createElement("b");
433
        b.appendChild(document.createTextNode(desc + ":"));
434
        li.appendChild(b);
435
    }
436
    li.appendChild(document.createTextNode(detail));
437
    return li;
307 by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call
438
}
711 by mattgiuca
Tutorial: Tabs now correctly indent code in exercise boxes.
439
440
/** Special key handlers for exercise text boxes */
441
function catch_textbox_input(exerciseid, filename, key)
442
{
443
    /* NOTE: Copied and modified from console/console.js:catch_input. */
444
    /* Always update the saved status, so it will enable the save button and
445
     * auto-save timer. */
446
    set_saved_status(exerciseid, filename, "Save");
447
    var inp = document.getElementById('textarea_' + exerciseid);
448
    switch (key)
449
    {
450
    case 9:                 /* Tab key */
451
        var selstart = inp.selectionStart;
452
        var selend = inp.selectionEnd;
453
        var chars_added;
454
        if (selstart == selend)
455
        {
456
            /* No selection, just a carat. Insert a tab here. */
457
            inp.value = inp.value.substr(0, selstart)
458
                + TAB_STRING + inp.value.substr(selstart);
459
            chars_added = TAB_STRING.length;
460
        }
461
        else
462
        {
463
            /* Text is selected.
464
             * Indent each line that is selected.
465
             */
466
            var pre_sel = inp.value.substr(0, selstart);
467
            var in_sel = inp.value.substr(selstart, selend-selstart);
468
            var post_sel = inp.value.substr(selend);
469
            /* Move everything after the last newline in pre_sel to in_sel,
470
             * so it will be indented too (ie. the first line
471
             * partially-selected). */
472
            var pre_sel_newline = pre_sel.lastIndexOf('\n')+1;
473
            in_sel = pre_sel.substr(pre_sel_newline) + in_sel;
474
            pre_sel = pre_sel.substr(0, pre_sel_newline);
475
            /* Now insert TAB_STRING before each line of in_sel */
476
            in_sel = in_sel.split('\n');
477
            var new_in_sel = TAB_STRING + in_sel[0]
478
            for (var i=1; i<in_sel.length; i++)
479
                new_in_sel += '\n' + TAB_STRING + in_sel[i];
480
            chars_added = TAB_STRING.length * in_sel.length;
481
482
            inp.value = pre_sel + new_in_sel + post_sel;
483
        }
484
        /* Update the selection so the same characters as before are selected
485
         */
486
        inp.selectionStart = selstart + chars_added;
487
        inp.selectionEnd = inp.selectionStart + (selend - selstart);
488
        /* Cancel the event, so the TAB key doesn't move focus away from this
489
         * box */
490
        return false;
491
        /* Note: If it happens that some browsers don't support event
492
         * cancelling properly, this hack might work instead:
493
        setTimeout(
494
            "document.getElementById('console_inputText').focus()",
495
            0);
496
         */
497
        break;
498
    }
499
}
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
500
501
/** User clicks "view previous attempts" button. Do an Ajax call and populate.
502
 * exerciseid: "id" of the exercise's div element.
503
 * filename: Filename of the exercise's XML file (used to identify the
504
 *     exercise when interacting with the server).
505
 */
506
function open_previous(exerciseid, filename)
507
{
508
    var exercisediv = document.getElementById(exerciseid);
509
    var divs = exercisediv.getElementsByTagName("div");
510
    var attempthistory;
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
511
    var attempts_path;
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
512
    for (var i=0; i<divs.length; i++)
513
        if (divs[i].getAttribute("class") == "attempthistory")
514
            attempthistory = divs[i];
515
516
    /* Get handles on the four existing elements of the history box */
517
    var openbutton = attempthistory.getElementsByTagName("p")[0];
518
    var openarea = attempthistory.getElementsByTagName("div")[0];
519
    var dropdown = attempthistory.getElementsByTagName("select")[0];
520
    var textarea = attempthistory.getElementsByTagName("textarea")[0];
1099.1.98 by Matt Giuca
Tutorial: Added a message, "no attempts have been made to this exercise",
521
    /* Further handles on the paragraphs for showing/hiding */
522
    var attemptslist = openarea.getElementsByTagName("p")[1];
523
    var noattempts = openarea.getElementsByTagName("p")[2];
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
524
1099.1.97 by Matt Giuca
tutorial/media/tutorial.js: Now sets the shown/hidden properties of the
525
    /* Clear the dropdown box */
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
526
    dom_removechildren(dropdown);
527
    var pleasewait = document.createElement("option");
528
    pleasewait.appendChild(document.createTextNode("Retrieving past attempts..."));
529
    dropdown.appendChild(pleasewait);
530
531
    /* Send the form as multipart/form-data, since we are sending a whole lump
532
     * of Python code, it should be treated like a file upload. */
533
    /* AJAX callback function */
534
    var callback = function(xhr)
535
        {
536
            var attempts;
537
            var attempt;
538
            var opt;
539
            try
540
            {
541
                attempts = JSON.parse(xhr.responseText);
542
            }
543
            catch (ex)
544
            {
545
                alert("There was an error fetching your attempt history. "
546
                    + "Please notify the administrators of this.");
547
                return;
548
            }
549
            /* Populate the attempt history div */
550
            dom_removechildren(dropdown);
551
            for (var i=0; i<attempts.length; i++)
552
            {
553
                /* An object with a date and complete */
554
                attempt = attempts[i];
555
                opt = document.createElement("option");
556
                opt.setAttribute("value", attempt.date);
557
                opt.appendChild(document.createTextNode(attempt.date));
558
                if (attempt.complete)
559
                {
560
                    /* Add a little green ball to this option
561
                     * This is probably hideously illegal, but looks nice :)
562
                     */
563
                    opt.appendChild(document.createTextNode(" "));
564
                    var img = document.createElement("img");
565
                    img.setAttribute("src",
1099.1.68 by William Grant
Move the remaining images to the new framework, in the new ivle.webapp.core
566
                        make_path("+media/ivle.webapp.tutorial/images/tiny/complete.png"));
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
567
                    img.setAttribute("alt", "Complete");
568
                    opt.appendChild(img);
569
                }
570
                dropdown.appendChild(opt);
571
            }
1099.1.97 by Matt Giuca
tutorial/media/tutorial.js: Now sets the shown/hidden properties of the
572
573
            /* Display the page elements */
574
            openbutton.setAttribute("style", "display: none");
575
            openarea.setAttribute("style", "display: auto");
576
            textarea.setAttribute("style", "display: none");
1099.1.98 by Matt Giuca
Tutorial: Added a message, "no attempts have been made to this exercise",
577
            attemptslist.setAttribute("style", "display: none");
578
            noattempts.setAttribute("style", "display: none");
579
            // NOTE: This must go after setting openarea to visible. For some
580
            // reason, Firefox will not display these elements otherwise.
581
            if (attempts.length > 0)
582
                attemptslist.setAttribute("style", "display: auto");
583
            else
584
                noattempts.setAttribute("style", "display: auto");
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
585
        }
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
586
    attempts_path = "api/subjects/" + subject + "/" + year + "/" + semester + 
587
        "/+worksheets/" + worksheet + "/" + filename + '/+attempts/' + username;
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
588
    ajax_call(callback, attempts_path, "", {}, "GET");
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
589
}
590
591
function close_previous(exerciseid)
592
{
593
    var exercisediv = document.getElementById(exerciseid);
594
    var divs = exercisediv.getElementsByTagName("div");
595
    var attempthistory;
596
    for (var i=0; i<divs.length; i++)
597
        if (divs[i].getAttribute("class") == "attempthistory")
598
            attempthistory = divs[i];
599
600
    /* Get handles on the four existing elements of the history box */
601
    var openbutton = attempthistory.getElementsByTagName("p")[0];
602
    var openarea = attempthistory.getElementsByTagName("div")[0];
603
604
    /* Deactivate the "open" state */
605
    openbutton.setAttribute("style", "display: auto");
606
    openarea.setAttribute("style", "display: none");
607
}
608
609
/** User selects an attempt in the dropdown. Do an Ajax call and populate.
610
 * exerciseid: "id" of the exercise's div element.
611
 * filename: Filename of the exercise's XML file (used to identify the
612
 *     exercise when interacting with the server).
613
 */
614
function select_attempt(exerciseid, filename)
615
{
616
    var exercisediv = document.getElementById(exerciseid);
617
    var divs = exercisediv.getElementsByTagName("div");
618
    var attempthistory;
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
619
    var call_path
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
620
    for (var i=0; i<divs.length; i++)
621
        if (divs[i].getAttribute("class") == "attempthistory")
622
            attempthistory = divs[i];
623
624
    /* Get handles on the four existing elements of the history box */
625
    var dropdown = attempthistory.getElementsByTagName("select")[0];
626
    var textarea = attempthistory.getElementsByTagName("textarea")[0];
627
628
    /* 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",
629
    if (dropdown.selectedIndex < 0)
630
        // Nothing is selected. Fail silently (should not occur in practice).
631
        return;
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
632
    var date = dropdown.options[dropdown.selectedIndex].getAttribute("value");
633
634
    /* Send the form as multipart/form-data, since we are sending a whole lump
635
     * of Python code, it should be treated like a file upload. */
636
    /* AJAX callback function */
637
    var callback = function(xhr)
638
        {
639
            var attempt;
640
            try
641
            {
1060 by wagrant
Make IVLE work fine in Firefox 3.1 (ie. Gecko/XULRunner 1.9.1). Gecko 1.9.1 has
642
                attempt = JSON.parse(xhr.responseText).code;
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
643
            }
644
            catch (ex)
645
            {
646
                alert("There was an error fetching your attempt history. "
647
                    + "Please notify the administrators of this.");
648
                return;
649
            }
650
            if (attempt == null)
651
            {
652
                /* There was no data for this date - that's odd */
653
                alert("There was no attempt made before that date.");
654
                return;
655
            }
656
            /* Populate the attempt text field */
657
            dom_removechildren(textarea);
658
            textarea.appendChild(document.createTextNode(attempt));
659
            textarea.setAttribute("style", "display: auto");
660
        }
1099.1.58 by Nick Chadwick
Updated the Worksheets to use a new tutorialservice, hosted in the
661
        
1099.1.141 by Nick Chadwick
Updated the exercises to be loaded from the database, not a local file.
662
    call_path = "api/subjects/" + subject + "/" + year + "/" + semester + 
663
            '/+worksheets/' + worksheet + '/' + filename + '/+attempts/' 
664
            + username + '/' + date;
1099.1.71 by William Grant
Remove an unneeded query string from a now-RESTful tutorial service call.
665
    ajax_call(callback, call_path, "", {}, "GET");
1027 by mattgiuca
Tutorial: Added new feature - previous attempt viewing. Allows users to see
666
}