diff --git a/IPython/html/static/notebook/js/widget.js b/IPython/html/static/notebook/js/widget.js
index 5e9b8fb..15bc293 100644
--- a/IPython/html/static/notebook/js/widget.js
+++ b/IPython/html/static/notebook/js/widget.js
@@ -63,16 +63,47 @@ define(["components/underscore/underscore-min",
}
}
},
+
+ send = function (content) {
+
+ // 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.
+ var cell = null;
+ if (this.last_modified_view != undefined &&
+ this.last_modified_view.cell != undefined) {
+ cell = this.last_modified_view.cell;
+ }
+ var callbacks = this._make_callbacks(cell);
+ var data = {custom_content: content};
+ this.comm.send(data, callbacks);
+ },
on_view_displayed: function (callback) {
this._view_displayed_callback = callback;
- }
+ },
on_close: function (callback) {
this._close_callback = callback;
- }
+ },
+
+
+ on_msg: function (callback) {
+ this._msg_callback = callback;
+ },
+
+
+ _handle_custom_msg: function (content) {
+ if (this._msg_callback) {
+ try {
+ this._msg_callback(content);
+ } catch (e) {
+ console.log("Exception in widget model msg callback", e, content);
+ }
+ }
+ },
// Handle when a widget is closed.
@@ -108,6 +139,9 @@ define(["components/underscore/underscore-min",
case 'update':
this._handle_update(msg.content.data.state);
break;
+ case 'custom':
+ this._handle_custom_msg(msg.content.data.custom_content);
+ break;
}
},
@@ -225,7 +259,7 @@ define(["components/underscore/underscore-min",
console.log("Exception in widget model view displayed callback", e, view, this);
}
}
- }
+ },
// Create view that represents the model.
diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py
index 47c2150..2c540eb 100644
--- a/IPython/html/widgets/widget.py
+++ b/IPython/html/widgets/widget.py
@@ -83,6 +83,7 @@ class Widget(LoggingConfigurable):
self._add_class = [0]
self._remove_class = [0]
self._display_callbacks = []
+ self._msg_callbacks = []
super(Widget, self).__init__(**kwargs)
# Register after init to allow default values to be specified
@@ -116,10 +117,38 @@ class Widget(LoggingConfigurable):
# Event handlers
def _handle_msg(self, msg):
"""Called when a msg is recieved from the frontend"""
+ data = msg['content']['data']
+
# Handle backbone sync methods CREATE, PATCH, and UPDATE
- sync_method = msg['content']['data']['sync_method']
- sync_data = msg['content']['data']['sync_data']
- self._handle_recieve_state(sync_data) # handles all methods
+ if 'sync_method' in data and 'sync_data' in data:
+ sync_method = data['sync_method']
+ sync_data = data['sync_data']
+ self._handle_recieve_state(sync_data) # handles all methods
+
+ # Handle a custom msg from the front-end
+ 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:
+ if callable(handler):
+ argspec = inspect.getargspec(handler)
+ nargs = len(argspec[0])
+
+ # Bound methods have an additional 'self' argument
+ if isinstance(handler, types.MethodType):
+ nargs -= 1
+
+ # Call the callback
+ if nargs == 1:
+ handler(content)
+ elif nargs == 2:
+ handler(self, content)
+ else:
+ raise TypeError('Widget msg callback must ' \
+ 'accept 1 or 2 arguments, not %d.' % nargs)
def _handle_recieve_state(self, sync_data):
@@ -146,6 +175,34 @@ class Widget(LoggingConfigurable):
def _handle_close(self):
"""Called when the comm is closed by the frontend."""
self._comm = None
+
+
+ def _handle_displayed(self, view_name):
+ """Called when a view has been displayed for this widget instance
+
+ Parameters
+ ----------
+ view_name: unicode
+ Name of the view that was displayed."""
+ for handler in self._display_callbacks:
+ if callable(handler):
+ argspec = inspect.getargspec(handler)
+ nargs = len(argspec[0])
+
+ # Bound methods have an additional 'self' argument
+ if isinstance(handler, types.MethodType):
+ nargs -= 1
+
+ # Call the callback
+ if nargs == 0:
+ handler()
+ elif nargs == 1:
+ handler(self)
+ elif nargs == 2:
+ handler(self, view_name)
+ else:
+ raise TypeError('Widget display callback must ' \
+ 'accept 0-2 arguments, not %d.' % nargs)
# Public methods
@@ -271,6 +328,36 @@ class Widget(LoggingConfigurable):
self.send_state(key='_remove_class')
+ def send(self, content):
+ """Sends a custom msg to the widget model in the front-end.
+
+ Parameters
+ ----------
+ content : dict
+ Content of the message to send.
+ """
+ if self._comm is not None:
+ self._comm.send({"method": "custom",
+ "custom_content": content})
+
+
+ def on_msg(self, callback, remove=False):
+ """Register a callback for when a custom msg is recieved from the front-end
+
+ Parameters
+ ----------
+ callback: method handler
+ Can have a signature of:
+ - callback(content)
+ - callback(sender, content)
+ remove: bool
+ True if the callback should be unregistered."""
+ if remove and callback in self._msg_callbacks:
+ self._msg_callbacks.remove(callback)
+ elif not remove and not callback in self._msg_callbacks:
+ self._msg_callbacks.append(callback)
+
+
def on_displayed(self, callback, remove=False):
"""Register a callback to be called when the widget has been displayed
@@ -283,40 +370,12 @@ class Widget(LoggingConfigurable):
- callback(sender, view_name)
remove: bool
True if the callback should be unregistered."""
- if remove:
+ if remove and callback in self._display_callbacks:
self._display_callbacks.remove(callback)
- elif not callback in self._display_callbacks:
+ elif not remove and not callback in self._display_callbacks:
self._display_callbacks.append(callback)
- def handle_displayed(self, view_name):
- """Called when a view has been displayed for this widget instance
-
- Parameters
- ----------
- view_name: unicode
- Name of the view that was displayed."""
- for handler in self._display_callbacks:
- if callable(handler):
- argspec = inspect.getargspec(handler)
- nargs = len(argspec[0])
-
- # Bound methods have an additional 'self' argument
- if isinstance(handler, types.MethodType):
- nargs -= 1
-
- # Call the callback
- if nargs == 0:
- handler()
- elif nargs == 1:
- handler(self)
- elif nargs == 2:
- handler(self, view_name)
- else:
- raise TypeError('Widget display callback must ' \
- 'accept 0-2 arguments, not %d.' % nargs)
-
-
# Support methods
def _repr_widget_(self, view_name=None):
"""Function that is called when `IPython.display.display` is called on
@@ -347,7 +406,7 @@ class Widget(LoggingConfigurable):
"view_name": view_name,
"parent": self.parent._comm.comm_id})
self._displayed = True
- self.handle_displayed(view_name)
+ self._handle_displayed(view_name)
# Now display children if any.
for child in self._children: