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