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