##// END OF EJS Templates
Merge pull request #2979 from Carreau/cm-configurable...
Merge pull request #2979 from Carreau/cm-configurable CM configurable Take 2 Change the way configurability works. Config dict should be passed down to the parent class where it will be merged with the default value and propagate to this only in the base class. This allow to both alter the configuration on a per instance basis, or globaly by tempering with the class instance. This also get rid of IPython global in some cases. -- Still not **perfect** but I think this is the limit of my js knowledge, there is a minimal amount of code of 4 line to propagate the configuration : ``` var options = {foo:bar}; // default options can be class parameter var overwrite_options ={boo:baz}; // came from args, or not options = this.mergeopt(CodeCell, options, overwrite options); IPython.Cell.apply(this,[options]); ```

File last commit:

r10045:d8ed554e
r10346:45c00333 merge
Show More
outputarea.js
557 lines | 18.5 KiB | application/javascript | JavascriptLexer
//----------------------------------------------------------------------------
// Copyright (C) 2008-2011 The IPython Development Team
//
// Distributed under the terms of the BSD License. The full license is in
// the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------
//============================================================================
// OutputArea
//============================================================================
var IPython = (function (IPython) {
"use strict";
var utils = IPython.utils;
var OutputArea = function (selector, prompt_area) {
this.selector = selector;
this.wrapper = $(selector);
this.outputs = [];
this.collapsed = false;
this.scrolled = false;
this.clear_out_timeout = null;
if (prompt_area === undefined) {
this.prompt_area = true;
} else {
this.prompt_area = prompt_area;
}
this.create_elements();
this.style();
this.bind_events();
};
OutputArea.prototype.create_elements = function () {
this.element = $("<div/>");
this.collapse_button = $("<div/>");
this.prompt_overlay = $("<div/>");
this.wrapper.append(this.prompt_overlay);
this.wrapper.append(this.element);
this.wrapper.append(this.collapse_button);
};
OutputArea.prototype.style = function () {
this.collapse_button.hide();
this.prompt_overlay.hide();
this.wrapper.addClass('output_wrapper');
this.element.addClass('output vbox');
this.collapse_button.button();
this.collapse_button.addClass('output_collapsed vbox');
this.collapse_button.attr('title', 'click to expand output');
this.collapse_button.html('. . .');
this.prompt_overlay.addClass('out_prompt_overlay prompt');
this.prompt_overlay.attr('title', 'click to expand output; double click to hide output');
this.collapse();
};
OutputArea.prototype._should_scroll = function (lines) {
if (!lines) {
lines = 100;
}
// line-height from http://stackoverflow.com/questions/1185151
var fontSize = this.element.css('font-size');
var lineHeight = Math.floor(parseInt(fontSize.replace('px','')) * 1.5);
return (this.element.height() > lines * lineHeight);
};
OutputArea.prototype.bind_events = function () {
var that = this;
this.prompt_overlay.dblclick(function () { that.toggle_output(); });
this.prompt_overlay.click(function () { that.toggle_scroll(); });
this.element.resize(function () {
// FIXME: Firefox on Linux misbehaves, so automatic scrolling is disabled
if ( IPython.utils.browser[0] === "Firefox" ) {
return;
}
// maybe scroll output,
// if it's grown large enough and hasn't already been scrolled.
if ( !that.scrolled && that._should_scroll()) {
that.scroll_area();
}
});
this.collapse_button.click(function () {
that.expand();
});
this.collapse_button.hover(function () {
$(this).addClass("ui-state-hover");
}, function () {
$(this).removeClass("ui-state-hover");
});
};
OutputArea.prototype.collapse = function () {
if (!this.collapsed) {
this.element.hide();
this.prompt_overlay.hide();
if (this.element.html()){
this.collapse_button.show();
}
this.collapsed = true;
}
};
OutputArea.prototype.expand = function () {
if (this.collapsed) {
this.collapse_button.hide();
this.element.show();
this.prompt_overlay.show();
this.collapsed = false;
}
};
OutputArea.prototype.toggle_output = function () {
if (this.collapsed) {
this.expand();
} else {
this.collapse();
}
};
OutputArea.prototype.scroll_area = function () {
this.element.addClass('output_scroll');
this.prompt_overlay.attr('title', 'click to unscroll output; double click to hide');
this.scrolled = true;
};
OutputArea.prototype.unscroll_area = function () {
this.element.removeClass('output_scroll');
this.prompt_overlay.attr('title', 'click to scroll output; double click to hide');
this.scrolled = false;
};
OutputArea.prototype.scroll_if_long = function (lines) {
if (this._should_scroll(lines)) {
// only allow scrolling long-enough output
this.scroll_area();
}
};
OutputArea.prototype.toggle_scroll = function () {
if (this.scrolled) {
this.unscroll_area();
} else {
// only allow scrolling long-enough output
this.scroll_if_long(20);
}
};
// typeset with MathJax if MathJax is available
OutputArea.prototype.typeset = function () {
if (window.MathJax){
MathJax.Hub.Queue(["Typeset",MathJax.Hub]);
}
};
OutputArea.prototype.handle_output = function (msg_type, content) {
var json = {};
json.output_type = msg_type;
if (msg_type === "stream") {
json.text = content.data;
json.stream = content.name;
} else if (msg_type === "display_data") {
json = this.convert_mime_types(json, content.data);
} else if (msg_type === "pyout") {
json.prompt_number = content.execution_count;
json = this.convert_mime_types(json, content.data);
} else if (msg_type === "pyerr") {
json.ename = content.ename;
json.evalue = content.evalue;
json.traceback = content.traceback;
}
// append with dynamic=true
this.append_output(json, true);
};
OutputArea.prototype.convert_mime_types = function (json, data) {
if (data['text/plain'] !== undefined) {
json.text = data['text/plain'];
}
if (data['text/html'] !== undefined) {
json.html = data['text/html'];
}
if (data['image/svg+xml'] !== undefined) {
json.svg = data['image/svg+xml'];
}
if (data['image/png'] !== undefined) {
json.png = data['image/png'];
}
if (data['image/jpeg'] !== undefined) {
json.jpeg = data['image/jpeg'];
}
if (data['text/latex'] !== undefined) {
json.latex = data['text/latex'];
}
if (data['application/json'] !== undefined) {
json.json = data['application/json'];
}
if (data['application/javascript'] !== undefined) {
json.javascript = data['application/javascript'];
}
return json;
};
OutputArea.prototype.append_output = function (json, dynamic) {
// If dynamic is true, javascript output will be eval'd.
this.expand();
this.flush_clear_timeout();
if (json.output_type === 'pyout') {
this.append_pyout(json, dynamic);
} else if (json.output_type === 'pyerr') {
this.append_pyerr(json);
} else if (json.output_type === 'display_data') {
this.append_display_data(json, dynamic);
} else if (json.output_type === 'stream') {
this.append_stream(json);
}
this.outputs.push(json);
var that = this;
setTimeout(function(){that.element.trigger('resize');}, 100);
};
OutputArea.prototype.create_output_area = function () {
var oa = $("<div/>").addClass("hbox output_area");
if (this.prompt_area) {
oa.append($('<div/>').addClass('prompt'));
}
return oa;
};
OutputArea.prototype.append_pyout = function (json, dynamic) {
var n = json.prompt_number || ' ';
var toinsert = this.create_output_area();
if (this.prompt_area) {
toinsert.find('div.prompt').addClass('output_prompt').html('Out[' + n + ']:');
}
this.append_mime_type(json, toinsert, dynamic);
this.element.append(toinsert);
// If we just output latex, typeset it.
if ((json.latex !== undefined) || (json.html !== undefined)) {
this.typeset();
}
};
OutputArea.prototype.append_pyerr = function (json) {
var tb = json.traceback;
if (tb !== undefined && tb.length > 0) {
var s = '';
var len = tb.length;
for (var i=0; i<len; i++) {
s = s + tb[i] + '\n';
}
s = s + '\n';
var toinsert = this.create_output_area();
this.append_text(s, toinsert);
this.element.append(toinsert);
}
};
OutputArea.prototype.append_stream = function (json) {
// temporary fix: if stream undefined (json file written prior to this patch),
// default to most likely stdout:
if (json.stream == undefined){
json.stream = 'stdout';
}
var text = json.text;
var subclass = "output_"+json.stream;
if (this.outputs.length > 0){
// have at least one output to consider
var last = this.outputs[this.outputs.length-1];
if (last.output_type == 'stream' && json.stream == last.stream){
// latest output was in the same stream,
// so append directly into its pre tag
// escape ANSI & HTML specials:
var pre = this.element.find('div.'+subclass).last().find('pre');
var html = utils.fixCarriageReturn(
pre.html() + utils.fixConsole(text));
pre.html(html);
return;
}
}
if (!text.replace("\r", "")) {
// text is nothing (empty string, \r, etc.)
// so don't append any elements, which might add undesirable space
return;
}
// If we got here, attach a new div
var toinsert = this.create_output_area();
this.append_text(text, toinsert, "output_stream "+subclass);
this.element.append(toinsert);
};
OutputArea.prototype.append_display_data = function (json, dynamic) {
var toinsert = this.create_output_area();
this.append_mime_type(json, toinsert, dynamic);
this.element.append(toinsert);
// If we just output latex, typeset it.
if ( (json.latex !== undefined) || (json.html !== undefined) ) {
this.typeset();
}
};
OutputArea.display_order = ['javascript','html','latex','svg','png','jpeg','text'];
OutputArea.prototype.append_mime_type = function (json, element, dynamic) {
for(var type_i in OutputArea.display_order){
var type = OutputArea.display_order[type_i];
if(json[type] != undefined ){
if(type == 'javascript'){
if (dynamic) {
this.append_javascript(json.javascript, element, dynamic);
}
} else {
this['append_'+type](json[type], element);
}
return;
}
}
};
OutputArea.prototype.append_html = function (html, element) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_html rendered_html");
toinsert.append(html);
element.append(toinsert);
};
OutputArea.prototype.append_javascript = function (js, container) {
// We just eval the JS code, element appears in the local scope.
var element = $("<div/>").addClass("box-flex1 output_subarea");
container.append(element);
// Div for js shouldn't be drawn, as it will add empty height to the area.
container.hide();
// If the Javascript appends content to `element` that should be drawn, then
// it must also call `container.show()`.
try {
eval(js);
} catch(err) {
console.log('Error in Javascript!');
console.log(err);
container.show();
element.append($('<div/>')
.html("Error in Javascript !<br/>"+
err.toString()+
'<br/>See your browser Javascript console for more details.')
.addClass('js-error')
);
}
};
OutputArea.prototype.append_text = function (data, element, extra_class) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_text");
// escape ANSI & HTML specials in plaintext:
data = utils.fixConsole(data);
data = utils.fixCarriageReturn(data);
data = utils.autoLinkUrls(data);
if (extra_class){
toinsert.addClass(extra_class);
}
toinsert.append($("<pre/>").html(data));
element.append(toinsert);
};
OutputArea.prototype.append_svg = function (svg, element) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_svg");
toinsert.append(svg);
element.append(toinsert);
};
OutputArea.prototype._dblclick_to_reset_size = function (img) {
// schedule wrapping image in resizable after a delay,
// so we don't end up calling resize on a zero-size object
var that = this;
setTimeout(function () {
var h0 = img.height();
var w0 = img.width();
if (!(h0 && w0)) {
// zero size, schedule another timeout
that._dblclick_to_reset_size(img);
return;
}
img.resizable({
aspectRatio: true,
autoHide: true
});
img.dblclick(function () {
// resize wrapper & image together for some reason:
img.parent().height(h0);
img.height(h0);
img.parent().width(w0);
img.width(w0);
});
}, 250);
};
OutputArea.prototype.append_png = function (png, element) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_png");
var img = $("<img/>").attr('src','data:image/png;base64,'+png);
this._dblclick_to_reset_size(img);
toinsert.append(img);
element.append(toinsert);
};
OutputArea.prototype.append_jpeg = function (jpeg, element) {
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_jpeg");
var img = $("<img/>").attr('src','data:image/jpeg;base64,'+jpeg);
this._dblclick_to_reset_size(img);
toinsert.append(img);
element.append(toinsert);
};
OutputArea.prototype.append_latex = function (latex, element) {
// This method cannot do the typesetting because the latex first has to
// be on the page.
var toinsert = $("<div/>").addClass("box-flex1 output_subarea output_latex");
toinsert.append(latex);
element.append(toinsert);
};
OutputArea.prototype.handle_clear_output = function (content) {
this.clear_output(content.stdout, content.stderr, content.other);
};
OutputArea.prototype.clear_output = function (stdout, stderr, other) {
var that = this;
if (this.clear_out_timeout != null){
// fire previous pending clear *immediately*
clearTimeout(this.clear_out_timeout);
this.clear_out_timeout = null;
this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
}
// store flags for flushing the timeout
this._clear_stdout = stdout;
this._clear_stderr = stderr;
this._clear_other = other;
this.clear_out_timeout = setTimeout(function() {
// really clear timeout only after a short delay
// this reduces flicker in 'clear_output; print' cases
that.clear_out_timeout = null;
that._clear_stdout = that._clear_stderr = that._clear_other = null;
that.clear_output_callback(stdout, stderr, other);
}, 500
);
};
OutputArea.prototype.clear_output_callback = function (stdout, stderr, other) {
var output_div = this.element;
if (stdout && stderr && other){
// clear all, no need for logic
output_div.html("");
this.outputs = [];
this.unscroll_area();
return;
}
// remove html output
// each output_subarea that has an identifying class is in an output_area
// which is the element to be removed.
if (stdout) {
output_div.find("div.output_stdout").parent().remove();
}
if (stderr) {
output_div.find("div.output_stderr").parent().remove();
}
if (other) {
output_div.find("div.output_subarea").not("div.output_stderr").not("div.output_stdout").parent().remove();
}
this.unscroll_area();
// remove cleared outputs from JSON list:
for (var i = this.outputs.length - 1; i >= 0; i--) {
var out = this.outputs[i];
var output_type = out.output_type;
if (output_type == "display_data" && other) {
this.outputs.splice(i,1);
} else if (output_type == "stream") {
if (stdout && out.stream == "stdout") {
this.outputs.splice(i,1);
} else if (stderr && out.stream == "stderr") {
this.outputs.splice(i,1);
}
}
}
};
OutputArea.prototype.flush_clear_timeout = function() {
var output_div = this.element;
if (this.clear_out_timeout){
clearTimeout(this.clear_out_timeout);
this.clear_out_timeout = null;
this.clear_output_callback(this._clear_stdout, this._clear_stderr, this._clear_other);
}
};
// JSON serialization
OutputArea.prototype.fromJSON = function (outputs) {
var len = outputs.length;
for (var i=0; i<len; i++) {
// append with dynamic=false.
this.append_output(outputs[i], false);
}
};
OutputArea.prototype.toJSON = function () {
var outputs = [];
var len = this.outputs.length;
for (var i=0; i<len; i++) {
outputs[i] = this.outputs[i];
}
return outputs;
};
IPython.OutputArea = OutputArea;
return IPython;
}(IPython));