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