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 |
{
|
705
by mattgiuca
tutorial (Python & JS) |
65 |
set_submit_status(exerciseid, filename, "Submitting..."); |
66 |
set_saved_status(exerciseid, filename, "Saving..."); |
|
307
by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call |
67 |
/* Get the source code the student is submitting */
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
68 |
var exercisediv = document.getElementById(exerciseid); |
69 |
var exercisebox = exercisediv.getElementsByTagName("textarea")[0]; |
|
70 |
var code = exercisebox.value; |
|
312
by mattgiuca
Full client-side testing - functional. |
71 |
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
72 |
var args = {"code": code, "exercise": filename, "action": "test"}; |
312
by mattgiuca
Full client-side testing - functional. |
73 |
|
74 |
/* Send the form as multipart/form-data, since we are sending a whole lump
|
|
75 |
* of Python code, it should be treated like a file upload. */
|
|
559
by mattgiuca
Major JavaScript refactor: util.ajax_call is now asynchronous, not |
76 |
/* AJAX callback function */
|
77 |
var callback = function(xhr) |
|
78 |
{
|
|
79 |
var testresponse = JSON.parse(xhr.responseText); |
|
707
by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts" |
80 |
handle_testresponse(exercisediv, exerciseid, testresponse); |
705
by mattgiuca
tutorial (Python & JS) |
81 |
set_saved_status(exerciseid, filename, "Saved"); |
82 |
set_submit_status(exerciseid, filename, "Submit"); |
|
559
by mattgiuca
Major JavaScript refactor: util.ajax_call is now asynchronous, not |
83 |
}
|
84 |
ajax_call(callback, "tutorialservice", "", args, "POST", |
|
312
by mattgiuca
Full client-side testing - functional. |
85 |
"multipart/form-data"); |
86 |
}
|
|
87 |
||
698
by mattgiuca
Added Save feature to tutorial system. |
88 |
/** User clicks "Save" button. Do an Ajax call to store it.
|
89 |
* exerciseid: "id" of the exercise's div element.
|
|
90 |
* filename: Filename of the exercise's XML file (used to identify the exercise
|
|
91 |
* when interacting with the server).
|
|
92 |
*/
|
|
93 |
function saveexercise(exerciseid, filename) |
|
94 |
{
|
|
703
by mattgiuca
tutorial: (Python + Javascript) |
95 |
set_saved_status(exerciseid, filename, "Saving..."); |
698
by mattgiuca
Added Save feature to tutorial system. |
96 |
/* Get the source code the student is submitting */
|
97 |
var exercisediv = document.getElementById(exerciseid); |
|
98 |
var exercisebox = exercisediv.getElementsByTagName("textarea")[0]; |
|
99 |
var code = exercisebox.value; |
|
100 |
||
101 |
var args = {"code": code, "exercise": filename, "action": "save"}; |
|
102 |
||
103 |
/* Send the form as multipart/form-data, since we are sending a whole lump
|
|
104 |
* of Python code, it should be treated like a file upload. */
|
|
105 |
/* AJAX callback function */
|
|
106 |
var callback = function(xhr) |
|
107 |
{
|
|
108 |
// XXX Maybe check to see if this worked?
|
|
703
by mattgiuca
tutorial: (Python + Javascript) |
109 |
set_saved_status(exerciseid, filename, "Saved"); |
698
by mattgiuca
Added Save feature to tutorial system. |
110 |
}
|
111 |
ajax_call(callback, "tutorialservice", "", args, "POST", |
|
112 |
"multipart/form-data"); |
|
113 |
}
|
|
114 |
||
715
by mattgiuca
Tutorial: Added "Reset" button to exercises, so you can get back to the |
115 |
/** User clicks "Reset" button. Replace the contents of the user program text
|
116 |
* box with the hidden "reset button backup" field, containing the original
|
|
117 |
* partial fragment.
|
|
118 |
* exerciseid: "id" of the exercise's div element.
|
|
119 |
*/
|
|
120 |
function resetexercise(exerciseid) |
|
121 |
{
|
|
122 |
conf_msg = "This will delete your solution to this exercise, and reset " |
|
123 |
+ "it back to the default partial solution.\n\n" |
|
124 |
+ "Are you sure you want to do this?"; |
|
125 |
if (!confirm(conf_msg)) |
|
126 |
return; |
|
127 |
/* Get the source code the student is submitting */
|
|
128 |
var exercisediv = document.getElementById(exerciseid); |
|
129 |
var exercisebox = exercisediv.getElementsByTagName("textarea")[0]; |
|
130 |
var resettextbox = document.getElementById("input_resettext_" + exerciseid); |
|
131 |
exercisebox.value = resettextbox.value; |
|
132 |
}
|
|
133 |
||
703
by mattgiuca
tutorial: (Python + Javascript) |
134 |
/* savetimers is a dict mapping exerciseIDs to timer IDs.
|
135 |
* Its members indicate all exercises that have been modified but not saved.
|
|
136 |
*/
|
|
137 |
savetimers = {} |
|
138 |
||
139 |
/** Changes whether an exercise is considered "saved" or not.
|
|
140 |
* stat is a string which specifies the status, and also the button text.
|
|
141 |
* If stat == "Save", then it indicates it is NOT saved. This will
|
|
142 |
* enable the "Save" button, and set a timer going which will auto-save
|
|
143 |
* after a set period of time (eg. 30 seconds).
|
|
144 |
* Any other value will disable the "Save" button and disable the timer.
|
|
145 |
* stat should be "Saving..." when the save request is issued, and "Saved"
|
|
146 |
* when the response comes back.
|
|
147 |
*/
|
|
148 |
function set_saved_status(exerciseid, filename, stat) |
|
149 |
{
|
|
150 |
var timername = "savetimer_" + exerciseid; |
|
151 |
var button = document.getElementById("savebutton_" + exerciseid); |
|
152 |
var is_saved = stat != "Save"; |
|
153 |
button.value = stat; |
|
154 |
||
155 |
/* Disable the timer, if it exists */
|
|
156 |
if (typeof(savetimers[timername]) != "undefined") |
|
157 |
{
|
|
158 |
clearTimeout(savetimers[timername]); |
|
159 |
savetimers[timername] = undefined; |
|
160 |
}
|
|
161 |
||
162 |
if (is_saved) |
|
163 |
{
|
|
164 |
/* Disable the button */
|
|
165 |
button.disabled = true; |
|
166 |
}
|
|
167 |
else
|
|
168 |
{
|
|
169 |
/* Enable the button */
|
|
170 |
button.disabled = false; |
|
171 |
/* Create a timer which will auto-save when it expires */
|
|
172 |
var save_string = "saveexercise(" + repr(exerciseid) + ", " |
|
173 |
+ repr(filename) + ")" |
|
174 |
savetimers[timername] = setTimeout(save_string, 10000); |
|
175 |
}
|
|
176 |
}
|
|
177 |
||
705
by mattgiuca
tutorial (Python & JS) |
178 |
/** Changes the state of the submit button, so it can appear disabled during
|
179 |
* the submission process (to avoid multiple clicks).
|
|
180 |
* stat is a string which specifies the status, and also the button text.
|
|
181 |
* If stat == "Submit", then it indicates it is available for submission.
|
|
182 |
* This will enable the "Submit" button.
|
|
183 |
* Any other value (recommended: "Submitting...") will disable the "Submit"
|
|
184 |
* button.
|
|
185 |
*/
|
|
186 |
function set_submit_status(exerciseid, filename, stat) |
|
187 |
{
|
|
188 |
var button = document.getElementById("submitbutton_" + exerciseid); |
|
189 |
button.disabled = stat != "Submit"; |
|
190 |
button.value = stat; |
|
191 |
}
|
|
192 |
||
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
193 |
/** Given a exercise div, return the testoutput div which is its child.
|
312
by mattgiuca
Full client-side testing - functional. |
194 |
* (The div which is its child whose class is "testoutput".
|
195 |
*/
|
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
196 |
function get_testoutput(exercisediv) |
312
by mattgiuca
Full client-side testing - functional. |
197 |
{
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
198 |
var childs = exercisediv.childNodes; |
312
by mattgiuca
Full client-side testing - functional. |
199 |
var i; |
200 |
var testoutput; |
|
201 |
for (i=0; i<childs.length; i++) |
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
202 |
if (childs[i].nodeType == exercisediv.ELEMENT_NODE && |
312
by mattgiuca
Full client-side testing - functional. |
203 |
childs[i].getAttribute("class") == "testoutput") |
204 |
return childs[i]; |
|
205 |
return null; |
|
206 |
}
|
|
207 |
||
208 |
/** Given a response object (JSON-parsed object), displays the result of the
|
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
209 |
* test to the user. This modifies the given exercisediv's children.
|
312
by mattgiuca
Full client-side testing - functional. |
210 |
*/
|
707
by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts" |
211 |
function handle_testresponse(exercisediv, exerciseid, testresponse) |
312
by mattgiuca
Full client-side testing - functional. |
212 |
{
|
515
by stevenbird
Propagated "problem" -> "exercise" nomenclature change. |
213 |
var testoutput = get_testoutput(exercisediv); |
312
by mattgiuca
Full client-side testing - functional. |
214 |
var i, j; |
215 |
var ul; |
|
216 |
var case_ul; |
|
217 |
if (testoutput == null) return; /* should not happen */ |
|
218 |
dom_removechildren(testoutput); |
|
219 |
||
220 |
ul = document.createElement("ul"); |
|
221 |
testoutput.appendChild(ul); |
|
222 |
||
223 |
if ("critical_error" in testresponse) |
|
224 |
{
|
|
225 |
/* Only one error - and it's bad.
|
|
226 |
* Just print and stop */
|
|
518
by stevenbird
Cleaned up display of test results: |
227 |
ul.appendChild(create_response_item("critical", 0, |
312
by mattgiuca
Full client-side testing - functional. |
228 |
testresponse.critical_error.name, |
229 |
testresponse.critical_error.detail)); |
|
230 |
return; |
|
231 |
}
|
|
232 |
||
233 |
for (i=0; i<testresponse.cases.length; i++) |
|
234 |
{
|
|
235 |
var testcase = testresponse.cases[i]; |
|
236 |
if ("exception" in testcase) |
|
237 |
{
|
|
238 |
/* User's code threw an exception */
|
|
518
by stevenbird
Cleaned up display of test results: |
239 |
fail_li = create_response_item("fail", 0, testcase.name); |
312
by mattgiuca
Full client-side testing - functional. |
240 |
ul.appendChild(fail_li); |
241 |
/* Create a sub-ul to display the failing cases. */
|
|
242 |
case_ul = document.createElement("ul"); |
|
243 |
fail_li.appendChild(case_ul); |
|
518
by stevenbird
Cleaned up display of test results: |
244 |
case_ul.appendChild(create_response_item("exception", 0, |
312
by mattgiuca
Full client-side testing - functional. |
245 |
testcase.exception.name, testcase.exception.detail)); |
246 |
}
|
|
247 |
else if (testcase.passed) |
|
248 |
{
|
|
249 |
/* All parts of the test case passed. Just report the overall case
|
|
250 |
* passing. */
|
|
518
by stevenbird
Cleaned up display of test results: |
251 |
ul.appendChild(create_response_item("pass", 0, testcase.name)); |
312
by mattgiuca
Full client-side testing - functional. |
252 |
}
|
253 |
else
|
|
254 |
{
|
|
518
by stevenbird
Cleaned up display of test results: |
255 |
var fail_li = create_response_item("fail", 0, testcase.name); |
312
by mattgiuca
Full client-side testing - functional. |
256 |
ul.appendChild(fail_li); |
257 |
/* Create a sub-ul to display the failing cases. */
|
|
258 |
case_ul = document.createElement("ul"); |
|
259 |
fail_li.appendChild(case_ul); |
|
260 |
||
261 |
for (j=0; j<testcase.parts.length; j++) |
|
262 |
{
|
|
263 |
var part = testcase.parts[j]; |
|
264 |
if (part.passed) |
|
265 |
{
|
|
518
by stevenbird
Cleaned up display of test results: |
266 |
case_ul.appendChild(create_response_item("pass", 1, |
312
by mattgiuca
Full client-side testing - functional. |
267 |
part.description)); |
268 |
}
|
|
269 |
else
|
|
270 |
{
|
|
518
by stevenbird
Cleaned up display of test results: |
271 |
case_ul.appendChild(create_response_item("fail", 1, |
272 |
part.description /*, part.error_message */)); |
|
312
by mattgiuca
Full client-side testing - functional. |
273 |
}
|
274 |
}
|
|
275 |
}
|
|
276 |
}
|
|
707
by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts" |
277 |
|
278 |
/* Update the summary box (completed, attempts) with the new values we got
|
|
279 |
* back from the tutorialservice.
|
|
710
by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top. |
280 |
* (Also update the balls in the table-of-contents).
|
707
by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts" |
281 |
*/
|
710
by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top. |
282 |
var toc_li = document.getElementById("toc_li_" + exerciseid); |
707
by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts" |
283 |
var summaryli = document.getElementById("summaryli_" + exerciseid); |
284 |
var summarycomplete = document.getElementById("summarycomplete_" |
|
285 |
+ exerciseid); |
|
286 |
var summaryattempts = document.getElementById("summaryattempts_" |
|
287 |
+ exerciseid); |
|
710
by mattgiuca
Tutorial: The tutorial system now presents a table of contents at the top. |
288 |
toc_li.setAttribute("class", |
289 |
(testresponse.completed ? "complete" : "incomplete")); |
|
707
by mattgiuca
tutorialservice: "submit" requests now return "completed" and "attempts" |
290 |
summaryli.setAttribute("class", |
291 |
(testresponse.completed ? "complete" : "incomplete")); |
|
292 |
summarycomplete.removeChild(summarycomplete.lastChild); |
|
293 |
summarycomplete.appendChild(document.createTextNode(testresponse.completed |
|
294 |
? "Complete" : "Incomplete")); |
|
295 |
var old_attempts_value = summaryattempts.lastChild.data; |
|
296 |
summaryattempts.removeChild(summaryattempts.lastChild); |
|
297 |
summaryattempts.appendChild(document.createTextNode( |
|
298 |
testresponse.attempts)); |
|
299 |
if (testresponse.completed && testresponse.attempts == 1 && |
|
300 |
old_attempts_value == "0") |
|
301 |
{
|
|
302 |
/* Add "Well done" for extra congratulations */
|
|
303 |
summaryli.appendChild(document.createTextNode( |
|
304 |
" Well done!")); |
|
305 |
}
|
|
312
by mattgiuca
Full client-side testing - functional. |
306 |
}
|
307 |
||
308 |
/* DOM creators for test case response elements */
|
|
309 |
||
310 |
/** Create a <li> element for the result of a test case.
|
|
311 |
* type: "pass", "fail", "exception" or "critical"
|
|
518
by stevenbird
Cleaned up display of test results: |
312 |
* level is 0 for outer, and 1 for inner
|
312
by mattgiuca
Full client-side testing - functional. |
313 |
* For exceptions and crits, "desc" is the exception name,
|
518
by stevenbird
Cleaned up display of test results: |
314 |
* detail is the message; detail should be null for passing cases.
|
312
by mattgiuca
Full client-side testing - functional. |
315 |
*/
|
518
by stevenbird
Cleaned up display of test results: |
316 |
function create_response_item(type, level, desc, detail) |
312
by mattgiuca
Full client-side testing - functional. |
317 |
{
|
318 |
var crit = false; |
|
319 |
if (type == "critical") |
|
320 |
{
|
|
321 |
/* Crits look like exceptions, but are slightly different */
|
|
322 |
crit = true; |
|
323 |
type = "exception"; |
|
324 |
}
|
|
325 |
var li = document.createElement("li"); |
|
518
by stevenbird
Cleaned up display of test results: |
326 |
if (level == 0) |
327 |
{
|
|
328 |
li.setAttribute("class", type); |
|
329 |
}
|
|
330 |
else
|
|
331 |
{
|
|
332 |
if (type == "pass") { li.setAttribute("class", "check") } |
|
333 |
else { li.setAttribute("class", "cross") } |
|
334 |
}
|
|
335 |
||
336 |
if (level == 0) /* print Pass/Fail tag at outer level only */ |
|
337 |
{
|
|
338 |
var b = document.createElement("b"); |
|
339 |
var text = type[0].toUpperCase() + type.substr(1) + ":"; |
|
340 |
b.appendChild(document.createTextNode(text)); |
|
341 |
li.appendChild(b); |
|
342 |
}
|
|
343 |
||
312
by mattgiuca
Full client-side testing - functional. |
344 |
if (type == "pass") |
345 |
text = desc; |
|
346 |
else if (type == "fail") |
|
347 |
text = desc + (detail == null ? "" : ":"); |
|
348 |
else if (type == "exception") |
|
349 |
{
|
|
350 |
if (crit) |
|
351 |
text = "Your code could not be executed, " |
|
352 |
+ "due to the following error:"; |
|
353 |
else
|
|
354 |
text = "The following exception occured " |
|
355 |
+ "while running your code:"; |
|
356 |
}
|
|
357 |
li.appendChild(document.createTextNode(" " + text)); |
|
358 |
if (type == "pass" || (type == "fail" && detail == null)) |
|
359 |
return li; |
|
360 |
||
361 |
/* Non-passes, display the error message */
|
|
362 |
li.appendChild(document.createElement("br")); |
|
363 |
if (type == "exception") |
|
364 |
{
|
|
365 |
b = document.createElement("b"); |
|
366 |
b.appendChild(document.createTextNode(desc + ":")); |
|
367 |
li.appendChild(b); |
|
368 |
}
|
|
369 |
li.appendChild(document.createTextNode(detail)); |
|
370 |
return li; |
|
307
by mattgiuca
tutorial: Now each problem div has an ID. Added submit buttons which call |
371 |
}
|
711
by mattgiuca
Tutorial: Tabs now correctly indent code in exercise boxes. |
372 |
|
373 |
/** Special key handlers for exercise text boxes */
|
|
374 |
function catch_textbox_input(exerciseid, filename, key) |
|
375 |
{
|
|
376 |
/* NOTE: Copied and modified from console/console.js:catch_input. */
|
|
377 |
/* Always update the saved status, so it will enable the save button and
|
|
378 |
* auto-save timer. */
|
|
379 |
set_saved_status(exerciseid, filename, "Save"); |
|
380 |
var inp = document.getElementById('textarea_' + exerciseid); |
|
381 |
switch (key) |
|
382 |
{
|
|
383 |
case 9: /* Tab key */ |
|
384 |
var selstart = inp.selectionStart; |
|
385 |
var selend = inp.selectionEnd; |
|
386 |
var chars_added; |
|
387 |
if (selstart == selend) |
|
388 |
{
|
|
389 |
/* No selection, just a carat. Insert a tab here. */
|
|
390 |
inp.value = inp.value.substr(0, selstart) |
|
391 |
+ TAB_STRING + inp.value.substr(selstart); |
|
392 |
chars_added = TAB_STRING.length; |
|
393 |
}
|
|
394 |
else
|
|
395 |
{
|
|
396 |
/* Text is selected.
|
|
397 |
* Indent each line that is selected.
|
|
398 |
*/
|
|
399 |
var pre_sel = inp.value.substr(0, selstart); |
|
400 |
var in_sel = inp.value.substr(selstart, selend-selstart); |
|
401 |
var post_sel = inp.value.substr(selend); |
|
402 |
console.log("pre_sel = " + repr(pre_sel)); |
|
403 |
console.log("in_sel = " + repr(in_sel)); |
|
404 |
console.log("post_sel = " + repr(post_sel)); |
|
405 |
/* Move everything after the last newline in pre_sel to in_sel,
|
|
406 |
* so it will be indented too (ie. the first line
|
|
407 |
* partially-selected). */
|
|
408 |
var pre_sel_newline = pre_sel.lastIndexOf('\n')+1; |
|
409 |
in_sel = pre_sel.substr(pre_sel_newline) + in_sel; |
|
410 |
pre_sel = pre_sel.substr(0, pre_sel_newline); |
|
411 |
/* Now insert TAB_STRING before each line of in_sel */
|
|
412 |
in_sel = in_sel.split('\n'); |
|
413 |
var new_in_sel = TAB_STRING + in_sel[0] |
|
414 |
for (var i=1; i<in_sel.length; i++) |
|
415 |
new_in_sel += '\n' + TAB_STRING + in_sel[i]; |
|
416 |
chars_added = TAB_STRING.length * in_sel.length; |
|
417 |
||
418 |
inp.value = pre_sel + new_in_sel + post_sel; |
|
419 |
}
|
|
420 |
/* Update the selection so the same characters as before are selected
|
|
421 |
*/
|
|
422 |
inp.selectionStart = selstart + chars_added; |
|
423 |
inp.selectionEnd = inp.selectionStart + (selend - selstart); |
|
424 |
/* Cancel the event, so the TAB key doesn't move focus away from this
|
|
425 |
* box */
|
|
426 |
return false; |
|
427 |
/* Note: If it happens that some browsers don't support event
|
|
428 |
* cancelling properly, this hack might work instead:
|
|
429 |
setTimeout(
|
|
430 |
"document.getElementById('console_inputText').focus()",
|
|
431 |
0);
|
|
432 |
*/
|
|
433 |
break; |
|
434 |
}
|
|
435 |
}
|