##// END OF EJS Templates
setting _comm to None
Sylvain Corlay -
Show More
@@ -1,448 +1,449 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 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
105 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
106 front-end can send before receiving an idle msg from the back-end.""")
106 front-end can send before receiving an idle msg from the back-end.""")
107
107
108 keys = List()
108 keys = List()
109 def _keys_default(self):
109 def _keys_default(self):
110 return [name for name in self.traits(sync=True)]
110 return [name for name in self.traits(sync=True)]
111
111
112 _property_lock = Tuple((None, None))
112 _property_lock = Tuple((None, None))
113
113
114 _display_callbacks = Instance(CallbackDispatcher, ())
114 _display_callbacks = Instance(CallbackDispatcher, ())
115 _msg_callbacks = Instance(CallbackDispatcher, ())
115 _msg_callbacks = Instance(CallbackDispatcher, ())
116
116
117 #-------------------------------------------------------------------------
117 #-------------------------------------------------------------------------
118 # (Con/de)structor
118 # (Con/de)structor
119 #-------------------------------------------------------------------------
119 #-------------------------------------------------------------------------
120 def __init__(self, **kwargs):
120 def __init__(self, **kwargs):
121 """Public constructor"""
121 """Public constructor"""
122 super(Widget, self).__init__(**kwargs)
122 super(Widget, self).__init__(**kwargs)
123
123
124 self.on_trait_change(self._handle_property_changed, self.keys)
124 self.on_trait_change(self._handle_property_changed, self.keys)
125 Widget._call_widget_constructed(self)
125 Widget._call_widget_constructed(self)
126
126
127 def __del__(self):
127 def __del__(self):
128 """Object disposal"""
128 """Object disposal"""
129 self.close()
129 self.close()
130
130
131 #-------------------------------------------------------------------------
131 #-------------------------------------------------------------------------
132 # Properties
132 # Properties
133 #-------------------------------------------------------------------------
133 #-------------------------------------------------------------------------
134
134
135 @property
135 @property
136 def comm(self):
136 def comm(self):
137 """Gets the Comm associated with this widget.
137 """Gets the Comm associated with this widget.
138
138
139 If a Comm doesn't exist yet, a Comm will be created automagically."""
139 If a Comm doesn't exist yet, a Comm will be created automagically."""
140 if self._comm is None:
140 if self._comm is None:
141 # Create a comm.
141 # Create a comm.
142 self._comm = Comm(target_name=self._model_name)
142 self._comm = Comm(target_name=self._model_name)
143 self._comm.on_msg(self._handle_msg)
143 self._comm.on_msg(self._handle_msg)
144 Widget.widgets[self.model_id] = self
144 Widget.widgets[self.model_id] = self
145
145
146 # first update
146 # first update
147 self.send_state()
147 self.send_state()
148 return self._comm
148 return self._comm
149
149
150 @property
150 @property
151 def model_id(self):
151 def model_id(self):
152 """Gets the model id of this widget.
152 """Gets the model id of this widget.
153
153
154 If a Comm doesn't exist yet, a Comm will be created automagically."""
154 If a Comm doesn't exist yet, a Comm will be created automagically."""
155 return self.comm.comm_id
155 return self.comm.comm_id
156
156
157 #-------------------------------------------------------------------------
157 #-------------------------------------------------------------------------
158 # Methods
158 # Methods
159 #-------------------------------------------------------------------------
159 #-------------------------------------------------------------------------
160
160
161 def close(self):
161 def close(self):
162 """Close method.
162 """Close method.
163
163
164 Closes the underlying comm.
164 Closes the underlying comm.
165 When the comm is closed, all of the widget views are automatically
165 When the comm is closed, all of the widget views are automatically
166 removed from the front-end."""
166 removed from the front-end."""
167 del Widget.widgets[self.model_id]
167 del Widget.widgets[self.model_id]
168 if self._comm is not None:
168 if self._comm is not None:
169 self._comm.close()
169 self._comm.close()
170 self._comm = None
170
171
171 def send_state(self, key=None):
172 def send_state(self, key=None):
172 """Sends the widget state, or a piece of it, to the front-end.
173 """Sends the widget state, or a piece of it, to the front-end.
173
174
174 Parameters
175 Parameters
175 ----------
176 ----------
176 key : unicode (optional)
177 key : unicode (optional)
177 A single property's name to sync with the front-end.
178 A single property's name to sync with the front-end.
178 """
179 """
179 self._send({
180 self._send({
180 "method" : "update",
181 "method" : "update",
181 "state" : self.get_state()
182 "state" : self.get_state()
182 })
183 })
183
184
184 def get_state(self, key=None):
185 def get_state(self, key=None):
185 """Gets the widget state, or a piece of it.
186 """Gets the widget state, or a piece of it.
186
187
187 Parameters
188 Parameters
188 ----------
189 ----------
189 key : unicode (optional)
190 key : unicode (optional)
190 A single property's name to get.
191 A single property's name to get.
191 """
192 """
192 keys = self.keys if key is None else [key]
193 keys = self.keys if key is None else [key]
193 state = {}
194 state = {}
194 for k in keys:
195 for k in keys:
195 f = self.trait_metadata(k, 'to_json')
196 f = self.trait_metadata(k, 'to_json')
196 if f is None:
197 if f is None:
197 f = self._trait_to_json
198 f = self._trait_to_json
198 value = getattr(self, k)
199 value = getattr(self, k)
199 state[k] = f(value)
200 state[k] = f(value)
200 return state
201 return state
201
202
202 def send(self, content):
203 def send(self, content):
203 """Sends a custom msg to the widget model in the front-end.
204 """Sends a custom msg to the widget model in the front-end.
204
205
205 Parameters
206 Parameters
206 ----------
207 ----------
207 content : dict
208 content : dict
208 Content of the message to send.
209 Content of the message to send.
209 """
210 """
210 self._send({"method": "custom", "content": content})
211 self._send({"method": "custom", "content": content})
211
212
212 def on_msg(self, callback, remove=False):
213 def on_msg(self, callback, remove=False):
213 """(Un)Register a custom msg receive callback.
214 """(Un)Register a custom msg receive callback.
214
215
215 Parameters
216 Parameters
216 ----------
217 ----------
217 callback: callable
218 callback: callable
218 callback will be passed two arguments when a message arrives::
219 callback will be passed two arguments when a message arrives::
219
220
220 callback(widget, content)
221 callback(widget, content)
221
222
222 remove: bool
223 remove: bool
223 True if the callback should be unregistered."""
224 True if the callback should be unregistered."""
224 self._msg_callbacks.register_callback(callback, remove=remove)
225 self._msg_callbacks.register_callback(callback, remove=remove)
225
226
226 def on_displayed(self, callback, remove=False):
227 def on_displayed(self, callback, remove=False):
227 """(Un)Register a widget displayed callback.
228 """(Un)Register a widget displayed callback.
228
229
229 Parameters
230 Parameters
230 ----------
231 ----------
231 callback: method handler
232 callback: method handler
232 Must have a signature of::
233 Must have a signature of::
233
234
234 callback(widget, **kwargs)
235 callback(widget, **kwargs)
235
236
236 kwargs from display are passed through without modification.
237 kwargs from display are passed through without modification.
237 remove: bool
238 remove: bool
238 True if the callback should be unregistered."""
239 True if the callback should be unregistered."""
239 self._display_callbacks.register_callback(callback, remove=remove)
240 self._display_callbacks.register_callback(callback, remove=remove)
240
241
241 #-------------------------------------------------------------------------
242 #-------------------------------------------------------------------------
242 # Support methods
243 # Support methods
243 #-------------------------------------------------------------------------
244 #-------------------------------------------------------------------------
244 @contextmanager
245 @contextmanager
245 def _lock_property(self, key, value):
246 def _lock_property(self, key, value):
246 """Lock a property-value pair.
247 """Lock a property-value pair.
247
248
248 NOTE: This, in addition to the single lock for all state changes, is
249 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
250 flawed. In the future we may want to look into buffering state changes
250 back to the front-end."""
251 back to the front-end."""
251 self._property_lock = (key, value)
252 self._property_lock = (key, value)
252 try:
253 try:
253 yield
254 yield
254 finally:
255 finally:
255 self._property_lock = (None, None)
256 self._property_lock = (None, None)
256
257
257 def _should_send_property(self, key, value):
258 def _should_send_property(self, key, value):
258 """Check the property lock (property_lock)"""
259 """Check the property lock (property_lock)"""
259 return key != self._property_lock[0] or \
260 return key != self._property_lock[0] or \
260 value != self._property_lock[1]
261 value != self._property_lock[1]
261
262
262 # Event handlers
263 # Event handlers
263 @_show_traceback
264 @_show_traceback
264 def _handle_msg(self, msg):
265 def _handle_msg(self, msg):
265 """Called when a msg is received from the front-end"""
266 """Called when a msg is received from the front-end"""
266 data = msg['content']['data']
267 data = msg['content']['data']
267 method = data['method']
268 method = data['method']
268 if not method in ['backbone', 'custom']:
269 if not method in ['backbone', 'custom']:
269 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
270 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
270
271
271 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
272 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
272 if method == 'backbone' and 'sync_data' in data:
273 if method == 'backbone' and 'sync_data' in data:
273 sync_data = data['sync_data']
274 sync_data = data['sync_data']
274 self._handle_receive_state(sync_data) # handles all methods
275 self._handle_receive_state(sync_data) # handles all methods
275
276
276 # Handle a custom msg from the front-end
277 # Handle a custom msg from the front-end
277 elif method == 'custom':
278 elif method == 'custom':
278 if 'content' in data:
279 if 'content' in data:
279 self._handle_custom_msg(data['content'])
280 self._handle_custom_msg(data['content'])
280
281
281 def _handle_receive_state(self, sync_data):
282 def _handle_receive_state(self, sync_data):
282 """Called when a state is received from the front-end."""
283 """Called when a state is received from the front-end."""
283 for name in self.keys:
284 for name in self.keys:
284 if name in sync_data:
285 if name in sync_data:
285 f = self.trait_metadata(name, 'from_json')
286 f = self.trait_metadata(name, 'from_json')
286 if f is None:
287 if f is None:
287 f = self._trait_from_json
288 f = self._trait_from_json
288 value = f(sync_data[name])
289 value = f(sync_data[name])
289 with self._lock_property(name, value):
290 with self._lock_property(name, value):
290 setattr(self, name, value)
291 setattr(self, name, value)
291
292
292 def _handle_custom_msg(self, content):
293 def _handle_custom_msg(self, content):
293 """Called when a custom msg is received."""
294 """Called when a custom msg is received."""
294 self._msg_callbacks(self, content)
295 self._msg_callbacks(self, content)
295
296
296 def _handle_property_changed(self, name, old, new):
297 def _handle_property_changed(self, name, old, new):
297 """Called when a property has been changed."""
298 """Called when a property has been changed."""
298 # Make sure this isn't information that the front-end just sent us.
299 # Make sure this isn't information that the front-end just sent us.
299 if self._should_send_property(name, new):
300 if self._should_send_property(name, new):
300 # Send new state to front-end
301 # Send new state to front-end
301 self.send_state(key=name)
302 self.send_state(key=name)
302
303
303 def _handle_displayed(self, **kwargs):
304 def _handle_displayed(self, **kwargs):
304 """Called when a view has been displayed for this widget instance"""
305 """Called when a view has been displayed for this widget instance"""
305 self._display_callbacks(self, **kwargs)
306 self._display_callbacks(self, **kwargs)
306
307
307 def _trait_to_json(self, x):
308 def _trait_to_json(self, x):
308 """Convert a trait value to json
309 """Convert a trait value to json
309
310
310 Traverse lists/tuples and dicts and serialize their values as well.
311 Traverse lists/tuples and dicts and serialize their values as well.
311 Replace any widgets with their model_id
312 Replace any widgets with their model_id
312 """
313 """
313 if isinstance(x, dict):
314 if isinstance(x, dict):
314 return {k: self._trait_to_json(v) for k, v in x.items()}
315 return {k: self._trait_to_json(v) for k, v in x.items()}
315 elif isinstance(x, (list, tuple)):
316 elif isinstance(x, (list, tuple)):
316 return [self._trait_to_json(v) for v in x]
317 return [self._trait_to_json(v) for v in x]
317 elif isinstance(x, Widget):
318 elif isinstance(x, Widget):
318 return "IPY_MODEL_" + x.model_id
319 return "IPY_MODEL_" + x.model_id
319 else:
320 else:
320 return x # Value must be JSON-able
321 return x # Value must be JSON-able
321
322
322 def _trait_from_json(self, x):
323 def _trait_from_json(self, x):
323 """Convert json values to objects
324 """Convert json values to objects
324
325
325 Replace any strings representing valid model id values to Widget references.
326 Replace any strings representing valid model id values to Widget references.
326 """
327 """
327 if isinstance(x, dict):
328 if isinstance(x, dict):
328 return {k: self._trait_from_json(v) for k, v in x.items()}
329 return {k: self._trait_from_json(v) for k, v in x.items()}
329 elif isinstance(x, (list, tuple)):
330 elif isinstance(x, (list, tuple)):
330 return [self._trait_from_json(v) for v in x]
331 return [self._trait_from_json(v) for v in x]
331 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
332 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
332 # we want to support having child widgets at any level in a hierarchy
333 # we want to support having child widgets at any level in a hierarchy
333 # trusting that a widget UUID will not appear out in the wild
334 # trusting that a widget UUID will not appear out in the wild
334 return Widget.widgets[x]
335 return Widget.widgets[x]
335 else:
336 else:
336 return x
337 return x
337
338
338 def _ipython_display_(self, **kwargs):
339 def _ipython_display_(self, **kwargs):
339 """Called when `IPython.display.display` is called on the widget."""
340 """Called when `IPython.display.display` is called on the widget."""
340 # Show view. By sending a display message, the comm is opened and the
341 # Show view. By sending a display message, the comm is opened and the
341 # initial state is sent.
342 # initial state is sent.
342 self._send({"method": "display"})
343 self._send({"method": "display"})
343 self._handle_displayed(**kwargs)
344 self._handle_displayed(**kwargs)
344
345
345 def _send(self, msg):
346 def _send(self, msg):
346 """Sends a message to the model in the front-end."""
347 """Sends a message to the model in the front-end."""
347 self.comm.send(msg)
348 self.comm.send(msg)
348
349
349
350
350 class DOMWidget(Widget):
351 class DOMWidget(Widget):
351 visible = Bool(True, help="Whether the widget is visible.", sync=True)
352 visible = Bool(True, help="Whether the widget is visible.", sync=True)
352 _css = List(sync=True) # Internal CSS property list: (selector, key, value)
353 _css = List(sync=True) # Internal CSS property list: (selector, key, value)
353
354
354 def get_css(self, key, selector=""):
355 def get_css(self, key, selector=""):
355 """Get a CSS property of the widget.
356 """Get a CSS property of the widget.
356
357
357 Note: This function does not actually request the CSS from the
358 Note: This function does not actually request the CSS from the
358 front-end; Only properties that have been set with set_css can be read.
359 front-end; Only properties that have been set with set_css can be read.
359
360
360 Parameters
361 Parameters
361 ----------
362 ----------
362 key: unicode
363 key: unicode
363 CSS key
364 CSS key
364 selector: unicode (optional)
365 selector: unicode (optional)
365 JQuery selector used when the CSS key/value was set.
366 JQuery selector used when the CSS key/value was set.
366 """
367 """
367 if selector in self._css and key in self._css[selector]:
368 if selector in self._css and key in self._css[selector]:
368 return self._css[selector][key]
369 return self._css[selector][key]
369 else:
370 else:
370 return None
371 return None
371
372
372 def set_css(self, dict_or_key, value=None, selector=''):
373 def set_css(self, dict_or_key, value=None, selector=''):
373 """Set one or more CSS properties of the widget.
374 """Set one or more CSS properties of the widget.
374
375
375 This function has two signatures:
376 This function has two signatures:
376 - set_css(css_dict, selector='')
377 - set_css(css_dict, selector='')
377 - set_css(key, value, selector='')
378 - set_css(key, value, selector='')
378
379
379 Parameters
380 Parameters
380 ----------
381 ----------
381 css_dict : dict
382 css_dict : dict
382 CSS key/value pairs to apply
383 CSS key/value pairs to apply
383 key: unicode
384 key: unicode
384 CSS key
385 CSS key
385 value:
386 value:
386 CSS value
387 CSS value
387 selector: unicode (optional, kwarg only)
388 selector: unicode (optional, kwarg only)
388 JQuery selector to use to apply the CSS key/value. If no selector
389 JQuery selector to use to apply the CSS key/value. If no selector
389 is provided, an empty selector is used. An empty selector makes the
390 is provided, an empty selector is used. An empty selector makes the
390 front-end try to apply the css to a default element. The default
391 front-end try to apply the css to a default element. The default
391 element is an attribute unique to each view, which is a DOM element
392 element is an attribute unique to each view, which is a DOM element
392 of the view that should be styled with common CSS (see
393 of the view that should be styled with common CSS (see
393 `$el_to_style` in the Javascript code).
394 `$el_to_style` in the Javascript code).
394 """
395 """
395 if value is None:
396 if value is None:
396 css_dict = dict_or_key
397 css_dict = dict_or_key
397 else:
398 else:
398 css_dict = {dict_or_key: value}
399 css_dict = {dict_or_key: value}
399
400
400 for (key, value) in css_dict.items():
401 for (key, value) in css_dict.items():
401 # First remove the selector/key pair from the css list if it exists.
402 # First remove the selector/key pair from the css list if it exists.
402 # Then add the selector/key pair and new value to the bottom of the
403 # Then add the selector/key pair and new value to the bottom of the
403 # list.
404 # list.
404 self._css = [x for x in self._css if not (x[0]==selector and x[1]==key)]
405 self._css = [x for x in self._css if not (x[0]==selector and x[1]==key)]
405 self._css += [(selector, key, value)]
406 self._css += [(selector, key, value)]
406 self.send_state('_css')
407 self.send_state('_css')
407
408
408 def add_class(self, class_names, selector=""):
409 def add_class(self, class_names, selector=""):
409 """Add class[es] to a DOM element.
410 """Add class[es] to a DOM element.
410
411
411 Parameters
412 Parameters
412 ----------
413 ----------
413 class_names: unicode or list
414 class_names: unicode or list
414 Class name(s) to add to the DOM element(s).
415 Class name(s) to add to the DOM element(s).
415 selector: unicode (optional)
416 selector: unicode (optional)
416 JQuery selector to select the DOM element(s) that the class(es) will
417 JQuery selector to select the DOM element(s) that the class(es) will
417 be added to.
418 be added to.
418 """
419 """
419 class_list = class_names
420 class_list = class_names
420 if isinstance(class_list, (list, tuple)):
421 if isinstance(class_list, (list, tuple)):
421 class_list = ' '.join(class_list)
422 class_list = ' '.join(class_list)
422
423
423 self.send({
424 self.send({
424 "msg_type" : "add_class",
425 "msg_type" : "add_class",
425 "class_list" : class_list,
426 "class_list" : class_list,
426 "selector" : selector
427 "selector" : selector
427 })
428 })
428
429
429 def remove_class(self, class_names, selector=""):
430 def remove_class(self, class_names, selector=""):
430 """Remove class[es] from a DOM element.
431 """Remove class[es] from a DOM element.
431
432
432 Parameters
433 Parameters
433 ----------
434 ----------
434 class_names: unicode or list
435 class_names: unicode or list
435 Class name(s) to remove from the DOM element(s).
436 Class name(s) to remove from the DOM element(s).
436 selector: unicode (optional)
437 selector: unicode (optional)
437 JQuery selector to select the DOM element(s) that the class(es) will
438 JQuery selector to select the DOM element(s) that the class(es) will
438 be removed from.
439 be removed from.
439 """
440 """
440 class_list = class_names
441 class_list = class_names
441 if isinstance(class_list, (list, tuple)):
442 if isinstance(class_list, (list, tuple)):
442 class_list = ' '.join(class_list)
443 class_list = ' '.join(class_list)
443
444
444 self.send({
445 self.send({
445 "msg_type" : "remove_class",
446 "msg_type" : "remove_class",
446 "class_list" : class_list,
447 "class_list" : class_list,
447 "selector" : selector,
448 "selector" : selector,
448 })
449 })
General Comments 0
You need to be logged in to leave comments. Login now