##// END OF EJS Templates
add locks to update everywhere by using options to pass this...
add locks to update everywhere by using options to pass this (and check for this)

File last commit:

r14568:515cf551
r14570:4e85339b
Show More
widget.js
325 lines | 12.6 KiB | application/javascript | JavascriptLexer
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 //----------------------------------------------------------------------------
// 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
**/
define(["notebook/js/widgetmanager",
"underscore",
"backbone"],
function(widget_manager, underscore, backbone){
//--------------------------------------------------------------------
// WidgetModel class
//--------------------------------------------------------------------
var WidgetModel = Backbone.Model.extend({
constructor: function (widget_manager, model_id, comm) {
Jonathan Frederic
Add constructor comment for widget model.
r14561 // Construcctor
//
// Creates a WidgetModel instance.
//
// Parameters
// ----------
// widget_manager : WidgetManager instance
// model_id : string
// An ID unique to this model.
// comm : Comm instance (optional)
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 this.widget_manager = widget_manager;
this.pending_msgs = 0;
this.msg_throttle = 3;
this.msg_buffer = null;
Jonathan Frederic
this.updating should be a key specific lock
r14563 this.key_value_lock = null;
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 this.id = model_id;
this.views = [];
if (comm !== undefined) {
// 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);
},
send: function (content, callbacks) {
if (this.comm !== undefined) {
var data = {method: 'custom', custom_content: content};
this.comm.send(data, callbacks);
}
},
// Handle when a widget is closed.
_handle_comm_closed: function (msg) {
this.trigger('comm:close');
delete this.comm.model; // Delete ref so GC will collect widget model.
delete this.comm;
delete this.model_id; // Delete id from model so widget manager cleans up.
// TODO: Handle deletion, like this.destroy(), and delete views, etc.
},
// Handle incoming comm msg.
_handle_comm_msg: function (msg) {
var method = msg.content.data.method;
switch (method) {
case 'update':
this.apply_update(msg.content.data.state);
break;
case 'custom':
this.trigger('msg:custom', msg.content.data.custom_content);
break;
Jonathan Frederic
handle_msg a display_model method.
r14559 case 'display':
this.widget_manager.display_view(msg.parent_header.msg_id, this);
break;
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 }
},
// Handle when a widget is updated via the python side.
apply_update: function (state) {
Jonathan Frederic
this.updating should be a key specific lock
r14563 for (var key in state) {
if (state.hasOwnProperty(key)) {
var value = state[key];
this.key_value_lock = [key, value];
try {
this.set(key, state[key]);
} finally {
this.key_value_lock = null;
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 }
}
}
Jonathan Frederic
this.updating should be a key specific lock
r14563 //TODO: are there callbacks that make sense in this case? If so, attach them here as an option
this.save();
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 },
_handle_status: function (msg, callbacks) {
//execution_state : ('busy', 'idle', 'starting')
if (this.comm !== undefined && msg.content.execution_state ==='idle') {
// Send buffer if this message caused another message to be
// throttled.
if (this.msg_buffer !== null &&
this.msg_throttle === this.pending_msgs) {
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.
Jonathan Frederic
this.updating should be a key specific lock
r14563 if (this.comm !== undefined) {
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) {
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 this.msg_buffer = $.extend({}, model_json); // Copy
}
Jonathan Frederic
this.updating should be a key specific lock
r14563 for (attr in options.attrs) {
var value = options.attrs[attr];
if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
this.msg_buffer[attr] = value;
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 }
}
Jonathan Frederic
this.updating should be a key specific lock
r14563 } else {
this.msg_buffer = $.extend({}, model_json); // Copy
}
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546
Jonathan Frederic
this.updating should be a key specific lock
r14563 } 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) {
var value = options.attrs[attr];
if (this.key_value_lock === null || attr != this.key_value_lock[0] || value != this.key_value_lock[1]) {
send_json[attr] = value;
}
}
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 }
Jonathan Frederic
this.updating should be a key specific lock
r14563
var data = {method: 'backbone', sync_data: send_json};
this.comm.send(data, options.callbacks);
this.pending_msgs++;
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 }
}
// Since the comm is a one-way communication, assume the message
// arrived.
return model_json;
},
});
//--------------------------------------------------------------------
// WidgetView class
//--------------------------------------------------------------------
Jonathan Frederic
s/BaseWidgetView/WidgetView and s/WidgetView/DOMWidgetView
r14564 var WidgetView = Backbone.View.extend({
Jonathan Frederic
un-nest options.options
r14565 initialize: function(parameters) {
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 this.model.on('change',this.update,this);
Jonathan Frederic
un-nest options.options
r14565 this.options = parameters.options;
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 this.child_views = [];
this.model.views.push(this);
},
update: function(){
// update view to be consistent with this.model
// triggered on model change
},
Jason Grout
Update option-passing for creating child views.
r14562 child_view: function(model_id, options) {
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 // create and return a child view, given a model id for a model and (optionally) a view name
// if the view name is not given, it defaults to the model's default view attribute
var child_model = this.widget_manager.get_model(model_id);
Jason Grout
Update option-passing for creating child views.
r14562 var child_view = this.widget_manager.create_view(child_model, options);
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 this.child_views[model_id] = child_view;
return child_view;
},
update_child_views: function(old_list, new_list) {
// this function takes an old list and new list of model ids
// views in child_views that correspond to deleted ids are deleted
// views corresponding to added ids are added child_views
// delete old views
_.each(_.difference(old_list, new_list), function(element, index, list) {
var view = this.child_views[element];
delete this.child_views[element];
view.remove();
}, this);
// add new views
_.each(_.difference(new_list, old_list), function(element, index, list) {
// this function adds the view to the child_views dictionary
this.child_view(element);
}, this);
},
callbacks: function(){
return this.widget_manager.callbacks(this);
Jonathan Frederic
Added missing comma
r14560 },
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546
render: function(){
// render the view. By default, this is only called the first time the view is created
},
send: function (content) {
this.model.send(content, this.callbacks());
},
touch: function () {
this.model.save(this.model.changedAttributes(), {patch: true, callbacks: this.callbacks()});
},
});
Jonathan Frederic
s/BaseWidgetView/WidgetView and s/WidgetView/DOMWidgetView
r14564 var DOMWidgetView = WidgetView.extend({
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 initialize: function (options) {
// TODO: make changes more granular (e.g., trigger on visible:change)
this.model.on('change', this.update, this);
this.model.on('msg:custom', this.on_msg, this);
Jonathan Frederic
s/BaseWidgetView/WidgetView and s/WidgetView/DOMWidgetView
r14564 WidgetView.prototype.initialize.apply(this, arguments);
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 },
on_msg: function(msg) {
switch(msg.msg_type) {
case 'add_class':
this.add_class(msg.selector, msg.class_list);
break;
case 'remove_class':
this.remove_class(msg.selector, msg.class_list);
break;
}
},
add_class: function (selector, class_list) {
Jonathan Frederic
remove length test add_class and remove_class
r14567 this._get_selector_element(selector).addClass(class_list);
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 },
remove_class: function (selector, class_list) {
Jonathan Frederic
remove length test add_class and remove_class
r14567 this._get_selector_element(selector).removeClass(class_list);
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 },
update: function () {
Jonathan Frederic
make JS update comment more descriptive (english)
r14568 // Update the contents of this view
//
// Called when the model is changed. The model may have been
// changed by another view or by a state update from the back-end.
// The very first update seems to happen before the element is
// finished rendering so we use setTimeout to give the element time
// to render
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546 var e = this.$el;
var visible = this.model.get('visible');
setTimeout(function() {e.toggle(visible)},0);
var css = this.model.get('_css');
if (css === undefined) {return;}
for (var selector in css) {
if (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 = 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;
if (selector === undefined || selector === null || selector === '') {
if (this.$el_to_style === undefined) {
elements = this.$el;
} else {
elements = this.$el_to_style;
}
} else {
elements = this.$el.find(selector);
}
return elements;
},
});
IPython.WidgetModel = WidgetModel;
IPython.WidgetView = WidgetView;
Jonathan Frederic
s/BaseWidgetView/WidgetView and s/WidgetView/DOMWidgetView
r14564 IPython.DOMWidgetView = DOMWidgetView;
Jonathan Frederic
renamed: basic_widgets.js -> init.js...
r14546
return widget_manager;
});