@ -20,8 +20,8 @@
// Use require.js 'define' method so that require.js is intelligent enough to
// syncronously load everything within this file when it is being 'required'
// elsewhere.
define ( [ " static /components/underscore/underscore-min.js",
" static /components/backbone/backbone-min.js",
define ( [ " ../.. /components/underscore/underscore-min.js",
" ../.. /components/backbone/backbone-min.js",
] , function ( ) {
// Only run once on a notebook.
@ -31,15 +31,236 @@ define(["static/components/underscore/underscore-min.js",
// WidgetModel class
//--------------------------------------------------------------------
var WidgetModel = Backbone . Model . extend ( {
apply : function ( sender ) {
constructor : function ( comm _manager , comm , widget _view _types ) {
this . comm _manager = comm _manager ;
this . widget _view _types = widget _view _types ;
this . pending _msgs = 0 ;
this . msg _throttle = 3 ;
this . msg _buffer = { } ;
this . views = { } ;
// Remember comm associated with the model.
this . comm = comm ;
comm . model = this ;
// Hook comm messages up to model.
comm . on _close ( $ . proxy ( this . handle _comm _closed , this ) ) ;
comm . on _msg ( $ . proxy ( this . handle _comm _msg , this ) ) ;
return Backbone . Model . apply ( this ) ;
} ,
update _other _views : function ( caller ) {
this . last _modified _view = caller ;
this . save ( this . changedAttributes ( ) , { patch : true } ) ;
for ( var index in this . views ) {
var view = this . views [ index ] ;
if ( view !== sender ) {
for ( var cell_ index in this . views ) {
var view = this . views [ cell_ index] ;
if ( view !== call er) {
view . refresh ( ) ;
}
}
} ,
handle _status : function ( output _area , msg ) {
//execution_state : ('busy', 'idle', 'starting')
if ( msg . content . execution _state == 'idle' ) {
// Send buffer if this message caused another message to be
// throttled.
if ( this . msg _throttle == this . pending _msgs &&
this . msg _buffer . length > 0 ) {
var output _area = this . _get _msg _output _area ( msg ) ;
var callbacks = this . _make _callbacks ( output _area ) ;
var data = { sync _method : 'patch' , sync _data : this . msg _buffer } ;
comm . send ( data , callbacks ) ;
this . msg _buffer = { } ;
} 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 ( ) ;
// Only send updated state if the state hasn't been changed
// during an update.
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' ) {
for ( var 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 ( var attr in options . attrs ) {
send _json [ attr ] = options . attrs [ attr ] ;
}
}
var data = { sync _method : method , sync _data : send _json } ;
var output _area = this . _get _view _output _area ( this . last _modified _view ) ;
var callbacks = this . _make _callbacks ( ) ;
this . comm . send ( data , callbacks ) ;
this . pending _msgs ++ ;
}
}
// Since the comm is a one-way communication, assume the message
// arrived.
return model _json ;
} ,
// Handle incomming comm msg.
handle _comm _msg : function ( comm , msg ) {
var method = msg . content . data . method ;
switch ( method ) {
case 'display' :
////////////////////////// TODO: Get cell index via currently executing cell.
var cell _index = IPython . notebook . get _selected _index ( ) - 1 ;
this . display _view ( msg . content . data . view _name ,
msg . content . data . parent ,
cell _index ) ;
break ;
case 'update' :
this . handle _update ( msg . content . data . state ) ;
break ;
}
}
// Handle when a widget is updated via the python side.
handle _update : function ( state ) {
this . updating = true ;
try {
for ( var key in state ) {
if ( state . hasOwnProperty ( key ) ) {
if ( key == "_css" ) {
this . css = state [ key ] ;
} else {
this . set ( key , state [ key ] ) ;
}
}
}
this . id = this . comm . comm _id ;
this . save ( ) ;
} finally {
this . updating = false ;
}
}
// Handle when a widget is closed.
handle _comm _closed : function ( msg ) {
for ( var cell _index in this . views ) {
var view = this . views [ cell _index ] ;
view . remove ( ) ;
}
}
// Create view that represents the model.
display _view = function ( view _name , parent _comm _id , cell _index ) {
var view = new this . widget _view _types [ view _name ] ( { model : this } ) ;
view . render ( ) ;
this . views [ cell _index ] = view ;
view . cell _index = cell _index ;
// Handle when the view element is remove from the page.
var that = this ;
view . $el . on ( "remove" , function ( ) {
var index = that . views . indexOf ( view ) ;
if ( index > - 1 ) {
that . views . splice ( index , 1 ) ;
}
view . remove ( ) ; // Clean-up view
// Close the comm if there are no views left.
if ( that . views . length ( ) == 0 ) {
that . comm . close ( ) ;
}
} ) ;
var display _child = null ;
if ( parent _comm _id != undefined ) {
var parent _comm = this . comm _manager . comms [ parent _comm _id ] ;
var parent _model = parent _comm . model ;
var parent _view = parent _model . views [ cell _id ] ;
if ( parent _view . display _child != undefined ) {
display _child = parent _view . display _child ;
}
}
if ( display _child != null ) {
display _child ( view . $el ) ;
} else {
// No parent view is defined or exists. Add the view's
// element to cell's widget div.
var cell = IPython . notebook . get _cell ( cell _index ) ;
cell . element . find ( '.widget_area' ) . find ( '.widget_subarea' )
. append ( view . $el )
. parent ( ) . show ( ) ; // Show the widget_area (parent of widget_subarea)
}
// Update the view based on the model contents.
view . refresh ( ) ;
}
// Build a callback dict.
_make _callbacks : function ( output _area ) {
var callbacks = { } ;
if ( output _area != null ) {
var that = this ;
callbacks = {
iopub : {
output : $ . proxy ( output _area . handle _output , output _area ) ,
clear _output : $ . proxy ( output _area . handle _clear _output , output _area ) ,
status : function ( msg ) {
that . handle _status ( output _area , msg ) ;
} ,
} ,
} ;
}
return callbacks ;
} ,
// Get the cell output area corresponding to the view.
_get _view _output _area : function ( view ) {
return this . _get _cell _output _area ( view . cell _index ) ;
}
// Get the cell output area corresponding to the cell id.
_get _cell _output _area : function ( cell _id ) {
var cell = IPython . notebook . get _cell ( cell _id )
return cell . output _area ;
}
} ) ;
@ -53,8 +274,8 @@ define(["static/components/underscore/underscore-min.js",
this . model . on ( 'change' , this . refresh , this ) ;
} ,
refresh : function ( ) {
this . update ( ) ;
update : function ( ) {
var results = Backbone . Model . prototype . update . call ( this ) ;
if ( this . model . css != undefined ) {
for ( var selector in this . model . css ) {
@ -79,6 +300,7 @@ define(["static/components/underscore/underscore-min.js",
}
}
}
return results ;
} ,
} ) ;
@ -86,240 +308,43 @@ define(["static/components/underscore/underscore-min.js",
//--------------------------------------------------------------------
// WidgetManager class
//--------------------------------------------------------------------
// Public constructor
var WidgetManager = function ( comm _manager ) {
this . comm _manager = comm _manager ;
this . widget _model _types = { } ;
this . widget _view _types = { } ;
this . model _widget _views = { } ;
this . pending _msgs = 0 ;
this . msg _throttle = 3 ;
this . msg _buffer = { } ;
var that = this ;
Backbone . sync = function ( method , model , options , error ) {
var result = that . handle _sync ( method , model , options ) ;
var result = model . handle _sync ( method , options ) ;
if ( options . success ) {
options . success ( result ) ;
}
} ;
}
// Register a widget model type.
WidgetManager . prototype . register _widget _model = function ( widget _model _name , widget _model _type ) {
// Register the widget with the comm manager. Make sure to pass this object's context
// in so `this` works in the call back.
this . comm _manager . register _target ( widget _model _name , $ . proxy ( this . handle _com _open , this ) ) ;
// Register the types of the model and view correspong to this widget type. Later
// the widget manager will initialize these when the comm is opened.
this . widget _model _types [ widget _model _name ] = widget _model _type ;
}
// Register a widget view type.
WidgetManager . prototype . register _widget _view = function ( widget _view _name , widget _view _type ) {
this . widget _view _types [ widget _view _name ] = widget _view _type ;
}
// Handle when a comm is opened.
WidgetManager . prototype . handle _com _open = function ( comm , msg ) {
var widget _type _name = msg . content . target _name ;
// Create the corresponding widget model.
var widget _model = new this . widget _model _types [ widget _type _name ] ;
// Remember comm associated with the model.
widget _model . comm = comm ;
comm . model = widget _model ;
// Create an array to remember the views associated with the model.
widget _model . views = [ ] ;
// Add a handle to delete the control when the comm is closed.
var that = this ;
var handle _close = function ( msg ) {
that . handle _comm _closed ( comm , msg ) ;
}
comm . on _close ( handle _close ) ;
// Handle incomming messages.
var handle _msg = function ( msg ) {
that . handle _comm _msg ( comm , msg ) ;
}
comm . on _msg ( handle _msg ) ;
var widget _model = new this . widget _model _types [ widget _type _name ] ( this . comm _manager , comm , view _types ) ;
}
// Create view that represents the model.
WidgetManager . prototype . show _view = function ( widget _area , widget _model , widget _view _name ) {
var widget _view = new this . widget _view _types [ widget _view _name ] ( { model : widget _model } ) ;
widget _view . render ( ) ;
widget _model . views . push ( widget _view ) ;
// Handle when the view element is remove from the page.
widget _view . $el . on ( "remove" , function ( ) {
var index = widget _model . views . indexOf ( widget _view ) ;
if ( index > - 1 ) {
widget _model . views . splice ( index , 1 ) ;
}
widget _view . remove ( ) ; // Clean-up view
// Close the comm if there are no views left.
if ( widget _model . views . length ( ) == 0 ) {
widget _model . comm . close ( ) ;
}
} ) ;
// Add the view's element to cell's widget div.
widget _area
. append ( widget _view . $el )
. parent ( ) . show ( ) ; // Show the widget_area (parent of widget_subarea)
// Update the view based on the model contents.
widget _view . refresh ( ) ;
}
// Handle incomming comm msg.
WidgetManager . prototype . handle _comm _msg = function ( comm , msg ) {
// Different logic for different methods.
var method = msg . content . data . method ;
switch ( method ) {
case 'show' :
// TODO: Get cell from registered output handler.
var cell = IPython . notebook . get _cell ( IPython . notebook . get _selected _index ( ) - 1 ) ;
var widget _subarea = cell . element . find ( '.widget_area' ) . find ( '.widget_subarea' ) ;
if ( msg . content . data . parent != undefined ) {
var find _results = widget _subarea . find ( "." + msg . content . data . parent ) ;
if ( find _results . length > 0 ) {
widget _subarea = find _results ;
}
}
this . show _view ( widget _subarea , comm . model , msg . content . data . view _name ) ;
break ;
case 'update' :
this . handle _update ( comm , msg . content . data . state ) ;
break ;
}
}
// Handle when a widget is updated via the python side.
WidgetManager . prototype . handle _update = function ( comm , state ) {
this . updating = true ;
for ( var key in state ) {
if ( state . hasOwnProperty ( key ) ) {
if ( key == "_css" ) {
comm . model . css = state [ key ] ;
} else {
comm . model . set ( key , state [ key ] ) ;
}
}
}
comm . model . id = comm . comm _id ;
comm . model . save ( ) ;
this . updating = false ;
}
// Handle when a widget is closed.
WidgetManager . prototype . handle _comm _closed = function ( comm , msg ) {
for ( var view _index in comm . model . views ) {
var view = comm . model . views [ view _index ] ;
view . remove ( ) ;
}
}
// Handle when a msg status changes in the kernel.
WidgetManager . prototype . handle _status = function ( msg ) {
//execution_state : ('busy', 'idle', 'starting')
if ( msg . content . execution _state == 'idle' ) {
// Send buffer if this message caused another message to be
// throttled.
if ( this . msg _throttle == -- this . pending _msgs &&
this . msg _buffer . length > 0 ) {
var outputarea = this . _get _msg _outputarea ( msg ) ;
var callbacks = this . _make _callbacks ( outputarea ) ;
var data = { sync _method : 'patch' , sync _data : this . msg _buffer } ;
comm . send ( data , callbacks ) ;
this . pending _msgs ++ ;
this . msg _buffer = { } ;
}
}
}
// Get the cell output area corresponding to the comm.
WidgetManager . prototype . _get _comm _outputarea = function ( comm ) {
// TODO: get element from comm instead of guessing
var cell = IPython . notebook . get _cell ( IPython . notebook . get _selected _index ( ) )
return cell . output _area ;
}
// Get the cell output area corresponding to the msg_id.
WidgetManager . prototype . _get _msg _outputarea = function ( msg ) {
// TODO: get element from msg_id instead of guessing
// msg.parent_header.msg_id
var cell = IPython . notebook . get _cell ( IPython . notebook . get _selected _index ( ) )
return cell . output _area ;
}
// Build a callback dict.
WidgetManager . prototype . _make _callbacks = function ( outputarea ) {
var callbacks = { } ;
if ( outputarea != null ) {
callbacks = {
iopub : {
status : $ . proxy ( this . handle _status , this ) ,
output : $ . proxy ( outputarea . handle _output , outputarea ) ,
clear _output : $ . proxy ( outputarea . handle _clear _output , outputarea ) }
} ;
}
return callbacks ;
}
// Send widget state to python backend.
WidgetManager . prototype . handle _sync = function ( method , model , options ) {
var model _json = model . toJSON ( ) ;
// Only send updated state if the state hasn't been changed during an update.
if ( ! this . updating ) {
// Create a callback for the output if the widget has an output area associate with it.
var callbacks = this . _make _callbacks ( this . _get _comm _outputarea ( comm ) ) ;
var comm = model . comm ;
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' ) {
for ( var 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 ( var attr in options . attrs ) {
send _json [ attr ] = options . attrs [ attr ] ;
}
}
var data = { sync _method : method , sync _data : send _json } ;
comm . send ( data , callbacks ) ;
this . pending _msgs ++ ;
}
}
// Since the comm is a one-way communication, assume the message
// arrived.
return model _json ;
}
//--------------------------------------------------------------------
// Init code
//--------------------------------------------------------------------
IPython . WidgetManager = WidgetManager ;
IPython . WidgetModel = WidgetModel ;
IPython . WidgetView = WidgetView ;