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: