##// END OF EJS Templates
Added event for widget construction
Jonathan Frederic -
Show More
@@ -1,423 +1,439 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 import inspect
20 import inspect
21 import types
21 import types
22
22
23 import IPython
23 import IPython
24 from IPython.kernel.comm import Comm
24 from IPython.kernel.comm import Comm
25 from IPython.config import LoggingConfigurable
25 from IPython.config import LoggingConfigurable
26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
26 from IPython.utils.traitlets import Unicode, Dict, List, Instance, Bool
27 from IPython.display import Javascript, display
27 from IPython.display import Javascript, display
28 from IPython.utils.py3compat import string_types
28 from IPython.utils.py3compat import string_types
29
29
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31 # Classes
31 # Classes
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 class Widget(LoggingConfigurable):
33 class Widget(LoggingConfigurable):
34
34
35 # Shared declarations
35 # Shared declarations (Class level)
36 _keys = []
36 _keys = []
37 widget_construction_callback = None
37
38
38 # Public declarations
39 def on_widget_constructed(callback):
40 """Class method, registers a callback to be called when a widget is
41 constructed. The callback must have the following signature:
42 callback(widget)"""
43 Widget.widget_construction_callback = callback
44
45 def _handle_widget_constructed(widget):
46 """Class method, called when a widget is constructed."""
47 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
48 Widget.widget_construction_callback(widget)
49
50
51
52 # Public declarations (Instance level)
39 target_name = Unicode('widget', help="""Name of the backbone model
53 target_name = Unicode('widget', help="""Name of the backbone model
40 registered in the frontend to create and sync this widget with.""")
54 registered in the frontend to create and sync this widget with.""")
41 default_view_name = Unicode(help="""Default view registered in the frontend
55 default_view_name = Unicode(help="""Default view registered in the frontend
42 to use to represent the widget.""")
56 to use to represent the widget.""")
43 parent = Instance('IPython.html.widgets.widget.Widget')
57 parent = Instance('IPython.html.widgets.widget.Widget')
44 visible = Bool(True, help="Whether or not the widget is visible.")
58 visible = Bool(True, help="Whether or not the widget is visible.")
45
59
46 def _parent_changed(self, name, old, new):
60 def _parent_changed(self, name, old, new):
47 if self._displayed:
61 if self._displayed:
48 raise Exception('Parent cannot be set because widget has been displayed.')
62 raise Exception('Parent cannot be set because widget has been displayed.')
49 elif new == self:
63 elif new == self:
50 raise Exception('Parent cannot be set to self.')
64 raise Exception('Parent cannot be set to self.')
51 else:
65 else:
52
66
53 # Parent/child association
67 # Parent/child association
54 if new is not None and not self in new._children:
68 if new is not None and not self in new._children:
55 new._children.append(self)
69 new._children.append(self)
56 if old is not None and self in old._children:
70 if old is not None and self in old._children:
57 old._children.remove(self)
71 old._children.remove(self)
58
72
59 # Private/protected declarations
73 # Private/protected declarations
60 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
74 _property_lock = (None, None) # Last updated (key, value) from the front-end. Prevents echo.
61 _css = Dict() # Internal CSS property dict
75 _css = Dict() # Internal CSS property dict
62 _displayed = False
76 _displayed = False
63 _comm = None
77 _comm = None
64
78
65
79
66 def __init__(self, **kwargs):
80 def __init__(self, **kwargs):
67 """Public constructor
81 """Public constructor
68
82
69 Parameters
83 Parameters
70 ----------
84 ----------
71 parent : Widget instance (optional)
85 parent : Widget instance (optional)
72 Widget that this widget instance is child of. When the widget is
86 Widget that this widget instance is child of. When the widget is
73 displayed in the frontend, it's corresponding view will be made
87 displayed in the frontend, it's corresponding view will be made
74 child of the parent's view if the parent's view exists already. If
88 child of the parent's view if the parent's view exists already. If
75 the parent's view is displayed, it will automatically display this
89 the parent's view is displayed, it will automatically display this
76 widget's default view as it's child. The default view can be set
90 widget's default view as it's child. The default view can be set
77 via the default_view_name property.
91 via the default_view_name property.
78 """
92 """
79 self._children = []
93 self._children = []
80 self._display_callbacks = []
94 self._display_callbacks = []
81 self._msg_callbacks = []
95 self._msg_callbacks = []
82 super(Widget, self).__init__(**kwargs)
96 super(Widget, self).__init__(**kwargs)
83
97
84 # Register after init to allow default values to be specified
98 # Register after init to allow default values to be specified
85 self.on_trait_change(self._handle_property_changed, self.keys)
99 self.on_trait_change(self._handle_property_changed, self.keys)
86
100
101 Widget._handle_widget_constructed(self)
102
87
103
88 def __del__(self):
104 def __del__(self):
89 """Object disposal"""
105 """Object disposal"""
90 self.close()
106 self.close()
91
107
92
108
93 def close(self):
109 def close(self):
94 """Close method. Closes the widget which closes the underlying comm.
110 """Close method. Closes the widget which closes the underlying comm.
95 When the comm is closed, all of the widget views are automatically
111 When the comm is closed, all of the widget views are automatically
96 removed from the frontend."""
112 removed from the frontend."""
97 try:
113 try:
98 self._comm.close()
114 self._comm.close()
99 del self._comm
115 del self._comm
100 except:
116 except:
101 pass # Comm doesn't exist and/or is already closed.
117 pass # Comm doesn't exist and/or is already closed.
102
118
103
119
104 # Properties
120 # Properties
105 def _get_keys(self):
121 def _get_keys(self):
106 keys = ['visible', '_css']
122 keys = ['visible', '_css']
107 keys.extend(self._keys)
123 keys.extend(self._keys)
108 return keys
124 return keys
109 keys = property(_get_keys)
125 keys = property(_get_keys)
110
126
111
127
112 # Event handlers
128 # Event handlers
113 def _handle_msg(self, msg):
129 def _handle_msg(self, msg):
114 """Called when a msg is recieved from the frontend"""
130 """Called when a msg is recieved from the frontend"""
115 data = msg['content']['data']
131 data = msg['content']['data']
116 method = data['method']
132 method = data['method']
117
133
118 # Handle backbone sync methods CREATE, PATCH, and UPDATE
134 # Handle backbone sync methods CREATE, PATCH, and UPDATE
119 if method == 'backbone':
135 if method == 'backbone':
120 if 'sync_method' in data and 'sync_data' in data:
136 if 'sync_method' in data and 'sync_data' in data:
121 sync_method = data['sync_method']
137 sync_method = data['sync_method']
122 sync_data = data['sync_data']
138 sync_data = data['sync_data']
123 self._handle_recieve_state(sync_data) # handles all methods
139 self._handle_recieve_state(sync_data) # handles all methods
124
140
125 # Handle a custom msg from the front-end
141 # Handle a custom msg from the front-end
126 elif method == 'custom':
142 elif method == 'custom':
127 if 'custom_content' in data:
143 if 'custom_content' in data:
128 self._handle_custom_msg(data['custom_content'])
144 self._handle_custom_msg(data['custom_content'])
129
145
130
146
131 def _handle_custom_msg(self, content):
147 def _handle_custom_msg(self, content):
132 """Called when a custom msg is recieved."""
148 """Called when a custom msg is recieved."""
133 for handler in self._msg_callbacks:
149 for handler in self._msg_callbacks:
134 if callable(handler):
150 if callable(handler):
135 argspec = inspect.getargspec(handler)
151 argspec = inspect.getargspec(handler)
136 nargs = len(argspec[0])
152 nargs = len(argspec[0])
137
153
138 # Bound methods have an additional 'self' argument
154 # Bound methods have an additional 'self' argument
139 if isinstance(handler, types.MethodType):
155 if isinstance(handler, types.MethodType):
140 nargs -= 1
156 nargs -= 1
141
157
142 # Call the callback
158 # Call the callback
143 if nargs == 1:
159 if nargs == 1:
144 handler(content)
160 handler(content)
145 elif nargs == 2:
161 elif nargs == 2:
146 handler(self, content)
162 handler(self, content)
147 else:
163 else:
148 raise TypeError('Widget msg callback must ' \
164 raise TypeError('Widget msg callback must ' \
149 'accept 1 or 2 arguments, not %d.' % nargs)
165 'accept 1 or 2 arguments, not %d.' % nargs)
150
166
151
167
152 def _handle_recieve_state(self, sync_data):
168 def _handle_recieve_state(self, sync_data):
153 """Called when a state is recieved from the frontend."""
169 """Called when a state is recieved from the frontend."""
154 # Use _keys instead of keys - Don't get retrieve the css from the client side.
170 # Use _keys instead of keys - Don't get retrieve the css from the client side.
155 for name in self._keys:
171 for name in self._keys:
156 if name in sync_data:
172 if name in sync_data:
157 try:
173 try:
158 self._property_lock = (name, sync_data[name])
174 self._property_lock = (name, sync_data[name])
159 setattr(self, name, sync_data[name])
175 setattr(self, name, sync_data[name])
160 finally:
176 finally:
161 self._property_lock = (None, None)
177 self._property_lock = (None, None)
162
178
163
179
164 def _handle_property_changed(self, name, old, new):
180 def _handle_property_changed(self, name, old, new):
165 """Called when a proeprty has been changed."""
181 """Called when a proeprty has been changed."""
166 # Make sure this isn't information that the front-end just sent us.
182 # Make sure this isn't information that the front-end just sent us.
167 if self._property_lock[0] != name and self._property_lock[1] != new \
183 if self._property_lock[0] != name and self._property_lock[1] != new \
168 and self._comm is not None:
184 and self._comm is not None:
169 # TODO: Validate properties.
185 # TODO: Validate properties.
170 # Send new state to frontend
186 # Send new state to frontend
171 self.send_state(key=name)
187 self.send_state(key=name)
172
188
173
189
174 def _handle_close(self):
190 def _handle_close(self):
175 """Called when the comm is closed by the frontend."""
191 """Called when the comm is closed by the frontend."""
176 self._comm = None
192 self._comm = None
177
193
178
194
179 def _handle_displayed(self, view_name, parent=None):
195 def _handle_displayed(self, view_name, parent=None):
180 """Called when a view has been displayed for this widget instance
196 """Called when a view has been displayed for this widget instance
181
197
182 Parameters
198 Parameters
183 ----------
199 ----------
184 view_name: unicode
200 view_name: unicode
185 Name of the view that was displayed.
201 Name of the view that was displayed.
186 parent: Widget instance [optional]
202 parent: Widget instance [optional]
187 Widget that this widget should be displayed as a child of."""
203 Widget that this widget should be displayed as a child of."""
188 for handler in self._display_callbacks:
204 for handler in self._display_callbacks:
189 if callable(handler):
205 if callable(handler):
190 argspec = inspect.getargspec(handler)
206 argspec = inspect.getargspec(handler)
191 nargs = len(argspec[0])
207 nargs = len(argspec[0])
192
208
193 # Bound methods have an additional 'self' argument
209 # Bound methods have an additional 'self' argument
194 if isinstance(handler, types.MethodType):
210 if isinstance(handler, types.MethodType):
195 nargs -= 1
211 nargs -= 1
196
212
197 # Call the callback
213 # Call the callback
198 if nargs == 0:
214 if nargs == 0:
199 handler()
215 handler()
200 elif nargs == 1:
216 elif nargs == 1:
201 handler(self)
217 handler(self)
202 elif nargs == 2:
218 elif nargs == 2:
203 handler(self, view_name)
219 handler(self, view_name)
204 elif nargs == 3:
220 elif nargs == 3:
205 handler(self, view_name, parent)
221 handler(self, view_name, parent)
206 else:
222 else:
207 raise TypeError('Widget display callback must ' \
223 raise TypeError('Widget display callback must ' \
208 'accept 0-2 arguments, not %d.' % nargs)
224 'accept 0-2 arguments, not %d.' % nargs)
209
225
210
226
211 # Public methods
227 # Public methods
212 def send_state(self, key=None):
228 def send_state(self, key=None):
213 """Sends the widget state, or a piece of it, to the frontend.
229 """Sends the widget state, or a piece of it, to the frontend.
214
230
215 Parameters
231 Parameters
216 ----------
232 ----------
217 key : unicode (optional)
233 key : unicode (optional)
218 A single property's name to sync with the frontend.
234 A single property's name to sync with the frontend.
219 """
235 """
220 if self._comm is not None:
236 if self._comm is not None:
221 state = {}
237 state = {}
222
238
223 # If a key is provided, just send the state of that key.
239 # If a key is provided, just send the state of that key.
224 keys = []
240 keys = []
225 if key is None:
241 if key is None:
226 keys.extend(self.keys)
242 keys.extend(self.keys)
227 else:
243 else:
228 keys.append(key)
244 keys.append(key)
229 for key in self.keys:
245 for key in self.keys:
230 try:
246 try:
231 state[key] = getattr(self, key)
247 state[key] = getattr(self, key)
232 except Exception as e:
248 except Exception as e:
233 pass # Eat errors, nom nom nom
249 pass # Eat errors, nom nom nom
234 self._comm.send({"method": "update",
250 self._comm.send({"method": "update",
235 "state": state})
251 "state": state})
236
252
237
253
238 def get_css(self, key, selector=""):
254 def get_css(self, key, selector=""):
239 """Get a CSS property of the widget. Note, this function does not
255 """Get a CSS property of the widget. Note, this function does not
240 actually request the CSS from the front-end; Only properties that have
256 actually request the CSS from the front-end; Only properties that have
241 been set with set_css can be read.
257 been set with set_css can be read.
242
258
243 Parameters
259 Parameters
244 ----------
260 ----------
245 key: unicode
261 key: unicode
246 CSS key
262 CSS key
247 selector: unicode (optional)
263 selector: unicode (optional)
248 JQuery selector used when the CSS key/value was set.
264 JQuery selector used when the CSS key/value was set.
249 """
265 """
250 if selector in self._css and key in self._css[selector]:
266 if selector in self._css and key in self._css[selector]:
251 return self._css[selector][key]
267 return self._css[selector][key]
252 else:
268 else:
253 return None
269 return None
254
270
255
271
256 def set_css(self, *args, **kwargs):
272 def set_css(self, *args, **kwargs):
257 """Set one or more CSS properties of the widget (shared among all of the
273 """Set one or more CSS properties of the widget (shared among all of the
258 views). This function has two signatures:
274 views). This function has two signatures:
259 - set_css(css_dict, [selector=''])
275 - set_css(css_dict, [selector=''])
260 - set_css(key, value, [selector=''])
276 - set_css(key, value, [selector=''])
261
277
262 Parameters
278 Parameters
263 ----------
279 ----------
264 css_dict : dict
280 css_dict : dict
265 CSS key/value pairs to apply
281 CSS key/value pairs to apply
266 key: unicode
282 key: unicode
267 CSS key
283 CSS key
268 value
284 value
269 CSS value
285 CSS value
270 selector: unicode (optional)
286 selector: unicode (optional)
271 JQuery selector to use to apply the CSS key/value.
287 JQuery selector to use to apply the CSS key/value.
272 """
288 """
273 selector = kwargs.get('selector', '')
289 selector = kwargs.get('selector', '')
274
290
275 # Signature 1: set_css(css_dict, [selector=''])
291 # Signature 1: set_css(css_dict, [selector=''])
276 if len(args) == 1:
292 if len(args) == 1:
277 if isinstance(args[0], dict):
293 if isinstance(args[0], dict):
278 for (key, value) in args[0].items():
294 for (key, value) in args[0].items():
279 self.set_css(key, value, selector=selector)
295 self.set_css(key, value, selector=selector)
280 else:
296 else:
281 raise Exception('css_dict must be a dict.')
297 raise Exception('css_dict must be a dict.')
282
298
283 # Signature 2: set_css(key, value, [selector=''])
299 # Signature 2: set_css(key, value, [selector=''])
284 elif len(args) == 2 or len(args) == 3:
300 elif len(args) == 2 or len(args) == 3:
285
301
286 # Selector can be a positional arg if it's the 3rd value
302 # Selector can be a positional arg if it's the 3rd value
287 if len(args) == 3:
303 if len(args) == 3:
288 selector = args[2]
304 selector = args[2]
289 if selector not in self._css:
305 if selector not in self._css:
290 self._css[selector] = {}
306 self._css[selector] = {}
291
307
292 # Only update the property if it has changed.
308 # Only update the property if it has changed.
293 key = args[0]
309 key = args[0]
294 value = args[1]
310 value = args[1]
295 if not (key in self._css[selector] and value in self._css[selector][key]):
311 if not (key in self._css[selector] and value in self._css[selector][key]):
296 self._css[selector][key] = value
312 self._css[selector][key] = value
297 self.send_state('_css') # Send new state to client.
313 self.send_state('_css') # Send new state to client.
298 else:
314 else:
299 raise Exception('set_css only accepts 1-3 arguments')
315 raise Exception('set_css only accepts 1-3 arguments')
300
316
301
317
302 def add_class(self, class_name, selector=""):
318 def add_class(self, class_name, selector=""):
303 """Add class[es] to a DOM element
319 """Add class[es] to a DOM element
304
320
305 Parameters
321 Parameters
306 ----------
322 ----------
307 class_name: unicode
323 class_name: unicode
308 Class name(s) to add to the DOM element(s). Multiple class names
324 Class name(s) to add to the DOM element(s). Multiple class names
309 must be space separated.
325 must be space separated.
310 selector: unicode (optional)
326 selector: unicode (optional)
311 JQuery selector to select the DOM element(s) that the class(es) will
327 JQuery selector to select the DOM element(s) that the class(es) will
312 be added to.
328 be added to.
313 """
329 """
314 if self._comm is not None:
330 if self._comm is not None:
315 self._comm.send({"method": "add_class",
331 self._comm.send({"method": "add_class",
316 "class_list": class_name,
332 "class_list": class_name,
317 "selector": selector})
333 "selector": selector})
318
334
319
335
320 def remove_class(self, class_name, selector=""):
336 def remove_class(self, class_name, selector=""):
321 """Remove class[es] from a DOM element
337 """Remove class[es] from a DOM element
322
338
323 Parameters
339 Parameters
324 ----------
340 ----------
325 class_name: unicode
341 class_name: unicode
326 Class name(s) to remove from the DOM element(s). Multiple class
342 Class name(s) to remove from the DOM element(s). Multiple class
327 names must be space separated.
343 names must be space separated.
328 selector: unicode (optional)
344 selector: unicode (optional)
329 JQuery selector to select the DOM element(s) that the class(es) will
345 JQuery selector to select the DOM element(s) that the class(es) will
330 be removed from.
346 be removed from.
331 """
347 """
332 if self._comm is not None:
348 if self._comm is not None:
333 self._comm.send({"method": "remove_class",
349 self._comm.send({"method": "remove_class",
334 "class_list": class_name,
350 "class_list": class_name,
335 "selector": selector})
351 "selector": selector})
336
352
337
353
338 def send(self, content):
354 def send(self, content):
339 """Sends a custom msg to the widget model in the front-end.
355 """Sends a custom msg to the widget model in the front-end.
340
356
341 Parameters
357 Parameters
342 ----------
358 ----------
343 content : dict
359 content : dict
344 Content of the message to send.
360 Content of the message to send.
345 """
361 """
346 if self._comm is not None:
362 if self._comm is not None:
347 self._comm.send({"method": "custom",
363 self._comm.send({"method": "custom",
348 "custom_content": content})
364 "custom_content": content})
349
365
350
366
351 def on_msg(self, callback, remove=False):
367 def on_msg(self, callback, remove=False):
352 """Register a callback for when a custom msg is recieved from the front-end
368 """Register a callback for when a custom msg is recieved from the front-end
353
369
354 Parameters
370 Parameters
355 ----------
371 ----------
356 callback: method handler
372 callback: method handler
357 Can have a signature of:
373 Can have a signature of:
358 - callback(content)
374 - callback(content)
359 - callback(sender, content)
375 - callback(sender, content)
360 remove: bool
376 remove: bool
361 True if the callback should be unregistered."""
377 True if the callback should be unregistered."""
362 if remove and callback in self._msg_callbacks:
378 if remove and callback in self._msg_callbacks:
363 self._msg_callbacks.remove(callback)
379 self._msg_callbacks.remove(callback)
364 elif not remove and not callback in self._msg_callbacks:
380 elif not remove and not callback in self._msg_callbacks:
365 self._msg_callbacks.append(callback)
381 self._msg_callbacks.append(callback)
366
382
367
383
368 def on_displayed(self, callback, remove=False):
384 def on_displayed(self, callback, remove=False):
369 """Register a callback to be called when the widget has been displayed
385 """Register a callback to be called when the widget has been displayed
370
386
371 Parameters
387 Parameters
372 ----------
388 ----------
373 callback: method handler
389 callback: method handler
374 Can have a signature of:
390 Can have a signature of:
375 - callback()
391 - callback()
376 - callback(sender)
392 - callback(sender)
377 - callback(sender, view_name)
393 - callback(sender, view_name)
378 - callback(sender, view_name, parent)
394 - callback(sender, view_name, parent)
379 remove: bool
395 remove: bool
380 True if the callback should be unregistered."""
396 True if the callback should be unregistered."""
381 if remove and callback in self._display_callbacks:
397 if remove and callback in self._display_callbacks:
382 self._display_callbacks.remove(callback)
398 self._display_callbacks.remove(callback)
383 elif not remove and not callback in self._display_callbacks:
399 elif not remove and not callback in self._display_callbacks:
384 self._display_callbacks.append(callback)
400 self._display_callbacks.append(callback)
385
401
386
402
387 # Support methods
403 # Support methods
388 def _repr_widget_(self, view_name=None):
404 def _repr_widget_(self, view_name=None):
389 """Function that is called when `IPython.display.display` is called on
405 """Function that is called when `IPython.display.display` is called on
390 the widget.
406 the widget.
391
407
392 Parameters
408 Parameters
393 ----------
409 ----------
394 view_name: unicode (optional)
410 view_name: unicode (optional)
395 View to display in the frontend. Overrides default_view_name."""
411 View to display in the frontend. Overrides default_view_name."""
396 if not view_name:
412 if not view_name:
397 view_name = self.default_view_name
413 view_name = self.default_view_name
398
414
399 # Create a comm.
415 # Create a comm.
400 if self._comm is None:
416 if self._comm is None:
401 self._comm = Comm(target_name=self.target_name)
417 self._comm = Comm(target_name=self.target_name)
402 self._comm.on_msg(self._handle_msg)
418 self._comm.on_msg(self._handle_msg)
403 self._comm.on_close(self._handle_close)
419 self._comm.on_close(self._handle_close)
404
420
405 # Make sure model is syncronized
421 # Make sure model is syncronized
406 self.send_state()
422 self.send_state()
407
423
408 # Show view.
424 # Show view.
409 if self.parent is None or self.parent._comm is None:
425 if self.parent is None or self.parent._comm is None:
410 self._comm.send({"method": "display", "view_name": view_name})
426 self._comm.send({"method": "display", "view_name": view_name})
411 self._handle_displayed(view_name)
427 self._handle_displayed(view_name)
412 else:
428 else:
413 self._comm.send({"method": "display",
429 self._comm.send({"method": "display",
414 "view_name": view_name,
430 "view_name": view_name,
415 "parent": self.parent._comm.comm_id})
431 "parent": self.parent._comm.comm_id})
416 self._handle_displayed(view_name, self.parent)
432 self._handle_displayed(view_name, self.parent)
417 self._displayed = True
433 self._displayed = True
418
434
419 # Now display children if any.
435 # Now display children if any.
420 for child in self._children:
436 for child in self._children:
421 if child != self:
437 if child != self:
422 child._repr_widget_()
438 child._repr_widget_()
423 return None
439 return None
General Comments 0
You need to be logged in to leave comments. Login now