##// END OF EJS Templates
fix logic for append_javascript...
MinRK -
Show More
@@ -1,556 +1,558 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008-2011 The IPython Development Team
3 3 //
4 4 // Distributed under the terms of the BSD License. The full license is in
5 5 // the file COPYING, distributed as part of this software.
6 6 //----------------------------------------------------------------------------
7 7
8 8 //============================================================================
9 9 // OutputArea
10 10 //============================================================================
11 11
12 12 var IPython = (function (IPython) {
13 13 "use strict";
14 14
15 15 var utils = IPython.utils;
16 16
17 17 var OutputArea = function (selector, prompt_area) {
18 18 this.selector = selector;
19 19 this.wrapper = $(selector);
20 20 this.outputs = [];
21 21 this.collapsed = false;
22 22 this.scrolled = false;
23 23 this.clear_out_timeout = null;
24 24 if (prompt_area === undefined) {
25 25 this.prompt_area = true;
26 26 } else {
27 27 this.prompt_area = prompt_area;
28 28 }
29 29 this.create_elements();
30 30 this.style();
31 31 this.bind_events();
32 32 };
33 33
34 34 OutputArea.prototype.create_elements = function () {
35 35 this.element = $("<div/>");
36 36 this.collapse_button = $("<div/>");
37 37 this.prompt_overlay = $("<div/>");
38 38 this.wrapper.append(this.prompt_overlay);
39 39 this.wrapper.append(this.element);
40 40 this.wrapper.append(this.collapse_button);
41 41 };
42 42
43 43
44 44 OutputArea.prototype.style = function () {
45 45 this.collapse_button.hide();
46 46 this.prompt_overlay.hide();
47 47
48 48 this.wrapper.addClass('output_wrapper');
49 49 this.element.addClass('output vbox');
50 50
51 51 this.collapse_button.button();
52 52 this.collapse_button.addClass('output_collapsed vbox');
53 53 this.collapse_button.attr('title', 'click to expand outout');
54 54 this.collapse_button.html('. . .');
55 55
56 56 this.prompt_overlay.addClass('out_prompt_overlay prompt');
57 57 this.prompt_overlay.attr('title', 'click to expand outout; double click to hide output');
58 58
59 59 this.collapse();
60 60 };
61 61
62 62
63 63 OutputArea.prototype._should_scroll = function (lines) {
64 64 if (!lines) {
65 65 lines = 100;
66 66 }
67 67 // line-height from http://stackoverflow.com/questions/1185151
68 68 var fontSize = this.element.css('font-size');
69 69 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
70 70
71 71 return (this.element.height() > lines * lineHeight);
72 72 };
73 73
74 74
75 75 OutputArea.prototype.bind_events = function () {
76 76 var that = this;
77 77 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
78 78 this.prompt_overlay.click(function () { that.toggle_scroll(); });
79 79
80 80 this.element.resize(function () {
81 81 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
82 82 if ( IPython.utils.browser[0] === "Firefox" ) {
83 83 return;
84 84 }
85 85 // maybe scroll output,
86 86 // if it's grown large enough and hasn't already been scrolled.
87 87 if ( !that.scrolled && that._should_scroll()) {
88 88 that.scroll_area();
89 89 }
90 90 });
91 91 this.collapse_button.click(function () {
92 92 that.expand();
93 93 });
94 94 this.collapse_button.hover(function () {
95 95 $(this).addClass("ui-state-hover");
96 96 }, function () {
97 97 $(this).removeClass("ui-state-hover");
98 98 });
99 99 };
100 100
101 101
102 102 OutputArea.prototype.collapse = function () {
103 103 if (!this.collapsed) {
104 104 this.element.hide();
105 105 this.prompt_overlay.hide();
106 106 if (this.element.html()){
107 107 this.collapse_button.show();
108 108 }
109 109 this.collapsed = true;
110 110 }
111 111 };
112 112
113 113
114 114 OutputArea.prototype.expand = function () {
115 115 if (this.collapsed) {
116 116 this.collapse_button.hide();
117 117 this.element.show();
118 118 this.prompt_overlay.show();
119 119 this.collapsed = false;
120 120 }
121 121 };
122 122
123 123
124 124 OutputArea.prototype.toggle_output = function () {
125 125 if (this.collapsed) {
126 126 this.expand();
127 127 } else {
128 128 this.collapse();
129 129 }
130 130 };
131 131
132 132
133 133 OutputArea.prototype.scroll_area = function () {
134 134 this.element.addClass('output_scroll');
135 135 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
136 136 this.scrolled = true;
137 137 };
138 138
139 139
140 140 OutputArea.prototype.unscroll_area = function () {
141 141 this.element.removeClass('output_scroll');
142 142 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
143 143 this.scrolled = false;
144 144 };
145 145
146 146
147 147 OutputArea.prototype.scroll_if_long = function (lines) {
148 148 if (this._should_scroll(lines)) {
149 149 // only allow scrolling long-enough output
150 150 this.scroll_area();
151 151 }
152 152 };
153 153
154 154
155 155 OutputArea.prototype.toggle_scroll = function () {
156 156 if (this.scrolled) {
157 157 this.unscroll_area();
158 158 } else {
159 159 // only allow scrolling long-enough output
160 160 this.scroll_if_long(20);
161 161 }
162 162 };
163 163
164 164
165 165 // typeset with MathJax if MathJax is available
166 166 OutputArea.prototype.typeset = function () {
167 167 if (window.MathJax){
168 168 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
169 169 }
170 170 };
171 171
172 172
173 173 OutputArea.prototype.handle_output = function (msg_type, content) {
174 174 var json = {};
175 175 json.output_type = msg_type;
176 176 if (msg_type === "stream") {
177 177 json.text = content.data;
178 178 json.stream = content.name;
179 179 } else if (msg_type === "display_data") {
180 180 json = this.convert_mime_types(json, content.data);
181 181 } else if (msg_type === "pyout") {
182 182 json.prompt_number = content.execution_count;
183 183 json = this.convert_mime_types(json, content.data);
184 184 } else if (msg_type === "pyerr") {
185 185 json.ename = content.ename;
186 186 json.evalue = content.evalue;
187 187 json.traceback = content.traceback;
188 188 }
189 189 // append with dynamic=true
190 190 this.append_output(json, true);
191 191 };
192 192
193 193
194 194 OutputArea.prototype.convert_mime_types = function (json, data) {
195 195 if (data['text/plain'] !== undefined) {
196 196 json.text = data['text/plain'];
197 197 }
198 198 if (data['text/html'] !== undefined) {
199 199 json.html = data['text/html'];
200 200 }
201 201 if (data['image/svg+xml'] !== undefined) {
202 202 json.svg = data['image/svg+xml'];
203 203 }
204 204 if (data['image/png'] !== undefined) {
205 205 json.png = data['image/png'];
206 206 }
207 207 if (data['image/jpeg'] !== undefined) {
208 208 json.jpeg = data['image/jpeg'];
209 209 }
210 210 if (data['text/latex'] !== undefined) {
211 211 json.latex = data['text/latex'];
212 212 }
213 213 if (data['application/json'] !== undefined) {
214 214 json.json = data['application/json'];
215 215 }
216 216 if (data['application/javascript'] !== undefined) {
217 217 json.javascript = data['application/javascript'];
218 218 }
219 219 return json;
220 220 };
221 221
222 222
223 223 OutputArea.prototype.append_output = function (json, dynamic) {
224 224 // If dynamic is true, javascript output will be eval'd.
225 225 this.expand();
226 226 this.flush_clear_timeout();
227 227 if (json.output_type === 'pyout') {
228 228 this.append_pyout(json, dynamic);
229 229 } else if (json.output_type === 'pyerr') {
230 230 this.append_pyerr(json);
231 231 } else if (json.output_type === 'display_data') {
232 232 this.append_display_data(json, dynamic);
233 233 } else if (json.output_type === 'stream') {
234 234 this.append_stream(json);
235 235 }
236 236 this.outputs.push(json);
237 237 var that = this;
238 238 setTimeout(function(){that.element.trigger('resize');}, 100);
239 239 };
240 240
241 241
242 242 OutputArea.prototype.create_output_area = function () {
243 243 var oa = $("<div/>").addClass("hbox output_area");
244 244 if (this.prompt_area) {
245 245 oa.append($('<div/>').addClass('prompt'));
246 246 }
247 247 return oa;
248 248 };
249 249
250 250
251 251 OutputArea.prototype.append_pyout = function (json, dynamic) {
252 252 var n = json.prompt_number || ' ';
253 253 var toinsert = this.create_output_area();
254 254 if (this.prompt_area) {
255 255 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
256 256 }
257 257 this.append_mime_type(json, toinsert, dynamic);
258 258 this.element.append(toinsert);
259 259 // If we just output latex, typeset it.
260 260 if ((json.latex !== undefined) || (json.html !== undefined)) {
261 261 this.typeset();
262 262 }
263 263 };
264 264
265 265
266 266 OutputArea.prototype.append_pyerr = function (json) {
267 267 var tb = json.traceback;
268 268 if (tb !== undefined && tb.length > 0) {
269 269 var s = '';
270 270 var len = tb.length;
271 271 for (var i=0; i<len; i++) {
272 272 s = s + tb[i] + '\n';
273 273 }
274 274 s = s + '\n';
275 275 var toinsert = this.create_output_area();
276 276 this.append_text(s, toinsert);
277 277 this.element.append(toinsert);
278 278 }
279 279 };
280 280
281 281
282 282 OutputArea.prototype.append_stream = function (json) {
283 283 // temporary fix: if stream undefined (json file written prior to this patch),
284 284 // default to most likely stdout:
285 285 if (json.stream == undefined){
286 286 json.stream = 'stdout';
287 287 }
288 288 var text = json.text;
289 289 var subclass = "output_"+json.stream;
290 290 if (this.outputs.length > 0){
291 291 // have at least one output to consider
292 292 var last = this.outputs[this.outputs.length-1];
293 293 if (last.output_type == 'stream' && json.stream == last.stream){
294 294 // latest output was in the same stream,
295 295 // so append directly into its pre tag
296 296 // escape ANSI & HTML specials:
297 297 var pre = this.element.find('div.'+subclass).last().find('pre');
298 298 var html = utils.fixCarriageReturn(
299 299 pre.html() + utils.fixConsole(text));
300 300 pre.html(html);
301 301 return;
302 302 }
303 303 }
304 304
305 305 if (!text.replace("\r", "")) {
306 306 // text is nothing (empty string, \r, etc.)
307 307 // so don't append any elements, which might add undesirable space
308 308 return;
309 309 }
310 310
311 311 // If we got here, attach a new div
312 312 var toinsert = this.create_output_area();
313 313 this.append_text(text, toinsert, "output_stream "+subclass);
314 314 this.element.append(toinsert);
315 315 };
316 316
317 317
318 318 OutputArea.prototype.append_display_data = function (json, dynamic) {
319 319 var toinsert = this.create_output_area();
320 320 this.append_mime_type(json, toinsert, dynamic);
321 321 this.element.append(toinsert);
322 322 // If we just output latex, typeset it.
323 323 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
324 324 this.typeset();
325 325 }
326 326 };
327 327
328 328 OutputArea.display_order = ['javascript','html','latex','svg','png','jpeg','text'];
329 329
330 330 OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
331 331 for(var type_i in OutputArea.display_order){
332 332 var type = OutputArea.display_order[type_i];
333 333 if(json[type] != undefined ){
334 if(type == 'javascript' && dynamic){
334 if(type == 'javascript'){
335 if (dynamic) {
335 336 this.append_javascript(json.javascript, element, dynamic);
337 }
336 338 } else {
337 339 this['append_'+type](json[type], element);
338 340 }
339 341 return;
340 342 }
341 343 }
342 344 };
343 345
344 346
345 347 OutputArea.prototype.append_html = function (html, element) {
346 348 var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_html rendered_html");
347 349 toinsert.append(html);
348 350 element.append(toinsert);
349 351 };
350 352
351 353
352 354 OutputArea.prototype.append_javascript = function (js, container) {
353 355 // We just eval the JS code, element appears in the local scope.
354 356 var element = $("<div/>").addClass("box-flex1 output_subarea");
355 357 container.append(element);
356 358 // Div for js shouldn't be drawn, as it will add empty height to the area.
357 359 container.hide();
358 360 // If the Javascript appends content to `element` that should be drawn, then
359 361 // it must also call `container.show()`.
360 362 try {
361 363 eval(js);
362 364 } catch(err) {
363 365 console.log('Error in Javascript!');
364 366 console.log(err);
365 367 container.show();
366 368 element.append($('<div/>')
367 369 .html("Error in Javascript !<br/>"+
368 370 err.toString()+
369 371 '<br/>See your browser Javascript console for more details.')
370 372 .addClass('js-error')
371 373 );
372 374 }
373 375 };
374 376
375 377
376 378 OutputArea.prototype.append_text = function (data, element, extra_class) {
377 379 var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_text");
378 380 // escape ANSI & HTML specials in plaintext:
379 381 data = utils.wrapUrls(data);
380 382 data = utils.fixConsole(data);
381 383 data = utils.fixCarriageReturn(data);
382 384 data = utils.autoLinkUrls(data);
383 385 if (extra_class){
384 386 toinsert.addClass(extra_class);
385 387 }
386 388 toinsert.append($("<pre/>").html(data));
387 389 element.append(toinsert);
388 390 };
389 391
390 392
391 393 OutputArea.prototype.append_svg = function (svg, element) {
392 394 var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_svg");
393 395 toinsert.append(svg);
394 396 element.append(toinsert);
395 397 };
396 398
397 399
398 400 OutputArea.prototype._dblclick_to_reset_size = function (img) {
399 401 // schedule wrapping image in resizable after a delay,
400 402 // so we don't end up calling resize on a zero-size object
401 403 var that = this;
402 404 setTimeout(function () {
403 405 var h0 = img.height();
404 406 var w0 = img.width();
405 407 if (!(h0 && w0)) {
406 408 // zero size, schedule another timeout
407 409 that._dblclick_to_reset_size(img);
408 410 return;
409 411 }
410 412 img.resizable({
411 413 aspectRatio: true,
412 414 autoHide: true
413 415 });
414 416 img.dblclick(function () {
415 417 // resize wrapper & image together for some reason:
416 418 img.parent().height(h0);
417 419 img.height(h0);
418 420 img.parent().width(w0);
419 421 img.width(w0);
420 422 });
421 423 }, 250);
422 424 };
423 425
424 426
425 427 OutputArea.prototype.append_png = function (png, element) {
426 428 var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_png");
427 429 var img = $("<img/>").attr('src','data:image/png;base64,'+png);
428 430 this._dblclick_to_reset_size(img);
429 431 toinsert.append(img);
430 432 element.append(toinsert);
431 433 };
432 434
433 435
434 436 OutputArea.prototype.append_jpeg = function (jpeg, element) {
435 437 var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_jpeg");
436 438 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
437 439 this._dblclick_to_reset_size(img);
438 440 toinsert.append(img);
439 441 element.append(toinsert);
440 442 };
441 443
442 444
443 445 OutputArea.prototype.append_latex = function (latex, element) {
444 446 // This method cannot do the typesetting because the latex first has to
445 447 // be on the page.
446 448 var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_latex");
447 449 toinsert.append(latex);
448 450 element.append(toinsert);
449 451 };
450 452
451 453
452 454 OutputArea.prototype.handle_clear_output = function (content) {
453 455 this.clear_output(content.stdout, content.stderr, content.other);
454 456 };
455 457
456 458
457 459 OutputArea.prototype.clear_output = function (stdout, stderr, other) {
458 460 var that = this;
459 461 if (this.clear_out_timeout != null){
460 462 // fire previous pending clear *immediately*
461 463 clearTimeout(this.clear_out_timeout);
462 464 this.clear_out_timeout = null;
463 465 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
464 466 }
465 467 // store flags for flushing the timeout
466 468 this._clear_stdout = stdout;
467 469 this._clear_stderr = stderr;
468 470 this._clear_other = other;
469 471 this.clear_out_timeout = setTimeout(function() {
470 472 // really clear timeout only after a short delay
471 473 // this reduces flicker in 'clear_output; print' cases
472 474 that.clear_out_timeout = null;
473 475 that._clear_stdout = that._clear_stderr = that._clear_other = null;
474 476 that.clear_output_callback(stdout, stderr, other);
475 477 }, 500
476 478 );
477 479 };
478 480
479 481
480 482 OutputArea.prototype.clear_output_callback = function (stdout, stderr, other) {
481 483 var output_div = this.element;
482 484
483 485 if (stdout && stderr && other){
484 486 // clear all, no need for logic
485 487 output_div.html("");
486 488 this.outputs = [];
487 489 this.unscroll_area();
488 490 return;
489 491 }
490 492 // remove html output
491 493 // each output_subarea that has an identifying class is in an output_area
492 494 // which is the element to be removed.
493 495 if (stdout) {
494 496 output_div.find("div.output_stdout").parent().remove();
495 497 }
496 498 if (stderr) {
497 499 output_div.find("div.output_stderr").parent().remove();
498 500 }
499 501 if (other) {
500 502 output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
501 503 }
502 504 this.unscroll_area();
503 505
504 506 // remove cleared outputs from JSON list:
505 507 for (var i = this.outputs.length - 1; i >= 0; i--) {
506 508 var out = this.outputs[i];
507 509 var output_type = out.output_type;
508 510 if (output_type == "display_data" && other) {
509 511 this.outputs.splice(i,1);
510 512 } else if (output_type == "stream") {
511 513 if (stdout && out.stream == "stdout") {
512 514 this.outputs.splice(i,1);
513 515 } else if (stderr && out.stream == "stderr") {
514 516 this.outputs.splice(i,1);
515 517 }
516 518 }
517 519 }
518 520 };
519 521
520 522
521 523 OutputArea.prototype.flush_clear_timeout = function() {
522 524 var output_div = this.element;
523 525 if (this.clear_out_timeout){
524 526 clearTimeout(this.clear_out_timeout);
525 527 this.clear_out_timeout = null;
526 528 this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
527 529 }
528 530 };
529 531
530 532
531 533 // JSON serialization
532 534
533 535 OutputArea.prototype.fromJSON = function (outputs) {
534 536 var len = outputs.length;
535 537 for (var i=0; i<len; i++) {
536 538 // append with dynamic=false.
537 539 this.append_output(outputs[i], false);
538 540 }
539 541 };
540 542
541 543
542 544 OutputArea.prototype.toJSON = function () {
543 545 var outputs = [];
544 546 var len = this.outputs.length;
545 547 for (var i=0; i<len; i++) {
546 548 outputs[i] = this.outputs[i];
547 549 }
548 550 return outputs;
549 551 };
550 552
551 553
552 554 IPython.OutputArea = OutputArea;
553 555
554 556 return IPython;
555 557
556 558 }(IPython));
General Comments 0
You need to be logged in to leave comments. Login now