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