##// END OF EJS Templates
make output code not drop non-mimetype-keyed json
Paul Ivanov -
Show More
@@ -1,739 +1,749 b''
1 1 //----------------------------------------------------------------------------
2 2 // Copyright (C) 2008 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 /**
13 13 * @module IPython
14 14 * @namespace IPython
15 15 * @submodule OutputArea
16 16 */
17 17 var IPython = (function (IPython) {
18 18 "use strict";
19 19
20 20 var utils = IPython.utils;
21 21
22 22 /**
23 23 * @class OutputArea
24 24 *
25 25 * @constructor
26 26 */
27 27
28 28 var OutputArea = function (selector, prompt_area) {
29 29 this.selector = selector;
30 30 this.wrapper = $(selector);
31 31 this.outputs = [];
32 32 this.collapsed = false;
33 33 this.scrolled = false;
34 34 this.clear_queued = null;
35 35 if (prompt_area === undefined) {
36 36 this.prompt_area = true;
37 37 } else {
38 38 this.prompt_area = prompt_area;
39 39 }
40 40 this.create_elements();
41 41 this.style();
42 42 this.bind_events();
43 43 };
44 44
45 45 OutputArea.prototype.create_elements = function () {
46 46 this.element = $("<div/>");
47 47 this.collapse_button = $("<div/>");
48 48 this.prompt_overlay = $("<div/>");
49 49 this.wrapper.append(this.prompt_overlay);
50 50 this.wrapper.append(this.element);
51 51 this.wrapper.append(this.collapse_button);
52 52 };
53 53
54 54
55 55 OutputArea.prototype.style = function () {
56 56 this.collapse_button.hide();
57 57 this.prompt_overlay.hide();
58 58
59 59 this.wrapper.addClass('output_wrapper');
60 60 this.element.addClass('output');
61 61
62 62 this.collapse_button.addClass("btn output_collapsed");
63 63 this.collapse_button.attr('title', 'click to expand output');
64 64 this.collapse_button.html('. . .');
65 65
66 66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68 68
69 69 this.collapse();
70 70 };
71 71
72 72 /**
73 73 * Should the OutputArea scroll?
74 74 * Returns whether the height (in lines) exceeds a threshold.
75 75 *
76 76 * @private
77 77 * @method _should_scroll
78 78 * @param [lines=100]{Integer}
79 79 * @return {Bool}
80 80 *
81 81 */
82 82 OutputArea.prototype._should_scroll = function (lines) {
83 83 if (lines <=0 ){ return }
84 84 if (!lines) {
85 85 lines = 100;
86 86 }
87 87 // line-height from http://stackoverflow.com/questions/1185151
88 88 var fontSize = this.element.css('font-size');
89 89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90 90
91 91 return (this.element.height() > lines * lineHeight);
92 92 };
93 93
94 94
95 95 OutputArea.prototype.bind_events = function () {
96 96 var that = this;
97 97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99 99
100 100 this.element.resize(function () {
101 101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 102 if ( IPython.utils.browser[0] === "Firefox" ) {
103 103 return;
104 104 }
105 105 // maybe scroll output,
106 106 // if it's grown large enough and hasn't already been scrolled.
107 107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 108 that.scroll_area();
109 109 }
110 110 });
111 111 this.collapse_button.click(function () {
112 112 that.expand();
113 113 });
114 114 };
115 115
116 116
117 117 OutputArea.prototype.collapse = function () {
118 118 if (!this.collapsed) {
119 119 this.element.hide();
120 120 this.prompt_overlay.hide();
121 121 if (this.element.html()){
122 122 this.collapse_button.show();
123 123 }
124 124 this.collapsed = true;
125 125 }
126 126 };
127 127
128 128
129 129 OutputArea.prototype.expand = function () {
130 130 if (this.collapsed) {
131 131 this.collapse_button.hide();
132 132 this.element.show();
133 133 this.prompt_overlay.show();
134 134 this.collapsed = false;
135 135 }
136 136 };
137 137
138 138
139 139 OutputArea.prototype.toggle_output = function () {
140 140 if (this.collapsed) {
141 141 this.expand();
142 142 } else {
143 143 this.collapse();
144 144 }
145 145 };
146 146
147 147
148 148 OutputArea.prototype.scroll_area = function () {
149 149 this.element.addClass('output_scroll');
150 150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 151 this.scrolled = true;
152 152 };
153 153
154 154
155 155 OutputArea.prototype.unscroll_area = function () {
156 156 this.element.removeClass('output_scroll');
157 157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 158 this.scrolled = false;
159 159 };
160 160
161 161 /**
162 162 * Threshold to trigger autoscroll when the OutputArea is resized,
163 163 * typically when new outputs are added.
164 164 *
165 165 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
166 166 * unless it is < 0, in which case autoscroll will never be triggered
167 167 *
168 168 * @property auto_scroll_threshold
169 169 * @type Number
170 170 * @default 100
171 171 *
172 172 **/
173 173 OutputArea.auto_scroll_threshold = 100;
174 174
175 175
176 176 /**
177 177 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
178 178 * shorter than this are never scrolled.
179 179 *
180 180 * @property minimum_scroll_threshold
181 181 * @type Number
182 182 * @default 20
183 183 *
184 184 **/
185 185 OutputArea.minimum_scroll_threshold = 20;
186 186
187 187
188 188 /**
189 189 *
190 190 * Scroll OutputArea if height supperior than a threshold (in lines).
191 191 *
192 192 * Threshold is a maximum number of lines. If unspecified, defaults to
193 193 * OutputArea.minimum_scroll_threshold.
194 194 *
195 195 * Negative threshold will prevent the OutputArea from ever scrolling.
196 196 *
197 197 * @method scroll_if_long
198 198 *
199 199 * @param [lines=20]{Number} Default to 20 if not set,
200 200 * behavior undefined for value of `0`.
201 201 *
202 202 **/
203 203 OutputArea.prototype.scroll_if_long = function (lines) {
204 204 var n = lines | OutputArea.minimum_scroll_threshold;
205 205 if(n <= 0){
206 206 return
207 207 }
208 208
209 209 if (this._should_scroll(n)) {
210 210 // only allow scrolling long-enough output
211 211 this.scroll_area();
212 212 }
213 213 };
214 214
215 215
216 216 OutputArea.prototype.toggle_scroll = function () {
217 217 if (this.scrolled) {
218 218 this.unscroll_area();
219 219 } else {
220 220 // only allow scrolling long-enough output
221 221 this.scroll_if_long();
222 222 }
223 223 };
224 224
225 225
226 226 // typeset with MathJax if MathJax is available
227 227 OutputArea.prototype.typeset = function () {
228 228 if (window.MathJax){
229 229 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
230 230 }
231 231 };
232 232
233 233
234 234 OutputArea.prototype.handle_output = function (msg) {
235 235 var json = {};
236 236 var msg_type = json.output_type = msg.header.msg_type;
237 237 var content = msg.content;
238 238 if (msg_type === "stream") {
239 239 json.text = content.data;
240 240 json.stream = content.name;
241 241 } else if (msg_type === "display_data") {
242 242 json = this.convert_mime_types(json, content.data);
243 243 json.metadata = this.convert_mime_types({}, content.metadata);
244 244 } else if (msg_type === "pyout") {
245 245 json.prompt_number = content.execution_count;
246 246 json = this.convert_mime_types(json, content.data);
247 247 json.metadata = this.convert_mime_types({}, content.metadata);
248 248 } else if (msg_type === "pyerr") {
249 249 json.ename = content.ename;
250 250 json.evalue = content.evalue;
251 251 json.traceback = content.traceback;
252 252 }
253 253 // append with dynamic=true
254 254 this.append_output(json, true);
255 255 };
256 256
257 var mime_types = ['application/javascript', 'application/json',
258 'image/jpeg', 'image/png', 'image/svg+xml', 'text/html',
259 'text/latex', 'text/plain'];
257 260
258 261 OutputArea.prototype.convert_mime_types = function (json, data) {
259 262 if (data === undefined) {
260 263 return json;
261 264 }
262 265 if (data['text/plain'] !== undefined) {
263 266 json.text = data['text/plain'];
264 267 }
265 268 if (data['text/html'] !== undefined) {
266 269 json.html = data['text/html'];
267 270 }
268 271 if (data['image/svg+xml'] !== undefined) {
269 272 json.svg = data['image/svg+xml'];
270 273 }
271 274 if (data['image/png'] !== undefined) {
272 275 json.png = data['image/png'];
273 276 }
274 277 if (data['image/jpeg'] !== undefined) {
275 278 json.jpeg = data['image/jpeg'];
276 279 }
277 280 if (data['text/latex'] !== undefined) {
278 281 json.latex = data['text/latex'];
279 282 }
280 283 if (data['application/json'] !== undefined) {
281 284 json.json = data['application/json'];
282 285 }
283 286 if (data['application/javascript'] !== undefined) {
284 287 json.javascript = data['application/javascript'];
285 288 }
289 // non-mimetype-keyed metadata used to get dropped here, this code
290 // re-injects it into the json.
291 for (x in data){
292 if( !(x in mime_types) ) {
293 json[x] = data[x];
294 }
295 }
286 296 return json;
287 297 };
288 298
289 299
290 300 OutputArea.prototype.append_output = function (json, dynamic) {
291 301 // If dynamic is true, javascript output will be eval'd.
292 302 this.expand();
293 303 // Clear the output if clear is queued.
294 304 var needs_height_reset = false;
295 305 if (this.clear_queued) {
296 306 this.clear_output(false);
297 307 needs_height_reset = true;
298 308 }
299 309
300 310 if (json.output_type === 'pyout') {
301 311 this.append_pyout(json, dynamic);
302 312 } else if (json.output_type === 'pyerr') {
303 313 this.append_pyerr(json);
304 314 } else if (json.output_type === 'display_data') {
305 315 this.append_display_data(json, dynamic);
306 316 } else if (json.output_type === 'stream') {
307 317 this.append_stream(json);
308 318 }
309 319 this.outputs.push(json);
310 320
311 321 // Only reset the height to automatic if the height is currently
312 322 // fixed (done by wait=True flag on clear_output).
313 323 if (needs_height_reset) {
314 324 this.element.height('');
315 325 }
316 326
317 327 var that = this;
318 328 setTimeout(function(){that.element.trigger('resize');}, 100);
319 329 };
320 330
321 331
322 332 OutputArea.prototype.create_output_area = function () {
323 333 var oa = $("<div/>").addClass("output_area");
324 334 if (this.prompt_area) {
325 335 oa.append($('<div/>').addClass('prompt'));
326 336 }
327 337 return oa;
328 338 };
329 339
330 340
331 341 OutputArea.prototype.create_output_subarea = function(md, classes) {
332 342 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
333 343 if (md['isolated']) {
334 344 // Create an iframe to isolate the subarea from the rest of the
335 345 // document
336 346 var iframe = $('<iframe/>').addClass('box-flex1');
337 347 iframe.css({'height':1, 'width':'100%', 'display':'block'});
338 348 iframe.attr('frameborder', 0);
339 349 iframe.attr('scrolling', 'auto');
340 350
341 351 // Once the iframe is loaded, the subarea is dynamically inserted
342 352 iframe.on('load', function() {
343 353 // Workaround needed by Firefox, to properly render svg inside
344 354 // iframes, see http://stackoverflow.com/questions/10177190/
345 355 // svg-dynamically-added-to-iframe-does-not-render-correctly
346 356 this.contentDocument.open();
347 357
348 358 // Insert the subarea into the iframe
349 359 // We must directly write the html. When using Jquery's append
350 360 // method, javascript is evaluated in the parent document and
351 361 // not in the iframe document.
352 362 this.contentDocument.write(subarea.html());
353 363
354 364 this.contentDocument.close();
355 365
356 366 var body = this.contentDocument.body;
357 367 // Adjust the iframe height automatically
358 368 iframe.height(body.scrollHeight + 'px');
359 369 });
360 370
361 371 // Elements should be appended to the inner subarea and not to the
362 372 // iframe
363 373 iframe.append = function(that) {
364 374 subarea.append(that);
365 375 };
366 376
367 377 return iframe;
368 378 } else {
369 379 return subarea;
370 380 }
371 381 }
372 382
373 383
374 384 OutputArea.prototype._append_javascript_error = function (err, element) {
375 385 // display a message when a javascript error occurs in display output
376 386 var msg = "Javascript error adding output!"
377 387 if ( element === undefined ) return;
378 388 element.append(
379 389 $('<div/>').html(msg + "<br/>" +
380 390 err.toString() +
381 391 '<br/>See your browser Javascript console for more details.'
382 392 ).addClass('js-error')
383 393 );
384 394 };
385 395
386 396 OutputArea.prototype._safe_append = function (toinsert) {
387 397 // safely append an item to the document
388 398 // this is an object created by user code,
389 399 // and may have errors, which should not be raised
390 400 // under any circumstances.
391 401 try {
392 402 this.element.append(toinsert);
393 403 } catch(err) {
394 404 console.log(err);
395 405 // Create an actual output_area and output_subarea, which creates
396 406 // the prompt area and the proper indentation.
397 407 var toinsert = this.create_output_area();
398 408 var subarea = $('<div/>').addClass('output_subarea');
399 409 toinsert.append(subarea);
400 410 this._append_javascript_error(err, subarea);
401 411 this.element.append(toinsert);
402 412 }
403 413 };
404 414
405 415
406 416 OutputArea.prototype.append_pyout = function (json, dynamic) {
407 417 var n = json.prompt_number || ' ';
408 418 var toinsert = this.create_output_area();
409 419 if (this.prompt_area) {
410 420 toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
411 421 }
412 422 this.append_mime_type(json, toinsert, dynamic);
413 423 this._safe_append(toinsert);
414 424 // If we just output latex, typeset it.
415 425 if ((json.latex !== undefined) || (json.html !== undefined)) {
416 426 this.typeset();
417 427 }
418 428 };
419 429
420 430
421 431 OutputArea.prototype.append_pyerr = function (json) {
422 432 var tb = json.traceback;
423 433 if (tb !== undefined && tb.length > 0) {
424 434 var s = '';
425 435 var len = tb.length;
426 436 for (var i=0; i<len; i++) {
427 437 s = s + tb[i] + '\n';
428 438 }
429 439 s = s + '\n';
430 440 var toinsert = this.create_output_area();
431 441 this.append_text(s, {}, toinsert);
432 442 this._safe_append(toinsert);
433 443 }
434 444 };
435 445
436 446
437 447 OutputArea.prototype.append_stream = function (json) {
438 448 // temporary fix: if stream undefined (json file written prior to this patch),
439 449 // default to most likely stdout:
440 450 if (json.stream == undefined){
441 451 json.stream = 'stdout';
442 452 }
443 453 var text = json.text;
444 454 var subclass = "output_"+json.stream;
445 455 if (this.outputs.length > 0){
446 456 // have at least one output to consider
447 457 var last = this.outputs[this.outputs.length-1];
448 458 if (last.output_type == 'stream' && json.stream == last.stream){
449 459 // latest output was in the same stream,
450 460 // so append directly into its pre tag
451 461 // escape ANSI & HTML specials:
452 462 var pre = this.element.find('div.'+subclass).last().find('pre');
453 463 var html = utils.fixCarriageReturn(
454 464 pre.html() + utils.fixConsole(text));
455 465 pre.html(html);
456 466 return;
457 467 }
458 468 }
459 469
460 470 if (!text.replace("\r", "")) {
461 471 // text is nothing (empty string, \r, etc.)
462 472 // so don't append any elements, which might add undesirable space
463 473 return;
464 474 }
465 475
466 476 // If we got here, attach a new div
467 477 var toinsert = this.create_output_area();
468 478 this.append_text(text, {}, toinsert, "output_stream "+subclass);
469 479 this._safe_append(toinsert);
470 480 };
471 481
472 482
473 483 OutputArea.prototype.append_display_data = function (json, dynamic) {
474 484 var toinsert = this.create_output_area();
475 485 if (this.append_mime_type(json, toinsert, dynamic)) {
476 486 this._safe_append(toinsert);
477 487 // If we just output latex, typeset it.
478 488 if ( (json.latex !== undefined) || (json.html !== undefined) ) {
479 489 this.typeset();
480 490 }
481 491 }
482 492 };
483 493
484 494 OutputArea.display_order = ['javascript','html','latex','svg','png','jpeg','text'];
485 495
486 496 OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
487 497 for(var type_i in OutputArea.display_order){
488 498 var type = OutputArea.display_order[type_i];
489 499 if(json[type] != undefined ){
490 500 var md = {};
491 501 if (json.metadata && json.metadata[type]) {
492 502 md = json.metadata[type];
493 503 };
494 504 if(type == 'javascript'){
495 505 if (dynamic) {
496 506 this.append_javascript(json.javascript, md, element, dynamic);
497 507 return true;
498 508 }
499 509 } else {
500 510 this['append_'+type](json[type], md, element);
501 511 return true;
502 512 }
503 513 return false;
504 514 }
505 515 }
506 516 return false;
507 517 };
508 518
509 519
510 520 OutputArea.prototype.append_html = function (html, md, element) {
511 521 var toinsert = this.create_output_subarea(md, "output_html rendered_html");
512 522 IPython.keyboard_manager.register_events(toinsert);
513 523 toinsert.append(html);
514 524 element.append(toinsert);
515 525 };
516 526
517 527
518 528 OutputArea.prototype.append_javascript = function (js, md, container) {
519 529 // We just eval the JS code, element appears in the local scope.
520 530 var element = this.create_output_subarea(md, "output_javascript");
521 531 IPython.keyboard_manager.register_events(element);
522 532 container.append(element);
523 533 try {
524 534 eval(js);
525 535 } catch(err) {
526 536 console.log(err);
527 537 this._append_javascript_error(err, element);
528 538 }
529 539 };
530 540
531 541
532 542 OutputArea.prototype.append_text = function (data, md, element, extra_class) {
533 543 var toinsert = this.create_output_subarea(md, "output_text");
534 544 // escape ANSI & HTML specials in plaintext:
535 545 data = utils.fixConsole(data);
536 546 data = utils.fixCarriageReturn(data);
537 547 data = utils.autoLinkUrls(data);
538 548 if (extra_class){
539 549 toinsert.addClass(extra_class);
540 550 }
541 551 toinsert.append($("<pre/>").html(data));
542 552 element.append(toinsert);
543 553 };
544 554
545 555
546 556 OutputArea.prototype.append_svg = function (svg, md, element) {
547 557 var toinsert = this.create_output_subarea(md, "output_svg");
548 558 toinsert.append(svg);
549 559 element.append(toinsert);
550 560 };
551 561
552 562
553 563 OutputArea.prototype._dblclick_to_reset_size = function (img) {
554 564 // schedule wrapping image in resizable after a delay,
555 565 // so we don't end up calling resize on a zero-size object
556 566 var that = this;
557 567 setTimeout(function () {
558 568 var h0 = img.height();
559 569 var w0 = img.width();
560 570 if (!(h0 && w0)) {
561 571 // zero size, schedule another timeout
562 572 that._dblclick_to_reset_size(img);
563 573 return;
564 574 }
565 575 img.resizable({
566 576 aspectRatio: true,
567 577 autoHide: true
568 578 });
569 579 img.dblclick(function () {
570 580 // resize wrapper & image together for some reason:
571 581 img.parent().height(h0);
572 582 img.height(h0);
573 583 img.parent().width(w0);
574 584 img.width(w0);
575 585 });
576 586 }, 250);
577 587 };
578 588
579 589
580 590 OutputArea.prototype.append_png = function (png, md, element) {
581 591 var toinsert = this.create_output_subarea(md, "output_png");
582 592 var img = $("<img/>");
583 593 img[0].setAttribute('src','data:image/png;base64,'+png);
584 594 if (md['height']) {
585 595 img[0].setAttribute('height', md['height']);
586 596 }
587 597 if (md['width']) {
588 598 img[0].setAttribute('width', md['width']);
589 599 }
590 600 this._dblclick_to_reset_size(img);
591 601 toinsert.append(img);
592 602 element.append(toinsert);
593 603 };
594 604
595 605
596 606 OutputArea.prototype.append_jpeg = function (jpeg, md, element) {
597 607 var toinsert = this.create_output_subarea(md, "output_jpeg");
598 608 var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
599 609 if (md['height']) {
600 610 img.attr('height', md['height']);
601 611 }
602 612 if (md['width']) {
603 613 img.attr('width', md['width']);
604 614 }
605 615 this._dblclick_to_reset_size(img);
606 616 toinsert.append(img);
607 617 element.append(toinsert);
608 618 };
609 619
610 620
611 621 OutputArea.prototype.append_latex = function (latex, md, element) {
612 622 // This method cannot do the typesetting because the latex first has to
613 623 // be on the page.
614 624 var toinsert = this.create_output_subarea(md, "output_latex");
615 625 toinsert.append(latex);
616 626 element.append(toinsert);
617 627 };
618 628
619 629 OutputArea.prototype.append_raw_input = function (msg) {
620 630 var that = this;
621 631 this.expand();
622 632 var content = msg.content;
623 633 var area = this.create_output_area();
624 634
625 635 // disable any other raw_inputs, if they are left around
626 636 $("div.output_subarea.raw_input").remove();
627 637
628 638 area.append(
629 639 $("<div/>")
630 640 .addClass("box-flex1 output_subarea raw_input")
631 641 .append(
632 642 $("<span/>")
633 643 .addClass("input_prompt")
634 644 .text(content.prompt)
635 645 )
636 646 .append(
637 647 $("<input/>")
638 648 .addClass("raw_input")
639 649 .attr('type', 'text')
640 650 .attr("size", 47)
641 651 .keydown(function (event, ui) {
642 652 // make sure we submit on enter,
643 653 // and don't re-execute the *cell* on shift-enter
644 654 if (event.which === utils.keycodes.ENTER) {
645 655 that._submit_raw_input();
646 656 return false;
647 657 }
648 658 })
649 659 )
650 660 );
651 661
652 662 this.element.append(area);
653 663 var raw_input = area.find('input.raw_input');
654 664 // Register events that enable/disable the keyboard manager while raw
655 665 // input is focused.
656 666 IPython.keyboard_manager.register_events(raw_input);
657 667 // Note, the following line used to read raw_input.focus().focus().
658 668 // This seemed to be needed otherwise only the cell would be focused.
659 669 // But with the modal UI, this seems to work fine with one call to focus().
660 670 raw_input.focus();
661 671 }
662 672
663 673 OutputArea.prototype._submit_raw_input = function (evt) {
664 674 var container = this.element.find("div.raw_input");
665 675 var theprompt = container.find("span.input_prompt");
666 676 var theinput = container.find("input.raw_input");
667 677 var value = theinput.val();
668 678 var content = {
669 679 output_type : 'stream',
670 680 name : 'stdout',
671 681 text : theprompt.text() + value + '\n'
672 682 }
673 683 // remove form container
674 684 container.parent().remove();
675 685 // replace with plaintext version in stdout
676 686 this.append_output(content, false);
677 687 $([IPython.events]).trigger('send_input_reply.Kernel', value);
678 688 }
679 689
680 690
681 691 OutputArea.prototype.handle_clear_output = function (msg) {
682 692 this.clear_output(msg.content.wait);
683 693 };
684 694
685 695
686 696 OutputArea.prototype.clear_output = function(wait) {
687 697 if (wait) {
688 698
689 699 // If a clear is queued, clear before adding another to the queue.
690 700 if (this.clear_queued) {
691 701 this.clear_output(false);
692 702 };
693 703
694 704 this.clear_queued = true;
695 705 } else {
696 706
697 707 // Fix the output div's height if the clear_output is waiting for
698 708 // new output (it is being used in an animation).
699 709 if (this.clear_queued) {
700 710 var height = this.element.height();
701 711 this.element.height(height);
702 712 this.clear_queued = false;
703 713 }
704 714
705 715 // clear all, no need for logic
706 716 this.element.html("");
707 717 this.outputs = [];
708 718 this.unscroll_area();
709 719 return;
710 720 };
711 721 };
712 722
713 723
714 724 // JSON serialization
715 725
716 726 OutputArea.prototype.fromJSON = function (outputs) {
717 727 var len = outputs.length;
718 728 for (var i=0; i<len; i++) {
719 729 // append with dynamic=false.
720 730 this.append_output(outputs[i], false);
721 731 }
722 732 };
723 733
724 734
725 735 OutputArea.prototype.toJSON = function () {
726 736 var outputs = [];
727 737 var len = this.outputs.length;
728 738 for (var i=0; i<len; i++) {
729 739 outputs[i] = this.outputs[i];
730 740 }
731 741 return outputs;
732 742 };
733 743
734 744
735 745 IPython.OutputArea = OutputArea;
736 746
737 747 return IPython;
738 748
739 749 }(IPython));
@@ -1,68 +1,94 b''
1 1 //
2 2 // Test display isolation
3 3 // An object whose metadata contains an "isolated" tag must be isolated
4 4 // from the rest of the document. In the case of inline SVGs, this means
5 5 // that multiple SVGs have different scopes. This test checks that there
6 6 // are no CSS leaks between two isolated SVGs.
7 7 //
8 8
9 9 casper.notebook_test(function () {
10 10 this.evaluate(function () {
11 11 var cell = IPython.notebook.get_cell(0);
12 12 cell.set_text( "from IPython.core.display import SVG, display_svg\n"
13 13 + "s1 = '''<svg width='1cm' height='1cm' viewBox='0 0 1000 500'>"
14 14 + "<defs><style>rect {fill:red;}; </style></defs>"
15 15 + "<rect id='r1' x='200' y='100' width='600' height='300' /></svg>"
16 16 + "'''\n"
17 17 + "s2 = '''<svg width='1cm' height='1cm' viewBox='0 0 1000 500'>"
18 18 + "<rect id='r2' x='200' y='100' width='600' height='300' /></svg>"
19 19 + "'''\n"
20 20 + "display_svg(SVG(s1), metadata=dict(isolated=True))\n"
21 21 + "display_svg(SVG(s2), metadata=dict(isolated=True))\n"
22 22 );
23 23 cell.execute();
24 console.log("hello" );
25 });
26
27 this.then(function() {
28 var fname=this.test.currentTestFile.split('/').pop().toLowerCase();
29 this.echo(fname)
30 this.echo(this.currentUrl)
31 this.evaluate(function (n) {
32 IPython.notebook.rename(n);
33 console.write("hello" + n);
34 IPython.notebook.save_notebook();
35 }, {n : fname});
36 this.echo(this.currentUrl)
37 });
38
39 this.then(function() {
40
41 url = this.evaluate(function() {
42 IPython.notebook.rename("foo");
43 //$("span#notebook_name")[0].click();
44 //$("input")[0].value = "please-work";
45 //$(".btn-primary")[0].click();
46 return document.location.href;
47 })
48 this.echo("renamed" + url);
49 this.echo(this.currentUrl);
24 50 });
25 51
26 52 this.wait_for_output(0);
27 53
28 54 this.then(function () {
29 55 var colors = this.evaluate(function () {
30 56 var colors = [];
31 57 var ifr = __utils__.findAll("iframe");
32 58 var svg1 = ifr[0].contentWindow.document.getElementById('r1');
33 59 colors[0] = window.getComputedStyle(svg1)["fill"];
34 60 var svg2 = ifr[1].contentWindow.document.getElementById('r2');
35 61 colors[1] = window.getComputedStyle(svg2)["fill"];
36 62 return colors;
37 63 });
38 64
39 65 this.test.assertEquals(colors[0], '#ff0000', 'First svg should be red');
40 66 this.test.assertEquals(colors[1], '#000000', 'Second svg should be black');
41 67 });
42 68
43 69 // now ensure that we can pass the same metadata dict to plain old display()
44 70 this.thenEvaluate(function () {
45 71 var cell = IPython.notebook.get_cell(0);
46 72 cell.set_text( "from IPython.display import display\n"
47 73 + "display(SVG(s1), metadata=dict(isolated=True))\n"
48 74 + "display(SVG(s2), metadata=dict(isolated=True))\n"
49 75 );
50 76 cell.execute();
51 77 });
52 78
53 79 // same test as original
54 80 this.then(function () {
55 81 var colors = this.evaluate(function () {
56 82 var colors = [];
57 83 var ifr = __utils__.findAll("iframe");
58 84 var svg1 = ifr[0].contentWindow.document.getElementById('r1');
59 85 colors[0] = window.getComputedStyle(svg1)["fill"];
60 86 var svg2 = ifr[1].contentWindow.document.getElementById('r2');
61 87 colors[1] = window.getComputedStyle(svg2)["fill"];
62 88 return colors;
63 89 });
64 90
65 91 this.test.assertEquals(colors[0], '#ff0000', 'First svg should be red');
66 92 this.test.assertEquals(colors[1], '#000000', 'Second svg should be black');
67 93 });
68 94 });
General Comments 0
You need to be logged in to leave comments. Login now