307
by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call |
1 |
/* IVLE - Informatics Virtual Learning Environment
|
2 |
* Copyright (C) 2007-2008 The University of Melbourne
|
|
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 |
||
331
by mattgiuca
Console: Configured console to display properly as a "floating" window in the |
23 |
/* Runs at startup. */
|
24 |
function onload() |
|
25 |
{
|
|
26 |
/* Set up the console plugin to display as a popup window */
|
|
27 |
console_init(true); |
|
28 |
}
|
|
29 |
||
325
by mattgiuca
tutorial: Added "run" button which submits the students code to the |
30 |
/** User clicks "Run" button. Do an Ajax call and print the test output.
|
31 |
*/
|
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
32 |
function runexercise(exerciseid, filename) |
325
by mattgiuca
tutorial: Added "run" button which submits the students code to the |
33 |
{
|
34 |
/* Get the source code the student is submitting */
|
|
537
by stevenbird
bugfix -- legacy of renaming problems to exercises; caused run button not to work |
35 |
var exercisediv = document.getElementById(exerciseid); |
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
36 |
var exercisebox = exercisediv.getElementsByTagName("textarea")[0]; |
37 |
var code = exercisebox.value; |
|
325
by mattgiuca
tutorial: Added "run" button which submits the students code to the |
38 |
|
333
by mattgiuca
console.js: enter_line now accepts the line as an argument instead of reading |
39 |
/* Dump the entire file to the console */
|
663
by drtomc
console: start console lazily. |
40 |
var callback = function() |
41 |
{
|
|
42 |
console_enter_line(code, "block"); |
|
43 |
}
|
|
44 |
start_server(callback) |
|
333
by mattgiuca
console.js: enter_line now accepts the line as an argument instead of reading |
45 |
return; |
325
by mattgiuca
tutorial: Added "run" button which submits the students code to the |
46 |
}
|
47 |
||
48 |
/** Given a response object (JSON-parsed object), displays the result of the
|
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
49 |
* 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 |
50 |
*/
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
51 |
function handle_runresponse(exercisediv, runresponse) |
325
by mattgiuca
tutorial: Added "run" button which submits the students code to the |
52 |
{
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
53 |
var runoutput = exercisediv.getElementsByTagName("textarea")[1]; |
325
by mattgiuca
tutorial: Added "run" button which submits the students code to the |
54 |
dom_removechildren(runoutput); |
55 |
runoutput.appendChild(document.createTextNode(runresponse.stdout)); |
|
56 |
}
|
|
57 |
||
307
by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call |
58 |
/** User clicks "Submit" button. Do an Ajax call and run the test.
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
59 |
* exerciseid: "id" of the exercise's div element.
|
60 |
* 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 |
61 |
* when interacting with the server).
|
62 |
*/
|
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
63 |
function submitexercise(exerciseid, filename) |
307
by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call |
64 |
{
|
719
by mattgiuca
tutorial.js: If the result returned from Submit is a JSON parse error, |
65 |
var original_saved_status = get_saved_status(exerciseid); |
705
by mattgiuca
tutorial (Python & JS) |
66 |
set_submit_status(exerciseid, filename, "Submitting..."); |
67 |
set_saved_status(exerciseid, filename, "Saving..."); |
|
307
by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call |
68 |
/* Get the source code the student is submitting */
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
69 |
var exercisediv = document.getElementById(exerciseid); |
70 |
var exercisebox = exercisediv.getElementsByTagName("textarea")[0]; |
|
71 |
var code = exercisebox.value; |
|
312
by mattgiuca
Full client-side testing - functional. |
72 |
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
73 |
var args = {"code": code, "exercise": filename, "action": "test"}; |
312
by mattgiuca
Full client-side testing - functional. |
74 |
|
75 |
/* Send the form as multipart/form-data, since we are sending a whole lump
|
|
76 |
* of Python code, it should be treated like a file upload. */
|
|
559
by mattgiuca
Major JavaScript refactor: util.ajax_call is now asynchronous, not |
77 |
/* AJAX callback function */
|
78 |
var callback = function(xhr) |
|
79 |
{
|
|
719
by mattgiuca
tutorial.js: If the result returned from Submit is a JSON parse error, |
80 |
var testresponse; |
81 |
try
|
|
82 |
{
|
|
83 |
testresponse = JSON.parse(xhr.responseText); |
|
84 |
}
|
|
85 |
catch (ex) |
|
86 |
{
|
|
87 |
alert("There was an error submitting or running your code. " |
|
88 |
+ "Please notify the administrators of this."); |
|
89 |
/* Since it failed, set the Save button back how it was. */
|
|
90 |
set_saved_status(exerciseid, filename, original_saved_status); |
|
91 |
set_submit_status(exerciseid, filename, "Submit"); |
|
92 |
return; |
|
93 |
}
|
|
707
by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts" |
94 |
handle_testresponse(exercisediv, exerciseid, testresponse); |
705
by mattgiuca
tutorial (Python & JS) |
95 |
set_saved_status(exerciseid, filename, "Saved"); |
96 |
set_submit_status(exerciseid, filename, "Submit"); |
|
559
by mattgiuca
Major JavaScript refactor: util.ajax_call is now asynchronous, not |
97 |
}
|
98 |
ajax_call(callback, "tutorialservice", "", args, "POST", |
|
312
by mattgiuca
Full client-side testing - functional. |
99 |
"multipart/form-data"); |
100 |
}
|
|
101 |
||
698
by mattgiuca
Added Save feature to tutorial system. |
102 |
/** User clicks "Save" button. Do an Ajax call to store it.
|
103 |
* exerciseid: "id" of the exercise's div element.
|
|
104 |
* filename: Filename of the exercise's XML file (used to identify the exercise
|
|
105 |
* when interacting with the server).
|
|
106 |
*/
|
|
107 |
function saveexercise(exerciseid, filename) |
|
108 |
{
|
|
703
by mattgiuca
tutorial: (Python + Javascript) |
109 |
set_saved_status(exerciseid, filename, "Saving..."); |
698
by mattgiuca
Added Save feature to tutorial system. |
110 |
/* Get the source code the student is submitting */
|
111 |
var exercisediv = document.getElementById(exerciseid); |
|
112 |
var exercisebox = exercisediv.getElementsByTagName("textarea")[0]; |
|
113 |
var code = exercisebox.value; |
|
114 |
||
115 |
var args = {"code": code, "exercise": filename, "action": "save"}; |
|
116 |
||
117 |
/* Send the form as multipart/form-data, since we are sending a whole lump
|
|
118 |
* of Python code, it should be treated like a file upload. */
|
|
119 |
/* AJAX callback function */
|
|
120 |
var callback = function(xhr) |
|
121 |
{
|
|
122 |
// XXX Maybe check to see if this worked?
|
|
703
by mattgiuca
tutorial: (Python + Javascript) |
123 |
set_saved_status(exerciseid, filename, "Saved"); |
698
by mattgiuca
Added Save feature to tutorial system. |
124 |
}
|
125 |
ajax_call(callback, "tutorialservice", "", args, "POST", |
|
126 |
"multipart/form-data"); |
|
127 |
}
|
|
128 |
||
715
by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the |
129 |
/** User clicks "Reset" button. Replace the contents of the user program text
|
130 |
* box with the hidden "reset button backup" field, containing the original
|
|
131 |
* partial fragment.
|
|
132 |
* exerciseid: "id" of the exercise's div element.
|
|
718
by mattgiuca
Tutorial: Minor fixes wrt addition of reset button. |
133 |
* filename: Filename of the exercise's XML file (used to identify the exercise
|
134 |
* when interacting with the server).
|
|
715
by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the |
135 |
*/
|
718
by mattgiuca
Tutorial: Minor fixes wrt addition of reset button. |
136 |
function resetexercise(exerciseid, filename) |
715
by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the |
137 |
{
|
138 |
conf_msg = "This will delete your solution to this exercise, and reset " |
|
139 |
+ "it back to the default partial solution.\n\n" |
|
140 |
+ "Are you sure you want to do this?"; |
|
141 |
if (!confirm(conf_msg)) |
|
142 |
return; |
|
143 |
/* Get the source code the student is submitting */
|
|
144 |
var exercisediv = document.getElementById(exerciseid); |
|
145 |
var exercisebox = exercisediv.getElementsByTagName("textarea")[0]; |
|
146 |
var resettextbox = document.getElementById("input_resettext_" + exerciseid); |
|
717
by mattgiuca
Tutorial: Bugfix - Reset Text was not escaped, so bad, horribly bad things |
147 |
var text_urlencoded = resettextbox.value; |
148 |
/* Need to un-urlencode the value */
|
|
149 |
exercisebox.value = decodeURIComponent(text_urlencoded); |
|
718
by mattgiuca
Tutorial: Minor fixes wrt addition of reset button. |
150 |
/* We changed the text, so make Save button available, and autosave after
|
151 |
* 10 seconds. */
|
|
152 |
set_saved_status(exerciseid, filename, "Save"); |
|
715
by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the |
153 |
}
|
154 |
||
703
by mattgiuca
tutorial: (Python + Javascript) |
155 |
/* savetimers is a dict mapping exerciseIDs to timer IDs.
|
156 |
* Its members indicate all exercises that have been modified but not saved.
|
|
157 |
*/
|
|
158 |
savetimers = {} |
|
159 |
||
160 |
/** Changes whether an exercise is considered "saved" or not.
|
|
161 |
* stat is a string which specifies the status, and also the button text.
|
|
162 |
* If stat == "Save", then it indicates it is NOT saved. This will
|
|
163 |
* enable the "Save" button, and set a timer going which will auto-save
|
|
164 |
* after a set period of time (eg. 30 seconds).
|
|
165 |
* Any other value will disable the "Save" button and disable the timer.
|
|
166 |
* stat should be "Saving..." when the save request is issued, and "Saved"
|
|
167 |
* when the response comes back.
|
|
168 |
*/
|
|
169 |
function set_saved_status(exerciseid, filename, stat) |
|
170 |
{
|
|
171 |
var timername = "savetimer_" + exerciseid; |
|
172 |
var button = document.getElementById("savebutton_" + exerciseid); |
|
173 |
var is_saved = stat != "Save"; |
|
174 |
button.value = stat; |
|
175 |
||
176 |
/* Disable the timer, if it exists */
|
|
177 |
if (typeof(savetimers[timername]) != "undefined") |
|
178 |
{
|
|
179 |
clearTimeout(savetimers[timername]); |
|
180 |
savetimers[timername] = undefined; |
|
181 |
}
|
|
182 |
||
183 |
if (is_saved) |
|
184 |
{
|
|
185 |
/* Disable the button */
|
|
186 |
button.disabled = true; |
|
187 |
}
|
|
188 |
else
|
|
189 |
{
|
|
190 |
/* Enable the button */
|
|
191 |
button.disabled = false; |
|
192 |
/* Create a timer which will auto-save when it expires */
|
|
193 |
var save_string = "saveexercise(" + repr(exerciseid) + ", " |
|
194 |
+ repr(filename) + ")" |
|
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++) |
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
235 |
if (childs[i].nodeType == exercisediv.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 |
console.log("pre_sel = " + repr(pre_sel)); |
|
436 |
console.log("in_sel = " + repr(in_sel)); |
|
437 |
console.log("post_sel = " + repr(post_sel)); |
|
438 |
/* Move everything after the last newline in pre_sel to in_sel,
|
|
439 |
* so it will be indented too (ie. the first line
|
|
440 |
* partially-selected). */
|
|
441 |
var pre_sel_newline = pre_sel.lastIndexOf('\n')+1; |
|
442 |
in_sel = pre_sel.substr(pre_sel_newline) + in_sel; |
|
443 |
pre_sel = pre_sel.substr(0, pre_sel_newline); |
|
444 |
/* Now insert TAB_STRING before each line of in_sel */
|
|
445 |
in_sel = in_sel.split('\n'); |
|
446 |
var new_in_sel = TAB_STRING + in_sel[0] |
|
447 |
for (var i=1; i<in_sel.length; i++) |
|
448 |
new_in_sel += '\n' + TAB_STRING + in_sel[i]; |
|
449 |
chars_added = TAB_STRING.length * in_sel.length; |
|
450 |
||
451 |
inp.value = pre_sel + new_in_sel + post_sel; |
|
452 |
}
|
|
453 |
/* Update the selection so the same characters as before are selected
|
|
454 |
*/
|
|
455 |
inp.selectionStart = selstart + chars_added; |
|
456 |
inp.selectionEnd = inp.selectionStart + (selend - selstart); |
|
457 |
/* Cancel the event, so the TAB key doesn't move focus away from this
|
|
458 |
* box */
|
|
459 |
return false; |
|
460 |
/* Note: If it happens that some browsers don't support event
|
|
461 |
* cancelling properly, this hack might work instead:
|
|
462 |
setTimeout(
|
|
463 |
"document.getElementById('console_inputText').focus()",
|
|
464 |
0);
|
|
465 |
*/
|
|
466 |
break; |
|
467 |
}
|
|
468 |
}
|