##// END OF EJS Templates
Reorganized attrs in widget.py
Jonathan Frederic -
Show More
@@ -26,7 +26,9 b' from IPython.utils.py3compat import string_types'
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 class Widget(LoggingConfigurable):
27 class Widget(LoggingConfigurable):
28
28
29 # Shared declarations (Class level)
29 #-------------------------------------------------------------------------
30 # Class attributes
31 #-------------------------------------------------------------------------
30 widget_construction_callback = None
32 widget_construction_callback = None
31 widgets = {}
33 widgets = {}
32
34
@@ -42,42 +44,18 b' class Widget(LoggingConfigurable):'
42 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
44 if Widget.widget_construction_callback is not None and callable(Widget.widget_construction_callback):
43 Widget.widget_construction_callback(widget)
45 Widget.widget_construction_callback(widget)
44
46
45 # Public declarations (Instance level)
47 #-------------------------------------------------------------------------
48 # Traits
49 #-------------------------------------------------------------------------
46 model_name = Unicode('WidgetModel', help="""Name of the backbone model
50 model_name = Unicode('WidgetModel', help="""Name of the backbone model
47 registered in the front-end to create and sync this widget with.""")
51 registered in the front-end to create and sync this widget with.""")
48 view_name = Unicode(help="""Default view registered in the front-end
52 view_name = Unicode(help="""Default view registered in the front-end
49 to use to represent the widget.""", sync=True)
53 to use to represent the widget.""", sync=True)
50
51 @contextmanager
52 def property_lock(self, key, value):
53 """Lock a property-value pair.
54
55 NOTE: This, in addition to the single lock for all state changes, is
56 flawed. In the future we may want to look into buffering state changes
57 back to the front-end."""
58 self._property_lock = (key, value)
59 try:
60 yield
61 finally:
62 self._property_lock = (None, None)
63
64 def should_send_property(self, key, value):
65 """Check the property lock (property_lock)"""
66 return key != self._property_lock[0] or \
67 value != self._property_lock[1]
68
69 @property
70 def keys(self):
71 if self._keys is None:
72 self._keys = []
73 for trait_name in self.trait_names():
74 if self.trait_metadata(trait_name, 'sync'):
75 self._keys.append(trait_name)
76 return self._keys
77
78 # Private/protected declarations
79 _comm = Instance('IPython.kernel.comm.Comm')
54 _comm = Instance('IPython.kernel.comm.Comm')
80
55
56 #-------------------------------------------------------------------------
57 # (Con/de)structor
58 #-------------------------------------------------------------------------
81 def __init__(self, **kwargs):
59 def __init__(self, **kwargs):
82 """Public constructor"""
60 """Public constructor"""
83 self.closed = False
61 self.closed = False
@@ -94,21 +72,17 b' class Widget(LoggingConfigurable):'
94 """Object disposal"""
72 """Object disposal"""
95 self.close()
73 self.close()
96
74
97 def close(self):
75 #-------------------------------------------------------------------------
98 """Close method.
76 # Properties
99
77 #-------------------------------------------------------------------------
100 Closes the widget which closes the underlying comm.
78 @property
101 When the comm is closed, all of the widget views are automatically
79 def keys(self):
102 removed from the front-end."""
80 if self._keys is None:
103 if not self.closed:
81 self._keys = []
104 self._comm.close()
82 for trait_name in self.trait_names():
105 self._close()
83 if self.trait_metadata(trait_name, 'sync'):
106
84 self._keys.append(trait_name)
107 def _close(self):
85 return self._keys
108 """Unsafe close"""
109 del Widget.widgets[self.model_id]
110 self._comm = None
111 self.closed = True
112
86
113 @property
87 @property
114 def comm(self):
88 def comm(self):
@@ -127,50 +101,19 b' class Widget(LoggingConfigurable):'
127 def model_id(self):
101 def model_id(self):
128 return self.comm.comm_id
102 return self.comm.comm_id
129
103
130 # Event handlers
104 #-------------------------------------------------------------------------
131 def _handle_msg(self, msg):
105 # Methods
132 """Called when a msg is received from the front-end"""
106 #-------------------------------------------------------------------------
133 data = msg['content']['data']
107 def close(self):
134 method = data['method']
108 """Close method.
135 if not method in ['backbone', 'custom']:
136 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
137
138 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
139 if method == 'backbone' and 'sync_data' in data:
140 sync_data = data['sync_data']
141 self._handle_receive_state(sync_data) # handles all methods
142
143 # Handle a custom msg from the front-end
144 elif method == 'custom':
145 if 'custom_content' in data:
146 self._handle_custom_msg(data['custom_content'])
147
148 def _handle_receive_state(self, sync_data):
149 """Called when a state is received from the front-end."""
150 for name in self.keys:
151 if name in sync_data:
152 value = self._unpack_widgets(sync_data[name])
153 with self.property_lock(name, value):
154 setattr(self, name, value)
155
156 def _handle_custom_msg(self, content):
157 """Called when a custom msg is received."""
158 for handler in self._msg_callbacks:
159 handler(self, content)
160
161 def _handle_property_changed(self, name, old, new):
162 """Called when a property has been changed."""
163 # Make sure this isn't information that the front-end just sent us.
164 if self.should_send_property(name, new):
165 # Send new state to front-end
166 self.send_state(key=name)
167
109
168 def _handle_displayed(self, **kwargs):
110 Closes the widget which closes the underlying comm.
169 """Called when a view has been displayed for this widget instance"""
111 When the comm is closed, all of the widget views are automatically
170 for handler in self._display_callbacks:
112 removed from the front-end."""
171 handler(self, **kwargs)
113 if not self.closed:
114 self._comm.close()
115 self._close()
172
116
173 # Public methods
174 def send_state(self, key=None):
117 def send_state(self, key=None):
175 """Sends the widget state, or a piece of it, to the front-end.
118 """Sends the widget state, or a piece of it, to the front-end.
176
119
@@ -195,49 +138,6 b' class Widget(LoggingConfigurable):'
195 keys = self.keys if key is None else [key]
138 keys = self.keys if key is None else [key]
196 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
139 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
197
140
198 def _pack_widgets(self, values):
199 """Recursively converts all widget instances to model id strings.
200
201 Children widgets will be stored and transmitted to the front-end by
202 their model ids."""
203 if isinstance(values, dict):
204 new_dict = {}
205 for key, value in values.items():
206 new_dict[key] = self._pack_widgets(value)
207 return new_dict
208 elif isinstance(values, list):
209 new_list = []
210 for value in values:
211 new_list.append(self._pack_widgets(value))
212 return new_list
213 elif isinstance(values, Widget):
214 return values.model_id
215 else:
216 return values
217
218 def _unpack_widgets(self, values):
219 """Recursively converts all model id strings to widget instances.
220
221 Children widgets will be stored and transmitted to the front-end by
222 their model ids."""
223 if isinstance(values, dict):
224 new_dict = {}
225 for key, values in values.items():
226 new_dict[key] = self._unpack_widgets(values[key])
227 return new_dict
228 elif isinstance(values, list):
229 new_list = []
230 for value in values:
231 new_list.append(self._unpack_widgets(value))
232 return new_list
233 elif isinstance(values, string_types):
234 if values in Widget.widgets:
235 return Widget.widgets[values]
236 else:
237 return values
238 else:
239 return values
240
241 def send(self, content):
141 def send(self, content):
242 """Sends a custom msg to the widget model in the front-end.
142 """Sends a custom msg to the widget model in the front-end.
243
143
@@ -300,7 +200,119 b' class Widget(LoggingConfigurable):'
300 else:
200 else:
301 raise Exception('Callback must be callable.')
201 raise Exception('Callback must be callable.')
302
202
203 #-------------------------------------------------------------------------
303 # Support methods
204 # Support methods
205 #-------------------------------------------------------------------------
206 @contextmanager
207 def _property_lock(self, key, value):
208 """Lock a property-value pair.
209
210 NOTE: This, in addition to the single lock for all state changes, is
211 flawed. In the future we may want to look into buffering state changes
212 back to the front-end."""
213 self._property_lock = (key, value)
214 try:
215 yield
216 finally:
217 self._property_lock = (None, None)
218
219 def _should_send_property(self, key, value):
220 """Check the property lock (property_lock)"""
221 return key != self._property_lock[0] or \
222 value != self._property_lock[1]
223
224 def _close(self):
225 """Unsafe close"""
226 del Widget.widgets[self.model_id]
227 self._comm = None
228 self.closed = True
229
230 # Event handlers
231 def _handle_msg(self, msg):
232 """Called when a msg is received from the front-end"""
233 data = msg['content']['data']
234 method = data['method']
235 if not method in ['backbone', 'custom']:
236 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
237
238 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
239 if method == 'backbone' and 'sync_data' in data:
240 sync_data = data['sync_data']
241 self._handle_receive_state(sync_data) # handles all methods
242
243 # Handle a custom msg from the front-end
244 elif method == 'custom':
245 if 'custom_content' in data:
246 self._handle_custom_msg(data['custom_content'])
247
248 def _handle_receive_state(self, sync_data):
249 """Called when a state is received from the front-end."""
250 for name in self.keys:
251 if name in sync_data:
252 value = self._unpack_widgets(sync_data[name])
253 with self._property_lock(name, value):
254 setattr(self, name, value)
255
256 def _handle_custom_msg(self, content):
257 """Called when a custom msg is received."""
258 for handler in self._msg_callbacks:
259 handler(self, content)
260
261 def _handle_property_changed(self, name, old, new):
262 """Called when a property has been changed."""
263 # Make sure this isn't information that the front-end just sent us.
264 if self._should_send_property(name, new):
265 # Send new state to front-end
266 self.send_state(key=name)
267
268 def _handle_displayed(self, **kwargs):
269 """Called when a view has been displayed for this widget instance"""
270 for handler in self._display_callbacks:
271 handler(self, **kwargs)
272
273 def _pack_widgets(self, values):
274 """Recursively converts all widget instances to model id strings.
275
276 Children widgets will be stored and transmitted to the front-end by
277 their model ids."""
278 if isinstance(values, dict):
279 new_dict = {}
280 for key, value in values.items():
281 new_dict[key] = self._pack_widgets(value)
282 return new_dict
283 elif isinstance(values, list):
284 new_list = []
285 for value in values:
286 new_list.append(self._pack_widgets(value))
287 return new_list
288 elif isinstance(values, Widget):
289 return values.model_id
290 else:
291 return values
292
293 def _unpack_widgets(self, values):
294 """Recursively converts all model id strings to widget instances.
295
296 Children widgets will be stored and transmitted to the front-end by
297 their model ids."""
298 if isinstance(values, dict):
299 new_dict = {}
300 for key, values in values.items():
301 new_dict[key] = self._unpack_widgets(values[key])
302 return new_dict
303 elif isinstance(values, list):
304 new_list = []
305 for value in values:
306 new_list.append(self._unpack_widgets(value))
307 return new_list
308 elif isinstance(values, string_types):
309 if values in Widget.widgets:
310 return Widget.widgets[values]
311 else:
312 return values
313 else:
314 return values
315
304 def _ipython_display_(self, **kwargs):
316 def _ipython_display_(self, **kwargs):
305 """Called when `IPython.display.display` is called on the widget."""
317 """Called when `IPython.display.display` is called on the widget."""
306 # Show view. By sending a display message, the comm is opened and the
318 # Show view. By sending a display message, the comm is opened and the
@@ -315,8 +327,6 b' class Widget(LoggingConfigurable):'
315
327
316 class DOMWidget(Widget):
328 class DOMWidget(Widget):
317 visible = Bool(True, help="Whether or not the widget is visible.", sync=True)
329 visible = Bool(True, help="Whether or not the widget is visible.", sync=True)
318
319 # Private/protected declarations
320 _css = Dict(sync=True) # Internal CSS property dict
330 _css = Dict(sync=True) # Internal CSS property dict
321
331
322 def get_css(self, key, selector=""):
332 def get_css(self, key, selector=""):
General Comments 0
You need to be logged in to leave comments. Login now