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})