##// END OF EJS Templates
Merge pull request #6069 from minrk/coalesce_streams...
Thomas Kluyver -
r17311:52e1e06a merge
parent child Browse files
Show More
@@ -0,0 +1,98 b''
1 //
2 // Various output tests
3 //
4
5 casper.notebook_test(function () {
6
7 this.test_coalesced_output = function (msg, code, expected) {
8 this.then(function () {
9 this.echo("Test coalesced output: " + msg);
10 });
11
12 this.thenEvaluate(function (code) {
13 IPython.notebook.insert_cell_at_index(0, "code");
14 var cell = IPython.notebook.get_cell(0);
15 cell.set_text(code);
16 cell.execute();
17 }, {code: code});
18
19 this.wait_for_output(0);
20
21 this.then(function () {
22 var results = this.evaluate(function () {
23 var cell = IPython.notebook.get_cell(0);
24 return cell.output_area.outputs;
25 });
26 this.test.assertEquals(results.length, expected.length, "correct number of outputs");
27 for (var i = 0; i < results.length; i++) {
28 var r = results[i];
29 var ex = expected[i];
30 this.test.assertEquals(r.output_type, ex.output_type, "output " + i);
31 if (r.output_type === 'stream') {
32 this.test.assertEquals(r.stream, ex.stream, "stream " + i);
33 this.test.assertEquals(r.text, ex.text, "content " + i);
34 }
35 }
36 });
37
38 };
39
40 this.thenEvaluate(function () {
41 IPython.notebook.insert_cell_at_index(0, "code");
42 var cell = IPython.notebook.get_cell(0);
43 cell.set_text([
44 "from __future__ import print_function",
45 "import sys",
46 "from IPython.display import display"
47 ].join("\n")
48 );
49 cell.execute();
50 });
51
52 this.test_coalesced_output("stdout", [
53 "print(1)",
54 "sys.stdout.flush()",
55 "print(2)",
56 "sys.stdout.flush()",
57 "print(3)"
58 ].join("\n"), [{
59 output_type: "stream",
60 stream: "stdout",
61 text: "1\n2\n3\n"
62 }]
63 );
64
65 this.test_coalesced_output("stdout+sdterr", [
66 "print(1)",
67 "sys.stdout.flush()",
68 "print(2)",
69 "print(3, file=sys.stderr)"
70 ].join("\n"), [{
71 output_type: "stream",
72 stream: "stdout",
73 text: "1\n2\n"
74 },{
75 output_type: "stream",
76 stream: "stderr",
77 text: "3\n"
78 }]
79 );
80
81 this.test_coalesced_output("display splits streams", [
82 "print(1)",
83 "sys.stdout.flush()",
84 "display(2)",
85 "print(3)"
86 ].join("\n"), [{
87 output_type: "stream",
88 stream: "stdout",
89 text: "1\n"
90 },{
91 output_type: "display_data",
92 },{
93 output_type: "stream",
94 stream: "stdout",
95 text: "3\n"
96 }]
97 );
98 });
@@ -0,0 +1,5 b''
1 - Consecutive stream (stdout/stderr) output is merged into a single output
2 in the notebook document.
3 Previously, all output messages were preserved as separate output fields in the JSON.
4 Now, the same merge is applied to the stored output as the displayed output,
5 improving document load time for notebooks with many small outputs.
@@ -1,992 +1,1001 b''
1 // Copyright (c) IPython Development Team.
1 // Copyright (c) IPython Development Team.
2 // Distributed under the terms of the Modified BSD License.
2 // Distributed under the terms of the Modified BSD License.
3
3
4 define([
4 define([
5 'base/js/namespace',
5 'base/js/namespace',
6 'jqueryui',
6 'jqueryui',
7 'base/js/utils',
7 'base/js/utils',
8 'base/js/security',
8 'base/js/security',
9 'base/js/keyboard',
9 'base/js/keyboard',
10 'notebook/js/mathjaxutils',
10 'notebook/js/mathjaxutils',
11 ], function(IPython, $, utils, security, keyboard, mathjaxutils) {
11 ], function(IPython, $, utils, security, keyboard, mathjaxutils) {
12 "use strict";
12 "use strict";
13
13
14 /**
14 /**
15 * @class OutputArea
15 * @class OutputArea
16 *
16 *
17 * @constructor
17 * @constructor
18 */
18 */
19
19
20 var OutputArea = function (options) {
20 var OutputArea = function (options) {
21 this.selector = options.selector;
21 this.selector = options.selector;
22 this.events = options.events;
22 this.events = options.events;
23 this.keyboard_manager = options.keyboard_manager;
23 this.keyboard_manager = options.keyboard_manager;
24 this.wrapper = $(options.selector);
24 this.wrapper = $(options.selector);
25 this.outputs = [];
25 this.outputs = [];
26 this.collapsed = false;
26 this.collapsed = false;
27 this.scrolled = false;
27 this.scrolled = false;
28 this.trusted = true;
28 this.trusted = true;
29 this.clear_queued = null;
29 this.clear_queued = null;
30 if (options.prompt_area === undefined) {
30 if (options.prompt_area === undefined) {
31 this.prompt_area = true;
31 this.prompt_area = true;
32 } else {
32 } else {
33 this.prompt_area = options.prompt_area;
33 this.prompt_area = options.prompt_area;
34 }
34 }
35 this.create_elements();
35 this.create_elements();
36 this.style();
36 this.style();
37 this.bind_events();
37 this.bind_events();
38 };
38 };
39
39
40
40
41 /**
41 /**
42 * Class prototypes
42 * Class prototypes
43 **/
43 **/
44
44
45 OutputArea.prototype.create_elements = function () {
45 OutputArea.prototype.create_elements = function () {
46 this.element = $("<div/>");
46 this.element = $("<div/>");
47 this.collapse_button = $("<div/>");
47 this.collapse_button = $("<div/>");
48 this.prompt_overlay = $("<div/>");
48 this.prompt_overlay = $("<div/>");
49 this.wrapper.append(this.prompt_overlay);
49 this.wrapper.append(this.prompt_overlay);
50 this.wrapper.append(this.element);
50 this.wrapper.append(this.element);
51 this.wrapper.append(this.collapse_button);
51 this.wrapper.append(this.collapse_button);
52 };
52 };
53
53
54
54
55 OutputArea.prototype.style = function () {
55 OutputArea.prototype.style = function () {
56 this.collapse_button.hide();
56 this.collapse_button.hide();
57 this.prompt_overlay.hide();
57 this.prompt_overlay.hide();
58
58
59 this.wrapper.addClass('output_wrapper');
59 this.wrapper.addClass('output_wrapper');
60 this.element.addClass('output');
60 this.element.addClass('output');
61
61
62 this.collapse_button.addClass("btn btn-default output_collapsed");
62 this.collapse_button.addClass("btn btn-default output_collapsed");
63 this.collapse_button.attr('title', 'click to expand output');
63 this.collapse_button.attr('title', 'click to expand output');
64 this.collapse_button.text('. . .');
64 this.collapse_button.text('. . .');
65
65
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
66 this.prompt_overlay.addClass('out_prompt_overlay prompt');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
67 this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
68
68
69 this.collapse();
69 this.collapse();
70 };
70 };
71
71
72 /**
72 /**
73 * Should the OutputArea scroll?
73 * Should the OutputArea scroll?
74 * Returns whether the height (in lines) exceeds a threshold.
74 * Returns whether the height (in lines) exceeds a threshold.
75 *
75 *
76 * @private
76 * @private
77 * @method _should_scroll
77 * @method _should_scroll
78 * @param [lines=100]{Integer}
78 * @param [lines=100]{Integer}
79 * @return {Bool}
79 * @return {Bool}
80 *
80 *
81 */
81 */
82 OutputArea.prototype._should_scroll = function (lines) {
82 OutputArea.prototype._should_scroll = function (lines) {
83 if (lines <=0 ){ return }
83 if (lines <=0 ){ return }
84 if (!lines) {
84 if (!lines) {
85 lines = 100;
85 lines = 100;
86 }
86 }
87 // line-height from http://stackoverflow.com/questions/1185151
87 // line-height from http://stackoverflow.com/questions/1185151
88 var fontSize = this.element.css('font-size');
88 var fontSize = this.element.css('font-size');
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
89 var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
90
90
91 return (this.element.height() > lines * lineHeight);
91 return (this.element.height() > lines * lineHeight);
92 };
92 };
93
93
94
94
95 OutputArea.prototype.bind_events = function () {
95 OutputArea.prototype.bind_events = function () {
96 var that = this;
96 var that = this;
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
97 this.prompt_overlay.dblclick(function () { that.toggle_output(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
98 this.prompt_overlay.click(function () { that.toggle_scroll(); });
99
99
100 this.element.resize(function () {
100 this.element.resize(function () {
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
101 // FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
102 if ( utils.browser[0] === "Firefox" ) {
102 if ( utils.browser[0] === "Firefox" ) {
103 return;
103 return;
104 }
104 }
105 // maybe scroll output,
105 // maybe scroll output,
106 // if it's grown large enough and hasn't already been scrolled.
106 // if it's grown large enough and hasn't already been scrolled.
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
107 if ( !that.scrolled && that._should_scroll(OutputArea.auto_scroll_threshold)) {
108 that.scroll_area();
108 that.scroll_area();
109 }
109 }
110 });
110 });
111 this.collapse_button.click(function () {
111 this.collapse_button.click(function () {
112 that.expand();
112 that.expand();
113 });
113 });
114 };
114 };
115
115
116
116
117 OutputArea.prototype.collapse = function () {
117 OutputArea.prototype.collapse = function () {
118 if (!this.collapsed) {
118 if (!this.collapsed) {
119 this.element.hide();
119 this.element.hide();
120 this.prompt_overlay.hide();
120 this.prompt_overlay.hide();
121 if (this.element.html()){
121 if (this.element.html()){
122 this.collapse_button.show();
122 this.collapse_button.show();
123 }
123 }
124 this.collapsed = true;
124 this.collapsed = true;
125 }
125 }
126 };
126 };
127
127
128
128
129 OutputArea.prototype.expand = function () {
129 OutputArea.prototype.expand = function () {
130 if (this.collapsed) {
130 if (this.collapsed) {
131 this.collapse_button.hide();
131 this.collapse_button.hide();
132 this.element.show();
132 this.element.show();
133 this.prompt_overlay.show();
133 this.prompt_overlay.show();
134 this.collapsed = false;
134 this.collapsed = false;
135 }
135 }
136 };
136 };
137
137
138
138
139 OutputArea.prototype.toggle_output = function () {
139 OutputArea.prototype.toggle_output = function () {
140 if (this.collapsed) {
140 if (this.collapsed) {
141 this.expand();
141 this.expand();
142 } else {
142 } else {
143 this.collapse();
143 this.collapse();
144 }
144 }
145 };
145 };
146
146
147
147
148 OutputArea.prototype.scroll_area = function () {
148 OutputArea.prototype.scroll_area = function () {
149 this.element.addClass('output_scroll');
149 this.element.addClass('output_scroll');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
150 this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
151 this.scrolled = true;
151 this.scrolled = true;
152 };
152 };
153
153
154
154
155 OutputArea.prototype.unscroll_area = function () {
155 OutputArea.prototype.unscroll_area = function () {
156 this.element.removeClass('output_scroll');
156 this.element.removeClass('output_scroll');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
157 this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
158 this.scrolled = false;
158 this.scrolled = false;
159 };
159 };
160
160
161 /**
161 /**
162 *
162 *
163 * Scroll OutputArea if height supperior than a threshold (in lines).
163 * Scroll OutputArea if height supperior than a threshold (in lines).
164 *
164 *
165 * Threshold is a maximum number of lines. If unspecified, defaults to
165 * Threshold is a maximum number of lines. If unspecified, defaults to
166 * OutputArea.minimum_scroll_threshold.
166 * OutputArea.minimum_scroll_threshold.
167 *
167 *
168 * Negative threshold will prevent the OutputArea from ever scrolling.
168 * Negative threshold will prevent the OutputArea from ever scrolling.
169 *
169 *
170 * @method scroll_if_long
170 * @method scroll_if_long
171 *
171 *
172 * @param [lines=20]{Number} Default to 20 if not set,
172 * @param [lines=20]{Number} Default to 20 if not set,
173 * behavior undefined for value of `0`.
173 * behavior undefined for value of `0`.
174 *
174 *
175 **/
175 **/
176 OutputArea.prototype.scroll_if_long = function (lines) {
176 OutputArea.prototype.scroll_if_long = function (lines) {
177 var n = lines | OutputArea.minimum_scroll_threshold;
177 var n = lines | OutputArea.minimum_scroll_threshold;
178 if(n <= 0){
178 if(n <= 0){
179 return
179 return
180 }
180 }
181
181
182 if (this._should_scroll(n)) {
182 if (this._should_scroll(n)) {
183 // only allow scrolling long-enough output
183 // only allow scrolling long-enough output
184 this.scroll_area();
184 this.scroll_area();
185 }
185 }
186 };
186 };
187
187
188
188
189 OutputArea.prototype.toggle_scroll = function () {
189 OutputArea.prototype.toggle_scroll = function () {
190 if (this.scrolled) {
190 if (this.scrolled) {
191 this.unscroll_area();
191 this.unscroll_area();
192 } else {
192 } else {
193 // only allow scrolling long-enough output
193 // only allow scrolling long-enough output
194 this.scroll_if_long();
194 this.scroll_if_long();
195 }
195 }
196 };
196 };
197
197
198
198
199 // typeset with MathJax if MathJax is available
199 // typeset with MathJax if MathJax is available
200 OutputArea.prototype.typeset = function () {
200 OutputArea.prototype.typeset = function () {
201 if (window.MathJax){
201 if (window.MathJax){
202 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
202 MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
203 }
203 }
204 };
204 };
205
205
206
206
207 OutputArea.prototype.handle_output = function (msg) {
207 OutputArea.prototype.handle_output = function (msg) {
208 var json = {};
208 var json = {};
209 var msg_type = json.output_type = msg.header.msg_type;
209 var msg_type = json.output_type = msg.header.msg_type;
210 var content = msg.content;
210 var content = msg.content;
211 if (msg_type === "stream") {
211 if (msg_type === "stream") {
212 json.text = content.data;
212 json.text = content.data;
213 json.stream = content.name;
213 json.stream = content.name;
214 } else if (msg_type === "display_data") {
214 } else if (msg_type === "display_data") {
215 json = content.data;
215 json = content.data;
216 json.output_type = msg_type;
216 json.output_type = msg_type;
217 json.metadata = content.metadata;
217 json.metadata = content.metadata;
218 } else if (msg_type === "execute_result") {
218 } else if (msg_type === "execute_result") {
219 json = content.data;
219 json = content.data;
220 json.output_type = msg_type;
220 json.output_type = msg_type;
221 json.metadata = content.metadata;
221 json.metadata = content.metadata;
222 json.prompt_number = content.execution_count;
222 json.prompt_number = content.execution_count;
223 } else if (msg_type === "error") {
223 } else if (msg_type === "error") {
224 json.ename = content.ename;
224 json.ename = content.ename;
225 json.evalue = content.evalue;
225 json.evalue = content.evalue;
226 json.traceback = content.traceback;
226 json.traceback = content.traceback;
227 } else {
227 } else {
228 console.log("unhandled output message", msg);
228 console.log("unhandled output message", msg);
229 return;
229 return;
230 }
230 }
231 this.append_output(json);
231 this.append_output(json);
232 };
232 };
233
233
234
234
235 OutputArea.prototype.rename_keys = function (data, key_map) {
235 OutputArea.prototype.rename_keys = function (data, key_map) {
236 var remapped = {};
236 var remapped = {};
237 for (var key in data) {
237 for (var key in data) {
238 var new_key = key_map[key] || key;
238 var new_key = key_map[key] || key;
239 remapped[new_key] = data[key];
239 remapped[new_key] = data[key];
240 }
240 }
241 return remapped;
241 return remapped;
242 };
242 };
243
243
244
244
245 OutputArea.output_types = [
245 OutputArea.output_types = [
246 'application/javascript',
246 'application/javascript',
247 'text/html',
247 'text/html',
248 'text/markdown',
248 'text/markdown',
249 'text/latex',
249 'text/latex',
250 'image/svg+xml',
250 'image/svg+xml',
251 'image/png',
251 'image/png',
252 'image/jpeg',
252 'image/jpeg',
253 'application/pdf',
253 'application/pdf',
254 'text/plain'
254 'text/plain'
255 ];
255 ];
256
256
257 OutputArea.prototype.validate_output = function (json) {
257 OutputArea.prototype.validate_output = function (json) {
258 // scrub invalid outputs
258 // scrub invalid outputs
259 // TODO: right now everything is a string, but JSON really shouldn't be.
259 // TODO: right now everything is a string, but JSON really shouldn't be.
260 // nbformat 4 will fix that.
260 // nbformat 4 will fix that.
261 $.map(OutputArea.output_types, function(key){
261 $.map(OutputArea.output_types, function(key){
262 if (json[key] !== undefined && typeof json[key] !== 'string') {
262 if (json[key] !== undefined && typeof json[key] !== 'string') {
263 console.log("Invalid type for " + key, json[key]);
263 console.log("Invalid type for " + key, json[key]);
264 delete json[key];
264 delete json[key];
265 }
265 }
266 });
266 });
267 return json;
267 return json;
268 };
268 };
269
269
270 OutputArea.prototype.append_output = function (json) {
270 OutputArea.prototype.append_output = function (json) {
271 this.expand();
271 this.expand();
272
272
273 // validate output data types
273 // validate output data types
274 json = this.validate_output(json);
274 json = this.validate_output(json);
275
275
276 // Clear the output if clear is queued.
276 // Clear the output if clear is queued.
277 var needs_height_reset = false;
277 var needs_height_reset = false;
278 if (this.clear_queued) {
278 if (this.clear_queued) {
279 this.clear_output(false);
279 this.clear_output(false);
280 needs_height_reset = true;
280 needs_height_reset = true;
281 }
281 }
282
282
283 var record_output = true;
284
283 if (json.output_type === 'execute_result') {
285 if (json.output_type === 'execute_result') {
284 this.append_execute_result(json);
286 this.append_execute_result(json);
285 } else if (json.output_type === 'error') {
287 } else if (json.output_type === 'error') {
286 this.append_error(json);
288 this.append_error(json);
287 } else if (json.output_type === 'stream') {
289 } else if (json.output_type === 'stream') {
288 this.append_stream(json);
290 // append_stream might have merged the output with earlier stream output
291 record_output = this.append_stream(json);
289 }
292 }
290
293
291 // We must release the animation fixed height in a callback since Gecko
294 // We must release the animation fixed height in a callback since Gecko
292 // (FireFox) doesn't render the image immediately as the data is
295 // (FireFox) doesn't render the image immediately as the data is
293 // available.
296 // available.
294 var that = this;
297 var that = this;
295 var handle_appended = function ($el) {
298 var handle_appended = function ($el) {
296 // Only reset the height to automatic if the height is currently
299 // Only reset the height to automatic if the height is currently
297 // fixed (done by wait=True flag on clear_output).
300 // fixed (done by wait=True flag on clear_output).
298 if (needs_height_reset) {
301 if (needs_height_reset) {
299 that.element.height('');
302 that.element.height('');
300 }
303 }
301 that.element.trigger('resize');
304 that.element.trigger('resize');
302 };
305 };
303 if (json.output_type === 'display_data') {
306 if (json.output_type === 'display_data') {
304 this.append_display_data(json, handle_appended);
307 this.append_display_data(json, handle_appended);
305 } else {
308 } else {
306 handle_appended();
309 handle_appended();
307 }
310 }
308
311
309 this.outputs.push(json);
312 if (record_output) {
313 this.outputs.push(json);
314 }
310 };
315 };
311
316
312
317
313 OutputArea.prototype.create_output_area = function () {
318 OutputArea.prototype.create_output_area = function () {
314 var oa = $("<div/>").addClass("output_area");
319 var oa = $("<div/>").addClass("output_area");
315 if (this.prompt_area) {
320 if (this.prompt_area) {
316 oa.append($('<div/>').addClass('prompt'));
321 oa.append($('<div/>').addClass('prompt'));
317 }
322 }
318 return oa;
323 return oa;
319 };
324 };
320
325
321
326
322 function _get_metadata_key(metadata, key, mime) {
327 function _get_metadata_key(metadata, key, mime) {
323 var mime_md = metadata[mime];
328 var mime_md = metadata[mime];
324 // mime-specific higher priority
329 // mime-specific higher priority
325 if (mime_md && mime_md[key] !== undefined) {
330 if (mime_md && mime_md[key] !== undefined) {
326 return mime_md[key];
331 return mime_md[key];
327 }
332 }
328 // fallback on global
333 // fallback on global
329 return metadata[key];
334 return metadata[key];
330 }
335 }
331
336
332 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
337 OutputArea.prototype.create_output_subarea = function(md, classes, mime) {
333 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
338 var subarea = $('<div/>').addClass('output_subarea').addClass(classes);
334 if (_get_metadata_key(md, 'isolated', mime)) {
339 if (_get_metadata_key(md, 'isolated', mime)) {
335 // Create an iframe to isolate the subarea from the rest of the
340 // Create an iframe to isolate the subarea from the rest of the
336 // document
341 // document
337 var iframe = $('<iframe/>').addClass('box-flex1');
342 var iframe = $('<iframe/>').addClass('box-flex1');
338 iframe.css({'height':1, 'width':'100%', 'display':'block'});
343 iframe.css({'height':1, 'width':'100%', 'display':'block'});
339 iframe.attr('frameborder', 0);
344 iframe.attr('frameborder', 0);
340 iframe.attr('scrolling', 'auto');
345 iframe.attr('scrolling', 'auto');
341
346
342 // Once the iframe is loaded, the subarea is dynamically inserted
347 // Once the iframe is loaded, the subarea is dynamically inserted
343 iframe.on('load', function() {
348 iframe.on('load', function() {
344 // Workaround needed by Firefox, to properly render svg inside
349 // Workaround needed by Firefox, to properly render svg inside
345 // iframes, see http://stackoverflow.com/questions/10177190/
350 // iframes, see http://stackoverflow.com/questions/10177190/
346 // svg-dynamically-added-to-iframe-does-not-render-correctly
351 // svg-dynamically-added-to-iframe-does-not-render-correctly
347 this.contentDocument.open();
352 this.contentDocument.open();
348
353
349 // Insert the subarea into the iframe
354 // Insert the subarea into the iframe
350 // We must directly write the html. When using Jquery's append
355 // We must directly write the html. When using Jquery's append
351 // method, javascript is evaluated in the parent document and
356 // method, javascript is evaluated in the parent document and
352 // not in the iframe document. At this point, subarea doesn't
357 // not in the iframe document. At this point, subarea doesn't
353 // contain any user content.
358 // contain any user content.
354 this.contentDocument.write(subarea.html());
359 this.contentDocument.write(subarea.html());
355
360
356 this.contentDocument.close();
361 this.contentDocument.close();
357
362
358 var body = this.contentDocument.body;
363 var body = this.contentDocument.body;
359 // Adjust the iframe height automatically
364 // Adjust the iframe height automatically
360 iframe.height(body.scrollHeight + 'px');
365 iframe.height(body.scrollHeight + 'px');
361 });
366 });
362
367
363 // Elements should be appended to the inner subarea and not to the
368 // Elements should be appended to the inner subarea and not to the
364 // iframe
369 // iframe
365 iframe.append = function(that) {
370 iframe.append = function(that) {
366 subarea.append(that);
371 subarea.append(that);
367 };
372 };
368
373
369 return iframe;
374 return iframe;
370 } else {
375 } else {
371 return subarea;
376 return subarea;
372 }
377 }
373 }
378 }
374
379
375
380
376 OutputArea.prototype._append_javascript_error = function (err, element) {
381 OutputArea.prototype._append_javascript_error = function (err, element) {
377 // display a message when a javascript error occurs in display output
382 // display a message when a javascript error occurs in display output
378 var msg = "Javascript error adding output!"
383 var msg = "Javascript error adding output!"
379 if ( element === undefined ) return;
384 if ( element === undefined ) return;
380 element
385 element
381 .append($('<div/>').text(msg).addClass('js-error'))
386 .append($('<div/>').text(msg).addClass('js-error'))
382 .append($('<div/>').text(err.toString()).addClass('js-error'))
387 .append($('<div/>').text(err.toString()).addClass('js-error'))
383 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
388 .append($('<div/>').text('See your browser Javascript console for more details.').addClass('js-error'));
384 };
389 };
385
390
386 OutputArea.prototype._safe_append = function (toinsert) {
391 OutputArea.prototype._safe_append = function (toinsert) {
387 // safely append an item to the document
392 // safely append an item to the document
388 // this is an object created by user code,
393 // this is an object created by user code,
389 // and may have errors, which should not be raised
394 // and may have errors, which should not be raised
390 // under any circumstances.
395 // under any circumstances.
391 try {
396 try {
392 this.element.append(toinsert);
397 this.element.append(toinsert);
393 } catch(err) {
398 } catch(err) {
394 console.log(err);
399 console.log(err);
395 // Create an actual output_area and output_subarea, which creates
400 // Create an actual output_area and output_subarea, which creates
396 // the prompt area and the proper indentation.
401 // the prompt area and the proper indentation.
397 var toinsert = this.create_output_area();
402 var toinsert = this.create_output_area();
398 var subarea = $('<div/>').addClass('output_subarea');
403 var subarea = $('<div/>').addClass('output_subarea');
399 toinsert.append(subarea);
404 toinsert.append(subarea);
400 this._append_javascript_error(err, subarea);
405 this._append_javascript_error(err, subarea);
401 this.element.append(toinsert);
406 this.element.append(toinsert);
402 }
407 }
403 };
408 };
404
409
405
410
406 OutputArea.prototype.append_execute_result = function (json) {
411 OutputArea.prototype.append_execute_result = function (json) {
407 var n = json.prompt_number || ' ';
412 var n = json.prompt_number || ' ';
408 var toinsert = this.create_output_area();
413 var toinsert = this.create_output_area();
409 if (this.prompt_area) {
414 if (this.prompt_area) {
410 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
415 toinsert.find('div.prompt').addClass('output_prompt').text('Out[' + n + ']:');
411 }
416 }
412 var inserted = this.append_mime_type(json, toinsert);
417 var inserted = this.append_mime_type(json, toinsert);
413 if (inserted) {
418 if (inserted) {
414 inserted.addClass('output_result');
419 inserted.addClass('output_result');
415 }
420 }
416 this._safe_append(toinsert);
421 this._safe_append(toinsert);
417 // If we just output latex, typeset it.
422 // If we just output latex, typeset it.
418 if ((json['text/latex'] !== undefined) ||
423 if ((json['text/latex'] !== undefined) ||
419 (json['text/html'] !== undefined) ||
424 (json['text/html'] !== undefined) ||
420 (json['text/markdown'] !== undefined)) {
425 (json['text/markdown'] !== undefined)) {
421 this.typeset();
426 this.typeset();
422 }
427 }
423 };
428 };
424
429
425
430
426 OutputArea.prototype.append_error = function (json) {
431 OutputArea.prototype.append_error = function (json) {
427 var tb = json.traceback;
432 var tb = json.traceback;
428 if (tb !== undefined && tb.length > 0) {
433 if (tb !== undefined && tb.length > 0) {
429 var s = '';
434 var s = '';
430 var len = tb.length;
435 var len = tb.length;
431 for (var i=0; i<len; i++) {
436 for (var i=0; i<len; i++) {
432 s = s + tb[i] + '\n';
437 s = s + tb[i] + '\n';
433 }
438 }
434 s = s + '\n';
439 s = s + '\n';
435 var toinsert = this.create_output_area();
440 var toinsert = this.create_output_area();
436 var append_text = OutputArea.append_map['text/plain'];
441 var append_text = OutputArea.append_map['text/plain'];
437 if (append_text) {
442 if (append_text) {
438 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
443 append_text.apply(this, [s, {}, toinsert]).addClass('output_error');
439 }
444 }
440 this._safe_append(toinsert);
445 this._safe_append(toinsert);
441 }
446 }
442 };
447 };
443
448
444
449
445 OutputArea.prototype.append_stream = function (json) {
450 OutputArea.prototype.append_stream = function (json) {
446 // temporary fix: if stream undefined (json file written prior to this patch),
451 // temporary fix: if stream undefined (json file written prior to this patch),
447 // default to most likely stdout:
452 // default to most likely stdout:
448 if (json.stream === undefined){
453 if (json.stream === undefined){
449 json.stream = 'stdout';
454 json.stream = 'stdout';
450 }
455 }
451 var text = json.text;
456 var text = json.text;
452 var subclass = "output_"+json.stream;
457 var subclass = "output_"+json.stream;
453 if (this.outputs.length > 0){
458 if (this.outputs.length > 0){
454 // have at least one output to consider
459 // have at least one output to consider
455 var last = this.outputs[this.outputs.length-1];
460 var last = this.outputs[this.outputs.length-1];
456 if (last.output_type == 'stream' && json.stream == last.stream){
461 if (last.output_type == 'stream' && json.stream == last.stream){
457 // latest output was in the same stream,
462 // latest output was in the same stream,
458 // so append directly into its pre tag
463 // so append directly into its pre tag
459 // escape ANSI & HTML specials:
464 // escape ANSI & HTML specials:
465 last.text = utils.fixCarriageReturn(last.text + json.text);
460 var pre = this.element.find('div.'+subclass).last().find('pre');
466 var pre = this.element.find('div.'+subclass).last().find('pre');
461 var html = utils.fixCarriageReturn(
467 var html = utils.fixConsole(last.text);
462 pre.html() + utils.fixConsole(text));
463 // The only user content injected with this HTML call is
468 // The only user content injected with this HTML call is
464 // escaped by the fixConsole() method.
469 // escaped by the fixConsole() method.
465 pre.html(html);
470 pre.html(html);
466 return;
471 // return false signals that we merged this output with the previous one,
472 // and the new output shouldn't be recorded.
473 return false;
467 }
474 }
468 }
475 }
469
476
470 if (!text.replace("\r", "")) {
477 if (!text.replace("\r", "")) {
471 // text is nothing (empty string, \r, etc.)
478 // text is nothing (empty string, \r, etc.)
472 // so don't append any elements, which might add undesirable space
479 // so don't append any elements, which might add undesirable space
473 return;
480 // return true to indicate the output should be recorded.
481 return true;
474 }
482 }
475
483
476 // If we got here, attach a new div
484 // If we got here, attach a new div
477 var toinsert = this.create_output_area();
485 var toinsert = this.create_output_area();
478 var append_text = OutputArea.append_map['text/plain'];
486 var append_text = OutputArea.append_map['text/plain'];
479 if (append_text) {
487 if (append_text) {
480 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
488 append_text.apply(this, [text, {}, toinsert]).addClass("output_stream " + subclass);
481 }
489 }
482 this._safe_append(toinsert);
490 this._safe_append(toinsert);
491 return true;
483 };
492 };
484
493
485
494
486 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
495 OutputArea.prototype.append_display_data = function (json, handle_inserted) {
487 var toinsert = this.create_output_area();
496 var toinsert = this.create_output_area();
488 if (this.append_mime_type(json, toinsert, handle_inserted)) {
497 if (this.append_mime_type(json, toinsert, handle_inserted)) {
489 this._safe_append(toinsert);
498 this._safe_append(toinsert);
490 // If we just output latex, typeset it.
499 // If we just output latex, typeset it.
491 if ((json['text/latex'] !== undefined) ||
500 if ((json['text/latex'] !== undefined) ||
492 (json['text/html'] !== undefined) ||
501 (json['text/html'] !== undefined) ||
493 (json['text/markdown'] !== undefined)) {
502 (json['text/markdown'] !== undefined)) {
494 this.typeset();
503 this.typeset();
495 }
504 }
496 }
505 }
497 };
506 };
498
507
499
508
500 OutputArea.safe_outputs = {
509 OutputArea.safe_outputs = {
501 'text/plain' : true,
510 'text/plain' : true,
502 'text/latex' : true,
511 'text/latex' : true,
503 'image/png' : true,
512 'image/png' : true,
504 'image/jpeg' : true
513 'image/jpeg' : true
505 };
514 };
506
515
507 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
516 OutputArea.prototype.append_mime_type = function (json, element, handle_inserted) {
508 for (var i=0; i < OutputArea.display_order.length; i++) {
517 for (var i=0; i < OutputArea.display_order.length; i++) {
509 var type = OutputArea.display_order[i];
518 var type = OutputArea.display_order[i];
510 var append = OutputArea.append_map[type];
519 var append = OutputArea.append_map[type];
511 if ((json[type] !== undefined) && append) {
520 if ((json[type] !== undefined) && append) {
512 var value = json[type];
521 var value = json[type];
513 if (!this.trusted && !OutputArea.safe_outputs[type]) {
522 if (!this.trusted && !OutputArea.safe_outputs[type]) {
514 // not trusted, sanitize HTML
523 // not trusted, sanitize HTML
515 if (type==='text/html' || type==='text/svg') {
524 if (type==='text/html' || type==='text/svg') {
516 value = security.sanitize_html(value);
525 value = security.sanitize_html(value);
517 } else {
526 } else {
518 // don't display if we don't know how to sanitize it
527 // don't display if we don't know how to sanitize it
519 console.log("Ignoring untrusted " + type + " output.");
528 console.log("Ignoring untrusted " + type + " output.");
520 continue;
529 continue;
521 }
530 }
522 }
531 }
523 var md = json.metadata || {};
532 var md = json.metadata || {};
524 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
533 var toinsert = append.apply(this, [value, md, element, handle_inserted]);
525 // Since only the png and jpeg mime types call the inserted
534 // Since only the png and jpeg mime types call the inserted
526 // callback, if the mime type is something other we must call the
535 // callback, if the mime type is something other we must call the
527 // inserted callback only when the element is actually inserted
536 // inserted callback only when the element is actually inserted
528 // into the DOM. Use a timeout of 0 to do this.
537 // into the DOM. Use a timeout of 0 to do this.
529 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
538 if (['image/png', 'image/jpeg'].indexOf(type) < 0 && handle_inserted !== undefined) {
530 setTimeout(handle_inserted, 0);
539 setTimeout(handle_inserted, 0);
531 }
540 }
532 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
541 this.events.trigger('output_appended.OutputArea', [type, value, md, toinsert]);
533 return toinsert;
542 return toinsert;
534 }
543 }
535 }
544 }
536 return null;
545 return null;
537 };
546 };
538
547
539
548
540 var append_html = function (html, md, element) {
549 var append_html = function (html, md, element) {
541 var type = 'text/html';
550 var type = 'text/html';
542 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
551 var toinsert = this.create_output_subarea(md, "output_html rendered_html", type);
543 this.keyboard_manager.register_events(toinsert);
552 this.keyboard_manager.register_events(toinsert);
544 toinsert.append(html);
553 toinsert.append(html);
545 element.append(toinsert);
554 element.append(toinsert);
546 return toinsert;
555 return toinsert;
547 };
556 };
548
557
549
558
550 var append_markdown = function(markdown, md, element) {
559 var append_markdown = function(markdown, md, element) {
551 var type = 'text/markdown';
560 var type = 'text/markdown';
552 var toinsert = this.create_output_subarea(md, "output_markdown", type);
561 var toinsert = this.create_output_subarea(md, "output_markdown", type);
553 var text_and_math = mathjaxutils.remove_math(markdown);
562 var text_and_math = mathjaxutils.remove_math(markdown);
554 var text = text_and_math[0];
563 var text = text_and_math[0];
555 var math = text_and_math[1];
564 var math = text_and_math[1];
556 var html = marked.parser(marked.lexer(text));
565 var html = marked.parser(marked.lexer(text));
557 html = mathjaxutils.replace_math(html, math);
566 html = mathjaxutils.replace_math(html, math);
558 toinsert.append(html);
567 toinsert.append(html);
559 element.append(toinsert);
568 element.append(toinsert);
560 return toinsert;
569 return toinsert;
561 };
570 };
562
571
563
572
564 var append_javascript = function (js, md, element) {
573 var append_javascript = function (js, md, element) {
565 // We just eval the JS code, element appears in the local scope.
574 // We just eval the JS code, element appears in the local scope.
566 var type = 'application/javascript';
575 var type = 'application/javascript';
567 var toinsert = this.create_output_subarea(md, "output_javascript", type);
576 var toinsert = this.create_output_subarea(md, "output_javascript", type);
568 this.keyboard_manager.register_events(toinsert);
577 this.keyboard_manager.register_events(toinsert);
569 element.append(toinsert);
578 element.append(toinsert);
570
579
571 // Fix for ipython/issues/5293, make sure `element` is the area which
580 // Fix for ipython/issues/5293, make sure `element` is the area which
572 // output can be inserted into at the time of JS execution.
581 // output can be inserted into at the time of JS execution.
573 element = toinsert;
582 element = toinsert;
574 try {
583 try {
575 eval(js);
584 eval(js);
576 } catch(err) {
585 } catch(err) {
577 console.log(err);
586 console.log(err);
578 this._append_javascript_error(err, toinsert);
587 this._append_javascript_error(err, toinsert);
579 }
588 }
580 return toinsert;
589 return toinsert;
581 };
590 };
582
591
583
592
584 var append_text = function (data, md, element) {
593 var append_text = function (data, md, element) {
585 var type = 'text/plain';
594 var type = 'text/plain';
586 var toinsert = this.create_output_subarea(md, "output_text", type);
595 var toinsert = this.create_output_subarea(md, "output_text", type);
587 // escape ANSI & HTML specials in plaintext:
596 // escape ANSI & HTML specials in plaintext:
588 data = utils.fixConsole(data);
597 data = utils.fixConsole(data);
589 data = utils.fixCarriageReturn(data);
598 data = utils.fixCarriageReturn(data);
590 data = utils.autoLinkUrls(data);
599 data = utils.autoLinkUrls(data);
591 // The only user content injected with this HTML call is
600 // The only user content injected with this HTML call is
592 // escaped by the fixConsole() method.
601 // escaped by the fixConsole() method.
593 toinsert.append($("<pre/>").html(data));
602 toinsert.append($("<pre/>").html(data));
594 element.append(toinsert);
603 element.append(toinsert);
595 return toinsert;
604 return toinsert;
596 };
605 };
597
606
598
607
599 var append_svg = function (svg_html, md, element) {
608 var append_svg = function (svg_html, md, element) {
600 var type = 'image/svg+xml';
609 var type = 'image/svg+xml';
601 var toinsert = this.create_output_subarea(md, "output_svg", type);
610 var toinsert = this.create_output_subarea(md, "output_svg", type);
602
611
603 // Get the svg element from within the HTML.
612 // Get the svg element from within the HTML.
604 var svg = $('<div />').html(svg_html).find('svg');
613 var svg = $('<div />').html(svg_html).find('svg');
605 var svg_area = $('<div />');
614 var svg_area = $('<div />');
606 var width = svg.attr('width');
615 var width = svg.attr('width');
607 var height = svg.attr('height');
616 var height = svg.attr('height');
608 svg
617 svg
609 .width('100%')
618 .width('100%')
610 .height('100%');
619 .height('100%');
611 svg_area
620 svg_area
612 .width(width)
621 .width(width)
613 .height(height);
622 .height(height);
614
623
615 // The jQuery resize handlers don't seem to work on the svg element.
624 // The jQuery resize handlers don't seem to work on the svg element.
616 // When the svg renders completely, measure it's size and set the parent
625 // When the svg renders completely, measure it's size and set the parent
617 // div to that size. Then set the svg to 100% the size of the parent
626 // div to that size. Then set the svg to 100% the size of the parent
618 // div and make the parent div resizable.
627 // div and make the parent div resizable.
619 this._dblclick_to_reset_size(svg_area, true, false);
628 this._dblclick_to_reset_size(svg_area, true, false);
620
629
621 svg_area.append(svg);
630 svg_area.append(svg);
622 toinsert.append(svg_area);
631 toinsert.append(svg_area);
623 element.append(toinsert);
632 element.append(toinsert);
624
633
625 return toinsert;
634 return toinsert;
626 };
635 };
627
636
628 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
637 OutputArea.prototype._dblclick_to_reset_size = function (img, immediately, resize_parent) {
629 // Add a resize handler to an element
638 // Add a resize handler to an element
630 //
639 //
631 // img: jQuery element
640 // img: jQuery element
632 // immediately: bool=False
641 // immediately: bool=False
633 // Wait for the element to load before creating the handle.
642 // Wait for the element to load before creating the handle.
634 // resize_parent: bool=True
643 // resize_parent: bool=True
635 // Should the parent of the element be resized when the element is
644 // Should the parent of the element be resized when the element is
636 // reset (by double click).
645 // reset (by double click).
637 var callback = function (){
646 var callback = function (){
638 var h0 = img.height();
647 var h0 = img.height();
639 var w0 = img.width();
648 var w0 = img.width();
640 if (!(h0 && w0)) {
649 if (!(h0 && w0)) {
641 // zero size, don't make it resizable
650 // zero size, don't make it resizable
642 return;
651 return;
643 }
652 }
644 img.resizable({
653 img.resizable({
645 aspectRatio: true,
654 aspectRatio: true,
646 autoHide: true
655 autoHide: true
647 });
656 });
648 img.dblclick(function () {
657 img.dblclick(function () {
649 // resize wrapper & image together for some reason:
658 // resize wrapper & image together for some reason:
650 img.height(h0);
659 img.height(h0);
651 img.width(w0);
660 img.width(w0);
652 if (resize_parent === undefined || resize_parent) {
661 if (resize_parent === undefined || resize_parent) {
653 img.parent().height(h0);
662 img.parent().height(h0);
654 img.parent().width(w0);
663 img.parent().width(w0);
655 }
664 }
656 });
665 });
657 };
666 };
658
667
659 if (immediately) {
668 if (immediately) {
660 callback();
669 callback();
661 } else {
670 } else {
662 img.on("load", callback);
671 img.on("load", callback);
663 }
672 }
664 };
673 };
665
674
666 var set_width_height = function (img, md, mime) {
675 var set_width_height = function (img, md, mime) {
667 // set width and height of an img element from metadata
676 // set width and height of an img element from metadata
668 var height = _get_metadata_key(md, 'height', mime);
677 var height = _get_metadata_key(md, 'height', mime);
669 if (height !== undefined) img.attr('height', height);
678 if (height !== undefined) img.attr('height', height);
670 var width = _get_metadata_key(md, 'width', mime);
679 var width = _get_metadata_key(md, 'width', mime);
671 if (width !== undefined) img.attr('width', width);
680 if (width !== undefined) img.attr('width', width);
672 };
681 };
673
682
674 var append_png = function (png, md, element, handle_inserted) {
683 var append_png = function (png, md, element, handle_inserted) {
675 var type = 'image/png';
684 var type = 'image/png';
676 var toinsert = this.create_output_subarea(md, "output_png", type);
685 var toinsert = this.create_output_subarea(md, "output_png", type);
677 var img = $("<img/>");
686 var img = $("<img/>");
678 if (handle_inserted !== undefined) {
687 if (handle_inserted !== undefined) {
679 img.on('load', function(){
688 img.on('load', function(){
680 handle_inserted(img);
689 handle_inserted(img);
681 });
690 });
682 }
691 }
683 img[0].src = 'data:image/png;base64,'+ png;
692 img[0].src = 'data:image/png;base64,'+ png;
684 set_width_height(img, md, 'image/png');
693 set_width_height(img, md, 'image/png');
685 this._dblclick_to_reset_size(img);
694 this._dblclick_to_reset_size(img);
686 toinsert.append(img);
695 toinsert.append(img);
687 element.append(toinsert);
696 element.append(toinsert);
688 return toinsert;
697 return toinsert;
689 };
698 };
690
699
691
700
692 var append_jpeg = function (jpeg, md, element, handle_inserted) {
701 var append_jpeg = function (jpeg, md, element, handle_inserted) {
693 var type = 'image/jpeg';
702 var type = 'image/jpeg';
694 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
703 var toinsert = this.create_output_subarea(md, "output_jpeg", type);
695 var img = $("<img/>");
704 var img = $("<img/>");
696 if (handle_inserted !== undefined) {
705 if (handle_inserted !== undefined) {
697 img.on('load', function(){
706 img.on('load', function(){
698 handle_inserted(img);
707 handle_inserted(img);
699 });
708 });
700 }
709 }
701 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
710 img[0].src = 'data:image/jpeg;base64,'+ jpeg;
702 set_width_height(img, md, 'image/jpeg');
711 set_width_height(img, md, 'image/jpeg');
703 this._dblclick_to_reset_size(img);
712 this._dblclick_to_reset_size(img);
704 toinsert.append(img);
713 toinsert.append(img);
705 element.append(toinsert);
714 element.append(toinsert);
706 return toinsert;
715 return toinsert;
707 };
716 };
708
717
709
718
710 var append_pdf = function (pdf, md, element) {
719 var append_pdf = function (pdf, md, element) {
711 var type = 'application/pdf';
720 var type = 'application/pdf';
712 var toinsert = this.create_output_subarea(md, "output_pdf", type);
721 var toinsert = this.create_output_subarea(md, "output_pdf", type);
713 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
722 var a = $('<a/>').attr('href', 'data:application/pdf;base64,'+pdf);
714 a.attr('target', '_blank');
723 a.attr('target', '_blank');
715 a.text('View PDF')
724 a.text('View PDF')
716 toinsert.append(a);
725 toinsert.append(a);
717 element.append(toinsert);
726 element.append(toinsert);
718 return toinsert;
727 return toinsert;
719 }
728 }
720
729
721 var append_latex = function (latex, md, element) {
730 var append_latex = function (latex, md, element) {
722 // This method cannot do the typesetting because the latex first has to
731 // This method cannot do the typesetting because the latex first has to
723 // be on the page.
732 // be on the page.
724 var type = 'text/latex';
733 var type = 'text/latex';
725 var toinsert = this.create_output_subarea(md, "output_latex", type);
734 var toinsert = this.create_output_subarea(md, "output_latex", type);
726 toinsert.append(latex);
735 toinsert.append(latex);
727 element.append(toinsert);
736 element.append(toinsert);
728 return toinsert;
737 return toinsert;
729 };
738 };
730
739
731
740
732 OutputArea.prototype.append_raw_input = function (msg) {
741 OutputArea.prototype.append_raw_input = function (msg) {
733 var that = this;
742 var that = this;
734 this.expand();
743 this.expand();
735 var content = msg.content;
744 var content = msg.content;
736 var area = this.create_output_area();
745 var area = this.create_output_area();
737
746
738 // disable any other raw_inputs, if they are left around
747 // disable any other raw_inputs, if they are left around
739 $("div.output_subarea.raw_input_container").remove();
748 $("div.output_subarea.raw_input_container").remove();
740
749
741 var input_type = content.password ? 'password' : 'text';
750 var input_type = content.password ? 'password' : 'text';
742
751
743 area.append(
752 area.append(
744 $("<div/>")
753 $("<div/>")
745 .addClass("box-flex1 output_subarea raw_input_container")
754 .addClass("box-flex1 output_subarea raw_input_container")
746 .append(
755 .append(
747 $("<span/>")
756 $("<span/>")
748 .addClass("raw_input_prompt")
757 .addClass("raw_input_prompt")
749 .text(content.prompt)
758 .text(content.prompt)
750 )
759 )
751 .append(
760 .append(
752 $("<input/>")
761 $("<input/>")
753 .addClass("raw_input")
762 .addClass("raw_input")
754 .attr('type', input_type)
763 .attr('type', input_type)
755 .attr("size", 47)
764 .attr("size", 47)
756 .keydown(function (event, ui) {
765 .keydown(function (event, ui) {
757 // make sure we submit on enter,
766 // make sure we submit on enter,
758 // and don't re-execute the *cell* on shift-enter
767 // and don't re-execute the *cell* on shift-enter
759 if (event.which === keyboard.keycodes.enter) {
768 if (event.which === keyboard.keycodes.enter) {
760 that._submit_raw_input();
769 that._submit_raw_input();
761 return false;
770 return false;
762 }
771 }
763 })
772 })
764 )
773 )
765 );
774 );
766
775
767 this.element.append(area);
776 this.element.append(area);
768 var raw_input = area.find('input.raw_input');
777 var raw_input = area.find('input.raw_input');
769 // Register events that enable/disable the keyboard manager while raw
778 // Register events that enable/disable the keyboard manager while raw
770 // input is focused.
779 // input is focused.
771 this.keyboard_manager.register_events(raw_input);
780 this.keyboard_manager.register_events(raw_input);
772 // Note, the following line used to read raw_input.focus().focus().
781 // Note, the following line used to read raw_input.focus().focus().
773 // This seemed to be needed otherwise only the cell would be focused.
782 // This seemed to be needed otherwise only the cell would be focused.
774 // But with the modal UI, this seems to work fine with one call to focus().
783 // But with the modal UI, this seems to work fine with one call to focus().
775 raw_input.focus();
784 raw_input.focus();
776 }
785 }
777
786
778 OutputArea.prototype._submit_raw_input = function (evt) {
787 OutputArea.prototype._submit_raw_input = function (evt) {
779 var container = this.element.find("div.raw_input_container");
788 var container = this.element.find("div.raw_input_container");
780 var theprompt = container.find("span.raw_input_prompt");
789 var theprompt = container.find("span.raw_input_prompt");
781 var theinput = container.find("input.raw_input");
790 var theinput = container.find("input.raw_input");
782 var value = theinput.val();
791 var value = theinput.val();
783 var echo = value;
792 var echo = value;
784 // don't echo if it's a password
793 // don't echo if it's a password
785 if (theinput.attr('type') == 'password') {
794 if (theinput.attr('type') == 'password') {
786 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
795 echo = 'Β·Β·Β·Β·Β·Β·Β·Β·';
787 }
796 }
788 var content = {
797 var content = {
789 output_type : 'stream',
798 output_type : 'stream',
790 stream : 'stdout',
799 stream : 'stdout',
791 text : theprompt.text() + echo + '\n'
800 text : theprompt.text() + echo + '\n'
792 }
801 }
793 // remove form container
802 // remove form container
794 container.parent().remove();
803 container.parent().remove();
795 // replace with plaintext version in stdout
804 // replace with plaintext version in stdout
796 this.append_output(content, false);
805 this.append_output(content, false);
797 this.events.trigger('send_input_reply.Kernel', value);
806 this.events.trigger('send_input_reply.Kernel', value);
798 }
807 }
799
808
800
809
801 OutputArea.prototype.handle_clear_output = function (msg) {
810 OutputArea.prototype.handle_clear_output = function (msg) {
802 // msg spec v4 had stdout, stderr, display keys
811 // msg spec v4 had stdout, stderr, display keys
803 // v4.1 replaced these with just wait
812 // v4.1 replaced these with just wait
804 // The default behavior is the same (stdout=stderr=display=True, wait=False),
813 // The default behavior is the same (stdout=stderr=display=True, wait=False),
805 // so v4 messages will still be properly handled,
814 // so v4 messages will still be properly handled,
806 // except for the rarely used clearing less than all output.
815 // except for the rarely used clearing less than all output.
807 this.clear_output(msg.content.wait || false);
816 this.clear_output(msg.content.wait || false);
808 };
817 };
809
818
810
819
811 OutputArea.prototype.clear_output = function(wait) {
820 OutputArea.prototype.clear_output = function(wait) {
812 if (wait) {
821 if (wait) {
813
822
814 // If a clear is queued, clear before adding another to the queue.
823 // If a clear is queued, clear before adding another to the queue.
815 if (this.clear_queued) {
824 if (this.clear_queued) {
816 this.clear_output(false);
825 this.clear_output(false);
817 };
826 };
818
827
819 this.clear_queued = true;
828 this.clear_queued = true;
820 } else {
829 } else {
821
830
822 // Fix the output div's height if the clear_output is waiting for
831 // Fix the output div's height if the clear_output is waiting for
823 // new output (it is being used in an animation).
832 // new output (it is being used in an animation).
824 if (this.clear_queued) {
833 if (this.clear_queued) {
825 var height = this.element.height();
834 var height = this.element.height();
826 this.element.height(height);
835 this.element.height(height);
827 this.clear_queued = false;
836 this.clear_queued = false;
828 }
837 }
829
838
830 // Clear all
839 // Clear all
831 // Remove load event handlers from img tags because we don't want
840 // Remove load event handlers from img tags because we don't want
832 // them to fire if the image is never added to the page.
841 // them to fire if the image is never added to the page.
833 this.element.find('img').off('load');
842 this.element.find('img').off('load');
834 this.element.html("");
843 this.element.html("");
835 this.outputs = [];
844 this.outputs = [];
836 this.trusted = true;
845 this.trusted = true;
837 this.unscroll_area();
846 this.unscroll_area();
838 return;
847 return;
839 };
848 };
840 };
849 };
841
850
842
851
843 // JSON serialization
852 // JSON serialization
844
853
845 OutputArea.prototype.fromJSON = function (outputs) {
854 OutputArea.prototype.fromJSON = function (outputs) {
846 var len = outputs.length;
855 var len = outputs.length;
847 var data;
856 var data;
848
857
849 for (var i=0; i<len; i++) {
858 for (var i=0; i<len; i++) {
850 data = outputs[i];
859 data = outputs[i];
851 var msg_type = data.output_type;
860 var msg_type = data.output_type;
852 if (msg_type == "pyout") {
861 if (msg_type == "pyout") {
853 // pyout message has been renamed to execute_result,
862 // pyout message has been renamed to execute_result,
854 // but the nbformat has not been updated,
863 // but the nbformat has not been updated,
855 // so transform back to pyout for json.
864 // so transform back to pyout for json.
856 msg_type = data.output_type = "execute_result";
865 msg_type = data.output_type = "execute_result";
857 } else if (msg_type == "pyerr") {
866 } else if (msg_type == "pyerr") {
858 // pyerr message has been renamed to error,
867 // pyerr message has been renamed to error,
859 // but the nbformat has not been updated,
868 // but the nbformat has not been updated,
860 // so transform back to pyerr for json.
869 // so transform back to pyerr for json.
861 msg_type = data.output_type = "error";
870 msg_type = data.output_type = "error";
862 }
871 }
863 if (msg_type === "display_data" || msg_type === "execute_result") {
872 if (msg_type === "display_data" || msg_type === "execute_result") {
864 // convert short keys to mime keys
873 // convert short keys to mime keys
865 // TODO: remove mapping of short keys when we update to nbformat 4
874 // TODO: remove mapping of short keys when we update to nbformat 4
866 data = this.rename_keys(data, OutputArea.mime_map_r);
875 data = this.rename_keys(data, OutputArea.mime_map_r);
867 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
876 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map_r);
868 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
877 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
869 if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
878 if (data["application/json"] !== undefined && typeof data["application/json"] === 'string') {
870 data["application/json"] = JSON.parse(data["application/json"]);
879 data["application/json"] = JSON.parse(data["application/json"]);
871 }
880 }
872 }
881 }
873
882
874 this.append_output(data);
883 this.append_output(data);
875 }
884 }
876 };
885 };
877
886
878
887
879 OutputArea.prototype.toJSON = function () {
888 OutputArea.prototype.toJSON = function () {
880 var outputs = [];
889 var outputs = [];
881 var len = this.outputs.length;
890 var len = this.outputs.length;
882 var data;
891 var data;
883 for (var i=0; i<len; i++) {
892 for (var i=0; i<len; i++) {
884 data = this.outputs[i];
893 data = this.outputs[i];
885 var msg_type = data.output_type;
894 var msg_type = data.output_type;
886 if (msg_type === "display_data" || msg_type === "execute_result") {
895 if (msg_type === "display_data" || msg_type === "execute_result") {
887 // convert mime keys to short keys
896 // convert mime keys to short keys
888 data = this.rename_keys(data, OutputArea.mime_map);
897 data = this.rename_keys(data, OutputArea.mime_map);
889 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
898 data.metadata = this.rename_keys(data.metadata, OutputArea.mime_map);
890 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
899 // msg spec JSON is an object, nbformat v3 JSON is a JSON string
891 if (data.json !== undefined && typeof data.json !== 'string') {
900 if (data.json !== undefined && typeof data.json !== 'string') {
892 data.json = JSON.stringify(data.json);
901 data.json = JSON.stringify(data.json);
893 }
902 }
894 }
903 }
895 if (msg_type == "execute_result") {
904 if (msg_type == "execute_result") {
896 // pyout message has been renamed to execute_result,
905 // pyout message has been renamed to execute_result,
897 // but the nbformat has not been updated,
906 // but the nbformat has not been updated,
898 // so transform back to pyout for json.
907 // so transform back to pyout for json.
899 data.output_type = "pyout";
908 data.output_type = "pyout";
900 } else if (msg_type == "error") {
909 } else if (msg_type == "error") {
901 // pyerr message has been renamed to error,
910 // pyerr message has been renamed to error,
902 // but the nbformat has not been updated,
911 // but the nbformat has not been updated,
903 // so transform back to pyerr for json.
912 // so transform back to pyerr for json.
904 data.output_type = "pyerr";
913 data.output_type = "pyerr";
905 }
914 }
906 outputs[i] = data;
915 outputs[i] = data;
907 }
916 }
908 return outputs;
917 return outputs;
909 };
918 };
910
919
911 /**
920 /**
912 * Class properties
921 * Class properties
913 **/
922 **/
914
923
915 /**
924 /**
916 * Threshold to trigger autoscroll when the OutputArea is resized,
925 * Threshold to trigger autoscroll when the OutputArea is resized,
917 * typically when new outputs are added.
926 * typically when new outputs are added.
918 *
927 *
919 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
928 * Behavior is undefined if autoscroll is lower than minimum_scroll_threshold,
920 * unless it is < 0, in which case autoscroll will never be triggered
929 * unless it is < 0, in which case autoscroll will never be triggered
921 *
930 *
922 * @property auto_scroll_threshold
931 * @property auto_scroll_threshold
923 * @type Number
932 * @type Number
924 * @default 100
933 * @default 100
925 *
934 *
926 **/
935 **/
927 OutputArea.auto_scroll_threshold = 100;
936 OutputArea.auto_scroll_threshold = 100;
928
937
929 /**
938 /**
930 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
939 * Lower limit (in lines) for OutputArea to be made scrollable. OutputAreas
931 * shorter than this are never scrolled.
940 * shorter than this are never scrolled.
932 *
941 *
933 * @property minimum_scroll_threshold
942 * @property minimum_scroll_threshold
934 * @type Number
943 * @type Number
935 * @default 20
944 * @default 20
936 *
945 *
937 **/
946 **/
938 OutputArea.minimum_scroll_threshold = 20;
947 OutputArea.minimum_scroll_threshold = 20;
939
948
940
949
941
950
942 OutputArea.mime_map = {
951 OutputArea.mime_map = {
943 "text/plain" : "text",
952 "text/plain" : "text",
944 "text/html" : "html",
953 "text/html" : "html",
945 "image/svg+xml" : "svg",
954 "image/svg+xml" : "svg",
946 "image/png" : "png",
955 "image/png" : "png",
947 "image/jpeg" : "jpeg",
956 "image/jpeg" : "jpeg",
948 "text/latex" : "latex",
957 "text/latex" : "latex",
949 "application/json" : "json",
958 "application/json" : "json",
950 "application/javascript" : "javascript",
959 "application/javascript" : "javascript",
951 };
960 };
952
961
953 OutputArea.mime_map_r = {
962 OutputArea.mime_map_r = {
954 "text" : "text/plain",
963 "text" : "text/plain",
955 "html" : "text/html",
964 "html" : "text/html",
956 "svg" : "image/svg+xml",
965 "svg" : "image/svg+xml",
957 "png" : "image/png",
966 "png" : "image/png",
958 "jpeg" : "image/jpeg",
967 "jpeg" : "image/jpeg",
959 "latex" : "text/latex",
968 "latex" : "text/latex",
960 "json" : "application/json",
969 "json" : "application/json",
961 "javascript" : "application/javascript",
970 "javascript" : "application/javascript",
962 };
971 };
963
972
964 OutputArea.display_order = [
973 OutputArea.display_order = [
965 'application/javascript',
974 'application/javascript',
966 'text/html',
975 'text/html',
967 'text/markdown',
976 'text/markdown',
968 'text/latex',
977 'text/latex',
969 'image/svg+xml',
978 'image/svg+xml',
970 'image/png',
979 'image/png',
971 'image/jpeg',
980 'image/jpeg',
972 'application/pdf',
981 'application/pdf',
973 'text/plain'
982 'text/plain'
974 ];
983 ];
975
984
976 OutputArea.append_map = {
985 OutputArea.append_map = {
977 "text/plain" : append_text,
986 "text/plain" : append_text,
978 "text/html" : append_html,
987 "text/html" : append_html,
979 "text/markdown": append_markdown,
988 "text/markdown": append_markdown,
980 "image/svg+xml" : append_svg,
989 "image/svg+xml" : append_svg,
981 "image/png" : append_png,
990 "image/png" : append_png,
982 "image/jpeg" : append_jpeg,
991 "image/jpeg" : append_jpeg,
983 "text/latex" : append_latex,
992 "text/latex" : append_latex,
984 "application/javascript" : append_javascript,
993 "application/javascript" : append_javascript,
985 "application/pdf" : append_pdf
994 "application/pdf" : append_pdf
986 };
995 };
987
996
988 // For backwards compatability.
997 // For backwards compatability.
989 IPython.OutputArea = OutputArea;
998 IPython.OutputArea = OutputArea;
990
999
991 return {'OutputArea': OutputArea};
1000 return {'OutputArea': OutputArea};
992 });
1001 });
@@ -1,188 +1,188 b''
1 var xor = function (a, b) {return !a ^ !b;};
1 var xor = function (a, b) {return !a ^ !b;};
2 var isArray = function (a) {
2 var isArray = function (a) {
3 try {
3 try {
4 return Object.toString.call(a) === "[object Array]" || Object.toString.call(a) === "[object RuntimeArray]";
4 return Object.toString.call(a) === "[object Array]" || Object.toString.call(a) === "[object RuntimeArray]";
5 } catch (e) {
5 } catch (e) {
6 return Array.isArray(a);
6 return Array.isArray(a);
7 }
7 }
8 };
8 };
9 var recursive_compare = function(a, b) {
9 var recursive_compare = function(a, b) {
10 // Recursively compare two objects.
10 // Recursively compare two objects.
11 var same = true;
11 var same = true;
12 same = same && !xor(a instanceof Object || typeof a == 'object', b instanceof Object || typeof b == 'object');
12 same = same && !xor(a instanceof Object || typeof a == 'object', b instanceof Object || typeof b == 'object');
13 same = same && !xor(isArray(a), isArray(b));
13 same = same && !xor(isArray(a), isArray(b));
14
14
15 if (same) {
15 if (same) {
16 if (a instanceof Object) {
16 if (a instanceof Object) {
17 var key;
17 var key;
18 for (key in a) {
18 for (key in a) {
19 if (a.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) {
19 if (a.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) {
20 same = false;
20 same = false;
21 break;
21 break;
22 }
22 }
23 }
23 }
24 for (key in b) {
24 for (key in b) {
25 if (b.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) {
25 if (b.hasOwnProperty(key) && !recursive_compare(a[key], b[key])) {
26 same = false;
26 same = false;
27 break;
27 break;
28 }
28 }
29 }
29 }
30 } else {
30 } else {
31 return a === b;
31 return a === b;
32 }
32 }
33 }
33 }
34
34
35 return same;
35 return same;
36 };
36 };
37
37
38 // Test the widget framework.
38 // Test the widget framework.
39 casper.notebook_test(function () {
39 casper.notebook_test(function () {
40 var index;
40 var index;
41
41
42 this.then(function () {
42 this.then(function () {
43
43
44 // Check if the WidgetManager class is defined.
44 // Check if the WidgetManager class is defined.
45 this.test.assert(this.evaluate(function() {
45 this.test.assert(this.evaluate(function() {
46 return IPython.WidgetManager !== undefined;
46 return IPython.WidgetManager !== undefined;
47 }), 'WidgetManager class is defined');
47 }), 'WidgetManager class is defined');
48 });
48 });
49
49
50 index = this.append_cell(
50 index = this.append_cell(
51 'from IPython.html import widgets\n' +
51 'from IPython.html import widgets\n' +
52 'from IPython.display import display, clear_output\n' +
52 'from IPython.display import display, clear_output\n' +
53 'print("Success")');
53 'print("Success")');
54 this.execute_cell_then(index);
54 this.execute_cell_then(index);
55
55
56 this.then(function () {
56 this.then(function () {
57 // Check if the widget manager has been instantiated.
57 // Check if the widget manager has been instantiated.
58 this.test.assert(this.evaluate(function() {
58 this.test.assert(this.evaluate(function() {
59 return IPython.notebook.kernel.widget_manager !== undefined;
59 return IPython.notebook.kernel.widget_manager !== undefined;
60 }), 'Notebook widget manager instantiated');
60 }), 'Notebook widget manager instantiated');
61
61
62 // Functions that can be used to test the packing and unpacking APIs
62 // Functions that can be used to test the packing and unpacking APIs
63 var that = this;
63 var that = this;
64 var test_pack = function (input) {
64 var test_pack = function (input) {
65 var output = that.evaluate(function(input) {
65 var output = that.evaluate(function(input) {
66 var model = new IPython.WidgetModel(IPython.notebook.kernel.widget_manager, undefined);
66 var model = new IPython.WidgetModel(IPython.notebook.kernel.widget_manager, undefined);
67 var results = model._pack_models(input);
67 var results = model._pack_models(input);
68 return results;
68 return results;
69 }, {input: input});
69 }, {input: input});
70 that.test.assert(recursive_compare(input, output),
70 that.test.assert(recursive_compare(input, output),
71 JSON.stringify(input) + ' passed through Model._pack_model unchanged');
71 JSON.stringify(input) + ' passed through Model._pack_model unchanged');
72 };
72 };
73 var test_unpack = function (input) {
73 var test_unpack = function (input) {
74 var output = that.evaluate(function(input) {
74 var output = that.evaluate(function(input) {
75 var model = new IPython.WidgetModel(IPython.notebook.kernel.widget_manager, undefined);
75 var model = new IPython.WidgetModel(IPython.notebook.kernel.widget_manager, undefined);
76 var results = model._unpack_models(input);
76 var results = model._unpack_models(input);
77 return results;
77 return results;
78 }, {input: input});
78 }, {input: input});
79 that.test.assert(recursive_compare(input, output),
79 that.test.assert(recursive_compare(input, output),
80 JSON.stringify(input) + ' passed through Model._unpack_model unchanged');
80 JSON.stringify(input) + ' passed through Model._unpack_model unchanged');
81 };
81 };
82 var test_packing = function(input) {
82 var test_packing = function(input) {
83 test_pack(input);
83 test_pack(input);
84 test_unpack(input);
84 test_unpack(input);
85 };
85 };
86
86
87 test_packing({0: 'hi', 1: 'bye'});
87 test_packing({0: 'hi', 1: 'bye'});
88 test_packing(['hi', 'bye']);
88 test_packing(['hi', 'bye']);
89 test_packing(['hi', 5]);
89 test_packing(['hi', 5]);
90 test_packing(['hi', '5']);
90 test_packing(['hi', '5']);
91 test_packing([1.0, 0]);
91 test_packing([1.0, 0]);
92 test_packing([1.0, false]);
92 test_packing([1.0, false]);
93 test_packing([1, false]);
93 test_packing([1, false]);
94 test_packing([1, false, {a: 'hi'}]);
94 test_packing([1, false, {a: 'hi'}]);
95 test_packing([1, false, ['hi']]);
95 test_packing([1, false, ['hi']]);
96
96
97 // Test multi-set, single touch code. First create a custom widget.
97 // Test multi-set, single touch code. First create a custom widget.
98 this.evaluate(function() {
98 this.evaluate(function() {
99 var MultiSetView = IPython.DOMWidgetView.extend({
99 var MultiSetView = IPython.DOMWidgetView.extend({
100 render: function(){
100 render: function(){
101 this.model.set('a', 1);
101 this.model.set('a', 1);
102 this.model.set('b', 2);
102 this.model.set('b', 2);
103 this.model.set('c', 3);
103 this.model.set('c', 3);
104 this.touch();
104 this.touch();
105 },
105 },
106 });
106 });
107 IPython.WidgetManager.register_widget_view('MultiSetView', MultiSetView);
107 IPython.WidgetManager.register_widget_view('MultiSetView', MultiSetView);
108 }, {});
108 }, {});
109 });
109 });
110
110
111 // Try creating the multiset widget, verify that sets the values correctly.
111 // Try creating the multiset widget, verify that sets the values correctly.
112 var multiset = {};
112 var multiset = {};
113 multiset.index = this.append_cell(
113 multiset.index = this.append_cell(
114 'from IPython.utils.traitlets import Unicode, CInt\n' +
114 'from IPython.utils.traitlets import Unicode, CInt\n' +
115 'class MultiSetWidget(widgets.Widget):\n' +
115 'class MultiSetWidget(widgets.Widget):\n' +
116 ' _view_name = Unicode("MultiSetView", sync=True)\n' +
116 ' _view_name = Unicode("MultiSetView", sync=True)\n' +
117 ' a = CInt(0, sync=True)\n' +
117 ' a = CInt(0, sync=True)\n' +
118 ' b = CInt(0, sync=True)\n' +
118 ' b = CInt(0, sync=True)\n' +
119 ' c = CInt(0, sync=True)\n' +
119 ' c = CInt(0, sync=True)\n' +
120 ' d = CInt(-1, sync=True)\n' + // See if it sends a full state.
120 ' d = CInt(-1, sync=True)\n' + // See if it sends a full state.
121 ' def _handle_receive_state(self, sync_data):\n' +
121 ' def _handle_receive_state(self, sync_data):\n' +
122 ' widgets.Widget._handle_receive_state(self, sync_data)\n'+
122 ' widgets.Widget._handle_receive_state(self, sync_data)\n'+
123 ' self.d = len(sync_data)\n' +
123 ' self.d = len(sync_data)\n' +
124 'multiset = MultiSetWidget()\n' +
124 'multiset = MultiSetWidget()\n' +
125 'display(multiset)\n' +
125 'display(multiset)\n' +
126 'print(multiset.model_id)');
126 'print(multiset.model_id)');
127 this.execute_cell_then(multiset.index, function(index) {
127 this.execute_cell_then(multiset.index, function(index) {
128 multiset.model_id = this.get_output_cell(index).text.trim();
128 multiset.model_id = this.get_output_cell(index).text.trim();
129 });
129 });
130
130
131 this.wait_for_widget(multiset);
131 this.wait_for_widget(multiset);
132
132
133 index = this.append_cell(
133 index = this.append_cell(
134 'print("%d%d%d" % (multiset.a, multiset.b, multiset.c))');
134 'print("%d%d%d" % (multiset.a, multiset.b, multiset.c))');
135 this.execute_cell_then(index, function(index) {
135 this.execute_cell_then(index, function(index) {
136 this.test.assertEquals(this.get_output_cell(index).text.trim(), '123',
136 this.test.assertEquals(this.get_output_cell(index).text.trim(), '123',
137 'Multiple model.set calls and one view.touch update state in back-end.');
137 'Multiple model.set calls and one view.touch update state in back-end.');
138 });
138 });
139
139
140 index = this.append_cell(
140 index = this.append_cell(
141 'print("%d" % (multiset.d))');
141 'print("%d" % (multiset.d))');
142 this.execute_cell_then(index, function(index) {
142 this.execute_cell_then(index, function(index) {
143 this.test.assertEquals(this.get_output_cell(index).text.trim(), '3',
143 this.test.assertEquals(this.get_output_cell(index).text.trim(), '3',
144 'Multiple model.set calls sent a partial state.');
144 'Multiple model.set calls sent a partial state.');
145 });
145 });
146
146
147 var textbox = {};
147 var textbox = {};
148 throttle_index = this.append_cell(
148 throttle_index = this.append_cell(
149 'import time\n' +
149 'import time\n' +
150 'textbox = widgets.TextWidget()\n' +
150 'textbox = widgets.TextWidget()\n' +
151 'display(textbox)\n' +
151 'display(textbox)\n' +
152 'textbox.add_class("my-throttle-textbox")\n' +
152 'textbox.add_class("my-throttle-textbox")\n' +
153 'def handle_change(name, old, new):\n' +
153 'def handle_change(name, old, new):\n' +
154 ' print(len(new))\n' +
154 ' display(len(new))\n' +
155 ' time.sleep(0.5)\n' +
155 ' time.sleep(0.5)\n' +
156 'textbox.on_trait_change(handle_change, "value")\n' +
156 'textbox.on_trait_change(handle_change, "value")\n' +
157 'print(textbox.model_id)');
157 'print(textbox.model_id)');
158 this.execute_cell_then(throttle_index, function(index){
158 this.execute_cell_then(throttle_index, function(index){
159 textbox.model_id = this.get_output_cell(index).text.trim();
159 textbox.model_id = this.get_output_cell(index).text.trim();
160
160
161 this.test.assert(this.cell_element_exists(index,
161 this.test.assert(this.cell_element_exists(index,
162 '.widget-area .widget-subarea'),
162 '.widget-area .widget-subarea'),
163 'Widget subarea exists.');
163 'Widget subarea exists.');
164
164
165 this.test.assert(this.cell_element_exists(index,
165 this.test.assert(this.cell_element_exists(index,
166 '.my-throttle-textbox'), 'Textbox exists.');
166 '.my-throttle-textbox'), 'Textbox exists.');
167
167
168 // Send 20 characters
168 // Send 20 characters
169 this.sendKeys('.my-throttle-textbox', '....................');
169 this.sendKeys('.my-throttle-textbox', '....................');
170 });
170 });
171
171
172 this.wait_for_widget(textbox);
172 this.wait_for_widget(textbox);
173
173
174 this.then(function () {
174 this.then(function () {
175 var outputs = this.evaluate(function(i) {
175 var outputs = this.evaluate(function(i) {
176 return IPython.notebook.get_cell(i).output_area.outputs;
176 return IPython.notebook.get_cell(i).output_area.outputs;
177 }, {i : throttle_index});
177 }, {i : throttle_index});
178
178
179 // Only 4 outputs should have printed, but because of timing, sometimes
179 // Only 4 outputs should have printed, but because of timing, sometimes
180 // 5 outputs will print. All we need to do is verify num outputs <= 5
180 // 5 outputs will print. All we need to do is verify num outputs <= 5
181 // because that is much less than 20.
181 // because that is much less than 20.
182 this.test.assert(outputs.length <= 5, 'Messages throttled.');
182 this.test.assert(outputs.length <= 5, 'Messages throttled.');
183
183
184 // We also need to verify that the last state sent was correct.
184 // We also need to verify that the last state sent was correct.
185 var last_state = outputs[outputs.length-1].text;
185 var last_state = outputs[outputs.length-1]['text/plain'];
186 this.test.assertEquals(last_state, "20\n", "Last state sent when throttling.");
186 this.test.assertEquals(last_state, "20", "Last state sent when throttling.");
187 });
187 });
188 });
188 });
@@ -1,43 +1,43 b''
1 // Test widget button class
1 // Test widget button class
2 casper.notebook_test(function () {
2 casper.notebook_test(function () {
3 index = this.append_cell(
3 index = this.append_cell(
4 'from IPython.html import widgets\n' +
4 'from IPython.html import widgets\n' +
5 'from IPython.display import display, clear_output\n' +
5 'from IPython.display import display, clear_output\n' +
6 'print("Success")');
6 'print("Success")');
7 this.execute_cell_then(index);
7 this.execute_cell_then(index);
8
8
9 var button_index = this.append_cell(
9 var button_index = this.append_cell(
10 'button = widgets.ButtonWidget(description="Title")\n' +
10 'button = widgets.ButtonWidget(description="Title")\n' +
11 'display(button)\n'+
11 'display(button)\n' +
12 'print("Success")\n' +
12 'print("Success")\n' +
13 'def handle_click(sender):\n' +
13 'def handle_click(sender):\n' +
14 ' print("Clicked")\n' +
14 ' display("Clicked")\n' +
15 'button.on_click(handle_click)');
15 'button.on_click(handle_click)');
16 this.execute_cell_then(button_index, function(index){
16 this.execute_cell_then(button_index, function(index){
17
17
18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
18 this.test.assertEquals(this.get_output_cell(index).text, 'Success\n',
19 'Create button cell executed with correct output.');
19 'Create button cell executed with correct output.');
20
20
21 this.test.assert(this.cell_element_exists(index,
21 this.test.assert(this.cell_element_exists(index,
22 '.widget-area .widget-subarea'),
22 '.widget-area .widget-subarea'),
23 'Widget subarea exists.');
23 'Widget subarea exists.');
24
24
25 this.test.assert(this.cell_element_exists(index,
25 this.test.assert(this.cell_element_exists(index,
26 '.widget-area .widget-subarea button'),
26 '.widget-area .widget-subarea button'),
27 'Widget button exists.');
27 'Widget button exists.');
28
28
29 this.test.assert(this.cell_element_function(index,
29 this.test.assert(this.cell_element_function(index,
30 '.widget-area .widget-subarea button', 'html')=='Title',
30 '.widget-area .widget-subarea button', 'html')=='Title',
31 'Set button description.');
31 'Set button description.');
32
32
33 this.cell_element_function(index,
33 this.cell_element_function(index,
34 '.widget-area .widget-subarea button', 'click');
34 '.widget-area .widget-subarea button', 'click');
35 });
35 });
36
36
37 this.wait_for_output(button_index, 1);
37 this.wait_for_output(button_index, 1);
38
38
39 this.then(function () {
39 this.then(function () {
40 this.test.assertEquals(this.get_output_cell(button_index, 1).text, 'Clicked\n',
40 this.test.assertEquals(this.get_output_cell(button_index, 1)['text/plain'], "'Clicked'",
41 'Button click event fires.');
41 'Button click event fires.');
42 });
42 });
43 }); No newline at end of file
43 });
General Comments 0
You need to be logged in to leave comments. Login now