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