##// END OF EJS Templates
Merge pull request #6377 from jasongrout/fix-widget-prefix...
Jonathan Frederic -
r17846:d9696592 merge
parent child Browse files
Show More
@@ -1,478 +1,478 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.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int, Set
21 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, Tuple, Int, Set
22 from IPython.utils.py3compat import string_types
22 from IPython.utils.py3compat import string_types
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Classes
25 # Classes
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27 class CallbackDispatcher(LoggingConfigurable):
27 class CallbackDispatcher(LoggingConfigurable):
28 """A structure for registering and running callbacks"""
28 """A structure for registering and running callbacks"""
29 callbacks = List()
29 callbacks = List()
30
30
31 def __call__(self, *args, **kwargs):
31 def __call__(self, *args, **kwargs):
32 """Call all of the registered callbacks."""
32 """Call all of the registered callbacks."""
33 value = None
33 value = None
34 for callback in self.callbacks:
34 for callback in self.callbacks:
35 try:
35 try:
36 local_value = callback(*args, **kwargs)
36 local_value = callback(*args, **kwargs)
37 except Exception as e:
37 except Exception as e:
38 ip = get_ipython()
38 ip = get_ipython()
39 if ip is None:
39 if ip is None:
40 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
40 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
41 else:
41 else:
42 ip.showtraceback()
42 ip.showtraceback()
43 else:
43 else:
44 value = local_value if local_value is not None else value
44 value = local_value if local_value is not None else value
45 return value
45 return value
46
46
47 def register_callback(self, callback, remove=False):
47 def register_callback(self, callback, remove=False):
48 """(Un)Register a callback
48 """(Un)Register a callback
49
49
50 Parameters
50 Parameters
51 ----------
51 ----------
52 callback: method handle
52 callback: method handle
53 Method to be registered or unregistered.
53 Method to be registered or unregistered.
54 remove=False: bool
54 remove=False: bool
55 Whether to unregister the callback."""
55 Whether to unregister the callback."""
56
56
57 # (Un)Register the callback.
57 # (Un)Register the callback.
58 if remove and callback in self.callbacks:
58 if remove and callback in self.callbacks:
59 self.callbacks.remove(callback)
59 self.callbacks.remove(callback)
60 elif not remove and callback not in self.callbacks:
60 elif not remove and callback not in self.callbacks:
61 self.callbacks.append(callback)
61 self.callbacks.append(callback)
62
62
63 def _show_traceback(method):
63 def _show_traceback(method):
64 """decorator for showing tracebacks in IPython"""
64 """decorator for showing tracebacks in IPython"""
65 def m(self, *args, **kwargs):
65 def m(self, *args, **kwargs):
66 try:
66 try:
67 return(method(self, *args, **kwargs))
67 return(method(self, *args, **kwargs))
68 except Exception as e:
68 except Exception as e:
69 ip = get_ipython()
69 ip = get_ipython()
70 if ip is None:
70 if ip is None:
71 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
71 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
72 else:
72 else:
73 ip.showtraceback()
73 ip.showtraceback()
74 return m
74 return m
75
75
76 class Widget(LoggingConfigurable):
76 class Widget(LoggingConfigurable):
77 #-------------------------------------------------------------------------
77 #-------------------------------------------------------------------------
78 # Class attributes
78 # Class attributes
79 #-------------------------------------------------------------------------
79 #-------------------------------------------------------------------------
80 _widget_construction_callback = None
80 _widget_construction_callback = None
81 widgets = {}
81 widgets = {}
82
82
83 @staticmethod
83 @staticmethod
84 def on_widget_constructed(callback):
84 def on_widget_constructed(callback):
85 """Registers a callback to be called when a widget is constructed.
85 """Registers a callback to be called when a widget is constructed.
86
86
87 The callback must have the following signature:
87 The callback must have the following signature:
88 callback(widget)"""
88 callback(widget)"""
89 Widget._widget_construction_callback = callback
89 Widget._widget_construction_callback = callback
90
90
91 @staticmethod
91 @staticmethod
92 def _call_widget_constructed(widget):
92 def _call_widget_constructed(widget):
93 """Static method, called when a widget is constructed."""
93 """Static method, called when a widget is constructed."""
94 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
94 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
95 Widget._widget_construction_callback(widget)
95 Widget._widget_construction_callback(widget)
96
96
97 #-------------------------------------------------------------------------
97 #-------------------------------------------------------------------------
98 # Traits
98 # Traits
99 #-------------------------------------------------------------------------
99 #-------------------------------------------------------------------------
100 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
100 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
101 registered in the front-end to create and sync this widget with.""")
101 registered in the front-end to create and sync this widget with.""")
102 _view_name = Unicode('WidgetView', help="""Default view registered in the front-end
102 _view_name = Unicode('WidgetView', help="""Default view registered in the front-end
103 to use to represent the widget.""", sync=True)
103 to use to represent the widget.""", sync=True)
104 comm = Instance('IPython.kernel.comm.Comm')
104 comm = Instance('IPython.kernel.comm.Comm')
105
105
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 _send_state_lock = Int(0)
114 _send_state_lock = Int(0)
115 _states_to_send = Set(allow_none=False)
115 _states_to_send = Set(allow_none=False)
116 _display_callbacks = Instance(CallbackDispatcher, ())
116 _display_callbacks = Instance(CallbackDispatcher, ())
117 _msg_callbacks = Instance(CallbackDispatcher, ())
117 _msg_callbacks = Instance(CallbackDispatcher, ())
118
118
119 #-------------------------------------------------------------------------
119 #-------------------------------------------------------------------------
120 # (Con/de)structor
120 # (Con/de)structor
121 #-------------------------------------------------------------------------
121 #-------------------------------------------------------------------------
122 def __init__(self, **kwargs):
122 def __init__(self, **kwargs):
123 """Public constructor"""
123 """Public constructor"""
124 self._model_id = kwargs.pop('model_id', None)
124 self._model_id = kwargs.pop('model_id', None)
125 super(Widget, self).__init__(**kwargs)
125 super(Widget, self).__init__(**kwargs)
126
126
127 self.on_trait_change(self._handle_property_changed, self.keys)
127 self.on_trait_change(self._handle_property_changed, self.keys)
128 Widget._call_widget_constructed(self)
128 Widget._call_widget_constructed(self)
129 self.open()
129 self.open()
130
130
131 def __del__(self):
131 def __del__(self):
132 """Object disposal"""
132 """Object disposal"""
133 self.close()
133 self.close()
134
134
135 #-------------------------------------------------------------------------
135 #-------------------------------------------------------------------------
136 # Properties
136 # Properties
137 #-------------------------------------------------------------------------
137 #-------------------------------------------------------------------------
138
138
139 def open(self):
139 def open(self):
140 """Open a comm to the frontend if one isn't already open."""
140 """Open a comm to the frontend if one isn't already open."""
141 if self.comm is None:
141 if self.comm is None:
142 if self._model_id is None:
142 if self._model_id is None:
143 self.comm = Comm(target_name=self._model_name)
143 self.comm = Comm(target_name=self._model_name)
144 self._model_id = self.model_id
144 self._model_id = self.model_id
145 else:
145 else:
146 self.comm = Comm(target_name=self._model_name, comm_id=self._model_id)
146 self.comm = Comm(target_name=self._model_name, comm_id=self._model_id)
147 self.comm.on_msg(self._handle_msg)
147 self.comm.on_msg(self._handle_msg)
148 Widget.widgets[self.model_id] = self
148 Widget.widgets[self.model_id] = self
149
149
150 # first update
150 # first update
151 self.send_state()
151 self.send_state()
152
152
153 @property
153 @property
154 def model_id(self):
154 def model_id(self):
155 """Gets the model id of this widget.
155 """Gets the model id of this widget.
156
156
157 If a Comm doesn't exist yet, a Comm will be created automagically."""
157 If a Comm doesn't exist yet, a Comm will be created automagically."""
158 return self.comm.comm_id
158 return self.comm.comm_id
159
159
160 #-------------------------------------------------------------------------
160 #-------------------------------------------------------------------------
161 # Methods
161 # Methods
162 #-------------------------------------------------------------------------
162 #-------------------------------------------------------------------------
163
163
164 def close(self):
164 def close(self):
165 """Close method.
165 """Close method.
166
166
167 Closes the underlying comm.
167 Closes the underlying comm.
168 When the comm is closed, all of the widget views are automatically
168 When the comm is closed, all of the widget views are automatically
169 removed from the front-end."""
169 removed from the front-end."""
170 if self.comm is not None:
170 if self.comm is not None:
171 Widget.widgets.pop(self.model_id, None)
171 Widget.widgets.pop(self.model_id, None)
172 self.comm.close()
172 self.comm.close()
173 self.comm = None
173 self.comm = None
174
174
175 def send_state(self, key=None):
175 def send_state(self, key=None):
176 """Sends the widget state, or a piece of it, to the front-end.
176 """Sends the widget state, or a piece of it, to the front-end.
177
177
178 Parameters
178 Parameters
179 ----------
179 ----------
180 key : unicode, or iterable (optional)
180 key : unicode, or iterable (optional)
181 A single property's name or iterable of property names to sync with the front-end.
181 A single property's name or iterable of property names to sync with the front-end.
182 """
182 """
183 self._send({
183 self._send({
184 "method" : "update",
184 "method" : "update",
185 "state" : self.get_state(key=key)
185 "state" : self.get_state(key=key)
186 })
186 })
187
187
188 def get_state(self, key=None):
188 def get_state(self, key=None):
189 """Gets the widget state, or a piece of it.
189 """Gets the widget state, or a piece of it.
190
190
191 Parameters
191 Parameters
192 ----------
192 ----------
193 key : unicode or iterable (optional)
193 key : unicode or iterable (optional)
194 A single property's name or iterable of property names to get.
194 A single property's name or iterable of property names to get.
195 """
195 """
196 if key is None:
196 if key is None:
197 keys = self.keys
197 keys = self.keys
198 elif isinstance(key, string_types):
198 elif isinstance(key, string_types):
199 keys = [key]
199 keys = [key]
200 elif isinstance(key, collections.Iterable):
200 elif isinstance(key, collections.Iterable):
201 keys = key
201 keys = key
202 else:
202 else:
203 raise ValueError("key must be a string, an iterable of keys, or None")
203 raise ValueError("key must be a string, an iterable of keys, or None")
204 state = {}
204 state = {}
205 for k in keys:
205 for k in keys:
206 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
206 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
207 value = getattr(self, k)
207 value = getattr(self, k)
208 state[k] = f(value)
208 state[k] = f(value)
209 return state
209 return state
210
210
211 def send(self, content):
211 def send(self, content):
212 """Sends a custom msg to the widget model in the front-end.
212 """Sends a custom msg to the widget model in the front-end.
213
213
214 Parameters
214 Parameters
215 ----------
215 ----------
216 content : dict
216 content : dict
217 Content of the message to send.
217 Content of the message to send.
218 """
218 """
219 self._send({"method": "custom", "content": content})
219 self._send({"method": "custom", "content": content})
220
220
221 def on_msg(self, callback, remove=False):
221 def on_msg(self, callback, remove=False):
222 """(Un)Register a custom msg receive callback.
222 """(Un)Register a custom msg receive callback.
223
223
224 Parameters
224 Parameters
225 ----------
225 ----------
226 callback: callable
226 callback: callable
227 callback will be passed two arguments when a message arrives::
227 callback will be passed two arguments when a message arrives::
228
228
229 callback(widget, content)
229 callback(widget, content)
230
230
231 remove: bool
231 remove: bool
232 True if the callback should be unregistered."""
232 True if the callback should be unregistered."""
233 self._msg_callbacks.register_callback(callback, remove=remove)
233 self._msg_callbacks.register_callback(callback, remove=remove)
234
234
235 def on_displayed(self, callback, remove=False):
235 def on_displayed(self, callback, remove=False):
236 """(Un)Register a widget displayed callback.
236 """(Un)Register a widget displayed callback.
237
237
238 Parameters
238 Parameters
239 ----------
239 ----------
240 callback: method handler
240 callback: method handler
241 Must have a signature of::
241 Must have a signature of::
242
242
243 callback(widget, **kwargs)
243 callback(widget, **kwargs)
244
244
245 kwargs from display are passed through without modification.
245 kwargs from display are passed through without modification.
246 remove: bool
246 remove: bool
247 True if the callback should be unregistered."""
247 True if the callback should be unregistered."""
248 self._display_callbacks.register_callback(callback, remove=remove)
248 self._display_callbacks.register_callback(callback, remove=remove)
249
249
250 #-------------------------------------------------------------------------
250 #-------------------------------------------------------------------------
251 # Support methods
251 # Support methods
252 #-------------------------------------------------------------------------
252 #-------------------------------------------------------------------------
253 @contextmanager
253 @contextmanager
254 def _lock_property(self, key, value):
254 def _lock_property(self, key, value):
255 """Lock a property-value pair.
255 """Lock a property-value pair.
256
256
257 The value should be the JSON state of the property.
257 The value should be the JSON state of the property.
258
258
259 NOTE: This, in addition to the single lock for all state changes, is
259 NOTE: This, in addition to the single lock for all state changes, is
260 flawed. In the future we may want to look into buffering state changes
260 flawed. In the future we may want to look into buffering state changes
261 back to the front-end."""
261 back to the front-end."""
262 self._property_lock = (key, value)
262 self._property_lock = (key, value)
263 try:
263 try:
264 yield
264 yield
265 finally:
265 finally:
266 self._property_lock = (None, None)
266 self._property_lock = (None, None)
267
267
268 @contextmanager
268 @contextmanager
269 def hold_sync(self):
269 def hold_sync(self):
270 """Hold syncing any state until the context manager is released"""
270 """Hold syncing any state until the context manager is released"""
271 # We increment a value so that this can be nested. Syncing will happen when
271 # We increment a value so that this can be nested. Syncing will happen when
272 # all levels have been released.
272 # all levels have been released.
273 self._send_state_lock += 1
273 self._send_state_lock += 1
274 try:
274 try:
275 yield
275 yield
276 finally:
276 finally:
277 self._send_state_lock -=1
277 self._send_state_lock -=1
278 if self._send_state_lock == 0:
278 if self._send_state_lock == 0:
279 self.send_state(self._states_to_send)
279 self.send_state(self._states_to_send)
280 self._states_to_send.clear()
280 self._states_to_send.clear()
281
281
282 def _should_send_property(self, key, value):
282 def _should_send_property(self, key, value):
283 """Check the property lock (property_lock)"""
283 """Check the property lock (property_lock)"""
284 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
284 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
285 if (key == self._property_lock[0]
285 if (key == self._property_lock[0]
286 and to_json(value) == self._property_lock[1]):
286 and to_json(value) == self._property_lock[1]):
287 return False
287 return False
288 elif self._send_state_lock > 0:
288 elif self._send_state_lock > 0:
289 self._states_to_send.add(key)
289 self._states_to_send.add(key)
290 return False
290 return False
291 else:
291 else:
292 return True
292 return True
293
293
294 # Event handlers
294 # Event handlers
295 @_show_traceback
295 @_show_traceback
296 def _handle_msg(self, msg):
296 def _handle_msg(self, msg):
297 """Called when a msg is received from the front-end"""
297 """Called when a msg is received from the front-end"""
298 data = msg['content']['data']
298 data = msg['content']['data']
299 method = data['method']
299 method = data['method']
300 if not method in ['backbone', 'custom']:
300 if not method in ['backbone', 'custom']:
301 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
301 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
302
302
303 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
303 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
304 if method == 'backbone' and 'sync_data' in data:
304 if method == 'backbone' and 'sync_data' in data:
305 sync_data = data['sync_data']
305 sync_data = data['sync_data']
306 self._handle_receive_state(sync_data) # handles all methods
306 self._handle_receive_state(sync_data) # handles all methods
307
307
308 # Handle a custom msg from the front-end
308 # Handle a custom msg from the front-end
309 elif method == 'custom':
309 elif method == 'custom':
310 if 'content' in data:
310 if 'content' in data:
311 self._handle_custom_msg(data['content'])
311 self._handle_custom_msg(data['content'])
312
312
313 def _handle_receive_state(self, sync_data):
313 def _handle_receive_state(self, sync_data):
314 """Called when a state is received from the front-end."""
314 """Called when a state is received from the front-end."""
315 for name in self.keys:
315 for name in self.keys:
316 if name in sync_data:
316 if name in sync_data:
317 json_value = sync_data[name]
317 json_value = sync_data[name]
318 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
318 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
319 with self._lock_property(name, json_value):
319 with self._lock_property(name, json_value):
320 setattr(self, name, from_json(json_value))
320 setattr(self, name, from_json(json_value))
321
321
322 def _handle_custom_msg(self, content):
322 def _handle_custom_msg(self, content):
323 """Called when a custom msg is received."""
323 """Called when a custom msg is received."""
324 self._msg_callbacks(self, content)
324 self._msg_callbacks(self, content)
325
325
326 def _handle_property_changed(self, name, old, new):
326 def _handle_property_changed(self, name, old, new):
327 """Called when a property has been changed."""
327 """Called when a property has been changed."""
328 # Make sure this isn't information that the front-end just sent us.
328 # Make sure this isn't information that the front-end just sent us.
329 if self._should_send_property(name, new):
329 if self._should_send_property(name, new):
330 # Send new state to front-end
330 # Send new state to front-end
331 self.send_state(key=name)
331 self.send_state(key=name)
332
332
333 def _handle_displayed(self, **kwargs):
333 def _handle_displayed(self, **kwargs):
334 """Called when a view has been displayed for this widget instance"""
334 """Called when a view has been displayed for this widget instance"""
335 self._display_callbacks(self, **kwargs)
335 self._display_callbacks(self, **kwargs)
336
336
337 def _trait_to_json(self, x):
337 def _trait_to_json(self, x):
338 """Convert a trait value to json
338 """Convert a trait value to json
339
339
340 Traverse lists/tuples and dicts and serialize their values as well.
340 Traverse lists/tuples and dicts and serialize their values as well.
341 Replace any widgets with their model_id
341 Replace any widgets with their model_id
342 """
342 """
343 if isinstance(x, dict):
343 if isinstance(x, dict):
344 return {k: self._trait_to_json(v) for k, v in x.items()}
344 return {k: self._trait_to_json(v) for k, v in x.items()}
345 elif isinstance(x, (list, tuple)):
345 elif isinstance(x, (list, tuple)):
346 return [self._trait_to_json(v) for v in x]
346 return [self._trait_to_json(v) for v in x]
347 elif isinstance(x, Widget):
347 elif isinstance(x, Widget):
348 return "IPY_MODEL_" + x.model_id
348 return "IPY_MODEL_" + x.model_id
349 else:
349 else:
350 return x # Value must be JSON-able
350 return x # Value must be JSON-able
351
351
352 def _trait_from_json(self, x):
352 def _trait_from_json(self, x):
353 """Convert json values to objects
353 """Convert json values to objects
354
354
355 Replace any strings representing valid model id values to Widget references.
355 Replace any strings representing valid model id values to Widget references.
356 """
356 """
357 if isinstance(x, dict):
357 if isinstance(x, dict):
358 return {k: self._trait_from_json(v) for k, v in x.items()}
358 return {k: self._trait_from_json(v) for k, v in x.items()}
359 elif isinstance(x, (list, tuple)):
359 elif isinstance(x, (list, tuple)):
360 return [self._trait_from_json(v) for v in x]
360 return [self._trait_from_json(v) for v in x]
361 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
361 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
362 # we want to support having child widgets at any level in a hierarchy
362 # we want to support having child widgets at any level in a hierarchy
363 # trusting that a widget UUID will not appear out in the wild
363 # trusting that a widget UUID will not appear out in the wild
364 return Widget.widgets[x]
364 return Widget.widgets[x[10:]]
365 else:
365 else:
366 return x
366 return x
367
367
368 def _ipython_display_(self, **kwargs):
368 def _ipython_display_(self, **kwargs):
369 """Called when `IPython.display.display` is called on the widget."""
369 """Called when `IPython.display.display` is called on the widget."""
370 # Show view. By sending a display message, the comm is opened and the
370 # Show view. By sending a display message, the comm is opened and the
371 # initial state is sent.
371 # initial state is sent.
372 self._send({"method": "display"})
372 self._send({"method": "display"})
373 self._handle_displayed(**kwargs)
373 self._handle_displayed(**kwargs)
374
374
375 def _send(self, msg):
375 def _send(self, msg):
376 """Sends a message to the model in the front-end."""
376 """Sends a message to the model in the front-end."""
377 self.comm.send(msg)
377 self.comm.send(msg)
378
378
379
379
380 class DOMWidget(Widget):
380 class DOMWidget(Widget):
381 visible = Bool(True, help="Whether the widget is visible.", sync=True)
381 visible = Bool(True, help="Whether the widget is visible.", sync=True)
382 _css = List(sync=True) # Internal CSS property list: (selector, key, value)
382 _css = List(sync=True) # Internal CSS property list: (selector, key, value)
383
383
384 def get_css(self, key, selector=""):
384 def get_css(self, key, selector=""):
385 """Get a CSS property of the widget.
385 """Get a CSS property of the widget.
386
386
387 Note: This function does not actually request the CSS from the
387 Note: This function does not actually request the CSS from the
388 front-end; Only properties that have been set with set_css can be read.
388 front-end; Only properties that have been set with set_css can be read.
389
389
390 Parameters
390 Parameters
391 ----------
391 ----------
392 key: unicode
392 key: unicode
393 CSS key
393 CSS key
394 selector: unicode (optional)
394 selector: unicode (optional)
395 JQuery selector used when the CSS key/value was set.
395 JQuery selector used when the CSS key/value was set.
396 """
396 """
397 if selector in self._css and key in self._css[selector]:
397 if selector in self._css and key in self._css[selector]:
398 return self._css[selector][key]
398 return self._css[selector][key]
399 else:
399 else:
400 return None
400 return None
401
401
402 def set_css(self, dict_or_key, value=None, selector=''):
402 def set_css(self, dict_or_key, value=None, selector=''):
403 """Set one or more CSS properties of the widget.
403 """Set one or more CSS properties of the widget.
404
404
405 This function has two signatures:
405 This function has two signatures:
406 - set_css(css_dict, selector='')
406 - set_css(css_dict, selector='')
407 - set_css(key, value, selector='')
407 - set_css(key, value, selector='')
408
408
409 Parameters
409 Parameters
410 ----------
410 ----------
411 css_dict : dict
411 css_dict : dict
412 CSS key/value pairs to apply
412 CSS key/value pairs to apply
413 key: unicode
413 key: unicode
414 CSS key
414 CSS key
415 value:
415 value:
416 CSS value
416 CSS value
417 selector: unicode (optional, kwarg only)
417 selector: unicode (optional, kwarg only)
418 JQuery selector to use to apply the CSS key/value. If no selector
418 JQuery selector to use to apply the CSS key/value. If no selector
419 is provided, an empty selector is used. An empty selector makes the
419 is provided, an empty selector is used. An empty selector makes the
420 front-end try to apply the css to a default element. The default
420 front-end try to apply the css to a default element. The default
421 element is an attribute unique to each view, which is a DOM element
421 element is an attribute unique to each view, which is a DOM element
422 of the view that should be styled with common CSS (see
422 of the view that should be styled with common CSS (see
423 `$el_to_style` in the Javascript code).
423 `$el_to_style` in the Javascript code).
424 """
424 """
425 if value is None:
425 if value is None:
426 css_dict = dict_or_key
426 css_dict = dict_or_key
427 else:
427 else:
428 css_dict = {dict_or_key: value}
428 css_dict = {dict_or_key: value}
429
429
430 for (key, value) in css_dict.items():
430 for (key, value) in css_dict.items():
431 # First remove the selector/key pair from the css list if it exists.
431 # First remove the selector/key pair from the css list if it exists.
432 # Then add the selector/key pair and new value to the bottom of the
432 # Then add the selector/key pair and new value to the bottom of the
433 # list.
433 # list.
434 self._css = [x for x in self._css if not (x[0]==selector and x[1]==key)]
434 self._css = [x for x in self._css if not (x[0]==selector and x[1]==key)]
435 self._css += [(selector, key, value)]
435 self._css += [(selector, key, value)]
436 self.send_state('_css')
436 self.send_state('_css')
437
437
438 def add_class(self, class_names, selector=""):
438 def add_class(self, class_names, selector=""):
439 """Add class[es] to a DOM element.
439 """Add class[es] to a DOM element.
440
440
441 Parameters
441 Parameters
442 ----------
442 ----------
443 class_names: unicode or list
443 class_names: unicode or list
444 Class name(s) to add to the DOM element(s).
444 Class name(s) to add to the DOM element(s).
445 selector: unicode (optional)
445 selector: unicode (optional)
446 JQuery selector to select the DOM element(s) that the class(es) will
446 JQuery selector to select the DOM element(s) that the class(es) will
447 be added to.
447 be added to.
448 """
448 """
449 class_list = class_names
449 class_list = class_names
450 if isinstance(class_list, (list, tuple)):
450 if isinstance(class_list, (list, tuple)):
451 class_list = ' '.join(class_list)
451 class_list = ' '.join(class_list)
452
452
453 self.send({
453 self.send({
454 "msg_type" : "add_class",
454 "msg_type" : "add_class",
455 "class_list" : class_list,
455 "class_list" : class_list,
456 "selector" : selector
456 "selector" : selector
457 })
457 })
458
458
459 def remove_class(self, class_names, selector=""):
459 def remove_class(self, class_names, selector=""):
460 """Remove class[es] from a DOM element.
460 """Remove class[es] from a DOM element.
461
461
462 Parameters
462 Parameters
463 ----------
463 ----------
464 class_names: unicode or list
464 class_names: unicode or list
465 Class name(s) to remove from the DOM element(s).
465 Class name(s) to remove from the DOM element(s).
466 selector: unicode (optional)
466 selector: unicode (optional)
467 JQuery selector to select the DOM element(s) that the class(es) will
467 JQuery selector to select the DOM element(s) that the class(es) will
468 be removed from.
468 be removed from.
469 """
469 """
470 class_list = class_names
470 class_list = class_names
471 if isinstance(class_list, (list, tuple)):
471 if isinstance(class_list, (list, tuple)):
472 class_list = ' '.join(class_list)
472 class_list = ' '.join(class_list)
473
473
474 self.send({
474 self.send({
475 "msg_type" : "remove_class",
475 "msg_type" : "remove_class",
476 "class_list" : class_list,
476 "class_list" : class_list,
477 "selector" : selector,
477 "selector" : selector,
478 })
478 })
General Comments 0
You need to be logged in to leave comments. Login now