##// END OF EJS Templates
Separate the display from the models on the python side, creating a BaseWidget class....
Jason Grout -
Show More
@@ -30,10 +30,13 b' from IPython.utils.py3compat import string_types'
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Classes
31 # Classes
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 class Widget(LoggingConfigurable):
33
34 class BaseWidget(LoggingConfigurable):
34
35
35 # Shared declarations (Class level)
36 # Shared declarations (Class level)
36 _keys = []
37 _keys = List(Unicode, help="List of keys comprising the state of the model.")
38 _children_attr = List(Unicode, help="List of keys of children objects of the model.")
39 _children_lists_attr = List(Unicode, help="List of keys containing lists of children objects of the model.")
37 widget_construction_callback = None
40 widget_construction_callback = None
38
41
39 def on_widget_constructed(callback):
42 def on_widget_constructed(callback):
@@ -54,52 +57,24 b' class Widget(LoggingConfigurable):'
54 registered in the frontend to create and sync this widget with.""")
57 registered in the frontend to create and sync this widget with.""")
55 default_view_name = Unicode(help="""Default view registered in the frontend
58 default_view_name = Unicode(help="""Default view registered in the frontend
56 to use to represent the widget.""")
59 to use to represent the widget.""")
57 parent = Instance('IPython.html.widgets.widget.Widget')
58 visible = Bool(True, help="Whether or not the widget is visible.")
59
60 def _parent_changed(self, name, old, new):
61 if self._displayed:
62 raise Exception('Parent cannot be set because widget has been displayed.')
63 elif new == self:
64 raise Exception('Parent cannot be set to self.')
65 else:
66
67 # Parent/child association
68 if new is not None and not self in new._children:
69 new._children.append(self)
70 if old is not None and self in old._children:
71 old._children.remove(self)
72
60
73 # Private/protected declarations
61 # Private/protected declarations
74 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
62 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
75 _css = Dict() # Internal CSS property dict
76 _displayed = False
63 _displayed = False
77
64 _comm = None
78
65
79 def __init__(self, **kwargs):
66 def __init__(self, **kwargs):
80 """Public constructor
67 """Public constructor
81
82 Parameters
83 ----------
84 parent : Widget instance (optional)
85 Widget that this widget instance is child of. When the widget is
86 displayed in the frontend, it's corresponding view will be made
87 child of the parent's view if the parent's view exists already. If
88 the parent's view is displayed, it will automatically display this
89 widget's default view as it's child. The default view can be set
90 via the default_view_name property.
91 """
68 """
92 self._children = []
93 self._display_callbacks = []
69 self._display_callbacks = []
94 self._msg_callbacks = []
70 self._msg_callbacks = []
95 super(Widget, self).__init__(**kwargs)
71 super(BaseWidget, self).__init__(**kwargs)
96
72
97 # Register after init to allow default values to be specified
73 # Register after init to allow default values to be specified
98 self.on_trait_change(self._handle_property_changed, self.keys)
74 # TODO: register three different handlers, one for each list, and abstract out the common parts
99
75 self.on_trait_change(self._handle_property_changed, self.keys+self._children_attr+self._children_lists_attr)
100 Widget._handle_widget_constructed(self)
76 Widget._handle_widget_constructed(self)
101
77
102
103 def __del__(self):
78 def __del__(self):
104 """Object disposal"""
79 """Object disposal"""
105 self.close()
80 self.close()
@@ -113,12 +88,17 b' class Widget(LoggingConfigurable):'
113
88
114
89
115 # Properties
90 # Properties
116 def _get_keys(self):
91 @property
117 keys = ['visible', '_css']
92 def keys(self):
93 keys = ['_children_attr', '_children_lists_attr']
118 keys.extend(self._keys)
94 keys.extend(self._keys)
119 return keys
95 return keys
120 keys = property(_get_keys)
121
96
97 @property
98 def comm(self):
99 if self._comm is None:
100 self._open_communication()
101 return self._comm
122
102
123 # Event handlers
103 # Event handlers
124 def _handle_msg(self, msg):
104 def _handle_msg(self, msg):
@@ -179,7 +159,6 b' class Widget(LoggingConfigurable):'
179 # Send new state to frontend
159 # Send new state to frontend
180 self.send_state(key=name)
160 self.send_state(key=name)
181
161
182
183 def _handle_displayed(self, **kwargs):
162 def _handle_displayed(self, **kwargs):
184 """Called when a view has been displayed for this widget instance
163 """Called when a view has been displayed for this widget instance
185
164
@@ -206,7 +185,6 b' class Widget(LoggingConfigurable):'
206 else:
185 else:
207 handler(self, **kwargs)
186 handler(self, **kwargs)
208
187
209
210 # Public methods
188 # Public methods
211 def send_state(self, key=None):
189 def send_state(self, key=None):
212 """Sends the widget state, or a piece of it, to the frontend.
190 """Sends the widget state, or a piece of it, to the frontend.
@@ -216,119 +194,43 b' class Widget(LoggingConfigurable):'
216 key : unicode (optional)
194 key : unicode (optional)
217 A single property's name to sync with the frontend.
195 A single property's name to sync with the frontend.
218 """
196 """
219 state = {}
220
221 # If a key is provided, just send the state of that key.
222 keys = []
223 if key is None:
224 keys.extend(self.keys)
225 else:
226 keys.append(key)
227 for key in self.keys:
228 try:
229 state[key] = getattr(self, key)
230 except Exception as e:
231 pass # Eat errors, nom nom nom
232 self._send({"method": "update",
197 self._send({"method": "update",
233 "state": state})
198 "state": self.get_state()})
234
199
235
200 def get_state(self, key=None)
236 def get_css(self, key, selector=""):
201 """Gets the widget state, or a piece of it.
237 """Get a CSS property of the widget. Note, this function does not
238 actually request the CSS from the front-end; Only properties that have
239 been set with set_css can be read.
240
202
241 Parameters
203 Parameters
242 ----------
204 ----------
243 key: unicode
205 key : unicode (optional)
244 CSS key
206 A single property's name to get.
245 selector: unicode (optional)
246 JQuery selector used when the CSS key/value was set.
247 """
248 if selector in self._css and key in self._css[selector]:
249 return self._css[selector][key]
250 else:
251 return None
252
253
254 def set_css(self, *args, **kwargs):
255 """Set one or more CSS properties of the widget (shared among all of the
256 views). This function has two signatures:
257 - set_css(css_dict, [selector=''])
258 - set_css(key, value, [selector=''])
259
260 Parameters
261 ----------
262 css_dict : dict
263 CSS key/value pairs to apply
264 key: unicode
265 CSS key
266 value
267 CSS value
268 selector: unicode (optional)
269 JQuery selector to use to apply the CSS key/value.
270 """
207 """
271 selector = kwargs.get('selector', '')
208 state = {}
272
209
273 # Signature 1: set_css(css_dict, [selector=''])
210 # If a key is provided, just send the state of that key.
274 if len(args) == 1:
211 if key is None:
275 if isinstance(args[0], dict):
212 keys = self.keys[:]
276 for (key, value) in args[0].items():
213 children_attr = self._children_attr[:]
277 self.set_css(key, value, selector=selector)
214 children_lists_attr = self._children_lists_attr[:]
278 else:
215 else:
279 raise Exception('css_dict must be a dict.')
216 keys = []
280
217 children_attr = []
281 # Signature 2: set_css(key, value, [selector=''])
218 children_lists_attr = []
282 elif len(args) == 2 or len(args) == 3:
219 if key in self._children_attr:
283
220 children_attr.append(key)
284 # Selector can be a positional arg if it's the 3rd value
221 elif key in self._children_lists_attr:
285 if len(args) == 3:
222 children_lists_attr.append(key)
286 selector = args[2]
287 if selector not in self._css:
288 self._css[selector] = {}
289
290 # Only update the property if it has changed.
291 key = args[0]
292 value = args[1]
293 if not (key in self._css[selector] and value in self._css[selector][key]):
294 self._css[selector][key] = value
295 self.send_state('_css') # Send new state to client.
296 else:
223 else:
297 raise Exception('set_css only accepts 1-3 arguments')
224 keys.append(key)
298
225 for k in keys:
299
226 state[k] = getattr(self, k)
300 def add_class(self, class_name, selector=""):
227 for k in children_attr:
301 """Add class[es] to a DOM element
228 # automatically create models on the browser side if they aren't already created
302
229 state[k] = getattr(self, k).comm.comm_id
303 Parameters
230 for k in children_lists_attr:
304 ----------
231 # automatically create models on the browser side if they aren't already created
305 class_name: unicode
232 state[k] = [i.comm.comm_id for i in getattr(self, k)]
306 Class name(s) to add to the DOM element(s). Multiple class names
233 return state
307 must be space separated.
308 selector: unicode (optional)
309 JQuery selector to select the DOM element(s) that the class(es) will
310 be added to.
311 """
312 self._send({"method": "add_class",
313 "class_list": class_name,
314 "selector": selector})
315
316
317 def remove_class(self, class_name, selector=""):
318 """Remove class[es] from a DOM element
319
320 Parameters
321 ----------
322 class_name: unicode
323 Class name(s) to remove from the DOM element(s). Multiple class
324 names must be space separated.
325 selector: unicode (optional)
326 JQuery selector to select the DOM element(s) that the class(es) will
327 be removed from.
328 """
329 self._send({"method": "remove_class",
330 "class_list": class_name,
331 "selector": selector})
332
234
333
235
334 def send(self, content):
236 def send(self, content):
@@ -398,20 +300,9 b' class Widget(LoggingConfigurable):'
398 self.send_state()
300 self.send_state()
399
301
400 # Show view.
302 # Show view.
401 if self.parent is None or self.parent._comm is None:
402 self._send({"method": "display", "view_name": view_name})
303 self._send({"method": "display", "view_name": view_name})
403 else:
404 self._send({"method": "display",
405 "view_name": view_name,
406 "parent": self.parent._comm.comm_id})
407 self._handle_displayed(**kwargs)
408 self._displayed = True
304 self._displayed = True
409
305 self._handle_displayed(**kwargs)
410 # Now display children if any.
411 for child in self._children:
412 if child != self:
413 child._repr_widget_()
414 return None
415
306
416
307
417 def _open_communication(self):
308 def _open_communication(self):
@@ -434,8 +325,121 b' class Widget(LoggingConfigurable):'
434
325
435 def _send(self, msg):
326 def _send(self, msg):
436 """Sends a message to the model in the front-end"""
327 """Sends a message to the model in the front-end"""
437 if hasattr(self, '_comm') and self._comm is not None:
328 if self._comm is not None:
438 self._comm.send(msg)
329 self._comm.send(msg)
439 return True
330 return True
440 else:
331 else:
441 return False
332 return False
333
334 class Widget(BaseWidget):
335
336 _children = List(Instance('IPython.html.widgets.widget.Widget'))
337 _children_lists_attr = List(Unicode, ['_children'])
338 visible = Bool(True, help="Whether or not the widget is visible.")
339
340 # Private/protected declarations
341 _css = Dict() # Internal CSS property dict
342
343 # Properties
344 @property
345 def keys(self):
346 keys = ['visible', '_css']
347 keys.extend(super(Widget, self).keys)
348 return keys
349
350 def get_css(self, key, selector=""):
351 """Get a CSS property of the widget. Note, this function does not
352 actually request the CSS from the front-end; Only properties that have
353 been set with set_css can be read.
354
355 Parameters
356 ----------
357 key: unicode
358 CSS key
359 selector: unicode (optional)
360 JQuery selector used when the CSS key/value was set.
361 """
362 if selector in self._css and key in self._css[selector]:
363 return self._css[selector][key]
364 else:
365 return None
366
367
368 def set_css(self, *args, **kwargs):
369 """Set one or more CSS properties of the widget (shared among all of the
370 views). This function has two signatures:
371 - set_css(css_dict, [selector=''])
372 - set_css(key, value, [selector=''])
373
374 Parameters
375 ----------
376 css_dict : dict
377 CSS key/value pairs to apply
378 key: unicode
379 CSS key
380 value
381 CSS value
382 selector: unicode (optional)
383 JQuery selector to use to apply the CSS key/value.
384 """
385 selector = kwargs.get('selector', '')
386
387 # Signature 1: set_css(css_dict, [selector=''])
388 if len(args) == 1:
389 if isinstance(args[0], dict):
390 for (key, value) in args[0].items():
391 self.set_css(key, value, selector=selector)
392 else:
393 raise Exception('css_dict must be a dict.')
394
395 # Signature 2: set_css(key, value, [selector=''])
396 elif len(args) == 2 or len(args) == 3:
397
398 # Selector can be a positional arg if it's the 3rd value
399 if len(args) == 3:
400 selector = args[2]
401 if selector not in self._css:
402 self._css[selector] = {}
403
404 # Only update the property if it has changed.
405 key = args[0]
406 value = args[1]
407 if not (key in self._css[selector] and value in self._css[selector][key]):
408 self._css[selector][key] = value
409 self.send_state('_css') # Send new state to client.
410 else:
411 raise Exception('set_css only accepts 1-3 arguments')
412
413
414 def add_class(self, class_name, selector=""):
415 """Add class[es] to a DOM element
416
417 Parameters
418 ----------
419 class_name: unicode
420 Class name(s) to add to the DOM element(s). Multiple class names
421 must be space separated.
422 selector: unicode (optional)
423 JQuery selector to select the DOM element(s) that the class(es) will
424 be added to.
425 """
426 self._send({"method": "add_class",
427 "class_list": class_name,
428 "selector": selector})
429
430
431 def remove_class(self, class_name, selector=""):
432 """Remove class[es] from a DOM element
433
434 Parameters
435 ----------
436 class_name: unicode
437 Class name(s) to remove from the DOM element(s). Multiple class
438 names must be space separated.
439 selector: unicode (optional)
440 JQuery selector to select the DOM element(s) that the class(es) will
441 be removed from.
442 """
443 self._send({"method": "remove_class",
444 "class_list": class_name,
445 "selector": selector})
General Comments 0
You need to be logged in to leave comments. Login now