##// END OF EJS Templates
Actual absolute paths...
Jonathan Frederic -
Show More
@@ -1,251 +1,251 b''
1 """Base Widget class. Allows user to create widgets in the backend that render
1 """Base Widget class. Allows user to create widgets in the backend that render
2 in the IPython notebook frontend.
2 in the IPython notebook frontend.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from copy import copy
15 from copy import copy
16 from glob import glob
16 from glob import glob
17 import uuid
17 import uuid
18 import sys
18 import sys
19 import os
19 import os
20
20
21 import IPython
21 import IPython
22 from IPython.kernel.comm import Comm
22 from IPython.kernel.comm import Comm
23 from IPython.config import LoggingConfigurable
23 from IPython.config import LoggingConfigurable
24 from IPython.utils.traitlets import Unicode, Dict, List
24 from IPython.utils.traitlets import Unicode, Dict, List
25 from IPython.display import Javascript, display
25 from IPython.display import Javascript, display
26 from IPython.utils.py3compat import string_types
26 from IPython.utils.py3compat import string_types
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Shared
29 # Shared
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 def init_widget_js():
31 def init_widget_js():
32 path = os.path.split(os.path.abspath( __file__ ))[0]
32 path = os.path.split(os.path.abspath( __file__ ))[0]
33 for filepath in glob(os.path.join(path, "*.py")):
33 for filepath in glob(os.path.join(path, "*.py")):
34 filename = os.path.split(filepath)[1]
34 filename = os.path.split(filepath)[1]
35 name = filename.rsplit('.', 1)[0]
35 name = filename.rsplit('.', 1)[0]
36 if not (name == 'widget' or name == '__init__') and name.startswith('widget_'):
36 if not (name == 'widget' or name == '__init__') and name.startswith('widget_'):
37 # Remove 'widget_' from the start of the name before compiling the path.
37 # Remove 'widget_' from the start of the name before compiling the path.
38 js_path = 'static/notebook/js/widgets/%s.js' % name[7:]
38 js_path = '/static/notebook/js/widgets/%s.js' % name[7:]
39 display(Javascript(data='$.getScript("%s");' % js_path))
39 display(Javascript(data='$.getScript("%s");' % js_path))
40
40
41
41
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43 # Classes
43 # Classes
44 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
45 class Widget(LoggingConfigurable):
45 class Widget(LoggingConfigurable):
46
46
47 # Public declarations
47 # Public declarations
48 target_name = Unicode('widget', help="""Name of the backbone model
48 target_name = Unicode('widget', help="""Name of the backbone model
49 registered in the frontend to create and sync this widget with.""")
49 registered in the frontend to create and sync this widget with.""")
50 default_view_name = Unicode(help="""Default view registered in the frontend
50 default_view_name = Unicode(help="""Default view registered in the frontend
51 to use to represent the widget.""")
51 to use to represent the widget.""")
52
52
53
53
54 # Private/protected declarations
54 # Private/protected declarations
55 _keys = []
55 _keys = []
56 _property_lock = False
56 _property_lock = False
57 _parent = None
57 _parent = None
58 _children = []
58 _children = []
59 _css = Dict()
59 _css = Dict()
60
60
61
61
62 def __init__(self, parent=None, **kwargs):
62 def __init__(self, parent=None, **kwargs):
63 """Public constructor
63 """Public constructor
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 parent : Widget instance (optional)
67 parent : Widget instance (optional)
68 Widget that this widget instance is child of. When the widget is
68 Widget that this widget instance is child of. When the widget is
69 displayed in the frontend, it's corresponding view will be made
69 displayed in the frontend, it's corresponding view will be made
70 child of the parent's view if the parent's view exists already. If
70 child of the parent's view if the parent's view exists already. If
71 the parent's view is displayed, it will automatically display this
71 the parent's view is displayed, it will automatically display this
72 widget's default view as it's child. The default view can be set
72 widget's default view as it's child. The default view can be set
73 via the default_view_name property.
73 via the default_view_name property.
74 """
74 """
75 super(Widget, self).__init__(**kwargs)
75 super(Widget, self).__init__(**kwargs)
76
76
77 # Parent/child association
77 # Parent/child association
78 self._children = []
78 self._children = []
79 if parent is not None:
79 if parent is not None:
80 parent._children.append(self)
80 parent._children.append(self)
81 self._parent = parent
81 self._parent = parent
82 self.comm = None
82 self.comm = None
83
83
84 # Register after init to allow default values to be specified
84 # Register after init to allow default values to be specified
85 self.on_trait_change(self._handle_property_changed, self.keys)
85 self.on_trait_change(self._handle_property_changed, self.keys)
86
86
87
87
88 def __del__(self):
88 def __del__(self):
89 """Object disposal"""
89 """Object disposal"""
90 self.close()
90 self.close()
91
91
92
92
93 def close(self):
93 def close(self):
94 """Close method. Closes the widget which closes the underlying comm.
94 """Close method. Closes the widget which closes the underlying comm.
95 When the comm is closed, all of the widget views are automatically
95 When the comm is closed, all of the widget views are automatically
96 removed from the frontend."""
96 removed from the frontend."""
97 self.comm.close()
97 self.comm.close()
98 del self.comm
98 del self.comm
99
99
100
100
101 # Properties
101 # Properties
102 def _get_parent(self):
102 def _get_parent(self):
103 return self._parent
103 return self._parent
104 parent = property(_get_parent)
104 parent = property(_get_parent)
105
105
106
106
107 def _get_children(self):
107 def _get_children(self):
108 return copy(self._children)
108 return copy(self._children)
109 children = property(_get_children)
109 children = property(_get_children)
110
110
111
111
112 def _get_keys(self):
112 def _get_keys(self):
113 keys = ['_css']
113 keys = ['_css']
114 keys.extend(self._keys)
114 keys.extend(self._keys)
115 return keys
115 return keys
116 keys = property(_get_keys)
116 keys = property(_get_keys)
117
117
118
118
119 # Event handlers
119 # Event handlers
120 def _handle_msg(self, msg):
120 def _handle_msg(self, msg):
121 """Called when a msg is recieved from the frontend"""
121 """Called when a msg is recieved from the frontend"""
122 # Handle backbone sync methods CREATE, PATCH, and UPDATE
122 # Handle backbone sync methods CREATE, PATCH, and UPDATE
123 sync_method = msg['content']['data']['sync_method']
123 sync_method = msg['content']['data']['sync_method']
124 sync_data = msg['content']['data']['sync_data']
124 sync_data = msg['content']['data']['sync_data']
125 self._handle_recieve_state(sync_data) # handles all methods
125 self._handle_recieve_state(sync_data) # handles all methods
126
126
127
127
128 def _handle_recieve_state(self, sync_data):
128 def _handle_recieve_state(self, sync_data):
129 """Called when a state is recieved from the frontend."""
129 """Called when a state is recieved from the frontend."""
130 self._property_lock = True
130 self._property_lock = True
131 try:
131 try:
132
132
133 # Use _keys instead of keys - Don't get retrieve the css from the client side.
133 # Use _keys instead of keys - Don't get retrieve the css from the client side.
134 for name in self._keys:
134 for name in self._keys:
135 if name in sync_data:
135 if name in sync_data:
136 setattr(self, name, sync_data[name])
136 setattr(self, name, sync_data[name])
137 finally:
137 finally:
138 self._property_lock = False
138 self._property_lock = False
139
139
140
140
141 def _handle_property_changed(self, name, old, new):
141 def _handle_property_changed(self, name, old, new):
142 """Called when a proeprty has been changed."""
142 """Called when a proeprty has been changed."""
143 if not self._property_lock and self.comm is not None:
143 if not self._property_lock and self.comm is not None:
144 # TODO: Validate properties.
144 # TODO: Validate properties.
145 # Send new state to frontend
145 # Send new state to frontend
146 self.send_state(key=name)
146 self.send_state(key=name)
147
147
148
148
149 def _handle_close(self):
149 def _handle_close(self):
150 """Called when the comm is closed by the frontend."""
150 """Called when the comm is closed by the frontend."""
151 self.comm = None
151 self.comm = None
152
152
153
153
154 # Public methods
154 # Public methods
155 def send_state(self, key=None):
155 def send_state(self, key=None):
156 """Sends the widget state, or a piece of it, to the frontend.
156 """Sends the widget state, or a piece of it, to the frontend.
157
157
158 Parameters
158 Parameters
159 ----------
159 ----------
160 key : unicode (optional)
160 key : unicode (optional)
161 A single property's name to sync with the frontend.
161 A single property's name to sync with the frontend.
162 """
162 """
163 state = {}
163 state = {}
164
164
165 # If a key is provided, just send the state of that key.
165 # If a key is provided, just send the state of that key.
166 keys = []
166 keys = []
167 if key is None:
167 if key is None:
168 keys.extend(self.keys)
168 keys.extend(self.keys)
169 else:
169 else:
170 keys.append(key)
170 keys.append(key)
171 for key in self.keys:
171 for key in self.keys:
172 try:
172 try:
173 state[key] = getattr(self, key)
173 state[key] = getattr(self, key)
174 except Exception as e:
174 except Exception as e:
175 pass # Eat errors, nom nom nom
175 pass # Eat errors, nom nom nom
176 self.comm.send({"method": "update",
176 self.comm.send({"method": "update",
177 "state": state})
177 "state": state})
178
178
179
179
180 def get_css(self, key, selector=""):
180 def get_css(self, key, selector=""):
181 """Get a CSS property of the widget views (shared among all of the
181 """Get a CSS property of the widget views (shared among all of the
182 views)
182 views)
183
183
184 Parameters
184 Parameters
185 ----------
185 ----------
186 key: unicode
186 key: unicode
187 CSS key
187 CSS key
188 selector: unicode (optional)
188 selector: unicode (optional)
189 JQuery selector used when the CSS key/value was set.
189 JQuery selector used when the CSS key/value was set.
190 """
190 """
191 if selector in self._css and key in self._css[selector]:
191 if selector in self._css and key in self._css[selector]:
192 return self._css[selector][key]
192 return self._css[selector][key]
193 else:
193 else:
194 return None
194 return None
195
195
196
196
197 def set_css(self, key, value, selector=""):
197 def set_css(self, key, value, selector=""):
198 """Set a CSS property of the widget views (shared among all of the
198 """Set a CSS property of the widget views (shared among all of the
199 views)
199 views)
200
200
201 Parameters
201 Parameters
202 ----------
202 ----------
203 key: unicode
203 key: unicode
204 CSS key
204 CSS key
205 value
205 value
206 CSS value
206 CSS value
207 selector: unicode (optional)
207 selector: unicode (optional)
208 JQuery selector to use to apply the CSS key/value.
208 JQuery selector to use to apply the CSS key/value.
209 """
209 """
210 if selector not in self._css:
210 if selector not in self._css:
211 self._css[selector] = {}
211 self._css[selector] = {}
212
212
213 # Only update the property if it has changed.
213 # Only update the property if it has changed.
214 if not (key in self._css[selector] and value in self._css[selector][key]):
214 if not (key in self._css[selector] and value in self._css[selector][key]):
215 self._css[selector][key] = value
215 self._css[selector][key] = value
216 self.send_state() # Send new state to client.
216 self.send_state() # Send new state to client.
217
217
218
218
219 # Support methods
219 # Support methods
220 def _repr_widget_(self, view_name=None):
220 def _repr_widget_(self, view_name=None):
221 """Function that is called when `IPython.display.display` is called on
221 """Function that is called when `IPython.display.display` is called on
222 the widget.
222 the widget.
223
223
224 Parameters
224 Parameters
225 ----------
225 ----------
226 view_name: unicode (optional)
226 view_name: unicode (optional)
227 View to display in the frontend. Overrides default_view_name."""
227 View to display in the frontend. Overrides default_view_name."""
228 if not view_name:
228 if not view_name:
229 view_name = self.default_view_name
229 view_name = self.default_view_name
230
230
231 # Create a comm.
231 # Create a comm.
232 if self.comm is None:
232 if self.comm is None:
233 self.comm = Comm(target_name=self.target_name)
233 self.comm = Comm(target_name=self.target_name)
234 self.comm.on_msg(self._handle_msg)
234 self.comm.on_msg(self._handle_msg)
235 self.comm.on_close(self._handle_close)
235 self.comm.on_close(self._handle_close)
236
236
237 # Make sure model is syncronized
237 # Make sure model is syncronized
238 self.send_state()
238 self.send_state()
239
239
240 # Show view.
240 # Show view.
241 if self.parent is None:
241 if self.parent is None:
242 self.comm.send({"method": "display", "view_name": view_name})
242 self.comm.send({"method": "display", "view_name": view_name})
243 else:
243 else:
244 self.comm.send({"method": "display",
244 self.comm.send({"method": "display",
245 "view_name": view_name,
245 "view_name": view_name,
246 "parent": self.parent.comm.comm_id})
246 "parent": self.parent.comm.comm_id})
247
247
248 # Now display children if any.
248 # Now display children if any.
249 for child in self.children:
249 for child in self.children:
250 child._repr_widget_()
250 child._repr_widget_()
251 return None
251 return None
General Comments 0
You need to be logged in to leave comments. Login now