From 66c820949571ffa94ae4b79e8553ddb54f9f0746 2014-01-16 10:57:10 From: Jason Grout Date: 2014-01-16 10:57:10 Subject: [PATCH] Intermediate changes to javascript side of backbone widgets --- diff --git a/IPython/html/static/notebook/js/widgetmanager.js b/IPython/html/static/notebook/js/widgetmanager.js index ee31373..d35bbf1 100644 --- a/IPython/html/static/notebook/js/widgetmanager.js +++ b/IPython/html/static/notebook/js/widgetmanager.js @@ -46,9 +46,9 @@ WidgetManager.prototype.attach_comm_manager = function (comm_manager) { this.comm_manager = comm_manager; - // Register already register widget model types with the comm manager. + // Register already-registered widget model types with the comm manager. for (var widget_model_name in this.widget_model_types) { - this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this)); + this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this)); } }; @@ -57,7 +57,7 @@ // Register the widget with the comm manager. Make sure to pass this object's context // in so `this` works in the call back. if (this.comm_manager !== null) { - this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_com_open, this)); + this.comm_manager.register_target(widget_model_name, $.proxy(this._handle_comm_open, this)); } this.widget_model_types[widget_model_name] = widget_model_type; }; @@ -66,12 +66,97 @@ WidgetManager.prototype.register_widget_view = function (widget_view_name, widget_view_type) { this.widget_view_types[widget_view_name] = widget_view_type; }; - + WidgetManager.prototype.handle_msg = function(msg, model) { + var method = msg.content.data.method; + switch (method) { + case 'display': + var cell = this.get_msg_cell(msg.parent_header.msg_id); + if (cell === null) { + console.log("Could not determine where the display" + + " message was from. Widget will not be displayed"); + } else { + var view = this.create_view(model, + msg.content.data.view_name, cell); + if (view !== undefined + && cell.widget_subarea !== undefined + && cell.widget_subarea !== null) { + cell.widget_area.show(); + cell.widget_subarea.append(view.$el); + } + } + break; + case 'set_snapshot': + var cell = this.get_msg_cell(msg.parent_header.msg_id); + cell.metadata.snapshot = msg.content.data.snapshot; + break; + } + } + + WidgetManager.prototype.create_view = function(model, view_name, cell) { + view_name = view_name || model.get('default_view_name'); + var ViewType = this.widget_view_types[view_name]; + if (ViewType !== undefined && ViewType !== null) { + var view = new ViewType({model: model, widget_manager: this, cell: cell}); + view.render(); + //this.views.push(view); + + /* + // jng: Handle when the view element is remove from the page. + // observe the view destruction event and do this. We may need + // to override the view's remove method to trigger this event. + 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) { + //jng: trigger comm close event + } + + + if (that.comm !== undefined) { + that.comm.close(); + delete that.comm.model; // Delete ref so GC will collect widget model. + delete that.comm; + } + delete that.widget_id; // Delete id from model so widget manager cleans up. + }); + */ + return view; + } + }, WidgetManager.prototype.get_msg_cell = function (msg_id) { + var cell = null; + // First, check to see if the msg was triggered by cell execution. if (IPython.notebook !== undefined && IPython.notebook !== null) { - return IPython.notebook.get_msg_cell(msg_id); + cell = IPython.notebook.get_msg_cell(msg_id); + } + if (cell !== null) { + return cell + } + // Second, check to see if a get_cell callback was defined + // for the message. get_cell callbacks are registered for + // widget messages, so this block is actually checking to see if the + // message was triggered by a widget. + var kernel = this.get_kernel(); + if (kernel !== undefined && kernel !== null) { + var callbacks = kernel.get_callbacks_for_msg(msg_id); + if (callbacks !== undefined && + callbacks.iopub !== undefined && + callbacks.iopub.get_cell !== undefined) { + + return callbacks.iopub.get_cell(); + } } + + // Not triggered by a cell or widget (no get_cell callback + // exists). + return null; }; @@ -109,7 +194,7 @@ }; - WidgetManager.prototype._handle_com_open = function (comm, msg) { + WidgetManager.prototype._handle_comm_open = function (comm, msg) { var widget_type_name = msg.content.target_name; var widget_model = new this.widget_model_types[widget_type_name](this, comm.comm_id, comm); this._model_instances[comm.comm_id] = widget_model; @@ -126,4 +211,4 @@ return IPython.widget_manager; }); -}()); \ No newline at end of file +}()); diff --git a/IPython/html/static/notebook/js/widgets/base.js b/IPython/html/static/notebook/js/widgets/base.js index e6d561d..8aa4ee0 100644 --- a/IPython/html/static/notebook/js/widgets/base.js +++ b/IPython/html/static/notebook/js/widgets/base.js @@ -28,17 +28,15 @@ function(widget_manager, underscore, backbone){ this.pending_msgs = 0; this.msg_throttle = 3; this.msg_buffer = null; - this.views = []; this.id = widget_id; - this._custom_msg_callbacks = []; if (comm !== undefined) { - // Remember comm associated with the model. this.comm = comm; comm.model = this; // Hook comm messages up to model. + var that = this; comm.on_close($.proxy(this._handle_comm_closed, this)); comm.on_msg($.proxy(this._handle_comm_msg, this)); } @@ -47,68 +45,18 @@ function(widget_manager, underscore, backbone){ }, - send: function (content, cell) { - if (this._has_comm()) { - // 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 (cell === undefined) { - if (this.last_modified_view !== undefined && - this.last_modified_view.cell !== undefined) { - cell = this.last_modified_view.cell; - } - } - var callbacks = this._make_callbacks(cell); + send: function (content, callbacks) { + console.log('send',content, callbacks); + if (this.comm !== undefined) { var data = {method: 'custom', custom_content: content}; - this.comm.send(data, callbacks); } }, - - on_view_created: function (callback) { - this._view_created_callback = callback; - }, - - - on_close: function (callback) { - this._close_callback = callback; - }, - - - on_msg: function (callback, remove) { - if (remove) { - var found_index = -1; - for (var index in this._custom_msg_callbacks) { - if (callback === this._custom_msg_callbacks[index]) { - found_index = index; - break; - } - } - - if (found_index >= 0) { - this._custom_msg_callbacks.splice(found_index, 1); - } - } else { - this._custom_msg_callbacks.push(callback); - } - }, - - - _handle_custom_msg: function (content) { - for (var index in this._custom_msg_callbacks) { - try { - this._custom_msg_callbacks[index](content); - } catch (e) { - console.log("Exception in widget model msg callback", e, content); - } - } - }, - - // Handle when a widget is closed. _handle_comm_closed: function (msg) { - this._execute_views_method('remove'); + // jng: widget manager should observe the comm_close event and delete views when triggered + this.trigger('comm:close'); if (this._has_comm()) { delete this.comm.model; // Delete ref so GC will collect widget model. delete this.comm; @@ -117,43 +65,19 @@ function(widget_manager, underscore, backbone){ }, - // Handle incomming comm msg. + // Handle incoming comm msg. _handle_comm_msg: function (msg) { var method = msg.content.data.method; switch (method) { - case 'display': - - // Try to get the cell. - var cell = this._get_msg_cell(msg.parent_header.msg_id); - if (cell === null) { - console.log("Could not determine where the display" + - " message was from. Widget will not be displayed"); - } else { - this.create_views(msg.content.data.view_name, - msg.content.data.parent, - cell); - } - break; case 'update': this.apply_update(msg.content.data.state); break; - case 'add_class': - case 'remove_class': - var selector = msg.content.data.selector; - if (selector === undefined) { - selector = ''; - } - - var class_list = msg.content.data.class_list; - this._execute_views_method(method, selector, class_list); - break; - case 'set_snapshot': - var cell = this._get_msg_cell(msg.parent_header.msg_id); - cell.metadata.snapshot = msg.content.data.snapshot; - break; case 'custom': - this._handle_custom_msg(msg.content.data.custom_content); + this.trigger('msg:custom', msg.content.data.custom_content); break; + default: + // pass on to widget manager + this.widget_manager.handle_msg(msg, this); } }, @@ -164,18 +88,7 @@ function(widget_manager, underscore, backbone){ try { for (var key in state) { if (state.hasOwnProperty(key)) { - if (key == "_css") { - - // Set the css value of the model as an attribute - // instead of a backbone trait because we are only - // interested in backend css -> frontend css. In - // other words, if the css dict changes in the - // frontend, we don't need to push the changes to - // the backend. - this.css = state[key]; - } else { - this.set(key, state[key]); - } + this.set(key, state[key]); } } this.save(); @@ -185,17 +98,15 @@ function(widget_manager, underscore, backbone){ }, - _handle_status: function (cell, msg) { + _handle_status: function (msg, callbacks) { //execution_state : ('busy', 'idle', 'starting') - if (this._has_comm()) { - if (msg.content.execution_state=='idle') { + if (this.comm !== undefined) { + if (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 callbacks = this._make_callbacks(cell); + 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; @@ -217,7 +128,7 @@ function(widget_manager, underscore, backbone){ // Only send updated state if the state hasn't been changed // during an update. - if (this._has_comm()) { + if (this.comm !== undefined) { if (!this.updating) { if (this.pending_msgs >= this.msg_throttle) { // The throttle has been exceeded, buffer the current msg so @@ -247,14 +158,7 @@ function(widget_manager, underscore, backbone){ } var data = {method: 'backbone', sync_method: method, sync_data: send_json}; - - var cell = null; - if (this.last_modified_view !== undefined && this.last_modified_view !== null) { - cell = this.last_modified_view.cell; - } - - var callbacks = this._make_callbacks(cell); - this.comm.send(data, callbacks); + this.comm.send(data, this.cell_callbacks()); this.pending_msgs++; } } @@ -265,133 +169,22 @@ function(widget_manager, underscore, backbone){ return model_json; }, - - _handle_view_created: function (view) { - if (this._view_created_callback) { - try { - this._view_created_callback(view); - } catch (e) { - console.log("Exception in widget model view displayed callback", e, view, this); - } - } - }, - - - _execute_views_method: function (/* method_name, [argument0], [argument1], [...] */) { - var method_name = arguments[0]; - var args = null; - if (arguments.length > 1) { - args = [].splice.call(arguments,1); - } - - for (var view_index in this.views) { - var view = this.views[view_index]; - var method = view[method_name]; - if (args === null) { - method.apply(view); - } else { - method.apply(view, args); - } - } - }, - - - // Create view that represents the model. - create_views: function (view_name, parent_id, cell) { - var new_views = []; - var view; - - // Try creating and adding the view to it's parent. - var displayed = false; - if (parent_id !== undefined) { - var parent_model = this.widget_manager.get_model(parent_id); - if (parent_model !== null) { - var parent_views = parent_model.views; - for (var parent_view_index in parent_views) { - var parent_view = parent_views[parent_view_index]; - if (parent_view.cell === cell) { - if (parent_view.display_child !== undefined) { - view = this._create_view(view_name, cell); - if (view !== null) { - new_views.push(view); - parent_view.display_child(view); - displayed = true; - this._handle_view_created(view); - } - } - } - } - } - } - - // If no parent view is defined or exists. Add the view's - // element to cell's widget div. - if (!displayed) { - view = this._create_view(view_name, cell); - if (view !== null) { - new_views.push(view); - - if (cell.widget_subarea !== undefined && cell.widget_subarea !== null) { - cell.widget_area.show(); - cell.widget_subarea.append(view.$el); - this._handle_view_created(view); - } - } - } - - // Force the new view(s) to update their selves - for (var view_index in new_views) { - view = new_views[view_index]; - view.update(); - } - }, - - - // Create a view - _create_view: function (view_name, cell) { - var ViewType = this.widget_manager.widget_view_types[view_name]; - if (ViewType !== undefined && ViewType !== null) { - var view = new ViewType({model: this}); - view.render(); - this.views.push(view); - view.cell = cell; - - // 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) { - if (that._close_callback) { - try { - that._close_callback(that); - } catch (e) { - console.log("Exception in widget model close callback", e, that); - } - } - - if (that._has_comm()) { - that.comm.close(); - delete that.comm.model; // Delete ref so GC will collect widget model. - delete that.comm; - } - delete that.widget_id; // Delete id from model so widget manager cleans up. - } - }); - return view; - } - return null; - }, - - // Build a callback dict. - _make_callbacks: function (cell) { + cell_callbacks: function (cell) { var callbacks = {}; + console.log('cell_callbacks A', cell); + 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; + } + } + console.log('cell_callbacks B', cell); if (cell !== null) { // Try to get output handlers @@ -402,7 +195,7 @@ function(widget_manager, underscore, backbone){ handle_clear_output = $.proxy(cell.output_area.handle_clear_output, cell.output_area); } - // Create callback dict usign what is known + // Create callback dict using what is known var that = this; callbacks = { iopub : { @@ -410,7 +203,7 @@ function(widget_manager, underscore, backbone){ clear_output : handle_clear_output, status : function (msg) { - that._handle_status(cell, msg); + that._handle_status(msg, that.cell_callbacks(cell)); }, // Special function only registered by widget messages. @@ -422,57 +215,86 @@ function(widget_manager, underscore, backbone){ }, }; } + console.log('constructed callbacks for',cell); return callbacks; }, + }); - // Get the output area corresponding to the msg_id. - // cell is an instance of IPython.Cell - _get_msg_cell: function (msg_id) { - - // First, check to see if the msg was triggered by cell execution. - var cell = this.widget_manager.get_msg_cell(msg_id); - if (cell !== null) { - return cell; - } + //-------------------------------------------------------------------- + // 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 + this.render(); + // jng: maybe the following shouldn't be automatic---maybe the render method should take + // care of knowing what needs to be added as a child? + var children_attr = this.model.get('_children_attr'); + for (var i in children_attr) { + var child_attr = children_attr[i]; + var child_model = this.comm_manager.comms[this.model.get(child_attr)].model; + var child_view_name = this.child_view_name(child_attr, child_model); + var child_view = this.widget_manager.create_view(child_model, child_view_name, this.cell); + this.add_child_view(child_attr, child_view); + } + var children_lists_attr = this.model.get('_children_lists_attr') + for (var i in children_lists_attr) { + var child_attr = children_lists_attr[i]; + var child_list = this.model.get(child_attr); + for (var j in child_list) { + var child_model = this.comm_manager.comms[child_list[j]].model; + var child_view_name = this.child_view_name(child_attr, child_model); + var child_view = this.widget_manager.create_view(child_model, child_view_name, this.cell); + this.add_child_view(child_attr, child_view); + } + } + }, + update: function(){ + // update thyself to be consistent with this.model + }, + + child_view: function(attr, viewname) { + var child_model = this.comm_manager.comms[this.model.get(attr)].model; + var child_view = this.widget_manager.create_view(child_model, view_name, this.cell); + return child_view; + }, + + render: function(){ + // render thyself + }, + child_view_name: function(attr, model) { + // attr is the name of the attribute we are constructing a view for + // model is the model stored in that attribute + // return a valid view_name to construct a view of that type + // or null for the default view for the model + return null; + }, + add_child_view: function(attr, view) { + //attr is the name of the attribute containing a reference to this child + //view is the child view that has been constructed + //typically this will just add the child view's view.el attribute to some dom element + }, + + send: function (content) { + this.model.send(content, this.model.cell_callbacks(this.cell)); + }, - // Second, check to see if a get_cell callback was defined - // for the message. get_cell callbacks are registered for - // widget messages, so this block is actually checking to see if the - // message was triggered by a widget. - var kernel = this.widget_manager.get_kernel(); - if (kernel !== undefined && kernel !== null) { - var callbacks = kernel.get_callbacks_for_msg(msg_id); - if (callbacks !== undefined && - callbacks.iopub !== undefined && - callbacks.iopub.get_cell !== undefined) { - - return callbacks.iopub.get_cell(); - } - } - - // Not triggered by a cell or widget (no get_cell callback - // exists). - return null; + touch: function () { + this.model.last_modified_view = this; + this.model.save(this.model.changedAttributes(), {patch: true}); }, - // Function that checks if a comm has been attached to this widget - // model. Returns True if a valid comm is attached. - _has_comm: function() { - return this.comm !== undefined && this.comm !== null; - }, }); - - //-------------------------------------------------------------------- - // WidgetView class - //-------------------------------------------------------------------- - var WidgetView = Backbone.View.extend({ - - initialize: function () { + var WidgetView = BaseWidgetView.extend({ + initialize: function (options) { this.visible = true; - this.model.on('sync',this.update,this); + BaseWidgetView.prototype.initialize.apply(this, arguments); }, add_class: function (selector, class_list) { @@ -489,27 +311,12 @@ function(widget_manager, underscore, backbone){ } }, - - send: function (content) { - this.model.send(content, this.cell); - }, - - - touch: function () { - this.model.last_modified_view = this; - this.model.save(this.model.changedAttributes(), {patch: true}); - }, - update: function () { - if (this.model.get('visible') !== undefined) { - if (this.visible != this.model.get('visible')) { - this.visible = this.model.get('visible'); - if (this.visible) { - this.$el.show(); - } else { - this.$el.hide(); - } - } + // 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) } if (this.model.css !== undefined) { @@ -552,4 +359,4 @@ function(widget_manager, underscore, backbone){ IPython.WidgetView = WidgetView; return widget_manager; -}); \ No newline at end of file +}); diff --git a/IPython/html/static/notebook/js/widgets/container.js b/IPython/html/static/notebook/js/widgets/container.js index 2f618a5..2834414 100644 --- a/IPython/html/static/notebook/js/widgets/container.js +++ b/IPython/html/static/notebook/js/widgets/container.js @@ -52,16 +52,21 @@ define(["notebook/js/widgets/base"], function(widget_manager) { render: function(){ this.$el .addClass('widget-container'); + var children = this.model.get('children') + for(var i in this.model.get('children')) { + + this.update() }, update: function(){ set_flex_properties(this, this.$el); return IPython.WidgetView.prototype.update.call(this); }, - - display_child: function(view) { - this.$el.append(view.$el); - }, + add_child_view: function(attr, view) { + if (attr==='children') { + this.$el.append(view.$el); + } + } }); widget_manager.register_widget_view('ContainerView', ContainerView); @@ -229,8 +234,10 @@ define(["notebook/js/widgets/base"], function(widget_manager) { return IPython.WidgetView.prototype.update.call(this); }, - display_child: function(view) { - this.$body.append(view.$el); + add_child_view: function(attr, view) { + if (attr==='children') { + this.$body.append(view.$el); + } }, _get_selector_element: function(selector) { diff --git a/IPython/html/static/notebook/js/widgets/float_range.js b/IPython/html/static/notebook/js/widgets/float_range.js index e9f29a3..735a8b8 100644 --- a/IPython/html/static/notebook/js/widgets/float_range.js +++ b/IPython/html/static/notebook/js/widgets/float_range.js @@ -70,6 +70,7 @@ define(["notebook/js/widgets/base"], function(widget_manager){ this.$slider.slider('option', 'orientation', orientation); value = this.model.get('value'); this.$slider.slider('option', 'value', value); + console.log('updating value',value) // Use the right CSS classes for vertical & horizontal sliders if (orientation=='vertical') { @@ -109,6 +110,7 @@ define(["notebook/js/widgets/base"], function(widget_manager){ events: { "slide" : "handleSliderChange" }, handleSliderChange: function(e, ui) { this.model.set('value', ui.value); + console.log('triggered value change', ui.value, this.model); this.touch(); }, }); diff --git a/IPython/html/static/notebook/js/widgets/multicontainer.js b/IPython/html/static/notebook/js/widgets/multicontainer.js index a93f4e5..f28bb6b 100644 --- a/IPython/html/static/notebook/js/widgets/multicontainer.js +++ b/IPython/html/static/notebook/js/widgets/multicontainer.js @@ -27,7 +27,6 @@ define(["notebook/js/widgets/base"], function(widget_manager){ .addClass('accordion'); this.containers = []; }, - update: function() { // Set tab titles var titles = this.model.get('_titles'); @@ -58,7 +57,7 @@ define(["notebook/js/widgets/base"], function(widget_manager){ return IPython.WidgetView.prototype.update.call(this); }, - display_child: function(view) { + add_child_view: function(attr, view) { var index = this.containers.length; var uuid = IPython.utils.uuid(); @@ -103,7 +102,13 @@ define(["notebook/js/widgets/base"], function(widget_manager){ var TabView = IPython.WidgetView.extend({ + initialize: function() { + this.containers = []; + IPython.WidgetView.prototype.initialize.apply(this, arguments); + }, + render: function(){ + console.log('rendering tabs', this); var uuid = 'tabs'+IPython.utils.uuid(); var that = this; this.$tabs = $('
', {id: uuid}) @@ -113,8 +118,7 @@ define(["notebook/js/widgets/base"], function(widget_manager){ this.$tab_contents = $('
', {id: uuid + 'Content'}) .addClass('tab-content') .appendTo(this.$el); - - this.containers = []; + this.update(); }, update: function() { @@ -135,8 +139,8 @@ define(["notebook/js/widgets/base"], function(widget_manager){ return IPython.WidgetView.prototype.update.call(this); }, - display_child: function(view) { - + add_child_view: function(attr, view) { + console.log('adding child view', attr, view); var index = this.containers.length; var uuid = IPython.utils.uuid(); diff --git a/IPython/html/static/services/kernels/js/comm.js b/IPython/html/static/services/kernels/js/comm.js index d9b3288..22e132a 100644 --- a/IPython/html/static/services/kernels/js/comm.js +++ b/IPython/html/static/services/kernels/js/comm.js @@ -86,7 +86,7 @@ var IPython = (function (IPython) { try { f(comm, msg); } catch (e) { - console.log("Exception opening new comm:", e, msg); + console.log("Exception opening new comm:", e, e.stack, msg); comm.close(); this.unregister_comm(comm); } @@ -102,7 +102,7 @@ var IPython = (function (IPython) { try { comm.handle_close(msg); } catch (e) { - console.log("Exception closing comm: ", e, msg); + console.log("Exception closing comm: ", e, e.stack, msg); } }; @@ -115,7 +115,7 @@ var IPython = (function (IPython) { try { comm.handle_msg(msg); } catch (e) { - console.log("Exception handling comm msg: ", e, msg); + console.log("Exception handling comm msg: ", e, e.stack, msg); } }; @@ -176,7 +176,7 @@ var IPython = (function (IPython) { try { callback(msg); } catch (e) { - console.log("Exception in Comm callback", e, msg); + console.log("Exception in Comm callback", e, e.stack, msg); } } }; diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py index 5329625..2af8ab9 100644 --- a/IPython/html/widgets/widget.py +++ b/IPython/html/widgets/widget.py @@ -34,9 +34,13 @@ from IPython.utils.py3compat import string_types class BaseWidget(LoggingConfigurable): # Shared declarations (Class level) - _keys = List(Unicode, help="List of keys comprising the state of the model.") - _children_attr = List(Unicode, help="List of keys of children objects of the model.") - _children_lists_attr = List(Unicode, help="List of keys containing lists of children objects of the model.") + _keys = List(Unicode, default_value = [], + help="List of keys comprising the state of the model.", allow_none=False) + _children_attr = List(Unicode, default_value = [], + help="List of keys of children objects of the model.", allow_none=False) + _children_lists_attr = List(Unicode, default_value = [], + help="List of keys containing lists of children objects of the model.", + allow_none=False) widget_construction_callback = None def on_widget_constructed(callback): @@ -59,9 +63,10 @@ class BaseWidget(LoggingConfigurable): to use to represent the widget.""") # Private/protected declarations + # todo: change this to a context manager _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo. _displayed = False - _comm = None + _comm = Instance('IPython.kernel.comm.Comm') def __init__(self, **kwargs): """Public constructor @@ -72,6 +77,7 @@ class BaseWidget(LoggingConfigurable): # Register after init to allow default values to be specified # TODO: register three different handlers, one for each list, and abstract out the common parts + #print self.keys, self._children_attr, self._children_lists_attr self.on_trait_change(self._handle_property_changed, self.keys+self._children_attr+self._children_lists_attr) Widget._handle_widget_constructed(self) @@ -90,7 +96,7 @@ class BaseWidget(LoggingConfigurable): # Properties @property def keys(self): - keys = ['_children_attr', '_children_lists_attr'] + keys = ['_children_attr', '_children_lists_attr', 'default_view_name'] keys.extend(self._keys) return keys @@ -118,7 +124,6 @@ class BaseWidget(LoggingConfigurable): if 'custom_content' in data: self._handle_custom_msg(data['custom_content']) - def _handle_custom_msg(self, content): """Called when a custom msg is recieved.""" for handler in self._msg_callbacks: @@ -153,7 +158,7 @@ class BaseWidget(LoggingConfigurable): def _handle_property_changed(self, name, old, new): - """Called when a proeprty has been changed.""" + """Called when a property has been changed.""" # Make sure this isn't information that the front-end just sent us. if self._property_lock[0] != name and self._property_lock[1] != new: # Send new state to frontend @@ -197,7 +202,7 @@ class BaseWidget(LoggingConfigurable): self._send({"method": "update", "state": self.get_state()}) - def get_state(self, key=None) + def get_state(self, key=None): """Gets the widget state, or a piece of it. Parameters @@ -308,15 +313,18 @@ class BaseWidget(LoggingConfigurable): def _open_communication(self): """Opens a communication with the front-end.""" # Create a comm. - if not hasattr(self, '_comm') or self._comm is None: + if self._comm is None: self._comm = Comm(target_name=self.target_name) self._comm.on_msg(self._handle_msg) self._comm.on_close(self._close_communication) + # first update + self.send_state() + def _close_communication(self): """Closes a communication with the front-end.""" - if hasattr(self, '_comm') and self._comm is not None: + if self._comm is not None: try: self._comm.close() finally: @@ -332,9 +340,6 @@ class BaseWidget(LoggingConfigurable): return False class Widget(BaseWidget): - - _children = List(Instance('IPython.html.widgets.widget.Widget')) - _children_lists_attr = List(Unicode, ['_children']) visible = Bool(True, help="Whether or not the widget is visible.") # Private/protected declarations diff --git a/IPython/html/widgets/widget_button.py b/IPython/html/widgets/widget_button.py index bb5d65d..0bb693a 100644 --- a/IPython/html/widgets/widget_button.py +++ b/IPython/html/widgets/widget_button.py @@ -59,7 +59,7 @@ class ButtonWidget(Widget): def _handle_button_msg(self, content): - """Hanlde a msg from the front-end + """Handle a msg from the front-end Parameters ---------- diff --git a/IPython/html/widgets/widget_container.py b/IPython/html/widgets/widget_container.py index 4fa6aab..754a967 100644 --- a/IPython/html/widgets/widget_container.py +++ b/IPython/html/widgets/widget_container.py @@ -14,7 +14,7 @@ Represents a container that can be used to group other widgets. # Imports #----------------------------------------------------------------------------- from .widget import Widget -from IPython.utils.traitlets import Unicode, Bool +from IPython.utils.traitlets import Unicode, Bool, List, Instance #----------------------------------------------------------------------------- # Classes @@ -23,6 +23,9 @@ class ContainerWidget(Widget): target_name = Unicode('ContainerWidgetModel') default_view_name = Unicode('ContainerView') + children = []#List(Instance('IPython.html.widgets.widget.Widget')) + _children_lists_attr = List(Unicode, ['children']) + # Keys, all private and managed by helper methods. Flexible box model # classes... _keys = ['_vbox', '_hbox', '_align_start', '_align_end', '_align_center', diff --git a/IPython/html/widgets/widget_multicontainer.py b/IPython/html/widgets/widget_multicontainer.py index 3c06fd8..9835c08 100644 --- a/IPython/html/widgets/widget_multicontainer.py +++ b/IPython/html/widgets/widget_multicontainer.py @@ -15,7 +15,7 @@ pages. # Imports #----------------------------------------------------------------------------- from .widget import Widget -from IPython.utils.traitlets import Unicode, Dict, Int +from IPython.utils.traitlets import Unicode, Dict, Int, List, Instance #----------------------------------------------------------------------------- # Classes @@ -29,9 +29,12 @@ class MulticontainerWidget(Widget): _titles = Dict(help="Titles of the pages") selected_index = Int(0) + children = []#List(Instance('IPython.html.widgets.widget.Widget')) + _children_lists_attr = List(Unicode, ['children']) + # Public methods def set_title(self, index, title): - """Sets the title of a container pages + """Sets the title of a container page Parameters ---------- diff --git a/IPython/utils/traitlets.py b/IPython/utils/traitlets.py index 31fb5cf..63cd5ce 100644 --- a/IPython/utils/traitlets.py +++ b/IPython/utils/traitlets.py @@ -149,7 +149,7 @@ def parse_notifier_name(name): return ['anytrait'] elif isinstance(name, (list, tuple)): for n in name: - assert isinstance(n, str), "names must be strings" + assert isinstance(n, basestring), "names must be strings: %s, %r"%(type(n), n) return name @@ -801,11 +801,14 @@ class Instance(ClassBasedTraitType): if self._allow_none: return value self.error(obj, value) - - if isinstance(value, self.klass): - return value - else: - self.error(obj, value) + try: + if isinstance(value, self.klass): + return value + else: + self.error(obj, value) + except TypeError as e: + print self.klass, type(self.klass) + raise TypeError("validate, %s, %s"%(self.klass, type(self.klass))) def info(self): if isinstance(self.klass, py3compat.string_types): diff --git a/examples/widgets/Part 1 - Basics.ipynb b/examples/widgets/Part 1 - Basics.ipynb index ae6ea3b..f7695ac 100644 --- a/examples/widgets/Part 1 - Basics.ipynb +++ b/examples/widgets/Part 1 - Basics.ipynb @@ -110,7 +110,17 @@ ], "language": "python", "metadata": {}, - "outputs": [], + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'step', 'max', 'min', 'disabled', 'orientation', 'description']\n", + "[]\n", + "[]\n" + ] + } + ], "prompt_number": 3 }, { @@ -132,6 +142,26 @@ "prompt_number": 4 }, { + "cell_type": "code", + "collapsed": false, + "input": [ + "mywidget.value" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 13, + "text": [ + "55.1" + ] + } + ], + "prompt_number": 13 + }, + { "cell_type": "markdown", "metadata": {}, "source": [ @@ -152,10 +182,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 5, + "prompt_number": 11, "text": [ "['visible',\n", " '_css',\n", + " '_children_attr',\n", + " '_children_lists_attr',\n", + " 'default_view_name',\n", " 'value',\n", " 'step',\n", " 'max',\n", @@ -166,7 +199,7 @@ ] } ], - "prompt_number": 5 + "prompt_number": 11 }, { "cell_type": "markdown", @@ -184,7 +217,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 6 + "prompt_number": 12 }, { "cell_type": "markdown", @@ -205,13 +238,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 7, + "prompt_number": 30, "text": [ - "0.0" + "25.0" ] } ], - "prompt_number": 7 + "prompt_number": 30 }, { "cell_type": "markdown", @@ -229,8 +262,38 @@ ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 8 + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'values', 'disabled', 'description']\n", + "[]\n", + "[]\n" + ] + } + ], + "prompt_number": 14 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "mysecondwidget.value" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 15, + "text": [ + "u'Item C'" + ] + } + ], + "prompt_number": 15 }, { "cell_type": "heading", @@ -259,13 +322,13 @@ { "metadata": {}, "output_type": "pyout", - "prompt_number": 9, + "prompt_number": 16, "text": [ "u'FloatSliderView'" ] } ], - "prompt_number": 9 + "prompt_number": 16 }, { "cell_type": "markdown", @@ -284,7 +347,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 10 + "prompt_number": 17 }, { "cell_type": "markdown", diff --git a/examples/widgets/Part 2 - Events.ipynb b/examples/widgets/Part 2 - Events.ipynb index 3b5b0a5..bd5c574 100644 --- a/examples/widgets/Part 2 - Events.ipynb +++ b/examples/widgets/Part 2 - Events.ipynb @@ -108,7 +108,7 @@ "def on_value_change(name, value):\n", " print(value)\n", "\n", - "intrange.on_trait_change(on_value_change, 'value')" + "#intrange.on_trait_change(on_value_change, 'value')" ], "language": "python", "metadata": {}, @@ -117,25 +117,13 @@ "output_type": "stream", "stream": "stdout", "text": [ - "28\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "55\n" - ] - }, - { - "output_type": "stream", - "stream": "stdout", - "text": [ - "94\n" + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'step', 'max', 'min', 'disabled', 'orientation', 'description']\n", + "[]\n", + "[]\n" ] } ], - "prompt_number": 3 + "prompt_number": 9 }, { "cell_type": "heading", @@ -200,11 +188,32 @@ "cell_type": "code", "collapsed": false, "input": [ + "display(intrange)\n", + "print('hi')" + ], + "language": "python", + "metadata": {}, + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "hi\n" + ] + } + ], + "prompt_number": 5 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ "button = widgets.ButtonWidget(description=\"Click Me!\")\n", "display(button)\n", "\n", "def on_button_clicked(sender):\n", " print(\"Button clicked.\")\n", + " intrange.value +=1\n", "\n", "button.on_click(on_button_clicked)" ], @@ -215,6 +224,22 @@ "output_type": "stream", "stream": "stdout", "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "Button clicked.\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ "Button clicked.\n" ] }, @@ -233,7 +258,7 @@ ] } ], - "prompt_number": 5 + "prompt_number": 12 }, { "cell_type": "markdown", @@ -245,6 +270,36 @@ { "cell_type": "code", "collapsed": false, + "input": [], + "language": "python", + "metadata": {}, + "outputs": [ + { + "metadata": {}, + "output_type": "pyout", + "prompt_number": 11, + "text": [ + "{'content': {'data': \"{'parent_header': {}, 'msg_type': u'comm_msg', 'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', 'content': {u'data': {u'method': u'custom', u'custom_content': {u'event': u'click'}}, u'comm_id': u'eea5f11ae7aa473993dd0c81d6016648'}, 'header': {u'username': u'username', u'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', u'msg_type': u'comm_msg', u'session': u'0F6D6BE728DA47A38CFC4BDEACF34FC4'}, 'buffers': [], 'metadata': {}}\\ncustom message {'parent_header': {}, 'msg_type': u'comm_msg', 'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', 'content': {u'data': {u'method': u'custom', u'custom_content': {u'event': u'click'}}, u'comm_id': u'eea5f11ae7aa473993dd0c81d6016648'}, 'header': {u'username': u'username', u'msg_id': u'3DBB06AD83C942DD85DC6477B08F1FBF', u'msg_type': u'comm_msg', u'session': u'0F6D6BE728DA47A38CFC4BDEACF34FC4'}, 'buffers': [], 'metadata': {}}\\nhandling click\\n{u'event': u'click'}\\nButton clicked.\\n2\\n\",\n", + " 'name': 'stdout'},\n", + " 'header': {'msg_id': 'd9dc144a-d86c-42c1-8bab-f8a6bc525723',\n", + " 'msg_type': 'stream',\n", + " 'session': '9b9408d8-7420-4e0c-976d-cdda9f8d2564',\n", + " 'username': 'kernel'},\n", + " 'metadata': {},\n", + " 'msg_id': 'd9dc144a-d86c-42c1-8bab-f8a6bc525723',\n", + " 'msg_type': 'stream',\n", + " 'parent_header': {'msg_id': '3DBB06AD83C942DD85DC6477B08F1FBF',\n", + " 'msg_type': 'comm_msg',\n", + " 'session': '0F6D6BE728DA47A38CFC4BDEACF34FC4',\n", + " 'username': 'username'}}" + ] + } + ], + "prompt_number": 11 + }, + { + "cell_type": "code", + "collapsed": false, "input": [ "def show_button(sender=None):\n", " button = widgets.ButtonWidget()\n", @@ -261,8 +316,161 @@ ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 6 + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'description', 'disabled']\n", + "[]\n", + "[]\n" + ] + } + ], + "prompt_number": 7 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [], + "language": "python", + "metadata": {}, + "outputs": [] } ], "metadata": {} diff --git a/examples/widgets/Part 3 - Placement.ipynb b/examples/widgets/Part 3 - Placement.ipynb index 49b20ea..0bf580d 100644 --- a/examples/widgets/Part 3 - Placement.ipynb +++ b/examples/widgets/Part 3 - Placement.ipynb @@ -46,12 +46,11 @@ "cell_type": "code", "collapsed": false, "input": [ - "container = widgets.MulticontainerWidget()\n", "\n", - "floatrange = widgets.FloatRangeWidget(parent=container) # You can set the parent in the constructor,\n", + "floatrange = widgets.FloatRangeWidget() # You can set the parent in the constructor,\n", "\n", - "string = widgets.StringWidget()\n", - "string.parent = container # or after the widget has been created.\n", + "string = widgets.StringWidget(value='hi')\n", + "container = widgets.MulticontainerWidget(children=[floatrange, string])\n", "\n", "display(container) # Displays the `container` and all of it's children." ], @@ -187,12 +186,22 @@ "collapsed": false, "input": [ "string = widgets.StringWidget(value=\"Hello World!\")\n", - "display(string, view_name=\"LabelView\") " + "display(string, view_name=\"HTMLView\") " ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 7 + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "['visible', '_css', '_children_attr', '_children_lists_attr', 'default_view_name', 'value', 'disabled', 'description']\n", + "[]\n", + "[]\n" + ] + } + ], + "prompt_number": 11 }, { "cell_type": "code", @@ -203,7 +212,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 8 + "prompt_number": 12 }, { "cell_type": "code", @@ -214,7 +223,7 @@ "language": "python", "metadata": {}, "outputs": [], - "prompt_number": 9 + "prompt_number": 13 }, { "cell_type": "markdown", @@ -228,18 +237,38 @@ "collapsed": false, "input": [ "form = widgets.ContainerWidget()\n", - "first = widgets.StringWidget(description=\"First Name:\", parent=form)\n", - "last = widgets.StringWidget(description=\"Last Name:\", parent=form)\n", + "first = widgets.StringWidget(description=\"First Name:\")\n", + "last = widgets.StringWidget(description=\"Last Name:\")\n", "\n", - "student = widgets.BoolWidget(description=\"Student:\", value=False, parent=form)\n", - "school_info = widgets.ContainerWidget(visible=False, parent=form)\n", - "school = widgets.StringWidget(description=\"School:\", parent=school_info)\n", - "grade = widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView', parent=school_info)\n", + "student = widgets.BoolWidget(description=\"Student:\", value=False)\n", + "form.children=[first, last, student]\n", + "display(form)" + ], + "language": "python", + "metadata": {}, + "outputs": [], + "prompt_number": 2 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [ + "form = widgets.ContainerWidget()\n", + "first = widgets.StringWidget(description=\"First Name:\")\n", + "last = widgets.StringWidget(description=\"Last Name:\")\n", + "\n", + "student = widgets.BoolWidget(description=\"Student:\", value=False)\n", + "school_info = widgets.ContainerWidget(visible=False, children=[\n", + " widgets.StringWidget(description=\"School:\"),\n", + " widgets.IntRangeWidget(description=\"Grade:\", min=0, max=12, default_view_name='IntTextView')\n", + " ])\n", "\n", - "pet = widgets.StringWidget(description=\"Pet's Name:\", parent=form)\n", + "pet = widgets.StringWidget(description=\"Pet's Name:\")\n", + "form.children = [first, last, student, school_info, pet]\n", "display(form)\n", "\n", "def on_student_toggle(name, value):\n", + " print value\n", " if value:\n", " school_info.visible = True\n", " else:\n", @@ -248,8 +277,66 @@ ], "language": "python", "metadata": {}, - "outputs": [], - "prompt_number": 10 + "outputs": [ + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "True\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "False\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "True\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "False\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "True\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "False\n" + ] + }, + { + "output_type": "stream", + "stream": "stdout", + "text": [ + "True\n" + ] + } + ], + "prompt_number": 2 + }, + { + "cell_type": "code", + "collapsed": false, + "input": [], + "language": "python", + "metadata": {}, + "outputs": [] } ], "metadata": {}