base.js
322 lines
| 11.9 KiB
| application/javascript
|
JavascriptLexer
Jonathan Frederic
|
r14470 | //---------------------------------------------------------------------------- | ||
// Copyright (C) 2013 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. | ||||
//---------------------------------------------------------------------------- | ||||
//============================================================================ | ||||
// Base Widget Model and View classes | ||||
//============================================================================ | ||||
/** | ||||
* @module IPython | ||||
* @namespace IPython | ||||
**/ | ||||
Jonathan Frederic
|
r14471 | define(["notebook/js/widgetmanager", | ||
Jonathan Frederic
|
r14483 | "underscore", | ||
"backbone"], | ||||
Jonathan Frederic
|
r14470 | function(widget_manager, underscore, backbone){ | ||
//-------------------------------------------------------------------- | ||||
// WidgetModel class | ||||
//-------------------------------------------------------------------- | ||||
var WidgetModel = Backbone.Model.extend({ | ||||
constructor: function (widget_manager, widget_id, comm) { | ||||
this.widget_manager = widget_manager; | ||||
this.pending_msgs = 0; | ||||
this.msg_throttle = 3; | ||||
this.msg_buffer = null; | ||||
this.id = widget_id; | ||||
if (comm !== undefined) { | ||||
// Remember comm associated with the model. | ||||
this.comm = comm; | ||||
comm.model = this; | ||||
// Hook comm messages up to model. | ||||
Jason Grout
|
r14486 | var that = this; | ||
Jonathan Frederic
|
r14470 | comm.on_close($.proxy(this._handle_comm_closed, this)); | ||
comm.on_msg($.proxy(this._handle_comm_msg, this)); | ||||
} | ||||
return Backbone.Model.apply(this); | ||||
}, | ||||
Jason Grout
|
r14486 | send: function (content, callbacks) { | ||
if (this.comm !== undefined) { | ||||
Jonathan Frederic
|
r14470 | var data = {method: 'custom', custom_content: content}; | ||
this.comm.send(data, callbacks); | ||||
} | ||||
}, | ||||
// Handle when a widget is closed. | ||||
_handle_comm_closed: function (msg) { | ||||
Jason Grout
|
r14486 | // jng: widget manager should observe the comm_close event and delete views when triggered | ||
this.trigger('comm:close'); | ||||
Jonathan Frederic
|
r14470 | if (this._has_comm()) { | ||
delete this.comm.model; // Delete ref so GC will collect widget model. | ||||
delete this.comm; | ||||
} | ||||
delete this.widget_id; // Delete id from model so widget manager cleans up. | ||||
}, | ||||
Jason Grout
|
r14486 | // Handle incoming comm msg. | ||
Jonathan Frederic
|
r14470 | _handle_comm_msg: function (msg) { | ||
var method = msg.content.data.method; | ||||
switch (method) { | ||||
case 'update': | ||||
Jonathan Frederic
|
r14474 | this.apply_update(msg.content.data.state); | ||
Jonathan Frederic
|
r14470 | break; | ||
case 'custom': | ||||
Jason Grout
|
r14486 | this.trigger('msg:custom', msg.content.data.custom_content); | ||
Jonathan Frederic
|
r14470 | break; | ||
Jason Grout
|
r14486 | default: | ||
// pass on to widget manager | ||||
this.widget_manager.handle_msg(msg, this); | ||||
Jonathan Frederic
|
r14470 | } | ||
}, | ||||
// Handle when a widget is updated via the python side. | ||||
Jonathan Frederic
|
r14474 | apply_update: function (state) { | ||
Jonathan Frederic
|
r14470 | this.updating = true; | ||
try { | ||||
for (var key in state) { | ||||
if (state.hasOwnProperty(key)) { | ||||
Jason Grout
|
r14486 | this.set(key, state[key]); | ||
Jonathan Frederic
|
r14470 | } | ||
} | ||||
this.save(); | ||||
} finally { | ||||
this.updating = false; | ||||
} | ||||
}, | ||||
Jason Grout
|
r14486 | _handle_status: function (msg, callbacks) { | ||
Jonathan Frederic
|
r14470 | //execution_state : ('busy', 'idle', 'starting') | ||
Jason Grout
|
r14486 | if (this.comm !== undefined) { | ||
if (msg.content.execution_state ==='idle') { | ||||
Jonathan Frederic
|
r14470 | |||
// Send buffer if this message caused another message to be | ||||
// throttled. | ||||
if (this.msg_buffer !== null && | ||||
Jason Grout
|
r14486 | this.msg_throttle === this.pending_msgs) { | ||
Jonathan Frederic
|
r14470 | var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer}; | ||
this.comm.send(data, callbacks); | ||||
this.msg_buffer = null; | ||||
} else { | ||||
// Only decrease the pending message count if the buffer | ||||
// doesn't get flushed (sent). | ||||
--this.pending_msgs; | ||||
} | ||||
} | ||||
} | ||||
}, | ||||
// Custom syncronization logic. | ||||
_handle_sync: function (method, options) { | ||||
var model_json = this.toJSON(); | ||||
var attr; | ||||
// Only send updated state if the state hasn't been changed | ||||
// during an update. | ||||
Jason Grout
|
r14486 | if (this.comm !== undefined) { | ||
Jonathan Frederic
|
r14470 | if (!this.updating) { | ||
if (this.pending_msgs >= this.msg_throttle) { | ||||
// The throttle has been exceeded, buffer the current msg so | ||||
// it can be sent once the kernel has finished processing | ||||
// some of the existing messages. | ||||
if (method=='patch') { | ||||
if (this.msg_buffer === null) { | ||||
this.msg_buffer = $.extend({}, model_json); // Copy | ||||
} | ||||
for (attr in options.attrs) { | ||||
this.msg_buffer[attr] = options.attrs[attr]; | ||||
} | ||||
} else { | ||||
this.msg_buffer = $.extend({}, model_json); // Copy | ||||
} | ||||
} else { | ||||
// We haven't exceeded the throttle, send the message like | ||||
// normal. If this is a patch operation, just send the | ||||
// changes. | ||||
var send_json = model_json; | ||||
if (method =='patch') { | ||||
send_json = {}; | ||||
for (attr in options.attrs) { | ||||
send_json[attr] = options.attrs[attr]; | ||||
} | ||||
} | ||||
var data = {method: 'backbone', sync_method: method, sync_data: send_json}; | ||||
Jason Grout
|
r14486 | this.comm.send(data, this.cell_callbacks()); | ||
Jonathan Frederic
|
r14470 | this.pending_msgs++; | ||
} | ||||
} | ||||
} | ||||
// Since the comm is a one-way communication, assume the message | ||||
// arrived. | ||||
return model_json; | ||||
}, | ||||
// Build a callback dict. | ||||
Jason Grout
|
r14486 | cell_callbacks: function (cell) { | ||
Jonathan Frederic
|
r14470 | var callbacks = {}; | ||
Jason Grout
|
r14486 | if (cell === undefined) { | ||
// Used the last modified view as the sender of the message. This | ||||
// will insure that any python code triggered by the sent message | ||||
// can create and display widgets and output. | ||||
if (this.last_modified_view !== undefined && | ||||
this.last_modified_view.cell !== undefined) { | ||||
cell = this.last_modified_view.cell; | ||||
} else { | ||||
cell = null; | ||||
} | ||||
} | ||||
Jonathan Frederic
|
r14470 | if (cell !== null) { | ||
// Try to get output handlers | ||||
var handle_output = null; | ||||
var handle_clear_output = null; | ||||
if (cell.output_area !== undefined && cell.output_area !== null) { | ||||
handle_output = $.proxy(cell.output_area.handle_output, cell.output_area); | ||||
handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area); | ||||
} | ||||
Jason Grout
|
r14486 | // Create callback dict using what is known | ||
Jonathan Frederic
|
r14470 | var that = this; | ||
callbacks = { | ||||
iopub : { | ||||
output : handle_output, | ||||
clear_output : handle_clear_output, | ||||
status : function (msg) { | ||||
Jason Grout
|
r14486 | that._handle_status(msg, that.cell_callbacks(cell)); | ||
Jonathan Frederic
|
r14470 | }, | ||
// Special function only registered by widget messages. | ||||
// Allows us to get the cell for a message so we know | ||||
// where to add widgets if the code requires it. | ||||
get_cell : function () { | ||||
return cell; | ||||
}, | ||||
}, | ||||
}; | ||||
} | ||||
return callbacks; | ||||
}, | ||||
Jason Grout
|
r14486 | }); | ||
Jonathan Frederic
|
r14470 | |||
Jason Grout
|
r14486 | //-------------------------------------------------------------------- | ||
// WidgetView class | ||||
//-------------------------------------------------------------------- | ||||
var BaseWidgetView = Backbone.View.extend({ | ||||
initialize: function(options) { | ||||
this.model.on('change',this.update,this); | ||||
this.widget_manager = options.widget_manager; | ||||
this.comm_manager = options.widget_manager.comm_manager; | ||||
this.cell = options.cell | ||||
}, | ||||
Jason Grout
|
r14487 | |||
Jason Grout
|
r14486 | update: function(){ | ||
// update thyself to be consistent with this.model | ||||
}, | ||||
Jason Grout
|
r14487 | child_view: function(comm_id, view_name) { | ||
var child_model = this.comm_manager.comms[comm_id].model; | ||||
Jason Grout
|
r14486 | var child_view = this.widget_manager.create_view(child_model, view_name, this.cell); | ||
return child_view; | ||||
}, | ||||
render: function(){ | ||||
// render thyself | ||||
}, | ||||
send: function (content) { | ||||
this.model.send(content, this.model.cell_callbacks(this.cell)); | ||||
}, | ||||
Jonathan Frederic
|
r14470 | |||
Jason Grout
|
r14486 | touch: function () { | ||
this.model.last_modified_view = this; | ||||
this.model.save(this.model.changedAttributes(), {patch: true}); | ||||
Jonathan Frederic
|
r14470 | }, | ||
}); | ||||
Jason Grout
|
r14486 | var WidgetView = BaseWidgetView.extend({ | ||
initialize: function (options) { | ||||
Jonathan Frederic
|
r14470 | this.visible = true; | ||
Jason Grout
|
r14486 | BaseWidgetView.prototype.initialize.apply(this, arguments); | ||
Jonathan Frederic
|
r14470 | }, | ||
add_class: function (selector, class_list) { | ||||
var elements = this._get_selector_element(selector); | ||||
if (elements.length > 0) { | ||||
elements.addClass(class_list); | ||||
} | ||||
}, | ||||
remove_class: function (selector, class_list) { | ||||
var elements = this._get_selector_element(selector); | ||||
if (elements.length > 0) { | ||||
elements.removeClass(class_list); | ||||
} | ||||
}, | ||||
update: function () { | ||||
Jason Grout
|
r14486 | // jng: hook into change:visible trigger | ||
var visible = this.model.get('visible'); | ||||
if (visible !== undefined && this.visible !== visible) { | ||||
this.visible = visible; | ||||
this.$el.toggle(visible) | ||||
Jonathan Frederic
|
r14470 | } | ||
if (this.model.css !== undefined) { | ||||
for (var selector in this.model.css) { | ||||
if (this.model.css.hasOwnProperty(selector)) { | ||||
// Apply the css traits to all elements that match the selector. | ||||
var elements = this._get_selector_element(selector); | ||||
if (elements.length > 0) { | ||||
var css_traits = this.model.css[selector]; | ||||
for (var css_key in css_traits) { | ||||
if (css_traits.hasOwnProperty(css_key)) { | ||||
elements.css(css_key, css_traits[css_key]); | ||||
} | ||||
} | ||||
} | ||||
} | ||||
} | ||||
} | ||||
}, | ||||
_get_selector_element: function (selector) { | ||||
// Get the elements via the css selector. If the selector is | ||||
// blank, apply the style to the $el_to_style element. If | ||||
// the $el_to_style element is not defined, use apply the | ||||
// style to the view's element. | ||||
var elements = this.$el.find(selector); | ||||
if (selector === undefined || selector === null || selector === '') { | ||||
if (this.$el_to_style === undefined) { | ||||
elements = this.$el; | ||||
} else { | ||||
elements = this.$el_to_style; | ||||
} | ||||
} | ||||
return elements; | ||||
}, | ||||
}); | ||||
IPython.WidgetModel = WidgetModel; | ||||
IPython.WidgetView = WidgetView; | ||||
Jonathan Frederic
|
r14472 | |||
return widget_manager; | ||||
Jason Grout
|
r14486 | }); | ||