##// END OF EJS Templates
Changed children list to CTuple....
Jonathan Frederic -
Show More
@@ -1,443 +1,443 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
16
17 from IPython.core.getipython import get_ipython
17 from IPython.core.getipython import get_ipython
18 from IPython.kernel.comm import Comm
18 from IPython.kernel.comm import Comm
19 from IPython.config import LoggingConfigurable
19 from IPython.config import LoggingConfigurable
20 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int
20 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int
21 from IPython.utils.py3compat import string_types
21 from IPython.utils.py3compat import string_types
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Classes
24 # Classes
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26 class CallbackDispatcher(LoggingConfigurable):
26 class CallbackDispatcher(LoggingConfigurable):
27 """A structure for registering and running callbacks"""
27 """A structure for registering and running callbacks"""
28 callbacks = List()
28 callbacks = List()
29
29
30 def __call__(self, *args, **kwargs):
30 def __call__(self, *args, **kwargs):
31 """Call all of the registered callbacks."""
31 """Call all of the registered callbacks."""
32 value = None
32 value = None
33 for callback in self.callbacks:
33 for callback in self.callbacks:
34 try:
34 try:
35 local_value = callback(*args, **kwargs)
35 local_value = callback(*args, **kwargs)
36 except Exception as e:
36 except Exception as e:
37 ip = get_ipython()
37 ip = get_ipython()
38 if ip is None:
38 if ip is None:
39 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
39 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
40 else:
40 else:
41 ip.showtraceback()
41 ip.showtraceback()
42 else:
42 else:
43 value = local_value if local_value is not None else value
43 value = local_value if local_value is not None else value
44 return value
44 return value
45
45
46 def register_callback(self, callback, remove=False):
46 def register_callback(self, callback, remove=False):
47 """(Un)Register a callback
47 """(Un)Register a callback
48
48
49 Parameters
49 Parameters
50 ----------
50 ----------
51 callback: method handle
51 callback: method handle
52 Method to be registered or unregistered.
52 Method to be registered or unregistered.
53 remove=False: bool
53 remove=False: bool
54 Whether to unregister the callback."""
54 Whether to unregister the callback."""
55
55
56 # (Un)Register the callback.
56 # (Un)Register the callback.
57 if remove and callback in self.callbacks:
57 if remove and callback in self.callbacks:
58 self.callbacks.remove(callback)
58 self.callbacks.remove(callback)
59 elif not remove and callback not in self.callbacks:
59 elif not remove and callback not in self.callbacks:
60 self.callbacks.append(callback)
60 self.callbacks.append(callback)
61
61
62 def _show_traceback(method):
62 def _show_traceback(method):
63 """decorator for showing tracebacks in IPython"""
63 """decorator for showing tracebacks in IPython"""
64 def m(self, *args, **kwargs):
64 def m(self, *args, **kwargs):
65 try:
65 try:
66 return(method(self, *args, **kwargs))
66 return(method(self, *args, **kwargs))
67 except Exception as e:
67 except Exception as e:
68 ip = get_ipython()
68 ip = get_ipython()
69 if ip is None:
69 if ip is None:
70 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
70 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
71 else:
71 else:
72 ip.showtraceback()
72 ip.showtraceback()
73 return m
73 return m
74
74
75 class Widget(LoggingConfigurable):
75 class Widget(LoggingConfigurable):
76 #-------------------------------------------------------------------------
76 #-------------------------------------------------------------------------
77 # Class attributes
77 # Class attributes
78 #-------------------------------------------------------------------------
78 #-------------------------------------------------------------------------
79 _widget_construction_callback = None
79 _widget_construction_callback = None
80 widgets = {}
80 widgets = {}
81
81
82 @staticmethod
82 @staticmethod
83 def on_widget_constructed(callback):
83 def on_widget_constructed(callback):
84 """Registers a callback to be called when a widget is constructed.
84 """Registers a callback to be called when a widget is constructed.
85
85
86 The callback must have the following signature:
86 The callback must have the following signature:
87 callback(widget)"""
87 callback(widget)"""
88 Widget._widget_construction_callback = callback
88 Widget._widget_construction_callback = callback
89
89
90 @staticmethod
90 @staticmethod
91 def _call_widget_constructed(widget):
91 def _call_widget_constructed(widget):
92 """Static method, called when a widget is constructed."""
92 """Static method, called when a widget is constructed."""
93 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
93 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
94 Widget._widget_construction_callback(widget)
94 Widget._widget_construction_callback(widget)
95
95
96 #-------------------------------------------------------------------------
96 #-------------------------------------------------------------------------
97 # Traits
97 # Traits
98 #-------------------------------------------------------------------------
98 #-------------------------------------------------------------------------
99 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
99 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
100 registered in the front-end to create and sync this widget with.""")
100 registered in the front-end to create and sync this widget with.""")
101 _view_name = Unicode(help="""Default view registered in the front-end
101 _view_name = Unicode(help="""Default view registered in the front-end
102 to use to represent the widget.""", sync=True)
102 to use to represent the widget.""", sync=True)
103 _comm = Instance('IPython.kernel.comm.Comm')
103 _comm = Instance('IPython.kernel.comm.Comm')
104
104
105 closed = Bool(False)
105 closed = Bool(False)
106 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
106 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
107 front-end can send before receiving an idle msg from the back-end.""")
107 front-end can send before receiving an idle msg from the back-end.""")
108
108
109 keys = List()
109 keys = List()
110 def _keys_default(self):
110 def _keys_default(self):
111 return [name for name in self.traits(sync=True)]
111 return [name for name in self.traits(sync=True)]
112
112
113 _property_lock = Tuple((None, None))
113 _property_lock = Tuple((None, None))
114
114
115 _display_callbacks = Instance(CallbackDispatcher, ())
115 _display_callbacks = Instance(CallbackDispatcher, ())
116 _msg_callbacks = Instance(CallbackDispatcher, ())
116 _msg_callbacks = Instance(CallbackDispatcher, ())
117
117
118 #-------------------------------------------------------------------------
118 #-------------------------------------------------------------------------
119 # (Con/de)structor
119 # (Con/de)structor
120 #-------------------------------------------------------------------------
120 #-------------------------------------------------------------------------
121 def __init__(self, **kwargs):
121 def __init__(self, **kwargs):
122 """Public constructor"""
122 """Public constructor"""
123 super(Widget, self).__init__(**kwargs)
123 super(Widget, self).__init__(**kwargs)
124
124
125 self.on_trait_change(self._handle_property_changed, self.keys)
125 self.on_trait_change(self._handle_property_changed, self.keys)
126 Widget._call_widget_constructed(self)
126 Widget._call_widget_constructed(self)
127
127
128 def __del__(self):
128 def __del__(self):
129 """Object disposal"""
129 """Object disposal"""
130 self.close()
130 self.close()
131
131
132 #-------------------------------------------------------------------------
132 #-------------------------------------------------------------------------
133 # Properties
133 # Properties
134 #-------------------------------------------------------------------------
134 #-------------------------------------------------------------------------
135
135
136 @property
136 @property
137 def comm(self):
137 def comm(self):
138 """Gets the Comm associated with this widget.
138 """Gets the Comm associated with this widget.
139
139
140 If a Comm doesn't exist yet, a Comm will be created automagically."""
140 If a Comm doesn't exist yet, a Comm will be created automagically."""
141 if self._comm is None:
141 if self._comm is None:
142 # Create a comm.
142 # Create a comm.
143 self._comm = Comm(target_name=self._model_name)
143 self._comm = Comm(target_name=self._model_name)
144 self._comm.on_msg(self._handle_msg)
144 self._comm.on_msg(self._handle_msg)
145 self._comm.on_close(self._close)
145 self._comm.on_close(self._close)
146 Widget.widgets[self.model_id] = self
146 Widget.widgets[self.model_id] = self
147
147
148 # first update
148 # first update
149 self.send_state()
149 self.send_state()
150 return self._comm
150 return self._comm
151
151
152 @property
152 @property
153 def model_id(self):
153 def model_id(self):
154 """Gets the model id of this widget.
154 """Gets the model id of this widget.
155
155
156 If a Comm doesn't exist yet, a Comm will be created automagically."""
156 If a Comm doesn't exist yet, a Comm will be created automagically."""
157 return self.comm.comm_id
157 return self.comm.comm_id
158
158
159 #-------------------------------------------------------------------------
159 #-------------------------------------------------------------------------
160 # Methods
160 # Methods
161 #-------------------------------------------------------------------------
161 #-------------------------------------------------------------------------
162 def _close(self):
162 def _close(self):
163 """Private close - cleanup objects, registry entries"""
163 """Private close - cleanup objects, registry entries"""
164 del Widget.widgets[self.model_id]
164 del Widget.widgets[self.model_id]
165 self._comm = None
165 self._comm = None
166 self.closed = True
166 self.closed = True
167
167
168 def close(self):
168 def close(self):
169 """Close method.
169 """Close method.
170
170
171 Closes the widget which closes the underlying comm.
171 Closes the widget which closes the underlying comm.
172 When the comm is closed, all of the widget views are automatically
172 When the comm is closed, all of the widget views are automatically
173 removed from the front-end."""
173 removed from the front-end."""
174 if not self.closed:
174 if not self.closed:
175 self._comm.close()
175 self._comm.close()
176 self._close()
176 self._close()
177
177
178 def send_state(self, key=None):
178 def send_state(self, key=None):
179 """Sends the widget state, or a piece of it, to the front-end.
179 """Sends the widget state, or a piece of it, to the front-end.
180
180
181 Parameters
181 Parameters
182 ----------
182 ----------
183 key : unicode (optional)
183 key : unicode (optional)
184 A single property's name to sync with the front-end.
184 A single property's name to sync with the front-end.
185 """
185 """
186 self._send({
186 self._send({
187 "method" : "update",
187 "method" : "update",
188 "state" : self.get_state()
188 "state" : self.get_state()
189 })
189 })
190
190
191 def get_state(self, key=None):
191 def get_state(self, key=None):
192 """Gets the widget state, or a piece of it.
192 """Gets the widget state, or a piece of it.
193
193
194 Parameters
194 Parameters
195 ----------
195 ----------
196 key : unicode (optional)
196 key : unicode (optional)
197 A single property's name to get.
197 A single property's name to get.
198 """
198 """
199 keys = self.keys if key is None else [key]
199 keys = self.keys if key is None else [key]
200 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
200 return {k: self._pack_widgets(getattr(self, k)) for k in keys}
201
201
202 def send(self, content):
202 def send(self, content):
203 """Sends a custom msg to the widget model in the front-end.
203 """Sends a custom msg to the widget model in the front-end.
204
204
205 Parameters
205 Parameters
206 ----------
206 ----------
207 content : dict
207 content : dict
208 Content of the message to send.
208 Content of the message to send.
209 """
209 """
210 self._send({"method": "custom", "content": content})
210 self._send({"method": "custom", "content": content})
211
211
212 def on_msg(self, callback, remove=False):
212 def on_msg(self, callback, remove=False):
213 """(Un)Register a custom msg receive callback.
213 """(Un)Register a custom msg receive callback.
214
214
215 Parameters
215 Parameters
216 ----------
216 ----------
217 callback: callable
217 callback: callable
218 callback will be passed two arguments when a message arrives::
218 callback will be passed two arguments when a message arrives::
219
219
220 callback(widget, content)
220 callback(widget, content)
221
221
222 remove: bool
222 remove: bool
223 True if the callback should be unregistered."""
223 True if the callback should be unregistered."""
224 self._msg_callbacks.register_callback(callback, remove=remove)
224 self._msg_callbacks.register_callback(callback, remove=remove)
225
225
226 def on_displayed(self, callback, remove=False):
226 def on_displayed(self, callback, remove=False):
227 """(Un)Register a widget displayed callback.
227 """(Un)Register a widget displayed callback.
228
228
229 Parameters
229 Parameters
230 ----------
230 ----------
231 callback: method handler
231 callback: method handler
232 Must have a signature of::
232 Must have a signature of::
233
233
234 callback(widget, **kwargs)
234 callback(widget, **kwargs)
235
235
236 kwargs from display are passed through without modification.
236 kwargs from display are passed through without modification.
237 remove: bool
237 remove: bool
238 True if the callback should be unregistered."""
238 True if the callback should be unregistered."""
239 self._display_callbacks.register_callback(callback, remove=remove)
239 self._display_callbacks.register_callback(callback, remove=remove)
240
240
241 #-------------------------------------------------------------------------
241 #-------------------------------------------------------------------------
242 # Support methods
242 # Support methods
243 #-------------------------------------------------------------------------
243 #-------------------------------------------------------------------------
244 @contextmanager
244 @contextmanager
245 def _lock_property(self, key, value):
245 def _lock_property(self, key, value):
246 """Lock a property-value pair.
246 """Lock a property-value pair.
247
247
248 NOTE: This, in addition to the single lock for all state changes, is
248 NOTE: This, in addition to the single lock for all state changes, is
249 flawed. In the future we may want to look into buffering state changes
249 flawed. In the future we may want to look into buffering state changes
250 back to the front-end."""
250 back to the front-end."""
251 self._property_lock = (key, value)
251 self._property_lock = (key, value)
252 try:
252 try:
253 yield
253 yield
254 finally:
254 finally:
255 self._property_lock = (None, None)
255 self._property_lock = (None, None)
256
256
257 def _should_send_property(self, key, value):
257 def _should_send_property(self, key, value):
258 """Check the property lock (property_lock)"""
258 """Check the property lock (property_lock)"""
259 return key != self._property_lock[0] or \
259 return key != self._property_lock[0] or \
260 value != self._property_lock[1]
260 value != self._property_lock[1]
261
261
262 # Event handlers
262 # Event handlers
263 @_show_traceback
263 @_show_traceback
264 def _handle_msg(self, msg):
264 def _handle_msg(self, msg):
265 """Called when a msg is received from the front-end"""
265 """Called when a msg is received from the front-end"""
266 data = msg['content']['data']
266 data = msg['content']['data']
267 method = data['method']
267 method = data['method']
268 if not method in ['backbone', 'custom']:
268 if not method in ['backbone', 'custom']:
269 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
269 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
270
270
271 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
271 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
272 if method == 'backbone' and 'sync_data' in data:
272 if method == 'backbone' and 'sync_data' in data:
273 sync_data = data['sync_data']
273 sync_data = data['sync_data']
274 self._handle_receive_state(sync_data) # handles all methods
274 self._handle_receive_state(sync_data) # handles all methods
275
275
276 # Handle a custom msg from the front-end
276 # Handle a custom msg from the front-end
277 elif method == 'custom':
277 elif method == 'custom':
278 if 'content' in data:
278 if 'content' in data:
279 self._handle_custom_msg(data['content'])
279 self._handle_custom_msg(data['content'])
280
280
281 def _handle_receive_state(self, sync_data):
281 def _handle_receive_state(self, sync_data):
282 """Called when a state is received from the front-end."""
282 """Called when a state is received from the front-end."""
283 for name in self.keys:
283 for name in self.keys:
284 if name in sync_data:
284 if name in sync_data:
285 value = self._unpack_widgets(sync_data[name])
285 value = self._unpack_widgets(sync_data[name])
286 with self._lock_property(name, value):
286 with self._lock_property(name, value):
287 setattr(self, name, value)
287 setattr(self, name, value)
288
288
289 def _handle_custom_msg(self, content):
289 def _handle_custom_msg(self, content):
290 """Called when a custom msg is received."""
290 """Called when a custom msg is received."""
291 self._msg_callbacks(self, content)
291 self._msg_callbacks(self, content)
292
292
293 def _handle_property_changed(self, name, old, new):
293 def _handle_property_changed(self, name, old, new):
294 """Called when a property has been changed."""
294 """Called when a property has been changed."""
295 # Make sure this isn't information that the front-end just sent us.
295 # Make sure this isn't information that the front-end just sent us.
296 if self._should_send_property(name, new):
296 if self._should_send_property(name, new):
297 # Send new state to front-end
297 # Send new state to front-end
298 self.send_state(key=name)
298 self.send_state(key=name)
299
299
300 def _handle_displayed(self, **kwargs):
300 def _handle_displayed(self, **kwargs):
301 """Called when a view has been displayed for this widget instance"""
301 """Called when a view has been displayed for this widget instance"""
302 self._display_callbacks(self, **kwargs)
302 self._display_callbacks(self, **kwargs)
303
303
304 def _pack_widgets(self, x):
304 def _pack_widgets(self, x):
305 """Recursively converts all widget instances to model id strings.
305 """Recursively converts all widget instances to model id strings.
306
306
307 Children widgets will be stored and transmitted to the front-end by
307 Children widgets will be stored and transmitted to the front-end by
308 their model ids. Return value must be JSON-able."""
308 their model ids. Return value must be JSON-able."""
309 if isinstance(x, dict):
309 if isinstance(x, dict):
310 return {k: self._pack_widgets(v) for k, v in x.items()}
310 return {k: self._pack_widgets(v) for k, v in x.items()}
311 elif isinstance(x, list):
311 elif isinstance(x, (list, tuple)):
312 return [self._pack_widgets(v) for v in x]
312 return [self._pack_widgets(v) for v in x]
313 elif isinstance(x, Widget):
313 elif isinstance(x, Widget):
314 return x.model_id
314 return x.model_id
315 else:
315 else:
316 return x # Value must be JSON-able
316 return x # Value must be JSON-able
317
317
318 def _unpack_widgets(self, x):
318 def _unpack_widgets(self, x):
319 """Recursively converts all model id strings to widget instances.
319 """Recursively converts all model id strings to widget instances.
320
320
321 Children widgets will be stored and transmitted to the front-end by
321 Children widgets will be stored and transmitted to the front-end by
322 their model ids."""
322 their model ids."""
323 if isinstance(x, dict):
323 if isinstance(x, dict):
324 return {k: self._unpack_widgets(v) for k, v in x.items()}
324 return {k: self._unpack_widgets(v) for k, v in x.items()}
325 elif isinstance(x, list):
325 elif isinstance(x, (list, tuple)):
326 return [self._unpack_widgets(v) for v in x]
326 return [self._unpack_widgets(v) for v in x]
327 elif isinstance(x, string_types):
327 elif isinstance(x, string_types):
328 return x if x not in Widget.widgets else Widget.widgets[x]
328 return x if x not in Widget.widgets else Widget.widgets[x]
329 else:
329 else:
330 return x
330 return x
331
331
332 def _ipython_display_(self, **kwargs):
332 def _ipython_display_(self, **kwargs):
333 """Called when `IPython.display.display` is called on the widget."""
333 """Called when `IPython.display.display` is called on the widget."""
334 # Show view. By sending a display message, the comm is opened and the
334 # Show view. By sending a display message, the comm is opened and the
335 # initial state is sent.
335 # initial state is sent.
336 self._send({"method": "display"})
336 self._send({"method": "display"})
337 self._handle_displayed(**kwargs)
337 self._handle_displayed(**kwargs)
338
338
339 def _send(self, msg):
339 def _send(self, msg):
340 """Sends a message to the model in the front-end."""
340 """Sends a message to the model in the front-end."""
341 self.comm.send(msg)
341 self.comm.send(msg)
342
342
343
343
344 class DOMWidget(Widget):
344 class DOMWidget(Widget):
345 visible = Bool(True, help="Whether the widget is visible.", sync=True)
345 visible = Bool(True, help="Whether the widget is visible.", sync=True)
346 _css = Dict(sync=True) # Internal CSS property dict
346 _css = Dict(sync=True) # Internal CSS property dict
347
347
348 def get_css(self, key, selector=""):
348 def get_css(self, key, selector=""):
349 """Get a CSS property of the widget.
349 """Get a CSS property of the widget.
350
350
351 Note: This function does not actually request the CSS from the
351 Note: This function does not actually request the CSS from the
352 front-end; Only properties that have been set with set_css can be read.
352 front-end; Only properties that have been set with set_css can be read.
353
353
354 Parameters
354 Parameters
355 ----------
355 ----------
356 key: unicode
356 key: unicode
357 CSS key
357 CSS key
358 selector: unicode (optional)
358 selector: unicode (optional)
359 JQuery selector used when the CSS key/value was set.
359 JQuery selector used when the CSS key/value was set.
360 """
360 """
361 if selector in self._css and key in self._css[selector]:
361 if selector in self._css and key in self._css[selector]:
362 return self._css[selector][key]
362 return self._css[selector][key]
363 else:
363 else:
364 return None
364 return None
365
365
366 def set_css(self, dict_or_key, value=None, selector=''):
366 def set_css(self, dict_or_key, value=None, selector=''):
367 """Set one or more CSS properties of the widget.
367 """Set one or more CSS properties of the widget.
368
368
369 This function has two signatures:
369 This function has two signatures:
370 - set_css(css_dict, selector='')
370 - set_css(css_dict, selector='')
371 - set_css(key, value, selector='')
371 - set_css(key, value, selector='')
372
372
373 Parameters
373 Parameters
374 ----------
374 ----------
375 css_dict : dict
375 css_dict : dict
376 CSS key/value pairs to apply
376 CSS key/value pairs to apply
377 key: unicode
377 key: unicode
378 CSS key
378 CSS key
379 value:
379 value:
380 CSS value
380 CSS value
381 selector: unicode (optional, kwarg only)
381 selector: unicode (optional, kwarg only)
382 JQuery selector to use to apply the CSS key/value. If no selector
382 JQuery selector to use to apply the CSS key/value. If no selector
383 is provided, an empty selector is used. An empty selector makes the
383 is provided, an empty selector is used. An empty selector makes the
384 front-end try to apply the css to a default element. The default
384 front-end try to apply the css to a default element. The default
385 element is an attribute unique to each view, which is a DOM element
385 element is an attribute unique to each view, which is a DOM element
386 of the view that should be styled with common CSS (see
386 of the view that should be styled with common CSS (see
387 `$el_to_style` in the Javascript code).
387 `$el_to_style` in the Javascript code).
388 """
388 """
389 if not selector in self._css:
389 if not selector in self._css:
390 self._css[selector] = {}
390 self._css[selector] = {}
391 my_css = self._css[selector]
391 my_css = self._css[selector]
392
392
393 if value is None:
393 if value is None:
394 css_dict = dict_or_key
394 css_dict = dict_or_key
395 else:
395 else:
396 css_dict = {dict_or_key: value}
396 css_dict = {dict_or_key: value}
397
397
398 for (key, value) in css_dict.items():
398 for (key, value) in css_dict.items():
399 if not (key in my_css and value == my_css[key]):
399 if not (key in my_css and value == my_css[key]):
400 my_css[key] = value
400 my_css[key] = value
401 self.send_state('_css')
401 self.send_state('_css')
402
402
403 def add_class(self, class_names, selector=""):
403 def add_class(self, class_names, selector=""):
404 """Add class[es] to a DOM element.
404 """Add class[es] to a DOM element.
405
405
406 Parameters
406 Parameters
407 ----------
407 ----------
408 class_names: unicode or list
408 class_names: unicode or list
409 Class name(s) to add to the DOM element(s).
409 Class name(s) to add to the DOM element(s).
410 selector: unicode (optional)
410 selector: unicode (optional)
411 JQuery selector to select the DOM element(s) that the class(es) will
411 JQuery selector to select the DOM element(s) that the class(es) will
412 be added to.
412 be added to.
413 """
413 """
414 class_list = class_names
414 class_list = class_names
415 if isinstance(class_list, list):
415 if isinstance(class_list, (list, tuple)):
416 class_list = ' '.join(class_list)
416 class_list = ' '.join(class_list)
417
417
418 self.send({
418 self.send({
419 "msg_type" : "add_class",
419 "msg_type" : "add_class",
420 "class_list" : class_list,
420 "class_list" : class_list,
421 "selector" : selector
421 "selector" : selector
422 })
422 })
423
423
424 def remove_class(self, class_names, selector=""):
424 def remove_class(self, class_names, selector=""):
425 """Remove class[es] from a DOM element.
425 """Remove class[es] from a DOM element.
426
426
427 Parameters
427 Parameters
428 ----------
428 ----------
429 class_names: unicode or list
429 class_names: unicode or list
430 Class name(s) to remove from the DOM element(s).
430 Class name(s) to remove from the DOM element(s).
431 selector: unicode (optional)
431 selector: unicode (optional)
432 JQuery selector to select the DOM element(s) that the class(es) will
432 JQuery selector to select the DOM element(s) that the class(es) will
433 be removed from.
433 be removed from.
434 """
434 """
435 class_list = class_names
435 class_list = class_names
436 if isinstance(class_list, list):
436 if isinstance(class_list, (list, tuple)):
437 class_list = ' '.join(class_list)
437 class_list = ' '.join(class_list)
438
438
439 self.send({
439 self.send({
440 "msg_type" : "remove_class",
440 "msg_type" : "remove_class",
441 "class_list" : class_list,
441 "class_list" : class_list,
442 "selector" : selector,
442 "selector" : selector,
443 })
443 })
@@ -1,34 +1,34 b''
1 """BoolWidget class.
1 """BoolWidget class.
2
2
3 Represents a boolean using a widget.
3 Represents a boolean using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, Bool, List
17 from IPython.utils.traitlets import Unicode, Bool
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class _BoolWidget(DOMWidget):
22 class _BoolWidget(DOMWidget):
23 value = Bool(False, help="Bool value", sync=True)
23 value = Bool(False, help="Bool value", sync=True)
24 description = Unicode('', help="Description of the boolean (label).", sync=True)
24 description = Unicode('', help="Description of the boolean (label).", sync=True)
25 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
25 disabled = Bool(False, help="Enable or disable user changes.", sync=True)
26
26
27
27
28 class CheckboxWidget(_BoolWidget):
28 class CheckboxWidget(_BoolWidget):
29 _view_name = Unicode('CheckboxView', sync=True)
29 _view_name = Unicode('CheckboxView', sync=True)
30
30
31
31
32 class ToggleButtonWidget(_BoolWidget):
32 class ToggleButtonWidget(_BoolWidget):
33 _view_name = Unicode('ToggleButtonView', sync=True)
33 _view_name = Unicode('ToggleButtonView', sync=True)
34 No newline at end of file
34
@@ -1,51 +1,51 b''
1 """ContainerWidget class.
1 """ContainerWidget class.
2
2
3 Represents a container that can be used to group other widgets.
3 Represents a container that can be used to group other widgets.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, Bool, List, Instance
17 from IPython.utils.traitlets import Unicode, CTuple, Instance
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class ContainerWidget(DOMWidget):
22 class ContainerWidget(DOMWidget):
23 _view_name = Unicode('ContainerView', sync=True)
23 _view_name = Unicode('ContainerView', sync=True)
24
24
25 # Keys, all private and managed by helper methods. Flexible box model
25 # Keys, all private and managed by helper methods. Flexible box model
26 # classes...
26 # classes...
27 children = List(Instance(DOMWidget))
27 children = CTuple(Instance(DOMWidget))
28 _children = List(Instance(DOMWidget), sync=True)
28 _children = CTuple(Instance(DOMWidget), sync=True)
29
29
30 def _children_changed(self, name, old, new):
30 def _children_changed(self, name, old, new):
31 """Validate children list.
31 """Validate children list.
32
32
33 Makes sure only one instance of any given model can exist in the
33 Makes sure only one instance of any given model can exist in the
34 children list.
34 children list.
35 An excellent post on uniqifiers is available at
35 An excellent post on uniqifiers is available at
36 http://www.peterbe.com/plog/uniqifiers-benchmark
36 http://www.peterbe.com/plog/uniqifiers-benchmark
37 which provides the inspiration for using this implementation. Below
37 which provides the inspiration for using this implementation. Below
38 I've implemented the `f5` algorithm using Python comprehensions."""
38 I've implemented the `f5` algorithm using Python comprehensions."""
39 if new is not None and isinstance(new, list):
39 if new is not None and isinstance(new, list):
40 seen = {}
40 seen = {}
41 def add_item(i):
41 def add_item(i):
42 seen[i.model_id] = True
42 seen[i.model_id] = True
43 return i
43 return i
44 self._children = [add_item(i) for i in new if not i.model_id in seen]
44 self._children = [add_item(i) for i in new if not i.model_id in seen]
45
45
46
46
47 class PopupWidget(ContainerWidget):
47 class PopupWidget(ContainerWidget):
48 _view_name = Unicode('PopupView', sync=True)
48 _view_name = Unicode('PopupView', sync=True)
49
49
50 description = Unicode(sync=True)
50 description = Unicode(sync=True)
51 button_text = Unicode(sync=True)
51 button_text = Unicode(sync=True)
@@ -1,60 +1,60 b''
1 """FloatWidget class.
1 """FloatWidget class.
2
2
3 Represents an unbounded float using a widget.
3 Represents an unbounded float using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, CFloat, Bool, List, Enum
17 from IPython.utils.traitlets import Unicode, CFloat, Bool, Enum
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class _FloatWidget(DOMWidget):
22 class _FloatWidget(DOMWidget):
23 value = CFloat(0.0, help="Float value", sync=True)
23 value = CFloat(0.0, help="Float value", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
26
26
27
27
28 class _BoundedFloatWidget(_FloatWidget):
28 class _BoundedFloatWidget(_FloatWidget):
29 max = CFloat(100.0, help="Max value", sync=True)
29 max = CFloat(100.0, help="Max value", sync=True)
30 min = CFloat(0.0, help="Min value", sync=True)
30 min = CFloat(0.0, help="Min value", sync=True)
31 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
31 step = CFloat(0.1, help="Minimum step that the value can take (ignored by some views)", sync=True)
32
32
33 def __init__(self, *pargs, **kwargs):
33 def __init__(self, *pargs, **kwargs):
34 """Constructor"""
34 """Constructor"""
35 DOMWidget.__init__(self, *pargs, **kwargs)
35 DOMWidget.__init__(self, *pargs, **kwargs)
36 self.on_trait_change(self._validate, ['value', 'min', 'max'])
36 self.on_trait_change(self._validate, ['value', 'min', 'max'])
37
37
38 def _validate(self, name, old, new):
38 def _validate(self, name, old, new):
39 """Validate value, max, min."""
39 """Validate value, max, min."""
40 if self.min > new or new > self.max:
40 if self.min > new or new > self.max:
41 self.value = min(max(new, self.min), self.max)
41 self.value = min(max(new, self.min), self.max)
42
42
43
43
44 class FloatTextWidget(_FloatWidget):
44 class FloatTextWidget(_FloatWidget):
45 _view_name = Unicode('FloatTextView', sync=True)
45 _view_name = Unicode('FloatTextView', sync=True)
46
46
47
47
48 class BoundedFloatTextWidget(_BoundedFloatWidget):
48 class BoundedFloatTextWidget(_BoundedFloatWidget):
49 _view_name = Unicode('FloatTextView', sync=True)
49 _view_name = Unicode('FloatTextView', sync=True)
50
50
51
51
52 class FloatSliderWidget(_BoundedFloatWidget):
52 class FloatSliderWidget(_BoundedFloatWidget):
53 _view_name = Unicode('FloatSliderView', sync=True)
53 _view_name = Unicode('FloatSliderView', sync=True)
54 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
54 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
55 help="Vertical or horizontal.", sync=True)
55 help="Vertical or horizontal.", sync=True)
56 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
56 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
57
57
58
58
59 class FloatProgressWidget(_BoundedFloatWidget):
59 class FloatProgressWidget(_BoundedFloatWidget):
60 _view_name = Unicode('ProgressView', sync=True)
60 _view_name = Unicode('ProgressView', sync=True)
@@ -1,60 +1,60 b''
1 """IntWidget class.
1 """IntWidget class.
2
2
3 Represents an unbounded int using a widget.
3 Represents an unbounded int using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget
16 from .widget import DOMWidget
17 from IPython.utils.traitlets import Unicode, CInt, Bool, List, Enum
17 from IPython.utils.traitlets import Unicode, CInt, Bool, Enum
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class _IntWidget(DOMWidget):
22 class _IntWidget(DOMWidget):
23 value = CInt(0, help="Int value", sync=True)
23 value = CInt(0, help="Int value", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
26
26
27
27
28 class _BoundedIntWidget(_IntWidget):
28 class _BoundedIntWidget(_IntWidget):
29 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
29 step = CInt(1, help="Minimum step that the value can take (ignored by some views)", sync=True)
30 max = CInt(100, help="Max value", sync=True)
30 max = CInt(100, help="Max value", sync=True)
31 min = CInt(0, help="Min value", sync=True)
31 min = CInt(0, help="Min value", sync=True)
32
32
33 def __init__(self, *pargs, **kwargs):
33 def __init__(self, *pargs, **kwargs):
34 """Constructor"""
34 """Constructor"""
35 DOMWidget.__init__(self, *pargs, **kwargs)
35 DOMWidget.__init__(self, *pargs, **kwargs)
36 self.on_trait_change(self._validate, ['value', 'min', 'max'])
36 self.on_trait_change(self._validate, ['value', 'min', 'max'])
37
37
38 def _validate(self, name, old, new):
38 def _validate(self, name, old, new):
39 """Validate value, max, min."""
39 """Validate value, max, min."""
40 if self.min > new or new > self.max:
40 if self.min > new or new > self.max:
41 self.value = min(max(new, self.min), self.max)
41 self.value = min(max(new, self.min), self.max)
42
42
43
43
44 class IntTextWidget(_IntWidget):
44 class IntTextWidget(_IntWidget):
45 _view_name = Unicode('IntTextView', sync=True)
45 _view_name = Unicode('IntTextView', sync=True)
46
46
47
47
48 class BoundedIntTextWidget(_BoundedIntWidget):
48 class BoundedIntTextWidget(_BoundedIntWidget):
49 _view_name = Unicode('IntTextView', sync=True)
49 _view_name = Unicode('IntTextView', sync=True)
50
50
51
51
52 class IntSliderWidget(_BoundedIntWidget):
52 class IntSliderWidget(_BoundedIntWidget):
53 _view_name = Unicode('IntSliderView', sync=True)
53 _view_name = Unicode('IntSliderView', sync=True)
54 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
54 orientation = Enum([u'horizontal', u'vertical'], u'horizontal',
55 help="Vertical or horizontal.", sync=True)
55 help="Vertical or horizontal.", sync=True)
56 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
56 readout = Bool(True, help="Display the current value of the slider next to it.", sync=True)
57
57
58
58
59 class IntProgressWidget(_BoundedIntWidget):
59 class IntProgressWidget(_BoundedIntWidget):
60 _view_name = Unicode('ProgressView', sync=True)
60 _view_name = Unicode('ProgressView', sync=True)
@@ -1,58 +1,58 b''
1 """SelectionContainerWidget class.
1 """SelectionContainerWidget class.
2
2
3 Represents a multipage container that can be used to group other widgets into
3 Represents a multipage container that can be used to group other widgets into
4 pages.
4 pages.
5 """
5 """
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Copyright (c) 2013, the IPython Development Team.
7 # Copyright (c) 2013, the IPython Development Team.
8 #
8 #
9 # Distributed under the terms of the Modified BSD License.
9 # Distributed under the terms of the Modified BSD License.
10 #
10 #
11 # The full license is in the file COPYING.txt, distributed with this software.
11 # The full license is in the file COPYING.txt, distributed with this software.
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Imports
15 # Imports
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17 from .widget_container import ContainerWidget
17 from .widget_container import ContainerWidget
18 from IPython.utils.traitlets import Unicode, Dict, CInt, List, Instance
18 from IPython.utils.traitlets import Unicode, Dict, CInt
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Classes
21 # Classes
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23 class _SelectionContainerWidget(ContainerWidget):
23 class _SelectionContainerWidget(ContainerWidget):
24 _titles = Dict(help="Titles of the pages", sync=True)
24 _titles = Dict(help="Titles of the pages", sync=True)
25 selected_index = CInt(0, sync=True)
25 selected_index = CInt(0, sync=True)
26
26
27 # Public methods
27 # Public methods
28 def set_title(self, index, title):
28 def set_title(self, index, title):
29 """Sets the title of a container page.
29 """Sets the title of a container page.
30
30
31 Parameters
31 Parameters
32 ----------
32 ----------
33 index : int
33 index : int
34 Index of the container page
34 Index of the container page
35 title : unicode
35 title : unicode
36 New title"""
36 New title"""
37 self._titles[index] = title
37 self._titles[index] = title
38 self.send_state('_titles')
38 self.send_state('_titles')
39
39
40 def get_title(self, index):
40 def get_title(self, index):
41 """Gets the title of a container pages.
41 """Gets the title of a container pages.
42
42
43 Parameters
43 Parameters
44 ----------
44 ----------
45 index : int
45 index : int
46 Index of the container page"""
46 Index of the container page"""
47 if index in self._titles:
47 if index in self._titles:
48 return self._titles[index]
48 return self._titles[index]
49 else:
49 else:
50 return None
50 return None
51
51
52
52
53 class AccordionWidget(_SelectionContainerWidget):
53 class AccordionWidget(_SelectionContainerWidget):
54 _view_name = Unicode('AccordionView', sync=True)
54 _view_name = Unicode('AccordionView', sync=True)
55
55
56
56
57 class TabWidget(_SelectionContainerWidget):
57 class TabWidget(_SelectionContainerWidget):
58 _view_name = Unicode('TabView', sync=True)
58 _view_name = Unicode('TabView', sync=True)
@@ -1,72 +1,72 b''
1 """StringWidget class.
1 """StringWidget class.
2
2
3 Represents a unicode string using a widget.
3 Represents a unicode string using a widget.
4 """
4 """
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6 # Copyright (c) 2013, the IPython Development Team.
6 # Copyright (c) 2013, the IPython Development Team.
7 #
7 #
8 # Distributed under the terms of the Modified BSD License.
8 # Distributed under the terms of the Modified BSD License.
9 #
9 #
10 # The full license is in the file COPYING.txt, distributed with this software.
10 # The full license is in the file COPYING.txt, distributed with this software.
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12
12
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14 # Imports
14 # Imports
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 from .widget import DOMWidget, CallbackDispatcher
16 from .widget import DOMWidget, CallbackDispatcher
17 from IPython.utils.traitlets import Unicode, Bool, List
17 from IPython.utils.traitlets import Unicode, Bool
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Classes
20 # Classes
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 class _StringWidget(DOMWidget):
22 class _StringWidget(DOMWidget):
23 value = Unicode(help="String value", sync=True)
23 value = Unicode(help="String value", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
24 disabled = Bool(False, help="Enable or disable user changes", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
25 description = Unicode(help="Description of the value this widget represents", sync=True)
26
26
27
27
28 class HTMLWidget(_StringWidget):
28 class HTMLWidget(_StringWidget):
29 _view_name = Unicode('HTMLView', sync=True)
29 _view_name = Unicode('HTMLView', sync=True)
30
30
31
31
32 class LatexWidget(_StringWidget):
32 class LatexWidget(_StringWidget):
33 _view_name = Unicode('LatexView', sync=True)
33 _view_name = Unicode('LatexView', sync=True)
34
34
35
35
36 class TextareaWidget(_StringWidget):
36 class TextareaWidget(_StringWidget):
37 _view_name = Unicode('TextareaView', sync=True)
37 _view_name = Unicode('TextareaView', sync=True)
38
38
39 def scroll_to_bottom(self):
39 def scroll_to_bottom(self):
40 self.send({"method": "scroll_to_bottom"})
40 self.send({"method": "scroll_to_bottom"})
41
41
42
42
43 class TextWidget(_StringWidget):
43 class TextWidget(_StringWidget):
44 _view_name = Unicode('TextView', sync=True)
44 _view_name = Unicode('TextView', sync=True)
45
45
46 def __init__(self, **kwargs):
46 def __init__(self, **kwargs):
47 super(TextWidget, self).__init__(**kwargs)
47 super(TextWidget, self).__init__(**kwargs)
48 self._submission_callbacks = CallbackDispatcher()
48 self._submission_callbacks = CallbackDispatcher()
49 self.on_msg(self._handle_string_msg)
49 self.on_msg(self._handle_string_msg)
50
50
51 def _handle_string_msg(self, _, content):
51 def _handle_string_msg(self, _, content):
52 """Handle a msg from the front-end.
52 """Handle a msg from the front-end.
53
53
54 Parameters
54 Parameters
55 ----------
55 ----------
56 content: dict
56 content: dict
57 Content of the msg."""
57 Content of the msg."""
58 if content.get('event', '') == 'submit':
58 if content.get('event', '') == 'submit':
59 self._submission_callbacks(self)
59 self._submission_callbacks(self)
60
60
61 def on_submit(self, callback, remove=False):
61 def on_submit(self, callback, remove=False):
62 """(Un)Register a callback to handle text submission.
62 """(Un)Register a callback to handle text submission.
63
63
64 Triggered when the user clicks enter.
64 Triggered when the user clicks enter.
65
65
66 Parameters
66 Parameters
67 ----------
67 ----------
68 callback: callable
68 callback: callable
69 Will be called with exactly one argument: the Widget instance
69 Will be called with exactly one argument: the Widget instance
70 remove: bool (optional)
70 remove: bool (optional)
71 Whether to unregister the callback"""
71 Whether to unregister the callback"""
72 self._submission_callbacks.register_callback(callback, remove=remove)
72 self._submission_callbacks.register_callback(callback, remove=remove)
General Comments 0
You need to be logged in to leave comments. Login now