mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-27 04:20:22 +08:00
990303f3eb
this model does not work with the comm/widget communication model. We need to be able to load the dependencies before we send the show() message to the widget manager. Otherwise a race condition will exist where the widget manager may recieve the show message before the dependencies have been loaded (happened almost every time).
169 lines
4.8 KiB
Python
169 lines
4.8 KiB
Python
from copy import copy
|
|
from glob import glob
|
|
import uuid
|
|
import sys
|
|
import os
|
|
|
|
import IPython
|
|
from IPython.kernel.comm import Comm
|
|
from IPython.config import LoggingConfigurable
|
|
from IPython.utils.traitlets import Unicode, Dict, List
|
|
from IPython.display import Javascript, display
|
|
from IPython.utils.py3compat import string_types
|
|
|
|
|
|
class Widget(LoggingConfigurable):
|
|
|
|
### Public declarations
|
|
target_name = Unicode('widget')
|
|
default_view_name = Unicode()
|
|
js_requirements = List()
|
|
|
|
|
|
### 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 = None
|
|
|
|
# Register after init to allow default values to be specified
|
|
self.on_trait_change(self._handle_property_changed, self.keys)
|
|
|
|
|
|
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 and self.comm is not None:
|
|
# TODO: Validate properties.
|
|
# Send new state to frontend
|
|
self.send_state()
|
|
|
|
|
|
def _handle_close(self):
|
|
self.comm = None
|
|
|
|
|
|
### Public methods
|
|
def _repr_widget_(self, view_name=None):
|
|
if not view_name:
|
|
view_name = self.default_view_name
|
|
|
|
# Require traitlet specified widget js
|
|
self._require_js('static/notebook/js/widget.js')
|
|
for requirement in self.js_requirements:
|
|
self._require_js(requirement)
|
|
|
|
# Create a comm.
|
|
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._handle_close)
|
|
|
|
# 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._repr_widget_()
|
|
return None
|
|
|
|
|
|
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})
|
|
|
|
### Private methods
|
|
|
|
def _require_js(self, js_path):
|
|
# Since we are loading requirements that must be loaded before this call
|
|
# returns, preform async js load.
|
|
display(Javascript(data='$.ajax({url: "%s", async: false, dataType: "script", timeout: 1000});' % js_path))
|