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 |
}
|