diff --git a/IPython/html/widgets/__init__.py b/IPython/html/widgets/__init__.py new file mode 100644 index 0000000..58dd147 --- /dev/null +++ b/IPython/html/widgets/__init__.py @@ -0,0 +1 @@ +from widget import Widget \ No newline at end of file diff --git a/IPython/html/widgets/widget.py b/IPython/html/widgets/widget.py new file mode 100644 index 0000000..5f7feb0 --- /dev/null +++ b/IPython/html/widgets/widget.py @@ -0,0 +1,148 @@ +from IPython.kernel.comm import Comm +from IPython.config import LoggingConfigurable +from IPython.utils.traitlets import Unicode, Float, Bool, Dict +from IPython.display import clear_output + +from copy import copy +import uuid +import sys + + +class Widget(Comm): + + ### Public declarations + target_name = Unicode('widget') + default_view_name = Unicode() + + + ### Private/protected declarations + _keys = [] + _property_lock = False + _parent = None + _children = [] + _css = Dict() + + + ### Public constructor + def __init__(self, parent=None, **kwargs): + super(Widget, self).__init__(**kwargs) + + self._children = [] + if parent is not None: + parent._children.append(self) + self._parent = parent + + self.comm = Comm(target_name=self.target_name) + self.comm.on_msg(self._handle_msg) + + # Register after init to allow default values to be specified + self.on_trait_change(self._handle_property_changed, self.keys) + + # Set initial properties on client model. + self.send_state() + + + def __del__(self): + self.close() + + def close(self): + self.comm.close() + del self.comm + + + ### Properties + def _get_parent(self): + return self._parent + parent = property(_get_parent) + + + def _get_children(self): + return copy(self._children) + children = property(_get_children) + + + def _get_keys(self): + keys = ['_css'] + keys.extend(self._keys) + return keys + keys = property(_get_keys) + + def _get_css(self, key, selector=""): + if selector in self._css and key in self._css[selector]: + return self._css[selector][key] + else: + return None + def _set_css(self, value, key, selector=""): + if selector not in self._css: + self._css[selector] = {} + + # Only update the property if it has changed. + if not (key in self._css[selector] and value in self._css[selector][key]): + self._css[selector][key] = value + self.send_state() # Send new state to client. + + css = property(_get_css, _set_css) + + + ### Event handlers + def _handle_msg(self, msg): + + # Handle backbone sync methods + sync_method = msg['content']['data']['sync_method'] + sync_data = msg['content']['data']['sync_data'] + if sync_method.lower() in ['create', 'update']: + self._handle_recieve_state(sync_data) + + + def _handle_recieve_state(self, sync_data): + self._property_lock = True + try: + + # Use _keys instead of keys - Don't get retrieve the css from the client side. + for name in self._keys: + if name in sync_data: + setattr(self, name, sync_data[name]) + finally: + self._property_lock = False + + + def _handle_property_changed(self, name, old, new): + if not self._property_lock: + # TODO: Validate properties. + # Send new state to frontend + self.send_state() + + + ### Public methods + def show(self, view_name=None): + if not view_name: + view_name = self.default_view_name + if not view_name: + view_name = self.target_name + + # Make sure model is syncronized + self.send_state() + + # Show view. + if self.parent is None: + self.comm.send({"method": "show", "view_name": view_name}) + else: + self.comm.send({"method": "show", + "view_name": view_name, + "parent": self.parent.comm.comm_id}) + + # Now show children if any. + for child in self.children: + child.show() + + + def send_state(self): + state = {} + for key in self.keys: + try: + state[key] = getattr(self, key) + except Exception as e: + pass # Eat errors, nom nom nom + self.comm.send({"method": "update", + "state": state}) + \ No newline at end of file