##// END OF EJS Templates
Address some more review comments...
Jonathan Frederic -
Show More
@@ -1,36 +1,36 b''
1 // Test the widget manager.
1 // Test the widget manager.
2 casper.notebook_test(function () {
2 casper.notebook_test(function () {
3 var index;
3 var index;
4 var slider = {};
4 var slider = {};
5
5
6 this.then(function () {
6 this.then(function () {
7
7
8 // Check if the WidgetManager class is defined.
8 // Check if the WidgetManager class is defined.
9 this.test.assert(this.evaluate(function() {
9 this.test.assert(this.evaluate(function() {
10 return IPython.WidgetManager !== undefined;
10 return IPython.WidgetManager !== undefined;
11 }), 'WidgetManager class is defined');
11 }), 'WidgetManager class is defined');
12
12
13 // Check if the widget manager has been instantiated.
13 // Check if the widget manager has been instantiated.
14 this.test.assert(this.evaluate(function() {
14 this.test.assert(this.evaluate(function() {
15 return IPython.notebook.kernel.widget_manager !== undefined;
15 return IPython.notebook.kernel.widget_manager !== undefined;
16 }), 'Notebook widget manager instantiated');
16 }), 'Notebook widget manager instantiated');
17
17
18 // Try creating a widget from Javascript.
18 // Try creating a widget from Javascript.
19 slider.id = this.evaluate(function() {
19 slider.id = this.evaluate(function() {
20 var slider = IPython.notebook.kernel.widget_manager.create_model({
20 var slider = IPython.notebook.kernel.widget_manager.create_model({
21 model_name: 'WidgetModel',
21 model_name: 'WidgetModel',
22 target_name: 'IPython.html.widgets.widget_int.IntSlider',
22 target_name: 'IPython.html.widgets.widget_int.IntSlider',
23 init_state_callback: function(model) { console.log('Create success!', model); }});
23 init_state_callback: function(model) { console.log('Create success!', model); }});
24 return slider.id;
24 return slider.id;
25 });
25 });
26 });
26 });
27
27
28 index = this.append_cell(
28 index = this.append_cell(
29 'from IPython.html.widgets import Widget\n' +
29 'from IPython.html.widgets import Widget\n' +
30 'widget = Widget.widgets.values()[0]\n' +
30 'widget = list(Widget.widgets.values())[0]\n' +
31 'print(widget.model_id)');
31 'print(widget.model_id)');
32 this.execute_cell_then(index, function(index) {
32 this.execute_cell_then(index, function(index) {
33 var output = this.get_output_cell(index).text.trim();
33 var output = this.get_output_cell(index).text.trim();
34 this.test.assertEquals(output, slider.id, "Widget created from the front-end.");
34 this.test.assertEquals(output, slider.id, "Widget created from the front-end.");
35 });
35 });
36 });
36 });
@@ -1,469 +1,467 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
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 contextlib import contextmanager
15 from contextlib import contextmanager
16 import collections
16 import collections
17
17
18 from IPython.core.getipython import get_ipython
18 from IPython.core.getipython import get_ipython
19 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
20 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
21 from IPython.utils.importstring import import_item
21 from IPython.utils.importstring import import_item
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
24 from IPython.utils.py3compat import string_types
24 from IPython.utils.py3compat import string_types
25
25
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 # Classes
27 # Classes
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 class CallbackDispatcher(LoggingConfigurable):
29 class CallbackDispatcher(LoggingConfigurable):
30 """A structure for registering and running callbacks"""
30 """A structure for registering and running callbacks"""
31 callbacks = List()
31 callbacks = List()
32
32
33 def __call__(self, *args, **kwargs):
33 def __call__(self, *args, **kwargs):
34 """Call all of the registered callbacks."""
34 """Call all of the registered callbacks."""
35 value = None
35 value = None
36 for callback in self.callbacks:
36 for callback in self.callbacks:
37 try:
37 try:
38 local_value = callback(*args, **kwargs)
38 local_value = callback(*args, **kwargs)
39 except Exception as e:
39 except Exception as e:
40 ip = get_ipython()
40 ip = get_ipython()
41 if ip is None:
41 if ip is None:
42 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
42 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 else:
43 else:
44 ip.showtraceback()
44 ip.showtraceback()
45 else:
45 else:
46 value = local_value if local_value is not None else value
46 value = local_value if local_value is not None else value
47 return value
47 return value
48
48
49 def register_callback(self, callback, remove=False):
49 def register_callback(self, callback, remove=False):
50 """(Un)Register a callback
50 """(Un)Register a callback
51
51
52 Parameters
52 Parameters
53 ----------
53 ----------
54 callback: method handle
54 callback: method handle
55 Method to be registered or unregistered.
55 Method to be registered or unregistered.
56 remove=False: bool
56 remove=False: bool
57 Whether to unregister the callback."""
57 Whether to unregister the callback."""
58
58
59 # (Un)Register the callback.
59 # (Un)Register the callback.
60 if remove and callback in self.callbacks:
60 if remove and callback in self.callbacks:
61 self.callbacks.remove(callback)
61 self.callbacks.remove(callback)
62 elif not remove and callback not in self.callbacks:
62 elif not remove and callback not in self.callbacks:
63 self.callbacks.append(callback)
63 self.callbacks.append(callback)
64
64
65 def _show_traceback(method):
65 def _show_traceback(method):
66 """decorator for showing tracebacks in IPython"""
66 """decorator for showing tracebacks in IPython"""
67 def m(self, *args, **kwargs):
67 def m(self, *args, **kwargs):
68 try:
68 try:
69 return(method(self, *args, **kwargs))
69 return(method(self, *args, **kwargs))
70 except Exception as e:
70 except Exception as e:
71 ip = get_ipython()
71 ip = get_ipython()
72 if ip is None:
72 if ip is None:
73 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
73 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 else:
74 else:
75 ip.showtraceback()
75 ip.showtraceback()
76 return m
76 return m
77
77
78 class Widget(LoggingConfigurable):
78 class Widget(LoggingConfigurable):
79 #-------------------------------------------------------------------------
79 #-------------------------------------------------------------------------
80 # Class attributes
80 # Class attributes
81 #-------------------------------------------------------------------------
81 #-------------------------------------------------------------------------
82 _widget_construction_callback = None
82 _widget_construction_callback = None
83 widgets = {}
83 widgets = {}
84
84
85 @staticmethod
85 @staticmethod
86 def on_widget_constructed(callback):
86 def on_widget_constructed(callback):
87 """Registers a callback to be called when a widget is constructed.
87 """Registers a callback to be called when a widget is constructed.
88
88
89 The callback must have the following signature:
89 The callback must have the following signature:
90 callback(widget)"""
90 callback(widget)"""
91 Widget._widget_construction_callback = callback
91 Widget._widget_construction_callback = callback
92
92
93 @staticmethod
93 @staticmethod
94 def _call_widget_constructed(widget):
94 def _call_widget_constructed(widget):
95 """Static method, called when a widget is constructed."""
95 """Static method, called when a widget is constructed."""
96 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
96 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
97 Widget._widget_construction_callback(widget)
97 Widget._widget_construction_callback(widget)
98
98
99 @staticmethod
99 @staticmethod
100 def handle_comm_opened(comm, msg):
100 def handle_comm_opened(comm, msg):
101 """Static method, called when a widget is constructed."""
101 """Static method, called when a widget is constructed."""
102 target_name = msg['content']['data']['target_name']
102 target_name = msg['content']['data']['target_name']
103 widget_class = import_item(target_name)
103 widget_class = import_item(target_name)
104 widget = widget_class(open_comm=False)
104 widget = widget_class(comm=comm)
105 widget.comm = comm
106
105
107
106
108 #-------------------------------------------------------------------------
107 #-------------------------------------------------------------------------
109 # Traits
108 # Traits
110 #-------------------------------------------------------------------------
109 #-------------------------------------------------------------------------
111 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
110 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
112 in which to find _model_name. If empty, look in the global registry.""")
111 in which to find _model_name. If empty, look in the global registry.""")
113 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
112 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
114 registered in the front-end to create and sync this widget with.""")
113 registered in the front-end to create and sync this widget with.""")
115 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
114 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
116 If empty, look in the global registry.""", sync=True)
115 If empty, look in the global registry.""", sync=True)
117 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
116 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
118 to use to represent the widget.""", sync=True)
117 to use to represent the widget.""", sync=True)
119 comm = Instance('IPython.kernel.comm.Comm')
118 comm = Instance('IPython.kernel.comm.Comm')
120
119
121 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
120 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
122 front-end can send before receiving an idle msg from the back-end.""")
121 front-end can send before receiving an idle msg from the back-end.""")
123
122
124 version = Int(0, sync=True, help="""Widget's version""")
123 version = Int(0, sync=True, help="""Widget's version""")
125 keys = List()
124 keys = List()
126 def _keys_default(self):
125 def _keys_default(self):
127 return [name for name in self.traits(sync=True)]
126 return [name for name in self.traits(sync=True)]
128
127
129 _property_lock = Tuple((None, None))
128 _property_lock = Tuple((None, None))
130 _send_state_lock = Int(0)
129 _send_state_lock = Int(0)
131 _states_to_send = Set(allow_none=False)
130 _states_to_send = Set(allow_none=False)
132 _display_callbacks = Instance(CallbackDispatcher, ())
131 _display_callbacks = Instance(CallbackDispatcher, ())
133 _msg_callbacks = Instance(CallbackDispatcher, ())
132 _msg_callbacks = Instance(CallbackDispatcher, ())
134
133
135 #-------------------------------------------------------------------------
134 #-------------------------------------------------------------------------
136 # (Con/de)structor
135 # (Con/de)structor
137 #-------------------------------------------------------------------------
136 #-------------------------------------------------------------------------
138 def __init__(self, open_comm=True, **kwargs):
137 def __init__(self, open_comm=True, **kwargs):
139 """Public constructor"""
138 """Public constructor"""
140 self._model_id = kwargs.pop('model_id', None)
139 self._model_id = kwargs.pop('model_id', None)
141 super(Widget, self).__init__(**kwargs)
140 super(Widget, self).__init__(**kwargs)
142
141
143 Widget._call_widget_constructed(self)
142 Widget._call_widget_constructed(self)
144 if open_comm:
143 self.open()
145 self.open()
146
144
147 def __del__(self):
145 def __del__(self):
148 """Object disposal"""
146 """Object disposal"""
149 self.close()
147 self.close()
150
148
151 #-------------------------------------------------------------------------
149 #-------------------------------------------------------------------------
152 # Properties
150 # Properties
153 #-------------------------------------------------------------------------
151 #-------------------------------------------------------------------------
154
152
155 def open(self):
153 def open(self):
156 """Open a comm to the frontend if one isn't already open."""
154 """Open a comm to the frontend if one isn't already open."""
157 if self.comm is None:
155 if self.comm is None:
158 args = dict(target_name='ipython.widget',
156 args = dict(target_name='ipython.widget',
159 data={'model_name': self._model_name,
157 data={'model_name': self._model_name,
160 'model_module': self._model_module})
158 'model_module': self._model_module})
161 if self._model_id is not None:
159 if self._model_id is not None:
162 args['comm_id'] = self._model_id
160 args['comm_id'] = self._model_id
163 self.comm = Comm(**args)
161 self.comm = Comm(**args)
164
162
165 def _comm_changed(self, name, new):
163 def _comm_changed(self, name, new):
166 """Called when the comm is changed."""
164 """Called when the comm is changed."""
167 self.comm = new
165 self.comm = new
168 self._model_id = self.model_id
166 self._model_id = self.model_id
169
167
170 self.comm.on_msg(self._handle_msg)
168 self.comm.on_msg(self._handle_msg)
171 Widget.widgets[self.model_id] = self
169 Widget.widgets[self.model_id] = self
172
170
173 # first update
171 # first update
174 self.send_state()
172 self.send_state()
175
173
176 @property
174 @property
177 def model_id(self):
175 def model_id(self):
178 """Gets the model id of this widget.
176 """Gets the model id of this widget.
179
177
180 If a Comm doesn't exist yet, a Comm will be created automagically."""
178 If a Comm doesn't exist yet, a Comm will be created automagically."""
181 return self.comm.comm_id
179 return self.comm.comm_id
182
180
183 #-------------------------------------------------------------------------
181 #-------------------------------------------------------------------------
184 # Methods
182 # Methods
185 #-------------------------------------------------------------------------
183 #-------------------------------------------------------------------------
186
184
187 def close(self):
185 def close(self):
188 """Close method.
186 """Close method.
189
187
190 Closes the underlying comm.
188 Closes the underlying comm.
191 When the comm is closed, all of the widget views are automatically
189 When the comm is closed, all of the widget views are automatically
192 removed from the front-end."""
190 removed from the front-end."""
193 if self.comm is not None:
191 if self.comm is not None:
194 Widget.widgets.pop(self.model_id, None)
192 Widget.widgets.pop(self.model_id, None)
195 self.comm.close()
193 self.comm.close()
196 self.comm = None
194 self.comm = None
197
195
198 def send_state(self, key=None):
196 def send_state(self, key=None):
199 """Sends the widget state, or a piece of it, to the front-end.
197 """Sends the widget state, or a piece of it, to the front-end.
200
198
201 Parameters
199 Parameters
202 ----------
200 ----------
203 key : unicode, or iterable (optional)
201 key : unicode, or iterable (optional)
204 A single property's name or iterable of property names to sync with the front-end.
202 A single property's name or iterable of property names to sync with the front-end.
205 """
203 """
206 self._send({
204 self._send({
207 "method" : "update",
205 "method" : "update",
208 "state" : self.get_state(key=key)
206 "state" : self.get_state(key=key)
209 })
207 })
210
208
211 def get_state(self, key=None):
209 def get_state(self, key=None):
212 """Gets the widget state, or a piece of it.
210 """Gets the widget state, or a piece of it.
213
211
214 Parameters
212 Parameters
215 ----------
213 ----------
216 key : unicode or iterable (optional)
214 key : unicode or iterable (optional)
217 A single property's name or iterable of property names to get.
215 A single property's name or iterable of property names to get.
218 """
216 """
219 if key is None:
217 if key is None:
220 keys = self.keys
218 keys = self.keys
221 elif isinstance(key, string_types):
219 elif isinstance(key, string_types):
222 keys = [key]
220 keys = [key]
223 elif isinstance(key, collections.Iterable):
221 elif isinstance(key, collections.Iterable):
224 keys = key
222 keys = key
225 else:
223 else:
226 raise ValueError("key must be a string, an iterable of keys, or None")
224 raise ValueError("key must be a string, an iterable of keys, or None")
227 state = {}
225 state = {}
228 for k in keys:
226 for k in keys:
229 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
227 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
230 value = getattr(self, k)
228 value = getattr(self, k)
231 state[k] = f(value)
229 state[k] = f(value)
232 return state
230 return state
233
231
234 def set_state(self, sync_data):
232 def set_state(self, sync_data):
235 """Called when a state is received from the front-end."""
233 """Called when a state is received from the front-end."""
236 for name in self.keys:
234 for name in self.keys:
237 if name in sync_data:
235 if name in sync_data:
238 json_value = sync_data[name]
236 json_value = sync_data[name]
239 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
237 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
240 with self._lock_property(name, json_value):
238 with self._lock_property(name, json_value):
241 setattr(self, name, from_json(json_value))
239 setattr(self, name, from_json(json_value))
242
240
243 def send(self, content):
241 def send(self, content):
244 """Sends a custom msg to the widget model in the front-end.
242 """Sends a custom msg to the widget model in the front-end.
245
243
246 Parameters
244 Parameters
247 ----------
245 ----------
248 content : dict
246 content : dict
249 Content of the message to send.
247 Content of the message to send.
250 """
248 """
251 self._send({"method": "custom", "content": content})
249 self._send({"method": "custom", "content": content})
252
250
253 def on_msg(self, callback, remove=False):
251 def on_msg(self, callback, remove=False):
254 """(Un)Register a custom msg receive callback.
252 """(Un)Register a custom msg receive callback.
255
253
256 Parameters
254 Parameters
257 ----------
255 ----------
258 callback: callable
256 callback: callable
259 callback will be passed two arguments when a message arrives::
257 callback will be passed two arguments when a message arrives::
260
258
261 callback(widget, content)
259 callback(widget, content)
262
260
263 remove: bool
261 remove: bool
264 True if the callback should be unregistered."""
262 True if the callback should be unregistered."""
265 self._msg_callbacks.register_callback(callback, remove=remove)
263 self._msg_callbacks.register_callback(callback, remove=remove)
266
264
267 def on_displayed(self, callback, remove=False):
265 def on_displayed(self, callback, remove=False):
268 """(Un)Register a widget displayed callback.
266 """(Un)Register a widget displayed callback.
269
267
270 Parameters
268 Parameters
271 ----------
269 ----------
272 callback: method handler
270 callback: method handler
273 Must have a signature of::
271 Must have a signature of::
274
272
275 callback(widget, **kwargs)
273 callback(widget, **kwargs)
276
274
277 kwargs from display are passed through without modification.
275 kwargs from display are passed through without modification.
278 remove: bool
276 remove: bool
279 True if the callback should be unregistered."""
277 True if the callback should be unregistered."""
280 self._display_callbacks.register_callback(callback, remove=remove)
278 self._display_callbacks.register_callback(callback, remove=remove)
281
279
282 #-------------------------------------------------------------------------
280 #-------------------------------------------------------------------------
283 # Support methods
281 # Support methods
284 #-------------------------------------------------------------------------
282 #-------------------------------------------------------------------------
285 @contextmanager
283 @contextmanager
286 def _lock_property(self, key, value):
284 def _lock_property(self, key, value):
287 """Lock a property-value pair.
285 """Lock a property-value pair.
288
286
289 The value should be the JSON state of the property.
287 The value should be the JSON state of the property.
290
288
291 NOTE: This, in addition to the single lock for all state changes, is
289 NOTE: This, in addition to the single lock for all state changes, is
292 flawed. In the future we may want to look into buffering state changes
290 flawed. In the future we may want to look into buffering state changes
293 back to the front-end."""
291 back to the front-end."""
294 self._property_lock = (key, value)
292 self._property_lock = (key, value)
295 try:
293 try:
296 yield
294 yield
297 finally:
295 finally:
298 self._property_lock = (None, None)
296 self._property_lock = (None, None)
299
297
300 @contextmanager
298 @contextmanager
301 def hold_sync(self):
299 def hold_sync(self):
302 """Hold syncing any state until the context manager is released"""
300 """Hold syncing any state until the context manager is released"""
303 # We increment a value so that this can be nested. Syncing will happen when
301 # We increment a value so that this can be nested. Syncing will happen when
304 # all levels have been released.
302 # all levels have been released.
305 self._send_state_lock += 1
303 self._send_state_lock += 1
306 try:
304 try:
307 yield
305 yield
308 finally:
306 finally:
309 self._send_state_lock -=1
307 self._send_state_lock -=1
310 if self._send_state_lock == 0:
308 if self._send_state_lock == 0:
311 self.send_state(self._states_to_send)
309 self.send_state(self._states_to_send)
312 self._states_to_send.clear()
310 self._states_to_send.clear()
313
311
314 def _should_send_property(self, key, value):
312 def _should_send_property(self, key, value):
315 """Check the property lock (property_lock)"""
313 """Check the property lock (property_lock)"""
316 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
314 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
317 if (key == self._property_lock[0]
315 if (key == self._property_lock[0]
318 and to_json(value) == self._property_lock[1]):
316 and to_json(value) == self._property_lock[1]):
319 return False
317 return False
320 elif self._send_state_lock > 0:
318 elif self._send_state_lock > 0:
321 self._states_to_send.add(key)
319 self._states_to_send.add(key)
322 return False
320 return False
323 else:
321 else:
324 return True
322 return True
325
323
326 # Event handlers
324 # Event handlers
327 @_show_traceback
325 @_show_traceback
328 def _handle_msg(self, msg):
326 def _handle_msg(self, msg):
329 """Called when a msg is received from the front-end"""
327 """Called when a msg is received from the front-end"""
330 data = msg['content']['data']
328 data = msg['content']['data']
331 method = data['method']
329 method = data['method']
332 if not method in ['backbone', 'custom']:
330 if not method in ['backbone', 'custom']:
333 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
331 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
334
332
335 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
333 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
336 if method == 'backbone' and 'sync_data' in data:
334 if method == 'backbone' and 'sync_data' in data:
337 sync_data = data['sync_data']
335 sync_data = data['sync_data']
338 self.set_state(sync_data) # handles all methods
336 self.set_state(sync_data) # handles all methods
339
337
340 # Handle a custom msg from the front-end
338 # Handle a custom msg from the front-end
341 elif method == 'custom':
339 elif method == 'custom':
342 if 'content' in data:
340 if 'content' in data:
343 self._handle_custom_msg(data['content'])
341 self._handle_custom_msg(data['content'])
344
342
345 def _handle_custom_msg(self, content):
343 def _handle_custom_msg(self, content):
346 """Called when a custom msg is received."""
344 """Called when a custom msg is received."""
347 self._msg_callbacks(self, content)
345 self._msg_callbacks(self, content)
348
346
349 def _notify_trait(self, name, old_value, new_value):
347 def _notify_trait(self, name, old_value, new_value):
350 """Called when a property has been changed."""
348 """Called when a property has been changed."""
351 # Trigger default traitlet callback machinery. This allows any user
349 # Trigger default traitlet callback machinery. This allows any user
352 # registered validation to be processed prior to allowing the widget
350 # registered validation to be processed prior to allowing the widget
353 # machinery to handle the state.
351 # machinery to handle the state.
354 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
352 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
355
353
356 # Send the state after the user registered callbacks for trait changes
354 # Send the state after the user registered callbacks for trait changes
357 # have all fired (allows for user to validate values).
355 # have all fired (allows for user to validate values).
358 if self.comm is not None and name in self.keys:
356 if self.comm is not None and name in self.keys:
359 # Make sure this isn't information that the front-end just sent us.
357 # Make sure this isn't information that the front-end just sent us.
360 if self._should_send_property(name, new_value):
358 if self._should_send_property(name, new_value):
361 # Send new state to front-end
359 # Send new state to front-end
362 self.send_state(key=name)
360 self.send_state(key=name)
363
361
364 def _handle_displayed(self, **kwargs):
362 def _handle_displayed(self, **kwargs):
365 """Called when a view has been displayed for this widget instance"""
363 """Called when a view has been displayed for this widget instance"""
366 self._display_callbacks(self, **kwargs)
364 self._display_callbacks(self, **kwargs)
367
365
368 def _trait_to_json(self, x):
366 def _trait_to_json(self, x):
369 """Convert a trait value to json
367 """Convert a trait value to json
370
368
371 Traverse lists/tuples and dicts and serialize their values as well.
369 Traverse lists/tuples and dicts and serialize their values as well.
372 Replace any widgets with their model_id
370 Replace any widgets with their model_id
373 """
371 """
374 if isinstance(x, dict):
372 if isinstance(x, dict):
375 return {k: self._trait_to_json(v) for k, v in x.items()}
373 return {k: self._trait_to_json(v) for k, v in x.items()}
376 elif isinstance(x, (list, tuple)):
374 elif isinstance(x, (list, tuple)):
377 return [self._trait_to_json(v) for v in x]
375 return [self._trait_to_json(v) for v in x]
378 elif isinstance(x, Widget):
376 elif isinstance(x, Widget):
379 return "IPY_MODEL_" + x.model_id
377 return "IPY_MODEL_" + x.model_id
380 else:
378 else:
381 return x # Value must be JSON-able
379 return x # Value must be JSON-able
382
380
383 def _trait_from_json(self, x):
381 def _trait_from_json(self, x):
384 """Convert json values to objects
382 """Convert json values to objects
385
383
386 Replace any strings representing valid model id values to Widget references.
384 Replace any strings representing valid model id values to Widget references.
387 """
385 """
388 if isinstance(x, dict):
386 if isinstance(x, dict):
389 return {k: self._trait_from_json(v) for k, v in x.items()}
387 return {k: self._trait_from_json(v) for k, v in x.items()}
390 elif isinstance(x, (list, tuple)):
388 elif isinstance(x, (list, tuple)):
391 return [self._trait_from_json(v) for v in x]
389 return [self._trait_from_json(v) for v in x]
392 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
390 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
393 # we want to support having child widgets at any level in a hierarchy
391 # we want to support having child widgets at any level in a hierarchy
394 # trusting that a widget UUID will not appear out in the wild
392 # trusting that a widget UUID will not appear out in the wild
395 return Widget.widgets[x[10:]]
393 return Widget.widgets[x[10:]]
396 else:
394 else:
397 return x
395 return x
398
396
399 def _ipython_display_(self, **kwargs):
397 def _ipython_display_(self, **kwargs):
400 """Called when `IPython.display.display` is called on the widget."""
398 """Called when `IPython.display.display` is called on the widget."""
401 # Show view.
399 # Show view.
402 if self._view_name is not None:
400 if self._view_name is not None:
403 self._send({"method": "display"})
401 self._send({"method": "display"})
404 self._handle_displayed(**kwargs)
402 self._handle_displayed(**kwargs)
405
403
406 def _send(self, msg):
404 def _send(self, msg):
407 """Sends a message to the model in the front-end."""
405 """Sends a message to the model in the front-end."""
408 self.comm.send(msg)
406 self.comm.send(msg)
409
407
410
408
411 class DOMWidget(Widget):
409 class DOMWidget(Widget):
412 visible = Bool(True, help="Whether the widget is visible.", sync=True)
410 visible = Bool(True, help="Whether the widget is visible.", sync=True)
413 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
411 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
414 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
412 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
415
413
416 width = CUnicode(sync=True)
414 width = CUnicode(sync=True)
417 height = CUnicode(sync=True)
415 height = CUnicode(sync=True)
418 padding = CUnicode(sync=True)
416 padding = CUnicode(sync=True)
419 margin = CUnicode(sync=True)
417 margin = CUnicode(sync=True)
420
418
421 color = Unicode(sync=True)
419 color = Unicode(sync=True)
422 background_color = Unicode(sync=True)
420 background_color = Unicode(sync=True)
423 border_color = Unicode(sync=True)
421 border_color = Unicode(sync=True)
424
422
425 border_width = CUnicode(sync=True)
423 border_width = CUnicode(sync=True)
426 border_radius = CUnicode(sync=True)
424 border_radius = CUnicode(sync=True)
427 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
425 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
428 'none',
426 'none',
429 'hidden',
427 'hidden',
430 'dotted',
428 'dotted',
431 'dashed',
429 'dashed',
432 'solid',
430 'solid',
433 'double',
431 'double',
434 'groove',
432 'groove',
435 'ridge',
433 'ridge',
436 'inset',
434 'inset',
437 'outset',
435 'outset',
438 'initial',
436 'initial',
439 'inherit', ''],
437 'inherit', ''],
440 default_value='', sync=True)
438 default_value='', sync=True)
441
439
442 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
440 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
443 'normal',
441 'normal',
444 'italic',
442 'italic',
445 'oblique',
443 'oblique',
446 'initial',
444 'initial',
447 'inherit', ''],
445 'inherit', ''],
448 default_value='', sync=True)
446 default_value='', sync=True)
449 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
447 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
450 'normal',
448 'normal',
451 'bold',
449 'bold',
452 'bolder',
450 'bolder',
453 'lighter',
451 'lighter',
454 'initial',
452 'initial',
455 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
453 'inherit', ''] + [str(100 * (i+1)) for i in range(9)],
456 default_value='', sync=True)
454 default_value='', sync=True)
457 font_size = CUnicode(sync=True)
455 font_size = CUnicode(sync=True)
458 font_family = Unicode(sync=True)
456 font_family = Unicode(sync=True)
459
457
460 def __init__(self, *pargs, **kwargs):
458 def __init__(self, *pargs, **kwargs):
461 super(DOMWidget, self).__init__(*pargs, **kwargs)
459 super(DOMWidget, self).__init__(*pargs, **kwargs)
462
460
463 def _validate_border(name, old, new):
461 def _validate_border(name, old, new):
464 if new is not None and new != '':
462 if new is not None and new != '':
465 if name != 'border_width' and not self.border_width:
463 if name != 'border_width' and not self.border_width:
466 self.border_width = 1
464 self.border_width = 1
467 if name != 'border_style' and self.border_style == '':
465 if name != 'border_style' and self.border_style == '':
468 self.border_style = 'solid'
466 self.border_style = 'solid'
469 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
467 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
General Comments 0
You need to be logged in to leave comments. Login now